wc3campaigns
WC3C Homepage - www.wc3c.netUser Control Panel (Requires Log-In)Engage in discussions with other users and join contests in the WC3C forums!Read one of our many tutorials, ranging in difficulty from beginner to advanced!Show off your artistic talents in the WC3C Gallery!Download quality models, textures, spells (vJASS/JASS), systems, and scripts!Download maps that have passed through our rigorous approval process!

Go Back   Wc3C.net > Tutorials > JASS/AI scripts tutorials
User Name
Password
Register Rules Get Hosted! Chat Pastebin FAQ and Rules Members List Calendar



Reply
 
Thread Tools Search this Thread
Old 09-30-2007, 11:00 AM   #1
Silvenon
User
 
Silvenon's Avatar
 
Join Date: May 2007
Posts: 492

Submissions (1)

Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)

Send a message via MSN to Silvenon
Default Coding an efficient knockback (vJass)

INTRODUCTION + REQUIREMENTS

Knockback is probably one of the most popular things in JASS, because it can be made to be very smooth. Now, the purpose of this tutorial is how to make an efficient knockback - using vJass.

Just a good knowledge of structs is required. You can find more info on the structs in the jasshelper manual. We aren't going to use the game cache at all, and it will still be MUI.

PARAMETERS

First, let's examine our four main parameters:
  • u = the unit that is being knockbacked
  • d = the distance the unit is knockbacked to
  • a = the angle (direction) - in RADIANS
  • w = the duration of the knockback
RADIANS

Now, why am I using radians instead of degrees? Simple, so I don't have to convert degrees to radians and other way around. I have to convert degrees to radians when I'm using trigonometrical functions and I have to convert radians to degrees when I'm using Atan2() function. Example:

Collapse JASS:
function IHateDegrees takes nothing returns nothing
    local real a = 30 // degrees
    call Sin(a * bj_DEGTORAD)
    // ...
    // ...
    set a = Atan2(y, x) * bj_RADTODEG
endfunction

It's more efficient to work with radians all the time. All natives that work with angles take radians, except these two: SetUnitFacing(), GetUnitFacing(). That could be annoying sometimes, but get used to it.Now, let's actually code something here, for starters, let's make a function that will take our four main parameters:

function Knockback takes unit u, real d, real a, real w returns nothing

TIMER

The basic thing you must have when you're making a knockback code is a timer, because a knockback is moving a unit a bit a time. That timer will periodically fire a function that will move the knockbacked unit a bit. We are going to make a global timer using the global block (vJass feature), you'll see later why:

Collapse JASS:
globals
    timer Tim = CreateTimer()
endglobals

INTERVAL

Now, when having a timer, you must also have to have the interval of timer's periodic executions. This will tell the timer how fast he has to execute.

The best way is to make a constant interval, we have 2 ways of achieving that, we can:
  1. Make a global block, then create a (constant) global real which will hold the interval
  2. Make a constant function which will return the interval

There is actually no difference between those two methods afaik, so I'll choose the second one, because I want the interval to be seperated from the other globals for some reason. The question you are probably (or probably not) asking is: what interval is best to put? I, personally, use 0.035, but it seems like 0.04 is the most popular one, so we are going to use that one (you can use any you like, but recommended is somewhere between 0.03 and 0.04 so we can achieve a smooth effect):

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

NUMBER OF EXECUTIONS

Now when that is clear, we have one last thing we need to have: the number of executions. We have to calculate how many executions the timer has to perform, that is necessary for the timer to know when to stop. We will going to name that variable q and it will be an integer (makes sense). How to calculate q? That's simple, it's the duration divided by the interval. I hope you know math well enough to know why is it like that, if not, then learn. We will convert the result to integer, so we can put it in our integer variable:

Collapse JASS:
function Knockback takes unit u, real d, real a, real w returns nothing
    local integer q = R2I(w / Interval())
endfunction

What now? First, we need to create a function that will be assigned to moving the unit (that function will be executed periodically by the timer). Now we must name that function somehow:

function Knockback_Execute takes nothing returns nothing

STRUCT

The function takes nothing and returns nothing because it will be executed by the timer, and we all know that when executing a function like that, we can not transfer parameters. We have to think of a way to transfer the values from Knockback to Knockback_Execute. Hmm.... I know! Lets use structs, we will create a simple struct that will hold the values we need to transfer:

Collapse JASS:
struct Knockback_Data
    unit u
    real a
endstruct

And let's create a global array of Knockback_Datas (again, later you'll see why):

Collapse JASS:
globals
    timer Tim = CreateTimer()
    Knockback_Data array Ar
endglobals

What about the rest of the parameters? Well, this part gets a little complicated, but don't lose hope! We're going to go through this together.

A knockback is a process in which the unit is pushed back and then it is slowing down until it reaches the speed of 0, in other words, until it stops.

So we have to have create two new variables:
  • d1 = the "bit" in "moving a unit a bit a time"
  • d2 = will decrease d1 with each execution

CALCULATIONS

The variable d2 is required because it will decrease the d1 until it reaches 0 (or slightly less than that), that will cause deceleration in unit movement and then the unit will eventually stop. Now we'll gonna do some calculations, which is a little advanced math, but don't worry, I'll try to explain everything. I suggest you take a paper and a pencil/pen, because this calculations look ugly when written like this. I'm going to use jass tags so it's more readable (I hope):

d1 = (d - d2) + (d - 2 * d2) + (d - 3 * d2) + ... + (d - q * d2)

A math rule allows us to write that like this:

d1 = q * d - q * (q + 1) * d1 / 2

We'll just make a supstitute for 'q + 1' so the calculation looks a little nicer (yeah, right...), lets name it x:

d1 = q * (d - (x * d2) / 2)

Lets fix it a little bit:

d1 = q * ((2 * d - x * d2) / 2)

When d1 reaches 0:

0 = q * ((2 * d - x * d2) / 2)

Now we'll multiply the equation by 2:

0 = q * (2 * d - x * d2)

0 = 2 * d * q - x * d2 * q

We'll going to return 'q + 1' now:

0 = 2 * d * q - (q + 1) * d2 * q

We'll going to move '- (q + 1) * d2 * q' to the other side:

(q + 1) * d2 * q = 2 * d * q

On the both sides we have q so if we divide the equation with 'q', they will be gone (meaning they will turn to 1 and we all know that 'x * 1' is 'x'):

(q + 1) * d2 = 2 * d

d2 = 2 * d / (q + 1)

