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

RPG Threat System v3.2c

The system provides various behaviors to creeps, like:
- Sleeping
- Wandering
- Ambushing
- Helping
- Retreating
- etc.

JASS:
library RPGTS initializer onInit uses GetClosestWidget, Table, UnitIndexer, optional TimerUtils

    /************************************************************************************
    *                                                                                   *
    *                           RPG Threat System v3.2c                                 *
    *                                         *****                                     *
    *                                       by: Quilnez                                 *
    *                                                                                   *
    *  This system generates special behavior to creep units. Gives some brains to        *
    *  those shrimp-head creeps. They can flee, they have artificial sight area            *
    *  (you can sneak behind them), they are able to help each other, they become        *
    *  aggressive or not aggressive as well. And many more!                             *
    *                                                                                   *
    *  External dependencies:                                                           *
    *   (required)                                                                      *
    *     - Table                                                                       *
    *     - UnitIndexer                                                                 *
    *     - GetClosestWidget                                                            *
    *     - Any DDS                                                                     *
    *   (optional)                                                                      *
    *     - TimerUtils                                                                  *
    *                                                                                   *
    *  Implementation:                                                                  *
    *     - Copy paste RPG Threat System folder into your map                            *
    *     - Give two player slots for passive and aggresive player, better just         *
    *       leave'em as empty player slot                                               *
    *     - Configure the system & start defining your creep behavior                    *
    *     - Done.                                                                       *
    *                                                                                   *
    *  Credits:                                                                         *
    *     - GDD by Weep                                                                 *
    *     - GetClosestWidget by Bannar                                                  *
    *     - UnitIndexer by Nestharus                                                    *
    *     - TimerUtils by Vexorian                                                      *
    *     - Table by Bribe                                                              *
    *                                                                                   *
    *   (You can read more info at READ ME trigger)                                     *
    *                                                                                   *
    *************************************************************************************
    *                                                                                   *
    *                                CONFIGURATION                                      */
    globals
        // Owner of passive creeps
        private constant    player      PASSIVE             = Player(10)
     
        // Owner of active creeps
        private constant    player      AGGRESSIVE          = Player(11)
     
        // If true, creeps will use PASSIVE's player color
        private constant    boolean     COLOR               = true
     
        // Behavior generation area (distance from generator units)
        private constant    real        DISTANCE            = 2400.0
     
        // Created sfx when creeps are sleeping
        private constant    string      SLEEP_SFX           = "Abilities\\Spells\\Undead\\Sleep\\SleepTarget.mdl"
        private constant    string      SLEEP_SFX_PT        = "overhead"
     
        // Order Ids
        private constant    integer     ATTACK_ORDER_ID     = 851983
        private constant    integer     MOVE_ORDER_ID       = 851986
        private constant    integer     STOP_ORDER_ID       = 851972
     
        // Total seconds in a day (can be found in gameplay constants)
        private constant    real        SECONDS_IN_A_DAY    = 480
     
        // If true, all units owned by PASSIVE and AGGRESSIVE will be
        // registered automatically on unit indexing event
        private constant    boolean     AUTO_REGISTER       = true
     
        // Just leave it alone
        private constant    real        INTERVAL            = 0.03125
     
        // Don't touch this
        private group CreepGroup = CreateGroup()
    endglobals
 
    // Damage event of your DDS
    private function addEvent takes trigger t returns nothing
        call TriggerRegisterVariableEvent(t, "udg_GDD_Event", EQUAL, 0.0)
    endfunction
 
    // Damage source of your DDS
    private constant function getSource takes nothing returns unit
        return udg_GDD_DamageSource
    endfunction
 
    // Damage target of your DDS
    private constant function getTarget takes nothing returns unit
        return udg_GDD_DamagedUnit
    endfunction
 
    // Set classifications of affected creeps
    private function creepFilter takes unit u returns boolean
 
        // For safety, just keep this format
        if IsUnitType(u, UNIT_TYPE_HERO) or IsUnitType(u, UNIT_TYPE_STRUCTURE) then
            return false
        endif
     
    /*                                END OF CONFIGURATION                              *
    *                                                                                   *
    ************************************************************************************/
 
        if IsUnitInGroup(u, CreepGroup) then
            return false
        endif
        call GroupAddUnit(CreepGroup, u)
     
        return true
    endfunction
 
    struct SCDataLib
        private static thistype Dex
        private static Table LibIndex
     
        //! textmacro CREEP_DATA_TYPE takes VAR_NAME,VAL_TYPE
        readonly $VAL_TYPE$ $VAR_NAME$Var
        static method operator $VAR_NAME$= takes $VAL_TYPE$ value returns nothing
            set Dex.$VAR_NAME$Var = value
        endmethod
     
        //! endtextmacro
        //! runtextmacro CREEP_DATA_TYPE( "maxHelp",         "integer" )
        //! runtextmacro CREEP_DATA_TYPE( "helpType",        "integer" )
        //! runtextmacro CREEP_DATA_TYPE( "resetTimer",      "boolean" )
        //! runtextmacro CREEP_DATA_TYPE( "canFlee",         "boolean" )
        //! runtextmacro CREEP_DATA_TYPE( "isTamed",         "boolean" )
        //! runtextmacro CREEP_DATA_TYPE( "seekHelp",        "boolean" )
        //! runtextmacro CREEP_DATA_TYPE( "callHelp",        "boolean" )
        //! runtextmacro CREEP_DATA_TYPE( "seekRadius",      "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "callRadius",      "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "sightRadius",     "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "sleepTime",       "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "sleepDur",        "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "lowHealth",       "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "acquisitionRange","real"    )
        //! runtextmacro CREEP_DATA_TYPE( "ambushDelay",     "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "combatDuration",  "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "wanderDelay",     "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "wanderVariant",   "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "wanderDist",      "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "fleeDist",        "real"    )
        //! runtextmacro CREEP_DATA_TYPE( "nestArea",        "real"    )
     
        static method getIndex takes integer unitId returns integer
            return LibIndex.integer[unitId]
        endmethod
     
        static method add takes integer unitId returns nothing
            if LibIndex.integer[unitId] == 0 then
                set Dex = allocate()
                set LibIndex.integer[unitId] = Dex
            endif
        endmethod
     
        static method end takes nothing returns nothing
            set Dex = 0
        endmethod
     
        static method onInit takes nothing returns nothing
            set LibIndex = Table.create()
        endmethod
    endstruct
 
    // Container for creep data
    private struct CreepData
 
        boolean isSleep
        integer helpCount
        effect  sleepSfx
     
        unit    target
        timer   sleepTimer
        real    wanderDelay
     
        real    nestX
        real    nestY
        real    combatDur
     
        real    combatDly
        real    unitX
        real    unitY
     
    endstruct
 
    globals
        private boolean Move          = false
        private group PickGroup       = CreateGroup()
        private group TempGroup       = CreateGroup()
        private integer DummyType
        private integer Total         = -1
        private integer GenTotal      = 0
        private integer array GenDex
        private real array Real
        private timer Timer           = CreateTimer()
        private trigger array Handler
        private unit TempUnit
        private unit EventUnit        = null
        private unit EventThreat      = null
        private unit array GenUnit
        private CreepData array Data
     
        // Real array indexes
        private constant integer R_X             = 0
        private constant integer R_Y             = 1
        private constant integer R_TOD           = 2
        private constant integer R_WAKE          = 3
        private constant integer R_DISTANCE      = 4
        private constant integer R_FACING        = 5
        private constant integer R_DURATION      = 6
     
        // Event constants
        constant integer EVENT_CREEP_SLEEP       = 0
        constant integer EVENT_CREEP_AWAKE       = 1
        constant integer EVENT_CREEP_WANDER      = 2
        constant integer EVENT_CREEP_FLEE        = 3
        constant integer EVENT_CREEP_THREAT      = 4
        constant integer EVENT_CREEP_UNTHREAT    = 5
        constant integer EVENT_CREEP_ATTACK      = 6
        constant integer EVENT_CREEP_CALL_HELP   = 7
        constant integer EVENT_CREEP_SEEK_HELP   = 8
        constant integer EVENT_CREEP_GIVE_HELP   = 9
        constant integer EVENT_CREEP_LOW_HEALTH  = 10
        constant integer EVENT_CREEP_DEAL_DAMAGE = 11
        constant integer EVENT_CREEP_TAKE_DAMAGE = 12
        constant integer EVENT_CREEP_UNATTACK    = 13
    endglobals
 
    native UnitAlive takes unit id returns boolean
 
    // API functions
    function SetGenUnit takes unit u, boolean b returns nothing
 
        local integer uDex = GetUnitUserData(u)
 
        if b then
            if GenDex[uDex] == 0 then
                set GenTotal = GenTotal + 1
                set GenUnit[GenTotal] = u
                set GenDex[uDex] = GenTotal
            endif
        elseif GenDex[uDex] > 0 then
            set GenDex[GetUnitUserData(GenUnit[GenTotal])] = GenDex[uDex]
            set GenUnit[GenDex[uDex]] = GenUnit[GenTotal]
            set GenUnit[GenTotal] = null
            set GenTotal = GenTotal - 1
            set GenDex[uDex] = 0
        endif
     
    endfunction
 
    function AddCreepEventHandler takes integer whichEvent, boolexpr func returns nothing
        call TriggerAddCondition(Handler[whichEvent], func)
    endfunction
 
    function GetTriggerCreep takes nothing returns unit
        return EventUnit
    endfunction
 
    function GetTriggerThreat takes nothing returns unit
        return EventThreat
    endfunction
 
    function GetCreepNestX takes unit u returns real
        return Data[GetUnitUserData(u)].nestX
    endfunction
 
    function GetCreepNestY takes unit u returns real
        return Data[GetUnitUserData(u)].nestY
    endfunction
 
    function IsCreepSleeping takes unit u returns boolean
        return Data[GetUnitUserData(u)].isSleep
    endfunction
 
    function IsCreepMoving takes unit u returns boolean
        local integer uDex = GetUnitUserData(u)
        return GetUnitCurrentOrder(u) == MOVE_ORDER_ID or GetUnitX(u) != Data[uDex].unitX or GetUnitY(u) != Data[uDex].unitY
    endfunction
 
    // Get distance between points
    private function getDistance takes real x, real y, real xt, real yt returns real
        return SquareRoot((xt-x)*(xt-x)+(yt-y)*(yt-y))
    endfunction
 
    // Get angle between points
    private function getAngle takes real x, real y, real xt, real yt returns real
        return Atan2(yt-y, xt-x) * bj_RADTODEG
    endfunction
 
    // Get circular difference between two angles
    private function circularDifference takes real a, real b returns real
 
        local real r = RAbsBJ(a-b)
     
        if r <= 180 then
            return r
        else
            return 180 - r
        endif
     
    endfunction
 
    // Filter enemy units around creep
    private function targetFilter takes nothing returns boolean
 
        local unit u       = GetFilterUnit()
        local player p     = GetOwningPlayer(u)
        local SCDataLib lDex = SCDataLib.getIndex(GetUnitTypeId(TempUnit))
        local real a       = getAngle(GetUnitX(TempUnit), GetUnitY(TempUnit), GetUnitX(u), GetUnitY(u))
        local boolean b    = circularDifference(GetUnitFacing(TempUnit), a) < lDex.sightRadiusVar/4
     
        set b = b and (p != PASSIVE and p != AGGRESSIVE and UnitAlive(u))
        set b = b and (IsUnitVisible(u, PASSIVE) or IsUnitVisible(u, AGGRESSIVE))
        set u = null
     
        return b
    endfunction
 
    // Filter possible helper for creep
    private function helperFilter takes nothing returns boolean
 
        local unit    u    = GetFilterUnit()
        local integer id   = GetUnitTypeId(u)
        local integer uDex = GetUnitUserData(u)
        local SCDataLib lDex = SCDataLib.getIndex(id)
        local boolean b    = (lDex.helpTypeVar != 1 or id == DummyType) and lDex.helpTypeVar >= 1
     
        set b = b and (GetOwningPlayer(u) == PASSIVE and not lDex.isTamedVar)
        set b = b and not IsCreepSleeping(u)
        set u = null
     
        return b
    endfunction
 
    private function fireEvent takes integer whichEvent, unit u, unit t returns nothing
     
        local unit tu   = EventUnit
        local unit tt   = EventThreat
     
        set EventUnit   = u
        set EventThreat = t
     
        call TriggerEvaluate(Handler[whichEvent])
     
        set EventUnit   = tu
        set EventThreat = tt
     
        set tu = null
        set tt = null
     
    endfunction
 
    // Seek for nearby helpers
    private function pickHelper takes unit u, unit t, integer uDex, SCDataLib lDex returns nothing
     
        local integer i = 0
        local SCDataLib lDex2
        local integer uDex2
        local unit h
     
        loop
            exitwhen Data[uDex].helpCount == lDex.maxHelpVar
            set h = GetClosestUnitInRange(Data[uDex].unitX, Data[uDex].unitY, lDex.callRadiusVar, Filter(function helperFilter))
            exitwhen h == null
         
            call SetUnitOwner(h, AGGRESSIVE, not COLOR)
            set uDex2 = GetUnitUserData(h)
            set lDex2 = SCDataLib.getIndex(GetUnitTypeId(h))
         
            set Data[uDex2].combatDur = lDex2.combatDurationVar
            set Data[uDex2].target    = t
         
            call IssueTargetOrderById(h, ATTACK_ORDER_ID, t)
            call fireEvent(EVENT_CREEP_GIVE_HELP, h, null)
            set Data[uDex].helpCount = Data[uDex].helpCount + 1
        endloop
        set h = null
     
    endfunction
 
    // Filter creeps around generators
    private function pickFilter takes nothing returns boolean
     
        local unit u = GetFilterUnit()
     
        if not IsUnitInGroup(u, PickGroup) and IsUnitInGroup(u, CreepGroup) then
            call GroupAddUnit(PickGroup, u)
        endif
        set u = null
     
        return false
    endfunction
 
    private function onLoop takes nothing returns nothing
 
        local unit u
        local unit t
        local SCDataLib lDex
        local integer uDex
        local integer i = 1
     
        // Collect creeps around generator units' positions
        loop
            exitwhen i > GenTotal
            call GroupEnumUnitsInRange(TempGroup, GetUnitX(GenUnit[i]), GetUnitY(GenUnit[i]), DISTANCE, function pickFilter)
            set i = i + 1
        endloop
     
        loop
            set u = FirstOfGroup(PickGroup)
            exitwhen u == null
            call GroupRemoveUnit(PickGroup, u)
         
            set uDex = GetUnitUserData(u)
            if GetUnitTypeId(u) != 0 then
                if UnitAlive(u) then
                    set lDex = SCDataLib.getIndex(GetUnitTypeId(u))
                    // Determine whether the creep is moving atm or not
                    set Move = IsCreepMoving(u)
                    if Move then
                        set Data[uDex].unitX = GetUnitX(u)
                        set Data[uDex].unitY = GetUnitY(u)
                    endif
                 
                    if GetOwningPlayer(u) == PASSIVE then
                        if Data[uDex].combatDly < 0 then
                            // If the creep is able to sleep
                            if lDex.sleepDurVar > 0 then
                                // Gather sleep time datas
                                set Real[R_TOD]  = GetFloatGameState(GAME_STATE_TIME_OF_DAY)
                                set Real[R_WAKE] = lDex.sleepTimeVar + lDex.sleepDurVar
                                if  Real[R_TOD] <= Real[R_WAKE] - 24 then
                                    set Real[R_TOD] = Real[R_TOD] + 24
                                endif
                             
                                // Check sleep time
                                if Real[R_TOD] >= lDex.sleepTimeVar and Real[R_TOD] < Real[R_WAKE] and not Data[uDex].isSleep then
                                    if not Move and not(IsUnitType(u, UNIT_TYPE_STUNNED) or IsUnitPaused(u)) then
                                        set Data[uDex].isSleep  = true
                                        set Data[uDex].sleepSfx = AddSpecialEffectTarget(SLEEP_SFX, u, SLEEP_SFX_PT)
                                        if Data[uDex].sleepTimer == null then
                                            static if LIBRARY_TimerUtils then
                                                set Data[uDex].sleepTimer = NewTimer()
                                            else
                                                set Data[uDex].sleepTimer = CreateTimer()
                                            endif
                                            // Calculate sleep duration
                                            set Real[R_DURATION] = (SECONDS_IN_A_DAY/86400)*((lDex.sleepDurVar-(Real[R_TOD]-lDex.sleepTimeVar))*3600)/GetTimeOfDayScale()
                                            call TimerStart(Data[uDex].sleepTimer, Real[R_DURATION], false, null)
                                        endif
                                        call fireEvent(EVENT_CREEP_SLEEP, u, null)
                                    endif
                                endif
                            endif
                         
                            if Data[uDex].isSleep then
                                // Wake up time
                                if TimerGetRemaining(Data[uDex].sleepTimer) <= 0 then
                                    call DestroyEffect(Data[uDex].sleepSfx)
                                    static if LIBRARY_TimerUtils then
                                        call ReleaseTimer(Data[uDex].sleepTimer)
                                    else
                                        call DestroyTimer(Data[uDex].sleepTimer)
                                    endif
                                 
                                    set Data[uDex].isSleep    = false
                                    set Data[uDex].sleepTimer = null
                                    set Data[uDex].sleepSfx   = null
                                 
                                    call fireEvent(EVENT_CREEP_AWAKE, u, null)
                                endif
                            else
                                // Check if wandering or not
                                if lDex.wanderDelayVar > 0 then
                                    if Data[uDex].wanderDelay > INTERVAL then
                                        set Data[uDex].wanderDelay = Data[uDex].wanderDelay - INTERVAL
                                    else
                                        set Real[R_DISTANCE] = GetRandomReal(lDex.wanderDistVar, lDex.nestAreaVar)
                                        set Real[R_FACING]   = GetRandomReal(0,bj_PI*2)
                                     
                                        // Reset wander delay
                                        set Data[uDex].wanderDelay = lDex.wanderDelayVar + GetRandomReal(-lDex.wanderVariantVar, lDex.wanderVariantVar)/2
                                     
                                        set Real[R_X] = Data[uDex].nestX + Real[R_DISTANCE] * Cos(Real[R_FACING])
                                        set Real[R_Y] = Data[uDex].nestY + Real[R_DISTANCE] * Sin(Real[R_FACING])
                                     
                                        call IssuePointOrderById(u, MOVE_ORDER_ID, Real[R_X], Real[R_Y])
                                        call fireEvent(EVENT_CREEP_WANDER, u, null)
                                    endif
                                endif
                             
                                // If the creep is aggressive
                                if lDex.acquisitionRangeVar > 0 then
                                    // If is around it's nest area
                                    if getDistance(Data[uDex].unitX, Data[uDex].unitY, Data[uDex].nestX, Data[uDex].nestY) <= lDex.nestAreaVar then
                                        set TempUnit = u
                                        set t = GetClosestUnitInRange(Data[uDex].unitX, Data[uDex].unitY, lDex.acquisitionRangeVar, Filter(function targetFilter))
                                        if t != null then
                                            set Data[uDex].target    = t
                                            set Data[uDex].combatDly = lDex.ambushDelayVar
                                            call IssueImmediateOrderById(u, STOP_ORDER_ID)
                                            call fireEvent(EVENT_CREEP_THREAT, u, t)
                                            set t = null
                                        endif
                                    endif
                                endif
                            endif
                        else
                            set Real[R_X] = GetUnitX(Data[uDex].target)
                            set Real[R_Y] = GetUnitY(Data[uDex].target)
                            call SetUnitFacing(u, getAngle(Data[uDex].unitX, Data[uDex].unitY, Real[R_X], Real[R_Y]))
                         
                            // If target has fleed
                            if getDistance(Data[uDex].unitX, Data[uDex].unitY, Real[R_X], Real[R_Y]) > lDex.acquisitionRangeVar then
                                set Data[uDex].combatDly = -1
                                set Data[uDex].target    = null
                                call fireEvent(EVENT_CREEP_UNTHREAT, u, Data[uDex].target)
                            else
                                if Data[uDex].combatDly > INTERVAL then
                                    set Data[uDex].combatDly = Data[uDex].combatDly - INTERVAL
                                else
                                    set Data[uDex].combatDur = lDex.combatDurationVar
                                    call SetUnitOwner(u, AGGRESSIVE, not COLOR)
                                    call IssueTargetOrderById(u, ATTACK_ORDER_ID, Data[uDex].target)
                                    call fireEvent(EVENT_CREEP_ATTACK, u, Data[uDex].target)
                                endif
                            endif
                        endif
                    else
                        // Calm down if the combat timer is up or if the target has died
                        if Data[uDex].combatDur > INTERVAL and UnitAlive(Data[uDex].target) then
                            set Data[uDex].combatDur = Data[uDex].combatDur - INTERVAL
                        else
                            call SetUnitOwner(u, PASSIVE, not COLOR)
                            set Data[uDex].helpCount = 0
                         
                            // Order to move away
                            set Real[R_DISTANCE] = GetRandomReal(0, lDex.nestAreaVar)
                            set Real[R_FACING]   = GetRandomReal(0, bj_PI*2)
                         
                            set Real[R_X] = Data[uDex].nestX + Real[R_DISTANCE] * Cos(Real[R_FACING])
                            set Real[R_Y] = Data[uDex].nestY + Real[R_DISTANCE] * Sin(Real[R_FACING])
                         
                            call IssuePointOrderById(u, MOVE_ORDER_ID, Real[R_X], Real[R_Y])
                            call fireEvent(EVENT_CREEP_UNATTACK, u, Data[uDex].target)
                            set Data[uDex].target = null
                        endif
                    endif
                endif
            else
                call Data[uDex].destroy()
                set  Data[uDex].sleepTimer = null
                set  Data[uDex].sleepSfx   = null
                set  Data[uDex].target     = null
     
                set Total = Total - 1
                if Total < 0 then
                    call PauseTimer(Timer)
                endif
            endif
        endloop
     
    endfunction

    private function onHit takes nothing returns boolean
 
        local SCDataLib lDex
        local integer uDex
        local integer i
        local integer ix
        local unit t = getTarget()
        local unit s = getSource()
        local unit h
     
        if GetOwningPlayer(s) == AGGRESSIVE and IsUnitInGroup(s, CreepGroup) then
            set DummyType = GetUnitTypeId(s)
            set uDex = GetUnitUserData(s)
            set lDex = SCDataLib.getIndex(DummyType)
            call fireEvent(EVENT_CREEP_DEAL_DAMAGE, s, t)
         
            if lDex.resetTimerVar then
                if getDistance(Data[uDex].unitX, Data[uDex].unitY, Data[uDex].nestX, Data[uDex].nestY) <= lDex.nestAreaVar then
                    set Data[uDex].combatDur = lDex.combatDurationVar
                endif
            endif
         
            // If call for help when attacking
            if lDex.maxHelpVar > 0 and lDex.callHelpVar then
                call pickHelper(s, t, uDex, lDex)
                call fireEvent(EVENT_CREEP_CALL_HELP, s, null)
            endif
         
        elseif IsUnitInGroup(t, CreepGroup) then
            set DummyType = GetUnitTypeId(t)
            set uDex = GetUnitUserData(t)
            set lDex = SCDataLib.getIndex(DummyType)
            set Data[uDex].combatDur = lDex.combatDurationVar
            call fireEvent(EVENT_CREEP_TAKE_DAMAGE, t, s)
         
            // If the creep is currently sleeping
            if Data[uDex].isSleep then
                call DestroyEffect(Data[uDex].sleepSfx)
                set Data[uDex].sleepSfx = null
                set Data[uDex].isSleep  = false
                call fireEvent(EVENT_CREEP_AWAKE, t, null)
            endif
         
            if lDex.canFleeVar and lDex.fleeDistVar > 0 then
                // If still around it's nest area
                if getDistance(Data[uDex].unitX, Data[uDex].unitY, Data[uDex].nestX, Data[uDex].nestY) < lDex.nestAreaVar then
                    set Real[R_FACING] = getAngle(GetUnitX(s), GetUnitY(s), Data[uDex].unitX, Data[uDex].unitY) * bj_DEGTORAD
                    set Real[R_X] = Data[uDex].unitX + lDex.fleeDistVar * Cos(Real[R_FACING])
                    set Real[R_Y] = Data[uDex].unitY + lDex.fleeDistVar * Sin(Real[R_FACING])
                else
                    set Real[R_FACING] = GetRandomReal(0, bj_PI*2)
                    set Real[R_X] = Data[uDex].nestX + lDex.fleeDistVar * Cos(Real[R_FACING])
                    set Real[R_Y] = Data[uDex].nestY + lDex.fleeDistVar * Sin(Real[R_FACING])
                endif
                call IssuePointOrderById(t, MOVE_ORDER_ID, Real[R_X], Real[R_Y])
                call fireEvent(EVENT_CREEP_FLEE, t, null)
            endif
         
            if GetOwningPlayer(t) == PASSIVE then
                set Data[uDex].target = s
                // If not tamed
                if not lDex.isTamedVar then
                    call SetUnitOwner(t, AGGRESSIVE, not COLOR)
                    call IssueTargetOrderById(t, ATTACK_ORDER_ID, s)
                endif
             
                if lDex.maxHelpVar > 0 then
                    call pickHelper(t, s, uDex, lDex)
                    call fireEvent(EVENT_CREEP_CALL_HELP, t, s)
                endif
            endif
         
            if GetWidgetLife(t) <= lDex.lowHealthVar then
                call fireEvent(EVENT_CREEP_LOW_HEALTH, t, null)
                // If able to seek help
                if lDex.seekHelpVar and lDex.maxHelpVar > 0 then
                    set h = GetClosestUnitInRange(Data[uDex].unitX,Data[uDex].unitY,lDex.seekRadiusVar,Filter(function helperFilter))
                    // Order to move to helper's position
                    if h != null then
                        call IssuePointOrderById(t, MOVE_ORDER_ID, GetUnitX(h), GetUnitY(h))
                        set h = null
                    endif
                    call fireEvent(EVENT_CREEP_SEEK_HELP, t, null)
                endif
            endif
        endif
        set t = null
        set s = null
     
        return false
    endfunction
 
    private function onDeath takes nothing returns boolean
     
        local integer uDex
        local SCDataLib lDex
        local player  p = GetTriggerPlayer()
        local unit u
     
        if p == PASSIVE or p == AGGRESSIVE then
            set u    = GetTriggerUnit()
            set uDex = GetUnitUserData(u)
            set lDex = SCDataLib.getIndex(GetUnitTypeId(u))
         
            call SetUnitOwner(u, PASSIVE, not COLOR)
            set Data[uDex].wanderDelay = lDex.wanderDelayVar + GetRandomReal(-lDex.wanderVariantVar, lDex.wanderVariantVar)/2
            set Data[uDex].combatDur   = -1
         
            set Data[uDex].combatDly   = -1
            set Data[uDex].helpCount   = 0
            set Data[uDex].isSleep     = false
         
            set u = null
        endif
     
        return false
    endfunction

    function EnableCreepBehavior takes unit u, boolean b returns nothing
 
        local SCDataLib lDex
        local integer uDex
        local player p = GetOwningPlayer(u)
     
        if b then
            if p == PASSIVE or p == AGGRESSIVE then
                if p == AGGRESSIVE then
                    call SetUnitOwner(u, PASSIVE, false)
                    static if COLOR then
                        call SetUnitColor(u, GetPlayerColor(PASSIVE))
                    endif
                else
                    static if not COLOR then
                        call SetUnitColor(u, GetPlayerColor(AGGRESSIVE))
                    endif
                endif
             
                if creepFilter(u) then
                    set Total = Total + 1
                    set uDex  = GetUnitUserData(u)
                    set lDex  = SCDataLib.getIndex(GetUnitTypeId(u))
                 
                    set Data[uDex] = CreepData.create()
                    set Data[uDex].helpCount = 0
                    set Data[uDex].isSleep   = false
                 
                    set Data[uDex].wanderDelay = GetRandomReal(0, lDex.wanderDelayVar)
                    set Data[uDex].nestX = GetUnitX(u)
                    set Data[uDex].nestY = GetUnitY(u)
                 
                    set Data[uDex].combatDly = -1
                    set Data[uDex].unitX = 0
                    set Data[uDex].unitY = 0
                 
                    if Total == 0 then
                        call TimerStart(Timer, INTERVAL, true, function onLoop)
                    endif
                endif
            endif
        elseif IsUnitInGroup(u, CreepGroup) then
            call GroupRemoveUnit(CreepGroup, u)
        endif
     
    endfunction

    private function onIndex takes nothing returns boolean
        call EnableCreepBehavior(GetIndexedUnit(), true)
        return false
    endfunction

    private function setAlliance takes nothing returns nothing
 
        local player p = GetEnumPlayer()
     
        if p != PASSIVE and p != AGGRESSIVE then
            // Aggressive player threats others as enemies
            call SetPlayerAlliance(AGGRESSIVE, p, ALLIANCE_PASSIVE, false)
            // Passive player threats other players as allies
            call SetPlayerAlliance(PASSIVE, p, ALLIANCE_PASSIVE, true)
        endif
     
    endfunction

    private function onInit takes nothing returns nothing
 
        local player  p  = GetLocalPlayer()
        local trigger t1 = CreateTrigger()
        local trigger t2 = CreateTrigger()
     
        static if AUTO_REGISTER then
            call RegisterUnitIndexEvent(Condition(function onIndex), UnitIndexer.INDEX)
        endif
     
        call addEvent(t1)
        call TriggerAddCondition(t1, Condition(function onHit))
     
        call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddCondition(t2, Condition(function onDeath))
     
        // Alliance setting
        call SetPlayerAlliance(PASSIVE, AGGRESSIVE, ALLIANCE_PASSIVE, true)
        call SetPlayerAlliance(AGGRESSIVE, PASSIVE, ALLIANCE_PASSIVE, true)
        call ForForce(bj_FORCE_ALL_PLAYERS, function setAlliance)
     
        // Give global sight for both player
        if p == AGGRESSIVE or p == PASSIVE then
            call FogEnable(false)
            call FogMaskEnable(false)
        endif
     
        set t1 = null
        set t2 = null
     
    endfunction
 
