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

GetStockInShop v1.02b

Demo has GUI example

JASS:
library GetStockInShop/*
    ==================================================================================================
    OPTIONAL LIBRARIES: I suggest using one of the 2 libraries below for Timer Recycling,
                        but if you don't care about efficiency, this system will still work 
                        even without either of the two libraries recommended.
    */ uses /*
    
    */optional TimerUtilsEx, /*             
                        by Magtheridon96
                        http://www.hiveworkshop.com/forums/graveyard-418/system-timerutilsex-204500/
    */optional TimerUtils  /*              
                        by Vexorian
                        http://www.wc3c.net/showthread.php?t=101322
    ==================================================================================================
    
    
                GetStockInShop v1.02b written by Shadow Flux
                
        This System allows you to get the current stock of an item/unit in a certain shop. 
        For this system to work, the shop and the item/unit type must first be defined using
        Stock.monitor(id, shopUnit, maximumStock, replenishInterval, stockStartDelay)
        
        This system is useful if you want to get the current stock of certain critical items/units
        
        Author's Note:
            I used this system to check if a certain shop has Observer Wards to let the
            AI skip buying an Observer Ward in its next item-to-purchase list if it 
            currently has no Stock.
        
        Look in the Demo Trigger Folder for an example and how it use used in GUI
        
        Requires:
            Jass New Gen Pack
        
        
    Struct Stock
    API:
    *********************************************************************************************
    
    static method monitor takes unit shopUnit, integer id, integer maximumStock, real replenishInterval, 
    real stockStartDelay returns nothing
        - Tells the system to monitor the stock of a certain item/unit type from a certain shopUnit.
        
    *********************************************************************************************
    
    static method get takes unit shopUnit, integer id returns integer
        - Returns the current stock of item/unit type in shopUnit
        
    *********************************************************************************************
    */
    
    globals
        //To display System Messages, set DEBUG_SYSTEM = true and run in DEBUG MODE (Main Toolbar -> JassHelper -> Debug Mode)
        private constant boolean DEBUG_SYSTEM = true
        ////////////////////////////////////////
        private trigger trg = CreateTrigger()
        private hashtable ht = InitHashtable()
    endglobals
    
 
    struct Stock
    
        private integer currentStock
        private integer maxStock
        private real refreshTime
        private boolean onStartDelay
        
        debug private unit shop
        debug private integer id
        
        static method get takes unit shopUnit, integer id returns integer
            return thistype(LoadInteger( ht, GetHandleId(shopUnit), id )).currentStock
        endmethod
        
        private static method timerExpire takes nothing returns nothing
            local timer t = GetExpiredTimer()
            static if LIBRARY_TimerUtilsEx then
                local thistype this = GetTimerData(t)
            elseif LIBRARY_TimerUtils then
                local thistype this = GetTimerData(t)
            else
                local thistype this = LoadInteger(ht, GetHandleId(t), 0)
            endif
            set this.currentStock = this.currentStock + 1
            if this.currentStock == this.maxStock then
                static if LIBRARY_TimerUtilsEx then
                    call ReleaseTimer(t)
                elseif LIBRARY_TimerUtils then
                    call ReleaseTimer(t)
                else
                    call PauseTimer(t)
                    call DestroyTimer(t)
                endif
            else
                if this.onStartDelay then
                    call TimerStart(t, this.refreshTime, true, function thistype.timerExpire)
                    set this.onStartDelay = false
                endif
            endif
            
            set t = null
            //////////////////////////////////////////////////////////////////////////////////////////
            static if DEBUG_SYSTEM then
                debug call BJDebugMsg("|cffffcc00GetStockInShop|r: Stock of |cffffcc00" + GetObjectName(this.id)/*
                */+ "|r in |cffffcc00" + GetUnitName(this.shop) + "|r is increased to " + I2S(this.currentStock))
            endif
        endmethod
        
        private static method onBuy takes nothing returns boolean
            local integer idType = GetUnitTypeId(GetSoldUnit())
            local thistype this
            static if not(LIBRARY_TimerUtilsEx) then
                static if not(LIBRARY_TimerUtils) then
                    local timer t
                endif
            endif
            
            //Check if the stock sold is unit or item
            if idType == 0 then
                set idType = GetItemTypeId(GetSoldItem())
            endif
            set this = LoadInteger( ht, GetHandleId(GetTriggerUnit()), idType )
            
            if this != 0 then
                if this.maxStock == this.currentStock then
                    static if LIBRARY_TimerUtilsEx then
                        call TimerStart(NewTimerEx(this), this.refreshTime, true, function thistype.timerExpire)
                    elseif LIBRARY_TimerUtils then
                        call TimerStart(NewTimerEx(this), this.refreshTime, true, function thistype.timerExpire)
                    else
                        set t = CreateTimer()
                        call SaveInteger(ht, GetHandleId(t), 0, this)
                        call TimerStart(t, this.refreshTime, true, function thistype.timerExpire)
                        set t = null
                    endif
                endif
                set this.currentStock = this.currentStock - 1
                static if DEBUG_SYSTEM then
                    debug call BJDebugMsg("|cffffcc00GetStockInShop|r: Stock of |cffffcc00" + GetObjectName(this.id)/*
                    */+ "|r in |cffffcc00" + GetUnitName(this.shop) + "|r is decreased to " + I2S(this.currentStock))
                endif
            endif
            return false
        endmethod
        
        
        
        static method monitor takes unit shopUnit, integer stockId, integer maximumStock, real replenishInterval, real stockStartDelay returns nothing
            local thistype this = thistype.allocate()
            local integer hId = GetHandleId(shopUnit)
            static if not(LIBRARY_TimerUtilsEx) then
                static if not(LIBRARY_TimerUtils) then
                    local timer t
                endif
            endif
            
            //Struct Data
            debug set this.id = stockId
            debug set this.shop = shopUnit
            set this.maxStock = maximumStock
            set this.refreshTime = replenishInterval
            call SaveInteger(ht, hId, stockId, this)
            
            //Register Shop to Trigger Event to detect when a player has bought from that shop
            if not(HaveSavedBoolean(ht, hId, 0)) then
                call SaveBoolean(ht, hId, 0, true)
                call TriggerRegisterUnitEvent(trg, shopUnit, EVENT_UNIT_SELL_ITEM)
                call TriggerRegisterUnitEvent(trg, shopUnit, EVENT_UNIT_SELL)
                //////////////////////////////////////////////////////////////////////////////////////////
                static if DEBUG_SYSTEM then
                    debug call BJDebugMsg("|cffffcc00GetStockInShop|r: " + GetUnitName(shopUnit) + " is now being monitored")
                endif
            endif
            
            //If there is a stockStartDelay
            if stockStartDelay > 0 then
                set this.onStartDelay = true
                set this.currentStock = 0
                static if LIBRARY_TimerUtilsEx then
                    call TimerStart(NewTimerEx(this), stockStartDelay, false, function thistype.timerExpire)
                elseif LIBRARY_TimerUtils then
                    call TimerStart(NewTimerEx(this), stockStartDelay, false, function thistype.timerExpire)
                else
                    set t = CreateTimer()
                    call SaveInteger(ht, GetHandleId(t), 0, this)
                    call TimerStart(t, stockStartDelay, false, function thistype.timerExpire)
                    set t = null
                endif
            else
                set this.currentStock = maximumStock
            endif
            
        endmethod
        
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(trg, Condition(function thistype.onBuy))
        endmethod
        
    endstruct
    