Now we have:

Collapse JASS:
d1 = 2 * d / (q + 1)
d2 = d1 / q

So we'll add those two in our struct:

Collapse JASS:
struct Knockback_Data
    unit u
    real a
    real d1
    real d2
endstruct

So, now we have to create that struct as a local in the Knockback function and set its values:

Collapse JASS:
function Knockback takes unit u, real d, real a, real w returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q
endfunction

I think everything is clear here, we need to transfer the knockbacked unit, the angle, d1 and d2 to the Knockback_Execute function.

The important part in knockbacking is to pause the knockbacked unit, because not pausing it can cause undesireable effects (like not moving in a straight line). But beware, pausing a unit can sometimes be a bit.......screwed up. In this case it's important that we know that it also pauses buffs, so I suggest you code with that fact on your mind. Now lets pause it:

Collapse JASS:
function Knockback takes unit u, real d, real a, real w returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q

    call PauseUnit(u, true)
endfunction

STOPPING ANY ORDERS

For moving the unit, we'll going to use SetUnitX/Y, because it has (in this case) a better effect than SetUnitPosition. But SetUnitPosition can break channeling spells, moreover, stop the unit's orders (among other things), and it is important for the knockback to be able to do that. We'll going to move the unit to the same point it already is, because we just want to stop its orders:

Collapse JASS:
function Knockback takes unit u, real d, real a, real w returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)
endfunction

NUMBER OF KNOCKBACKS

Now, lets get back to that misterious global timer. The reason I'm using a global timer is because I don't want to use game cache. Instead of creating a local timer every time, attaching the struct to it and execute the movement in the Knockback_Execute function for each unit separately, I'm going to use a single timer that will perform each active knockback. I'm going to pause it when there are no active knockbacks (and unpause it when there are). I'll do this by creating a new integer global that will tell me how many units do I have to move. I'll set it's initial value to 0, because in the start, there will be 0 active knockbacks:

Collapse JASS:
globals
    timer Tim = CreateTimer()
    Knockback_Data array Ar
    integer Total = 0
endglobals

Then I'm going to start the timer in the Knockback function if Total is equal to 0, because that will mean it is paused and it needs to be started again because the function was called (which means there is a knockback to be performed):

Collapse JASS:
function Knockback takes unit u, real d, real a, real w returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

    if Total == 0 then
        call TimerStart(Tim, Interval(), true, function Knockback_Execute)
    endif
endfunction

Now we have to tell the system that a new knockback is active, we'll going to do that by increasing Total by 1. Another thing we also have to do is add the Knockback_Data that was created (kd) in the Knockback_Data array (Ar), but the index of the first one will be 0:

Collapse JASS:
function Knockback takes unit u, real d, real a, real w returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

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

    set Total = Total + 1
    set Ar[Total - 1] = kd
endfunction

"KNOCKBACK_EXECUTE" FUNCTION

We have to move to the Knockback_Execute function now, what we'll do is create a local Knockback_Data variable, so we can handle each knockback (that needs to be processed) more easily:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
endfunction

We are going to have a loop now, because we need to process each active knockback, so we'll also have to create a local integer for the loop:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
endfunction

And because we are going to move units, we are going to add two coordinate variables (x and y):

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y
endfunction

Now we will make a loop that will stop when i (the variable, not me) reaches Total, and just in case of it skipping Total, we'll make it stop when i is equal or greater than Total:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

    loop
        exitwhen i >= Total
        set i = i + 1
    endloop
endfunction

In the beginning of the loop we'll set kd to Ar[i] (so it's easier to handle):

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

    loop
        exitwhen i >= Total
        set kd = Ar[i]
        set i = i + 1
    endloop
endfunction

MOVING THE UNIT

Remember what we said about d1? It is the "bit" in "moving a unit a bit a time", we will put its destination coordinates in variables, then we'll move the unit to those coordinates with SetUnitX/Y:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

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

        set x = GetUnitX(kd.u) + kd.d1 * Cos(kd.a)
        set y = GetUnitY(kd.u) + kd.d1 * Sin(kd.a)

        call SetUnitX(kd.u, x)
        call SetUnitY(kd.u, y)

        set i = i + 1
    endloop
endfunction

I hope we all know how to do a polar projection with coordinates, because that is what I did here.

