RVons SpellQuestions

Coding Help

13 years
Well, it haven't been much time since my last post, and syntax-writing have started to take over my GUI-usage more and more. But I got a question I want answered; How do I pick units in a line (for creating a trigger-based spell like shockwave or similar). I have before used a number of rects to simulate the line-effect, but is there a better way, then I am interested in hearing it.

And a little of-topic question; a quite long time ago, I read a topic about vJass and its strengths against normal GUI, and one particular thing I remembered was that someone mentioned another way to fire of triggers rather than the way triggers runs of in the GUI that was more efficient, but I can't recall how nor where I read it. Someone who knows anything about it?
13 years
Quote from: rvonsonsnadtz on October 14, 2012, 09:45:56 AM
Well, it haven't been much time since my last post, and syntax-writing have started to take over my GUI-usage more and more. But I got a question I want answered; How do I pick units in a line (for creating a trigger-based spell like shockwave or similar). I have before used a number of rects to simulate the line-effect, but is there a better way, then I am interested in hearing it.
Well, you seem to read my mind :) Right now I'm working in a code to group units in a line. And yes you can avoid rects, in fact rects is not the proper way. You can do this process in code as follows:

  • define the cast point and final point with X,Y coordinates
  • set the middle point (with average formula 0.5*(x1+x2) )
  • group units in range with center in the middle point
  • Measure the angle between the cast point and the unit, if it doesn't differ much from the original angle (you set that parameter) then add it to the destination group
  • Repeat until covers all thew units in the initail range[/i]
If you're a little patient I'm gonna finish this code very quickly (I have the algorithm almost done)

QuoteAnd a little of-topic question; a quite long time ago, I read a topic about vJass and its strengths against normal GUI, and one particular thing I remembered was that someone mentioned another way to fire of triggers rather than the way triggers runs of in the GUI that was more efficient, but I can't recall how nor where I read it. Someone who knows anything about it?
Definitely!!!

the problem with GUI is that you generate one trigger and every conditional create a new function which trigger ANOTHER function. Check this example:

[trigger]Untitled Trigger 001
    Events
        Unit - A unit Finishes casting an ability
    Conditions
        (Ability being cast) Equal to Animate Dead
    Actions
        Unit - Create 1 Footman for Player 1 (Red) at (Position of (Triggering unit)) facing Default building facing degrees
        -------- Etc, etc, etc... --------
[/trigger]

This GUI viewed as jass looks like this:
Code (jass) Select
1
function Trig_Untitled_Trigger_001_Conditions takes nothing returns boolean
2
    if ( not ( GetSpellAbilityId() == 'AUan' ) ) then
3
        return false
4
    endif
5
    return true
6
endfunction
7
8
function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
9
    call CreateNUnitsAtLoc( 1, 'hfoo', Player(0), GetUnitLoc(GetTriggerUnit()), bj_UNIT_FACING )
10
    // Etc, etc, etc...
11
endfunction
12
13
//===========================================================================
14
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
15
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
16
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_001, EVENT_PLAYER_UNIT_SPELL_FINISH )
17
    call TriggerAddCondition( gg_trg_Untitled_Trigger_001, Condition( function Trig_Untitled_Trigger_001_Conditions ) )
18
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
19
endfunction

As you can see, this code is too long in all the terms possible and that's because GUI puts a lot "just in case" code which in most of the situations is inefficient as hell. In the triggering part is the same: you register the event (Unit finish casting an ability) then you set a WHOLE FUNCTION just to check the spell type and then it runs another function to run the script.

In fact the question is: couldn't we do this in only one function? and the answer is YES: Let's optimize.

Code (jass) Select
1
function Trig_Untitled_Trigger_001_Conditions takes nothing returns boolean
2
    local location L // I define a local variable so this code doesn't leak a location
3
    if GetSpellAbilityId() == 'AUan' then
4
        set L = GetUnitLoc(GetTriggerUnit())
5
        call CreateNUnitsAtLoc( 1, 'hfoo', Player(0), L, bj_UNIT_FACING )
