Detachments v1.2.0.0

This system is a combination of concepts from games Battle for Middle Earth and Company of Heroes.
It allows users to group unit types into lined up detachments or squads.
Every detachment is led by a leader and its members follow him/her unquestionably. Direct order issues to detachment members are rejected and the only way to control them is by controlling the leader so it acts as a whole group.
When near friendly building, the leader can reinforce the detachment by replacing missing members.
If the leader dies, his/her role is taken over by the first member available.

Code

Credits

How to use

JASS:
library Detachments /* v1.2.0.0
****************************************************************************************
*                         
*                     Detachments by Garfield1337
*
*    Allows grouping unit types into lined up detachments or squads.
*
****************************************************************************************
*
*    */uses/*
*        */ AutoIndex               /*    wc3c.net/showthread.php?t=105643
*        */ Table                   /*    hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*        */ SimError                /*    wc3c.net/showthread.php?t=101260
*        */ TimerUtils              /*    wc3c.net/showthread.php?t=101322
*        */ II                      /*    hiveworkshop.com/forums/pastebin.php?id=x178a8
*    */optional/*
*        */ RegisterPlayerUnitEvent /*    hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*
****************************************************************************************
*
*    How to use:
*    ---------- 
*
*    For each detachment type...
*    - Create a leading and a member unit type (possibly based on same unit, but using different model.)
*    - Give member units ward classification.
*    - Create a spell for transforming leader into member using bear form, you can copy the spell from this map.
*      In this spell, change the fields "Alternate Form Unit" and "Normal Form Unit" to member and leader unit type respectively.
*    - Create a spell for reinforcing the detachment, you can copy the spell from this map.
*      Other than aesthetics and text fields, nothing needs change here.
*    - Create a spell for canceling training, you can copy the spell from this map.
*      Same as for reinforcing, there are no important field changes here.
*    - Initialize detachments using functions from the API.
*
*    Tips:
*    - When the leader dies, a member turns into a leader and takes over. For balance, leader and members should have
*      same or similar stats.
*    - Giving detachment units smaller collision than usual will help them move more freely and not bump one into another.
*    - Members mimic the leader; if the leader casts a spell, they will too if they have it.
*
*    API:
*    ----
*
*    To set up a detachment type, call the following function (Soldiers are detachment members.):
*    
*    function InitDetachment takes integer LeaderRawCode, integer SoldierRawCode, integer ReinforceRawcode,
*       integer TransformRawcode, integer SoldierGoldCost, integer SoldierLumberCost, integer SoldierFoodCost, integer TrainTime
*        
*    You may optionally alter detachment size settings with the following function,
*    otherwise the detachment will use default values:
*    
*    function SetDetachmentSize takes integer LeaderRawCode, integer MaximumMemberCount, integer SoldiersPerRow
*                                     real VerticalSpacing*, real HorizontalSpacing*
*
*                                     *Spacing determines distance between soldiers
*
****************************************************************************************
*
*                                     Settings
*/
    globals
        private constant integer CANCEL                     = 'A004' //Rawcode of the cancel ability.
        private constant string BAR                         = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
        private constant string BAR_COLOR                   = "|c00006988" //Color code for the training bar.
        private constant real TRAIN_RANGE                   = 600.0 //Range from the nearest friendly building at which a unit will finish training.
        private constant real ARRIVAL_RANGE                 = 200.0 //How close will the newly trained unit to the leader.
        private constant real WANDER_DISTANCE               = 900.0 //How far may a unit go away from the leader before automatically going back.
        private constant integer DEFAULT_SIZE               = 12 //Default detachment size; can be changed per detachment on initialization.
        private constant integer DEFAULT_SOLDIERS_PER_ROW   = 4 //Member count in every row
        private constant real DEFAULT_VERTICAL_SPACING      = 150.0 //Distance between soldiers; Vertical is away from leader.
        private constant real DEFAULT_HORIZONTAL_SPACING    = 140.0 //Distance between soldiers; Horizontal is between soldiers in a row.
        private constant string ERROR_DETACHMENT_FULL       = "Detachment is full." //Error messages
        private constant string ERROR_INSUFFICIENT_GOLD     = "Not enough gold."
        private constant string ERROR_INSUFFICIENT_LUMBER   = "Not enough lumber."
        private constant string ERROR_INSUFFICIENT_FOOD     = "Not enough food."
        private group enumGroup                             = CreateGroup() //Group used for dynamic enumeration
    endglobals                                                              //If there is a global group with such role present in map, assign it here