Now, after that we need to decrease d1 by d2 (that's what I explained in the "calculations" part):

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

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

        set x = GetUnitX(kd.u) + kd.d1 * Cos(kd.a)
        set y = GetUnitY(kd.u) + kd.d1 * Sin(kd.a)

        call SetUnitX(kd.u, x)
        call SetUnitY(kd.u, y)

        set kd.d1 = kd.d1 - kd.d2

        set i = i + 1
    endloop
endfunction

DESTROYING THE STRUCT

When a knockback finishes, we have to destroy its struct, right? The knockback stops when d1 reaches 0, because that means that the unit has stopped moving. We'll check if d1 reached 0 (or less) with an if/then/else:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

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

        set x = GetUnitX(kd.u) + kd.d1 * Cos(kd.a)
        set y = GetUnitY(kd.u) + kd.d1 * Sin(kd.a)

        call SetUnitX(kd.u, x)
        call SetUnitY(kd.u, y)

        set kd.d1 = kd.d1 - kd.d2

        if kd.d1 <= 0 then
            call kd.destroy()
        endif

        set i = i + 1
    endloop
endfunction

But when we are destroying that struct, it will leave an empty space, and that is definitely not good. We will have to fill it out somehow, I think the easiest way would be to move the last struct to that empty place (we'll going to do that before destroying the struct):

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

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

        set x = GetUnitX(kd.u) + kd.d1 * Cos(kd.a)
        set y = GetUnitY(kd.u) + kd.d1 * Sin(kd.a)

        call SetUnitX(kd.u, x)
        call SetUnitY(kd.u, y)

        set kd.d1 = kd.d1 - kd.d2

        if kd.d1 <= 0 then
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop
endfunction

I used Ar[Total - 1] because the first index of the array is 0, not 1 (I hope that's clear). I'm setting Total to Total - 1 because when a struct is destroyed, that means there is a finished knockback that is no longer active, but done.

One thing we're forgetting (or maybe not) is unpausing the unit when the knockback is over:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

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

        set x = GetUnitX(kd.u) + kd.d1 * Cos(kd.a)
        set y = GetUnitY(kd.u) + kd.d1 * Sin(kd.a)

        call SetUnitX(kd.u, x)
        call SetUnitY(kd.u, y)

        set kd.d1 = kd.d1 - kd.d2

        if kd.d1 <= 0 then
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop
endfunction

Also, when there are no active knockbacks (meaning Total is equal to 0), we need to pause the timer:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

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

        set x = GetUnitX(kd.u) + kd.d1 * Cos(kd.a)
        set y = GetUnitY(kd.u) + kd.d1 * Sin(kd.a)

        call SetUnitX(kd.u, x)
        call SetUnitY(kd.u, y)

        set kd.d1 = kd.d1 - kd.d2

        if kd.d1 <= 0 then
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop

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

SINE AND COSINE

Pay attention to that Cos() and Sin() in the Knockback_Execute function, we don't have to call them every 0.04 seconds, right? That's really inefficient, we can just store them in a variable. Moreover, we are going to put them in our struct:

Collapse JASS:
struct Knockback_Data
    unit u
    real a
    real d1
    real d2

    real sin
    real cos
endstruct

Now, what we're going to do now is give those variables a value:

Collapse JASS:
function Knockback takes unit u, real d, real a, real w returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q
    
    set kd.sin = Sin(a)
    set kd.cos = Cos(a)

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

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

    set Total = Total + 1
    set Ar[Total - 1] = kd
endfunction

But note that then we don't need the angle variable in our struct anymore, so we can remove it:

Collapse JASS:
struct Knockback_Data
    unit u
    real a
    real d1
    real d2

    real sin
    real cos
endstruct

function Knockback takes unit u, real d, real a, real w returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q
    
    set kd.sin = Sin(a)
    set kd.cos = Cos(a)

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

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

    set Total = Total + 1
    set Ar[Total - 1] = kd
endfunction

Also, don't forget to do use them in the Knockback_Execute function:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

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

        set x = GetUnitX(kd.u) + kd.d1 * kd.cos
        set y = GetUnitY(kd.u) + kd.d1 * kd.sin

        call SetUnitX(kd.u, x)
        call SetUnitY(kd.u, y)

        set kd.d1 = kd.d1 - kd.d2

        if kd.d1 <= 0 then
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop

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

MAP BOUNDS CAUTION

I think we all know that when a unit (or anything else) gets outside the map bounds, the game crashes. What we're going to do is prevent our knockback code to push the unit outside those bounds. Instead, we'll make the unit stop if it reaches the bounds. First, we will implement Vexorian's CheckPathability function, which is very useful I must say. Then lets put the function on the top of our script (or in the custom script section, wherever you're coding this).

Now when we have that implemented, here's what we're going to do: in the Knockback_Execute function we're going to check if the point that the unit is supposed to move to is pathable, if it's not, then that means something is already there (tree, building, map bound, cliffs etc.). Here's how we're going to do that:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y

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

        set x = GetUnitX(kd.u) + kd.d1 * kd.cos
        set y = GetUnitY(kd.u) + kd.d1 * kd.sin

        if CheckPathability(x, y) then
            call SetUnitX(kd.u, x)
            call SetUnitY(kd.u, y)
        endif

        set kd.d1 = kd.d1 - kd.d2

        if kd.d1 <= 0 or not CheckPathability(x, y) then
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop

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

The problem here is that we're creating an item and removing it every 0.04 seconds, that's inefficient. We can make a single global item and just move it around in each execution:

Collapse JASS:
globals
    timer Tim = CreateTimer()
    Knockback_Data array Ar
    integer Total = 0

    item I = CreateItem('ciri', 0, 0)
endglobals

function CheckPathabilityTrickGet takes nothing returns nothing
    set bj_rescueChangeColorUnit = bj_rescueChangeColorUnit or GetEnumItem() != I
endfunction

function CheckPathabilityTrick takes real x, real y returns boolean
    local integer i = 30
    local real X
    local real Y
    local rect r
    call SetItemPosition(I, x, y)
    set X = GetItemX(I) - x
    set Y = GetItemY(I) - y
    if X * X + Y * Y <= 100 then
        return true
    endif
    set r = Rect(x - i, y - i, x + i, y + i)
    set bj_rescueChangeColorUnit = false
    call EnumItemsInRect(r, null, function CheckPathabilityTrickGet)
    call RemoveRect(r)
    set r = null
    return bj_rescueChangeColorUnit
endfunction

function CheckPathability takes real x, real y returns boolean
    local boolean b = CheckPathabilityTrick(x, y)
    call SetItemVisible(I, false)
    return b
endfunction

I also got rid of those darn Pow() functions, I don't know why Vex used them. I highlighted all the lines I changed, so it is easier for you to see what I changed.

HITTING TREES

You have probably seen that some knockbacked units take down trees if they bump into them. So that's what we're also going to do here, otherwise, the unit would stop if it rans into a tree also (the CheckPathability part). What we'll do is add a new parameter in our Knockback function:

function Knockback takes unit u, real d, real a, real w, real r returns nothing

This parameter represents the range in which the unit must come within the tree in order to take it down. We'll make it so if you put 0 as a range parameter, no trees will be destroyed. We have to put a new variable in our struct (the one that will hold the range):

Collapse JASS:
struct Knockback_Data
    unit u
    real d1
    real d2

    real sin
    real cos

    real r
endstruct

function Knockback takes unit u, real d, real a, real w, real r returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q
    
    set kd.sin = Sin(a)
    set kd.cos = Cos(a)

    set kd.r = r

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

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

    set Total = Total + 1
    set Ar[Total - 1] = kd
endfunction

This is a function I wrote (actually copied from Hero12341234's Tree Revival tutorial, hehe, sorry Hero....):

Collapse JASS:
function Knockback_TreeFilter takes nothing returns boolean
    local integer d = GetDestructableTypeId(GetFilterDestructable())
    return d == 'ATtr' or d == 'BTtw' or d == 'KTtw' or d == 'YTft' or d == 'JTct' or d == 'YTst' or d == 'YTct' or d == 'YTwt' or d == 'JTwt' or d == 'JTwt' or d == 'FTtw' or d == 'CTtr' or d == 'ITtw' or d == 'NTtw' or d == 'OTtw' or d == 'ZTtw' or d == 'WTst' or d == 'LTlt' or d == 'GTsh' or d == 'Xtlt' or d == 'WTtw' or d == 'Attc' or d == 'BTtc' or d == 'CTtc' or d == 'ITtc' or d == 'NTtc' or d == 'ZTtc'
endfunction

With this function we will check if the destructible (or, in JASS, destructable, I don't know which is right anymore) in range is a tree.

Ok, now we will go to the Knockback_Execute function, because from there we're going to destroy trees. This part is kinda weird because we'll have to create a rect, because the only way we could reach "destructibles in range" is through EnumDestructablesInRect() function. We will create a rect out of range, there's a BJ that does that:

Collapse JASS:
function GetRectFromCircleBJ takes location center, real radius returns rect
    local real centerX = GetLocationX(center)
    local real centerY = GetLocationY(center)
    return Rect(centerX - radius, centerY - radius, centerX + radius, centerY + radius)
endfunction

We will use pure natives, I just showed you that function so it's easier to understand what I'm doing. So basically, in the Knockback_Execute function:

Code:
centerX = x
centerY = y
radius = kd.r

Now we'll create a local rect variable and we will check if a destructible is in the circle:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y
    local rect r

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

        set x = GetUnitX(kd.u) + kd.d1 * kd.cos
        set y = GetUnitY(kd.u) + kd.d1 * kd.sin

        if kd.r != 0 then
            set r = Rect(x - kd.r, y - kd.r, x + kd.r, y + kd.r)
        endif

        if CheckPathability(x, y) then
            call SetUnitX(kd.u, x)
            call SetUnitY(kd.u, y)
        endif

        set kd.d1 = kd.d1 - kd.d2

        if kd.d1 <= 0 or not CheckPathability(x, y) then
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop

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

We'll create a new function that we will use for EnumDestructablesInRect() function, it will kill every picked destructable:

Collapse JASS:
function Knockback_KillTree takes nothing returns nothing
    call KillDestructable(GetEnumDestructable())
endfunction

We will put it above Knocback_Execute function, so we can reach it.

Now we have an action and a condition, we can use EnumDestructablesInRect() now, but first we have to create a boolexpr that will check if the picked destructable is a tree:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y
    local rect r
    local boolexpr b

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

        set x = GetUnitX(kd.u) + kd.d1 * kd.cos
        set y = GetUnitY(kd.u) + kd.d1 * kd.sin

        if kd.r != 0 then
            set r = Rect(x - kd.r, y - kd.r, x + kd.r, y + kd.r)
            set b = Filter(function Knockback_TreeFilter)
            call EnumDestructablesInRect(r, b, function Knockback_KillTree)
            call RemoveRect(r)
            call DestroyBoolExpr(b)
        endif

        if CheckPathability(x, y) then
            call SetUnitX(kd.u, x)
            call SetUnitY(kd.u, y)
        endif

        set kd.d1 = kd.d1 - kd.d2

        if kd.d1 <= 0 or not CheckPathability(x, y) then
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop

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

But instead of calling the Filter all the time, since the boolexpr is always the same, we can make a global boolexpr and set it in on map initialization:

Collapse JASS:
globals
    timer Tim = CreateTimer()
    Knockback_Data array Ar
    integer Total = 0

    item I = CreateItem('ciri', 0, 0)

    boolexpr Filter
endglobals

function Trig_Map_Initialization_Actions takes nothing returns nothing
    set Filter = Filter(function Knockback_TreeFilter)
endfunction

Note that this would mean that you need to transfer the Knockback_TreeFilter function somewhere reachable by Trig_Map_Initialization_Actions, now it doesn't have to be reachable by Knockback_Execute function anymore.

SPECIAL EFFECTS

In some knockbacks there are special effects involved during the process. We're going to make two types of making a special effect:
  1. Creating and destroying the special effect in the Knockback_Execute function
  2. Creating a special effect attached to the knockbacked unit, and when the knockback ends, destroy it

First we are going to make a new integer parameter that will represent the type. 0 will be none (no special effects), 1 will be type 1 and 2 will be type 2:

function Knockback takes unit u, real d, real a, real w, real r, integer t returns nothing

Now, of course, we have to add the string parameter for the special effect path, and if the type is 2, a string parameter for the attachment point. We're going to make it so if you input 1 as the type, you can put "" as attachment point. Just to make something clear, in the type 1 we're going to create the special effect at the position of knockbacked unit (and destroy it) periodically:

function Knockback takes unit u, real d, real a, real w, real r, integer t, string s, string p returns nothing

If you input 0 as type, you can put "" for s and p.

We have to create two new variables in our struct (we're going to leave out type variable, you'll see why) and we're going to set their intial values to 'none':

Collapse JASS:
struct Knockback_Data
    unit u
    real d1
    real d2

    real sin
    real cos

    real r
    
    string s = ""
    effect e = null
endstruct

What the hell is 'effect e'? This is used for the type 2, so it can be reachable when we want to destroy it.

Lets attach those parameters now, with few if/then/elses:

Collapse JASS:
function Knockback takes unit u, real d, real a, real w, real r, integer t, string s, string p returns nothing
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q
    
    set kd.sin = Sin(a)
    set kd.cos = Cos(a)

    set kd.r = r

    if s != "" and s != null then
        if t == 2 then
            if p != "" and p != null then
                set kd.e = AddSpecialEffectTarget(s, u, p)
            else
                set kd.e = AddSpecialEffectTarget(s, u, "chest")
            endif
        elseif t == 1 then
            set kd.s = s
        endif
    endif

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

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

    set Total = Total + 1
    set Ar[Total - 1] = kd
endfunction

I made it so if you pick type 2, but use attachment point "", the attachment point will be "chest" (that's like a default value).

Now lets go to Knockback_Execute function:

Collapse JASS:
function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y
    local rect r
    local boolexpr b

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

        if kd.s != null and kd.s != null then
            set x = GetUnitX(kd.u)
            set y = GetUnitY(kd.u)

            call DestroyEffect(AddSpecialEffect(kd.s, x, y)

            set x = x + kd.d1 * kd.cos
            set y = y + kd.d1 * kd.sin
        else
            set x = GetUnitX(kd.u) + kd.d1 * kd.cos
            set y = GetUnitY(kd.u) + kd.d1 * kd.sin
        endif

        if kd.r != 0 then
            set r = Rect(x - kd.r, y - kd.r, x + kd.r, y + kd.r)
            set b = Filter(function Knockback_TreeFilter)
            call EnumDestructablesInRect(r, b, function Knockback_KillTree)
            call RemoveRect(r)
            call DestroyBoolExpr(b)
        endif

        if CheckPathability(x, y) then
            call SetUnitX(kd.u, x)
            call SetUnitY(kd.u, y)
        endif

        set kd.d1 = kd.d1 - kd.d2


        if kd.d1 <= 0 or not CheckPathability(x, y) then
            if kd.e != null then
                call DestroyEffect(kd.e)
            endif
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop

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

I did that weird if/then/else with s to avoid calling GetUnitX/Y twice. You see now why I put effect e in the struct, right? I did it because it was possible to do without attaching type variable too, with a few if/then/elses everything is possible ;).

For the end I just want to make on more thing (I didn't know where to squeeze it):

Collapse JASS:
function Knockback takes unit u, real d, real a, real w, real r, integer t, string s, string p returns Knockback_Data
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q
    
    set kd.sin = Sin(a)
    set kd.cos = Cos(a)

    set kd.r = r

    if s != "" and s != null then
        if t == 2 then
            if p != "" and p != null then
                set kd.e = AddSpecialEffectTarget(s, u, p)
            else
                set kd.e = AddSpecialEffectTarget(s, u, "chest")
            endif
        elseif t == 1 then
            set kd.s = s
        endif
    endif

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

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

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

    return kd
endfunction

I put that if you want to mess with the knockback during it, for example if you want to:
  • change the angle by changing kd.sin and kd.cos
  • change the unit knockbacked, though that can lead to some screwy side-effects
  • change the special effect by changing kd.s/kd.e

FEW TRICKS & TIPS

We can put this code in the map header by putting it in a library, so it can be reachable by anywhere:

Collapse JASS:
library Knockback

struct Knockback_Data
    unit u
    real d1
    real d2

    real sin
    real cos

    real r
    
    string s = ""
    effect e = null
endstruct

globals
    timer Tim = CreateTimer()
    Knockback_Data array Ar
    integer Total = 0

    item I = CreateItem('ciri', 0, 0)

    boolexpr Filter
endglobals

function CheckPathabilityTrickGet takes nothing returns nothing
    set bj_rescueChangeColorUnit = bj_rescueChangeColorUnit or GetEnumItem() != I
endfunction

function CheckPathabilityTrick takes real x, real y returns boolean
    local integer i = 30
    local real X
    local real Y
    local rect r
    call SetItemPosition(I, x, y)
    set X = GetItemX(I) - x
    set Y = GetItemY(I) - y
    if X * X + Y * Y <= 100 then
        return true
    endif
    set r = Rect(x - i, y - i, x + i, y + i)
    set bj_rescueChangeColorUnit = false
    call EnumItemsInRect(r, null, function CheckPathabilityTrickGet)
    call RemoveRect(r)
    set r = null
    return bj_rescueChangeColorUnit
endfunction

function CheckPathability takes real x, real y returns boolean
    local boolean b = CheckPathabilityTrick(x, y)
    call SetItemVisible(I, false)
    return b
endfunction

function Knockback_TreeFilter takes nothing returns boolean
    local integer d = GetDestructableTypeId(GetFilterDestructable())
    return d == 'ATtr' or d == 'BTtw' or d == 'KTtw' or d == 'YTft' or d == 'JTct' or d == 'YTst' or d == 'YTct' or d == 'YTwt' or d == 'JTwt' or d == 'JTwt' or d == 'FTtw' or d == 'CTtr' or d == 'ITtw' or d == 'NTtw' or d == 'OTtw' or d == 'ZTtw' or d == 'WTst' or d == 'LTlt' or d == 'GTsh' or d == 'Xtlt' or d == 'WTtw' or d == 'Attc' or d == 'BTtc' or d == 'CTtc' or d == 'ITtc' or d == 'NTtc' or d == 'ZTtc'
endfunction

constant function Interval takes nothing returns real
    return 0.04
endfunction

function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y
    local rect r
    local boolexpr b

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

        if kd.s != null and kd.s != null then
            set x = GetUnitX(kd.u)
            set y = GetUnitY(kd.u)

            call DestroyEffect(AddSpecialEffect(kd.s, x, y)

            set x = x + kd.d1 * kd.cos
            set y = y + kd.d1 * kd.sin
        else
            set x = GetUnitX(kd.u) + kd.d1 * kd.cos
            set y = GetUnitY(kd.u) + kd.d1 * kd.sin
        endif

        if kd.r != 0 then
            set r = Rect(x - kd.r, y - kd.r, x + kd.r, y + kd.r)
            set b = Filter(function Knockback_TreeFilter)
            call EnumDestructablesInRect(r, b, function Knockback_KillTree)
            call RemoveRect(r)
            call DestroyBoolExpr(b)
        endif

        if CheckPathability(x, y) then
            call SetUnitX(kd.u, x)
            call SetUnitY(kd.u, y)
        endif

        set kd.d1 = kd.d1 - kd.d2


        if kd.d1 <= 0 or not CheckPathability(x, y) then
            if kd.e != null then
                call DestroyEffect(kd.e)
            endif
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop

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

function Knockback takes unit u, real d, real a, real w, real r, integer t, string s, string p returns Knockback_Data
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q
    
    set kd.sin = Sin(a)
    set kd.cos = Cos(a)

    set kd.r = r

    if s != "" and s != null then
        if t == 2 then
            if p != "" and p != null then
                set kd.e = AddSpecialEffectTarget(s, u, p)
            else
                set kd.e = AddSpecialEffectTarget(s, u, "chest")
            endif
        elseif t == 1 then
            set kd.s = s
        endif
    endif

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

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

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

    return kd
endfunction

endlibrary

Holy macarone, that's some code...... :). You can put that code in a trigger (not in the map header).

Now when we put it in a library, we can make the functions/variables/structs that we don't want to be reachable private:

Collapse JASS:
globals
    private timer Tim = CreateTimer()
    private Knockback_Data array Ar
    private integer Total = 0

    private item I = CreateItem('ciri', 0, 0)

    boolexpr Filter
endglobals

private function CheckPathabilityTrickGet takes nothing returns nothing

private function CheckPathabilityTrick takes real x, real y returns boolean

private function CheckPathability takes real x, real y returns boolean

private function Knockback_TreeFilter takes nothing returns boolean

private constant function Interval takes nothing returns nothing

private function Knockback_Execute takes nothing returns nothing

We are not making boolexpr Filter private because we need it for the map initialization function and we are not making the Knockback_Data struct private, because the return value of Knockback/Ex function would be impossible to use. I hope you understand why we don't make Knockback function private (because then we couldn't use it at all, well, actually, outside the library) ;)

I noticed (I don't know if you have) that we have a lot of parameters that we don't always use, so we can make that function KnockbackEx, and make a new function called Knockback that will take fewer parameters and set the rest of them to 'none':

Collapse JASS:
function KnockbackEx takes unit u, real d, real a, real w, real r, integer t, string s, string p returns Knockback_Data

function Knockback takes unit u, real d, real a, real w returns Knockback_Data
    return KnockbackEx(u, d, a, w, 0, 0, "", "")
endfunction

Of course, you don't have to leave out range parameter, you can put as many parameters you want to Knockback function.

Remember to put Knockback function below KnockbackEx function, you probably know why, if you don't, you should :)

LAST WORDS

You can check the Knockback(Ex) function here.

If you find any spelling mistake somewhere, or any error, please report it!

I hope this tutorial helped someone

Last edited by Silvenon : 11-03-2007 at 11:44 AM.
Silvenon is offline   Reply With Quote
Sponsored Links - Login to hide this ad!
Old 09-30-2007, 11:32 AM   #2
Malf
I LIKE PIZZA! | >
 
Malf's Avatar
 
Join Date: Sep 2007
Posts: 625

Submissions (2)

Malf has a spectacular aura about (92)Malf has a spectacular aura about (92)Malf has a spectacular aura about (92)Malf has a spectacular aura about (92)

Send a message via AIM to Malf
Default

Looks nice, will you add secondary knockbacks as well? Secondary as in, a unit getting knocked back hit a unit, the stationary unit gets knocked backwards as well.
Malf is offline   Reply With Quote
Old 09-30-2007, 01:52 PM   #3
Silvenon
User
 
Silvenon's Avatar
 
Join Date: May 2007
Posts: 492

Submissions (1)

Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)

Send a message via MSN to Silvenon
Default

Interesting idea, but first I'll add the extensions I mentioned. Thanks for the idea, I think I'll put that too.

EDIT: I gave your idea more thought, and I realized that if I want it to look good, it will be complicated like hell. I can do it in a simple way, so the knockbacked unit pushes units it hits........nah it's hard to explain :)
__________________
Cascading Style Shit

Last edited by Silvenon : 09-30-2007 at 02:55 PM.
Silvenon is offline   Reply With Quote
Old 09-30-2007, 06:41 PM   #4
Pyrogasm
Lackadaisically Absent.
 
Pyrogasm's Avatar


Respected User
 
Join Date: Sep 2006
Posts: 4,514

Submissions (9)

Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)

Hero Contest - Fourth place

Send a message via ICQ to Pyrogasm Send a message via AIM to Pyrogasm Send a message via MSN to Pyrogasm Send a message via Yahoo to Pyrogasm
Default

You spelled "endstruct" as "enstruct" and "I hope this tutorial helped anyone." should be "I hope this tutorial helped someone.".

Other than that, you use SetHandleHandle where you should be using SetHandleInt. "Structs" are integers, remember?
Also, if you tell people to use CSSafety, you might as well tell them to use CSCache for the SetCSData(...) and GetCSData(...) functions, as that will save them from using GameCache.

Another thing I think you should mention would be using 1 timer to control all the possible knockbacks instead of starting a new one for each unit. You'd have to make sure you circumvent the OP limit, and I it would be a useful thing to show people.
__________________
Quote:
Originally posted by Rising_Dusk
Your spells are mostly ignored because they are not very cool so we aren't very excited to review/approve them, but you are incredibly persistent and won't give us an excuse to graveyard it. That is generally what results in a resource being ignored for a long time.

The Spell Request Thread Done for, unless someone else wants to revive it...
It lasted a damn long time.

Please; Ask for Help Appropriately














Quote:
Originally posted by Kyrbi0
Huh. Almost makes me wish I had a girlfriend, to take advantage of today (wait, no, that's not what I meant... I mean, take advantage of the fact that it is international women's day... gah, never mind).
Quote:
Originally posted by Pyrogasm
Rome may not have been built in a day, but the Romans sure as hell didn't say "look at this great city we built guys!" when they had nothing more than a bit of stone and some cottages.

Last edited by Pyrogasm : 09-30-2007 at 06:41 PM.
Pyrogasm is offline   Reply With Quote
Old 09-30-2007, 11:23 PM   #5
botanic
User
 
botanic's Avatar
 
Join Date: Feb 2007
Posts: 551

Submissions (2)

botanic will become famous soon enough (41)botanic will become famous soon enough (41)

Send a message via AIM to botanic
Default

Um I fail to see where you cleared the timer leak :/

PS: "
If you find any spelling mistake somewhere, or any error, please report it." You just made pyro's day
__________________
Quote:
Originally Posted by cohadar
I really should not be the one telling you this
Quote:
Originally Posted by botanic
I think we established that
botanic is offline   Reply With Quote
Old 10-01-2007, 12:51 PM   #6
Silvenon
User
 
Silvenon's Avatar
 
Join Date: May 2007
Posts: 492

Submissions (1)

Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)

Send a message via MSN to Silvenon
Default

Quote:
You spelled "endstruct" as "enstruct"

My clumsiness knows no bounds.....

Quote:
Other than that, you use SetHandleHandle where you should be using SetHandleInt. "Structs" are integers, remember?

I think I would won "the stupidest mistake ever" contest :). Yes I do remember that they are integers, in Knockback function I used SetHandleInt.

Quote:
Also, if you tell people to use CSSafety, you might as well tell them to use CSCache for the SetCSData(...) and GetCSData(...) functions, as that will save them from using GameCache.

This is the part I don't understand, I've never heard about those, how do they work? I don't know what they are at all, sorry.

Quote:
Another thing I think you should mention would be using 1 timer to control all the possible knockbacks instead of starting a new one for each unit. You'd have to make sure you circumvent the OP limit, and I it would be a useful thing to show people.

Well, I have to tell you a little secret: I kinda never learned to use a single timer :), that part of the tutorial I didn't understand, that's why I didn't include that in the tutorial. When I understand how it works (should be pretty soon) I'll include that too. Btw, I put that in the extension.

Thanks a bunch Pyro, you really helped me (I fixed all the errors).

EDIT: Darn! I have to spread first :)

