• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!

[JASS] Help With Learning Stucts

Status
Not open for further replies.
Level 6
Joined
Jun 30, 2006
Messages
230
Okay, I think I understand the basic concept of structs. In my last ability I made, Blur, I used HandleVars to simple store a unit so I could adjust its vertex color. I tried to understand structs earlier, but my knowledge of JASS was simply not enough.

Anyways, I can easily store a unit in a struct and call it up in my refresh function. The problem is, it's only for 1 unit. It's no better than a single-global. I don't really get methods in structs. I understand them for PHP, but my mind must not be thinking correctly or they don't work the same... I'm obviously going to need a few globals.
JASS:
struct BlurData
    unit u
    boolean wantDestroy = false
endstruct
globals
    integer array Blur //this is simply to store a few constant integers for the spell, it has nothing to do with the structs part.
    BlurData array BlurData_Ar
    integer BlurData_total = 0
endglobals
Here's what I have so you can see what's kind of going on:
JASS:
function Refresh takes nothing returns nothing
    local unit u=BlurData.u //need to call up the unit, I realize this is not how you do it, it is just a placeholder.
    local integer i=0
    local integer int
    loop
        exitwhen i==BlurData_total
        set dat= BlurData_Ar[i]
        if (dat.wantDestroy) then
             set  BlurData_Ar[i]= BlurData_Ar[ BlurData_total - 1]
             set BlurData_total=BlurData_total-1
             call dat.destroy() 
             set i=i-1
        else
            set int=GetUnitAbilityLevel(u,Blur[0])
            if(GetUnitState(u,UNIT_STATE_LIFE)>1)then
                call SetUnitVertexColor(u,255,255,255,Blur[int])
            endif
        endif
        set i=i+1
    endloop
    if (BlurData_total==0) then
        // this is from the tut I read.  In the end I want all units to use the same timer, but that's details for now.
        call PauseTimer(Blur_timer)
    endif
endfunction
function Conditions takes nothing returns boolean
    set Blur[0] = 'A001'
    set Blur[1] = 60
    set Blur[2] = 40
    set Blur[3] = 20
    set Blur[4] = 2
    return GetLearnedSkill()==Blur[0] and IsUnitIllusion(GetTriggerUnit())==false
endfunction
function Actions takes nothing returns nothing
    local integer i=GetUnitAbilityLevel(GetTriggerUnit(),Blur[0])
    local trigger t
    local unit lu=GetTriggerUnit()
    local BlurData u = BlurData.create()
    if(i==1.0)then
        call SetUnitVertexColor(lu,255,255,255,Blur[i])
        set t=CreateTrigger()
        call TriggerAddAction(t,function Refresh)
        call TriggerRegisterTimerEventPeriodic(t,2)
        set u.u=lu
    else
        call SetUnitVertexColor(lu,255,255,255,Blur[i])
    endif
endfunction
function InitTrig_Blur takes nothing returns nothing
    set gg_trg_Blur=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Blur,EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(gg_trg_Blur,Condition(function Conditions))
    call TriggerAddAction(gg_trg_Blur,function Actions)
endfunction

Could someone please give a simple example of a way to store a unit using structs every time someone learns a specific ability, then call each unit in another function and do something with it? My "do something" is only setting a vertex color, so it doesn't need to be elaborate. I can do the work for my own spell myself, I just need to understand this concept better. All the tutorials I've seen do somewhat complicated things with each unit. I think I'm getting lost in what they are doing rather than concentrating on the concept. So if someone could give a simple example, that would be great.
Your help would be greatly appreciated!
 
Last edited:
Level 19
Joined
Aug 24, 2007
Messages
2,888
storing them in
SomeUnitGlobalArrayedVariable[H2I(<unit>)-0x100000] would be better I guess

you want to store one unit
structs are for storing multiple variables for one array
struct a
unit u
real r
enstruct

is like

globals
unit array s__a_u
real array s__a_r
endglobals

only differences I know
struct can have methods (insider functions)
and they can be destroyed per array (like arrayvar.destroy() )
and they called like groups so if you call s__a_u with an array you call other with same array

Sorry Im not so pro with these too (whatever someone pro will help you in a short time I guess)

Btw you have H2I right ?
[jass="H2I"]function H2I takes handle h returns integer
return h
return 0
endfunction[/code]
Well you may have H2I but you may dont know what does it do
H2I gets unique ID of a handle (unit-location-group etc etc)
what is 0x100000 ?
IDK too I guess its for debugging because H2I(<somehandle>) gives a very big number (that you cant store it in array)
H2I(<somehandle>)-0x100000 gives a smaller number

I hope if you understand (I didnt get what I said actually)
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
0x100000 is the null handle index (1048576 in decimal).

One of the good ways to 'attach' structs to handles would be what Need_O2 said: storing in an array variable :
JASS:
globals
    integer array Stored
endglobals
function H2I takes handle h returns integer
    return h
    return 0
endfunction
function Get takes handle h returns integer
    return Stored[H2I(h)-0x100000]
endfunction
function Store takes handle h, integer structs returns nothing
    set Stored[H2I(h)-0x100000]=structs
