• 🏆 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!

The basic of creating an own BonusMod and unit variable stats

Status
Not open for further replies.

Ardenian

A

Ardenian

Hello,

I would like to create my own BonusMod with GUI and custom scripts.
Why I don't use the one in the spell section ? I would like to achieve something else, too.

So, what do I aim for ? Each item should give stats with hidden abilities, as there are a lot, as seen in the BonusMod of Hive.

The questions I have now:

1. How do I efficiently register the stats an item give, without a limit on the amount of different stats ?

2. How do I create efficient ability levels ?
My approach would be to create one ability, for armor for example, and add levels. However, this would increase the object data unnecessarily.
How do I create a math function that allows the use of a few abilities, like one that gives 1, one 2, one 4 armor and such, how do I create this ?

---

3. I would like to use hashtables, making the whole system MUI, so every unit has its own stats. What I aim for is to create variable stats for every unit, like 'Critical Chance' and such, too, additionally to the BonusMod.
I can use Bribe's Unit Indexer for this, can't I ?
Are hashtables a good approach to register stat variables for every unit ? Can I also easily distinguish between unit types ?
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
1. This crosses the boundary in the sense that it's easier to make in JASS or vJASS than GUI. Although you can make this kind of a system in GUI, it probably won't be very handy.
2. JNGP includes a tool called ObjectMerger, which makes object data. Some lua knowledge is useful to use it, although not strictly necessary.
3. Yes, hashtables tend to be a decent approach in general for avoiding unneeded complexity. If this is your first time making a system like this, I'd definitely recommend starting out with a hashtable.
Later on you might find that some solution including arrays will be more efficient, but not drastically more.
 
Level 12
Joined
May 22, 2015
Messages
1,051
For 2, do it like this to save on levels needed:
dmg 1 - 1
dmg 2 - 2
dmg 3 - 4
dmg 4 - 8
dmg 5 - 16
dmg 6 - 32
dmg 7 - 64

etc. Multiplying the damage value for each ability by 2 for each new ability. Make sure they are all separate abilities. I don't think you can use the same one.

Make the same abilities with negative values if you want to.

Then you need to check when you want to modify the unit's damage. Basically, you need to store the unit's bonus damage somehow (unit indexer or you can just use a hashtable for this).

Go through the list of abilities and turn on / off the ones that need to be (by adding / removing them from the unit). Like this:

JASS:
If (damage > 32) then
    damage = damage - 32
    add ability that adds 32 damage
else
    remove ability that adds 32 damage
endif

if (damage > 16) then
    damage = damage - 16
    add ability that adds 16 damage
else
    remove ability that adds 16 damage
endif

That's in JASS (sort of lol), but hopefully you can understand it. Make sure it's in order of largest to smallest.

With it set up like this, adding 1 more ability doubles the values you can use. In my map, I only need 10 abilities and it will get all values I would need. It can get any number all the way up to 511 with just 10 abilities.
 

Ardenian

A

Ardenian

Hm, SAUS, thank you, if I understand right, then it is done this:

If an item would add 50 damage, then I start with the highest, 64. Since it is too high, I remove 32 from it = 32.
Since this is too low, I add 16 = 48. Now, I add 8, don't I ? = 56
Then I remove 4 and 2 = 50 ?

That's how it is done ?
I think it can easily done in GUI,
but Xonok, how would it be in a loop, could you give me some start help, please ?
 
Level 12
Joined
May 22, 2015
Messages
1,051
That is basically how it works, Ardenian. With those numbers, you can make any exact value all the way up to their total when all added together.

To make a loop for it, you would put the abilities in an array. The first one being the largest value ability and decreasing in order from biggest to smallest. At the start of the loop, set the value to check. Each time through the loop, you increase the array number (this would be like for loop integer A - so it does it by itself) and divide the value to check by 2. Set the value to check to the value on the largest ability and set the loop to go a number of times equal to the number of abilities.

