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

Be in fashion: Custom AI, Skill level almost Magtheridon96

Status
Not open for further replies.
Level 19
Joined
Mar 18, 2012
Messages
1,716
I was looking for names without meaning beyond being awesome. IAmBot is the new library name. Special thanks to WaterKnight.

The attached map is an old one and most part of the code had a radical change.

I'll update the code step by step each time one block is finished.

Limitations:
The amount of active units during the same time is directly limited by the maximum amount of allocations of the linked list struct.
I guess that in the end one unit consumes ~1.3 instances.
In conclusion it safely freezes your screen without creating an overflow.

JASS:
/* With special thanks to WaterKnight. */

library IAmBot /* v1.0.0
**************************************************************************************
*
*   */uses/*
*  
*       */ List        /* - [url]https://github.com/nestharus[/url]
*       */ Table       /* - [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/[/url]
*       */ TimerUtils  /* - [url]http://www.wc3c.net/showthread.php?t=101322[/url]
*       */ RegisterPlayerUnitEvent /* hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*
*       Requires one of these UnitIndexers:
*________________________________
*       */ optional UnitIndexer /* - [url]https://github.com/nestharus[/url]
*       */ optional UnitDex     /* - [url]http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/[/url]
*¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       */ optional IAmBotDebug /* 
*      
*   Credits to Bribe, Nestharus, Magtheridon96, TriggerHappy and Vexorian 
* 
**************************************************************************************
*/
                native UnitAlive takes unit id returns boolean 
/*              
**************************************************************************************
*
*   1. Import instruction
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       Copy the IAmBot script and the required libraries into your map.
*
*   2. API
*   ¯¯¯¯¯¯
*       First and foremost all struct members. 
*           -> Access is granted via IAmBot instance which equals UnitUserData.
*       
*           Fields are (all readonly)
*               - who, whoX, whoY, id (UnitTypeId)
*               - owner
*               - aim, aimX, aimY
*               - angle (towards aim)
*               - archer (is of type archer)
*               - maxRange
*               
*               Spam protection members
*               ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*               - paused (paused > 0 skips all operations of the periodic loop)
*                   --> decreases by 1 each loop.
*               - cd (cd > 0 skips some operations of the periodic loop, i.e. casting spells)
*                   --> decreases by 1 each loop.
*
*       Method operators:
*    
*           static method operator [] takes unit whichUnit returns IAmBot
*               --> returns UnitUserData
*           
*           method operator isBot takes nothing returns boolean
*
*           method operator pause= takes integer value returns nothing
*           method operator cooldown= takes integer value returns nothing
*           method operator target= takes unit whichUnit returns nothing
*
*       Methods:
*
*           method smart takes nothing returns boolean 
*           method attack takes nothing returns boolean
*           method move takes real x, real y returns boolean
*
*           method isUnitInGroup takes thistype whichGroup returns boolean
*           method countGroupAlliesInRangeXY takes real x, real y, real range returns integer
*           method countAlliesInRangeXY takes real x, real y, real range returns integer
*            
*   3. Configuration
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
    globals
        /*                General IAmBot library config
        *                 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        *   Debug only: Print out every bot descicion through texttags and textmessages. 
        */
        private constant boolean PRINT_BOT_DESCISION = true
        /*
        *   At which size a trigger will be rebuilt. A value of 80 units per trigger is recommended.
        *       - Information: A larger trigger size will save perfomance, but also creates more triggers. 
        *
        *   Which event is the core event of the IAmBot struct. EVENT_UNIT_ACQUIRED_TARGET is reommended.
        */
        private constant integer   TRIGGER_SIZE  = 80
        private constant unitevent TRIGGER_EVENT = EVENT_UNIT_ACQUIRED_TARGET
        /*  
        *                 IAmBot struct config
        *                 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        *   How long is one unit controlled by IAmBot. Refreshes on unit interaction.
        *
        *   How much time elapse between two loops. Recommended margin lies between 0.8 - 3.0 seconds.
        *       - Information: Lower values create more overhead, but can improve unit behaviour. 
        *                      A reasonable timeout is recommended    ¯¯¯
        *                                                             
        */
        private constant real DEFAULT_DURATION = 18.
        private constant real CLOCK_TIMEOUT    = 2.5
        /*
        *   Maximum size of one unit group. Units in one group share the same group behaviour.
        *       - Information: A smaller group size will decrease the loop duration, but create more timers. 
        */
        private constant integer GROUP_SIZE = 6
        /*
        *   Archer type units will retreat for DEFAULT_RETREAT_DISTANCE if their aim is of type meele and within MININUM_ARCHER_RANGE.
        */
        private constant real MININUM_ARCHER_RANGE     = 160.
        private constant real DEFAULT_RETREAT_DISTANCE = 260.
        /*
        *   Meele type units will consider their aim as WITHIN_MEELE_RANGE.
        *       - Information: Implemented as range buffer. Aim priority decreases outside this boundary. 
        */
        private constant real WITHIN_MEELE_RANGE = 270.
        /*
        *   Aims outside DEFAULT_MAXIMUM_RANGE are not considered as valid targets.
        *       - Information: You can customize this range per UnitTypeId by using library IAmBotExUnitInfo.
        */
        private constant real DEFAULT_MAXIMUM_RANGE = 1200.
        /*
        *   Units within CONSIDER_GROUP_RANGE can act like group units.
        *   Fresh activated units will look for incomplete groups within this range.
        *   
        *   A fresh activated unit can activate nearby inactive allies within TEAM_UP_RANGE.
        */
        private constant real CONSIDER_GROUP_RANGE = 1500.
        private constant real TEAM_UP_RANGE        = 500. 
        /*
        *   The stun buff your map is using. You may have a custom stun system in your map.
        *       - Information: Stunned units skip large parts of the periodic loop. 
        */
        private constant integer STUN_BUFF = 'BSTN'
        /*
        *   Player neutral passive is never considered by IAmBot.
        *
        *   IAmBot distinguishs between active and computer players. The latter gets injected by the IAmBot controlling system :).
        */
        private constant player NEUTRAL_PASSIVE_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
        private boolean array PLAYING_PLAYER   
    endglobals
    /*
    *                 IAmBot function config
    *                 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    */
    private function IsUnitNotStunned takes unit wichUnit returns boolean
        return (0 == GetUnitAbilityLevel(wichUnit, STUN_BUFF))
    endfunction
    /*
    *   Units sharing the same compatibility index are drift compatible and interact as allies.
    *      - Information: This function runs once on index event and eventually on unit change owner event.
    */
    private function AssignCompatibilityIndex takes unit whichUnit returns integer
        return GetPlayerId(GetOwningPlayer(whichUnit))
    endfunction
    /*
    *   Units passing this filter are taken over by IAmBot.
    *       - Information: Ensure to filter out playing player units and dummy units.
    *                      Each unit gets its own trigger, hence this filter must be very neat.
    */
    private function onFilter takes unit indexed returns boolean
        return not PLAYING_PLAYER[GetPlayerId(GetOwningPlayer(indexed))] and (GetOwningPlayer(indexed) != NEUTRAL_PASSIVE_PLAYER)
    endfunction