endlibrary

Credits
  • TimerUtils by Vexorian
  • GetClosestWidget by Spinnaker
  • UnitIndexer by Nestharus
  • GDD by Weep
  • Table by Bribe

Keywords:
rpg, creep, monster, behavior
Contents

RPG Threat System (Map)

Reviews
IcemanBo: - date: 3th March 2015 - submission: Special Creep Engine v3.2b The system allows to define unique unit-type behaviours for creeps to improve the enemy's AI creep controle. All in all the code is efficient, leakless and works fine...

Moderator

M

Moderator

IcemanBo:
- date: 3th March 2015
- submission: Special Creep Engine v3.2b

The system allows to define unique unit-type behaviours
for creeps to improve the enemy's AI creep controle.

All in all the code is efficient, leakless and works fine.
I have not found nor bugs nor major flaws in the system.

  • 2*r <= 360 -> r <= 180.
  • It might be a bit confusing for user that the creep sleeping is not the same as the normal wc3 unit sleeping.
    It might be helpful if it was clarified.
  • Arithmetic like "SECONDS_IN_A_DAY/86400" could be calculated once onInit.
  • The term "generator unit" that is used is not really explained. (?)

Good job. Approved.


IcemanBo: http://www.hiveworkshop.com/forums/...p-engine-v3-2b-248858/index5.html#post2640438

