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

Baradé's Paged Buttons 1.4

This bundle is marked as pending. It has not been reviewed by a staff member yet.
A simple system for paged button shops which allow one single shop to sell more items/units and provide more abilities than UI slots are available in Warcraft III.
Two user-defined unit types are used to change the pages forward and backward, so every unit nearby a shop can change pages.
You can add placeholders to show items/units grouped at specific pages.
You can give the pages names which are applied to the shop unit's name.
The current page is shown in form of stocks for the change page unit types.

The disadvantage of using one building is that in multiplayer more than one player can change the page at the same time.
However, if you use the custom UI which works per player locally this will not be that much of an issue since you can take your time to check the buttons and then click on one once to use it.

There is initial support for stock delays and intervals which are kept even when the page is changed.
Otherwise, you could buy a unit or item, change the page and then buy it again without any delay.
However, this requires you to specify the delays etc. manually per unit and item type since there is no native to retrieve this data.

This code shows how you enable a shop with two pages containing one unit and one item each and containing an ability in the second page:

JASS:
call EnablePagedButtons(shop)

call NextPagedButtonsPage(shop, "Page 1")
call AddPagedButtonsUnitType(shop, 'nrdr')
call AddPagedButtonsItemType(shop, 'stwp')

call NextPagedButtonsPage(shop, "Page 2")
call AddPagedButtonsUnitType(shop, 'nrwm')
call AddPagedButtonsItemType(shop, 'dust')
call AddPagedButtonsAbility(shop, 'Andt')

The optional extension PagedButtonsUI adds a custom UI which is shown to players when selecting a paged buttons shop.
It shows up to 180 buttons at once and works per player, so players do not interfere with each other except when clicking on the button which might change the shop's current page.
The number in the upper left of each button shows the matching page of the button. The number in the lower right shows the number of stocks. However, this number is 1 for each button for now.

The custom UI can be enabled/disabled per player with
JASS:
function SetPagedButtonsUIEnabledForPlayer takes player whichPlayer, boolean enabled returns nothing
.

History
I needed a system like this for my map World of Warcraft Reforged since there were too many heroes, items, learnable spells etc. available which would have required me to place many buildings on the map. Besides, the player can build such buildings which means that there would have been more space required in the base.

TODOS:
- Fix/highlight the hotkeys of shown items/units. Warcraft somehow does not show them when adding items/units with natives.
- Only allow certain shops for a player and his/her allies and not all players. Maybe create a shop per player which is still less than X non-paged shops.
- Make the fake stock delay system a bit more modular as an optional library and make it more precise to avoid duplicated buyings.
- Update the stock number in the custom UI.
Contents

Baradé's Paged Buttons 1.4 (Map)

Reviews
Wrda
Glad you managed to figure the problem out. While at first page, if I buy Mud Golem, it won't trigger the cooldown of Mud Golem from the other pages, while if I buy it, say on page 2 or 3, then the cooldown appears only on page 1. It looks like...

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,887
JASS:
public static method create takes unit shop, integer id, boolean isUnit, boolean isSpacer, integer startStock, integer maxStock, integer startDelay, integer replenishInterval returns thistype
    local thistype this = thistype.allocate()
    set this.shop = shop
    set this.id = id
    set this.isUnit = isUnit
    set this.startStock = startStock
    set this.maxStock = maxStock
    set this.currentStock = startStock
    set this.startDelay = startDelay
    set this.replenishInterval = replenishInterval
    set this.elapsedTimeStartDelay = 0
    set this.elapsedTimeReplenishInterval = 0
    if (head == 0) then
        set head = this
    else
        set this.previous = head
        set head = this
    endif
    return this
endmethod


Currently in the create method the instance isn't having the member isSpacing set as the value of the parameter. I assume with the TODO comment above about creating two types.

GetPagedButtonsType doesn't look like it's working properly. Attemped to print instance of Forest Troll Berserker and Forest Troll Shadow Priest and I got the same number (55). Same thing goes for Red Dragon Whelp and Red Drake (1).
It seems to me buying any unit on Advanced Mercenary Camp triggers the cooldown of Forest Troll Berserker when moving out to another page and then coming back, moving from these two pages makes the stock cooldown timer reset to 210 seconds.

