• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!

[JASS] Unit Formations, Squads, etc etc ;o

Status
Not open for further replies.
Level 31
Joined
Jul 10, 2007
Messages
6,306
So I've been working on a spiffy new system. Just sharing the previews here to see if there are any features people want or if there are any things in this that people think are a bad idea.


Each unit has a squad under them. Whenever that unit is issued an order, the squad under them is issued an order. In this way, if you have a unit with 12 units under them each with 12 units under them and you order the top unit, you will order 157 units.

commander- 1
12 units under commander- 12
12 units per sub unit- 144

144+12+1 = 157

Chart:
Unit- (unit1(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit2(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit3(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit4(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit5(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit6(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit7(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit8(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit9(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit10(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit11(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12), unit12(unit1, unit2, unit3, unit4, unit5, unit6, unit7, unit8, unit9, unit10, unit11, unit12))

Obviously, you probably only want squads to be bodyguards or actual squads. You don't want your entire army under your commanders, lol, but with a commanding system, you could order squads under your commanders ;P.

Each unit has a leash, meaning that units under them can't go farther than the leash. With a command system, you could set leashes for specific squads (a unit in charge of a battalion of 12 squads may have a leash for those 12 squads, a unit in charge of a set of an army of 12 battalions may have an epic leash or no leash at all).

So yea...

I also introduced until roles (just 4).
LEADER_MELEE
LEADER_RANGE
MELEE
RANGE

Obviously this can be set up to be squad roles too if you were to have squads of squads with the ordering.

Each position has a priority value on the role.

Each unit has strengths for different roles (only MELEE and RANGE strengths). A unit is placed into the highest priority position they can go into given their strength (loop through all units in squad in order from highest priority to lowest and compare strengths).

And that's sorta what I have atm.

This entire thing only runs on two hashtables right now ;D.

This is the code I have so far (priorities aren't ordered atm and the list of positions should be split up into 2 lists, one for each role as well as 2 vars, the squad leader positions for melee and ranged)

Yea, this still has quite a lot of work, but I felt like sharing ;D.

After this is done, I'll make the commanding lib >: p. Then I'll make a carrier lib for units to carry commands to other units ;D.

Welcome to the beginning of epic RTS.

For those of you who've been keeping up with my work, I know I was working on Bounty, but I'm waiting for Bribe to submit the Damage stuff I helped him with since Bounty requires damage detection for accuracy ;|.

And no, I haven't set up the actual formations yet. I got the positions down and what not, but the formations will be a lot of work. If a formation isn't present, it'll just be a group order. In order to maintain formations, all unit speeds will be set to the slowest unit in the group. If the slowest unit is removed, they'll be set to the next slowest ;P, etc. For squads of squads, this can get fun to code... ; D

Event is in there for dispersing squads (when the commander dies, chaos ensues).

This snippet should make RTS games more RTS like : P, fuahaha. It also makes it easier to micro manage as you don't have to handle each individual unit, but can rather order various squads about or w/e. You could use this for a variety of purposes: enhancing melee games, making new hardcore RTS games, or w/e else.

Yea, still has a lot of work, but ehhh... I've been wanting to code this for a long time ;O.
JASS:
library Formation uses /*
    */UnitIndexer /*hiveworkshop.com/forums/jass-functions-413/unit-indexer-172090/
    */Event /*hiveworkshop.com/forums/submissions-414/snippet-event-186555/
    */UnitEvent //

    globals
        private hashtable table = InitHashtable()
        private hashtable positioning = InitHashtable()
    endglobals
    
    private module UnitSquadMod
        private group orderGroup
        private thistype leader
        private UnitFormationPosition position
        readonly integer count
        real leash
        UnitFormation formation
        private static method targetOrder takes nothing returns boolean
            call GroupTargetOrderById(thistype(GetUnitUserData(GetTriggerUnit())).orderGroup, GetIssuedOrderId(), GetOrderTarget())
            return false
        endmethod
        private static method pointOrder takes nothing returns boolean
            call GroupPointOrderById(thistype(GetUnitUserData(GetTriggerUnit())).orderGroup, GetIssuedOrderId(), GetOrderPointX(), GetOrderPointY())
            return false
        endmethod
        private static method unitOrder takes nothing returns boolean
            call GroupImmediateOrderById(thistype(GetUnitUserData(GetTriggerUnit())).orderGroup, GetIssuedOrderId())
            return false
        endmethod
        private static method index takes nothing returns boolean
            set thistype(GetIndexedUnitId()).orderGroup = CreateGroup()
            return false
        endmethod
        private static method deindex takes nothing returns boolean
            call GroupClear(thistype(GetIndexedUnitId()).orderGroup)
            call DestroyGroup(thistype(GetIndexedUnitId()).orderGroup)
            set thistype(GetIndexedUnitId()).orderGroup = null
            return false
        endmethod
        method add takes unit u returns nothing
            local UnitRole role = GetUnitTypeId(u)
            local UnitRole role2 = 0
            local thistype uid = GetUnitUserData(u)
            local thistype uid2 = 0
            local integer primary = role.primary
            local integer posRole = 0
            local UnitRole secondary = role.secondary
            local integer strength = role[primary].strength
            local integer strength2 = 0
            
            local integer i = formation.positions
            local UnitFormationPosition pos
            
            //always add the unit in if there is no formation present
            //if there is a formation present, only add if there is room
            set uid.leader = this
            call GroupAddUnit(orderGroup, u)
            
            //loop will auto exit with no formation present
            loop
                exitwhen i == 0 or uid == 0
                set pos = formation[i]
                set uid2 = this[pos]
                set role2 = GetUnitTypeId(u)
                set posRole = pos.role
                set strength2 = role2[posRole].strength
                if (primary == posRole and strength > strength2) then
                    
                endif
                set i = i - 1
            endloop
            set i = formation.positions
            set 
            loop
                exitwhen i == 0 or uid == 0
                set i = i - 1
            endloop
        endmethod
        static method remove takes unit u returns nothing
            local thistype this = thistype(GetUnitUserData(u)).leader
            call GroupRemoveUnit(leader.orderGroup, u)
            set leader = 0
        endmethod
        private static method onInit takes nothing returns nothing
            local integer i = 16
            local trigger t = CreateTrigger()
            local trigger t2 = CreateTrigger()
            local trigger t3 = CreateTrigger()
            call UnitIndexer.INDEX.register(Condition(function thistype.index))
            call UnitIndexer.DEINDEX.register(Condition(function thistype.deindex))
            call TriggerAddCondition(t, Condition(function thistype.pointOrder))
            call TriggerAddCondition(t2, Condition(function thistype.targetOrder))
            call TriggerAddCondition(t3, Condition(function thistype.unitOrder))
            loop
                set i = i - 1
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
                call TriggerRegisterPlayerUnitEvent(t2, Player(i), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
                call TriggerRegisterPlayerUnitEvent(t3, Player(i), EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, null)
                exitwhen i == 0
            endloop
            set t = null
            set t2 = null
            set t3 = null
        endmethod
    endmodule
    private keyword allocate
    private keyword deallocate
    private keyword POSITION
    private keyword positionset
    private module UnitFormationPositionMod
        static constant integer DIRECTION = 0
        static constant integer FACING = 1
        static constant integer DISTANCE = 2
        static constant integer ROLE = 3
        private static constant integer RECYCLER = 4
        static constant integer ROLE_PRIORITY = 5
        static constant integer POSITION = 6
        static constant integer OPEN = 7
        
        private static integer count = 0
        private static integer recycleCount = 0
        
        static method allocate takes real direction, real facing, real distance, UnitRole role, integer rolePriority, integer position returns thistype
            local thistype this
            if (recycleCount == 0) then
                set count = count + 1
                set this = count
            else
                set recycleCount = recycleCount - 1
                set this = LoadInteger(table, recycleCount, RECYCLER)
            endif
            set this[DIRECTION] = direction
            set this[FACING] = facing
            set this[DISTANCE] = distance
            set priority = rolePriority
            set positionset = position
            set this.role = role
            
            return this
        endmethod
        method deallocate takes nothing returns nothing
            call SaveInteger(table, recycleCount, RECYCLER, this)
            set recycleCount = recycleCount + 1
        endmethod
    endmodule
    private module UnitFormationMod
        private static integer count = 0
        readonly integer positions
        static method create takes nothing returns thistype
            set count = count + 1
            return count
        endmethod
        method addPosition takes real direction, real facing, real distance, UnitRole role, integer rolePriority returns UnitFormationPosition
            local UnitFormationPosition new = UnitFormationPosition.allocate(direction, facing, distance, role, rolePriority, positions)
            set positions = positions + 1
            set this[positions] = new
            return new
        endmethod
        method removePosition takes UnitFormationPosition pos returns nothing
            local integer p = pos.position
            local UnitFormationPosition indexed = this[positions]
            set this = indexed
            set indexed.positionset = p
            set positions = positions - 1
        endmethod
    endmodule
    private module UnitRoleMod
        readonly static thistype MELEE = 0
        readonly static thistype RANGED = 1
        static constant integer MAX_STRENGTH = -1
        static constant integer NO_STRENGTH = 0
        method operator [] takes integer role returns integer
            return LoadInteger(table, -this, -role)
        endmethod
        method operator []= takes integer role, integer strength returns nothing
            call SaveInteger(table, -this, -role, strength)
        endmethod
        method operator role takes nothing returns integer
            local integer primary = this[MELEE]
            local integer secondary = this[RANGED]
            if (primary > secondary) then
                return MELEE
            endif
            return RANGED
        endmethod
        method operator secondary takes nothing returns integer
            local integer primary = this[MELEE]
            local integer secondary = this[RANGED]
            if (primary <= secondary) then
                return MELEE
            endif
            return RANGED
        endmethod
    endmodule
    struct UnitRole extends array
        implement UnitRoleMod
    endstruct
    struct UnitFormationPosition extends array
        method operator role takes nothing returns integer
            return LoadInteger(table, this, ROLE)
        endmethod
        private method operator role= takes UnitRole role returns nothing
            call SaveInteger(table, this, ROLE, role)
        endmethod
        method operator [] takes integer property returns real
            return LoadReal(table, this, property)
        endmethod
        private method operator []= takes integer property, real val returns nothing
            call SaveReal(table, this, property, val)
        endmethod
        method operator position takes nothing returns integer
            return LoadInteger(table, this, POSITION)
        endmethod
        method operator positionset= takes integer p returns nothing
            call SaveInteger(table, this, POSITION, p)
        endmethod
        method operator priority takes nothing returns integer
            return LoadInteger(table, this, ROLE_PRIORITY)
        endmethod
        method operator priority= takes integer newPriority returns nothing
            call SaveInteger(table, this, ROLE_PRIORITY, newPriority)
        endmethod
        implement UnitFormationPositionMod
    endstruct
    struct UnitFormation extends array
        method operator [] takes integer index returns UnitFormationPosition
            return LoadInteger(table, -this, index)
        endmethod
        private method operator []= takes integer index, UnitFormationPosition pos returns nothing
            call SaveInteger(table, -this, index, pos)
        endmethod
        implement UnitFormationMod
    endstruct
    struct UnitSquad extends array
        static method operator [] takes unit u returns thistype
            return GetUnitUserData(u)
        endmethod
        method operator [] takes UnitFormationPosition position returns integer
            return LoadInteger(positioning, this, position)
        endmethod
        private method operator []= takes UnitFormationPosition position, integer untiId returns nothing
            call SaveInteger(positioning, this, position, unitId)
        endmethod
        
        implement UnitSquadMod
    endstruct
endlibrary
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I'll make sure they don't clutter. I think GroupOrder does the wc3 non clutter stuff, but I'm not sure. If it doesn't, I will unclutter them manually >: D. Given all of their move speeds will be set to the slowest unit, I can order them to their locations in formation. If no formation, I can just disperse them so that they don't clutter in a single lane.

edit
idea

on turns and stuff, orders units to positions to complete the turns, then continue them on to their original order. In this way, the units are kept in formation to a degree all the way there.
 
Last edited:
Could you do a demo map from it?

I did such system which I still develop and I discovered there are many problems to handle.

When a leader dies? Replacing it makes it unselect and the reference to it, like with ctrl groups. You can ressurect it to keep it. And when dying, the group remains there, you must store current order somewhere along with its targets.

Regrouping. What would be best idea to keep the forces together? Even if they are not controllable, there will be always some units finding something interesting and not follow its group. And what do then, should they follow the leader's order or first attempt to come near the group?

But most problems are due to wc3 pathing. I discovered that more than 8 units in a group is impractical as they collide with eachother and ordering 12 of the groups it just messes everything up.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
->When a leader dies? Replacing it makes it unselect and the reference to it, like with ctrl groups. You can ressurect it to keep it. And when dying, the group remains there, you must store current order somewhere along with its targets.

When a leader dies, keep the units in the group and etc and fire the death event for the leader. If the author wants to make the units flee as part of the map, try to rez the unit, or w/e else, they can deal with it. They can also regroup the units. You don't worry about that.

->you must store current order somewhere along with its targets
The order is maintained in the units under the leader, but the order can easily be cleared out. Rezzing a leader shouldn't bring back the original order; that doesn't make sense ;P.

->What would be best idea to keep the forces together? Even if they are not controllable, there will be always some units finding something interesting and not follow its group. And what do then, should they follow the leader's order or first attempt to come near the group?

Leashes keep units in their squads. Furthermore, the unit speeds of all units in the group should be set to the slowest unit to keep them together.

->But most problems are due to wc3 pathing. I discovered that more than 8 units in a group is impractical as they collide with eachother and ordering 12 of the groups it just messes everything up.

I agree. You gotta do what wc3 does. There should be an enabled field in the thingie that keeps the units in formation. If enabled is false, that means they are going over some tricky marching. Staying in formation provides an advantage obviously as units can respond quickly to scenarios. Disabling enables them to walk over tough terrain. The feature should be auto disabled/enabled in background (if it's enabled) depending on the current terrain. Furthermore, move speed should be set back to norm when disabled (with leashes ofc).

Since you've already worked on tihs a bit, I can help you with your lib instead and you can take my code and syntax. I don't think there is any better way to organize this system, and most of the API is present in my current code. The API I got in that lib of mine is really good*. I got enough projects on my plate as it is, so if you were to code this properly, that'd be nice ;D.

I guess roles need to be changed a bit to work directly with the unit. UnitTypeId roles should only be used on index.


I leave it in your capable hands then =).

edit
I'll get to coding UnitState and Buff then.

UnitState is a lot like the current Bonus mods but takes the normal JASS syntax API approach (unit states) and will run off of the lua scripts for stability.

Buff would be stacked unit states that are on timers (last a duration).

All of the norm unit states would be included, like stun, miss, maxLife, life, and etc.

So, I'll get to coding it when I can. I got all the necessary lua scripts now (actually missing one, but shhh), so >: D.
 
Status
Not open for further replies.
Top