6
        call RemoveLocation(L) // removes the location
7
    endif
8
    set L = null // 
9
    return false // set it so nothing will run after this function, we3 don't need it :)
10
endfunction
11
12
//===========================================================================
13
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
14
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
15
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_001, EVENT_PLAYER_UNIT_SPELL_FINISH )
16
    call TriggerAddCondition( gg_trg_Untitled_Trigger_001, Condition( function Trig_Untitled_Trigger_001_Conditions ) )
17
endfunction


Here I merged the action inside the condition, doing this gives you an advantage: conditions are faster than actions. I added some code to remove the location leak in the original code. Let's optimize more, doing some vJASS:

Code (jass) Select
1
scope TestTrigger initializer init
2
3
private function Conditions takes nothing returns boolean
4
    if GetSpellAbilityId() == 'AUan' then
5
        call CreateUnit( Player(0), 'hfoo', GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), bj_UNIT_FACING )
6
        // Add more things to do...
7
    endif
8
    return false // set it so nothing will run after this function, we3 don't need it :)
9
endfunction
10
11
//===========================================================================
12
private function init takes nothing returns nothing
13
    local trigger t = CreateTrigger(  )
14
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_FINISH )
15
    call TriggerAddCondition( t, Condition( function Conditions ) )
16
    set t = null // to free handle counter
17
endfunction
18
19
endscope
Now this code is totally efficient, it DOES NOT use locations anymore and it has a nicer presentation and readability. I've set the trigger as a local variable so it doesn't interfere with other code making it totally MUI (multi unit instanceable).

I hope with this live example you can see the advantage of jass and vJASS in triggering terms.
13 years
edited 13 years
Ok, nice! If I could use your algorithms then I would be very thankfull. And since the things I read from your code-exemple, I belive I have to rewrite some of the codes I have done. Another question that I really need answered; are a local variable uniq per function and use or just a function? For example

Code (jass) Select
1
function exemple takes unit attacker, unit attacked returns nothing
2
local DamageC
3
  set DamageC = 50 //Just taking a number for example, the real code is a bit more complicated, and seldom the same
4
  call TriggerSleepAction( 0.50 )
5
  call UnitDamageTargetBJ(attacker, attacked, DamageC, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL)
6
endfunction


Would a multiple uses of the function nearly at the same time mess upp the variable, or are the variable uniq per function and use?
13 years
That's right, they're only valid inside the function you define.

Some small fixes:

Code (jass) Select
2
function exemple takes unit attacker, unit attacked returns nothing
3
local real DamageC // don't forget to add the type of the variable :)
4
  set DamageC = 50 //Just taking a number for example, the real code is a bit more complicated, and seldom the same
5
  call TriggerSleepAction( 0.50 )
6
  call UnitDamageTargetBJ(attacker, attacked, DamageC, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL)
7
endfunction


So no matters how many times or how simultaneously you call the function, those values are unique per call, that guarantee that your code won't mess up with other events in the game :)
13 years
edited 13 years
Ok, do I need to nullify real-locals?

Btw, is there a way to store a integer in a ability or similar (or even better, store a number of different integers in certain unit-types) so I could instead of using a large number of if/then/else after eachothers, I could just get the numbers directly? (A little like a Hashtable, but backwards or something).
Since I want to have complete control over all damage done, I don't want to use the already made- attacktypes and armortypes to declare certain strengths and weaknesses.