/*                    End of configuration
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯*/
    //IAmBot is not using group < agent < handle. IAmBot groups are linked lists.    
    struct BotList extends array
        implement List
        readonly integer index//Connect node with UnitUserData.
        static timer array clock//Connect collection with a timer.
        method link takes integer i returns nothing
            set index = i
        endmethod
    endstruct
    
    
    private keyword IAmBotInit
    private keyword requestHelp
    private keyword registerHealer
    private keyword releaseHealer
    private keyword IAmBotExUnitStateClear
    private keyword IAmBotExUnitStateUpdate
    private keyword IAmBotExUnitStateInit
    private keyword IAmBotExUnitStateAPI
    private keyword IAmBotExUnitStateResponsePrimary
    private keyword IAmBotExUnitStateResponseSecondary
    
    //! runtextmacro optional I_AM_BOT_EXPANSION_UNITSTATE_CODE()
    
    struct IAmBot extends array
        readonly player owner
        readonly unit who
        readonly real whoX
        readonly real whoY
        readonly unit aim
        readonly real aimX
        readonly real aimY
        readonly real angle
        
        readonly integer id
        readonly boolean archer
        readonly real maxRange
        //Spam protection
        readonly integer paused
        readonly integer cd    

        readonly static IAmBot current  //Required to fire custom events. --> current = this --> TriggerEvaluate(trigger)
        readonly static IAmBot secondary//Required for unit unit interaction.
       
        readonly integer comp//Drift compatibility. YAY! We can drive the Gipsy Danger.
        readonly boolean active//Unit status active.
        
        private real time//== 0 will set the unit to inactive. Refreshes onAquire and damage input.
        
        private BotList collection//All unit groups are data structures of type list.
        private static integer array nodeCounter//Each collection also has a node counter.
        
        static boolean array has//Controll taken over by IAmBot.

        private static group enu = CreateGroup()//For various group enumerations within the IAmBot library.
        
        static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
            private IAmBotTexttag text
        endif
        
        //----------------------Default API functions----------------------
        
        method move takes real x, real y returns boolean
            return IssuePointOrderById(who, 851986, x, y)
        endmethod
        //Directly attack aim.
        method smart takes nothing returns boolean
            return IssueTargetOrderById(who, 851971, aim)
        endmethod
        //Attack the x/y of aim.
        method attack takes nothing returns boolean
            return IssuePointOrderById(who, 851983, aimX, aimY)
        endmethod
       
        static method operator [] takes unit u returns IAmBot
            return thistype(GetUnitUserData(u))
        endmethod
       
        method operator isBot takes nothing returns boolean
            return has[this]
        endmethod
        
        method operator cooldown= takes integer value returns nothing
            set cd = value
        endmethod
        
        method operator pause= takes integer value returns nothing
            set paused = value
        endmethod
        
        method operator target= takes unit whichUnit returns nothing
            set aim = whichUnit
        endmethod

        method isUnitInGroup takes thistype node returns boolean
            
            static if DEBUG_MODE then
                if not has[this] then//Probably never true.
                    call BJDebugMsg("ERROR: library IAmBot, method isUnitInGroup, passed in unit is not a bot.") 
                endif
                if (0 == node.collection) then
                    call BJDebugMsg("ERROR: library IAmBot, method isUnitInGroup, passed in invalid collection")
                endif
            endif
            
            return (collection == node.collection) and (0 != node.collection)
        endmethod
        
        method countGroupAlliesInRangeXY takes real x, real y, real range returns integer
            local integer c    = 0
            local BotList node = collection.first
            loop
                exitwhen 0 == node
                if IsUnitInRangeXY(IAmBot(node.index).who, x, y, range) and (node.index != this) then
                    set c = c + 1
                endif
                set node = node.next
            endloop
            return c
        endmethod
        
        method countAlliesInRangeXY takes real x, real y, real range returns integer
            local integer c    = 0
            local thistype node
            local unit u
            call GroupEnumUnitsInRange(thistype.enu, x, y, range, null)
            loop
                set u = FirstOfGroup(thistype.enu)
                exitwhen u == null
                call GroupRemoveUnit(thistype.enu, u)
                set node = GetUnitUserData(u)
                if (has[node]) and (node.comp == this.comp) and (node != this) then
                    set c = c + 1
                endif
            endloop
            return c
        endmethod
        
        //-------------------- End of API functions------------------------------
        
        //-------------------- private functions --------------------------------
        
        //Does not exceed the maximum group size.
        private method findCloseCollection takes nothing returns BotList
            local unit u 
            local thistype node
            call GroupEnumUnitsInRange(enu, whoX, whoY, CONSIDER_GROUP_RANGE, null)
            loop
                set u = FirstOfGroup(enu)
                exitwhen u == null
                call GroupRemoveUnit(enu, u)
                set node = GetUnitUserData(u)

                //Unit has to be active
                //Alive
                //node != this
                //Drift compatible
                if (node.active) and (UnitAlive(u)) and (node != this) and (node.comp == this.comp) then

                    if (nodeCounter[node.collection] < GROUP_SIZE) then
                    
                        static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
                            call IAmBotTexttag.display("I'll join group " + I2S(node.collection), who)
                        endif
                        
                        set u = null
                        
                        return node.collection
                    endif
                endif
            endloop
            return 0
        endmethod
        
        //Method enforce may exceed the maximum group size.
        private method enforce takes unit target, thistype same returns nothing
            local BotList node

            call setup(target)

            //Using the same list. Group size is ignored.
            set collection = same.collection
            set node       = collection.enqueue()
            call node.link(this)
            set nodeCounter[collection] = nodeCounter[collection] + 1
            
            //Unit status active.
            set active = true
            
            static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
                set text = IAmBotTexttag.create(who, collection)
            endif

            //Issue attack on the same spot as the unit, which enforced this unit to status active.
            call attack()
            
        endmethod
        //----------------------------------------------------
        
        
        //-------------------- Cleanup --------------------------------
        
        private method clear takes nothing returns nothing
        
           static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
                call text.destroy()
            endif

            static if DEBUG_MODE and PRINT_BOT_DESCISION then
                debug call BJDebugMsg("Clear IAmBot for |cff20b2aa" + GetUnitName(who))
            endif
            
            implement optional IAmBotExUnitStateClear
            
            set who     = null
            set aim     = null
            set owner   = null
            set active  = false
            set nodeCounter[collection] = nodeCounter[collection] - 1
            
            static if DEBUG_MODE and PRINT_BOT_DESCISION then
                debug call BJDebugMsg(I2S(nodeCounter[collection]) + " remaining units in collection " + I2S(collection))
            endif
            
            if (0 == collection.first) then  
                call destroyCollection()
            endif

            //Required for method findCloseCollection.
            set collection = 0
        endmethod
        
        private method destroyCollection takes nothing returns nothing
            
            debug if BotList.clock[collection] == null then
                debug call BJDebugMsg("ERROR, library IAmBot, method destroyCollection, ATTEMPT TO RELEASE A NULL TIMER")
            debug endif
            
            call ReleaseTimer(BotList.clock[collection])
            
            static if DEBUG_MODE and PRINT_BOT_DESCISION then
                debug call BJDebugMsg("Collection " + I2S(collection) + " is empty and gets destroyed. Timer is stopped.")
            endif
            
            call collection.destroy()
            set BotList.clock[collection] = null
            set nodeCounter[collection]      = 0
        endmethod
        
        //-------------------- End of Cleanup functions--------------------
        
        //Runs for each BotList every CLOCK_TIMEOUT seconds.
        private static method onPeriodic takes nothing returns nothing
        
            //----------------- local instances -----------------
            local BotList temp = GetTimerData(GetExpiredTimer())//temp is the current collection. TimerUtils API
            local BotList node = temp.first
            local thistype this
            local thistype data
            //---------------------------------------------------
            
            //-------------- other local variables ---------------
            local boolean going = true//Allows to skip various operations of the loop if set to false.
            local boolean hasAim
            local integer order//current order, still not sure if it's worth to be a struct member, future will show.
            local integer count//in case something has to be counted.
            //---------------------------------------------------
            
            local unit u//group enumerations.
            
            //Step 1: Merge groups
            //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
            //if the group size equals 1, this block will try to release that group.
            if (node == temp.last) then//first == last
                
                set this = node.index
                set temp = findCloseCollection()//Can't return the same instance.
                
                if (0 != temp) then
                
                    set u = aim//Store the current target, may be null.

                    //clear() is not able to destroy lists if they are not empty.
                    call node.remove()//Hence remove the last node.
                    call clear()      //Cleanup

                    //Each unit has a reference to his list. Get the new from new list first unit.
                    set node = temp.first
                    set data = node.index
                    call enforce(u, data)//Same target, new list.
                    set u = null
                    return//Not entering the loop. This unit may have a additional timeout in range 0.0 to CLOCK_TIMEOUT
                endif
            endif
            //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
            
            loop
                //Step 2: Linked List usage
                //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                set this = node.index//Each BotList node is linked with a IAmBot instance.
                set temp = node.next //Linked list double free protection.
                //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                if (UnitAlive(who)) then
                
                    //Optional Pluggin: UnitState requires library IAmBotExUnitState
                    //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                    //
                    implement optional IAmBotExUnitStateUpdate
                    implement optional IAmBotExUnitStateResponsePrimary
                    //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                    
                
                    //Step 3: Spam protection
                    //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                    set cd   = cd - 1//Must be  cd < 0 to fire engage events.
                    set time = time - CLOCK_TIMEOUT//time < 0 sets unit back to status inactive. Complete cleanup.
                   
                    if (0 < paused) then//Skips all actions beside those of the UniState pluggin.
                        set paused = paused - 1
                     elseif IsUnitNotStunned(who) then
                    //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                    
                        //Step 4: Gather information
                        //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                        set hasAim = UnitAlive(aim) and IsUnitInRange(who, aim, maxRange)
                    
                        if hasAim then
                            set time     = DEFAULT_DURATION
                            set aimX     = GetUnitX(aim)
                            set aimY     = GetUnitY(aim)
                            set angle    = Atan2(aimY - whoY ,aimX - whoX)
                            set order    = GetUnitCurrentOrder(who) 
                            
                            implement optional IAmBotExUnitStateResponseSecondary
                            
                            //------------------- Yet flawed basic order -------------------
                            //Copied this condition from ZTS. May change while testing further AI behaviour.
                            if (851983 == order) or (0 == order) or (851971 == order) then
                                call smart()//May become a condition, because of invisible units. Needs a test. --> if false vs invis --> attack ground/move by 0 units --> new aquire event
                            endif
                            //Here will be the aim priority calculation: Status 0% :(
                            //---------------------------------------------------

                        else
                            set hasAim     = false
                        endif
                        //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                        
                        //Step 5: Unique action
                        //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                        if going then
                            //Optional Pluggin: IAmBotExPosition Status 75% finished. Imports additional API
                            //Features:
                            //  Additional new IAmBot members: none. (Table POSITION)
                            //      --> method surround: meele units may surround the target. sets going to false
                            //      --> method retreat: range units may retreat if their aim is meele and too close.
                            //      --> may fire custom position event (not sure, as mentioned 75%)
                            //implement optional IAmBotExUnitPosition
                        endif
                        //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                        
                        //Step 6: Enage event
                        //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                        //Optional Pluggin: IAmBotExEnage Status 100% finished. Imports additional API
                        //Features:
                        //  Additional new IAmBot members: Table ENAGE
                        //      --> fires engage event based on UnitTypeId if cd < 0 
                        //implement optional IAmBotExUnitEnage
                        //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                    endif
                    
                    //Step 7: Cleanup
                    //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                    if (0 > time) and (0 > cd) then//Has to wait until no cooldown is left
                        
                        call node.remove()
                        call clear()
                    endif
                    
                else//UnitAlive condition.
                
                    call node.remove()
                    call clear()
                endif
                //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  
                //Step 8: Next node
                //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                set node = temp
                exitwhen 0 == node
                //¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
            endloop
        endmethod
        
        //Gather basic information
        method setup takes unit target returns nothing
            //Not AIDS compatible, because of scope IAmBotRefresh
            set who      = GetUnitById(this)
        
            set whoX     = GetUnitX(who)
            set whoY     = GetUnitY(who)
            set owner    = GetOwningPlayer(who)

            set id       = GetUnitTypeId(who)
            set archer   = IsUnitType(who, UNIT_TYPE_RANGED_ATTACKER)
            set maxRange = DEFAULT_MAXIMUM_RANGE//Some ideas about an optional UnitInfo add-on

            set aim      = target
            set aimX     = GetUnitX(target)
            set aimY     = GetUnitY(target)
            
            implement optional IAmBotExUnitStateUpdate
            implement optional IAmBotExUnitStateSetup
            
            set paused   = 0
            set cd       = 0
            set time     = DEFAULT_DURATION           
        endmethod
        
        //Does not exceed the maximum group size.
        private method teamUp takes nothing returns nothing
            local thistype node
            local unit u
            call GroupEnumUnitsInRange(enu, whoX, whoY, TEAM_UP_RANGE, null)
            loop 
                set u = FirstOfGroup(enu)
                exitwhen u == null
                call GroupRemoveUnit(enu, u)
                set node = GetUnitUserData(u)
                
                //IAmBot has node
                //not active
                //Alive
                //Is drift compatible.
                if (has[node]) and (not node.active) and (UnitAlive(u)) and (this.comp == node.comp) then 
                    
                    //Maximum group size is not reached.
                    if (nodeCounter[collection] < GROUP_SIZE) then
                
                        static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
                            call IAmBotTexttag.display("I just teamed up with " + GetUnitName(who), u)
                        endif

                        //Same aim and group.
                        call node.enforce(aim, this)
                    else
                        set u = null
                        call node.setup(aim)//May takes null argument. Doesn't matter the operation is still safe.
                        call node.add()//Allows to create a new group or find an incomplete one.
                        call node.attack()//Do not order smart, instead find the closest target.
                        return//Stop this operation if maximum size is reached.
                    endif
                endif
            endloop
        endmethod
    
        method add takes nothing returns nothing
            local BotList node
            
            //Try to find a close incomplete unit group.
            set collection = findCloseCollection()
            
            //if 0 no group has been found or all nearby groups are full.
            if (0 == collection) then
                
                //Create a new list. Each list has an individual timer. TimerUtils API.
                set collection                 = BotList.create()
                set BotList.clock[collection]  = NewTimerEx(collection) 
                
                static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
                    call IAmBotTexttag.display("Couldn't find a valid group. I start my own group.", who)
                endif
                
                call TimerStart(BotList.clock[collection], CLOCK_TIMEOUT, true, function thistype.onPeriodic) 
            endif

            //Enqueue the list. Link the node with IAmBot instance. Increase the node counter.
            set node = collection.enqueue()
            call node.link(this)
            set nodeCounter[collection] = nodeCounter[collection] + 1

            //Unit status active.
            set active = true

            static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
                set text = IAmBotTexttag.create(who, collection)
            endif

            //Activate nearby allies.
            call teamUp()
            
        endmethod

        static method onAquireEvent takes nothing returns boolean
            local thistype this = GetUnitUserData(GetTriggerUnit())
            
            if (active) then
            
                set aim  = null
                set aim  = GetEventTargetUnit()
                set time = DEFAULT_DURATION
                //Do not order smart here, because nothing is known about the current unit state.
            else
            
                static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
                    call IAmBotTexttag.display("AQUIRE EVENT. Inactive. I'm now a bot", GetUnitById(this))
                endif
                //Gather basic info and add the unit to the IAmBot system.
                call setup(GetEventTargetUnit())
                call add()
            endif
            return false
        endmethod

        implement optional IAmBotExUnitStateAPI

        static method deactivate takes integer index returns nothing
            local thistype this = thistype(index)
            local BotList temp = this.collection
            local BotList node = temp.first
            if active then
                set temp = collection
                set node = collection.first
                loop
                    exitwhen 0 == node
                    if (node.index == this) then
                        call node.remove()
                        call clear()
                        return
                    endif
                    set node = node.next
                endloop
            endif
        endmethod
        
        static method compatibility takes thistype this returns nothing
            set this.comp = AssignCompatibilityIndex(GetUnitById(this))  
        endmethod
        
        private static method onInit takes nothing returns nothing
            implement optional IAmBotInit
            implement optional IAmBotExUnitStateInit
        endmethod
        
    endstruct
    