RemovePagedButtonsId seems incomplete.
Doesn't look that worthy to have RefreshPagedButtonsPage function instead of calling the function it calls directly.

I don't really understand why AddPagedButtonsId is defining startStock, maxStock, startDelay and replenishInterval for all units/items, while unit/item types have their own values. Even when buying stuff it shows the replenish interval matching the object editor's one, and not the one on code.

Good idea to start with, but requires some tweaking.
 
Level 25
Joined
Feb 2, 2006
Messages
1,689
I will adapt and fix the stuff you mentioned.

The reason why AddPagedButtonsId needs these values is that I cannot retrieve the values from the object editor (fields usit, usma, usrg, usst). I need the values to fake the whole stock delay stuff in Type.timerFunctionUpdateTime and only if AUTO_UPDATE_STOCKS is set true. If I do not do that, there won't ever be any delay and all items/units will be available only 1 or whatever the number is I pass. The system needs to know how many stocks should be available at any point in time to force it. It will look like the delay is always starting newly because I cannot fake the blue texture which shows the "stock cooldown" but in actually my own timer forces it to become available after the delay of 30 replenish interval of 30 seconds has passed.

For example, a Black Dragon Whelp ('nbdr') has these values:
  • Stock Initial After Start Delay: 1
  • Stock Maximum: 1
  • Stock Replenish Interval: 160
  • Stock Start Delay: 440

You have to specify them in PagedButtonsSystemConfig (which I will do in the next update of the system and I will also rename some stuff).
The data will then be used to set the correct stock in the beginning and the global timer will update it.
When it is sold, this data is also recognized and the delay starts until the stock is refilled.
It is still a bit buggy and as I wrote I cannot change the blue cooldown texture to a specific delay unless there would be a native function like changing the ability cooldown.

If a user does not care about the correct stock stuff, the user can simply set AUTO_UPDATE_STOCKS to false or not specify stuff in PagedButtonsSystemConfig.

If you know any better way to fake this stuff or to retrieve the fields etc. pleas let me know.
It was the only solution that came to mind except if Warcraft stores this stuff natively.

edit:
Updated to 1.1:
  • Fix GetPagedButtonsType.
  • Mention that RemovePagedButtonsId is incomplete.
  • Remove function RefreshPagedButtonsPage.
  • Make option constants public.
  • Add separators in API documentation comment.
  • Add callback functions GetTriggerShop, GetTriggerPreviousPage, GetTriggerAvailableObject, TriggerRegisterChangePagedButtons and TriggerRegisterObjectAvailable and use them in the example map.
  • Use different structs for spacers than for actual object IDs to save memory.
  • Configure unit and item types in the example map config to show how it can be used.
  • Only apply start delay once to refresh the stock.
  • Only apply replenish delay if the current stock is smaller than maximum stock.
  • Fix traversing list in method timerFunctionUpdateTime.
  • Fix setting elapsedTimeReplenishInterval to 0 in method timerFunctionUpdateTime.

There seems to be an issue with the native RemoveItemFromStock. It seems to remove the wrong item types from the shop when it refreshes the Boots of Speed item.
 
Last edited:
I feel like this is something that I could solve in Warsmash in a couple of seconds by just adding a "Next Page" button that only affects the local player's UI for that selected building, so that buildings could have more than 12 things on the "Sold" lists. Or even a new unit editor field like Techtree - Show "Next Page" Button.

But I guess for people who can't wait until Warsmash has feature parity with Blizzard, to each our own. Looks like a useful system. Seems a little weird that there isn't already an established standard for solving this.
 
Level 25
Joined
Feb 2, 2006
Messages
1,689
True, it would be so much easier to have native support for this also for training units and researching upgrades and upgrading buildings etc. etc. it would completely remove the limit of all fields. The same could be done for the unit inventory. It would be much easier to have this only changed per player that's why I am planning to add a custom UI extension which allows this.

I haven't looked for any specific system like that before and only know of the spell book ability stuff.
 
Last edited:
why I am planning to add a custom UI extension which allows this.
I do not envy you this aspiration.