General

Coding

Concept

Design

Misc

Docs

Required Changes

Purgeandfire :: 29 October 2014

Criteria (Systems):
  • Coding (20): efficiency and potential improvement.
  • Concept (10): originality and features.
  • Design (20): modularity, importability, ease of use, and method of attack.
  • Misc (10): test map, image, hive submission, misc.
  • Docs (10): documentation

Score:
  • Coding: 10 / 20
  • Concept: 7 / 10
  • Design: 14 / 20
  • Misc: 8 / 10
  • Docs: 5 / 10
  • Total Score: 44 / 70

Needs Fix :: see Required Changes for approval.

Current Rating
3 / 5
  • 65 - 70 becomes 5 / 5
  • 55 - 65 becomes 4 / 5
  • 40 - 55 becomes 3 / 5
  • 30 - 40 becomes 2 / 5
  • 0 - 30 becmes 1 / 5




  1. GetWorldBounds() leaks. You need to store it in a variable and then
    remove it manually (2 occurrences). [-3]
  2. Just a note, adding [13] does not do anything for the following line:
    private trigger array Handler[13]
    It does not change anything unless you use a size > 8192.
  3. filter2 should have a better name. [-1]
  4. In pickHelper, if I'm not mistaken, it seems like you are trying to
    choose the closest units and order them to help. In this case, it may
    be better to take advantage of Spinnaker's system and use:
    GetClosestNUnitsInRange
    It is designed to do what you need to do, and it will result in a
    significantly lighter computation. Note: You will have to change your
    entire loop to adapt to this change. [-2]
  5. This:
    set Real[R_TEMP_FACE] = GetRandomReal(0, 359.9) * bj_DEGTORAD
    Is equivalent to:
    sett Real[R_TEMP_FACE] = GetRandomReal(0, 2*bj_PI)
    Or you can use 6.28319 instead of 2*bj_PI.
  6. In onHit, you can move the assignment of 'dex' outside the blocks.
    (saves 1 line)
  7. In onDeath, you need to set u to null. [-1]
  8. If you really wanted to be extreme, you could redo this entire
    system using structs instead of hashtables. >:) [-2]
  9. Why use a trigger action for initUnit? (use a condition) [-1]