/****************************************************************************************/
    
    globals
        private TableArray detachmentData
        private constant real BAR_CONSTANT = StringLength(BAR)/32.00
    endglobals
    
    function InitDetachment takes integer leader, integer soldier, integer reinforce, integer transform, integer gold, integer lumber, integer food, integer time returns nothing
        //Saving detachment type data into table
        set detachmentData[0].boolean[leader] = true
        set detachmentData[1].boolean[soldier] = true
        set detachmentData[2].boolean[reinforce] = true
        set detachmentData[0][leader] = soldier
        set detachmentData[1][leader] = transform
        set detachmentData[2][leader] = gold
        set detachmentData[3][leader] = lumber
        set detachmentData[4][leader] = food
        set detachmentData[5][leader] = time
        
        set detachmentData[6][leader] = DEFAULT_SIZE
        set detachmentData[7][leader] = DEFAULT_SOLDIERS_PER_ROW
        set detachmentData[0].real[leader] = DEFAULT_VERTICAL_SPACING
        set detachmentData[1].real[leader] = DEFAULT_HORIZONTAL_SPACING
    endfunction
    
    function SetDetachmentSize takes integer leader, integer size, integer row, real vS, real hS returns nothing
        //Additional detachment settings
        set detachmentData[6][leader] = size
        set detachmentData[7][leader] = row
        set detachmentData[0].real[leader] = vS
        set detachmentData[1].real[leader] = hS
    endfunction
    
    //Functions for retrieving detachment data
    
    private function isLeader takes unit u returns boolean
        return detachmentData[0].boolean[GetUnitTypeId(u)]
    endfunction
    
    private function isSoldier takes unit u returns boolean
        return detachmentData[1].boolean[GetUnitTypeId(u)]
    endfunction
    
    private function isReinforce takes integer a returns boolean
        return detachmentData[2].boolean[a]
    endfunction
    
    private function soldierCode takes integer l returns integer
        return detachmentData[0][l]
    endfunction
    
    private function transformSoldier takes unit s, integer l returns nothing
        call UnitAddAbility(s, detachmentData[1][l])
        call UnitRemoveAbility(s, detachmentData[1][l])
    endfunction
    
    private function soldierGoldCost takes integer l returns integer
        return detachmentData[2][l]
    endfunction
    
    private function soldierLumberCost takes integer l returns integer
        return detachmentData[3][l]
    endfunction
    
    private function soldierFoodCost takes integer l returns integer
        return detachmentData[4][l]
    endfunction
    
    private function soldierTrainTime takes integer l returns integer
        return detachmentData[5][l]
    endfunction
    
    private function detachmentSize takes integer l returns integer
        return detachmentData[6][l]
    endfunction
    
    private function detachmentSPR takes integer l returns integer
        return detachmentData[7][l]
    endfunction
    
    private function vSpacing takes integer l returns real
        return detachmentData[0].real[l]
    endfunction
    
    private function hSpacing takes integer l returns real
        return detachmentData[1].real[l]
    endfunction
    
    private keyword Leader
    
    private struct Soldier
        //Soldier struct instances are attached to members of each detachment
        
        //It holds pointers to next and previous soldiers in the linked list and its parent Leader struct
        thistype next = 0
        thistype prev = 0
        Leader leader
        
        //These fields are used to prevent orders issued by player and resume previous one
        integer orderId = OrderId("stop")
        boolean orderPoint = false
        real orderX = 0.00
        real orderY = 0.00
        widget orderTarget = null
        static boolean checkOrder = true //If true, the orders will be resumed (prevents infinite loops)
        
        static method order takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            set .checkOrder = false
            call IssueImmediateOrderById(.me, .orderId)
            set .checkOrder = true
            call ReleaseTimer(GetExpiredTimer())
        endmethod
        
        method resumeOrder takes nothing returns nothing
            set .checkOrder = false
            if .orderPoint then
                call IssuePointOrderById(.me, .orderId, .orderX, .orderY)
            elseif .orderTarget != null then
                call IssueTargetOrderById(.me, .orderId, .orderTarget)
            else
                call TimerStart(NewTimerEx(this), 0.00, false, function thistype.order)
            endif
            set .checkOrder = true
        endmethod
        
        static method create takes unit s, Leader l returns thistype
            local thistype this = thistype.allocate()
            set .me = s
            set .leader = l
            return this
        endmethod
        
        //AutoData is neeeded to attach structs to units
        implement AutoData
        
        method remove takes nothing returns nothing
            //On removal, the two adjacent nodes in the list are joined together
            set .next.prev = .prev
            set .prev.next = .next
            set .leader.memberCount = .leader.memberCount - 1
            if this == .leader.first then
                //If the dying member is first in the list, the next member becomes first
                set .leader.first = .next
            endif
            call .destroy()
        endmethod
        
    endstruct

    private struct Leader
        //Leader struct instances are attached to leading units
        
        integer memberCount = 0
        Soldier first = 0 //First member in the list from which the iteration begins
        
        //Fields used during training
        boolean training = false
        texttag tag
        integer trainCount = 0
        string bar
        string filled
        real trainSpeed
        real trainProgress = 0.00
        
        //Although these can be retrieved anytime, since they're used in calculations, for optimization, they're saved here
        integer spr
        real vDistance
        real hDistance
        
        static method createFilter takes unit u returns boolean
            return isLeader(u) //Unit of leading type that enter map are automatically assigned a Leader struct instance
        endmethod
        
        method iterate1 takes nothing returns nothing
            //This method is executed every second and is used to check if a member wanders far off
            local Soldier soldier = .first //Starting for first soldier in the list
            local real lx = GetUnitX(.me)
            local real ly = GetUnitY(.me)
            local real a
            loop
                exitwhen soldier == 0 //When hitting the end of the list, the loop finishes
                if not IsUnitInRange(soldier.me, .me, WANDER_DISTANCE) then
                    //If the member is too far, he/she is issued an order to go back
                    set a = Atan2(GetUnitY(soldier.me) - ly, GetUnitX(soldier.me) - lx)
                    set Soldier.checkOrder = false //To prevent infinite loop
                    set soldier.orderId = OrderId("move")
                    set soldier.orderPoint = true
                    set soldier.orderX = lx + ARRIVAL_RANGE*Cos(a)
                    set soldier.orderY = ly + ARRIVAL_RANGE*Sin(a)
                    set soldier.orderTarget = null
                    call IssuePointOrderById(soldier.me, soldier.orderId, soldier.orderX, soldier.orderY)
                    set Soldier.checkOrder = true
                endif
                set soldier = soldier.next
            endloop
        endmethod
        
        static method create takes unit u returns thistype
            local thistype this = thistype.allocate()
            local integer id = GetUnitTypeId(u)
            set .spr = detachmentSPR(id)
            set .vDistance = vSpacing(id)
            set .hDistance = hSpacing(id)
            set .trainSpeed = BAR_CONSTANT/soldierTrainTime(id)
            call .start1() //Starts the one second interval iteration to prevent wandering
            return this
        endmethod
        
        static player owner
        static unit building
        static real distance
        static thistype dis
        
        static method filter takes nothing returns boolean
            //This method is used in a unit enumeration to find the nearest friendly building
            local unit f = GetFilterUnit()
            local real x
            local real y
            local real d
            local thistype this = .dis
            if IsUnitType(f, UNIT_TYPE_STRUCTURE) and IsUnitAlly(f, .owner) then
                set x = GetUnitX(.me) - GetUnitX(f)
                set y = GetUnitY(.me) - GetUnitY(f)
                set d = SquareRoot(x*x + y*y) 
                if d < .distance then
                    set .building = f
                    set .distance = d
                endif
            endif
            set f = null
            return false
        endmethod
        
        method iterate32 takes nothing returns nothing
            //This method is executed 32 times a second and handles training bars
            local Soldier new
            local real a
            call SetTextTagPos(.tag, GetUnitX(.me) - 100.0, GetUnitY(.me), GetUnitFlyHeight(.me) + 160.0)
            if .bar == "" then //When the bar finishes, the training is done
                set .owner = GetOwningPlayer(.me)
                set .building = null
                set .distance = TRAIN_RANGE + 100.0
                set .dis = this
                //Finding nearest friendly building
                call GroupEnumUnitsInRange(enumGroup, GetUnitX(.me), GetUnitY(.me), TRAIN_RANGE, Filter(function thistype.filter))
                if .building != null then //If there is one
                    //A unit is trained
                    set .trainCount = .trainCount - 1
                    call SetPlayerState(.owner, PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(.owner, PLAYER_STATE_RESOURCE_FOOD_USED) - soldierFoodCost(GetUnitTypeId(.me)))
                    set new = Soldier.create(CreateUnit(.owner, soldierCode(GetUnitTypeId(.me)), GetUnitX(.building), GetUnitY(.building), 270.0), this)
                    //This unit becomes the first in the list
                    set new.next = .first
                    set new.prev = 0
                    set .first.prev = new
                    set .first = new
                    set .memberCount = .memberCount + 1
                    set a = Atan2(GetUnitY(new.me) - GetUnitY(.me), GetUnitX(new.me) - GetUnitX(.me))
                    set Soldier.checkOrder = false
                    //Unit is issued to move close to the leader
                    set new.orderId = OrderId("move")
                    set new.orderPoint = true
                    set new.orderX = GetUnitX(.me) + ARRIVAL_RANGE*Cos(a)
                    set new.orderY = GetUnitY(.me) + ARRIVAL_RANGE*Sin(a)
                    call IssuePointOrderById(new.me, new.orderId, new.orderX, new.orderY)
                    set Soldier.checkOrder = true
                    if .trainCount == 0 then
                        //If all the training is done, the periodic execution is stopped and bar removed
                        call .stop32()
                        call DestroyTextTag(.tag)
                        set .training = false
                    else
                        //Otherwise it starts a new bar
                        set .bar = BAR
                        set .filled = "|r"
                        set .trainProgress = 0.00
                        call SetTextTagText(.tag, BAR_COLOR+I2S(.trainCount)+" : "+.filled+.bar, 0.021)
                    endif
                endif
            else
                //The training bar consists of two strings, a filled bar (.filled) and empty, original bar (.bar)
                //Progress consists of extending the filled bar and shortening the original one
                set .trainProgress = .trainProgress + .trainSpeed
                if .trainProgress >= 1.00 then
                    set .trainProgress = 0.00
                    set .filled = SubString(.filled, 0, StringLength(.filled) - 2) + "||" + "|r"
                    set .bar = SubString(.bar, 0, StringLength(.bar) - 2)
                    call SetTextTagText(.tag, BAR_COLOR+I2S(.trainCount)+" : "+.filled+.bar, 0.021)
                endif
            endif
        endmethod
        
        //Modules for attaching instances and periodic iterations
        implement AutoCreate
        implement II32
        implement II1
        
        method queue takes nothing returns nothing
            //Queueing a unit for training
            set .trainCount = .trainCount + 1
            if not .training then
                //If there are no other units being trained, create the bar for it
                set .training = true
                set .tag = CreateTextTag()
                set .bar = BAR
                set .filled = "|r"
                set .trainProgress = 0.00
                call SetTextTagPos(.tag, GetUnitX(.me) - 100.0, GetUnitY(.me), GetUnitFlyHeight(.me) + 160.0)
                call SetTextTagVisibility(.tag, GetLocalPlayer() == GetOwningPlayer(.me))
                call .start32()
            endif
            call SetTextTagText(.tag, BAR_COLOR+I2S(.trainCount)+" : "+.filled+.bar, 0.021)
        endmethod
        
        method cancel takes nothing returns nothing
            //Canceling a training unit
            local player p
            local integer id
            if .training then
                set p = GetOwningPlayer(.me)
                set id = GetUnitTypeId(.me)
                //Returning the resources back to player
                call SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) + soldierGoldCost(id))
                call SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER) + soldierLumberCost(id))
                call SetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED) - soldierFoodCost(id))
                set p = null
                set .trainCount = .trainCount - 1
                if .trainCount == 0 then
                    //If that was the last unit, stop the periodic execution and remove the bar
                    call .stop32()
                    call DestroyTextTag(.tag)
                    set .training = false
                else
                    //Otherwise start with next unit
                    set .bar = BAR
                    set .filled = "|r"
                    set .trainProgress = 0.00
                    call SetTextTagText(.tag, BAR_COLOR+I2S(.trainCount)+" : "+.filled+.bar, 0.021)
                endif
            endif
        endmethod
        
        method replace takes nothing returns nothing
            //When the leader dies, a replacement has to be found
            local Soldier newLeader = .first //The first available member is picked
            if newLeader == 0 then //If the list is empty
                //Stop any training and running methods and destroy the instance
                if .training then
                    call .stop32()
                    call DestroyTextTag(.tag)
                endif
                call .stop1()
                call .destroy()
            else
                //If there is an available member, then transform it into leading unit type
                call transformSoldier(newLeader.me, GetUnitTypeId(.me))
                //Assign the Leader instance to the new leader
                set .me = newLeader.me
                set .first = newLeader.next //The next member becomes the new first
                call newLeader.remove() //Removing Soldier instance attached to the new leader
            endif
        endmethod
        
        static method train takes nothing returns boolean
            //Issuing training for a detachment
            local thistype this
            local integer id
            local player p
            //Checking if the casted ability is one of the reinforcing abilities
            if isReinforce(GetSpellAbilityId()) then
                set this = thistype[GetTriggerUnit()]
                set id = GetUnitTypeId(.me)
                set p = GetTriggerPlayer()
                //Inspecting the requirements and whether they are fulfilled
                if .memberCount + .trainCount < detachmentSize(id) then
                    if GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) >= soldierGoldCost(id) then
                        if GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER) >= soldierLumberCost(id) then
                            if GetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_CAP) - GetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED) >= soldierFoodCost(id) then
                                //After all the checks, the resources are being deducted
                                call SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) - soldierGoldCost(id))
                                call SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER) - soldierLumberCost(id))
                                call SetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(p, PLAYER_STATE_RESOURCE_FOOD_USED) + soldierFoodCost(id))
                                //And a unit is queued for training
                                call .queue()
                            else
                                call SimError(p, ERROR_INSUFFICIENT_FOOD)
                            endif
                        else
                            call SimError(p, ERROR_INSUFFICIENT_LUMBER)
                        endif
                    else
                        call SimError(p, ERROR_INSUFFICIENT_GOLD)
                    endif
                else
                    call SimError(p, ERROR_DETACHMENT_FULL)
                endif
                set p = null
            elseif GetSpellAbilityId() == CANCEL then
                //If the casted ability was cancel, call the cancel method
                call thistype[GetTriggerUnit()].cancel()
            endif
            return false
        endmethod
        
        static method death takes nothing returns boolean
            //If a dying unit is a leader or a soldier, corresponding procedures are performed
            if isSoldier(GetTriggerUnit()) then
                call Soldier[GetTriggerUnit()].remove()
            elseif isLeader(GetTriggerUnit()) then
                call thistype[GetTriggerUnit()].replace()
            endif
            return false
        endmethod
        
        static method order takes nothing returns boolean
            //Mimicking leader's immediate orders
            local thistype this
            local Soldier soldier
            local integer id
            if isLeader(GetTriggerUnit()) then //If the issued unit is a leader
                set this = thistype[GetTriggerUnit()]
                if .memberCount > 0 then
                    set id = GetIssuedOrderId()
                    set soldier = .first
                    set Soldier.checkOrder = false
                    loop
                        exitwhen soldier == 0
                        //Issue the same order to every member
                        if IssueImmediateOrderById(soldier.me, id) then
                            //Issuing defend order alone interrupts previous orders so it has to be resumed
                            if id == OrderId("defend") or id == OrderId("undefend") then
                                call soldier.resumeOrder()
                                //Resuming order sets .checkOrder to true so it has to be set back to false
                                set Soldier.checkOrder = false
                            else
                                //If it wasn't a defend order, the standard procedure of saving issued order is carried out
                                set soldier.orderId = id
                                set soldier.orderPoint = false
                                set soldier.orderTarget = null
                            endif
                        endif
                        set soldier = soldier.next
                    endloop
                    set Soldier.checkOrder = true
                endif
            elseif isSoldier(GetTriggerUnit()) and Soldier.checkOrder then
                //If the issued unit was soldier and the order did not come from the leader, the previous order is resumed
                call Soldier[GetTriggerUnit()].resumeOrder()
            endif
            return false
        endmethod
        
        static method orderPoint takes nothing returns boolean
            //Mimicking leader's point orders
            local thistype this
            local Soldier soldier
            local integer id
            local integer i
            local integer rows
            local real leaderx
            local real leadery
            local real facing
            local real x
            local real y
            local real d
            local real a
            local integer c
            if isLeader(GetTriggerUnit()) then
                set this = thistype[GetTriggerUnit()]
                if .memberCount > 0 then
                    set id = GetIssuedOrderId()
                    //Caching order coordinates and angle towards it
                    set leaderx = GetOrderPointX()
                    set leadery = GetOrderPointY()
                    set facing = Atan2(leadery - GetUnitY(.me), leaderx - GetUnitX(.me))
                    set soldier = .first
                    set rows = (.memberCount + (.spr - 1)) / .spr //Getting number of rows (.spr - soldiers per row)
                    set i = 1
                    set Soldier.checkOrder = false
                    loop
                        //Iterating every row
                        exitwhen i > rows
                        set x = i*.vDistance //Vertical distance to the row
                        //Getting the number of soldiers in current row
                        if i == rows then //If it's the last row
                            //Then the number needs to be calculated
                            set c = .memberCount - (rows - 1)*.spr
                        else
                            //If it's not last row, then the number is standard
                            set c = .spr
                        endif
                        set y = (c - 1)*.hDistance/2 //Distance from the first soldier's position in the row to the middle of the row
                        set d = SquareRoot(x*x + y*y) //Distance between the leader and the soldier's position
                        set a = Atan2(y, x) + facing + bj_PI //Angle between the leader and the soldier's position
                        //Coordinates of the first soldier in the row
                        set x = leaderx + d*Cos(a)
                        set y = leadery + d*Sin(a)
                        set a = facing + bj_PI/2 //Row angle, used for lining soldiers up
                        loop
                            //Iterating row's soldiers and their positions
                            set c = c - 1
                            //Saving issued order and giving it to the soldiers
                            set soldier.orderPoint = true
                            set soldier.orderX = x + c*.hDistance*Cos(a)
                            set soldier.orderY = y + c*.hDistance*Sin(a)
                            set soldier.orderTarget = null
                            if IssuePointOrderById(soldier.me, id, soldier.orderX, soldier.orderY) then
                                set soldier.orderId = id
                            else
                                call IssuePointOrderById(soldier.me, OrderId("smart"), soldier.orderX, soldier.orderY)
                                set soldier.orderId = OrderId("smart")
                            endif
                            set soldier = soldier.next
                            exitwhen c == 0
                        endloop
                        set i = i + 1
                    endloop
                    set Soldier.checkOrder = true
                endif
            elseif isSoldier(GetTriggerUnit()) and Soldier.checkOrder then
                call Soldier[GetTriggerUnit()].resumeOrder()
            endif
            return false
        endmethod
        
        static method orderTarget takes nothing returns boolean
            //Mimicking leader's target orders
            local thistype this
            local Soldier soldier
            local integer id
            local widget target
            if isLeader(GetTriggerUnit()) then
                set this = thistype[GetTriggerUnit()]
                if .memberCount > 0 then
                    set id = GetIssuedOrderId()
                    set target = GetOrderTarget()
                    set soldier = .first
                    set Soldier.checkOrder = false
                    loop
                        exitwhen soldier == 0
                        //Saving leader's order and issuing it to members
                        set soldier.orderPoint = false
                        set soldier.orderTarget = target
                        if IssueTargetOrderById(soldier.me, id, target) then
                            set soldier.orderId = id
                        else
                            call IssueTargetOrderById(soldier.me, OrderId("smart"), target)
                            set soldier.orderId = OrderId("smart")
                        endif
                        set soldier = soldier.next
                    endloop
                    set Soldier.checkOrder = true
                    set target = null
                endif
            elseif isSoldier(GetTriggerUnit()) and Soldier.checkOrder then
                call Soldier[GetTriggerUnit()].resumeOrder()
            endif
            return false
        endmethod
        
        static method onInit takes nothing returns nothing
            static if LIBRARY_RegisterPlayerUnitEvent then
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.train)
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.death)
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function thistype.order)
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function thistype.orderPoint)
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.orderTarget)
            else
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Condition(function thistype.train))
                set t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
                call TriggerAddCondition(t, Condition(function thistype.death))
                set t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
                call TriggerAddCondition(t, Condition(function thistype.order))
                set t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
                call TriggerAddCondition(t, Condition(function thistype.orderPoint))
                set t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
                call TriggerAddCondition(t, Condition(function thistype.orderTarget))
                set t = null
            endif
        endmethod
        
    endstruct
    
    private module initModule
        private static method onInit takes nothing returns nothing
            //Initializing table and soldier linked list end
            set detachmentData = TableArray[8]
            set Soldier(0).next = 0
            set Soldier(0).prev = 0
        endmethod
    endmodule
    private struct initStruct
        implement initModule
    endstruct

