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. We have recently started the 16th edition of the Mini Mapping Contest. The theme is mini RPG. Do check it out and have fun.
    Dismiss Notice
  5. Dismiss Notice
  6. The Highway to Hell has been laid open. Come along and participate in the 5th Special Effect Contest.
    Dismiss Notice
  7. 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. 00:01, 23rd Jun 2013
    PurgeandFire: Changes made. Approved and recommended. A solid, unique system.
     
  2. .OmG.

    .OmG.

    Joined:
    May 9, 2010
    Messages:
    256
    Resources:
    2
    Spells:
    2
    Resources:
    2
    Sure that it could be useful.
    I think it would more realistic if at deaths door of a leader, control will not take over.
     
  3. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    But what should happen with the detachment then? It can't be controlled without a leader.
     
  4. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,839
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    you should link the Libraries with the /**/ parse.
    Code (vJASS):
    call IssueImmediateOrder(.me, "stop")
    set .orderTarget = null
    set .orderId = OrderId("stop")

    ->
    Code (vJASS):
    call IssueImmediateOrderById(.me, 851972)
    set .orderPoint = false
    set .orderId = 851972

    http://www.hiveworkshop.com/forums/jass-resources-412/repo-order-ids-197002/

    The number of ticks or "I" has can be configurable as well.
    You can move the bar variable into a global
     
  5. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    What's that? ;o
    The performance boost opposed to improved readability of using orders in string rather than integer form is negligible. You can more easily see or change the issued order if it's in string form.
    Well, bar fill speed is configurable so changing bar length shouldn't be needed.
    But okay, I'll add that.
     
  6. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,839
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    Code (vJASS):
    library Hello/*
    *
    *    */
    uses/*
    *    */
    SomeLib/* somelib.com
    */


    how about cacheing the order id with a variable? its readable :
    constant integer stop = 851972
    or just create a global Lib for OrderIds which i linked.
     
  7. deathismyfriend

    deathismyfriend

    Joined:
    Oct 24, 2012
    Messages:
    6,528
    Resources:
    14
    Spells:
    12
    Tutorials:
    2
    Resources:
    14
    U can use static ifs so that the library is optional also. This will give the user more flexibility.
     
  8. mckill2009

    mckill2009

    Joined:
    Mar 10, 2009
    Messages:
    4,696
    Resources:
    34
    Maps:
    5
    Spells:
    27
    JASS:
    2
    Resources:
    34
    - Pls make the RegisterPlayerUnitEvent NOT optional, the onInit is not good imo
    - Put API methods at documentation
    - Why you didnt use GetUnitGoldCost and GetUnitWoodCost?
    - Cache your PlayerState and alike via local variables
    - SimError notices be configurable
     
  9. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    Why would it not be optional? it gives user more freedom.
    And what's wrong with onInit? I have to initialize the events.
    The public functions are already in the API.
    Training unit through reinforcement doesn't necessarily have to use object editor values. The user might want to train them a unit using object editor cost, separately from this system.
    I'm using them twice only each in a function, is it worth creating a local variable for that?
    Ok, I'll add this.
     
  10. mckill2009

    mckill2009

    Joined:
    Mar 10, 2009
    Messages:
    4,696
    Resources:
    34
    Maps:
    5
    Spells:
    27
    JASS:
    2
    Resources:
    34
    Yeah but since you have many requirements atm, might as well include that...

    Even if 2, it's still slower coz total is (player+state)*2 = 4, while caching does it twice...also I'm suggesting this for readability's sake...

    EDIT:
    also I didnt see the libraries until now, why are you making a group library without anything in it except the group alone or one variable only?, and why dont you put the Z inside the code itself and the group?

    EDIT 2:
    you could use Nesthauru's TimerTolls for period frequency in it...
     
  11. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    It is included, but it's optional. Why would you want it to be more limited?
    What's "player+state"? The GetPlayerState function repeats once for each resource. It's very readable atm because of intuitive names of these functions; using local would add an unecessary line.
    Because if every library used its own global group or Z functions, you'd have unnecessary repetitions of these. Most systems or maps have a small library with Z functions, if they're present, the one used by this library can just be removed. Same goes for group, some libraries like GroupUtils have their own global group for such uses, so instead of importing GroupUtils, I added this group. If there's another library that has such group, the one used here can be removed.
    I'm not sure what TimerTools is for. The idea of merging timers with similar timeouts seems wrong to me. I'd rather have exact periods.
    The resource I use for periodic iterations is something between T32 and CTL.
    I didn't use CTL because it has what appears to me as many unneeded things.
     
  12. edo494

    edo494

    Joined:
    Apr 16, 2012
    Messages:
    3,846
    Resources:
    5
    Spells:
    1
    JASS:
    4
    Resources:
    5
    I must ask
    Code (vJASS):

    *        */ II                      /*
    *        */
    Group                   /*
    *        */
    Z                       /*


    what are those libraries? they have no links to them, so I wonder
     
  13. deathismyfriend

    deathismyfriend

    Joined:
    Oct 24, 2012
    Messages:
    6,528
    Resources:
    14
    Spells:
    12
    Tutorials:
    2
    Resources:
    14
    the onInit method is fine. not sure y it wouldnt be.

    i dont like how ur struct doesnt extend an array. it produces a lot more code when u dont extend an array. so its less effecient and slower
     
  14. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    They aren't public libraries. Group provides a global group for enumeration (so I don't have to recreate it each time), Z provides functions such as GetUnitZ, SetUnitZ and GetPointZ and II is used for periodic instance iteration, something like T32 or CTL.
    Meh, it probably would be better if it extended an array, but I don't think it matters that much. The main difference is, as far as I know, allocation method. Since the instances aren't allocated periodically or something like that, I don't think the efficiency beats readability and intuitivity here.
     
  15. deathismyfriend

    deathismyfriend

    Joined:
    Oct 24, 2012
    Messages:
    6,528
    Resources:
    14
    Spells:
    12
    Tutorials:
    2
    Resources:
    14
    nestharus made a page on it to show the differences and it is a big deal. it deals with the allocation and deallocation of structs.
     
  16. edo494

    edo494

    Joined:
    Apr 16, 2012
    Messages:
    3,846
    Resources:
    5
    Spells:
    1
    JASS:
    4
    Resources:
    5
    if they are not public you should provide the code of those, not everyone will download the map just to get 3 libraries, thats why we have vJass, so we dont have to download all maps to check code or copy it, unlike GUI
     
  17. Garfield1337

    Garfield1337

    Joined:
    Jul 6, 2009
    Messages:
    1,806
    Resources:
    4
    Maps:
    1
    Spells:
    3
    Resources:
    4
    Alright, I'll add it.

    EDIT: There's also a reason why I can't make these structs extend array. The AutoData module from AutoIndex, used in these structs, doesn't work when structs extend array.
     
    Last edited: Jun 21, 2013
  18. deathismyfriend

    deathismyfriend

    Joined:
    Oct 24, 2012
    Messages:
    6,528
    Resources:
    14
    Spells:
    12
    Tutorials:
    2
    Resources:
    14
    y doesnt it work for that ? ive never used the AutoData module b4. extending array should not cause problems tho.
     
  19. mckill2009

    mckill2009

    Joined:
    Mar 10, 2009
    Messages:
    4,696
    Resources:
    34
    Maps:
    5
    Spells:
    27
    JASS:
    2
    Resources:
    34
    it's not being limited, I can see also your Z, II and Group is not optional, why the Register is an exception?, now you've mentioned unecessary lines, well that is one, coz we have the register library already :D...

    GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD), calls the (player and gold), then you did it again so 2*2=calls 4, unecessary lines doesnt hurt as much as unecessary width...

    You dont need Group Utils, in fact also, bj_lastCreatedGroup can do, imo, it's a waste of importing libraries with that few code inside...also, moderators tend to approve systems with approved system resource...