Score: 10 / 20

  1. Cool concept! I love the diverse features! It really shows effort.
  2. Creep engines are quite common though.
  3. It could be cool to have creep-specific modularity, e.g. creep
    specific sleep durations, so forth.
Score: 8 / 10


  1. The module design is not very good. Generally, the best design is where
    a user does not have to touch the system at all, and only needs to know
    its functions. Generally, it is better to separate example code from the
    library itself (usually in a different trigger).
  2. A second design flaw is that the data is all expected upon initialization.
  3. The setup functions have too generic names, e.g. ResetTimer or LowBound
  4. To fix issues 1-3, I recommend translating your setup over to a struct
    and adding a .register() method so the user can invoke registering a creep.
    JASS:
            struct SpecialCreep extends array
                private static group   cg  = CreateGroup()
                private static integer uid = 0
    
                method operator tamed= takes boolean tame returns nothing
                    call SaveBoolean(Hashtable, P_TAMED_BOOL, this, tame)
                endmethod
    
                method operator sleepDuration= takes real duration returns nothing
                    call SaveReal(Hashtable, P_SLEEP_DUR, this, duration)
                endmethod
    
                // etc...
    
                private static method registerCreeps takes nothing returns boolean
                    local unit f = GetFilterUnit()
                    if GetUnitTypeId(f) == thistype.uid then
                        // ... actions to register creeps ... 
    
                        // uid -> rawcode of creep being registered
                        // f -> creep being registered
                    endif  
                    set f = null 
                    return false
                endmethod
    
                method update takes nothing returns nothing
                    local rect r = GetWorldBounds()
                    set uid = this
                    call GroupEnumUnitsInRect(cg, r, Filter(function thistype.registerCreeps))
                    call RemoveRect(r)
                    set r = null
                endmethod
    
                static method get takes integer raw returns thistype
                    return raw
                endmethod
            endstruct
    This may be a little advanced. In this situation, we are treating
    the raw ID as the struct itself. With this, we do not even need to
    map the rawcodes to integers 1, 2, 3... etc. We can just use the
    rawcode for everything! In case this was confusing, maybe this will
    help:
    JASS:
                local SpecialCreep murloc = SpecialCreep.get('nmrl')
                set murloc.tamed = false
                set murloc.sleepDuration = 7.25
                set murloc.sleepTime = 21.0 
                // etc. 
                call murloc.update()
    Note: my code above would change a lot of the system. I recommend
    making these changes only if you understand the code 100%. But the
    advantage is it would allow you to update unit stats at any time by
    using .update()! That is awesome modularity.
  5. When you order a unit to patrol (via the MOVE order), won't it ignore
    enemy targets?