Quote:
Um I fail to see where you cleared the timer leak :/

call ReleaseTimer(t)?
__________________
Cascading Style Shit

Last edited by Silvenon : 10-01-2007 at 12:52 PM.
Silvenon is offline   Reply With Quote
Old 10-01-2007, 01:25 PM   #7
Malf
I LIKE PIZZA! | >
 
Malf's Avatar
 
Join Date: Sep 2007
Posts: 625

Submissions (2)

Malf has a spectacular aura about (92)Malf has a spectacular aura about (92)Malf has a spectacular aura about (92)Malf has a spectacular aura about (92)

Send a message via AIM to Malf
Default

SetCSData and GetCSData are the "UserDatas" for handles. They are exactly like SetUnitUserData and GetUnitUserData

You should also really use just one global timer. When you want to knockback a unit, add that unit to an array of some sort. Then whenever the global timer expires, it loops through all the units in the array.

Collapse JASS:
globals
    private integer INDEX = 0
endglobals

function callback takes nothing returns nothing
local struct d
local struct D
local integer i = 0
    loop
        set i = i+1
        exitwhen  > INDEX
        set d = struct(i)
        if d.currentdist > d.maxdist then
          call struct.destroy(d) //Destroys the struct, but it leaves an empty "space" later on when the timer iterates again
          //So you'd have to find out how to reorganize the structs' indexing
          set INDEX = INDEX - 1
        else
          set d.x = d.x + d.distance * Cos(d.a*0.01745)
          set d.y = d.y + d.distance * Sin(d.a*0.01745)
          call SetUnitPosition(d.u, d.x, d.y)
          set d.currentdist = d.currentdist + d.distance
        endif
    endloop