endfunction
Here's the complete system by grim001 : http://wc3campaigns.net/pastebint.php?t=93235&code=29e26baa170b05af2d3ffd04408ae7e1

You can also use UnitUserData (custom value) for storing on units, and offset the timer's timeout (static timeouts only) (if you want more detail on this technique, just say so). Of course you can always use gamecache.
 
Level 6
Joined
Jun 30, 2006
Messages
230
The whole point of using structs for me was to avoid any handle vars in this instance. H2I is slow, while structs are basically arrays which are fast. I'm thinking that I'll register with wc3campaigns and get help there because there is a considerably larger group who uses structs over there.

Thanks for your help anyways, guys.
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
I'm telling you, H2I(h)-0x100000 is THE fastest way to attach structs (integers) to handles. H2I is not slow at all. What's slow in gamecache is the actual gamecache natives, the string leakage, and the I2S(H2I(h)) calls. H2I by itself is actually pretty fast.
 
Level 6
Joined
Jun 30, 2006
Messages
230
So HINDYhat, here is my pseudo-code:
JASS:
struct BlurData
    unit u
    boolean wantDestroy = false
endstruct
globals
    integer array Blur 
    BlurData array BlurData_Ar
    integer BlurData_total = 0
endglobals
function BlurRefresh takes nothing returns nothing
    local unit u //an unnecessary variable I think, it's just a placeholder for the current unit
    local integer i
    //here I need to grab the struct data, then
    //loop through each unit if I end up needing an array
        set i=GetUnitAbilityLevel(u,Blur[0])
        if(GetUnitState(u,UNIT_STATE_LIFE)>1)then //there was some reason I only set it if they had life, but the reason is forgotten.  This may be an unnecessary if.
            call SetUnitVertexColor(u,255,255,255,Blur[i])
        endif
    //endloop
endfunction
function BlurConditions takes nothing returns boolean 
    set Blur[0] = 'A000'
    set Blur[1] = 60
    set Blur[2] = 40
    set Blur[3] = 20
    set Blur[4] = 2
    return GetLearnedSkill()==Blur[0] and IsUnitIllusion(GetTriggerUnit())==false
endfunction
function BlurActions takes nothing returns nothing
    local unit u=GetTriggerUnit()
    local integer i=GetUnitAbilityLevel(u,Blur[0])
    local trigger t
    //I think I need a local BlurData b=BlurData.create(), don't I?
    if(i==1)then
        call SetUnitVertexColor(u,255,255,255,Blur[i])
        //I really only need one timer for ALL the instances, don't I?  Not sure how I'd do that...
        set t=CreateTrigger()
        call TriggerRegisterTimerEvent(t,2, true)
        call TriggerAddAction(t,function BlurRefresh)
        //add the unit to my struct array
        //something like set b.u=u?
    else
        call SetUnitVertexColor(u,255,255,255,Blur[i])
    endif
endfunction
function InitTrig_Blur takes nothing returns nothing
    set gg_trg_Blur=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Blur,EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(gg_trg_Blur,Condition(function BlurConditions))
    call TriggerAddAction(gg_trg_Blur,function BlurActions)
endfunction
What would you recommend next? PLEASE use an example. I learn best that way. The best way is for you to do it for me, then I take it into my game, stick it in, get it working, then modify it piece by piece to see exactly how it effects the trigger. You may or may not have time for that, just do what you can. Any help is appreciated!
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
Crap, I just typed out my whole post and lost it -.- Time to start over.

Okay, why are you using periodic triggers? If you'd use timers, it would be so much easier to attach structs to them, and faster. Here's a way (credit to DiscipleOfLife):
JASS:
function SomethingElse takes nothing returns nothing
    local SomeStruct s=SomeStruct(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-TIMEOUT))+1)
    call DestroyEffect(AddSpecialEffectTarget("SomeEffectHere",s.u,"origin"))
endfunction

function Something takes nothing returns nothing
    local SomeStruct s=SomeStruct.create()
    set s.u=GetTriggerUnit()
    call TimerStart(CreateTimer(),TIMEOUT+I2R(integer(s))/10000000,true,function SomethingElse)
endfunction
The integer(s) and SomeStruct(bleh) syntax is unnecessary (it's just typecasting from struct to integer and vice-versa, which is unnecessary since structs are just pointers to array indexes). It simply makes the code cleaner.

Anyway, what I do is I offset the timer's timeout (which is a constant TIMEOUT) by the struct's ID divided by a huge number. Then, in the callback function, I solve for the ID. This is an extemely fast method, except it only works with constant timeouts.

In your case, you COULD work with only one timer, but you'd need a stack of variables (which I only got to work once thanks to Earth-Fury). I'd stick with multiple timers and attaching the structs using the method described above.

Now, in response to your comments:
//I think I need a local BlurData b=BlurData.create(), don't I?
Maybe you shouldn't create it in the local declaration, because you won't use it if (i!=1). So just declare it, and create it in the if statement.

//I really only need one timer for ALL the instances, don't I? Not sure how I'd do that...
Yes, but you'd need a stack, and I don't remember how that works :p

//something like set b.u=u?
Yup. Giving a struct's member a value works like this:
set <structname>.<membername> = <value>
So in your case, set b.u = u would be good.

Using what I said above about offsetting a timer's timeout, you should be able to remake this without using triggers, and easily retrieve the struct's ID in the timer callback function.
 
Level 6
Joined
Jun 30, 2006
Messages
230
Okay, why are you using periodic triggers? If you'd use timers, it would be so much easier to attach structs to them, and faster.
Because I've never worked with timers? Sounds like a good reason to use periodic triggers... Time to learn time, eh?
Here's a way (credit to DiscipleOfLife):
JASS:
function SomethingElse takes nothing returns nothing
    local SomeStruct s=SomeStruct(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-TIMEOUT))+1)
    call DestroyEffect(AddSpecialEffectTarget("SomeEffectHere",s.u,"origin"))