Score: 14 / 20

  1. The test map should have instructions on what to do, or some
    form of debriefing. It can either be in a message, a quest, etc. [-2]
Score: 8 / 10


  1. Needs documentation. It is preferrable to have it at the top of
    the code, and have a list of the functions that should be used.
    Ideally, a user should not have to look through your system at all
    in order to use your system.

    Add some descriptions to functions and such. See the JASS section
    for examples. It will make your code a lot more user-friendly. [-5]
Score: 5 / 10

  • Coding: #1, #4 (or explain if I read your code wrong), #7, #9
  • Design: #5 (need a response)
  • Misc: #1
 
forgot to mention
this system is not friendly for GUI user because you need a little basic JASS knowledge about filtering unit.

Found some bugs:
- dead units still ordered to attack
- unit is attacked event is buggy, I recommend everybody to change the event with any DDS. I can do nothing about this.
Bug Patching Help :
- check if unit is alive, if it's not, removed it from list.
- I recommend create several versions that suits all renown DDS (LFH and Bribe GUI DDS' are top priority since they're used a lot).
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
You should give examples on how to use dds. Also if you use spells on the units they won't attack you.
you are right.. :) but there are many choices of DDS, that's why I got a feeling to left it optional (using basic unit is attacked event)

The big con is the tremendous ForGroup loop, which runs every 0.03125 seconds.