private module IAmBotInit
        local integer i = 16
        loop
            set i = i - 1
            set PLAYING_PLAYER[i] = false
            if (GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING) and (GetPlayerController(Player(i)) == MAP_CONTROL_USER) then
                set PLAYING_PLAYER[i] = true
            endif
            exitwhen i == 0
        endloop
endmodule

    //Of course IAmBot has a DDS Pluggin. Can use any DDS.
    //! runtextmacro optional IAmBot_DAMAGE_RESPONSE_PLUGGIN()
    
    //Special thanks to Bribe
    function RegisterBotEvent takes Table eventTable, integer unitTypeId, boolexpr booleanexpression returns nothing
        if not eventTable.handle.has(unitTypeId) then
            set eventTable.trigger[unitTypeId] = CreateTrigger()
        endif
        call TriggerAddCondition(eventTable.trigger[unitTypeId], booleanexpression)
    endfunction
    
    //Nestharus TriggerRefresh with some required modifications
    //
    //  Modifications:
    //      - added onFilter for index/deindex event
    //      - Some name changed to prevent them from interfering with other structs named Trigger.
    
    //Special thanks to Nestharus
    scope IAmBotRefresh
        globals
            private boolexpr condition
        endglobals
        
        struct IAmBotTrigger extends array
            private static integer instanceCount = 0
        
            private thistype first
            private thistype next
            private thistype prev
            readonly thistype parent
            
            private integer inactiveUnits
            readonly integer activeUnits
            
            readonly trigger trigger
            
            
            static if LIBRARY_UnitDex then
                private method registerUnit takes UnitDex whichUnit returns boolean
                    if (activeUnits < TRIGGER_SIZE) then
                        call TriggerRegisterUnitEvent(trigger, GetUnitById(whichUnit), TRIGGER_EVENT)
                        set activeUnits = activeUnits + 1
                        return true
                    endif
                    return false
                endmethod
            else
            
                private method registerUnit takes UnitIndex whichUnit returns boolean
                    if (activeUnits < TRIGGER_SIZE) then

                        call TriggerRegisterUnitEvent(trigger, GetUnitById(whichUnit), TRIGGER_EVENT)
                        set activeUnits = activeUnits + 1
                    
                        return true
                    endif
                
                    return false
                endmethod
            endif
            private method unregisterUnit takes nothing returns nothing
                set inactiveUnits = inactiveUnits + 1
                set activeUnits = activeUnits - 1
            endmethod
            
            private method createTrigger takes nothing returns nothing
                set trigger = CreateTrigger()
                call TriggerAddCondition(trigger, condition)
            endmethod
            private method remakeTrigger takes nothing returns nothing
                call DestroyTrigger(trigger)
                call createTrigger()
            endmethod
            private method rebuildTrigger takes nothing returns nothing
                local thistype current = first
                
                call remakeTrigger()
                
                /*
                *   Iterate over all units registered to the trigger and reregister them
                */
                set current.prev.next = 0
                loop
                    exitwhen 0 == current
                    call TriggerRegisterUnitEvent(trigger, GetUnitById(current), TRIGGER_EVENT)
                    set current = current.next
                endloop
                set first.prev.next = current
            endmethod
            
            private method remake takes nothing returns nothing
                if (inactiveUnits == TRIGGER_SIZE) then
                    set inactiveUnits = 0
                    call rebuildTrigger()
                endif
            endmethod
            
            private method addToList takes thistype whichUnit returns nothing
                set whichUnit.parent = this
            
                if (0 == first) then
                    set first = whichUnit
                    set whichUnit.next = whichUnit
                    set whichUnit.prev = whichUnit
                else
                    set this = first
                    
                    set whichUnit.prev = prev
                    set whichUnit.next = this
                    set prev.next = whichUnit
                    set prev = whichUnit
                endif
            endmethod
            method add takes thistype whichUnit returns boolean
                if (0 == this) then
                    return false
                endif
            
                if (registerUnit(whichUnit)) then
                    call addToList(whichUnit)
                    
                    return true
                endif
                
                return false
            endmethod
            
            private method removeFromList takes thistype whichUnit returns nothing
                set whichUnit.parent = 0
            
                set whichUnit.prev.next = whichUnit.next
                set whichUnit.next.prev = whichUnit.prev
                
                if (first == whichUnit) then
                    set first = whichUnit.next
                    if (first == whichUnit) then
                        set first = 0
                    endif
                endif
            endmethod
            static method remove takes thistype whichUnit returns nothing
                local thistype this = whichUnit.parent
            
                call removeFromList(whichUnit)
                call unregisterUnit()
                call remake()
            endmethod
            
            private static method allocate takes nothing returns thistype
                set instanceCount = instanceCount + 1
                return instanceCount
            endmethod
            static method create takes nothing returns thistype
                local thistype this = allocate()
                
                call createTrigger()
                
                return this
            endmethod
        endstruct
        
        private struct TriggerHeapInner extends array
            readonly static integer size = 0
            readonly thistype node
            readonly thistype heap
            
            public method bubbleUp takes nothing returns nothing
                local integer activeUnits = IAmBotTrigger(this).activeUnits
                local thistype heapPosition = heap
                
                local thistype parent
                
                /*
                *   Bubble node up
                */
                loop
                    set parent = heapPosition/2
                    
                    if (integer(parent) != 0 and activeUnits < IAmBotTrigger(parent.node).activeUnits) then
                        set heapPosition.node = parent.node
                        set heapPosition.node.heap = heapPosition
                    else
                        exitwhen true
                    endif
                    
                    set heapPosition = parent
                endloop
                
                /*
                *   Update pointers
                */
                set heapPosition.node = this
                set heap = heapPosition
            endmethod
            public method bubbleDown takes nothing returns nothing
                local integer activeUnits = IAmBotTrigger(this).activeUnits
                local thistype heapPosition = heap
                
                local thistype left
                local thistype right
                
                /*
                *   Bubble node down
                */
                loop
                    set left = heapPosition*2
                    set right = left + 1
                    
                    if (IAmBotTrigger(left.node).activeUnits < activeUnits and IAmBotTrigger(left.node).activeUnits < IAmBotTrigger(right.node).activeUnits) then
                        /*
                        *   Go left
                        */
                        set heapPosition.node = left.node
                        set heapPosition.node.heap = heapPosition
                        set heapPosition = left
                    elseif (IAmBotTrigger(right.node).activeUnits < activeUnits) then
                        /*
                        *   Go right
                        */
                        set heapPosition.node = right.node
                        set heapPosition.node.heap = heapPosition
                        set heapPosition = right
                    else
                        exitwhen true
                    endif
                endloop
                
                /*
                *   Update pointers
                */
                set heapPosition.node = this
                set heap = heapPosition
            endmethod
            
            static method insert takes thistype this returns nothing
                /*
                *   Increase heap size
                */
                set size = size + 1
                
                /*
                *   Store node in last heap position
                */
                set thistype(size).node = this
                set heap = size
                
                /*
                *   Bubble node into correct position
                */
                call bubbleUp()
            endmethod
        endstruct
        
        private struct TriggerHeap extends array
       
        static if LIBRARY_UnitDex then
            static method add takes UnitDex whichUnit returns nothing
                local IAmBotTrigger trig = TriggerHeapInner(1).node
                
                if (not trig.add(whichUnit)) then
                    set trig = IAmBotTrigger.create()
                    call trig .add(whichUnit)
                    call TriggerHeapInner.insert(trig)
                else
                    call TriggerHeapInner(trig).bubbleDown()
                endif
            endmethod
            static method remove takes UnitDex whichUnit returns nothing
                local IAmBotTrigger trig = IAmBotTrigger(whichUnit).parent
                call IAmBotTrigger.remove(whichUnit)
                call TriggerHeapInner(trig).bubbleUp()
            endmethod
        else
            static method add takes UnitIndex whichUnit returns nothing
                local IAmBotTrigger trig = TriggerHeapInner(1).node
                
                if (not trig.add(whichUnit)) then
                    set trig = IAmBotTrigger.create()
                    call trig .add(whichUnit)
                    call TriggerHeapInner.insert(trig)
                else
                    call TriggerHeapInner(trig).bubbleDown()
                endif
            endmethod
            static method remove takes UnitIndex whichUnit returns nothing
                local IAmBotTrigger trig = IAmBotTrigger(whichUnit).parent
                call IAmBotTrigger.remove(whichUnit)
                call TriggerHeapInner(trig).bubbleUp()
            endmethod
        endif

        endstruct
        
        private module IAmBotTriggerRefreshInitModule
            private static method onInit takes nothing returns nothing
                call init(function IAmBot.onAquireEvent)
            endmethod
        endmodule
        
        private struct TriggerRefreshInit extends array
            
            private static method onIndex takes nothing returns boolean
                if onFilter(GetIndexedUnit()) then
                    call TriggerHeap.add(GetIndexedUnitId())
                    set IAmBot.has[GetIndexedUnitId()] = true
                    call IAmBot.compatibility(GetIndexedUnitId())
                endif
                return false
            endmethod
            
            private static method onDeindex takes nothing returns boolean
                if IAmBot.has[GetIndexedUnitId()] then
                    call TriggerHeap.remove(GetIndexedUnitId())
                    set IAmBot.has[GetIndexedUnitId()] = false
                endif
                return false
            endmethod
        
            private static method onChangeOwnerEvent takes nothing returns boolean
                local integer index = GetUnitUserData(GetChangingUnit())
                debug call BJDebugMsg("Change owner event is runnin")
                if (0 != index) then
                    if onFilter(GetUnitById(index)) then
                        if not IAmBot.has[index] then
                            call TriggerHeap.add(index)
                            set IAmBot.has[index] = true
                            call IAmBot.compatibility(index)
                        endif
                    elseif IAmBot.has[index] then
                        set IAmBot.has[index] = false
                        call TriggerHeap.remove(index)
                        call IAmBot.deactivate(index)
                    endif
                endif
                return false
            endmethod
        
            private static method init takes code c returns nothing
                set condition = Condition(c)

                static if LIBRARY_UnitDex then
                    call RegisterUnitIndexEvent(Condition(function thistype.onIndex), 0)
                    call RegisterUnitIndexEvent(Condition(function thistype.onDeindex), 1)
                else
                    call RegisterUnitIndexEvent(Condition(function thistype.onIndex), UnitIndexer.INDEX)
                    call RegisterUnitIndexEvent(Condition(function thistype.onDeindex), UnitIndexer.DEINDEX)
                endif
                
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_CHANGE_OWNER, function thistype.onChangeOwnerEvent)
            endmethod
            
            implement IAmBotTriggerRefreshInitModule
        endstruct
    endscope

