Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!
This is long overdue I've worked on this map for quite a while, I have basic knowledge of removing leaks and I've done my best over the years to ensure leaks are removed in my triggers (except maybe some single use triggers where I was lazy and didn't remove a point, but you get the idea). But, I've also certainly frankensteined this thing together, using amazing systems from the Hive community, and over the years I know I did things not in the most efficient way but If I got it to work and look right from the player's viewpoint and didn't notice any performance issues at the time, then I went with it. I'm sure this has resulted in a number of ineffiencies in my triggers, and probably has accumulated many minor issues over time.
The map tends to get pretty laggy as the game draws on, but it only really happens during graphically busy periods (lots of units and effects on screen). Once that clears up, there isn't any type of consistent lagginess/stuttering...
The game also occasionally crashes, usually right when a graphical effect (crushing wave, lightning etc) hits a unit. More precisely, the game will freeze for 30 seconds or until you spam click and get hit with the 'program not responding' popup. I've tried setting graphics settings to minimum to no avail.
These lag/crash issues did not occur pre-Reforged for what it's worth.
There's also just a LOT of triggers, units and abilities in the map at this point (to me anyway, I'm sure plenty of maps have more).
It's become a daunting task to even want to go through ~4,000 triggers line by line and see if I've missed anything big. I've caught some things recently but who knows what else is in there.
Guess I'm at a bit of a loss. If I KNEW it was my triggers, I'd have no problem going through them all. I also wouldn't mind if anyone with more WE knowledge helping me with this But I don't know if it's just a Reforged issue with no workaround, or if there is something I can do about it whether that's outside or inside the map.
I'm gonna be wrapping up development on this map relatively soon, and I want to make sure it's in the best shape possible and performs well when that time comes.
Just had a "quick" (after what felt like an eternity of loading) overview.
I recommend you research more into the use and application of loops ("for each" especially) to help with efficiency and readability.
Also about MUI instead of player instanced triggers - very much so for timers.
You also made good use of variables here and there, then seemingly stopped using them later, why?
(i guess it is explained by the map version being 9407 - probably a decade old+)
Then there are abilities (like storm armor) for metamorphing units, which you reapply, although you can simply make abilities permanent.
The spell section demands an ability that will stay permanent even after morphing. So how can I use this function to make an ability permanent to all units on the map? where should I put this, in initialization? native UnitMakeAbilityPermanent takes unit whichUnit, boolean permanent, integer...
www.hiveworkshop.com
Also i personally wouldn't use that many waits and especially waits for condition.
These are fine for global triggers, like cutscenes, all player events, but not per unit/hero.
Is this SD or HD, because some models didn't load properly (i'm on SD)?!
On your points:
If the map crashes while on damage effects go off, then perhaps you need to update/investigate your DDS systems and triggers. This most likely has nothing to do with gfx quality (just setting it all to high is good practice).
You might have to undergo the laborious task, of manually testing portions of your map independently, in order to identify what's what.
Setting up a version, where you can get to wave 5 tops for example, or going through the heroes (one by one, with- and without spells).
The map tends to get pretty laggy as the game draws on, but it only really happens during graphically busy periods (lots of units and effects on screen). Once that clears up, there isn't any type of consistent lagginess/stuttering...
If you experience gradual FPS drop over time then that usually points to memory leaks. And from the few triggers I saw, you may still leak quite often.
You will experience high FPS drops when you have many units on screen and also many effects going on. Especially with HD models (both units and effects), as those require far more computational power to display than SD models.
This may be even more apparent when you emulate some spell/graphical effects using units (TFT solution) instead of just using special effects (~v1.30+ solution), as units require additional computational power by WC3 engine.
The game also occasionally crashes, usually right when a graphical effect (crushing wave, lightning etc) hits a unit. More precisely, the game will freeze for 30 seconds or until you spam click and get hit with the 'program not responding' popup. I've tried setting graphics settings to minimum to no avail.
On top of what @A]mun wrote, historically reforged HD models lead to a lot of game crashes. Don't know how much this is fixed, but the crash could be caused by some bad model. If you witness the moment game crashed, try to determine if such crash happens when some specific model is shown or should be created that moment. Or when a unit with specific model gets hit by spells and game crashes right after that, etc.
As for your triggers, I think you should learn to optimize/generalize your triggers and try to better leverage data structures.
By "generalizing" triggers I mean that you have basically copies of same trigger but each is, for example, for different player.
If you know a bit of jass (or don't mind learning the very basics), you can optimize your triggers even more. For example you have "Titan Armor Recharge P1" and copies of it named "...P2/P3/P4". The only difference there is which timer starts the trigger and which hero it affects. This could be merged into one trigger that uses a bit of JASS (only because you can do timer comparison in jass, while gui does not support that).
Or Quake Effect P1/P2/P3/P4 triggers which seem to do the exact same, but each for different player. Now why I wrote 'seem' is because only P1 trigger clears up memory leaks, while the P2-P4 versions do not.
This is another reason for merging your triggers - you only have one version of the trigger to go through to validate and then you can be sure it behaves same for all players, while right now you need to go through multiple triggers and ensure each version behaves same.
Another example is your 'Concoct Elixirs' section - a lot of those triggers seem to do basically the same thing (at least at first glance).
In that case you could prepare data structures that will contain data that differ in each trigger (i.e. the icon you show in multiboard) and initialize that data during map initialization. Merge those triggers into single one and modify it so that it loads and uses the data you prepared, instead of using constant literals.
As for what kind of data structure to use, you could use hashtable and use the item-type being parent key. Again, knowing a bit of jass could save you some time here, as you could write a single helper function, that accepts all data as arguments and writes them into hashtable - this way you initialize all item's data in a single line, compared to GUI, where you would need to have one "Hasthtable - Save <datatype>" action for each piece of data you want to store (this could bloat your trigger and make it hard to navigate through).
The jass option would seem even better if you use external editor, like VS Code for example, which has very good multi-line editing options.
Also worth mentioning are all the "Talent Selected ..." triggers, which seem to do basically the same thing. Again, you could prepare a data structure, which you fill in with unit-types and abilities and have a single "Talent Selected" trigger that just read the data structure.
(Edit: I counted an estimate, as it seems there's 8 such talents per hero, which means you have around 320 of those "Talen Selected" triggers )
Last thing is about waits. I am not sure if this could be a problem, so take it with a grain of salt. Look at Ifrit's Accelerate Time trigger:
Accelerate Time
Events
Unit - A unit Starts the effect of an ability
Conditions
(Level of (Learned Talent) Djinn Dimension - |cffffcc00Accelerate Time|r (Neutral Hostile) for TalentShop[(Player number of (Owner of (Triggering unit)))]) Not equal to 0
(Ability: (Unit: (Triggering unit)'s Ability with Ability Code: (Ability being cast))'s Boolean Field: Item Ability ('aite')) Equal to True
Actions
Wait 0.01 seconds
Set VariableSet GetAbilityCooldown = (Ability Cooldown Remaining of (Triggering unit) for ability (Ability being cast)..)
Unit - For Unit (Triggering unit), end cooldown of ability (Ability being cast)
Unit - For Unit (Triggering unit), start cooldown of ability (Ability being cast) " over "(GetAbilityCooldown x 0.86) seconds.
First of all, waits are not accurate and they are affected by latency. Usually the minimum observed wait is ~0.23 seconds.
Second, I don't know what value does (Ability being cast) have after wait - maybe it acts as local variable (similarly to (Triggering unit)) or it will be nulled or it could act as global variable and possibly point to some completely different spell.
If it is the latter, this could potentially be dangerous when reading/modifying spell data which the ability may not have at all. I would not be surprised if these newer natives had some issues in them.
That being said, I don't think this particular trigger could be an issue, as basically all abilities have cooldown, but if you have similar triggers where you read/modify some ability-specific fields using (Ability being cast) after wait, then I would approach it warily and check if that could cause some issues.
If you experience gradual FPS drop over time then that usually points to memory leaks. And from the few triggers I saw, you may still leak quite often.
You will experience high FPS drops when you have many units on screen and also many effects going on. Especially with HD models (both units and effects), as those require far more computational power to display than SD models.
This may be even more apparent when you emulate some spell/graphical effects using units (TFT solution) instead of just using special effects (~v1.30+ solution), as units require additional computational power by WC3 engine.
On top of what @A]mun wrote, historically reforged HD models lead to a lot of game crashes. Don't know how much this is fixed, but the crash could be caused by some bad model. If you witness the moment game crashed, try to determine if such crash happens when some specific model is shown or should be created that moment. Or when a unit with specific model gets hit by spells and game crashes right after that, etc.
As for your triggers, I think you should learn to optimize/generalize your triggers and try to better leverage data structures.
By "generalizing" triggers I mean that you have basically copies of same trigger but each is, for example, for different player.
If you know a bit of jass (or don't mind learning the very basics), you can optimize your triggers even more. For example you have "Titan Armor Recharge P1" and copies of it named "...P2/P3/P4". The only difference there is which timer starts the trigger and which hero it affects. This could be merged into one trigger that uses a bit of JASS (only because you can do timer comparison in jass, while gui does not support that).
Or Quake Effect P1/P2/P3/P4 triggers which seem to do the exact same, but each for different player. Now why I wrote 'seem' is because only P1 trigger clears up memory leaks, while the P2-P4 versions do not.
This is another reason for merging your triggers - you only have one version of the trigger to go through to validate and then you can be sure it behaves same for all players, while right now you need to go through multiple triggers and ensure each version behaves same.
Another example is your 'Concoct Elixirs' section - a lot of those triggers seem to do basically the same thing (at least at first glance).
In that case you could prepare data structures that will contain data that differ in each trigger (i.e. the icon you show in multiboard) and initialize that data during map initialization. Merge those triggers into single one and modify it so that it loads and uses the data you prepared, instead of using constant literals.
As for what kind of data structure to use, you could use hashtable and use the item-type being parent key. Again, knowing a bit of jass could save you some time here, as you could write a single helper function, that accepts all data as arguments and writes them into hashtable - this way you initialize all item's data in a single line, compared to GUI, where you would need to have one "Hasthtable - Save <datatype>" action for each piece of data you want to store (this could bloat your trigger and make it hard to navigate through).
The jass option would seem even better if you use external editor, like VS Code for example, which has very good multi-line editing options.
Also worth mentioning are all the "Talent Selected ..." triggers, which seem to do basically the same thing. Again, you could prepare a data structure, which you fill in with unit-types and abilities and have a single "Talent Selected" trigger that just read the data structure.
(Edit: I counted an estimate, as it seems there's 8 such talents per hero, which means you have around 320 of those "Talen Selected" triggers )
Last thing is about waits. I am not sure if this could be a problem, so take it with a grain of salt. Look at Ifrit's Accelerate Time trigger:
Accelerate Time
Events
Unit - A unit Starts the effect of an ability
Conditions
(Level of (Learned Talent) Djinn Dimension - |cffffcc00Accelerate Time|r (Neutral Hostile) for TalentShop[(Player number of (Owner of (Triggering unit)))]) Not equal to 0
(Ability: (Unit: (Triggering unit)'s Ability with Ability Code: (Ability being cast))'s Boolean Field: Item Ability ('aite')) Equal to True
Actions
Wait 0.01 seconds
Set VariableSet GetAbilityCooldown = (Ability Cooldown Remaining of (Triggering unit) for ability (Ability being cast)..)
Unit - For Unit (Triggering unit), end cooldown of ability (Ability being cast)
Unit - For Unit (Triggering unit), start cooldown of ability (Ability being cast) " over "(GetAbilityCooldown x 0.86) seconds.
First of all, waits are not accurate and they are affected by latency. Usually the minimum observed wait is ~0.23 seconds.
Second, I don't know what value does (Ability being cast) have after wait - maybe it acts as local variable (similarly to (Triggering unit)) or it will be nulled or it could act as global variable and possibly point to some completely different spell.
If it is the latter, this could potentially be dangerous when reading/modifying spell data which the ability may not have at all. I would not be surprised if these newer natives had some issues in them.
That being said, I don't think this particular trigger could be an issue, as basically all abilities have cooldown, but if you have similar triggers where you read/modify some ability-specific fields using (Ability being cast) after wait, then I would approach it warily and check if that could cause some issues.
Adding the wait action was the only solution I could find to make this actually work. For whatever reason, without the wait action the cooldown reduction effect would not apply. I remember trying to use someone else's system but if I remember correctly it didn't suit my needs - was missing the ability to add this 'cooldown reduction effect' based on if a unit had a buff, or specific items, something like that.
I would love to get this to work without the wait action - I'm just not sure how. There are quite a few similar triggers, so it would probably be a good problem to solve. I'll try messing with it to see if I can get it to work another way, and open to suggestions if you have any. Thanks for bringing this up.
Looking at the talent triggers... That makes sense, I could see how that could be set up as one trigger.. And yes, your estimate is correct. Same with elixirs, and surely some others. I don't think I've ever touched hashtables to be honest which might be why I didn't think of doing it that way.
Regarding timer triggers, if we're just talking about GUI... How would I 'connect' each timer to the correct player in a single trigger? Since the timer isn't 'owned' by a player like a unit would be, or something like comparing the player number to the array value of the timer, I've had trouble of thinking of a way to do this without just running completely separate triggers for each player. Or is this something that just can't be done in GUI?
EDIT: I see now you said 'with a bit of Jass' regarding the timer stuff.
And thanks for catching the leaks in Quake P2-P4, fixed those.
EDIT 2: Regarding
"This may be even more apparent when you emulate some spell/graphical effects using units (TFT solution) instead of just using special effects (~v1.30+ solution), as units require additional computational power by WC3 engine."
I do have many dummy units that are created on casting of hero/item abilities. The units are given a short expiration timer to remove them... But I've recently learned it's probably better to pre-place some amount of dummy units onto the map, and then just re-use those dummies repeatedly - instead of creating a new unit every time the trigger runs. Is that correct - Even though the units get removed from the game, they still cause a minor unavoidable leak (that probably becomes not so minor when multiplied by 100's or 1000's).
Just had a "quick" (after what felt like an eternity of loading) overview.
I recommend you research more into the use and application of loops ("for each" especially) to help with efficiency and readability.
Also about MUI instead of player instanced triggers - very much so for timers.
You also made good use of variables here and there, then seemingly stopped using them later, why?
(i guess it is explained by the map version being 9407 - probably a decade old+)
Then there are abilities (like storm armor) for metamorphing units, which you reapply, although you can simply make abilities permanent.
The spell section demands an ability that will stay permanent even after morphing. So how can I use this function to make an ability permanent to all units on the map? where should I put this, in initialization? native UnitMakeAbilityPermanent takes unit whichUnit, boolean permanent, integer...
www.hiveworkshop.com
Also i personally wouldn't use that many waits and especially waits for condition.
These are fine for global triggers, like cutscenes, all player events, but not per unit/hero.
Is this SD or HD, because some models didn't load properly (i'm on SD)?!
On your points:
If the map crashes while on damage effects go off, then perhaps you need to update/investigate your DDS systems and triggers. This most likely has nothing to do with gfx quality (just setting it all to high is good practice).
You might have to undergo the laborious task, of manually testing portions of your map independently, in order to identify what's what.
Setting up a version, where you can get to wave 5 tops for example, or going through the heroes (one by one, with- and without spells).
2) map is ~14 years old counting the time i worked on it before uploading to hive in 2014. That + the (now I see, rather bloated) amount of triggers making it cumbersome to sift through them all. The lack of variables in some places would probably be older stuff, or just me not realizing I could have done things more efficiently at that time and have recently learned.
3) I did not know about the UnitMakeAbilityPermanent - wow, much more simple and efficient. Wish I knew that beforehand thanks!
4) good to know, I'll try to replace any waits for condition. As I mentioned to Nichilus, all triggers that apply a cooldown reduction effect have the tiny wait action at the start, because it simply wouldn't work without it. Adding the wait got the cooldown reduction to apply. I'd definitely rather not do it that way, I'm open to suggestions.
5) The DDS stuff is a big concern of mine... There is a LOT going on in the DDS triggers in the map. The main DDS triggers have gotten so long that they become cumbersome to work with because they load/register clicks so slowly. I had started to move some effects that required the DDS into their own separate triggers just to make things easier to find/quicker to edit. But now I see that having a ton of different triggers with the 'damageEventTrigger becomes equal to 1.00' event is probably a bad idea. That might be the biggest return on investment I can get time-wise to improve the maps performance. I'll just have to suck up having several hundred actions inside a master DDS trigger I suppose, not like I plan on adding much more to it anyway.
I've compiled a bunch of other tips/suggestions for similar problems (mostly from other threads on Hive, some on reddit), so I'll be working on the suggestions you guys have given, along with all of those.
Adding the wait action was the only solution I could find to make this actually work. For whatever reason, without the wait action the cooldown reduction effect would not apply.
Check my posts here: Problem with ability recharge system. It was about a similar issue to yours.
In that regard, I think the easiest thing that could work for you is to modify caster's ability and change the cooldown to an updated value.
Consider the following trigger:
Example
Events
Unit - A unit Starts the effect of an ability
Conditions
(Ability being cast) Equal to Blizzard
Actions
Set VariableSet abilityLevel = ((Level of (Ability being cast) for (Triggering unit)) - 1)
Set VariableSet baseCd = (Cooldown of (Ability being cast), Level: abilityLevel.)
Set VariableSet newCd = (baseCd x 0.50)
Ability - Set Ability: (Unit: (Triggering unit)'s Ability with Ability Code: (Ability being cast))'s Real Level Field: Cooldown ('acdn') of Level: abilityLevel to newCd
This will modify Blizzard's cooldown only for the caster to half the amount of the default cooldown.
The action "set baseCd" action reads the base/unmodified cooldown as set in object editor.
The action "Ability - Set Ability..." modifies a specific instance of the ability that belongs only to the Triggering unit; it does not affect anyone else.
Note that "abilityLevel" is set as "level of casted ability - 1", because both "read" and "modify" actions use zero-based indexing for ability levels (e.g. value 0 reads/modifies ability level 1, value 1 reads/modifies ability level 2, etc.).
The array index is the connection. Timer[1] belongs to Player 1 and affects Hero[1]. Timer[2] belongs to Player 2 and affects Hero[2], etc. As already mentioned, there is no way to compare timers via GUI, you need a bit of JASS for it. For example this could work:
Titan Armor Recharge Merged
Events
Time - RechargeTimer[1] expires
Time - RechargeTimer[2] expires
Time - RechargeTimer[3] expires
Time - RechargeTimer[4] expires
Conditions
Actions
For each (Integer TempInt) from 1 to 4, do (Actions)
Loop - Actions
Custom script: if GetExpiredTimer() == udg_RechargeTimers[udg_TempInt] then
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Hero[TempInt] has buff Titan Armor ) Equal to True
Then - Actions
Special Effect - Create a special effect attached to the origin of Hero[TempInt] using Abilities\Spells\Items\AIma\AImaTarget.mdl
Special Effect - Destroy (Last created special effect)
Unit - Set mana of Hero[TempInt] to ((Mana of Hero[TempInteger]) + ((Real((Intelligence of Hero[TempInt] (Include bonuses)))) x 0.40))
Countdown Timer - Start RechargeTimer[TempInt] as a One-shot timer that will expire in 1.00 seconds
Else - Actions
Skip remaining actions
Custom script: endif
The custom script "if <condition>" needs a closing script at the end (the "endif"). The Skip Remaining Actions is there to prematurely exit the trigger when we found the expired timer (i.e. if expired timer is RechargeTimer[2], then there is no need to check RechargeTimer[3] and RechargeTimer[4]).
Yeah, I noticed your dummy units. I also saw that you have various versions of dummies - it seems to me you are using unit-type as a discriminator. Sometimes you have versions for each player (i.e. Ogre Club Explosion Dummy P1...P2...P3...P4) and other times for some other reason (i.e. "<dummy name>" and "<same dummy name> Turok").
What I think is better for you is to use a single dummy (unless you have some very specific reasons not to) and use arrays/hashtables to store additional information about them (i.e. boolean to set if it is Turok version or not). If you don't want to use arrays/hashtables for this, I think what would serve you better is to use abilities as an information marker/traits. such ability does not do anything by itself - only triggers would check to see if unit has such an ability to determine what to do with that unit.
For example you have 8 "Ogre Club Explosion Dummy" units. They can be categorized by 1. (not) being Turok and 2. by which player they belong to (P1-P4). From what I understand, those dummies belong to neutral player and once they reach their destination, they swap ownership to a given player (i.e. P1 version to player 1). What you could do instead is:
Create an ability, for example from "Item damage bonus" ability, as that ability can stack multiple times on a single unit and is not visible in unit's command panel. Make the ability give 0 bonus damage, but give it 4 levels. Name it for example "Owned by Player"
Create a second ability, again from "Item damage bonus", remove its bonus, leave it with a single level and just name it "Turok Version"
Replace any use of any of the "Ogre Club Explosion Dummy" with a generic dummy. On creation, give that dummy via trigger the "Owned by Player" ability and set its level to match player's number. So instead of creating a "Ogre Club Explosion Dummy P3" unit, you will create generic dummy, give it "Owned by Player" skill and set its level to 3.
If the dummy is supposed to be "Turok" version, then just give the dummy the "Turok Version" ability.
When you need to change ownership of the dummy, then just change it to player number "(level of Owned by Player ability)".
As you can see, those abilities act as a trait of the unit. It will scale better with your project. Imagine you wanted to add 5th player. Well, you would need to go all around your map to create new versions of dummies, then find all relevant triggers and add actions that handle them.
With the "trait" ability approach, all you would need to do is just increase max levels of "Owned by Player" ability to 5 levels from 4.
The units are given a short expiration timer to remove them... But I've recently learned it's probably better to pre-place some amount of dummy units onto the map, and then just re-use those dummies repeatedly
Yes, it is a good idea to use unit pool for dummies. Also a thing to keep in mind is to move dummies away to edges of your map. Although dummies don't have any model, they still take PC resources, so it might be good idea to place them in some unused spaces when they are not in use.
That's good catch - that could be part of the cause for your FPS drops if you have fight-intensive scene and you are doing a lot there with DDS.
Few pointers:
1. You could use a single trigger with "damageEventTrigger becomes equal to 1.00" event. That trigger would run your other triggers.
So instead of this:
Dodge
Events
Game - damageEventTrigger becomes Equal to 1.00
Conditions
DamageEventType Equal to PHYSICAL
Actions
...
Critical Hit
Events
Game - damageEventTrigger becomes Equal to 1.00
Conditions
damageType Equal to PHYSICAL
(Level of Critical Hit for source) Greater than 0
(Random integer number between 1 and 100) Less than or equal to 18
(target has buff Critical Hit ) Equal to True
Actions
...
Physical Damage Mods
Events
Game - damageEventTrigger becomes Equal to 1.00
Conditions
damageType Equal to PHYSICAL
Actions
...
You could have this:
On Damage Taken
Events
Game - damageEventTrigger becomes Equal to 1.00
Conditions
Actions
Trigger - Run Dodge <gen> (checking conditions)
Trigger - Run Critical Hit <gen> (checking conditions)
Trigger - Run Physical Damage Mods <gen> (checking conditions)
Dodge
Events
Conditions
DamageEventType Equal to PHYSICAL
Actions
...
Critical Hit
Events
Conditions
damageType Equal to PHYSICAL
(Level of Critical Hit for source) Greater than 0
(Random integer number between 1 and 100) Less than or equal to 18
(target has buff Critical Hit ) Equal to True
Actions
...
Physical Damage Mods
Events
Conditions
damageType Equal to PHYSICAL
Actions
...
Notice that in this version none of the three triggers have any events. The main "On Damage Taken" trigger defines a pipeline with how you process damage.
Crucially, you can add filters so that you skip various triggers in the pipeline. In case of the three triggers, all rely on damage done being physical damage. So you could add this single check into the "On Damage Taken" trigger.
You could use more filters: You could filter by hero/non-hero units for example. Or P1-P4 vs computer players. The point is to limit as many triggers from running as possible.
You should also aim to simplify/make more effective triggers that are often used. For example Physical Damage Mods trigger is huge.
2. Do you need most of the triggers to run for trivial damage? Let's say damage done is "2.0" of type Physical. Is there a need to evaluate all factors in "Physical Damage Mods" if it will increase damage done from 2.0 to let's say 4.0 max?
Also, you should check if the DDS you use filters out damage done lower than 0.01, otherwise you should do that. Applying debuffs actually deals some very low damage (< 0.01) and you could be calculating things for no reason.
3. Use data structures and precalculate everything you can. For example Dodge chance may be affected by items on the unit. Then have an array for base dodge chance that given unit gains from its items and update this whenever unit acquires/loses item. Similarly for abilities, if just learning them affects dodge chance.
In your Dodge trigger just load the precalculated value from the array.
Then use similar approach for things like critical chance and other modifiers you apply to damage.
4. Exit prematurely from your triggers whenever possible. Looking at part of the "Physical Damage Mods" trigger, I can see that you have one "if/then/else" for "Mind Rot miss chance" which nulls damage when it procs; and underneath that you have a separate "if/then/else" for "Bulwark Absorb Missiles" which also nulls damage if it procs. That "if/then/else" is followed by another damage-nulling "if/then/else" for "Sigil of Deceiver".
If the damage was nulled by "Mind Rot", then there is no need to evaluate "Bulwark Absorb Missiles" nor "Sigil of Deceiver".
5. Re-order your actions so that the most impactful ones are evaluated first. Continuing from point #4, those "if/then/else"s that null damage are at the bottom of that huge trigger. Before that you do many different evaluations and damage recalculations. It makes more sense to evaluate first if any damage would be done at all, before you try to apply other modifiers, because if damage was absorbed, then you can prematurely exit the trigger and avoid lot of evaluations.
Check my posts here: Problem with ability recharge system. It was about a similar issue to yours.
In that regard, I think the easiest thing that could work for you is to modify caster's ability and change the cooldown to an updated value.
Consider the following trigger:
Example
Events
Unit - A unit Starts the effect of an ability
Conditions
(Ability being cast) Equal to Blizzard
Actions
Set VariableSet abilityLevel = ((Level of (Ability being cast) for (Triggering unit)) - 1)
Set VariableSet baseCd = (Cooldown of (Ability being cast), Level: abilityLevel.)
Set VariableSet newCd = (baseCd x 0.50)
Ability - Set Ability: (Unit: (Triggering unit)'s Ability with Ability Code: (Ability being cast))'s Real Level Field: Cooldown ('acdn') of Level: abilityLevel to newCd
This will modify Blizzard's cooldown only for the caster to half the amount of the default cooldown.
The action "set baseCd" action reads the base/unmodified cooldown as set in object editor.
The action "Ability - Set Ability..." modifies a specific instance of the ability that belongs only to the Triggering unit; it does not affect anyone else.
Note that "abilityLevel" is set as "level of casted ability - 1", because both "read" and "modify" actions use zero-based indexing for ability levels (e.g. value 0 reads/modifies ability level 1, value 1 reads/modifies ability level 2, etc.).
The array index is the connection. Timer[1] belongs to Player 1 and affects Hero[1]. Timer[2] belongs to Player 2 and affects Hero[2], etc. As already mentioned, there is no way to compare timers via GUI, you need a bit of JASS for it. For example this could work:
Titan Armor Recharge Merged
Events
Time - RechargeTimer[1] expires
Time - RechargeTimer[2] expires
Time - RechargeTimer[3] expires
Time - RechargeTimer[4] expires
Conditions
Actions
For each (Integer TempInt) from 1 to 4, do (Actions)
Loop - Actions
Custom script: if GetExpiredTimer() == udg_RechargeTimers[udg_TempInt] then
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Hero[TempInt] has buff Titan Armor ) Equal to True
Then - Actions
Special Effect - Create a special effect attached to the origin of Hero[TempInt] using Abilities\Spells\Items\AIma\AImaTarget.mdl
Special Effect - Destroy (Last created special effect)
Unit - Set mana of Hero[TempInt] to ((Mana of Hero[TempInteger]) + ((Real((Intelligence of Hero[TempInt] (Include bonuses)))) x 0.40))
Countdown Timer - Start RechargeTimer[TempInt] as a One-shot timer that will expire in 1.00 seconds
Else - Actions
Skip remaining actions
Custom script: endif
The custom script "if <condition>" needs a closing script at the end (the "endif"). The Skip Remaining Actions is there to prematurely exit the trigger when we found the expired timer (i.e. if expired timer is RechargeTimer[2], then there is no need to check RechargeTimer[3] and RechargeTimer[4]).
Yeah, I noticed your dummy units. I also saw that you have various versions of dummies - it seems to me you are using unit-type as a discriminator. Sometimes you have versions for each player (i.e. Ogre Club Explosion Dummy P1...P2...P3...P4) and other times for some other reason (i.e. "<dummy name>" and "<same dummy name> Turok").
What I think is better for you is to use a single dummy (unless you have some very specific reasons not to) and use arrays/hashtables to store additional information about them (i.e. boolean to set if it is Turok version or not). If you don't want to use arrays/hashtables for this, I think what would serve you better is to use abilities as an information marker/traits. such ability does not do anything by itself - only triggers would check to see if unit has such an ability to determine what to do with that unit.
For example you have 8 "Ogre Club Explosion Dummy" units. They can be categorized by 1. (not) being Turok and 2. by which player they belong to (P1-P4). From what I understand, those dummies belong to neutral player and once they reach their destination, they swap ownership to a given player (i.e. P1 version to player 1). What you could do instead is:
Create an ability, for example from "Item damage bonus" ability, as that ability can stack multiple times on a single unit and is not visible in unit's command panel. Make the ability give 0 bonus damage, but give it 4 levels. Name it for example "Owned by Player"
Create a second ability, again from "Item damage bonus", remove its bonus, leave it with a single level and just name it "Turok Version"
Replace any use of any of the "Ogre Club Explosion Dummy" with a generic dummy. On creation, give that dummy via trigger the "Owned by Player" ability and set its level to match player's number. So instead of creating a "Ogre Club Explosion Dummy P3" unit, you will create generic dummy, give it "Owned by Player" skill and set its level to 3.
If the dummy is supposed to be "Turok" version, then just give the dummy the "Turok Version" ability.
When you need to change ownership of the dummy, then just change it to player number "(level of Owned by Player ability)".
As you can see, those abilities act as a trait of the unit. It will scale better with your project. Imagine you wanted to add 5th player. Well, you would need to go all around your map to create new versions of dummies, then find all relevant triggers and add actions that handle them.
With the "trait" ability approach, all you would need to do is just increase max levels of "Owned by Player" ability to 5 levels from 4.
Yes, it is a good idea to use unit pool for dummies. Also a thing to keep in mind is to move dummies away to edges of your map. Although dummies don't have any model, they still take PC resources, so it might be good idea to place them in some unused spaces when they are not in use.
That's good catch - that could be part of the cause for your FPS drops if you have fight-intensive scene and you are doing a lot there with DDS.
Few pointers:
1. You could use a single trigger with "damageEventTrigger becomes equal to 1.00" event. That trigger would run your other triggers.
So instead of this:
Dodge
Events
Game - damageEventTrigger becomes Equal to 1.00
Conditions
DamageEventType Equal to PHYSICAL
Actions
...
Critical Hit
Events
Game - damageEventTrigger becomes Equal to 1.00
Conditions
damageType Equal to PHYSICAL
(Level of Critical Hit for source) Greater than 0
(Random integer number between 1 and 100) Less than or equal to 18
(target has buff Critical Hit ) Equal to True
Actions
...
Physical Damage Mods
Events
Game - damageEventTrigger becomes Equal to 1.00
Conditions
damageType Equal to PHYSICAL
Actions
...
You could have this:
On Damage Taken
Events
Game - damageEventTrigger becomes Equal to 1.00
Conditions
Actions
Trigger - Run Dodge <gen> (checking conditions)
Trigger - Run Critical Hit <gen> (checking conditions)
Trigger - Run Physical Damage Mods <gen> (checking conditions)
Dodge
Events
Conditions
DamageEventType Equal to PHYSICAL
Actions
...
Critical Hit
Events
Conditions
damageType Equal to PHYSICAL
(Level of Critical Hit for source) Greater than 0
(Random integer number between 1 and 100) Less than or equal to 18
(target has buff Critical Hit ) Equal to True
Actions
...
Physical Damage Mods
Events
Conditions
damageType Equal to PHYSICAL
Actions
...
Notice that in this version none of the three triggers have any events. The main "On Damage Taken" trigger defines a pipeline with how you process damage.
Crucially, you can add filters so that you skip various triggers in the pipeline. In case of the three triggers, all rely on damage done being physical damage. So you could add this single check into the "On Damage Taken" trigger.
You could use more filters: You could filter by hero/non-hero units for example. Or P1-P4 vs computer players. The point is to limit as many triggers from running as possible.
You should also aim to simplify/make more effective triggers that are often used. For example Physical Damage Mods trigger is huge.
2. Do you need most of the triggers to run for trivial damage? Let's say damage done is "2.0" of type Physical. Is there a need to evaluate all factors in "Physical Damage Mods" if it will increase damage done from 2.0 to let's say 4.0 max?
Also, you should check if the DDS you use filters out damage done lower than 0.01, otherwise you should do that. Applying debuffs actually deals some very low damage (< 0.01) and you could be calculating things for no reason.
3. Use data structures and precalculate everything you can. For example Dodge chance may be affected by items on the unit. Then have an array for base dodge chance that given unit gains from its items and update this whenever unit acquires/loses item. Similarly for abilities, if just learning them affects dodge chance.
In your Dodge trigger just load the precalculated value from the array.
Then use similar approach for things like critical chance and other modifiers you apply to damage.
4. Exit prematurely from your triggers whenever possible. Looking at part of the "Physical Damage Mods" trigger, I can see that you have one "if/then/else" for "Mind Rot miss chance" which nulls damage when it procs; and underneath that you have a separate "if/then/else" for "Bulwark Absorb Missiles" which also nulls damage if it procs. That "if/then/else" is followed by another damage-nulling "if/then/else" for "Sigil of Deceiver".
If the damage was nulled by "Mind Rot", then there is no need to evaluate "Bulwark Absorb Missiles" nor "Sigil of Deceiver".
5. Re-order your actions so that the most impactful ones are evaluated first. Continuing from point #4, those "if/then/else"s that null damage are at the bottom of that huge trigger. Before that you do many different evaluations and damage recalculations. It makes more sense to evaluate first if any damage would be done at all, before you try to apply other modifiers, because if damage was absorbed, then you can prematurely exit the trigger and avoid lot of evaluations.
All of this is great! Thank you for explaining everything in detail. Adding these to the list.
modifying stats like dodge chance/spell power etc makes sense on acquiring/losing items, learning specific talents etc, now I can see why I shouldn't be calculating those every time damage occurs...
Would there be a way to calculate this on buff gain/loss? I.E. there are a number of buffs that grant temporary bonuses to spell power, cooldown reduction, dodge etc. The item acquisition/loss makes sense and is easy. Many of these buffs are created through the EoT system so it's easy to determine when a unit gains a buff, but I struggle to think of a way to specifically detect when a unit loses a buff (i.e. unit gets dispelled), and adjust these stats accordingly.
Even if these buff checks do need to occur at the time of damage, at least moving all the more persistent bonuses (items, abilities, etc) will help.
Currently consolidating all 'unit enters playable map area' events - there's a LOT of triggers all firing this event individually - and then getting to work on the DDS. Well I'll just go ahead and move the dodge/miss stuff to the top of the DDS trigger since that's fast, idk why I ended up placing them at the very end lol.
As far as I know, there is no way to detect buff loss. Buff gain may not be easy to detect because you have to account for auras as well.
The only way I can think of is to periodically check units - perhaps it is enough to check units once every .2 seconds? That may be short enough interval where the inaccuracy of your bonuses won't be noticeable.
For casted spells, detect when buff is applied via cast events and keep track of targeted units. Then periodically check if those units still have given buff(s).
For auras, you could either periodically check all units nearby aura source or you could just ignore that and calculate bonus from auras during damage event.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.