I became reasonably convinced that in order to construct what you describe in any reasonable amount of time, the most reasonable solution would be to use the UI natives added in Reforged prepatches and to recreate from nothing the Melee Game Interface. If you could have an emulation of the same interface that was recreated with the UI natives, then you would not face the obtuse horror of "the black box" as it is now, where there is a C++ Melee Game Interface that can be turned On or Off but its components can sometimes be exceedingly difficult to override.

Unfortunately, the result of my investigations in 2019 with these UI natives was that they did not support this concept fully, because of holes in the APIs and missing features, so for my own part I decided to rewrite the entire game on a different platform instead. It would be arguable that doing so would not be particularly much harder, given that even by 2019 the Hive's "View in 3D" open source tool already supported a "world frame" of sorts, and loading game terrain and previewing amap, so it would seem that if you do this undertaking you are considering you may as well not use Blizzard's proprietary system for it by the time you were done.

But this is only my conclusion. Maybe the complex web of interactions, like Way Gates that create holes in a pathfinding mechanism or Line of Sight Block (Large) objects that block the line of sight "just so" or the way that multiple abilities with the same Order ID erroneously cast each other when clicked are systems so vital to your Warcraft III experience -- and indeed systems currently missing from the Warsmash system that I so often like to mention -- that really what I am suggesting may sound like crazy talk and not something that interests you.

But if you fall that way, and it doesn't interest you, I would at least suggest trying to read through MeleeUI.java on Warsmash if you feel that way, and trying to consider: "Is Retera's hodgepodge imitation of the black box melee UI something that I would implement 1:1 inside of Reforged so that I could mod the game better?" If you find the answer, I might be interested to hear if you share it with me. I had originally considered constructing the entire UI with userland JASS code so that it would be easier to overwrite it from within a map on my system. Although I scrapped the feature, the function calls that I use to spawn UI components were in many cases inspired by the Reforged UI natives. It might be conceivable that you could "port it back" and try to get an open source Melee Game Interface running on Reforged with the original black box one turned off.

But I still think between gaps in Activision Blizzards new APIs, and the fact that my code is a grotesquity of everything being shoved into one file, you might easily decide that what I'm suggesting is madness, or that you just actually hate my code and wouldn't want to try to port it because you can do it so much better inside Reforged UI natives and have the end result code be so much cleaner than what I did in my own creative space. Honestly, if you find that out in your journey, maybe send me a DM about it. It might be a story I would be interested to hear, even if just for the personal curiosity of it.
 
Level 25
Joined
Feb 2, 2006
Messages
1,689
I think the easiest way with Warsmash for map creators like me would be to first have support for all existing stuff from Frozen Throne, all natives, all abilities etc. and basic AI and then to provide more natives and more data fields in a way it still works with Frozen Throne and even Reforged.

If there is a number of data fields and natives which are just too useful to be used, people might change to Warsmash and use their Warcraft installation only for the data files. Recently, I have played this mod for Caesar III: GitHub - Keriew/augustus: An open source re-implementation of Caesar III and it is based on the Open Source implementation of the engine of Caesar III: GitHub - bvschaik/julius: An open source re-implementation of Caesar III
You only use your Steam installation of Caesar III for the data files.
If Warsmash will reach this point in development, I would gladly use it for my map WoW Reforged.
I have so many ideas for natives and object data fields, running servers etc. etc. which is not fixed by Blizzard.
You could create a list with new natives and data fields which are supported by Warsmash only to advertise the project. However, I am not sure if people have the time or knowledge to contribute much to your source code.
 

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,887
There seems to be an issue with the native RemoveItemFromStock. It seems to remove the wrong item types from the shop when it refreshes the Boots of Speed item.
Looks like RemoveUnitFromStock also has the same problem.
Buying Forest Shadow Priest Troll removes page up button, buying Mud Golem removes Forest Troll Berserker, or page up button, or page down button, buying Ogre Mauler removes some of those too. It seems extremely janky.
 