endlibrary

JASS:
library IAmBotExIndicator uses IAmBot/* v1.0
*************************************************************************************
*
*   Indicator Pluggin for IAmBot.
*   Assigns a unique effect to an active IAmBot unit.
*
*************************************************************************************
*
*   SETTINGS
*/
globals
    constant string BOT_UNIQUE_INDICATOR      = "UI\\Feedback\\SelectionCircleEnemy\\SelectionCircleEnemy.mdx"
    constant string BOT_ATTACH_POINT          = "origin"
endglobals
/*
*************************************************************************************
*
*   API
*       - no additional API, everything works automatic.
*
*************************************************************************************/
//! textmacro I_AM_BOT_EXPANSION_INDICATOR_CODE
    scope IAmBotExIndicator

    module IAmBotExIndicatorAPI
        readonly effect sfx
        method display takes boolean flag returns nothing
            if (sfx != null) then
                call DestroyEffect(sfx)
            set sfx = null
            endif
            if flag then
                set sfx = AddSpecialEffectTarget(BOT_UNIQUE_INDICATOR, who, BOT_ATTACH_POINT)
            endif
        endmethod
    endmodule
            
    module IAmBotExIndicatorAdd
        call this.display(true)
    endmodule            
    module IAmBotExIndicatorClear
        call this.display(false)
    endmodule

    endscope