I would appreciate it, if you would outline your code with comments.
Makes it much easier for me and others to read.
yeah, actualy using high accuracy doesn't really affects the performance lol, event using 0.5 is not noticeable..

300th post!!!

EDIT:

Updated to v1.2 with the following:
- Accuracy changed to a reasonable value
- Added some outlines
- Added with DDS version

credit's going to looking_for_help
 
Last edited:
The documentation should be included in the code, so when people copy it to their maps or to another website, it's there.

Also, inside CBS_InitLibrary I think there should be a wrapper forSaveIntegerto improve readability and not require people to specify a parent key (custom allocation).
This will also prevent users from using a different hashtable.

By default this system should probably require a DDS, because of how inaccurate the attack event is.
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
JASS:
function DistanceBetweenCoords takes real x,real y,real xt,real yt returns real
        local real dx = xt - x
        local real dy = yt - y
        
        return SquareRoot(dx * dx + dy * dy)
    endfunction
->
JASS:
function DistanceBetweenCoords takes real x,real y,real xt,real yt returns real
        return SquareRoot((xt-x)*(xt-x) + (yt-y)*(yt-y))
    endfunction

JASS:
if angle > 360 then
            loop
                set angle = angle - 360
                exitwhen (angle <= 360)
            endloop
        elseif angle < 0 then
            loop
                set angle = angle + 360
                exitwhen angle >= 0
            endloop
        endif