endfunction

function knockback unit takes unit u, blah blah nothing
local struct d = struct.create()
    set d.u = u
    etc,.
    set INDEX = INDEX + 1
endfunction

NOTE: I just wrote this within 10 minutes, it may be crappy :p

Last edited by Malf : 10-01-2007 at 01:33 PM.
Malf is offline   Reply With Quote
Old 10-01-2007, 02:15 PM   #8
Silvenon
User
 
Silvenon's Avatar
 
Join Date: May 2007
Posts: 492

Submissions (1)

Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)

Send a message via MSN to Silvenon
Default

Nice coding!

Thanks for the advice, I'll implement those as soon as I learn it all, don't worry :)

EDIT: Pyro, could you please change the title of the tut to something better, like "vJass: Coding An Efficient Knockback"? If you have such power, of course.
__________________
Cascading Style Shit

Last edited by Silvenon : 10-01-2007 at 06:03 PM.
Silvenon is offline   Reply With Quote
Old 10-02-2007, 01:56 AM   #9
Pyrogasm
Lackadaisically Absent.
 
Pyrogasm's Avatar


Respected User
 
Join Date: Sep 2006
Posts: 4,514

Submissions (9)

Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)

Hero Contest - Fourth place

Send a message via ICQ to Pyrogasm Send a message via AIM to Pyrogasm Send a message via MSN to Pyrogasm Send a message via Yahoo to Pyrogasm
Default