//! endtextmacro
endlibrary

JASS:
library IAmBotExUnitState uses IAmBot /* v1.0
*************************************************************************************
*
*   Unit State Pluggin for IAmBot.
*   Adds additional API to the IAmBot struct.
*
*************************************************************************************
*
*   SETTINGS
*/
globals
    constant real BOT_MAXIMUM_HELP_RANGE   = 700.
    constant real BOT_ASK_FOR_HELP_LIFE    = 0.35
    constant real BOT_CRITICAL_LIFE        = 0.15
    constant real BOT_RETREAT_DISTANCE     = 230.
endglobals
/*
*************************************************************************************
*
*   API
*
*       readonly real life 
*           -> how much life the unit has
*       readonly real maxLife 
*           -> how much max life the unit has
*       readonly real lifeP
*           -> current life percent 
*       readonly real speed       
*           -> current movementspeed
*       readonly Table HEALER
*           -> Additional Table for custom heal events.
*  
*   Adds the following features by default:
*   
*       1. IAmBot units retreat for BOT_RETREAT_DISTANCE if lifeP is below critical life.
*
*       2. IAmBot units may request help:
*           -> units with less lifeP than THREAT_ASK_FOR_HELP_LIFE will request help from allied units within THREAT_MAXIMUM_HELP_RANGE
*           -> Only units types registered with the HEALER Table can offer help. 
*           -> Does work group-across, based on drift compatibility index.
*
*       (Possible future extra: Units may request passive aggressive help like a tactical storbolt) 
*           -> Depends much on how IAmBot priority is going to work out. 
*
*************************************************************************************/

