RVons SpellQuestions

Coding Help

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?
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
function Trig_Untitled_Trigger_001_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'AUan' ) ) then
        return false
    endif
    return true
endfunction

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
    call CreateNUnitsAtLoc( 1, 'hfoo', Player(0), GetUnitLoc(GetTriggerUnit()), bj_UNIT_FACING )
    // Etc, etc, etc...
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_001, EVENT_PLAYER_UNIT_SPELL_FINISH )
    call TriggerAddCondition( gg_trg_Untitled_Trigger_001, Condition( function Trig_Untitled_Trigger_001_Conditions ) )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
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
function Trig_Untitled_Trigger_001_Conditions takes nothing returns boolean
    local location L // I define a local variable so this code doesn't leak a location
    if GetSpellAbilityId() == 'AUan' then
        set L = GetUnitLoc(GetTriggerUnit())
        call CreateNUnitsAtLoc( 1, 'hfoo', Player(0), L, bj_UNIT_FACING )
        call RemoveLocation(L) // removes the location
    endif
    set L = null //
    return false // set it so nothing will run after this function, we3 don't need it :)
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_001, EVENT_PLAYER_UNIT_SPELL_FINISH )
    call TriggerAddCondition( gg_trg_Untitled_Trigger_001, Condition( function Trig_Untitled_Trigger_001_Conditions ) )
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
scope TestTrigger initializer init

private function Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'AUan' then
        call CreateUnit( Player(0), 'hfoo', GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), bj_UNIT_FACING )
        // Add more things to do...
    endif
    return false // set it so nothing will run after this function, we3 don't need it :)
endfunction

//===========================================================================
private function init takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_FINISH )
    call TriggerAddCondition( t, Condition( function Conditions ) )
    set t = null // to free handle counter
endfunction

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.
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
function exemple takes unit attacker, unit attacked returns nothing
local DamageC
  set DamageC = 50 //Just taking a number for example, the real code is a bit more complicated, and seldom the same
  call TriggerSleepAction( 0.50 )
  call UnitDamageTargetBJ(attacker, attacked, DamageC, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL)
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?
That's right, they're only valid inside the function you define.

Some small fixes:

Code (jass,2) Select
function exemple takes unit attacker, unit attacked returns nothing
local real DamageC // don't forget to add the type of the variable :)
  set DamageC = 50 //Just taking a number for example, the real code is a bit more complicated, and seldom the same
  call TriggerSleepAction( 0.50 )
  call UnitDamageTargetBJ(attacker, attacked, DamageC, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL)
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 :)
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
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)
Quote from: rvonsonsnadtz on October 14, 2012, 02:44:07 PM
Ok, 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
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,2,9) Select
function Cond1 takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), Player(attackerid)) == true and (GetFilterUnit() == attackedR) == false
endfunction


//...The function should be over this expresion, outside of the function. attackedR MUST be a global variable in order to make it work
function bla....
// code goes here...
    call GroupEnumUnitsInRange(unitsinrange, GetUnitX(attackedR), GetUnitY(attackedR), 300, Filter(function Cond1))
// ther's more code... blablabla...
endfunction

Just null everything which is not real, boolean, integer, string or code even if it extends handle without the agent type.
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.
Code (jass) Select
library UnitIdData initializer init requires Table // hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/

    globals
        // you could make the tables not private but encapsulation is a pretty good practice
        private Table Damage_table
        private Table Armor_table
    endglobals

    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
        set Damage_table[unit_id] = damage
        set Armor_table[unit_id] = armor
    endfunction

    private function Config takes nothing returns nothing
        /*
            here you will define Armor and Damage values according to the unit rawcode
            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"
        */

         call SetData( 'hfoo' , 42 , 666 )
    endfunction

    function GetUnitDamageBase takes unit which_unit returns integer
        return Damage_table[ GetUnitTypeId(which_unit) ]
    endfunction

    function GetUnitArmorBase takes unit which_unit returns integer
        return Armor_table[ GetUnitTypeId(which_unit) ]
    endfunction

    private function init takes nothing returns nothing
        set Damage_table = Table.create()
        set Armor_table = Table.create()
        call Config()
    endfunction

endlibrary


Note that there i use integers, you could use reals instead with some code editing, but is it really needed ?
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

    //other code above here
    call TriggerSleepAction( 0.50 )
    set i = 0 //i is just a integer used for loops here
    loop
        exitwhen i > ( 4 + ( udg_herolevelmodifier[casterOId] + casterlvl ) )
        call UnitDamageTarget( caster, targets[i], damageCend[i], FALSE, TRUE, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS )
        set targets[i] = null
        call DisplayTextToForce( GetPlayersAll(), I2S(i))
        set i = i + 1
    endloop
    set targetpoint = null
    call DestroyGroup(ug)
    set ug = null
    set caster = null
    set target = null
    set dummy = null
    set targetO = null
    set casterO = null
    return true
    else
    endif
return false
endfunction

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..)?
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

    //other code above here
    call TriggerSleepAction( 0.50 )
    set i = 0 //i is just a integer used for loops here
    loop
        exitwhen i > ( 4 + ( udg_herolevelmodifier[casterOId] + casterlvl ) )
        call UnitDamageTarget( caster, targets[i], damageCend[i], FALSE, TRUE, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS )
        set targets[i] = null
        call DisplayTextToForce( GetPlayersAll(), I2S(i))
        set i = i + 1
    endloop
    set targetpoint = null
    call DestroyGroup(ug)
    set ug = null
    set caster = null
    set target = null
    set dummy = null
    set targetO = null
    set casterO = null
    return true
    else
    endif
return false
endfunction

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.

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.
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.
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.



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

function LineKillFilter takes nothing returns boolean
local unit u = GetFilterUnit()
local real unitx = GetUnitX(u)
local real unity = GetUnitY(u)
local real startx = GetUnitX(gg_unit_hfoo_0001)
local real starty = GetUnitY(gg_unit_hfoo_0001)
local real endx = GetUnitX(gg_unit_hfoo_0002)
local real endy = GetUnitY(gg_unit_hfoo_0002)
local real a1 = 57.2958 * Atan2(unity - starty, unitx - startx)
local real a2 = 57.2958 * Atan2(endy - starty, endx - startx)
local real distance = SquareRoot(((unitx - startx) * (unitx - startx)) + ((unity - starty) * (unity - starty)))
    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.
        if (a1) > ((a2) - ((200/distance) * 57.2958) ) then
            call KillUnit(GetFilterUnit()) // 57.2958 is the round off from 180/pii
            set u = null
            return TRUE
        else
        endif
    else
    endif
    return FALSE
endfunction

function Trig_linekill_Actions takes nothing returns nothing
local group g = CreateGroup()
local real x1 = GetUnitX(gg_unit_hfoo_0001)
local real x2 = GetUnitX(gg_unit_hfoo_0002)
local real y1 = GetUnitY(gg_unit_hfoo_0001)
local real y2 = GetUnitY(gg_unit_hfoo_0002)
local real dx = x1 + 500 * Cos(Atan2(y2 - y1, x2 - x1))
local real dy = y1 + 500 * Sin(Atan2(y2 - y1, x2 - x1))
call GroupEnumUnitsInRange(g, dx, dy, 1000.00 , Condition(function LineKillFilter))
endfunction

//===========================================================================
function InitTrig_linekill takes nothing returns nothing
local trigger t = CreateTrigger()
    call TriggerRegisterPlayerChatEvent( t, Player(0), "-kill", false )
    call TriggerAddAction( t, function Trig_linekill_Actions )
    set t = null
endfunction


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