endfunction

function Something takes nothing returns nothing
    local SomeStruct s=SomeStruct.create()
    set s.u=GetTriggerUnit()
    call TimerStart(CreateTimer(),TIMEOUT+I2R(integer(s))/10000000,true,function SomethingElse)
endfunction

That's great, I'm sure, but I'm not sure how to use it. When I post more code, we'll find out, eh?

Anyway, what I do is I offset the timer's timeout (which is a constant TIMEOUT) by the struct's ID divided by a huge number. Then, in the callback function, I solve for the ID. This is an extemely fast method, except it only works with constant timeouts.
Divided by what number? Struct ID..? Nice theory, but could you do that part for me? I understand the theory behind it, but as far as doing it... I'm not getting it that part.

In your case, you COULD work with only one timer, but you'd need a stack of variables (which I only got to work once thanks to Earth-Fury). I'd stick with multiple timers and attaching the structs using the method described above.
Okay, for now, no stacks.
Maybe you shouldn't create it in the local declaration, because you won't use it if (i!=1). So just declare it, and create it in the if statement.
Well I was thinking we'd only have to add it to an array once (which I thought we would probably be using). So only if it was level 1 would you add. But the way we are going to do it we don't need the if at all, do we?

I'm going to do some coding and I'll edit to add it.
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
Divided by what number? Struct ID..? Nice theory, but could you do that part for me? I understand the theory behind it, but as far as doing it... I'm not getting it that part.
No, divided by a huge number like 10000000. See what I do is set the timeout to : TIMEOUT + (structID)/10000000. So the (structID)/10000000 is such an insignifact difference to the regular TIMEOUT, barely any change is notable. It's like solving for 'x', if 'x' is the struct's ID:

t2 = t1 + x/10000000 (so let's imagine that t1 is 0.03)
t2 = 0.03 + x/10000000

In the timer callback function, we HAVE the value of t2 (which would be found by calling TimerGetTimeout(GetExpiredTimer()) ), so we have to solve for 'x', since t1 is a constant value (0.03):

t2 = 0.03 + x/10000000
10000000*(t2-0.03) = x

And due to some lame R2I() rounding bug, we have to add 1 to the value of 'x'. So we end with this beautiful equation:
10000000*(t2-0.03) + 1 = x

So if we substitute back our variables...
10000000*(t2-TIMEOUT) + 1 = StructID
 
Level 6
Joined
Jun 30, 2006
Messages
230
JASS:
struct BlurUnit
    unit u
endstruct
globals
    integer array Blur 
endglobals
function BlurRefresh takes nothing returns nothing
    local BlurUnit=BlurUnit(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-TIMEOUT))+1)
    local integer i=GetUnitAbilityLevel(s.u,Blur[0])
    call SetUnitVertexColor(s.u,255,255,255,Blur[i])
endfunction
function BlurConditions takes nothing returns boolean 
    set Blur[0] = 'A000'
    set Blur[1] = 60
    set Blur[2] = 40
    set Blur[3] = 20
    set Blur[4] = 2
    return GetLearnedSkill()==Blur[0] and IsUnitIllusion(GetTriggerUnit())==false
endfunction
function BlurActions takes nothing returns nothing
    local BlurUnit b=BlurUnit.create()
    local unit u=GetTriggerUnit()
    local integer i=GetUnitAbilityLevel(u,Blur[0])
    local trigger t
    set b.u=u
    call TimerStart(CreateTimer(),TIMEOUT+I2R(integer(s))/10000000,true,function BlurRefresh)
endfunction
function InitTrig_Blur takes nothing returns nothing
    set gg_trg_Blur=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Blur,EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(gg_trg_Blur,Condition(function BlurConditions))
    call TriggerAddAction(gg_trg_Blur,function BlurActions)
endfunction

I'm still not getting it, if you could just plug it in and show me, I'm sure it will make sense. I need to set TIMEOUT to a global, I would guess?
Edit: it just clicked. Except, what should I set "t1" to? And replace integer(s) with what? It has to be something, we can't just leave it at an "x", can we?
Edit2: integer(s) should be our variable b, shouldn't it?
 
Last edited:
Level 20
Joined
Apr 22, 2007
Messages
1,960
TIMEOUT just has to be a constant value (in your case, 2).

Some problems with your code:
local trigger t in function BlurActions... ehm... what's it doing there?
Same for local integer i
Same for local unit u. You could simply do:
JASS:
local BlurUnit b=BlurUnit.create()
set b.u=GetTriggerUnit()

In the TimerStart calls, you didn't replace the 's' variable by your struct (which in this case would be 'b').

local BlurUnit in function BlurRefresh. The local has no declared name :p
Again, replace TIMEOUT by 2.

The timer is neverending. Maybe you should store an integer in the struct too, to count the iterations.

EDIT: Why don't you log into the chat room so we can converse better?
 
Level 6
Joined
Jun 30, 2006
Messages
230
JASS:
struct BlurUnit
    unit u
endstruct
globals
    integer array Blur
    real TIMEOUT = 2
endglobals
function BlurRefresh takes nothing returns nothing
    local BlurUnit b=BlurUnit(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-TIMEOUT))+1)
    local integer i=GetUnitAbilityLevel(b.u,Blur[0])
    call SetUnitVertexColor(b.u,255,255,255,Blur[i])
endfunction
function BlurConditions takes nothing returns boolean 
    set Blur[0] = 'A000'
    set Blur[1] = 60
    set Blur[2] = 40
    set Blur[3] = 20
    set Blur[4] = 2
    return GetLearnedSkill()==Blur[0] and IsUnitIllusion(GetTriggerUnit())==false
endfunction
function BlurActions takes nothing returns nothing
    local BlurUnit b=BlurUnit.create()
    set b.u=GetTriggerUnit()
    call TimerStart(CreateTimer(),TIMEOUT+I2R(b)/10000000,true,function BlurRefresh)
endfunction
function InitTrig_Blur takes nothing returns nothing
    set gg_trg_Blur=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Blur,EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(gg_trg_Blur,Condition(function BlurConditions))
    call TriggerAddAction(gg_trg_Blur,function BlurActions)
endfunction
How's that?
 
Level 6
Joined
Jun 30, 2006
Messages
230
I have an interesting bug. Create a test map and stick two units who have an ability A000 and set their level to at least 7. Doesn't matter what ability it is or what it does, just make it. Create a trigger called Blur and stick the following code inside. Save and run the map. Level up Unit A's ability to level 1. Then level up Unit B's ability to level 1. When I do this, Unit B's vertex coloring does not get set. If you level Unit B's ability again, IT SETS CORRECTLY! Why? I've attached a replay. Edit: It won't let me attach the replay, so I just attached a map you can test it out for yourself on.

If you uncomment
JASS:
//local integer i=GetUnitAbilityLevel(GetTriggerUnit(),AID_BLUR)
and
JASS:
//call SetUnitVertexColor(b.u,255,255,255,Blur[i])
then it works because... I am not sure why but it does.
JASS:
struct BlurUnit
    unit u
endstruct

scope Blur

globals
    constant integer AID_BLUR='A000'
    integer array Blur
endglobals

private function Refresh takes nothing returns nothing
    local BlurUnit b=BlurUnit(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-1))+1)
    local integer i=GetUnitAbilityLevel(b.u,AID_BLUR)
    call SetUnitVertexColor(b.u,255,255,255,Blur[i])
endfunction
private function Conditions takes nothing returns boolean
    return GetLearnedSkill()==AID_BLUR and IsUnitIllusion(GetTriggerUnit())==false
endfunction
private function Actions takes nothing returns nothing
    local BlurUnit b=BlurUnit.create()
    //local integer i=GetUnitAbilityLevel(GetTriggerUnit(),AID_BLUR)
    set b.u=GetTriggerUnit()
    set Blur[1] = 80
    set Blur[2] = 60
    set Blur[3] = 40
    set Blur[4] = 20
    //call SetUnitVertexColor(b.u,255,255,255,Blur[i])
    call TimerStart(CreateTimer(),1+I2R(b)/10000000,true,function Refresh)
endfunction
public function InitTrig takes nothing returns nothing
    local trigger trig=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(trig,Condition(function Conditions))
    call TriggerAddAction(trig,function Actions)
endfunction

endscope
@HINDYhat Is my code a bit cleaner? :)
 

Attachments

  • Blur.w3x
    18.2 KB · Views: 85
Last edited:
Level 20
Joined
Apr 22, 2007
Messages
1,960
Blue_Jeans, I'm not sure about this, but I think that with your event, you'll need to use something like TimerStart(CreateTimer(),0.,false,function Actions_Child) in your actions (and transfer all of your regular actions to the Actions_Child function), to avoid the delay between learning the ability and actually registering it.

About your code:
Why isn't the BlurUnit struct private to the Blur scope?
Why are you initializing Blur[]'s values in the triggeraction? You should initialize it at least in the InitTrig.
Why aren't your custom globals private to the Blur scope?
The Refresh function can be simplified in one line, but for code-reading's sake, we'll leave it like this.

Other than that, sounds okay. Try what I said about a one time 0 second timeout timer... as far as I know, that should fix the problem.

If it still doesn't work, place some debug messages around your code (using call BJDebugMsg("Some string here")) to see if it's actually running. Also, do you have war3err on? It detects thread crashes (although I doubt there would be one here).

I have to go to sleep now, and school tomorrow, so I probably won't have the time to deal with this until tomorrow night. Bleh...
 
Level 6
Joined
Jun 30, 2006
Messages
230
Its not the delay I'm worried about. Unit B's Vertex Coloring NEVER gets set at level 1. Uncommenting those two lines will get rid of delay if that's what I want to do.
HINDYhat said:
Why isn't the BlurUnit struct private to the Blur scope?
Why are you initializing Blur[]'s values in the triggeraction? You should initialize it at least in the InitTrig.
Why aren't your custom globals private to the Blur scope?
The Refresh function can be simplified in one line, but for code-reading's sake, we'll leave it like this.
1. I think that BlurUnit will eventually be renamed as it is a basic struct I'm sure I'll need in other functions. That's why it's outside the scope. It will be renamed, of course.
2. Uh, good point.
3. I didn't know you could make those globals scope specific. Do I just add private before?
4. Yeah, I realized you could stick it into:
JASS:
SetUnitVertexColor(BlurUnit(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-1))+1).u,255,255,255,Blur[GetUnitAbilityLevel(BlurUnit(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-1))+1).u,AID_BLUR)])
or at least that is close. I'm not on a computer with a decent editor for anything, but I'll repost my code when I get home.

Edit: My current trigger:
JASS:
scope Blur

struct StructUnit
    unit u
endstruct
globals
    private constant integer AID_BLUR='A000'
    private integer array Blur
endglobals

private function Refresh takes nothing returns nothing
    //I still set this to a variable even though I don't need to. It's because I'm going 
    //to guess that those calculations done twice is roughly the same speed as
    //storing it to a variable, but the variable is easier to read.  I did get rid of the
    //integer i, though.  GetUnitAbilityLevel(s.u,AID_BLUR) is pretty easy to read.
    local StructUnit s=StructUnit(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-1))+1) 
    call SetUnitVertexColor(s.u,255,255,255,Blur[GetUnitAbilityLevel(s.u,AID_BLUR)])
endfunction
private function Conditions takes nothing returns boolean
    return GetLearnedSkill()==AID_BLUR and IsUnitIllusion(GetTriggerUnit())==false
endfunction
private function Actions takes nothing returns nothing
    local StructUnit s=StructUnit.create()
    set s.u=GetTriggerUnit()
    call SetUnitVertexColor(s.u,255,255,255,Blur[GetUnitAbilityLevel(s.u,AID_BLUR)])
    call TimerStart(CreateTimer(),1+I2R(s)/10000000,true,function Refresh)
endfunction
public function InitTrig takes nothing returns nothing
    local trigger trig=CreateTrigger()
    set Blur[1] = 90
    set Blur[2] = 65
    set Blur[3] = 40
    set Blur[4] = 20
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(trig,Condition(function Conditions))
    call TriggerAddAction(trig,function Actions)
endfunction

endscope
 
Last edited:
Level 6
Joined
Jun 30, 2006
Messages
230
Okay. I understood the last one pretty easily. My next trigger I want to use structs on is Fluid Critical Strike, an ability I thought up and Paskovich made and then I made some decent adjustments to. Basically, it is a critical strike that does more damage and/or goes off more frequently when the unit it is attacking is lower on life. This one is important to have code efficiency as it is run anytime a unit with the ability attacks. Most likely this will be an agility hero, so it will be fast.

The original trigger:
JASS:
globals
    gamecache MyCache 
endglobals
function InitCache takes nothing returns nothing
    call FlushGameCache(InitGameCache("cache.x"))
    set MyCache=InitGameCache("cache.x")
endfunction
// ===========================
function H2I takes handle h returns integer
    return h
    return 0
endfunction
function SetHandleHandle takes handle subject, string name, handle value returns nothing
    if value==null then
        call FlushStoredInteger(MyCache,I2S(H2I(subject)),name)
    else
        call StoreInteger(MyCache, I2S(H2I(subject)), name, H2I(value))
    endif
endfunction
function GetHandleUnit takes handle subject, string name returns unit
    return GetStoredInteger(MyCache, I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleTrigger takes handle subject, string name returns trigger
    return GetStoredInteger(MyCache, I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleTriggerCondition takes handle subject, string name returns triggercondition
    return GetStoredInteger(MyCache, I2S(H2I(subject)), name)
    return null
endfunction
function GetHandleTriggerAction takes handle subject, string name returns triggeraction
    return GetStoredInteger(MyCache, I2S(H2I(subject)), name)
    return null
endfunction
function FlushHandleLocals takes handle subject returns nothing
    call FlushStoredMission(MyCache, I2S(H2I(subject)) )
endfunction

//=======================
//Spell options
//=======================
constant function FCSAbilityRC takes nothing returns integer
	return 'A000' //The ability's raw code.
endfunction
constant function FCSDamageMultiplier takes integer level returns real
	return level * 1.8 //The critical strike damage multiplier.
endfunction
constant function FCSEffect takes nothing returns string
	return "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
endfunction
//=======================
//The spell
//=======================
function FluidCriticalStrikeDamageConditions takes nothing returns boolean
	return GetEventDamageSource() == GetHandleUnit(GetTriggeringTrigger(), "Attacker")
endfunction
function FluidCriticalStrikeDamageActions takes nothing returns nothing
	local trigger tr = GetTriggeringTrigger()
	local unit u = GetTriggerUnit()
	local unit a = GetEventDamageSource()
	local texttag t = CreateTextTag()
	local real dmg = GetEventDamage()*FCSDamageMultiplier(GetUnitAbilityLevel(a,FCSAbilityRC()))
	call TriggerRemoveCondition(tr,GetHandleTriggerCondition(tr,"Condition"))
	call TriggerRemoveAction(tr,GetHandleTriggerAction(tr,"Action"))
	call FlushHandleLocals(tr)
	call DestroyTrigger(tr)
	call SetHandleHandle(a, "DamageTrig", null)
	call UnitDamageTarget(a, u, dmg-GetEventDamage(), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
	call SetTextTagText(t, I2S(R2I(dmg))+"!",0.024)
	call SetTextTagPos(t,GetUnitX(a),GetUnitY(a), 0.00)
	call SetTextTagColor(t,255,0,0,255)
	call SetTextTagVelocity(t,0,0.04)
	call SetTextTagFadepoint(t, 1.2)
	call SetTextTagLifespan(t, 4.5)
	call SetTextTagPermanent(t, false)
	call SetTextTagVisibility(t, true)
	if FCSEffect() != "" then
		call DestroyEffect(AddSpecialEffectTarget(FCSEffect(),u,"chest"))
	endif
	set tr = null
	set u = null
	set a = null
	set t = null
endfunction
function FluidCriticalStrikeConditions takes nothing returns boolean
	return GetUnitAbilityLevel(GetAttacker(),FCSAbilityRC()) > 0 and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE) and GetHandleTrigger(GetAttacker(), "DamageTrig") == null
endfunction
function FluidCriticalStrikeActions takes nothing returns nothing 
	local unit a = GetAttacker()
	local unit u = GetTriggerUnit()
	local real hp = GetUnitState(u,UNIT_STATE_LIFE) / GetUnitState(u,UNIT_STATE_MAX_LIFE)
	local integer chc
	local trigger t
	local boolexpr be
	if hp < 0.30 then
		set chc = GetUnitAbilityLevel(a,FCSAbilityRC()) * 40
	elseif 0.65 < hp then
		set chc = GetUnitAbilityLevel(a,FCSAbilityRC()) * 15
	else
		set chc = GetUnitAbilityLevel(a,FCSAbilityRC()) * 25
	endif
	if chc >= GetRandomInt(1,100) then
		set t = CreateTrigger()
		call SetHandleHandle(a, "DamageTrig", t)
		call SetHandleHandle(t, "Attacker", a)
		call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_DAMAGED)
		set be = Condition(function FluidCriticalStrikeDamageConditions)
		call SetHandleHandle(t,"Condition", TriggerAddCondition(t, be))
		call DestroyBoolExpr(be)
		set be = null
		call SetHandleHandle(t,"Action", TriggerAddAction(t, function FluidCriticalStrikeDamageActions))
		call TriggerSleepAction(1.0)
		call FlushHandleLocals(t)
		call DestroyTrigger(t)
		call SetHandleHandle(a, "DamageTrig", null)
		set t = null
	endif
	set a = null
	set u = null
endfunction
//=======================
function InitTrig_FluidCriticalStrike takes nothing returns nothing
	set gg_trg_FluidCriticalStrike = CreateTrigger( )
	call TriggerRegisterAnyUnitEventBJ(gg_trg_FluidCriticalStrike, EVENT_PLAYER_UNIT_ATTACKED)
	call TriggerAddCondition(gg_trg_FluidCriticalStrike, Condition( function FluidCriticalStrikeConditions))
	call TriggerAddAction(gg_trg_FluidCriticalStrike, function FluidCriticalStrikeActions)
endfunction
My code so far:
JASS:
struct StructUnit
	unit a
	trigger t
endstruct

scope FluidCriticalStrike

//Spell options =========
globals
    private constant integer AID_FLUID_CRITICAL_STRIKE='A000'
    private constant string EFFECT_FLUID_CRITICAL_STRIKE="Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
endglobals
private constant function DamageMultiplier takes integer level returns real
	return 2.0 //The critical strike damage multiplier. I haven't balanced the ability yet, so for now it is just 2x damage
endfunction
//The spell =============
private function DamageConditions takes nothing returns boolean
	return GetEventDamageSource() == StructUnit.a
endfunction
private function DamageActions takes nothing returns nothing
	local trigger tr = GetTriggeringTrigger()
	local unit u = GetTriggerUnit()
	local unit a = GetEventDamageSource()
	local texttag t = CreateTextTag()
	local real dmg = GetEventDamage()*DamageMultiplier(GetUnitAbilityLevel(a,AID_FLUID_CRITICAL_STRIKE))
	//MakesNoSense
	//Why would you remove them?  Do you have to remove them first before you destroy the trigger
	//for it to be 'leak-less'? This is the only reason I can think of...
	call TriggerRemoveCondition(tr,Condition(function Conditions)
	call TriggerRemoveAction(tr,function Actions)
	call FlushHandleLocals(tr)
	call DestroyTrigger(tr)
	set tr = null
	//endMakesNoSense
	set StructUnit a.t=null
	call UnitDamageTarget(a, u, dmg-GetEventDamage(), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
	call SetTextTagText(t, I2S(R2I(dmg))+"!",0.024)
	call SetTextTagPos(t,GetUnitX(a),GetUnitY(a), 0.00)
	call SetTextTagColor(t,255,0,0,255)
	call SetTextTagVelocity(t,0,0.04)
	call SetTextTagFadepoint(t, 1.2)
	call SetTextTagLifespan(t, 4.5)
	call SetTextTagPermanent(t, false)
	call SetTextTagVisibility(t, true)
	if EFFECT_FLUID_CRITICAL_STRIKE != "" then
		call DestroyEffect(AddSpecialEffectTarget(EFFECT_FLUID_CRITICAL_STRIKE,u,"chest"))
	endif
	set u = null
	set a = null
	set t = null
endfunction
private function Conditions takes nothing returns boolean
	return GetUnitAbilityLevel(GetAttacker(),AID_FLUID_CRITICAL_STRIKE) > 0 and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE) and StructUnit.t == null
endfunction
private function Actions takes nothing returns nothing 
	local unit a = GetAttacker()
	local unit u = GetTriggerUnit()
	local real hp = GetUnitState(u,UNIT_STATE_LIFE) / GetUnitState(u,UNIT_STATE_MAX_LIFE)
	local integer chance=GetUnitAbilityLevel(a,AID_FLUID_CRITICAL_STRIKE)
	local trigger t
	//local boolexpr be
	local StructUnit s=StructUnit.create()
	set s.a=a
	if hp < 0.25 then
		set chance = 30 + chance * 15
	elseif hp > 0.50 then
		set chance = 10 + chance * 5
	else
		set chance = 20 + chance * 10
	endif
	if chance >= GetRandomInt(1,100) then
		set t = CreateTrigger()
		set s.t=t
		//call SetHandleHandle(t, "Attacker", a)
		set s.a=a
		call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_DAMAGED)
		
		//what would be the best way to convert the next commented lines into structs?
		//set be = Condition(function DamageConditions)
		//call SetHandleHandle(t,"Condition", TriggerAddCondition(t, be))
		//call DestroyBoolExpr(be)
		//set be = null
		//call SetHandleHandle(t,"Action", TriggerAddAction(t, function DamageActions))
		
		//call TriggerSleepAction(1.0)
		//call FlushHandleLocals(t)
		call DestroyTrigger(t)
		set s.t=null
		set t = null
	endif
	set a = null
	set u = null
endfunction
//=======================
public function InitTrig takes nothing returns nothing
    local trigger trig=CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ATTACKED)
	call TriggerAddCondition(trig, Condition( function Conditions))
	call TriggerAddAction(trig, function Actions)