//! textmacro I_AM_BOT_EXPANSION_UNITSTATE_CODE

    scope IAmBotExUnitState
        
        module IAmBotExUnitStateAPI
            readonly real life
            readonly real maxLife
            readonly real lifeP
            readonly real speed
            readonly static Table HEALER      = 0
            readonly static BotList healstack = 0
            readonly static integer array healR

            method requestHeal takes nothing returns boolean
                local BotList node = healstack.first
                local IAmBot temp
                loop
                    exitwhen 0 == node
                    set temp = node.index
                    if (0 > temp.cd) and (IsUnitInRange(who, temp.who, BOT_MAXIMUM_HELP_RANGE)) and (comp == temp.comp) then
                        if (UnitAlive(temp.who)) then
                            set IAmBot.current   = temp
                            set IAmBot.secondary = this
                            return TriggerEvaluate(IAmBot.HEALER.trigger[temp.id])
                        endif
                    endif
                    set node = node.next
                endloop
                return false
            endmethod
            
            method registerHealer takes nothing returns nothing
                local BotList node = healstack.enqueue()
                call node.link(this)
                set healR[this] = node
            endmethod
            
            method releaseHealer takes nothing returns nothing
                local BotList node = healR[this]
                call node.remove()
                set healR[this] = 0
            endmethod
        endmodule
            
        module IAmBotExUnitStateInit
            set HEALER    = Table.create()
            set healstack = BotList.create() 
        endmodule
        
        module IAmBotExUnitStateUpdate
            set life     = GetWidgetLife(who)
            set maxLife  = GetUnitState(who, UNIT_STATE_MAX_LIFE)
            set lifeP    = life/maxLife
            set speed    = GetUnitMoveSpeed(who)
        endmodule
        
        module IAmBotExUnitStateSetup
            if HEALER.handle.has(id) then
                call this.registerHealer()
            endif
        endmodule
        
        module IAmBotExUnitStateResponsePrimary
            if (lifeP < BOT_ASK_FOR_HELP_LIFE) then
                if this.requestHeal() then
                    implement IAmBotExUnitStateUpdate 
                endif
            endif
        endmodule
        
        module IAmBotExUnitStateResponseSecondary
            debug call BJDebugMsg(R2S(lifeP)+  "/" + R2S(BOT_CRITICAL_LIFE))
            if (lifeP < BOT_CRITICAL_LIFE) then
                set paused         = 1
                call move(whoX - BOT_RETREAT_DISTANCE*Cos(angle),  whoY - BOT_RETREAT_DISTANCE*Sin(angle))
            endif
        endmodule
        
        module IAmBotExUnitStateClear
            if (0 != healR[this]) then
                call this.releaseHealer()
            endif
        endmodule

    endscope