Level 25
Joined
Feb 2, 2006
Messages
1,689
:( Is there any solution for this issue? I do not know how to fix the natives.

edit:

Found the bug. It occurs only when I remove the types while the stock is still being refilled. Adding some further delay fixes this issue however if it is too long it will allow buying an item or unit twice. Maybe I will decrease the timer interval in the future to prevent this issue.

Update 1.2:

  • Improve API documentation.
  • Implement function RemovePagedButtonsId.
  • Add function IsPagedButtonItem.
  • Add function IsPagedButtonAbility.
  • Add function AddPagedButtonsAbility.
  • Add Advanced Goblin Laboratory to example map to demonstrate abilities.
  • Add function NextPagedButtonsPage.
  • Add function IsPagedButtonEnabled.
  • Add function SetPagedButtonEnabled.
  • Show next and previous page numbers as stock charges for the next and previous page buttons.
  • Add option SHOW_NEXT_AND_PREVIOUS_PAGE_NUMBER.
  • Add option STOCK_DELAY_THRESHOLD to fix removing wrong unit or item types on refreshing their stocks.
  • Fix used page number in function SetPagedButtonsCurrentPageName.
  • TriggerRegisterObjectAvailable works now even if the ID is not visible in the shop.


Abilities can be useful too and enabling/disabling stuff makes it more flexible.

I also thought about adding true per player support by creating shops per user in the game and redirecting selection events to them.
Hence, every user will see a custom shop. The stocks have to be synchronized between them.
I would make this an optional feature since it requires many shop buildings inside each other.
 
Last edited:

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,887
Glad you managed to figure the problem out.
While at first page, if I buy Mud Golem, it won't trigger the cooldown of Mud Golem from the other pages, while if I buy it, say on page 2 or 3, then the cooldown appears only on page 1.
It looks like there's no actual de-indexing on the hashtable, specifically on RemovePagedButtonsId function. You could apply an adapted form of the dynamic indexing de-indexing technique, swapping the destroying instance in child key with the one in max index, actually destroy the instance the concerned instance, updating KEY_TYPE, and KEY_COUNTER. Tell me if you want me to elaborate if this wasn't clear enough.
Local integer count isn't being used in AddPageButtonsIdType function at all.
Perhaps GetPagedButton should be renamed to GetPagedButtonTypeByIndex since you have GetPagedButtonsIndex and GetPagedButtonsType

It's looking good, although the documention is lacking a bit, even on the most obvious functions it's preferable to have some sort of description. Is the intention shop per player to remove the possibility of players switching pages all the time? It kind of looks funny to troll with such a thing, nonetheless it might not happen that often.
I tried to see how computers react to your shops, they only like scrolls of town portals, sad.
Occasionaly the shop replenishes the wrong unit (Black Dragon Whelp message popped up) when I never bought it. Don't know what exactly happened there.

All in all, it's starting to get solid, just some a few issues here and there.
 
Level 25
Joined
Feb 2, 2006
Messages
1,689
Yes that is the intention. It could be annoying to look at a shop together with many players if they keep switching pages.

About the de-indexing etc. I thought about first creating a struct Shop and only store its instance per shop unit and keep the list of buttons in the struct in some vJass linke list (there are some modules). This might make the deindexing obsolete.

The replenish message shows up when the stock refills it even although it is currently not shown and you never bought it since I guess units and items are replenished from the beginning.

About the AI: I guess they would need to understand how to change pages to get items. For better AI support the best way would be to create a dummy shop for EVERY PAGE, so AI can use it properly. Maybe I could add such an optional feature.

I will check the rest at home.
 
Last edited:
Level 25
Joined
Feb 2, 2006
Messages
1,689
A major update to version 1.4 which adds an optional library to show a custom UI which appears when selecting any shop with paged buttons. The custom UI lists up to 180 buttons per page. This helps in my map which has hundreds of different heroes and spells in one single tavern. There is an option now to disable paged buttons shops automatically on death which is true by default.

Besides, I refactored the code a lot and tried to improve the performance etc. It does not use the LinkedList dependency anymore which was pretty slow compared to JASS native arrays when getting stuff by index.
Some more helper functions have been added and documented.

edit:
  • Fixed an issue of showing the same buttons again at the second page of the custom UI for paged buttons shops.
  • Open the custom UI at the page matching the current page of the shop.
  • Added more Sunken Ruins creeps to the creep shop.
 
Last edited:
Top