• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Solved] Essence of blight as an autocast base. + Turning Phoenix autocastable.

Status
Not open for further replies.
Level 14
Joined
Mar 11, 2017
Messages
587
Hi I've been making an autocast version of the Phoenix spell of Kael'thas. Not knowing which autocast spell to base it on, I chose essence of blight because of the possibility of not selecting a target.

I'm noticing that when mana runs low the essence of blight-based ability doesn't become blue and uncastable like the rest of the spells, not even at the lowest mana levels. Even though I thought I set the mana cost correctly.
I assume that I'm setting some values in the object editor wrong, and instead of spending a lot of time figuring this out on my own, I thought that if I'm lucky someone might know this off hand.

(Alternatively, I might be making some errors in the triggering work. This is intentionally not yet MUI, it's just to see if I was able to make it work prior to bothering with mui techniques)

Thanks a lot for any help or insight!
 

Attachments

  • Autocast Phoenix nonMUI.w3x
    48.5 KB · Views: 58
Level 14
Joined
Mar 11, 2017
Messages
587
Really basic stuff. First couple words say it all. Resummons the phoenix automatically if a phoenix egg dies, as long as you keep the autocast on. Behaves like the vanilla phoenix if you don't activate autocast.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
The statue abilities have varying mana cost depending on the number of targets it affects. I assume because of this you could have a potential mana cost of 0, so the spell will never be blue.
If I remember correctly essence of blight has a mana cost of 2 in the OE yet it says 10 in the command card, because this is the maximum mana cost.

Keep in mind that every autocast ability follows a certain behaviour. Essence of blight will be casted if wounded units can be healed (except their hp regen is negative? At least it's like that for the mana ability). You could also use frenzy, which would be cast, if the unit attacks.
I would trigger it, because it's much more reliable.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
If that's is necessary for you, then it's not that easy.
Problem is, that you can't really use order detection, because this does not work correctly, when you order it, while the unit is stunned.
 
Level 14
Joined
Mar 11, 2017
Messages
587
@Jampion thanks for the insight. I have read Essence of Blight mana cost multiplier and the reason for the odd behavior at low mana was that i set Maximum units charged to caster equal to 0. Setting it to 1 solves the issue.
Restricting the legal targets of Essence of blight to "Self" seems to do the trick as it seems to be silencing the coded "healer" behavior of the statue: my kael'thas isn't running towards hurt units to heal them.

While further information on how to code an autocast button shiny appearance would be very welcome, this:
If that's is necessary for you, then it's not that easy.
Problem is, that you can't really use order detection, because this does not work correctly, when you order it, while the unit is stunned.
this I don't understand in the least, sorry. I am assuming that the event that would need to be caught is something along the lines of "the player right clicks a button", more than a specific ability represented by an order (which by common sense comes AFTER you click a button).
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
my kael'thas isn't running towards hurt units to heal them.
That would be the expected behaviour of priests' heal. Statues just cast it when something wounded is in range. They won't move though.
"the player right clicks a button"
Unfortunately no such event exists. When you try to detect ability usage their are usually 2 possibilities:
1. Use one of the casting events. Most of the time this is "starts the effect of an ability"
2. Detect order: "a unit is issued an order..."

1. Does not work here, because toggling autocast is not casting an ability.
2. does work, because there are "on" and "off" orders for autocast abilities. For example heal has the following three order strings: heal, healon, healoff
The problems with orders is, that you can't be sure, that the order will be executed. When a unit is stunned you can still give it orders, but only the last given order will be executed after the stun.

Apparently I found a way to solve this (using the boolean return value of order functions). I can detect if a ability is set on autocast. With this method you can just trigger it, when the abiltiy is set on autocast (order the unit to cast it, when the phoenix dies). You just have to use an autocast ability, that is not casted automatically. Essence of Blight with no target could work.

You can try it out in the test map. When you issue an order with no target, it will print on the screen, if autocast is currently activated or not. You can use the mountain king to see, that it also works, if the unit is stunned.
 

Attachments

  • Autocast.w3x
    17.2 KB · Views: 51
Level 14
Joined
Mar 11, 2017
Messages
587
Nice, the idea of using the bolean return from IssueImmediateOrder() is super clever! Could you spend some words describing the rest of the code briefly for me?

It appears to me that a timer is used to call a function after a certain delay, which is however set to 0.
Code:
    call TimerStart(t,0,false,function Trig_Autocast_Is_On_Delayed)
    call SaveUnitHandle(udg_Hash,GetHandleId(t),0,GetTriggerUnit())
Is the aim here to allow to execute the second-line function first?

the two local timer t, how are they related? My understanding is that the fact that they're local restricts them to the block where they are written. However, they're used to save and load what seems to be the same unit to/from the table hash. Must be the same thing then, but how? Mind explaining this to me?

while having the trigger disabled, this runs.
Code:
if(IssueImmediateOrder( u,"healon")) then
        call BJDebugMsg( "OFF" )
        call IssueImmediateOrder( u, "healoff" )
    else
        call BJDebugMsg( "ON" )

the if(IssueImmediateOrder( u,"healon")) returns true if the heal autocast is able to be ordered <-> the heal autocast is off. Why then is it necessary to call IssueImmediateOrder( u, "healoff" )? Could it be because the if in the line "if(IssueImmediateOrder( u,"healon"))" can only be fed a boolean if the argument IssueImmediateOrder( u,"healon") has completed all of its actions which include executing the order and flipping the autocast status to on if it was off?

Lastly, how does this all run during the situation of a stunned caster? Is the applied delay a solution to the stun "order blockage"?

Thanks for the testmap and the little code. I look forward to understand it a little better.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
It appears to me that a timer is used to call a function after a certain delay, which is however set to 0.
This is a common technique and I can see why it is confusing. A 0 second delay makes no sense usually.
Let's say you have two triggers:
  • Kill 1
    • Events
      • Unit - A unit Dies
    • Conditions
    • Actions
      • Player - Add 10 to Player 1 (Red) Current gold
  • Kill 2
    • Events
      • Unit - A unit Dies
    • Conditions
    • Actions
      • Player - Add 10 to Player 2 (Red) Current gold
Both will be triggered by the same event, so they will happen at the same time (within 0 seconds). However they will run sequentially, so one will run first after that the other will run.
If you use a 0 second delay on one of many triggers that happen within 0 seconds, it will start after all triggers without the delay. So the 0 second delay lets you control in which order some triggers run, if they are at the same time.
In warcraft 3 most events are triggered, before they actually happen.

In our case we detect, when an order is given.
1. player gives order
2. event fires
3. unit executes order

0. Priest currently has autocast on
1. Player deactivates heal autocast (healoff)
2. event is fired
now we check, if "healon" can be used. Healon can only be used, if heal autocast is deactivated (you can't activate it, if it's already activated)
we get, that heal autocast is activated (because the order was not eexecuted yet) - that's not what we want, because we just deactivated it
3. now Priest has autocast off

Solution:

0. Priest currently has autocast on
1. Player deactivates heal autocast (healoff)
2. event is fired
wait 0 seconds
3. now Priest has autocast off
now we check and get what we expected

So basically we delay the trigger, so it runs after 3.

the two local timer t, how are they related? My understanding is that the fact that they're local restricts them to the block where they are written. However, they're used to save and load what seems to be the same unit to/from the table hash. Must be the same thing then, but how? Mind explaining this to me?
There are two types of variables:
primitive variables : integer, string, real, boolean, probably some more
handles (reference varaible, pointer) : anything else: units, points, regions, unit groups, hashtables, timers ...

Handles just point towards an object. They are not the object itself. For example a unit variable is no unit, but just points towards a unit. Destroying the variable won't kill the unit.
All handles have explicit create and destroy functions. CreateTimer, DestroyTimer, CreateGroup, DestroyGroup, Location, RemoveLocation, CreateUnit, RemoveUnit
Unit is special, because it can also be created/removed without the trigger editor (training/summoning a unit, kill -> decay)

A local variable only exists within one function, that's correct. But if a local unit variable no longer exists, the unit won't die. It's the same for timers. The local variable no longer exists, but the timer still exists. You can get the timer in the other function by using GetExpiredTimer().
The hashtable attaches data to the timer, so we have access to the unit in a different function, where GetTriggerUnit() does no longer work.

while having the trigger disabled, this runs.
If I don't disable the trigger, there will be an infinte loop.
Event: order is issued
Action: issue order

the if(IssueImmediateOrder( u,"healon")) returns true if the heal autocast is able to be ordered <-> the heal autocast is off. Why then is it necessary to call IssueImmediateOrder( u, "healoff" )?
It not only returns true, it also gives the order and we need to revert it.

Lastly, how does this all run during the situation of a stunned caster? Is the applied delay a solution to the stun "order blockage"?
It is all just because of the return value.
It will say ON, if you cannot use "healon" else it will say OFF. You can't use "healon", only if it is already activated. So this solution can directly detect whether autocast is on or off, so it's 100% safe and stun does not matter.


Here is the problem with stun explained, if you use a simple apporach.

A unit is ordered "healon"
-> set VariableAutocast = true
A unit is ordered "healoff"
-> set VariableAutocast= false

unit is stunned, autocast is off:
player toggles autocast several times (allare healon, because unit is stunned)
"healon" -> true, but autocast still off, because of stun
"healon" -> true, but autocast still off, because of stun
...
player uses stop (or any other order)
"stop" ->still true, but autocast still off, because of stun
unit no longer stunned. Last order will be executed (stop)
now autocast is still off, but variable is set to true

So it will cause problem whenever you toggle autocast while stunned, but give a different order to the unit afterwards.
 
Level 14
Joined
Mar 11, 2017
Messages
587
This is so helpful. +rep at least thrice
Very very kind of you to explain all this to me.

_________

Edit:
I'll report here resources or discussions that may be related, or of interest to future readers wanting to make autocast abilities.

This code detects an autocast ability being turned on or off: [Snippet] AutocastOrderEvent
(note that it doesn't detect when an autocast ability is cast, just turned on or off; and it doesn't appear to explicitly check whether an autocast is on or off to the best of my limited code-understanding abilities)

This thread suggests a gui method of detecting autocast by assigning casters to an autocast group based on the order issued (through a "a unit is issued an order.." gui event): Autocast Events?
(note: to the best of my limited knowledge, the method proposed by Jampion in this thread seems more solid than the one above linked here, especially when one considers the possibility of the caster being stunned)

___

Concluding, the current spell is working exclusively for a single Kael'thas, suitable in a single-player situation where there is only one copy of him controllable at all times.
Given my inexperience, it is also highy likely to be weak to many influences even though it hasn't bugged out during my limited practice time in the testmap.
It is not currently meant to support more than one caster, and it is also not adapted for MUI. I am satisfied with the current situation as my goal is merely to use this in that specific kind of single-player setting but feel free to contact me if you desire to implement MUI on this. I will be happy to provide you with the latest code.

Marking issue as solved.
 
Last edited:
Level 14
Joined
Mar 11, 2017
Messages
587
If the phoenix dies, you check if autocast is active and if it is, you order the caster to cast the autocast spell. Every cast of the spell should have the same effect as using the normal phoenix ability. One difficulty is to detect, when it should be cast automatically. Most autocast abilities are only cast, if the caster is on attack-move, stop, hold position, patrol and maybe other orders.
You don't want to interrupt important orders the player gave.
yes renewing a phoenix is one of the core functionaities and it is working exactly as you describe.
However there is another functionality to the autocast spell, that is summoning a phoenix when a phoenix isn't there even in situations when the phoenix didn't just die, as long as autocast is turned on. This allows to effectively autocast-summon the phoenix the first time autocast is turned on, and to auto-summon a phoneix if it died during the spell's cooldown period. Again I'm using your system to detect if autocast is on, and a bool udg_ variable that turns false when a phoenix dies -- remains false as long as no phoenix is in play -- turns true upon a phoenix entering play. I've made a periodic loop that keeps on ordering to cast the phoenix every second as long as the bool is false and autocast is on ---> i think that this way of doing it is causing too many unnecessary loop iterations and I wonder if there's a way to catch the event when the autocast cooldown is up to time the recast and avoid loopspam. (Edit: this thread answers the question with a definite NO: certain event. No "spell's ready" event. It's however doable by manually setting up a timer with the appropriate cooldown time. I wonder if it's a better idea than the spammy loop I use.)

As far as detection and prevention of dangerous interactions with other orders go, in other words detecting when it should be cast automatically, I think my code is deeply lacking and I wouldn't know what to do about it.

I'd also like to try and make this MUI to train my meager abilities. I plan on using hash tables and I think I can make it happen by following some tutorials :)
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
One thing you should keep in mind when using periodic events is, that it does not make any difference, if you do it every 0.5 seconds, or every 10 seconds. If you have a performance critical algorithm that will cause fps drops it does not really matter, if you have a lag spike every 10 seconds or every 0.5 seconds, because both is bad. The way to go is to use a short periodic interval instead and try to only use the algorithm for smaller parts of the problem. For example if you want to perform something for a unit group every second (autocast for example) use 0.1 second timer instead and only do it for 10% of the unit group.
To avoid loops, you can use timers. Whenever a Blood Mage (I refer to the phoenix caster a Blood Mage) uses Phoenix, you know he won't be able to use it again for say 90 seconds. Use timer and attach the unit to it, remove the Blood Mage from the caster group and when the timer runs out, add him to the caster group. You only perform the actions for the caster group.

As far as detection and prevention of dangerous interactions with other orders go, in other words detecting when it should be cast automatically, I think my code is deeply lacking and I wouldn't know what to do about it.
That's pretty hard though, to be honest. You want it to be used on attack-move, but not on single target ordered attack, but both have the same order (so current order of unit does not work).
You need to catch every order and determine, if this order can be interrupted and set a boolean value occordingly for every Blood Mage.

Like this:

unit issued an order with unit target
if(issued order = attack)
interrupt = false

unit issued an order with point target
if(issued order = attack)
interrupt = true

...
 
Level 14
Joined
Mar 11, 2017
Messages
587
I have avoided periodic timers for years, believing then to be inherently problematic. I was told recently that they really aren't so bad... What is the consensus here? What situations make them ok?
An answer to this would encompass things that go far beyond my noob knowledge. A premise is needed here however, that is I'm using a GUI periodic Event in a GUI trigger, which is not the same thing as (and is actually probably far worse than) a JASS timer that is run periodically: newbie, timers vs periodic ttime

In this specific instance, trying to force autocast in the way I'm doing it, by spamming orders every .x seconds of game time to be sure that when the cooldown is up, the spell gets immediately cast, makes the trigger run a lot of times when the cooldown has not yet resolved. These are all useless runs. Maybe it's tolerable for one single caster unit, in a Single-player situation, to have thousands of loop iterations for a 10 second cooldown. I don't honestly know. I can only imagine it causing issues in a different situation.
I don't see why I shouldn't be using a timer tailored to the ability's cooldown like Jampion says:
To avoid loops, you can use timers. Whenever a Blood Mage (I refer to the phoenix caster a Blood Mage) uses Phoenix, you know he won't be able to use it again for say 90 seconds. Use timer and attach the unit to it, remove the Blood Mage from the caster group and when the timer runs out, add him to the caster group. You only perform the actions for the caster group.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
I have avoided periodic timers for years, believing then to be inherently problematic. I was told recently that they really aren't so bad... What is the consensus here? What situations make them ok?
You can have hundreds of periodic timers, if you wan't. They really aren't that bad. I explained earlier, that loop interval is not that big of a deal (just avoid very small numbers. Use 0.02 or 0.03 for smooth graphics for example unit movement; use 0.1-0.25 for spells for example damage over time). The importance is, what actions a periodic trigger has (just like every other trigger).

Periodic things are not bad, because for me it's irrelevant, if I have a lag spike every second or every minute, because it's always bad. If you have a trigger that causes a lag spike, it does not matter, if it's periodic or any other event. You should get rid of the lag spike anyway.
If you have a lot of periodic events that run at the same interval (for example every second), try to add an offset: trigger 1 starts at 0.1 seconds and runs every second, trigger 2 starts at 0.2 seconds and runs every second.
This way not as many actions happen at the same time (this is all that matters for efficiency).
The problem with periodic triggers/timers is, that they increase the background activity and make it more likely that too many actions run at the same time. Whenever you have fps problems, first try to optimize your trigger in terms of efficiency. Sometimes there is an algorithm that is much faster.
If you have done that, you can try that only a portion of the trigger runs at the same time, like the timers that have an offset, so they don't run at the same time.

Example:

normal method: two trigger run every second, and both run always at the same time
  • Periodic 1 a
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • -------- do stuff --------
  • Periodic 1 b
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • -------- do stuff --------
normal method: two trigger run every second, but one runs 0.1 seconds later

This is the counter, but it also contains the actions of one periodic trigger
  • Periodic 2 a
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Set Counter = (Counter + 1)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Counter Greater than or equal to 10
        • Then - Actions
          • Set Counter = 0
        • Else - Actions
          • Skip remaining actions
      • -------- counter = 0 --------
      • -------- do stuff --------
  • Periodic 2 b
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
      • Counter Equal to 1
    • Actions
      • -------- counter = 1 --------
      • -------- do stuff --------
Let's take that phoenix trigger as an example. Picking every unit on the map and check, if it can cast it is horrible in terms of efficiency. First improvement is to use a group variable instead, that only contains units that have learned phoenix. When a Blood Mage learns phoenix to the group.
After that you will only very few units, so it's completely fine. You could still improve it, but looping to like 20 units every 0.5 seconds is no problem at all.
A further improvement would be to have let's say 5 unit groups and each group has around 20% of the casters. This way you will only pick like 4 units every 0.5 seconds.
Using Jass timers you can easily ignore casters while their ability is on cooldown.

Concerning the thread you mentioned. I am not an expert when it's about these things, but I have never heard, that periodic triggers are inaccurate. In fact I think periodic triggers is exactly the same time system as timers. After all the function to register a periodic event is called "TriggerRegisterTimerEvent".
I doubt timers are "a lot" faster than periodic events. Periodic events might take slightly longer, but it won't be noticeable. Timer/periodic events don't take long by themselves at all. What can take long are the actions they perform and they won't be different for a timer than for a periodic trigger.

Edit: both periodic triggers and timers are equally accurate. They are not 100% accurate though it seems. Not sure why. I used a 0.01 periodic timer for a counter and used 3 seconds periodic timer and trigger to print the current counter. At first it was ok and only printed multiples of 300: 300,600,900, but at some point weird numbers were printed. Something like: 73544
 
Last edited:

Kyrbi0

Arena Moderator
Level 44
Joined
Jul 29, 2008
Messages
9,487
(sorry to hijack the thread, just had the questions boiling around for a while)

So if someone has, say, a periodic timer (ex 0.03sec) checking every unit on the map for a particular buff, then adding/removing a certain ability(ies) appropriately... Is that crazy? How many of those could one have expecting a noticeable decrease in performance?
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
So if someone has, say, a periodic timer (ex 0.03sec) checking every unit on the map for a particular buff, then adding/removing a certain ability(ies) appropriately... Is that crazy? How many of those could one have expecting a noticeable decrease in performance?
It's definitely performance heavy, but would probably not drop your fps. It highly depends on the amounts of units on your map. If you have finished the trigger you can show it to me and I will see, if it can be improved. Make sure that the trigger is efficient. This means for example that you first check if the unit has the buff and if it hasn't you should not do anymore actions for that unit.
I have a similar trigger in my map: damage all units with a certain buff every 0.25 seconds.
If you want multiple of those triggers I highly recommend to use my offset method and change the interval to 0.1 seconds. Then you can use offset of 0.01 seconds to have 10 triggers with none running at the same time.
 
Level 14
Joined
Mar 11, 2017
Messages
587
Gone as in too difficult for me.
Concluding points:
  • A minimal autocast phoenix spell requires, as long as autocast is on, that the phoenix is renewed upon its death, and also that it is summoned in any other situation in which the phoenix is absent even though it didn't just die (this encompasses situations such as the spell having been on cooldown when the phoenix died, the spell failing due to various reasons such as stun or silence)
  • a simplistic behavior that tries to cast a spell automatically whenever required through a loop that spams the issue of a cast order is not enough to emulate a simple autocast behavior: it deals with stuns etc., but it interrupts every other order upon firing and it shouldn't.
  • a slightly less simplistic behavior requires to code interactions with other orders and this requires to create a small decision system that is way beyond what I can make. I speculate that it should work like this:
    (1) something prompts an automatic cast (phoenix death etc.), in other words answers "should I cast phoenix automatically?"
    (2) if the answer is positive, another system should check if the phoenix automatic cast should be allowed to interrupt the current order or not and in case it shouldn't break it, "queue" up the automatic cast after the current order. In other words something that answers "may I cast phoenix automatically?"
    (3) if the spell should and may be cast automatically, a spamming system that tries to spam the cast order until it gets executed would overcome stun/silence situations
  • this decisional process should be able to be interrupted at any time by intervening things that disable the automaticity, id est autocast being turned off.
Request:
if the decisional systems underlying the basic autocast spells are available anywhere, it would be very nice to read how they function to possibly try and emulate them. I am not aware of how this info could be accessed and I'd appreciate if someone told me.
 
Last edited:
Status
Not open for further replies.
Top