1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  5. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. The poll for Hive's 12th Concept Art Contest is up! Go cast your vote for your favourite genie!
    Dismiss Notice
  8. The raddest synthwave tracks were chosen - Check out our Music Contest #12 - Results and congratulate the winners!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Detachments v1.2.0.0

Submitted by Garfield1337
This bundle is marked as approved. It works and satisfies the submission rules.
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 (vJASS):

    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)

    For GUI users
    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.



Keywords
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
Moderator
00:01, 23rd Jun 2013 PurgeandFire: Changes made. Approved and recommended. A solid, unique system.
  1. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    Im currently unable but ill hopefully update this soon to fit your needs.
     
  2. Vincent_Freeman

    Vincent_Freeman

    Joined:
    Apr 26, 2014
    Messages:
    35
    Resources:
    0
    Resources:
    0
    I didn't get it. I've copied the whole system into a map. I changed your footmen and archers, and everything was ok. Then I created another commander and unit (with all spells and styff). I haven't done any scripts, so it was still ok. This new troops weren't working, wich is logical at this stage.
    Then I created the variables, you've mentioned in this theme. I made them array (was that a mistake?) Copied the custom srcipts, put them after setting [1] variables. Now JNPG doesn't want to save my map and finds some mistakes.
    Could you please and some screenshots to show how and where exactly do i need to put new variables? How custom units-commanders should look like in GUI?
    Hope to hear from you soon.
     
  3. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    Your new units need to fit the description given in the How-to-use section of the code.
    You get errors because you use an array, which isn't really needed and has different syntax from what is used in custom script.
    Just make the variables listed in the variable editor. Then in an initialization trigger, set the values for these variables according to your new units and call the custom script right after.
     
  4. Vincent_Freeman

    Vincent_Freeman

    Joined:
    Apr 26, 2014
    Messages:
    35
    Resources:
    0
    Resources:
    0
    Thank you) I corretced mistakes, now everything is working)
    This little challenge makes me feel like a real coda :)
    Your system is perfect, but are you going to make some map using it?
    Another side question - how could possibly AI use this system?
     
  5. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    There is a map using this, it was for a techtree competition in my awards :D

    You'd have to make custom AI to make it use this.
     
  6. Vincent_Freeman

    Vincent_Freeman

    Joined:
    Apr 26, 2014
    Messages:
    35
    Resources:
    0
    Resources:
    0
    There is some problems. After a fight, where detachments' leaders died several times I got this picture. Orcs in the red square don't move at all and don't follow any leaders, Trolls in the blie square are don't know whom to follow - orc commanders or their troll commanders.[​IMG]
     
  7. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    Are you using the latest version? I've seen the problem before, but I thought I fixed it.
     
  8. Vincent_Freeman

    Vincent_Freeman

    Joined:
    Apr 26, 2014
    Messages:
    35
    Resources:
    0
    Resources:
    0
    Yes, since you haven't uploaded the resource since may, the version nust be the latest.
     
  9. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    1,177
    Resources:
    3
    Spells:
    2
    Tutorials:
    1
    Resources:
    3
    There is a bug with the detachment training of captain. If you have a full detachment I can still queue up 4 units it will reach full training time and then complain correctly that I can't train, however for the archer captain this option is not posible, it will complain as soon as I try to trian more then what is posible.

    Could you make the squad units unselectable?
     
  10. Hazop

    Hazop

    Joined:
    Jul 2, 2015
    Messages:
    765
    Resources:
    2
    Maps:
    2
    Resources:
    2
    It dosent work....
    Every time I copy past the "Detachments" folder thingy and I save, it fails on me saying all of the triggers have been disabled and world edit crashes on me.
     
  11. GhostHunter123

    GhostHunter123

    Joined:
    Oct 17, 2012
    Messages:
    442
    Resources:
    1
    Spells:
    1
    Resources:
    1
    Hazop, are you using WEX to save and test this resource? The regular world editor cannot save this resource, but you can still test without saving.
     
  12. Hazop

    Hazop

    Joined:
    Jul 2, 2015
    Messages:
    765
    Resources:
    2
    Maps:
    2
    Resources:
    2
    Thanks for telling me, everything works now, its just I still cant get it to work... do I have to change the ability's or?
     
  13. GhostHunter123

    GhostHunter123

    Joined:
    Oct 17, 2012
    Messages:
    442
    Resources:
    1
    Spells:
    1
    Resources:
    1
    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?
     
  14. Hazop

    Hazop

    Joined:
    Jul 2, 2015
    Messages:
    765
    Resources:
    2
    Maps:
    2
    Resources:
    2
    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)
     
  15. GhostHunter123

    GhostHunter123

    Joined:
    Oct 17, 2012
    Messages:
    442
    Resources:
    1
    Spells:
    1
    Resources:
    1
    Post your code, and did you read the How To Use Tab? SetDetachmentSize controls number of units.
     
  16. Hazop

    Hazop

    Joined:
    Jul 2, 2015
    Messages:
    765
    Resources:
    2
    Maps:
    2
    Resources:
    2
    Good to know but where do I find it? and where do I put it? and what do you mean by post your code?
     
  17. GhostHunter123

    GhostHunter123

    Joined:
    Oct 17, 2012
    Messages:
    442
    Resources:
    1
    Spells:
    1
    Resources:
    1
    Show your triggers here or provide me your map. I need to know how you are using the system.
     
    Last edited: Sep 26, 2017
  18. Hazop

    Hazop

    Joined:
    Jul 2, 2015
    Messages:
    765
    Resources:
    2
    Maps:
    2
    Resources:
    2
     

    Attached Files:

  19. GhostHunter123

    GhostHunter123

    Joined:
    Oct 17, 2012
    Messages:
    442
    Resources:
    1
    Spells:
    1
    Resources:
    1
    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)
     

    Attached Files:

    Last edited: Sep 28, 2017
  20. Hazop

    Hazop

    Joined:
    Jul 2, 2015
    Messages:
    765
    Resources:
    2
    Maps:
    2
    Resources:
    2
    I copied this and pasted into my map copying the Variables as well and it still says "Detachment Full"
     

    Attached Files: