1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. The Aftermath has been revealed for the 19th Terraining Contest! Be sure to check out the Results and see what came out of it.
    Dismiss Notice
  3. Melee Mapping Contest #3 - Results are out! Congratulate the winners and check plenty of new 4v4 melee maps designed for this competition!
    Dismiss Notice
  4. The winners of our cinematic soundtrack competition have been decided! Step by the Music Contest #11 - Results to check the entries and congratulate the winners!
    Dismiss Notice

TrainingDetection v3.1

Submitted by IcemanBo
This bundle is marked as approved. It works and satisfies the submission rules.
Info:

The system provides information about objects being trained.
It also allows you to loop through the current training queue,
and saves the amount object types that were entirely trained by a unit.
Now it also provides funtion to get remaining time of a unit being trained.

Credits:


Code:
Code (vJASS):

library TrainingDetection initializer Init/* v3.1 -- www.hiveworkshop.com/threads/trainingdetection.248978/

Information
 
   The system provides information about objects being trained.
   It also allows you to loop through the current training queue,
   and saves the amount object types that were entirely trained by a unit.

   Objects are: Units, Researches/Techs, Upgrades

*/
requires /*

        */
TimerUtils     /* www.wc3c.net/showthread.php?t=101322
        */
Table          /* www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/


                                API
*/
                       
//! novjass      
 
    // Last & Current getters
 
    GetCurrentTrainedObjectId   takes unit whichbuilding returns integer
    GetLastTrainedObjectId      takes unit whichbuilding returns integer
 
    GetBuildTimeRemaining       takes unit whichbuilding returns real
    // It will return the remaining time for a unit being trained.
    // Sadly the native only works for normal units, not for heroes.
    // For anything but a normal unit it will return 0.
 
 
    // Queue Getters
 
    // Attention!
    // The slot number might not be identical with the true position in order-queue.
    // Only the size number is identical. But the true slot positon might change.
    // With knowing the size you might loop through the queue each get each element.
 
    GetTrainingQueueSize        takes unit whichbuilding                    returns integer
    GetTrainedObjectId          takes unit whichbuilding, integer whichslot returns
 
    ReOrderTrainingQueue        takes unit building returns nothing
        // ReOrder will internally stop and start trainings to simulate cancel at front,
        // be aware of that. After cleaning the queue, all units will have correct position in queue.
        // (works with GetCleanTrainingQueue, see later)
     
    CancelTrainingBack          takes unit building returns nothing
         // Back works same as using order native with CANCEL-id
    CancelTrainingFront         takes unit building returns nothing
         // Front will internally stop and start trainings to simulate cancel at front, be aware of that.

    // Count objects
 
    CountFinishedUnits          takes unit whichbuilding returns integer
    CountFinishedTechs          takes unit whichbuilding returns integer
    CountFinishedUpgrades       takes unit whichbuilding returns integer
 
    // A bit advanced:
    GetCleanTraningQueue        takes unit building, boolean keepFirst returns nothing
        // After calling it the building will have all traings canceled.
        // set keepFirst "true" to not having currently object training canceled. This is an
        // exception, because first element does not need to get canceled to retrieve
        // the correct object ID.
     
        // After calling, you will have access to:
            integer array TrainingDetection_Queue[]
        // which will hold correct order of trained objects. Loop from 1 to .. until Queue[i] == 0.
        // to access all elements. The array should be used to re-order the building's training.
        // It does not matter if you keepFirst, or not, Queue[1] is always the first object in training.
     
        // ==> you may use this tequnique to only re-order certain objects, for example,
        // how it's used inside function CancelTrainingFront, which does not keep first, and then
        // starts at Queue[2] to re-order training the objects.
     
//! endnovjass

//  =============================   End API  =============================  //

native GetUnitBuildTime     takes integer unitid                        returns integer
private struct TimeData
    readonly boolean allocated
    readonly timer clock  
    private static key k
    private static Table table = k
    method destroy takes nothing returns nothing
        if .allocated then
             // for safety we let expire the timer like instantly
            call TimerStart(.clock, 0, false, null)
            call ReleaseTimer(.clock)
            call .deallocate()
            set .allocated = false
        endif
    endmethod
    static method create takes unit building, integer unitId returns thistype
        local thistype this = thistype.allocate()
        set .allocated = true
        set table[GetHandleId(building)] = this
        call TimerStart(NewTimerEx(this), GetUnitBuildTime(unitId), false, null)
        return this
    endmethod

    static method operator [] takes unit building returns thistype
        return table[GetHandleId(building)]
    endmethod