I don't have such power; only PitzerMike and Admins do.

The first thing you should add to this tutorial is how to check for map bounds. It is vitally important that people know how to do this. I would suggest using some constant globals instead of calling the functions every time to get the min/max values:
Collapse JASS:
globals
    constant real KB_MAPMINX = GetRectMinX(bj_mapInitialPlayableArea)
    constant real KB_MAPMAXX = GetRectMaxX(bj_mapInitialPlayableArea)
    constant real KB_MAPMINY = GetRectMinY(bj_mapInitialPlayableArea)
    constant real KB_MAPMAXY = GetRectMaxX(bj_mapInitialPlayableArea)
endglobals
__________________
Quote:
Originally posted by Rising_Dusk
Your spells are mostly ignored because they are not very cool so we aren't very excited to review/approve them, but you are incredibly persistent and won't give us an excuse to graveyard it. That is generally what results in a resource being ignored for a long time.

The Spell Request Thread Done for, unless someone else wants to revive it...
It lasted a damn long time.

Please; Ask for Help Appropriately














Quote:
Originally posted by Kyrbi0
Huh. Almost makes me wish I had a girlfriend, to take advantage of today (wait, no, that's not what I meant... I mean, take advantage of the fact that it is international women's day... gah, never mind).
Quote:
Originally posted by Pyrogasm
Rome may not have been built in a day, but the Romans sure as hell didn't say "look at this great city we built guys!" when they had nothing more than a bit of stone and some cottages.
Pyrogasm is offline   Reply With Quote
Old 10-02-2007, 10:39 AM   #10
PitzerMike
Alcopops
 