endfunction

endscope
Edit: forgot to mention that I wasn't sure how to call the struct in the other functions as there aren't any timers... so for the time being I just said StructUnit.<variable>
 
Last edited:
Level 20
Joined
Apr 22, 2007
Messages
1,960
Best and most efficient way to attach the struct in this case would be to store it in the damaged unit's custom value, and then retrieve it in the actions of the EVENT_UNIT_DAMAGED trigger.

Or you could use the H2I(h)-0x100000 method with the newly created trigger too.

Got to go to school now, I might check this out later.
 
Level 6
Joined
Jun 30, 2006
Messages
230
Okay, I'm not on a computer with warcraft at the moment, but do you think that this would work?
JASS:
struct StructUnit
	unit a
	unit u
endstruct

scope FluidCriticalStrike

//Spell options =========
globals
    private constant integer AID_FLUID_CRITICAL_STRIKE='A000'
    private constant string EFFECT_FLUID_CRITICAL_STRIKE="Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
endglobals
private constant function DamageMultiplier takes integer level returns real
	return 2.0 //The critical strike damage multiplier. I haven't balanced the ability yet, so for now it is just 2x damage
endfunction
//The spell =============
private function DamageActions takes nothing returns nothing
	local StructUnit s=StructUnit(R2I(10000000*(TimerGetTimeout(GetExpiredTimer())-0))+1)
	local texttag t = CreateTextTag()
	local real damage = GetEventDamage()*DamageMultiplier(GetUnitAbilityLevel(s.a,AID_FLUID_CRITICAL_STRIKE))
	call UnitDamageTarget(s.a, s.u, damage-GetEventDamage(), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
	call SetTextTagText(t, I2S(R2I(damage))+"!",0.024)
	call SetTextTagPos(t,GetUnitX(s.a),GetUnitY(s.a), 0.00)
	call SetTextTagColor(t,255,0,0,255)
	call SetTextTagVelocity(t,0,0.04)
	call SetTextTagFadepoint(t, 1.2)
	call SetTextTagLifespan(t, 4.5)
	call SetTextTagPermanent(t, false)
	call SetTextTagVisibility(t, true)
	if EFFECT_FLUID_CRITICAL_STRIKE != "" then
		call DestroyEffect(AddSpecialEffectTarget(EFFECT_FLUID_CRITICAL_STRIKE,s.u,"chest"))
	endif
	set t = null
endfunction
private function Conditions takes nothing returns boolean
	return GetUnitAbilityLevel(GetEventDamageSource(),AID_FLUID_CRITICAL_STRIKE) > 0 and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE)