->
JASS:
if angle > 360. then
            set angle = ModuloReal(angle, 360)
        elseif angle < 0. then
            set angle = ModuloReal(-angle, 360)
        endif
:p

JASS:
function CBS_Filter2 takes nothing returns boolean
        local unit u = GetFilterUnit()
        local unit c = LoadUnitHandle(udg_CBS_Hash, 0, 0)
        local real a1
        local real a2
        local real rad
        local integer dex
        local player pa = Player(CBS_PassivePlayer())
        local player ag = Player(CBS_AggressivePlayer())
        local player p = GetOwningPlayer(u)
        
        set dex = LoadInteger(udg_CBS_Hash, 1911, GetUnitTypeId(c))
        set a1 = GetUnitFacing(c) + 360.0
        set a2 = AngleBetweenCoords(GetUnitX(c),GetUnitY(c),GetUnitX(u),GetUnitY(u)) + 360.0
        // Load sight radius
        set rad = LoadReal(udg_CBS_Hash, 5511, dex) * 0.5
        // Filter out target not in sight radius
        if (a2 <= a1 - rad or a2 >= a1 + rad) and (a2 <= a1 - rad - 360.0 or a2 >= a1 + rad - 360.0) then
            return false
        endif
        if p == pa or p == ag then
            return false
        endif
        return IsUnitVisible(u, pa) and IsUnitVisible(u, ag) and not IsUnitType(u, UNIT_TYPE_DEAD)
    endfunction
This function leak ^^'
I suggest storing the result into a boolean, null every local handle and at least return the boolean.

JASS:
function CBS_HelpFilter takes nothing returns boolean
        local unit u = GetFilterUnit()
        local integer ut = GetUnitTypeId(u)
        local integer dex = LoadInteger(udg_CBS_Hash, 1911, ut)
        local integer ht = LoadInteger(udg_CBS_Hash, 7550, dex)
        
        if ht == 1 then
            if ut != udg_CBS_DuTy then
                return false
            endif
        else
            if ht < 1 then
                return false
            endif
        endif
        return GetOwningPlayer(u) == Player(CBS_PassivePlayer()) and not LoadBoolean(udg_CBS_Hash, 2343, dex) 
    endfunction
Same there.

if GetWidgetLife(u) > .405 then isn't enough to check if a unit is dead :
if GetWidgetLife(u) > .405 and not IsUnitType(u, UNIT_TYPE_DEAD)


JASS:
local player pa = Player(CBS_PassivePlayer())
        
        if GetOwningPlayer(a) == Player(CBS_AggressivePlayer()) then
->
JASS:
local player pa = Player(CBS_PassivePlayer())
        
        if GetOwningPlayer(a) == pa then


JASS:
else
                if not CBS_ColorBool() then
                    call SetUnitColor(u, GetPlayerColor(ag))
                endif
->
JASS:
elseif not CBS_ColorBool() then
    ....
endif


JASS:
call SetPlayerAllianceStateBJ(ag, p, bj_ALLIANCE_UNALLIED)
                // Passive player threats other players as allies
                call SetPlayerAllianceStateBJ(pa, p, bj_ALLIANCE_ALLIED)
Bjs needed ?


call TriggerRegisterEnterRectSimple(t, GetWorldBounds())
This one can be avoid and it leaks :/
 
JASS:
private function AngleBetweenCoords takes real x, real y, real xt, real yt returns real
        local real angle = Atan2((yt - y), (xt - x)) * bj_RADTODEG
       
        if angle > 360 then
            loop
                set angle = angle - 360
                exitwhen (angle <= 360)
            endloop
        elseif angle < 0 then
            loop
                set angle = angle + 360
                exitwhen angle >= 0
            endloop
        endif
        return angle
    endfunction
This is ok, but I would just do:
JASS:
private function AngleBetweenCoords takes real x, real y, real xt, real yt returns real
    return Atan2((yt - y), (xt - x)) * bj_RADTODEG
endfunction
If you keep yours (not wrong) change the first check "if angle > 360 then" to if equal or bigger. (because if you have 360 you want to get it 0)
JASS:
private function filter2 takes nothing returns boolean
        local unit u = GetFilterUnit()
        local unit c = LoadUnitHandle(HASH, 0, 0)
        local real a1
        local real a2
        local real rad
        local integer dex
        local player pa = Player(PASSIVE)
        local player ag = Player(AGGRESSIVE)
        local player p = GetOwningPlayer(u)
        local boolean b = true
       
        set dex = LoadInteger(HASH, 1911, GetUnitTypeId(c))
        set a1 = GetUnitFacing(c) + 360.0
        set a2 = AngleBetweenCoords(GetUnitX(c),GetUnitY(c),GetUnitX(u),GetUnitY(u)) + 360.0
        set rad = LoadReal(HASH, 5511, dex) * 0.5
        if (a2 <= a1 - rad or a2 >= a1 + rad) and (a2 <= a1 - rad - 360.0 or a2 >= a1 + rad - 360.0) then
            set b = false
        endif
        if (p == pa or p == ag) or IsUnitType(u, UNIT_TYPE_DEAD) then
            set b = false
        endif
        if not IsUnitVisible(u, pa) and not IsUnitVisible(u, ag) then
            set b = false
        endif
        set u = null
        set c = null
        return b
    endfunction