PitzerMike's Avatar


Tools & Tutorials Moderator
 
Join Date: Jan 2003
Posts: 2,791

Submissions (12)

PitzerMike is a splendid one to behold (643)PitzerMike is a splendid one to behold (643)PitzerMike is a splendid one to behold (643)PitzerMike is a splendid one to behold (643)

Approved Map: Pitzer's Minesweeper

Default

Hopefully InitBlizzard gets called before custom globals are initialized.
__________________
Zoom (requires log in)
PitzerMike is offline   Reply With Quote
Old 10-02-2007, 03:27 PM   #11
Silvenon
User
 
Silvenon's Avatar
 
Join Date: May 2007
Posts: 492

Submissions (1)

Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)

Send a message via MSN to Silvenon
Default

Pyro,

Collapse JASS:
function SetCSData takes handle h, integer v returns nothing
    local integer i=CS_H2I(h)-0x100000
    if (i>=8191) then
        call StoreInteger(cs_cache,"csdata",I2S(i),v)
    else
        set cs_array3[i]=v
    endif
endfunction

function GetCSData takes handle h returns integer
    local integer i=CS_H2I(h)-0x100000
    if (i>=8191) then
    //can't use Get without Set
        return GetStoredInteger(cs_cache,"csdata",I2S(i))
    endif
    return cs_array3[i]
endfunction

Seems to me that they are both using game cache......but I'll listen to your advice.

Quote:
Hopefully InitBlizzard gets called before custom globals are initialized.

Hopefully...... :)

I'm going to update the tutorial in about an hour or two.

EDIT1: Whoops, I just saw the functions again, they use the game cache only if the index is higher than the maximum, sorry :)

EDIT2: *Edited

Guys, this tutorial editing is starting to get kinda hard:
  • in the beginning I said that CSCache is required, and in the end it turned up like it's actually not required
  • I don't have WE to test these codes, so I'm gonna have to trust your error-finding skills
....and some more I can't think of right now.

Did I do the single timer usage correctly? Is there a more efficient solution than a onDestroy method in the part when I'm destroying the structs? Is there something I could've done better/simpler?