endlibrary
This system uses following external resources:
  • Copy the pages containing the system code and code used by the system. (Found in the map; also listed in system code under the "uses" and "optional" keywords.)
  • Follow the instructions given in the "How to use" section of system code and adjust the constants as needed.
  • Use functions from the API to set your detachments up. (If you're a GUI user, read the coming text)

If you're using GUI, you can use functions to setup detachments with Custom scripts.
In a trigger that runs on initialization, for each detachment type add this Custom script:
  • Custom script: call InitDetachment(udg_LeaderRawCode, udg_SoldierRawCode, udg_ReinforceRawcode, udg_TransformRawcode, udg_SoldierGoldCost, udg_SoldierLumberCost, udg_SoldierFoodCost, udg_TrainTime)
To simplify this, instead of editing the actual Custom script, create these variables in the variable editor:
  • LeaderRawCode (unit type, assign it the leader unit type)
  • SoldierRawCode (unit type, same as previous, but soldier unit type)
  • ReinforceRawCode (ability, assign it the Reinforce ability)
  • TransformRawcode (ability, same as previous, but transform ability)
  • SoldierGoldCost (integer, the gold cost of a soldier)
  • SoldierLumberCost
  • SoldierFoodCost
  • TrainTime (integer)
Before the Custom script, set the values of these variables.

There is another function you can use, this one lets you change maximum member count per detachment, soldiers per row and spacing between soldiers:
  • Custom script: call SetDetachmentSize(udg_LeaderRawCode, udg_MaximumMemberCount, udg_SoldiersPerRow, udg_VerticalSpacing, udg_HorizontalSpacing)
Variables:
  • LeaderRawCode (unit type, the one used in previous function, it is used to determine which detachment type you're changing)
  • MaximumMemberCount (integer)
  • SoldiersPerRow (integer)
  • VerticalSpacing (real, space between rows)
  • HorizontalSpacing (real, space between soldiers within row)
Like before, you have to set values for these variables before using the Custom script.



Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Squad Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group


Keywords:
Detachment, Squad, Battalion, Group, Team, Company, Division, Regiment, Force, Squadron, Troop, Formation, Order, Lieutenant, Captain, Leader, General
Contents

Detachments (Map)

Reviews
00:01, 23rd Jun 2013 PurgeandFire: Changes made. Approved and recommended. A solid, unique system.
Level 13
Joined
Jul 2, 2015
Messages
875
I am going to need more details on what exactly is not working. Can you show me a picture of the problem or describe it?
so after pasting the whole "Detachments" Folder with the triggers into my trigger editor I then saved the map and exited, I started it up again and started reading the "AutoIndex" after not understanding I then read the Detachments I still didn't get it, I then went back and also copied the initialization trigger, now when I used the reinforce ability (renamed to my liking) it says "Detachment Full" when there is only one man.
(and yes I did copy all of the abilities and do the whole Footman => Captain Abilities)
 
Level 18
Joined
Oct 17, 2012
Messages
837
All right. The issue is that you haven't actually used the system.

Do the following in your map (or import this from Map attached below):
  • Setup Detachments
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- SETUP THE FOLLOWING FOR EACH UNIQUE SQUAD --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- UNIT TYPE THAT LEADS A SQUAD --------
      • Set LeaderRawCode = Swordsman
      • -------- UNIT TYPE THAT FOLLOWS THE ABOVE LEADER --------
      • Set SoldierRawCode = Footman
      • -------- ABILITY THAT CALLS FOR A SOLDIER TO JOIN A SQUAD --------
      • Set ReinforceRawcode = Reinforce Footmen
      • -------- ABILITY THAT TRANSFORMS A SQUAD MEMBER TO LEADER WHEN LEADER DIES --------
      • Set TransformRawcode = Footman => Captain
      • -------- OBJECT EDITOR VALUES --------
      • Set SoldierGoldCost = 100
      • Set SoldierLumberCost = 0
      • Set SoldierFoodCost = 2
      • Set TrainTime = 3
      • -------- CALL FUNCTION FROM SYSTEM TO CREATE DETACHMENT --------
      • Custom script: call InitDetachment(udg_LeaderRawCode, udg_SoldierRawCode, udg_ReinforceRawcode, udg_TransformRawcode, udg_SoldierGoldCost, udg_SoldierLumberCost, udg_SoldierFoodCost, udg_TrainTime)
      • -------- ------------------------------ ------------------------------ --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- THE FOLLOWING ARE OPTIONAL --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- NUMBER OF MEMBERS IN A SQUAD --------
      • Set MaximumMemberCount = 10
      • -------- SELF - EXPLANATORY --------
      • Set SoldiersPerRow = 5
      • -------- SPACE BETWEEN ROWS --------
      • Set VerticalSpacing = 150.00
      • -------- SPACE BETWEEN SOLDIERS WITHIN EACH ROW --------
      • Set HorizontalSpacing = 150.00
      • -------- CALL FUNCTION FROM SYSTEM TO CHANGE DETACHMENT VISUALS --------
      • Custom script: call SetDetachmentSize(udg_LeaderRawCode, udg_MaximumMemberCount, udg_SoldiersPerRow, udg_VerticalSpacing, udg_HorizontalSpacing)
 

Attachments

  • Detachments HOW-TO.w3x
    118.5 KB · Views: 59
Last edited:
Level 13
Joined
Jul 2, 2015
Messages
875
All right. The issue is that you haven't actually used the system.

Do the following in your map (or import this from Map attached below):
  • Setup Detachments
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- SETUP THE FOLLOWING FOR EACH UNIQUE SQUAD --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- UNIT TYPE THAT LEADS A SQUAD --------
      • Set LeaderRawCode = Swordsman
      • -------- UNIT TYPE THAT FOLLOWS THE ABOVE LEADER --------
      • Set SoldierRawCode = Footman
      • -------- ABILITY THAT CALLS FOR A SOLDIER TO JOIN A SQUAD --------
      • Set ReinforceRawcode = Reinforce Footmen
      • -------- ABILITY THAT TRANSFORMS A SQUAD MEMBER TO LEADER WHEN LEADER DIES --------
      • Set TransformRawcode = Footman => Captain
      • -------- OBJECT EDITOR VALUES --------
      • Set SoldierGoldCost = 100
      • Set SoldierLumberCost = 0
      • Set SoldierFoodCost = 2
      • Set TrainTime = 3
      • -------- CALL FUNCTION FROM SYSTEM TO CREATE DETACHMENT --------
      • Custom script: call InitDetachment(udg_LeaderRawCode, udg_SoldierRawCode, udg_ReinforceRawcode, udg_TransformRawcode, udg_SoldierGoldCost, udg_SoldierLumberCost, udg_SoldierFoodCost, udg_TrainTime)
      • -------- ------------------------------ ------------------------------ --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- THE FOLLOWING ARE OPTIONAL --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- ------------------------------ ------------------------------ --------
      • -------- NUMBER OF MEMBERS IN A SQUAD --------
      • Set MaximumMemberCount = 10
      • -------- SELF - EXPLANATORY --------
      • Set SoldiersPerRow = 5
      • -------- SPACE BETWEEN ROWS --------
      • Set VerticalSpacing = 150.00
      • -------- SPACE BETWEEN SOLDIERS WITHIN EACH ROW --------
      • Set HorizontalSpacing = 150.00
      • -------- CALL FUNCTION FROM SYSTEM TO CHANGE DETACHMENT VISUALS --------
      • Custom script: call SetDetachmentSize(udg_LeaderRawCode, udg_MaximumMemberCount, udg_SoldiersPerRow, udg_VerticalSpacing, udg_HorizontalSpacing)
I copied this and pasted into my map copying the Variables as well and it still says "Detachment Full"
 

Attachments

  • Teutonic Crusades1.w3x
    6.3 MB · Views: 81
Level 18
Joined
Oct 17, 2012
Messages
837
Here is what I found out:
  1. The captains must come from a building, not spawned (?) or preplaced ---> I believe this sets the spawn point for members.
  2. Members must have the ward classification.
  3. Train time bar will not finish and members will not spawn unless captain is near a building of same unit type that trains him. (I recall this not being the case in the original test map. I need to take another look at it.)
Edit: Never mind about third point. Description at top mentions this. TRAIN_RANGE of the Settings section of this system's code affects this. This feature is in a global state, thus affects all detachments.
 
Last edited:
Level 13
Joined
Jul 2, 2015
Messages
875
Here is what I found out:
  1. The captains must come from a building, not spawned (?) or preplaced ---> I believe this sets the spawn point for members.
  2. Members must have the ward classification.
  3. Train time bar will not finish and members will not spawn unless captain is near a building of same unit type that trains him. (I recall this not being the case in the original test map. I need to take another look at it.)
Edit: Never mind about third point. Description at top mentions this. TRAIN_RANGE of the Settings section of this system's code affects this. This feature is in a global state, thus affects all detachments.
IT WORKS! thank you comrade!
*Update* the Cancel button isn't umm working..
Also How do I remove the Green Text that pops up at the start

Sorry if I am a bother
 
Last edited:
Level 3
Joined
Apr 30, 2019
Messages
13
What versions of warcraft 3 can this tool be used with? I have version 1.26 I tried to use the SharpCraft, but it does not allow me to save the test map when I activate and deactivate the trigger, it shows me a syntax error
 
I like this trigger, but I don't know how to use Vjass. Is there a separate system for GUI?
A massive system like this needs JASS. Anything is “possible” with GUI given enough complimentary custom script, but at some point you’ll need to learn some JASS in order to really avail of systems like this one.
 
Level 1
Joined
Nov 27, 2021
Messages
3
A massive system like this needs JASS. Anything is “possible” with GUI given enough complimentary custom script, but at some point you’ll need to learn some JASS in order to really avail of systems like this one.
Thank you for your information.
 
Level 3
Joined
Apr 30, 2019
Messages
13
the test map does not let me save in another folder when editing it or when not editing it I use the normal editor
 
Top