//! endtextmacro

endlibrary
JASS:
library IAmBotExEange uses IAmBot /* v1.0
*************************************************************************************
*
*   Engage Pluggin for IAmBot.
*   Adds additional API to the IAmBot struct.
*
*************************************************************************************
*
*   SETTINGS
*/
globals
    constant real BOT_DEFAULT_RESPONSE_RANGE = 950.
endglobals
/*
*************************************************************************************
*
*   API
*
*   readonly static Table ENGAGE
*       --> fire custom engage events from this Table.
*  
*   Adds the following features by default:
*   
*       1. IAmBot units now have unique engage behaviour based on their UnitTypeId.
*
*************************************************************************************/
//! textmacro I_AM_BOT_EXPANSION_ENGAGE_CODE

    scope IAmBotExEnage
        module IAmBotExEnageAPI
            readonly static Table ENGAGE = 0
        endmodule
        
        module IAmBotExEnageInit
            set ENGAGE = Table.create()
        endmodule
    
        module IAmBotExEngageResponse
            if (0 == paused) and (0 > cd) and ENGAGE.handle.has(id) and (hasAim) then
                if IsUnitInRange(who, aim, BOT_DEFAULT_RESPONSE_RANGE) then
                    set IAmBot.current = this
                    call TriggerEvaluate(ENGAGE.trigger[id])
                    set going          = false
                else
                    set aim            = null
                endif
            endif
        endmodule
    endscope
    
//! endtextmacro
endlibrary

JASS:
scope Example initializer init

    private function Paladin takes nothing returns boolean
        local IAmBot healer    = IAmBot.current
        local IAmBot needsHelp = IAmBot.secondary
        //i.e. Don't consider healing units with less than 100 max life. 
        if (needsHelp.maxLife > 100) and IssueTargetOrderById(healer.who, 852092, needsHelp.who) then
            set healer.cooldown  = 1//Add a spell cooldown of 1 clock timeouts
            set healer.pause     = 1//And a total pause of 1
        endif

        return false
    endfunction
    
    private function init takes nothing returns nothing
        call RegisterBotEvent(IAmBot.HEALER, 'Hpal', Condition(function Paladin))
    endfunction

endscope
 

Attachments

  • Threat System.w3x
    257.9 KB · Views: 123
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
no API at the top of the Threat library makes it very hard to use, because you have to scroll through code to check the function names, and guess what they do
Of course, this is not finished at all. It's just what I wrote that night :)
Give me some time.
Whats wrong with Zwiebelchen's Threat System 2.6 ?
Nothing I guess.
@BPower: A test map would be awesome! The features sound pretty nifty. I wanna see it in action.
I will make a proper testing ground once the library is finished. I will also add more debug features like: how many timers are currently running, a trigger is rebuilt, ...

My main goal is to gather usefull unit and unit goup informations, not so much about giving orders. The latter is very map specific and should be done by the map makers.
That's why I came up with the pluggin idea. You create your for instance engage pluggin and implement it into the loop. //! runtextmacro THREAT_ENGAGE_PLUGGIN()
or make an external library, after all every unit information in the Threat library is readonly and allocated with unit user data. This allows you to access all required information easy and everywhere.
 
Level 25
Joined
Jun 5, 2008
Messages
2,572
Please change the:
JASS:
readonly trigger trigger

To something like
JASS:
readonly trigger trig

To avoid confusion.
Try to add some lines of space between some methods to ensure better readability.
This is the only feedback i can give you atm, as i have no time to get deeper into the code itself and from the surface it looks good.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
@Kingz that code was written by Nestharus and approved in the jass section before he took down his stuff.
Understandably I expect it to work as intended, hence I haven't gone throught the code in detail, but just scanned it.
I'm not changing it, because it can also found in his DDS which I will use for the Threat libarary.
Changing it here and not there somehow makes no sense to me.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
because one person has one naming convention doesnt mean everyone should use the same naming convention