endfunction
private function Actions takes nothing returns nothing 
	local unit a = GetEventDamageSource()
	local unit u = GetTriggerUnit()
	local real hp = GetUnitState(u,UNIT_STATE_LIFE) / GetUnitState(u,UNIT_STATE_MAX_LIFE)
	local integer chance=GetUnitAbilityLevel(a,AID_FLUID_CRITICAL_STRIKE)
	local StructUnit s=StructUnit.create()
	set s.a=a
	set s.u=u
	if hp < 0.25 then
		set chance = 30 + chance * 15
	elseif hp > 0.50 then
		set chance = 10 + chance * 5
	else
		set chance = 20 + chance * 10
	endif
	if chance >= GetRandomInt(1,100) then
		call TimerStart(CreateTimer(),0+I2R(s)/10000000,false,function DamageActions)
	endif
	set a = null
	set u = null
endfunction
//=======================
public function InitTrig takes nothing returns nothing
    local trigger trig=CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(trig, EVENT_Unit_DAMAGED)
	call TriggerAddCondition(trig, Condition( function Conditions))
	call TriggerAddAction(trig, function Actions)
endfunction

endscope
 
Level 6
Joined
Jun 30, 2006
Messages
230
I've been very busy lately but this morning I had some time to play with it. I finally conquered this beast! I'm sure it can be optimized more, so I'm opening it up for constructive criticism.
JASS:
scope FluidCriticalStrike

globals
    private constant integer AID_FLUID_CRITICAL_STRIKE='A000'
    private constant string EFFECT_FLUID_CRITICAL_STRIKE="Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
endglobals

struct FCS
	trigger t
	triggercondition b
	triggeraction a
	unit attacker
	
	private method textTag takes real dmg, real x, real y returns nothing
		local texttag t=CreateTextTag()
		call SetTextTagText(t, I2S(R2I(dmg))+"!",0.024)
		call SetTextTagPos(t,x,y,0.00)
		call SetTextTagColor(t,255,0,0,255)
		call SetTextTagVelocity(t,0,0.04)
		call SetTextTagFadepoint(t,1.2)
		call SetTextTagLifespan(t,4.5)
		call SetTextTagPermanent(t,false)
		call SetTextTagVisibility(t,true)
		set t=null
	endmethod
	public method Damage takes unit a, unit u, real dmg returns nothing
		set dmg=dmg*(1+GetUnitAbilityLevel(a,AID_FLUID_CRITICAL_STRIKE)*.4)
		call UnitDamageTarget(a, u, dmg, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
		call this.textTag(dmg,GetUnitX(a),GetUnitY(a))
		if EFFECT_FLUID_CRITICAL_STRIKE != "" then
			call DestroyEffect(AddSpecialEffectTarget(EFFECT_FLUID_CRITICAL_STRIKE,u,"chest"))
		endif
	endmethod
	
endstruct

private function DamageConditions takes nothing returns boolean
	local FCS fcs=H2I(GetTriggeringTrigger())
	return GetEventDamageSource()==fcs.attacker
endfunction

private function DamageActions takes nothing returns nothing
	local trigger trig=GetTriggeringTrigger()
	local FCS fcs=H2I(GetTriggeringTrigger())
	call TriggerRemoveCondition(trig,fcs.b)
	call TriggerRemoveAction(trig,fcs.a)
	call DestroyTrigger(trig)
	set fcs.t=null
	call fcs.Damage(GetEventDamageSource(),GetTriggerUnit(),GetEventDamage())
	set trig=null
endfunction

private function Conditions takes nothing returns boolean
	return GetUnitAbilityLevel(GetAttacker(),AID_FLUID_CRITICAL_STRIKE) > 0 and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE)
endfunction

private function Actions takes nothing returns nothing 
	local unit u=GetTriggerUnit()
	local real hp=GetUnitState(u,UNIT_STATE_LIFE) / GetUnitState(u,UNIT_STATE_MAX_LIFE)
	local integer chance=GetUnitAbilityLevel(GetAttacker(),AID_FLUID_CRITICAL_STRIKE)
	local trigger trig
	local boolexpr be
	local FCS fcs=FCS.create()
	if hp<0.25 then
		set chance=30 + chance * 15
	elseif hp>0.50 then
		set chance=10 + chance * 5
	else
		set chance=20 + chance * 10
	endif
	if chance>=GetRandomInt(1,100) then
		set trig=CreateTrigger()
		set fcs.attacker=GetAttacker()
		set fcs.t=trig
		call TriggerRegisterUnitEvent(trig, u, EVENT_UNIT_DAMAGED)
		set be=Condition(function DamageConditions)
		set fcs.b=TriggerAddCondition(trig,be)
		call DestroyBoolExpr(be)
		set be=null
		set fcs.a=TriggerAddAction(trig, function DamageActions)
		call TriggerSleepAction(1.0)
		call DestroyTrigger(trig)
		set fcs.t=null
		set trig=null
	endif
	set u=null
endfunction

public function InitTrig takes nothing returns nothing
    local trigger trig=CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ATTACKED)
	call TriggerAddCondition(trig, Condition(function Conditions))
	call TriggerAddAction(trig, function Actions)
endfunction

endscope
 
Last edited:
Status
Not open for further replies.
Top