Thanks again for the suggestions.

Last edited by Silvenon : 11-03-2007 at 11:45 AM.
Silvenon is offline   Reply With Quote
Old 10-02-2007, 07:26 PM   #12
Pyrogasm
Lackadaisically Absent.
 
Pyrogasm's Avatar


Respected User
 
Join Date: Sep 2006
Posts: 4,514

Submissions (9)

Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)

Hero Contest - Fourth place

Send a message via ICQ to Pyrogasm Send a message via AIM to Pyrogasm Send a message via MSN to Pyrogasm Send a message via Yahoo to Pyrogasm
Default

It only uses gamecache if there are more than 8191 handles on your map at the time you attempt to set the data.

Why would you say it isn't required? I mean, sure you could have them make their own UserData function(s) or use something like grim001's datasystem example. But it doesn't really matter.
__________________
Quote:
Originally posted by Rising_Dusk
Your spells are mostly ignored because they are not very cool so we aren't very excited to review/approve them, but you are incredibly persistent and won't give us an excuse to graveyard it. That is generally what results in a resource being ignored for a long time.

The Spell Request Thread Done for, unless someone else wants to revive it...
It lasted a damn long time.

Please; Ask for Help Appropriately














Quote:
Originally posted by Kyrbi0
Huh. Almost makes me wish I had a girlfriend, to take advantage of today (wait, no, that's not what I meant... I mean, take advantage of the fact that it is international women's day... gah, never mind).
Quote:
Originally posted by Pyrogasm
Rome may not have been built in a day, but the Romans sure as hell didn't say "look at this great city we built guys!" when they had nothing more than a bit of stone and some cottages.

Last edited by Pyrogasm : 10-02-2007 at 07:27 PM.
Pyrogasm is offline   Reply With Quote
Old 10-02-2007, 08:42 PM   #13
Silvenon
User
 
Silvenon's Avatar
 
Join Date: May 2007
Posts: 492

Submissions (1)

Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)

Send a message via MSN to Silvenon
Default

Quote:
Originally Posted by Pryrogasm
It only uses gamecache if there are more than 8191 handles on your map at the time you attempt to set the data.

Quote:
Originally Posted by Silvenon
EDIT: Whoops, I just saw the functions again, they use the game cache only if the index is higher than the maximum, sorry :)

Yeah, I realized that, that's why I edited :)

Quote:
Why would you say it isn't required?

Look at the final code and you'll see what I mean.
__________________
Cascading Style Shit
Silvenon is offline   Reply With Quote
Old 10-03-2007, 06:44 AM   #14
Pyrogasm
Lackadaisically Absent.
 
Pyrogasm's Avatar


Respected User
 
Join Date: Sep 2006
Posts: 4,514

Submissions (9)

Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)

Hero Contest - Fourth place

Send a message via ICQ to Pyrogasm Send a message via AIM to Pyrogasm Send a message via MSN to Pyrogasm Send a message via Yahoo to Pyrogasm
Default

I saw your edit, but I was clarifying. The way you said it seemed to imply that there was some sort of counter variable increasing until it got to the maximum, though that is not exactly the case.

Ah, I didn't see that you had changed it to all run off of a single timer. In that case, no you do not need CSCache.
__________________
Quote:
Originally posted by Rising_Dusk
Your spells are mostly ignored because they are not very cool so we aren't very excited to review/approve them, but you are incredibly persistent and won't give us an excuse to graveyard it. That is generally what results in a resource being ignored for a long time.

The Spell Request Thread Done for, unless someone else wants to revive it...
It lasted a damn long time.

Please; Ask for Help Appropriately














Quote:
Originally posted by Kyrbi0
Huh. Almost makes me wish I had a girlfriend, to take advantage of today (wait, no, that's not what I meant... I mean, take advantage of the fact that it is international women's day... gah, never mind).
Quote:
Originally posted by Pyrogasm
Rome may not have been built in a day, but the Romans sure as hell didn't say "look at this great city we built guys!" when they had nothing more than a bit of stone and some cottages.
Pyrogasm is offline   Reply With Quote
Old 10-03-2007, 10:03 AM   #15
Toink
User
 
Join Date: Oct 2006
Posts: 1,086

Submissions (3)

Toink is on a distinguished road (17)

Send a message via Yahoo to Toink
Default

Another issue about that is when you destroy some structs, which would then leave an empty space in the whole list.

Imagine there's 10 books piled up, these books represent each instances of a unit being knocked back. When a unit is done being knocked back, that book is removed.

[Book 10]
[Book 9]
[Book 8]
[Book 7]
[Book 6]
and so on..

[Book 10]
[Book 9]
[Book 8]
[Book 7] <---- This is done, it will be removed.
[Book 6]

[Book 10]
[Book 9]
[Book 8]
<---- empty space, your timer will loop through it, not to mention reducing the TOTAL index counter, which means that you will somehow lose track of Book 10
[Book 6]

[Null Book 10] <-- You set TOTAL = TOTAL - 1
[Book 9]
[Book 8]

[Book 6]

So to avoid those problems, you'd have to find a way of reorganizing the order of the structs. Fortunately, I have done a method like that along time ago and I'm gonna post it here.

Insert this BEFORE you set TOTAL = TOTAL - 1
Collapse JASS:
    loop
        set total = TOTAL
        set counter = kd
        set counter = counter + 1
        exitwhen counter > TOTAL
        set data = knockbackdata(counter) //a struct variable
         //Now we transfer the members of the next struct to the struct destroyed
        set kd.u = data.u
        set kd.a = data.a
        //And so on.. after you transfer all of the members..
        call knockbackdata.destroy(data) //Destroy the next struct, the iteration ends, the next iteration will transfer data from the other struct to the struct we just destroyed
    endloop

In other words, we would bring down Book 8, 9 and 10 to fill in the hole caused by the destruction of Book 7.

Last edited by Toink : 10-03-2007 at 10:06 AM.
Toink is offline   Reply With Quote
Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off


All times are GMT. The time now is 10:21 AM.


Donate

Affiliates
The Hubb http://bylur.com - Warcraft, StarCraft, Diablo and DotA Blog & Forums The JASS Vault Clan WEnW Campaign Creations Clan CBS GamesModding Flixreel Videos

Powered by vBulletin (Copyright ©2000 - 2014, Jelsoft Enterprises Ltd).
Hosted by www.OICcam.com
IT Support and Services provided by Executive IT Services