Edit: I ran across a annoying Compile-error.. The following code returns a "unexpected "("?"-error..
Code (jass) Select
1
call GroupEnumUnitsInRange(unitsinrange, GetUnitX(attackedR), GetUnitY(attackedR), 300, Filter(function IsUnitEnemy(GetFilterUnit(), Player(attackerid)) == true and (GetFilterUnit() == attackedR) == false))

I need to store a unitgroup (a group I call "unitsinrange)
13 years
edited 2 days
Quote from: rvonsonsnadtz on October 14, 2012, 02:44:07 PMOk, do I need to nullify real-locals?
No, real, integers, booleans and code dont' need to be nullified, other variables depending of agent type must be destroyed and nullified if they offer the option.

QuoteBtw, is there a way to store a integer in a ability or similar (or even better, store a number of different integers in certain unit-types) so I could instead of using a large number of if/then/else after eachothers, I could just get the numbers directly? (A little like a Hashtable, but backwards or something).
In fact the id of abilities and buff are integers. In fact a code like 'AUan' is actually an integer expressed in a special way. You can link the ability to other data using hashtables.
QuoteSince I want to have complete control over all damage done, I don't want to use the already made- attacktypes and armortypes to declare certain strengths and weaknesses.
There's some libraries which offers what you want. You can do this in JASS but it's easier in vJASS because you can define a struct or object which manages the new damage type you want. If you need more information of a possible way to do it just ask me, but first try to cover the manage of structs.

QuoteEdit: I ran across a annoying Compile-error.. The following code returns a "unexpected "("?"-error..
Code (jass) Select
1
call GroupEnumUnitsInRange(unitsinrange, GetUnitX(attackedR), GetUnitY(attackedR), 300, Filter(function IsUnitEnemy(GetFilterUnit(), Player(attackerid)) == true and (GetFilterUnit() == attackedR) == false))
I need to store a unitgroup (a group I call "unitsinrange)
Hmmm, unfortunately you can't define this in that way. You need to define a function and call it here GroupEnum function.

Code (jass) Select
2
function Cond1 takes nothing returns boolean
3
    return IsUnitEnemy(GetFilterUnit(), Player(attackerid)) == true and (GetFilterUnit() == attackedR) == false
4
endfunction
5
6
7
//...The function should be over this expresion, outside of the function. attackedR MUST be a global variable in order to make it work
8
function bla....
9
// code goes here...
10
    call GroupEnumUnitsInRange(unitsinrange, GetUnitX(attackedR), GetUnitY(attackedR), 300, Filter(function Cond1))
11
// ther's more code... blablabla...
12
endfunction
13 years
Just null everything which is not real, boolean, integer, string or code even if it extends handle without the agent type.
13 years
edited 13 years
Thanx again, but ye, I would like some extra information to do a struct that could manage the damage-types and armor-types for the units (since every player will just have one hero each in my map, I will use a hashtable for them where I could give any % I want). I was thinking if I should use a large if/then/else function to determine the types which returns integer attacktype or armortype depending on the units that is, but by some reason it feels like a inefficient way of doing it (and I would probably need 2 functions, one for armor and one for attack).

Edit: I read about structs and an example-use for it. But in the example it stored value for every single hero-unit, not just the hero-types. It feels like it if there isn't a way to store a struct for unit-types instead of units, this way would be a very memory-demanding-way since it would need a struct for all units on the map.
And on speak off that, a recycle-way of units would perhaps be usefull, since I will both have a part in the game that uses some DotA- or AoS-elements, as well as certain armies for every players that are used in certain events.
13 years
Code (jass) Select
1
library UnitIdData initializer init requires Table // hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
2
3
    globals
4
        // you could make the tables not private but encapsulation is a pretty good practice
5
        private Table Damage_table
6
        private Table Armor_table
7
    endglobals
8
9
    private function SetData takes integer unit_id , integer damage, integer armor // will be used only inside this library to make the config part less verbose
10
        set Damage_table[unit_id] = damage
11
        set Armor_table[unit_id] = armor
12
    endfunction
13
14
    private function Config takes nothing returns nothing
15
        /*
16
            here you will define Armor and Damage values according to the unit rawcode
17
            let's define it for the footman, note that you can see an unit rawcode in the object editor by using the shortcut "Ctrl + D"
18
        */
19
20
         call SetData( 'hfoo' , 42 , 666 )
21
    endfunction
22
23
    function GetUnitDamageBase takes unit which_unit returns integer
24
        return Damage_table[ GetUnitTypeId(which_unit) ]
25
    endfunction
26
27
    function GetUnitArmorBase takes unit which_unit returns integer
28
        return Armor_table[ GetUnitTypeId(which_unit) ]
29
    endfunction
30
31
    private function init takes nothing returns nothing
32
        set Damage_table = Table.create()
33
        set Armor_table = Table.create()
34
        call Config()
35
    endfunction
36
37
endlibrary


Note that there i use integers, you could use reals instead with some code editing, but is it really needed ?
13 years
edited 13 years
Ok, but perhaps I'll stick with the ability-things for the moment (I use a "Vulnerable to"/"Resistant to"-system instead of the ordinarie fixed armor-type-system, which gives me the ability to hand-tailor the damage-modifiers for every unit I would use, and the abilities serves as a teller for the player of what the unit is vulnerable to)
However, since Heroes uses alot of more values than in the vanilla wc3 (and the damage-values is very missleading) I would like to use a multiboard (or similar) to show this values. However.. It seems impossible to just show a specific multiboard for just one player, while I show another multiboard for another one.. I could use a chattext-triggered function to show all the stats, but I would like to hear if anyone has another idea. (I have calculated that I am using 26custom-values for heroes right now (with a small chance of reduction, I don't think I would be using anything more), so a text-message which shows 26lines of text is maybe not the best way)

Edit: I have encountered a very annoying glitch (which doesn't seem to be very unusual); the TriggerSleepAction is completely bugged.. It seems to do the same as SkipRemainingActions in many cases... Here is the the last part of the code;
Code (jass) Select
1
2
    //other code above here
3
    call TriggerSleepAction( 0.50 )
4
    set i = 0 //i is just a integer used for loops here
5
    loop
6
        exitwhen i > ( 4 + ( udg_herolevelmodifier[casterOId] + casterlvl ) )
7
        call UnitDamageTarget( caster, targets[i], damageCend[i], FALSE, TRUE, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS )
8
        set targets[i] = null
9
        call DisplayTextToForce( GetPlayersAll(), I2S(i))
10
        set i = i + 1
11
    endloop
12
    set targetpoint = null
13
    call DestroyGroup(ug)
14
    set ug = null
15
    set caster = null
16
    set target = null
17
    set dummy = null
18
    set targetO = null
19
    set casterO = null
20
    return true
21
    else
22
    endif
23
return false
24
endfunction
25

The code doesn't generate any error-messages, and works completely fine when TriggerSleepAction is deactivated or removed. But active, nothing after it will do anything at all. This wait is used to get a visual sync between the fireballs created by dummies earlier in the code, and isn't REALLY necessary for gameplay, but still, the bug is annoying. Heard about PolledWaits, but after both read it's very slow and by taking a quick look at the function and thinking the bug should not be fixed since that function also uses TriggerSleepAction, it feels like it really isn't a good idea at all. Now, the remaining option of what I can see, is a timer, but this trigger have a great chance of running multiple times, so I can't use globals for the trigger that would run when the timer runs out. Is it at this point that I should learn about some other kinds of variables (I don't know how privates work..)?
13 years
Quote from: rvonsonsnadtz on October 17, 2012, 03:27:15 PM
Ok, but perhaps I'll stick with the ability-things for the moment (I use a "Vulnerable to"/"Resistant to"-system instead of the ordinarie fixed armor-type-system, which gives me the ability to hand-tailor the damage-modifiers for every unit I would use, and the abilities serves as a teller for the player of what the unit is vulnerable to)
However, since Heroes uses alot of more values than in the vanilla wc3 (and the damage-values is very missleading) I would like to use a multiboard (or similar) to show this values. However.. It seems impossible to just show a specific multiboard for just one player, while I show another multiboard for another one.. I could use a chattext-triggered function to show all the stats, but I would like to hear if anyone has another idea. (I have calculated that I am using 26custom-values for heroes right now (with a small chance of reduction, I don't think I would be using anything more), so a text-message which shows 26lines of text is maybe not the best way)
Yes you can show a multiboard per player, but you must use jass to code that part. Using the awesome GetLocalPlayer() function (Be aware about this one because it can cause desyncs if not used properly)

QuoteEdit: I have encountered a very annoying glitch (which doesn't seem to be very unusual); the TriggerSleepAction is completely bugged.. It seems to do the same as SkipRemainingActions in many cases... Here is the the last part of the code;
Code (jass) Select
1
2
    //other code above here
3
    call TriggerSleepAction( 0.50 )
4
    set i = 0 //i is just a integer used for loops here
5
    loop
6
        exitwhen i > ( 4 + ( udg_herolevelmodifier[casterOId] + casterlvl ) )
7
        call UnitDamageTarget( caster, targets[i], damageCend[i], FALSE, TRUE, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS )
8
        set targets[i] = null
9
        call DisplayTextToForce( GetPlayersAll(), I2S(i))
10
        set i = i + 1
11
    endloop
12
    set targetpoint = null
13
    call DestroyGroup(ug)
14
    set ug = null
15
    set caster = null
16
    set target = null
17
    set dummy = null
18
    set targetO = null
19
    set casterO = null
20
    return true
21
    else
22
    endif
23
return false
24
endfunction
25

The code doesn't generate any error-messages, and works completely fine when TriggerSleepAction is deactivated or removed. But active, nothing after it will do anything at all. This wait is used to get a visual sync between the fireballs created by dummies earlier in the code, and isn't REALLY necessary for gameplay, but still, the bug is annoying. Heard about PolledWaits, but after both read it's very slow and by taking a quick look at the function and thinking the bug should not be fixed since that function also uses TriggerSleepAction, it feels like it really isn't a good idea at all. Now, the remaining option of what I can see, is a timer, but this trigger have a great chance of running multiple times, so I can't use globals for the trigger that would run when the timer runs out. Is it at this point that I should learn about some other kinds of variables (I don't know how privates work..)?
Hmmm, triggersleepaction can only be used in an action function. It can not be used in loops or in conditional functions. And seeing your situation yes, you're reaching the point where you need to explore the usage of timers. This feature used properly will make your map accurate and well developed.

13 years
edited 13 years
Ok, thanks! (:
But I really have no clue of how to do a very MUI-trigger using a timer? An array was a idea, but the size would need to be quite big... Cause, I don't know a safe, MUI, good way of using the data from one function to the function that starts after the timer..

EDIT: I fixed it. The only thing worring me is that I am using a loop to detect which array the timer is using, and since the size is on 1050 before reseting to 0, I am worried about the process-part.
13 years
edited 13 years
Moyack,if u haven't already figured it out, here is the angle-calculation of the line-spell;
(((Area/2) / Dist) * (180 / pii)) + ((180/pii) * Atan2(TPY - EPY, TPX - EPX))  >
(180/pii) * Atan2 (TPY- CPY, TPX - CPX) >
(((Area/2) / Dist) * (180 / pii)) - ((180/pii) * Atan2(TPY - EPY, TPX - EPX))

Area = Area of the spell
Dist = the distance from the unit to the caster(calc.: ((Target X - Caster X)^2) + ((Target Y - Caster Y)^2)
TPX/TPY = Target Unit X or Y
CPX/CPY = Casterunits X or Y
EPX/EPY =End X or Y of the line-group. However, this point doesn't necessary point out the range of the grouping, it is just a reference-point for the spells angle.
13 years
First of all, sorry for my delayed response, real life has been a bitsh with me ;)


Quote from: rvonsonsnadtz on October 19, 2012, 05:48:11 AM
Ok, thanks! (:
But I really have no clue of how to do a very MUI-trigger using a timer? An array was a idea, but the size would need to be quite big... Cause, I don't know a safe, MUI, good way of using the data from one function to the function that starts after the timer..
You have to use hashtables or a timer utils or manage one timer that loops through an array. Personally I love the last option.

QuoteEDIT: I fixed it. The only thing worring me is that I am using a loop to detect which array the timer is using, and since the size is on 1050 before reseting to 0, I am worried about the process-part.
You don't have to worry, the idea is to make it in such way that the data can recycle elements. You normally don't need to run a code for 1050 units at the same time, in fact if it happens, you're doing a bad approach to the problem.

Quote from: rvonsonsnadtz on October 20, 2012, 10:52:35 AM
Moyack,if u haven't already figured it out, here is the angle-calculation of the line-spell;
((Area / Dist) * (180 / pii)) + ((180/pii) * Atan2(TPY - CPY, TPX - CPX))  >
(180/pii) * Atan2 (TPY- CPY, TPX - CPX) >
((Area / Dist) * (180 / pii)) - ((180/pii) * Atan2(TPY - CPY, TPX - CPX))

Area = Area of the spell
Dist = the distance from the unit to the caster(calc.: ((Target X - Caster X)^2) + ((Target Y - Caster Y)^2)
TPX/TPY = Target Unit X or Y
CPX/CPY = Casterunits X or Y
My worry implies in that there's a few chance to catch a unit because it should be exactly at the same angle, and that situation normally doesn't happen :(

You need to have a kind of "range" so it can cast more units in a more or less lined way.



13 years
edited 13 years
Quote from: moyack on October 20, 2012, 11:57:42 AM

My worry implies in that there's a few chance to catch a unit because it should be exactly at the same angle, and that situation normally doesn't happen :(

You need to have a kind of "range" so it can cast more units in a more or less lined way.


It works now, sure, the math wasn't completely right, but I shall change the calculation. Btw, is there a place where I can upload a map to show my succes? (x
1
2
function LineKillFilter takes nothing returns boolean
3
local unit u = GetFilterUnit()
4
local real unitx = GetUnitX(u)
5
local real unity = GetUnitY(u)
6
local real startx = GetUnitX(gg_unit_hfoo_0001)
7
local real starty = GetUnitY(gg_unit_hfoo_0001)
8
local real endx = GetUnitX(gg_unit_hfoo_0002)
9
local real endy = GetUnitY(gg_unit_hfoo_0002)
10
local real a1 = 57.2958 * Atan2(unity - starty, unitx - startx)
11
local real a2 = 57.2958 * Atan2(endy - starty, endx - startx)
12
local real distance = SquareRoot(((unitx - startx) * (unitx - startx)) + ((unity - starty) * (unity - starty)))
13
    if (a1) < (((100/distance) * 57.2958) + (a2)) then //The number 100 is the area of the line divided by 2, and could be changed to any value.
14
        if (a1) > ((a2) - ((200/distance) * 57.2958) ) then
15
            call KillUnit(GetFilterUnit()) // 57.2958 is the round off from 180/pii
16
            set u = null
17
            return TRUE
18
        else
19
        endif
20
    else
21
    endif
22
    return FALSE
23
endfunction
24
25
function Trig_linekill_Actions takes nothing returns nothing
26
local group g = CreateGroup()
27
local real x1 = GetUnitX(gg_unit_hfoo_0001)
28
local real x2 = GetUnitX(gg_unit_hfoo_0002)
29
local real y1 = GetUnitY(gg_unit_hfoo_0001)
30
local real y2 = GetUnitY(gg_unit_hfoo_0002)
31
local real dx = x1 + 500 * Cos(Atan2(y2 - y1, x2 - x1))
32
local real dy = y1 + 500 * Sin(Atan2(y2 - y1, x2 - x1))
33
call GroupEnumUnitsInRange(g, dx, dy, 1000.00 , Condition(function LineKillFilter))
34
endfunction
35
36
//===========================================================================
37
function InitTrig_linekill takes nothing returns nothing
38
local trigger t = CreateTrigger()
39
    call TriggerRegisterPlayerChatEvent( t, Player(0), "-kill", false )
40
    call TriggerAddAction( t, function Trig_linekill_Actions )
41
    set t = null
42
endfunction
43
44

This code will kill everyone between the 2 footmen on my map (right now including the second footman) in an area of 200 and a range of 1000

EDIT: The prior error is fixed