It sounds complicated now that I read what I wrote haha. It is actually pretty simple in practice. I'd write the GUI trigger, but it is a bit annoying to do them by hand. Maybe I will later.
 

Ardenian

A

Ardenian

This way ?


  • Test
    • Events
      • Unit - A unit acquires an item
    • Conditions
    • Actions
      • -------- The item adds 50 damage --------
      • Set Bonus_Damage = 50
      • For each (Integer Integer_TempInteger) from 1 to 10, do (Actions)
        • Loop - Actions
          • Set Integer_TempArray = (Integer_TempArray + 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • 'IF'-Conditions
              • IntegerTempCheck Greater than Bonus_Damage
            • 'THEN'-Actions
              • Unit - Add BonusNegativeAbility_Damage[Integer_TempArray] from (Triggering unit)
            • 'ELSE'-Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • 'IF'-Conditions
                  • IntegerTempCheck Smaller than Bonus_Damage
                • 'THEN'-Actions
                  • Unit - Add BonusAbility_Damage[Integer_TempArray] to (Triggering unit)
                • 'ELSE'-Actions
                  • Skip remaining actions


Is this correct, especially with the skipping remaining actions ?

EDIT: Eh, yes, the Integer_TempCheck would have to be set to the value somehow, yes.

---

But how do I register what bonuses an item gives ?
Is there an easy way instead of hashtables or indexed variables ?
 
Level 12
Joined
May 22, 2015
Messages
1,051
This way ?


  • Test
    • Events
      • Unit - A unit acquires an item
    • Conditions
    • Actions
      • -------- The item adds 50 damage --------
      • Set Bonus_Damage = 50
      • For each (Integer Integer_TempInteger) from 1 to 10, do (Actions)
        • Loop - Actions
          • Set Integer_TempArray = (Integer_TempArray + 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • 'IF'-Conditions
              • IntegerTempCheck Greater than Bonus_Damage
            • 'THEN'-Actions
              • Unit - Add BonusNegativeAbility_Damage[Integer_TempArray] from (Triggering unit)
            • 'ELSE'-Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • 'IF'-Conditions
                  • IntegerTempCheck Smaller than Bonus_Damage
                • 'THEN'-Actions
                  • Unit - Add BonusAbility_Damage[Integer_TempArray] to (Triggering unit)
                • 'ELSE'-Actions
                  • Skip remaining actions


Is this correct, especially with the skipping remaining actions ?

EDIT: Eh, yes, the Integer_TempCheck would have to be set to the value somehow, yes.

---

But how do I register what bonuses an item gives ?
Is there an easy way instead of hashtables or indexed variables ?

Hmm we might need to pretty much write the whole trigger at once, mostly due to being GUI.

Hashtable is the easiest and probably best solution for your case. Basically, you have to have a trigger that sets up the whole table with all the item IDs and values for the stats (default integers from the hashtable are 0 - so you don't need to set values for unused stats on items). You can set up the ability arrays in this trigger as well.

Then you have one trigger that is like the one you have there, except it loads the values based on the item type of the item that the unit got or dropped.

Then you need to figure out how much of the bonus stat the unit already has (read this from the unit hashtable). Add the value from the item to the unit's value and then save the new value into the unit hashtable.

Then do your loop for the abilities (though there are some tweaks needed to your code) so the correct amount of the stat is added. Repeat this for all the different stats.

You'd need another trigger for when a unit drops an item.

A major issue with this way of doing it is that you can't reuse the loop that sets the stats without rewriting or copy-pasting the whole thing. It can get messy - mostly when you decide to change something (like add another ability to double the max value of the stat bonus) since you would have to update all the code that is doing this.

Best practice as a programmer is to use a function. In GUI, the easiest way to do this is to make a trigger that does the whole thing using global variables. You can set the global variables in any part of the code and then run this trigger from there. It may not be necessary at first, just keep this in mind if you want to add other ways to use the system.

Here is the trigger updated:

  • Test
    • Events
      • Unit - A unit acquires an item
    • Conditions
    • Actions
      • -------- Load the stored damage value for the unit --------
      • Set Damage_Mod = Load Integer ....
      • -------- Find out how much damage the item adds --------
      • Set Bonus_Damage = Load Integer ....
      • -------- Add the bonus damage and save the new damage value for the unit --------
      • Set Damage_Mod = Damage_Mod + Bonus_Damage
      • Save Integer .....
      • -------- Start with the damage of the max value from the list of the 10 abilities --------
      • Set IntegerTempCheck = 256
      • For each (Integer Integer_TempInteger) from 1 to 10, do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • 'IF'-Conditions
              • Damage_Mod Greater than or equal to IntegerTempCheck
            • 'THEN'-Actions
              • Unit - Add BonusAbility_Damage[Integer_TempArray] to (Triggering unit)
              • Set Damage_Mod = Damage_Mod - IntegerTempCheck
            • 'ELSE'-Actions
              • Unit - Remove BonusAbility_Damage[Integer_TempArray] from (Triggering unit)
          • Set IntegerTempCheck = IntegerTempCheck / 2
      • -------- Below, you would need to add another loop for the negative abilities if you want to have those as well. I'd start without them, though. --------


The jist of what I changed:
- You need to know the total modified damage of the unit, so load it, modify it based on the item, then change which abilities it has.
- It is much more efficient (amount of code, and how it runs) to load the damage for the item from a hash table.
- The damage to compare in the loop starts with the max ability value, not the item value.
- You don't need to increment Integer_TempInteger because the loop will do it for you on its own.
- The positive and negative abilities should be in separate loops - it is much more simple that way. Note that I didn't actually write out the negative one - it works exactly the same, but starts with -256 and the ability array will be different.
- It needs to check Greater than or equal to. Otherwise you would sometimes get very strange bugs.
- You either add the ability that adds the damage and remove that damage from the total (don't need to add that much again) or you remove the ability and do nothing. It is possible that it will try to remove abilities the unit doesn't have or add abilities it already does have, but it will not cause any problems.
- You shouldn't skip the remaining actions at all since it might need to remove the weaker abillities. It is only when a unit gets or loses an item, so you don't have to worry about it being a little slower.

To optimise, you can check if the modified damage is 0. If so, skip the whole trigger.

I hope this is making sense.

For the drop item check, do the same thing except subtract Bonus_Damage from Damage_Mod instead. That's it.

You would end up with 3 triggers:
1) Big initialisation trigger that saves all the item data and ability arrays.
2) Unit acquires an item trigger.
3) Unit loses an item trigger.
 

Ardenian

A

Ardenian

Hu, that's a lot of stuff, thank you!

I think I understand everything, but I have some questions:

1. Do I need a loop for every kind of bonus ?
Or could I create a loop that loops through the registered bonuses in the hashtable ( 0 results into 0) and then loop for every bonus like you showed ?

2. How do I efficiently register these boni ? A the moment, I have variables for every bonus, that means over 10, up to 15 or even more variable settings and hashtable settings for one item.
How do I create something like this:
  • Custom script: call CSS_ItemInitialize('afac', 150, 400, 800, 15, 45, 55, 85, 100, 100, 1000, 10, 3)
in GUI, including custom scripts, keeping in mind they are saved in a hashtable ?

3. As I need the ability Id for saving the boni, what is it exactly when pressing CTRL+D ?
For example, is it A001 und A002 or A001:Ad1 and A002:Ad1 ?

4. Is this the best way for adding custom stats ? It feels wrong to me having so many abilities added.
I mean, per item acquired a unit would get around, maybe 25 abilities this way which are never removed, which means after some time a lot of abilities will stack up.
To avoid this, could there be a workaround saving the added abilities with the item of the hero in a hashtable ?
 
Level 12
Joined
May 22, 2015
Messages
1,051
Hu, that's a lot of stuff, thank you!

I think I understand everything, but I have some questions:

1. Do I need a loop for every kind of bonus ?
Or could I create a loop that loops through the registered bonuses in the hashtable ( 0 results into 0) and then loop for every bonus like you showed ?

2. How do I efficiently register these boni ? A the moment, I have variables for every bonus, that means over 10, up to 15 or even more variable settings and hashtable settings for one item.
How do I create something like this:
  • Custom script: call CSS_ItemInitialize('afac', 150, 400, 800, 15, 45, 55, 85, 100, 100, 1000, 10, 3)
in GUI, including custom scripts, keeping in mind they are saved in a hashtable ?

3. As I need the ability Id for saving the boni, what is it exactly when pressing CTRL+D ?
For example, is it A001 und A002 or A001:Ad1 and A002:Ad1 ?

4. Is this the best way for adding custom stats ? It feels wrong to me having so many abilities added.
I mean, per item acquired a unit would get around, maybe 25 abilities this way which are never removed, which means after some time a lot of abilities will stack up.
To avoid this, could there be a workaround saving the added abilities with the item of the hero in a hashtable ?

1. You could maybe put them all in one loop, but the loop would get very cluttered. It would also mean that all stats have the same number of abilities (or some really ugly if statements).

2. You would need to define a function. Do you know how to shadow global variables and how to create a function using custom script? I know I saw it in a tutorial somewhere, but I'd need to dig it up.

An easier solution might be to add the function into your header as a global, reusable function, but then you'd have to make the whole thing in JASS. In the end, though, you can call the function from anywhere in your triggers with custom script.

3. It is just the A001 part.

4. The abilities are removed if their stats change. Using the abilities with the exponential values allows you to get any specific number within the range of 0 to the total of all their values added together. It may seem like a lot at first, but in the end, you would have only around maybe 50-100 abilities that handle ALL of your stat modifications. Your items will also have 0 abilities except for activated abilities and maybe some other unique abilities like auras.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
You can easily change the loops if you have a configuration trigger. Just load everything necessary from some variables, so you'd never need to touch the looping trigger itself.
Also, you can use the same loop for everything if you put all bonus types into the same array. This requires some math, but greatly decreases trigger editor clutter.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
You can open my Equipment Packages submission in the spell section for inspiration.
There are two resources implemented
1. BonusMod ( by Earthfury, vJass ) which allows to manipulate the wc3 stats.
2 ItemPower ( by me, vJass ) which allows you to create custom stats and register them to items.
Also handles units when equipping/unequipping registered items
 

Ardenian

A

Ardenian

Yes, I planned to give each bonus the same number of different stats, even if it is ridiculous for certain ones.

No, I am not familiar with this. If you mean something like 'local integer i = 1' or something, I used it already, but I don't understand it. I would be glad if you could dig it up. If I can learn it myself I don't have to spam threads and annoy people to learn it.

About 4. This is the loop, the third trigger in your trigger listening, right ?
Well, there is one point I don't understand.
How can I remove an ability a unit does not have ? It seems to me as if this happens when acquiring an item.
For example:
An item adds 255 damage and we start with 256. How would I remove the +1 ability the un it hasn't ? Or do I miss a point ?

Xonok said:
Also, you can use the same loop for everything if you put all bonus types into the same array. This requires some math, but greatly decreases trigger editor clutter.
Could you elaborate further, please ?

I planned to index a unit, saving it in the hashtable. Using a loop I add each required stat in a field, adding +1 to the childtable.
So filed 0 would be the unit, 1 the damage, 2 the critical chance, 3 the spell damage efficiency and so on.

Same for items. I add an item somewhere in the hashtable and its corresponding stats in the same fields like for the unit.

BPower said:
You can open my Equipment Packages submission in the spell section for inspiration.
There are two resources implemented
1. BonusMod ( by Earthfury, vJass ) which allows to manipulate the wc3 stats.
2 ItemPower ( by me, vJass ) which allows you to create custom stats and register them to items.
Also handles units when equipping/unequipping registered items
I see, I can get some information and help from there, thanks a lot!

---

Something else, for indexing the units, I use Bribe's Unit Indexer ? I think he does not index the units into a hashtable by default, does he ?

Also, how can I add an option that only certain unit types are indexed ?
Can I add them into the hashtable and save a Boolean with true to it ?
 
Level 12
Joined
May 22, 2015
Messages
1,051
With the numbers:
1
2
4
8
16
32
64
...

You can add up to any specific number from 0 all the way to the total when you add them all together. As an example, let's pick 83.
83 >= 64 - we turn on the +64 damage ability and subtract 64 from 83: 19
19 < 32 - we turn off the +32 damage ability since it would add too much damage.
19 >= 16 - we add the +16 damage ability and subtrack 16 from 19: 3
3 < 8 - turn off 8
3 < 4 - turn off 4
3 >= 2 - turn on 2 and subtract 2 from 3: 1
1 >= 1 - turn on 1

So now the hero has these abilities for damage:
+64, +16, +2, +1 (this adds up to 83)

The idea is to know how much total damage the unit has from the damage modification system (using a hashtable or a unit indexer to store this value) and then whenever you add or subtract damage, you update based on the total damage added, not just the amount that the one modification changes.

Since you are always updating based on the total, it is impossible for the system to ever need two of any of the abilities. Removing an ability that is not there will cause no problems in WC3, so you don't have to check if the unit has the ability or not before removing and you don't need to check while adding either. It would just add extra checks and slow down and clutter your code.

In the example above, if we subtract 35 damage (let's say the drop an item that added 35 damage), then we update it like this:
83 - 35 = 48

Now we update the abilities with the new damage value: 48
48 < 64 - remove the +64 damage ability
48 >= 32 - add the +32 damage ability and subtract 32: 16
16 >= 16 - add the +16 damage ability and subtract 16: 0
0 < 8 - remove the +8 damage ability
0 < 4 - remove the +4 damage ability
0 < 2 - remove the +2 damage ability
0 < 1 - remove the +1 damage ability

Now the hero has:
+32, +16 (this adds up to 48)

You have to always work with the total damage modification for the unit or you will run into problems where you might need two of the same ability.
 

Ardenian

A

Ardenian

Ahhh, I understand, thank you!

The damage modification value we save does only matter for the damage added with items/ boni, doesn't it ? It has nothing to do with the actual damage of the unit, but only with the damage added via the BonusMod.
 
Level 12
Joined
May 22, 2015
Messages
1,051
Ahhh, I understand, thank you!

The damage modification value we save does only matter for the damage added with items/ boni, doesn't it ? It has nothing to do with the actual damage of the unit, but only with the damage added via the BonusMod.

I think so. I am not 100% sure I fully understand what you are saying.

The damage on an item matters because it is how much you are going to modify the unit's damage, but the item never does anything with the damage modification, only the unit that gets the item. You only need to store the item damage so that you can figure out how much damage to add or remove when a unit gets the item.

Basically, the item will just have a bunch of numbers saved in a hashtable that don't actually do anything on their own. They just represent stats. Then when a unit gets or loses an item, you use that hashtable to look up how much of each stat to add or remove.

Then you modify the damage (or other stat) of the unit based on the number found in the hashtable. The item damage is never modified and the item never actually gains any abilities.
 

Ardenian

A

Ardenian

Ah, I meant, for example, an aura buff adding 10% damage doesn't matter, only the damage added by items does. We modify the damage added via registered items, modifying the damage abilities of the unit.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
I would honestly recommend using a pre-made system when possible. Maybe throw in some adapter functions to a more user-friendly interface and to allow for easy replacement if the system develops a fault.

1. How do I efficiently register the stats an item give, without a limit on the amount of different stats ?
Two possibilities. Most efficient is to make each item a struct where members represent stats and then map the item type to the struct reference using a hashtable. One hashtable lookup gets you the struct reference and that gives you efficient access to all stats (members lookups are just an array lookup). The number of stats is based on number of members and nothing stops you declaring a lot of them as each simply becomes an extra array (cheap resource wise).

A more flexible approach is to use a hashtable to map each stat as a unique child key to an item type as the parent key. This is data wise more efficient (no redundant member space) but comes at the cost of stat lookup being more expensive (each is a hashtable operation). There may also be performance problems for large numbers of entries (eg 200,000) but that usually is not the case.

Are hashtables a good approach to register stat variables for every unit ? Can I also easily distinguish between unit types ?
Yes hashtables are. You can either use a struct reference to a struct with all possible stats (fastest) or map each stat individually (most flexible).

Since units are handles while types are types you will want two separate hashtables for them. One would map keys to handles. The other maps keys to types. Both can be shared by multiple systems. Be aware that hashtable operation performance does degrade with a lot of mappings, in which case you will need multiple hashtables or to optimize mapping usage however this only affects excessive mapping usage (200,000 entries I think if I recall some test data).
 

Ardenian

A

Ardenian

Thanks, so one hashtable to save all items with their stats and one hashtable for all units with their stat variables.

For the unit type check, couldn't I simply save a Boolean == true with unit types within the same hashtable as the units ?
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
For the unit type check, couldn't I simply save a Boolean == true with unit types within the same hashtable as the units ?
If the parent keys are handles then they must only be handles. If you mix in a type key there could be a slim chance of a collision. If the parent keys are types then they must only be types. If you mix in a handle key there could be a slim chance of collision.

The same applies to children keys within a parent key as well. You need to make sure that the key source used for the keys cannot (or as good as never) result in a collision for two unique elements.

Here is a small list of various key type allocations for either parent or child.
  • String hash*
  • Object type
  • Handle ID
  • Enum (uniquely defined named constant)
  • Array index
  • Purpose allocated ranges
* Keys not guaranteed to be unique. However the chance of collision is insignificant enough it can be ignored for the most part.
Each hashtable parent key set must be composed of only one of the above categories. Each child key set for a single parent key must be composed of only one of the above categories. Within a hashtable it is valid for different parent keys to have child key sets composed of different categories as long as each parent has a child key set coming from a single category. Failure to obey this might result in hard to re-create and diagnose bugs.

Commonly you need 2 general purpose hashtables. The first is parent key of handle ID and child key of Enum (or string hash). This is for general purpose mapping of data to various handle types such as units, items, timers etc. The second is parent key of object type and child key of Enum (or string hash). This is for general purpose mapping of data to various object types such as for storing extracted object data to feed into ability triggers or unit type specific data to feed into unit system triggers.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Could you elaborate further, please?

Will your system ever use more than 8190 abilities? Probably not. As such, it is possible to put them all in the same array.
I typically use a scheme where all abilities except the last are negative, but it doesn't truly matter much.
What you need to do is keep track of when in the array the abilities for any bonus type start and end. For that I'd use separate arrays.

BonusAbil - The array that contains the abilities
BonusTypeStart - Where in the array a bonus starts
BonusTypeEnd - Where a bonus ends

In addition you could maybe have some constant non-array variables like:
BonusType_Attack
BonusType_Armor
etc, which can be used as array indices in BonusTypeStart and BonusTypeEnd.

Now when adding abilities you input the bonus type that you want to add among other things. The order that you add the abilities is always the same based on what scheme you use.
The normal scheme goes like this:
1
2
4
8
16
-32
Everything is positive, except the last. With this scheme you first check if the resulting bonus will be positive or negative. If negative, you set a boolean and subtract the value of this ability(-32) from the bonus that you want to add.
Then you loop down normally from the ability before the last(16). Once done, you check if the original bonus was negative and if so, add the last ability (-32). The reason this ability needs to be added last is that it can kill a unit if its max health becomes negative.

Note that you inherently know how large the bonus that a specific ability gives is and thus, don't need to keep track of it.
 

Ardenian

A

Ardenian

Thank you for your elaboration, Dr Super Good and Xonok, I think I can start with it now.
 
Status
Not open for further replies.
Top