it is confusing, I agree, but at the same time I dont really mind, because it works when compiled, because the name is changed to (PRIVATE_PREFIX__)Struct_Name__trigger

i dont get it, its a randon german word, nothing special..

german has lots of "weird"(to me at least, as non-german) long words, but there is the CTRL+C for a reason :D
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
It's Zwiebel which means onion. -chen is minimisation in the german language.
Thinking about it the german language requires more words for the same text.
This makes the enjoyable reading experience more awesome, because you actually read more for the same information:thumbs_up:.
 
Last edited:
Level 25
Joined
Jun 5, 2008
Messages
2,572
@Kingz that code was written by Nestharus and approved in the jass section before he took down his stuff.
Understandably I expect it to work as intended, hence I haven't gone throught the code in detail, but just scanned it.
I'm not changing it, because it can also found in his DDS which I will use for the Threat libarary.
Changing it here and not there somehow makes no sense to me.

Sounds reasonable enough.
 
I wrote an artificial intelligence system too:

JASS:
library AI initializer StartAI
    function StartAI takes nothing returns nothing
        loop
            call ExecuteFunc("StartAI")
        endloop
    endfunction
endlibrary

This AI is smart enough to know that your map is a piece of shit and playing it is not productive at all, so it intelligently quits Warcraft III for you~

Lovely ey?
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I just wanted to chime in to say that I didn't add my name into ZTS out of narcism, but because there was already a library called "Threat" on TheHelper back in the days when I wrote this. ;)
Also, people were most likely to confuse "Threat" with the resource named "Thread" by Nestharus, which does something completely different. ;)

Giving a resource that clearly has a personal touch (like a threat system always has, it's a matter of interpretation and not an established standard unless we consider World of Warcraft as the standard), it felt appropriate to not add generic ghibberish things like "dynamic" or "automatic" or "advanced" to the libname just to seperate it from other resources.

Anyways, I'm very curious about how your resource will work out, especially since you used a totally different approach (dynamic triggers instead of tables).
I assume it will be a lot slower that way, though.

I think it all depends on the target audience and API. The beauty about ZTS is that it's incredibly easy to use, since almost everything works automaticly. But that also limits flexibility.
If yours is more flexible in usage (module or plugin based), I can see it working great. However, the user base will probably be limited then [it's hard enough already to make a simple JASS resource appealing to the GUI majority, let alone a plugin based resource].


All hail to the onion!
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
A test map would be awesome! The features sound pretty nifty. I wanna see it in action.
I'm on it.
That's when I stopped reading :)
Mmh I understand, however unlike you I'm a big fan of UnitIndexers!
Anyways, I'm very curious about how your resource will work out, especially since you used a totally different approach (dynamic triggers instead of tables).
I assume it will be a lot slower that way, though.
Yeah the trigger creation is somehow a leviathan, I'm not sure if I want to keep it. I'm curious aswell, by now I haven't really run some ingame tests.

The code already looks different to the one I uploaded here. I'm going to do a realistic testmap with terrain, camera system, etc .. and see how it is working out.

Edit:
To keep this topic alive I've uploaded a demo map with the current state of the Threat system (I'll change the name in the next update)
There is still a lot to do and to fix, but some basics are already working.

Edit2: Fixed the "request help" feature, working now across groups. Basically used to order heals and retreat, ofc can also fire aggressive spells like a tactical stormbolt etc ...
(will upload tomorrow)
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
I just want to mention that this project is not dead, but I have to fix the map I'm testing the IAmBot script with first.
Something causes fps drops from time to time (IAmBot script completly disabled) and I wasn't able to narrow the problem down, yet.
I'm currently going through every system/spell I made. I hope the bug is not within one of the few external resource I've imported no questions asked.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I see. I don't explain everything in detail now, because I don't know when I will pick up the work on it again. I don't have the time at the moment.
It's not really useable at the moment, however the idea behind the script is very good (imo).

The script doesn't manipulate UnitUserData anywhere. UnitUserData equals the allocated struct instance for that unit.
So retrieving UnitUserData returns the correct struct instance.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
How often does stuff change so much that it breaks backwards compatibility? ; )

Stuff like unit indexer had an api from 2010. That's four years. I think you're safe :p

The likelihood of me discovering another radically new and awesome design like events with phases are slim, heh.

The only updates in the future will be cleaning up the module more. I like the api and it is finally modular to the degree that i want it to be.

You should use it ;D.

Oh, and Trigger is totally awesome. Learn it.

Also, i've wanted to do that event model and an API like that since 2012, i just couldn't figure out how. Trigger made everything possible :D
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
How often does stuff change so much that it breaks backwards compatibility? ; )

I can only speak for DDS, looking at 4 random versions from the last (about) 12 months:

JASS:
struct MyDDS1 extends array
    private static method onDamage takes nothing returns nothing
        if (DDS.archetype == Archetype.PHYSICAL) then

        endif
    endmethod

    implement DDS
endstruct

struct MyDDS2 extends array
    private static method onDamage takes nothing returns nothing
        if (damageSourceType == DamageSourceType.SPELL) then
            
        endif
    endmethod
    
    implement DamageEvent
endstruct

struct MyDDS3 extends array
    private static method onDamage takes nothing returns nothing
        if archetype == Archetype.PHYSICAL then

        endif
    endmethod
	
    implement DDS
endstruct

struct MyDDS4 extends array
    private static method onDamageBefore takes nothing returns nothing
        if (archetype == Archetype.PHYSICAL) then
            
        endif
    endmethod

    implement DDS
endstruct

4 different versions, 4 different APIs (and thats just the most important API). Plus everytime new/different requirements. Not speaking about the fundamental changes behind the scene with things like local events (that don't bring a real benefit except for a neglectable speed boost).

I can understand people that say that this release policy is too "unstable" for them. If I make a map I don't want to change perfectly working triggers/spells all the time, even if the changes are small. An API change (or worse, a rework of the whole system) should always be justified with real benefits.

You should consider this, especially as most people here don't want to use Github.
 
Status
Not open for further replies.
Top