endlibrary

DEMO TRIGGERS:

  • InitStockData
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- Set gold to very high value --------
      • Player - Set Player 1 (Red) Current gold to 99999
      • Game - Display to (All players) the text: Select a Shop and P...
      • -------- ----------------------------------------- --------
      • -------- Monitor the Items of these two shops --------
      • Set tempUnit = Goblin Merchant #1 0001 <gen>
      • Custom script: call Stock.monitor(udg_tempUnit, 'I000', 10, 30, 5)
      • Custom script: call Stock.monitor(udg_tempUnit, 'I001', 20, 5, 0)
      • Custom script: call Stock.monitor(udg_tempUnit, 'I002', 50, 2, 0)
      • Set tempUnit = Goblin Merchant #2 0002 <gen>
      • Custom script: call Stock.monitor(udg_tempUnit, 'I000', 10, 30, 5)
      • Custom script: call Stock.monitor(udg_tempUnit, 'I001', 20, 5, 0)
      • Custom script: call Stock.monitor(udg_tempUnit, 'I002', 50, 2, 0)
      • Set tempUnit = Red Dragon Roost 0003 <gen>
      • Custom script: call Stock.monitor(udg_tempUnit, 'n003', 10, 2, 1)
      • Custom script: call Stock.monitor(udg_tempUnit, 'n004', 5, 5, 5)

  • GetStock
    • Events
      • Player - Player 1 (Red) skips a cinematic sequence
    • Conditions
    • Actions
      • Set tempGroup = (Units currently selected by Player 1 (Red))
      • Set tempUnit = (Random unit from tempGroup)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Or - Any (Conditions) are true
            • Conditions
              • (Unit-type of tempUnit) Equal to Goblin Merchant #1
              • (Unit-type of tempUnit) Equal to Goblin Merchant #2
        • Then - Actions
          • Custom script: set udg_stock1 = Stock.get(udg_tempUnit, 'I000')
          • Custom script: set udg_stock2 = Stock.get(udg_tempUnit, 'I001')
          • Custom script: set udg_stock3 = Stock.get(udg_tempUnit, 'I002')
          • Game - Display to (All players) the text: ===================...
          • Game - Display to (All players) the text: (Looking at + (Name of tempUnit))
          • Game - Display to (All players) the text: (Stock #1 = + (String(stock1)))
          • Game - Display to (All players) the text: (Stock #2 = + (String(stock2)))
          • Game - Display to (All players) the text: (Stock #3 = + (String(stock3)))
          • Game - Display to (All players) the text: ===================...
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Unit-type of tempUnit) Equal to Red Dragon Roost
            • Then - Actions
              • Custom script: set udg_stock1 = Stock.get(udg_tempUnit, 'n003')
              • Custom script: set udg_stock2 = Stock.get(udg_tempUnit, 'n004')
              • Game - Display to (All players) the text: ===================...
              • Game - Display to (All players) the text: (Looking at + (Name of tempUnit))
              • Game - Display to (All players) the text: (Stock #1 = + (String(stock1)))
              • Game - Display to (All players) the text: (Stock #2 = + (String(stock2)))
              • Game - Display to (All players) the text: ===================...
            • Else - Actions
      • Custom script: call DestroyGroup(udg_tempGroup)
Credits:
- Magtheridon96
- Vexorian


Keywords:
get, item, unit, stock, shop
Contents

Just another Warcraft III map (Map)

Reviews
13:29, 11th Aug 2015 BPower: Looks good to me. Approved. For perfect cleanup you could remove the hashtable entry when destroying the native timer handle ( case: no TimerUtils/Ex in map ). RemoveSavedInteger(...) is the native you would need...

Moderator

M

Moderator

13:29, 11th Aug 2015
BPower: Looks good to me. Approved.

For perfect cleanup you could remove the hashtable entry when
destroying the native timer handle ( case: no TimerUtils/Ex in map ).
RemoveSavedInteger(...) is the native you would need for that.

In my opinion a rating of useful seems to fit to the content.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Changelog:

v1.02b [11 August 2015]
- Fixed compilation error in non-Debug Mode.
- Used TimerUtlils NewTimerEx API.

v1.02 [11 August 2015]
- Added monitoring unit stock
- Required libraries are now only optional
- Changed argument order of 'method get' for function inlining
- this.itemMonitored removed.

v1.01 [9 August 2015]
- Privatized struct variables and methods for encapsulation
- Used GetObjectName in Debugging
- Minor scripting changes.

v1.00 [8 August 2015]
- Initial Release
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
struct ItemStock -> struct ItemStock extends array(You never use allocate or deallocate, so it can extend array to not generate these useless methods[in this case]).

Provide a link to requirements, its not easy to download stuff that could be either here, wc3c, blizzmodding, the helper or some arbitrary website.

The requires is a little inprecise. You need JassHelper even if you have JNGP(you could just shift delete the folders and then good bye JassHelper :D)

Yes, Im nitpicking mofo.

onBuy and timerExpire should be private. You also have two extra new lines that is itching my butt after onBuy :D

Instead of creating and removing item, you can potentially use GetObjectName(Blizzard is claiming that it should work for items in their common.j).

If you really want to save a line :D
JASS:
        static method get takes integer itemTypeId, unit shopUnit returns integer
            return thistype(LoadInteger( ht, GetHandleId(shopUnit), itemTypeId )).currentStock
        endmethod

but yeah, a tad confusing.

All the variables should also be private, you dont want people to mess with these do you.

I like the fact that you allowed monitoring.
Could you maybe expand it to be able to not monitor unit, but unit type? So all shops(still could use the gethandleid etc)
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
struct ItemStock -> struct ItemStock extends array(You never use allocate or deallocate, so it can extend array to not generate these useless methods[in this case]).
Sorry, I'm just starting vJass, then how can I allocate instances?

Provide a link to requirements, its not easy to download stuff that could be either here, wc3c, blizzmodding, the helper or some arbitrary website.
Will do

The requires is a little inprecise. You need JassHelper even if you have JNGP(you could just shift delete the folders and then good bye JassHelper :D)
So I will just put " JassHelper (which comes with Jass New Gen Pack)"


onBuy and timerExpire should be private. You also have two extra new lines that is itching my butt after onBuy :D
Ooops, forgot to make those private. What extra lines?

Instead of creating and removing item, you can potentially use GetObjectName(Blizzard is claiming that it should work for items in their common.j).
If Blizzard's claim is true, then I will use that. I've been searching for that function for quite sometime now. I knew it, a function like that exist.

If you really want to save a line :D
JASS:
        static method get takes integer itemTypeId, unit shopUnit returns integer
            return thistype(LoadInteger( ht, GetHandleId(shopUnit), itemTypeId )).currentStock
        endmethod

but yeah, a tad confusing.
I actually want it to be a one line code but I don't know how to. Now I know.

All the variables should also be private, you dont want people to mess with these do you.
Ok, now I know that struct members can be private too.

I like the fact that you allowed monitoring.
Could you maybe expand it to be able to not monitor unit, but unit type? So all shops(still could use the gethandleid etc)
Sure, but in maybe later.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
ah no, just noticed you actually do call thistype.create(). Then you cant use extends array.

When your struct is declared with extends array, it will not generate two special methods, allocate and deallocate.
Allocate allocates new instance of that type, and deallocate deallocates that instance(puts it back so it can be allocated again).

If you do not define create and destroy methods in your struct, JassHelper will generate one for you, which is forwarder to allocate and deallocate(should even inline).

If you were to declare it with extends array, nothing of this would exist, and since you do allocate instances via create inside monitor, you cant even do that.

Thats my fault tho, I missed that line.

Well the JassHelper is just a nitpicky comment :D You can just say it requires JNGP, since JNGP defaultly comes with 2 JassHelpers(since 2.0.X), so.

I would test the GetObjectName first tho.

The one liner works like this:
You get integer from the hashtable, that is obvious.
Then there is the thistype(...), that basically says to the compiler that the integer you got(you put between the parenthesis) is actually going to be of your type(you basically typecast the integer to instance), and then after the closing bracket you do .currentStock and that will retrieve the currentStock for that intsance.

You need to typecast it, because otherwise the JassHelper would just say "Oh this is integer, integers do not allow . syntax! AHA! Lets shove some nasty, probably unrelated error message up his face!".
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
JASS:
        static method get takes integer itemTypeId, unit shopUnit returns integer
            return thistype(LoadInteger( ht, GetHandleId(shopUnit), itemTypeId )).currentStock
        endmethod
This line could potential be inlined by the JassHelper, if you change the order of arguments.
--> static method get takes unit shopUnit, integer itemTypeId returns integer most likely
fullfils all required criterias. For more information check out the JassHelperManual ( link in my signature. Browse for "inline" )

I guess your debug struct members ( shop, itemType ) should also be private.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
JASS:
        static method get takes integer itemTypeId, unit shopUnit returns integer
            return thistype(LoadInteger( ht, GetHandleId(shopUnit), itemTypeId )).currentStock
        endmethod
This line could potential be inlined by the JassHelper, if you change the order of arguments.
--> static method get takes unit shopUnit, integer itemTypeId returns integer most likely
fullfils all required criterias. For more information check out the JassHelperManual ( link in my signature. Browse for "inline" )

I guess your debug struct members ( shop, itemType ) should also be private.

Ok then. I will wait for other issues before I update because I've just recently updated and the changes you suggest are only few at the moment.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Status Report

Side note:
I wonder why Mag didn't care about optimal backwards compatibilty to TimerUtils. It would just be two words.
--> TimerUtilsEx requires optional Table optional TimerUtils


Issues you should take care about:
  • Initialize trigger trg directly in the global block or use a module initializer.
    I know it's nasty, but otherwise you might run into unwanted errors.
    private trigger trig = CreateTrigger()
  • Instead of boolean this.itemMonitored, you could do an integer comparison this != 0.
    this will be 0 in case there is no entry saved in the hashtable for the sold item.
  • static method get might be inlined by the Jasshelper, if you change to order of arguments.
    --> static method get takes unit shopUnit, integer itemId returns integer ( For inlining criterias please open the JassHelperManual )
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I think I should also make this compatible with TimerUtils using static ifs also AND alliow monitoring of unit sold stock.

edit: done

edit2:
Status Report

Issues you should take care about:
  • Initialize trigger trg directly in the global block or use a module initializer.
    I know it's nasty, but otherwise you might run into unwanted errors.
    private trigger trig = CreateTrigger()
  • Instead of boolean this.itemMonitored, you could do an integer comparison this != 0.
    this will be 0 in case there is no entry saved in the hashtable for the sold item.
  • static method get might be inlined by the Jasshelper, if you change to order of arguments.
    --> static method get takes unit shopUnit, integer itemId returns integer ( For inlining criterias please open the JassHelperManual )
Done

System updated to v1.02
 
Last edited:
Top