endstruct

    globals
        private TableArray table
                                                            // First 7 slots are rserved for training units.
        private constant integer COUNT_KEY           = 8    // How many objects are currently in queue.
        private constant integer CURRENT_KEY         = 9    // Currently trained object.
        private constant integer LAST_KEY            = 10   // Last Trained object.
     
        private constant integer TRAIN_UNITS_KEY     = 11   // Amount of trained units.
        private constant integer TRAIN_UPGRADES_KEY  = 12   // Amount of made upgrads.
        private constant integer TRAIN_TECHS_KEY     = 13   // Amount of made researches.
     
        private constant integer LAST_CANCEL_KEY     = 14
     
        private constant integer MAX_KEY             = 14
     
        private constant string  EMPTY_STRING        = "Default string"  // This is an indicator for an invalid ObjectName.
     
        private constant integer ORDER_CANCEL        = 851976
    endglobals
 
    // QueueClean will clean the queue from an object that was
    // canceled or finished training.

    private function QueueClean takes unit building, integer trainId returns nothing
        local integer unitId = GetHandleId(building)
        local integer i = 0
        local integer trainCounter = table[COUNT_KEY][unitId]
        set table[LAST_CANCEL_KEY][unitId] = trainId
        set table[COUNT_KEY][unitId] = (trainCounter - 1)
        if (trainCounter == 1) then // No loop needed if only 1 trained object exists.
            set table[CURRENT_KEY][unitId] = 0
            set table[1][unitId] = 0
        else
            loop // Loop thorugh queue to get correct object
                set i = (i + 1)
                if (table[i][unitId] == trainId) then
                    if (i != trainCounter) then
                        // we move last object from queue to fill the gap
                        set table[i][unitId] = table[trainCounter][unitId]
                    endif
                    set table[trainCounter][unitId] = 0
                    exitwhen(true)
                endif
            endloop
        endif
    endfunction
 
    // Building gets order to train an object.
    private function TrainOrder takes nothing returns boolean
        local integer trainId = GetIssuedOrderId()
        local integer unitId
        local integer trainCounter
        if (GetObjectName(trainId) != EMPTY_STRING) then
            set unitId = GetHandleId(GetTriggerUnit())
            set trainCounter = table[COUNT_KEY][unitId] + 1
            set table[COUNT_KEY][unitId] = trainCounter
            set table[trainCounter][unitId] = trainId
        endif
        return false
    endfunction
 
    // Building starts to train an object.
    private function TrainStart takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer unitId = GetHandleId(u)
        local integer trainId = GetTrainedUnitType()
        call TimeData[u].destroy()
        if (trainId == 0) then // Check if object is no unit.
            set trainId = GetResearched()
            if (trainId == 0) then // Check if object is no tech.
                set trainId = GetUnitTypeId(u) // -> Object is an upgrade.
            endif
        elseif not IsHeroUnitId(trainId) then
            // Start timer for training unit
            call TimeData.create(u, trainId)
        endif
        set table[CURRENT_KEY][unitId] = trainId
     
        set u = null
        return false
    endfunction

    // Building cansels training for a object in queue.
    private function TrainCancel takes nothing returns boolean
        local integer i = GetTrainedUnitType()
        local unit u = GetTriggerUnit()
        if (i == 0) then // Check if canceled object is an unit or a research, no need to check for upgrade in here.
            call QueueClean(u, GetResearched())          
        else
            call QueueClean(u, i)
         
            if not IsHeroUnitId(i) and table[COUNT_KEY][GetHandleId(u)] == 0 then
                // deallocate TimeData
                call TimeData[u].destroy()
            endif
        endif
     
        set u = null
        return false
    endfunction
 
    // Building finished training an objec.t
    private function TrainFinish takes nothing returns boolean
        local unit building = GetTriggerUnit()
        local integer unitId = GetHandleId(building)
        local integer trainId = GetTrainedUnitType()
     
        if (trainId == 0) then // Check if object was no unit.
            set trainId = GetResearched()
            if (trainId == 0) then // Check if object was no research.
                set trainId = GetUnitTypeId(building) // -> Object was an upgrade
                set table[TRAIN_UPGRADES_KEY][unitId] = table[TRAIN_UPGRADES_KEY][unitId] + 1
            else
                set table[TRAIN_TECHS_KEY][unitId] = table[TRAIN_TECHS_KEY][unitId] + 1
            endif
        else
            set table[TRAIN_UNITS_KEY][unitId] = table[TRAIN_UNITS_KEY][unitId] + 1
            if not IsHeroUnitId(trainId) then
                call TimeData[building].destroy()
            endif
        endif
     
        set table[LAST_KEY][unitId] = trainId
        set table[CURRENT_KEY][unitId] = 0
     
        call QueueClean(building, trainId)
        set building = null
       return false
   endfunction
 
    private function Init takes nothing returns nothing
        local trigger trigger_order = CreateTrigger()
        local trigger trigger_start = CreateTrigger()
        local trigger trigger_cancel = CreateTrigger()
        local trigger trigger_finish = CreateTrigger()
        local player p
        local integer i = 0
        set table = TableArray[MAX_KEY+1]
     
        loop
            exitwhen i > bj_MAX_PLAYERS
            set p = Player(i)
         
            call TriggerRegisterPlayerUnitEvent(trigger_order, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
         
            call TriggerRegisterPlayerUnitEvent(trigger_start, p, EVENT_PLAYER_UNIT_RESEARCH_START, null)
            call TriggerRegisterPlayerUnitEvent(trigger_start, p, EVENT_PLAYER_UNIT_TRAIN_START, null)
            call TriggerRegisterPlayerUnitEvent(trigger_start, p, EVENT_PLAYER_UNIT_UPGRADE_START, null)
         
            call TriggerRegisterPlayerUnitEvent(trigger_cancel, p, EVENT_PLAYER_UNIT_TRAIN_CANCEL, null)
            call TriggerRegisterPlayerUnitEvent(trigger_cancel, p, EVENT_PLAYER_UNIT_RESEARCH_CANCEL, null)
            call TriggerRegisterPlayerUnitEvent(trigger_cancel, p, EVENT_PLAYER_UNIT_UPGRADE_CANCEL, null)          
         
            call TriggerRegisterPlayerUnitEvent(trigger_finish, p, EVENT_PLAYER_UNIT_TRAIN_FINISH, null)
            call TriggerRegisterPlayerUnitEvent(trigger_finish, p, EVENT_PLAYER_UNIT_RESEARCH_FINISH, null)
            call TriggerRegisterPlayerUnitEvent(trigger_finish, p, EVENT_PLAYER_UNIT_UPGRADE_FINISH, null)
         
            set i = i + 1
        endloop
        call TriggerAddCondition(trigger_order, Condition(function TrainOrder))
        call TriggerAddCondition(trigger_start, Condition(function TrainStart))
        call TriggerAddCondition(trigger_finish, Condition(function TrainFinish))
        call TriggerAddCondition(trigger_cancel, Condition(function TrainCancel))
    endfunction
 
    function CountFinishedUpgrades takes unit building returns integer
        return table[TRAIN_UPGRADES_KEY][GetHandleId(building)]
    endfunction
 
    function CountFinishedUnits takes unit building returns integer
        return table[TRAIN_UNITS_KEY][GetHandleId(building)]
    endfunction
 
    function CountFinishedTechs takes unit building returns integer
        return table[TRAIN_TECHS_KEY][GetHandleId(building)]
    endfunction
 
    function GetTrainingQueueSize takes unit building returns integer
        return table[COUNT_KEY][GetHandleId(building)]
    endfunction
 
    function GetTrainedObjectId takes unit building, integer slot returns integer
        if (slot > 0) and (slot < 8) then
            return table[slot][GetHandleId(building)]
        else
            return 0
       endif
    endfunction
 
    function GetLastTrainedObjectId takes unit building returns integer
        return table[LAST_KEY][GetHandleId(building)]
    endfunction

    function GetCurrentTrainedObjectId takes unit building returns integer
        return table[CURRENT_KEY][GetHandleId(building)]
    endfunction
 
    function GetUnitBuildTimeRemaining takes unit building returns real
        local TimeData this = TimeData[building]
        if this.allocated then
            return TimerGetRemaining(this.clock)
        else
            return 0.
        endif
    endfunction
 
    private function GetLastCanceledObjectId takes unit building returns integer
        return table[LAST_CANCEL_KEY][GetHandleId(building)]
    endfunction
 
    globals
        public integer array Queue[10]
    endglobals
 
    function GetCleanTraningQueue takes unit building, boolean keepFirst returns nothing
        local integer i
        local integer lowerBound
        local integer size = GetTrainingQueueSize(building)
 
        if size < 1 or (size == 1 and keepFirst) then
            return
        endif
     
        if keepFirst then
            set lowerBound = 2
            set Queue[1] = GetCurrentTrainedObjectId(building)
        else
            set lowerBound = 1
        endif
     
        set i = size
        loop
            exitwhen i < lowerBound
            call IssueImmediateOrderById(building, ORDER_CANCEL)
            set Queue[i] = GetLastCanceledObjectId(building)
            set i = i - 1
        endloop
        set Queue[size + 1] = 0
    endfunction
 
    private function ReOrderTrainingQueue_p takes unit building, boolean keepFirst returns nothing
        local integer i
        local integer size = GetTrainingQueueSize(building)
     
        if size < 1 or (size == 1 and keepFirst) then
            return
        endif
 
        call GetCleanTraningQueue(building, keepFirst)
     
        if keepFirst then
            set i = 1
        else
            set i = 2
        endif
     
        loop
            exitwhen i > size
            call IssueImmediateOrderById(building, Queue[i])
            set i = i + 1
        endloop
    endfunction
 
    function ReOrderTrainingQueue takes unit building returns nothing
        call ReOrderTrainingQueue_p(building, true)
    endfunction
 
      function CancelTrainingBack takes unit building returns nothing
        call IssueImmediateOrderById(building, ORDER_CANCEL)
    endfunction
 
    function CancelTrainingFront takes unit building returns nothing
        call ReOrderTrainingQueue_p(building, false)
    endfunction
endlibrary
 


Keywords:
Unit, UnitType, Building, Train, Detect, System, Table, IcemanBo, null
Contents

TrainingDetection (Map)

Reviews
Moderator
10:58 2th May 2014 BPower: Code looks good to me. Approved. In my eyes you could spell all API functions in full i.e CountFinishedUps --> CountFinishedUpgrades. I changed version 0.9 to 1.0. It indicates this is working and ready to use. :)...
  1. 10:58 2th May 2014
    BPower: Code looks good to me. Approved.
    In my eyes you could spell all API functions in full i.e CountFinishedUps --> CountFinishedUpgrades.
    I changed version 0.9 to 1.0. It indicates this is working and ready to use. :)


    17:24, 11th Mar 2014
    BPower:
    I can see this beeing useful, however it is buggy at the moment.
    You need more safety. Refer to my last post.
     
  2. Quilnez

    Quilnez

    Joined:
    Oct 12, 2011
    Messages:
    3,169
    Resources:
    37
    Icons:
    2
    Tools:
    1
    Maps:
    7
    Spells:
    21
    Tutorials:
    2
    JASS:
    4
    Resources:
    37
    yo, there, I find this
    Code (vJASS):
            local trigger train_order = CreateTrigger()
            local trigger train_start = CreateTrigger()
            local trigger train_finish = CreateTrigger()
            local trigger train_cancel = CreateTrigger()

    you forgot to null them

    anything looks nice so far
     
    Last edited: Mar 3, 2014
  3. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,745
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    @Dalvengyr: You don't have to null these.

    Please follow the JPAG, you'll find the link in my signature. Get rid of that _
    Index indicates UnitUserData or loop index. It causes confusion because you actually refer to the unit id.
    I would prefer if you use triggercondition instead of triggeraction.
    The code is easy to understand, still some documentation would be nice.
    TrainDebug implies debug mode, which is also not true.
    Hu? There is like no context between your system and an UnitIndexer.
     
  4. Quilnez

    Quilnez

    Joined:
    Oct 12, 2011
    Messages:
    3,169
    Resources:
    37
    Icons:
    2
    Tools:
    1
    Maps:
    7
    Spells:
    21
    Tutorials:
    2
    JASS:
    4
    Resources:
    37
    oh, okay. New knowledge saved..
     
  5. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,929
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    unless they get not destroyed no need to null them :p

    Changed, and updated.

    That was a fail, lol. ^_^
     
  6. Wrda

    Wrda

    Joined:
    Nov 18, 2012
    Messages:
    736
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    Looks really useful, but i noted some bugs:
    - "building is currently training X units" looks like more the number of units attempted to be trained when you train some units and then cancel them.
    - when you train 7 units and then cancel them and click the building which was training them appears a weird number 12345678 or something.
    It would be great if you fixed this :p:thumbs_up:
     
  7. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,929
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    2. Aw, thank you. You mean the if you cancel all training objects, then there is still the CurrentTrainedUnit not null? (bug) Is this also what you meant?. Thank you. Edit: I see now what you mean
    1. Sorry what? Did not quizte understand. You're talkking about DisplayTrains, right?
    DisplayTrains should display amount of trained objects, and then list them. (not always in correct order, but thats not so important)

    Edit:

    Bug should be fixed now. DisplayTrains takes now player as parameter. Updated.
     
    Last edited: Mar 5, 2014
  8. Wrda

    Wrda

    Joined:
    Nov 18, 2012
    Messages:
    736
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    1. yes. It's like... well lets say you order the building to train 3 units, then you cancel them. After you click the building, it says that it is currently trainning 3 objects, while it isn't, because you canceled them :p
     
  9. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,929
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    But now it's fixed? or
     
  10. Wrda

    Wrda

    Joined:
    Nov 18, 2012
    Messages:
    736
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    Still not fixed ^^
     
  11. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,929
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    You sure you tested newest version? And you re-selected building to display a new msg? There was no bug for me.
     
  12. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,153
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    API looks useful. Why don't you use Table?
     
  13. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,929
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    ^Thanks. Will make Table optional in next update.

    Edit: mentioned bug when canceling researches. Will fix this soon.

    Edit 2:

    Updated.

    Now optional usage of Bribe's table. jesus.. code looks more ugly with this optional, next time I will just use it. (sorry mods^^)

    I know I could move my functions into the struct and change them to static methods, but there wont be any difference. Maybe I will do so in next update.
     
  14. Wrda

    Wrda

    Joined:
    Nov 18, 2012
    Messages:
    736
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    It looks like that it went worse, when make the building train 7 objects and then cancel them all, it starts to lag a lot and appears a message saying "objectId:0" along the chat.
     
  15. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,929
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Seriously, I tested now again and had no bug. I trained 7 random objects and canceled them. Repeated it with different ways of canceling and different objects.

    Could you please exactly explain in detail when the bug occurs. :S
     
  16. Wrda

    Wrda

    Joined:
    Nov 18, 2012
    Messages:
    736
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    I've just done it: train 6 units, *[cancel 1, click on the building, then train another, click on the building again], repeat these 4 last steps*, then it should appear a message saying ObjectId: 0. I don't see how it is working properly :S
    Edit: btw, correct me if I'm wrong:
    Code (vJASS):
    private struct A extends array

    ->
    Code (vJASS):
    private struct TD extends array

    Shouldn't it have a proper name? like TD (train detection), just an example.
    and shouldn't it be like this?
    Code (vJASS):
    //stuff
        private struct TD extends array
            static if LIBRARY_Table then
                static TableArray table
            else
                static hashtable hash = InitHashtable()
            endif
        endstruct
    //stuff

    ok that's all, don't be mad at me xD :grin:
     
  17. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,929
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    I testes as you explained and worked for me. :S

    Which ObjectId is 0? There is current and last.

    Current displays Id of object which is currently training.

    If it finished training it gets automaticlly the "LastTrained".

    Until no unit finished training ObjectId of last train is always 0.

    Sorry, what? I only understood you suggest better name for struct.
     
  18. Wrda

    Wrda

    Joined:
    Nov 18, 2012
    Messages:
    736
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    I meant the indention too. compare with yours.
    It looks like it spams ObjectId: 0 like 1000 times, causing lag.
     
  19. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,929
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Indention, I see.^^
    What do you mean "it looks like"? Does it spam the text, or sth else? Also I still don't know which ObjectId you mean.