Use locols for UnitX/Y since you make 2 function calls.
Atm you do 3 checks, even b would be FALSE already after first one. You could put further checks in 'else' of first 'if' to prevent useless checks. ;)

Same with move further 'if's' in 'else' of first 'if' to prevent needless checks for
JASS:
 private function helpfilter

Then soon stopped reading code carefully because Im too lazy to read this all :d

But in "private function ini takes nothing returns nothing" you forgot to null local region.

And does your system work if I want to have more than 1 AGGRESIVE and 1 PASSIVE player?

And for me idc, but you have to change variable names. Only capital letters --> only constants. Start normal variables with lowcase and each new word with capital letter.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
And does your system work if I want to have more than 1 AGGRESIVE and 1 PASSIVE player?
you want it for what? C:

Use locols for UnitX/Y since you make 2 function calls.
I use it only once

If you keep yours (not wrong) change the first check "if angle > 360 then" to if equal or bigger. (because if you have 360 you want to get it 0)
But in "private function ini takes nothing returns nothing" you forgot to null local region.
okay, thnks!

And for me idc, but you have to change variable names. Only capital letters --> only constants. Start normal variables with lowcase and each new word with capital letter.
can you give me example? I don't understand that part :)
thnks for the review..
 
you want it for what? C:
I dont need it, just wanted to know :p

I use it only once
Oh yo, one time 'c' and other one is 'u'. You're right

can you give me example? I don't understand that part :)
thnks for the review..

private constant string NAME : good
private constant string name : not good

private string MAPNAME : not good
private string mapName : good
 
Last edited:
Level 18
Joined
Sep 14, 2012
Messages
3,413
Remember that if you use only one time the value in the function there is no need to use a local :) I didn't check the code on this criteria but seemed good.

JASS:
set dex = LoadInteger(HASH, 1911, GetUnitTypeId(c))
        set a1 = GetUnitFacing(c) + 360.0
        set a2 = AngleBetweenCoords(GetUnitX(c),GetUnitY(c),GetUnitX(u),GetUnitY(u)) + 360.0
        set rad = LoadReal(HASH, 5511, dex) * 0.5
Could be done on local declaration :D

JASS:
local unit u = gettarget()
        local unit a = getsource()
Set the values of those local inside the if block and put the nulling of those local in the if block too :)

JASS:
local player pa = Player(PASSIVE)
        
        if GetOwningPlayer(a) == Player(AGGRESSIVE) then
->
JASS:
local player pa = Player(PASSIVE)
        
        if GetOwningPlayer(a) == pa then

You forgot to use call RemoveRect(r) in the init function :p


JASS:
if GetUnitCurrentOrder(u) != OrderId("move") and GetUnitX(u) == LoadReal(HASH, hand, 9) and GetUnitY(u) == LoadReal(HASH, hand, 10) then
            return false
        endif
        return true
->
return not(GetUnitCurrentOrder(u) != OrderId("move") and GetUnitX(u) == LoadReal(HASH, hand, 9) and GetUnitY(u) == LoadReal(HASH, hand, 10))
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
I recommend not using ForGroup at all. Use a FirstOfGroup loop or put units into an indexed array.
This is because ForGroup gets crazily expensive with lots of units. Tests show that it's about on par with FirstOfGroup with around 3-4 units. After that the performance has dropped drastically.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
I dont understand what are you gays talking about x.X

what's FoG? I never use it before

Basically FirstOfGroup (FoG) is just a function. It gives you 1 unit from a unit group.
If you wish to use groups, then do it like this:

JASS:
function stuff takes group g returns nothing
    local group backup = CreateGroup()
    local unit u
    loop
        set u = FirstOfGroup(g)
        exitwhen u == null
        call GroupAddUnit(backup,u)
        //Do something with unit
        call GroupRemoveUnit(g,u)
    endloop
    call DestroyGroup(g)
    set g = backup
    set backup = null
    set u = null
endfunction

This piece of code assumes that you want to keep the group intact.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
thanks, I will change it then :D
wait, are you sure this works
set g = backup
?

EDIT:
it works!
but strange, when I destroy the backup it stops working
call DestroyGroup(bu)
if I dont destroy it, will that causes leak?

Updated to v1.9
- Added 2 new APIs
- Improved loops
- Fixed another sleep bug
- Many improvements (credit's going to Malhorne and xonok)
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
thanks, I will change it then :D
wait, are you sure this works
set g = backup
?

EDIT:
it works!
but strange, when I destroy the backup it stops working
call DestroyGroup(bu)
if I dont destroy it, will that causes leak?

Updated to v1.9
- Added 2 new APIs
- Improved loops
- Fixed another sleep bug
- Many improvements (credit's going to Malhorne and xonok)

Well, you have to keep in mind that you're not destroying a variable, but an object.
A unit group is an object, so if you do something like:
local group a = CreateGroup
local group b = a
local group c = b

Then you have 1 group and 3 variables.
Thus, to clear them you have to destroy the group once (through any of them, but only 1) and null all 3 of them.
 
Last edited:
It seems to look useful... Though sadly I think most systems like this gets not much exposure from the users it was actually intended for...

Heck, my super now useless, inferior, maybe not so optimized missile system has more than 2x downloads than my really useful systems...


and maybe because less and less people mod nowadays...
 
If you read the first part of the greened text, it says, FOR EXAMPLE... IDK what part of the word EXAMPLE you don't understand...

and the next one too, YOU CAN

So you see, I said EXAMPLE and CAN... I never said YOU DID

and in case you missed my second line: "As for usability, IDK... haven't look closely at the code so..."
Logically, how can I say that your system is hard to implement when I haven't looked closely at it?
 
Level 8
Joined
Feb 3, 2013
Messages
277
sir this is excellent, maybe you can add some spells ai funcs.

spells
targetable (disable, nukes, ally buff, aoe disable, aoe buff, aoe disable, aoe damage)
immediate (self buff, aoe damage, aoe buff, aoe disable)
point (etc..)

like
function addTargetableSpell takes integer unitID, integer abilityID, integer castType, real time, integer chance returns nothing

let the time proc and when it hits 0, there's a chance for a unit to cast a spell with different controls per castType or something like

function addRetaliationSpell takes blah blah balh

every time the unit is damaged, there's a chance to proc this spell blah blah blah.

once again lovely script +rep
 
Top