• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

DockingSystem v1.05.3

full


Uses SmartTrack




JASS:
library DockingSystem /*

DockingSystem v1.05.3
By Spellbound

Special thanks to MyPad for finding out all the bugs and errors and helping me make this system better.

/*/*==== DESCRIPTION ====*/*/

DockingSystem intends to replicate the Undead Haunted Gold Mine system by allowing you to
'dock' units (aka plugs) into a stationary unit, usually a building. The system comes with
the ability to move docking ports (aka sockets) so if you want to dock units to another
unit that's in motion, you may do that as well. This system comes with a lot of features
so take a moment and read through the API.


/*/*==== INSTALLATION ====*/*/

• Copy-paste this trigger into your map.
• Copy-paste the required libraries into your map.
• The system is installed! Copy-paste the onDock Event trigger or make your own
  event trigger and start coding.

*/ requires /*
 
    */ SmartTrack   /* https://www.hiveworkshop.com/threads/smarttrack-v1-02-1.299957/
    */ Table        /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
 
    NB: SmartTrack requires a unit indexer that uses custom value. Either TriggerHappy's UnitDex or
    Bribe's GUI Unit Event/Unit Indexer  is recommended.
 
    Additionally, if you wish to dock units at different heights, you will need an Autofly
    library to allow ground units from changing height freely. The Autofly library in the
    DockingSystem demo map has been modified to work with UnitDex.
 
*/ optional /*

    */ WorldBounds /* https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
    */ ListT       /* https://www.hiveworkshop.com/threads/containers-list-t.249011/
 
    WorldBounds is really not necessary at all, but if you have it in your map (which you generally
    should), the constant MAX_DISTANCE can use its variabled to calculate the longest map distance.

/*/*==== API ====*/*/


/*SETUP and MODIFICATION________*/

DockingSystem.createStation( takes unit station, real socket_trigger_distance )
    This sets up a unit as the station that will then be given sockets for units to 'plug'
    into. Sockets are added separately.
 
DockingSystem.terminateStation( takes unit station )
    This terminates a station instance. Doing do will destroy all added sockets and undock
    any docked units.
 
DockingSystem.addSocket( takes unit station, real x, real y, real z, real facing_angle )
    Creates a socket and adds it to the desires station. Sockets can have any coordinates
    you desire them to have so they can be where ever you want them to be. The last argument,
    facing_angle, will determine the facing of the plug when it docks.
 
    IMPORTANT: This method will return the socket instance. If you wish to refer to this
    specific socket at a later date (eg: if you want to move it) you can do so by storing
    the instance in an integer variable.
 
DockingSystem.removeSocket( takes Socket socket_instance )
    Removes a socket from a station. Socket instances are obtained upon addSocket.
 
DockingSystem.moveSocket( takes Socket socket_instance, real x, real y, real z, real facing_angle )
    Alter the parameters of a socket. If a unit is currently docked in that socket, it will
    move with it. Socket instances are obtained upon addSocket.
 
DockingSystem.addSocketPointer( takes unit pointer, real range, boolean kill_pointer, Socket socket_instance )
    Creates a unit that acts as a pointer to a specific socket. When units dock into a station
    the chosen socket is done at random, but with a pointer you can tell the unit to dock into
    a specific socket. Pointers are proxies for the station itself with their own docking range,
    so placing one too far from the station will defacto teleport plugs around. If the
    socket_isntance is null, then they will teleport units to random open sockets. Socket
    instances are obtained upon addSocket.
 
DockinSystem.dock( takes unit plug, Socket socket_instance )
    Docks a unit into an empty plug. If the plug is currently occupied, nothing will happen.
 
DockinSystem.undock( takes unit plug )
    Undocks a unit from the station it is currently plugged into and fires an undocking event.
    Useful for when you want to handle the undocking manually.
 
DockingSystem.undockAll( takes unit station )
    Flushes all docked unit from a station. The sockets themselves remain intact.
 
RegisterNativeEvent( takes integer ev, code c )
    Sets up events for docking. The available events are EVENT_ON_PRE_DOCK, EVENT_ON_DOCK and
    EVENT_ON_UNDOCK. They should be fairly self-explanatory, but just in case I'm just
    projecting, here's what they do:
 
    EVENT_ON_PRE_DOCK  == BEFORE the plug has moved into place.
    EVENT_ON_DOCK      == After the plug has moved into place.
    EVENT_ON_UNDOCK    == When docking ends.
    EVENT_ON_REDOCK    == When a docked unit right-clicks its current station.
                       Can be used to issue error messages or reset animations.
                     
RegisterIndexNativeEvent( takes integer playerNumber, integer ev, code c )
    Same as above, but player-specific.
 
 
/*PRE-DOCKING________*/

InterruptDocking()
    call this on EVENT_ON_PRE_DOCK to stop docking from happening.

SetDockingManual()
    call this on EVENT_ON_PRE_DOCK to stop automatic undocking triggers from being created.
    This means that ordering plugs to move or to stop will no undock them, so you will have
    to undock them manually.


/*UNDOCKING________*/

ResetUnitFlyHeight()
    call this on EVENT_ON_UNDOCK to reset a unit's flying height to its default value.


/*REFERRENCE________*/

call GetPlug()
    returns the plug during a docking event
 
call GetStation()
    returns the station during a docking event

call GetRandomSocket( takes unit station )
    returns the instance of a random socket.
 
call GetRandomSocketWithState( takes unit station, boolean is_socket_occupied )
    returns the instance of a socket. If the boolean is true, returns an occupied socket. If
    false, returns a vacant one.
 
call GetClosestSocketWithState( takes unit station, unit plug, boolean is_socket_occupied )
    returns the station's socket that is nearest to the plug.

call GetNumberOfSockets( takes unit station )
    returns the total number of sockets the station has.

call GetNumberOfOccupiedSockets( takes unit station )
    returns the total number of a station's occupied sockets.
 
call GetNumberOfEmptySockets( takes unit station )
    returns the total number of a station's vacant sockets.
 
call GetSocketPlug( takes Socket socket_instance )
    returns the unit currently docked in that socket. If the socket is empty, returns null.

call GetSocketFromPlug( takes unit plug )
    returns the socket in which a unit is plugged into.

call GetSocketX( takes Socket socket_instance )
    returns the x co-ordinate of the socket.
 
call GetSocketY(  takes Socket socket_instance )
    returns the y co-ordinate of the socket.
 
call GetSocketZ( takes Socket socket_instance )
    returns the z co-ordinate of the socket.
 
call GetSocketFacing( takes Socket socket_instance )
    returns the facing angle of the socket in degrees.

call GetSocketAngleFromStation( takes Socket socket_instance, real offset)
    returns the angle between the socket and the station in radians. This is useful for when
    you want to rotate the socket around the station with DockinSystem.moveSocket(), for
    example. The offset value is for when you want to make sure your sockets are referrencing
    the actual center of the station model as some models tend to offset their center by -16
    both on their x and y coordinate. If you station center appears off, try setting the offset
    to 16 or modifying the collision size of the station unit in the object editor.
 
call GetStationAngleFromSocket( takes Socket socket_instance, real offset)
    Same as above, but the returns the angle from the station to the socket.
 
*/

globals

    private DockingSystem array DS_Instance
    private integer array DS_SocketId
    private integer array DS_PlugCount
 
    private unit eventPlug = null
    private unit eventStation = null
    private boolean interruptDocking = false
    private boolean manualDocking = false
 
    integer EVENT_ON_PRE_DOCK
    integer EVENT_ON_DOCK
    integer EVENT_ON_UNDOCK
    integer EVENT_ON_REDOCK
 
    private real MAX_DISTANCE = 0.
 
endglobals


private struct Station
    static Table s
endstruct


// picks a socket at random and check if it's occupied or not depending on the state of the boolean
// LookForOccupiedSocket. If that first random try doesn't return anything, cycle through the rest
// of the sockets.
function GetRandomSocketWithState takes unit station, boolean lookForOccupiedSocket returns Socket
 
    local DockingSystem this = DS_Instance[GetUnitUserData(station)]
    local integer i = GetRandomInt(1, this.maxSockets)
    local integer iLoop = i
    local boolean firstPass = false
    local Socket s
 
    set Station.s = this.tableInstance
    set s = Station.s.integer[i]
    if lookForOccupiedSocket then
        if not (s.plug == null) then
            return s
        endif
    else
        if s.plug == null then
            return s
        endif
    endif
 
    // this loop starts from integer i. Since i is unlikely to be zero, the loop must cycle back
    // to 1 to ensure all sockets are checked.
    loop
        set iLoop = iLoop + 1
        if iLoop > this.maxSockets then
            set firstPass = true // this tells the loop that all values above integer i have already been checked
            set iLoop = 1 // this sets iLoop to the first socket and moves upwards from there.
        endif
        exitwhen (iLoop >= i and firstPass) // if this returns true, then all sockets have been checked.
     
        set s = Station.s.integer[iLoop]
        // (s.plug != null) returns true if the socket is occupied and false if empty.
        // Eg: if true, and LookForOccupiedSocket is also true, then it returns s.
        // If false, but LookForOccupiedSocket is true, the loop moves on to the next socket.
        if (s.plug != null) == lookForOccupiedSocket then
            return s
        endif
    endloop
 
    return 0
endfunction

function GetRandomSocket takes unit station returns Socket
    local DockingSystem this = DS_Instance[GetUnitUserData(station)]
    local Socket s
    set Station.s = this.tableInstance
    set s = Station.s.integer[GetRandomInt(1, this.maxSockets)]
    return s
endfunction

function GetSocketFromPlug takes unit u returns Socket
    return DS_SocketId[GetUnitUserData(u)]
endfunction

// since the purpose of this is to only find which Socket is closest, there is no need to
// perform a costly square root operation since (x < y) is the same as (x*x < y*y).
private function GetDistanceSquared takes real ax, real ay, real bx, real by returns real
    local real dx = bx - ax
    local real dy = by - ay
    return dx * dx + dy * dy
endfunction

// returns the Socket closest to the unit that's about to dock into a Station. This loop through all
// of a Station's sockets and, depending on whether you are looking for an occupied or unoccupied
// Socket, will return the one with the closest unit distance from the Plug.
function GetClosestSocketWithState takes unit station, unit plug, boolean lookForOccupiedSocket returns Socket
    local DockingSystem this = DS_Instance[GetUnitUserData(station)]
    local real tempDist
    local real maxRange = MAX_DISTANCE
    local real xPlug = GetUnitX(plug)
    local real yPlug = GetUnitY(plug)
    local integer i = 0
 
    local Socket closestSocket = 0
    local Socket s
 
    static if LIBRARY_ListT then
     
        local IntegerListItem node
        local IntegerListItem nodeNext
     
        if lookForOccupiedSocket then
            set node = this.busySockets.first
        else
            set node = this.freeSockets.first
        endif
     
        loop
            exitwhen node == 0
            set s = node.data
            set nodeNext = node.next
         
            set tempDist = GetDistanceSquared(s.x, s.y, xPlug, yPlug)
            if tempDist < maxRange then
                set maxRange = tempDist
                set closestSocket = s
            endif
         
            set node = nodeNext
        endloop
     
    else
 
        set Station.s = this.tableInstance
     
        loop
            set i = i + 1
            exitwhen i > this.maxSockets
            set s = Station.s.integer[i]
         
            if (s.plug == null) != lookForOccupiedSocket then
                set tempDist = GetDistanceSquared(s.x, s.y, xPlug, yPlug)
                if tempDist < maxRange then
                    set maxRange = tempDist
                    set closestSocket = s
                endif
            endif
        endloop
     
    endif
 
    return closestSocket
endfunction

function GetNumberOfSockets takes unit station returns integer
    return DS_Instance[GetUnitUserData(station)].maxSockets
endfunction

function GetNumberOfOccupiedSockets takes unit station returns integer
    return DS_PlugCount[GetUnitUserData(station)]
endfunction

function GetNumberOfEmptySockets takes unit station returns integer
    local integer id_S = GetUnitUserData(station)
    return DS_Instance[id_S].maxSockets - DS_PlugCount[id_S]
endfunction

function GetPlug takes nothing returns unit
    return eventPlug
endfunction

function GetStation takes nothing returns unit
    return eventStation
endfunction

function GetSocketPlug takes Socket s returns unit
    return s.plug
endfunction

function GetSocketX takes Socket s returns real
    return s.x
endfunction

function GetSocketY takes Socket s returns real
    return s.y
endfunction

function GetSocketZ takes Socket s returns real
    return s.z
endfunction

function GetSocketFacing takes Socket s returns real
    return s.facing
endfunction

function GetSocketAngleFromStation takes Socket s, real Offset returns real
    return Atan2((GetUnitY(s.station) + Offset) - s.y, (GetUnitX(s.station) + Offset) - s.x)
endfunction

function GetStationAngleFromSocket takes Socket s, real Offset returns real
    return Atan2(s.y - (GetUnitY(s.station) + Offset), s.x - (GetUnitX(s.station) + Offset))
endfunction

// Backward compatibility

function GetNumberOfSocketsWithState takes unit station, boolean occupied returns integer
if occupied then
    return GetNumberOfOccupiedSockets(station)
endif
return GetNumberOfEmptySockets(station)
endfunction

// Pre-dock actions

// Units will not automatically un-dock when this is called on pre-dock.
function SetDockingManual takes nothing returns nothing
    set manualDocking = true
endfunction

// This will interrupt docking when called on pre-dock
function InterruptDocking takes nothing returns nothing
    set interruptDocking = true
endfunction

// This will interrupt docking when called on pre-dock
function ResetUnitFlyHeight takes unit u returns nothing
    call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0.)
endfunction

struct Socket

    real x
    real y
    real z
    real facing
    unit plug
    unit station
    unit socketPointer
    boolean killSocketPointer
    boolean ignoreOrder
    integer slot
    trigger trig
 
    method destroy takes nothing returns nothing
        if this.plug != null then
            call DockingSystem.undock.evaluate(this.plug)
        endif
     
        set this.station = null
        set this.slot = 0
     
        if this.killSocketPointer then
            call UnitApplyTimedLife(this.socketPointer, 'BTLF', .01)
            set this.killSocketPointer = false
        endif
     
        if this.socketPointer != null then
            set DS_SocketId[GetUnitUserData(this.socketPointer)] = 0
            set this.socketPointer = null
        endif
     
        call this.deallocate()
    endmethod
 
    // Safety measure
    static if LIBRARY_WorldBounds then
        method secureBounds takes nothing returns nothing
            if this.x > WorldBounds.maxX then
                set this.x = WorldBounds.maxX
            elseif this.x < WorldBounds.minX then
                set this.x = WorldBounds.minX
            endif
            if this.y > WorldBounds.maxY then
                set this.y = WorldBounds.maxY
            elseif this.y < WorldBounds.minY then
                set this.y = WorldBounds.minY
            endif
        endmethod
    endif
 
endstruct


// Fires one of the four custom events of DockingSystem. These are registered with RegisterDockEvent above.
private function FireEvent takes unit plug, unit station, integer ev returns nothing
    local integer playerId = GetPlayerId(GetOwningPlayer(plug))
    local unit prevPlug = eventPlug
    local unit prevStation = eventStation
 
    set eventPlug = plug
    set eventStation = station
 
    call TriggerEvaluate(GetNativeEventTrigger(ev))
    if IsNativeEventRegistered(playerId, ev) then
        call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, ev))
    endif
 
    set eventPlug = prevPlug
    set eventStation = prevStation
    set prevPlug = null
    set prevStation = null
endfunction


struct DockingSystem
 
    readonly Table tableInstance
    readonly integer maxSockets
 
    static if LIBRARY_ListT then
        readonly IntegerList busySockets
        readonly IntegerList freeSockets
    endif
 
    // stuns the unit after 0 seconds. Needed to prevent docked units from keeping on walking
    // once they get in range of a station. Also used to stop plugs from walking into a building
    // when they right-click on their current station.
    private static method zeroTimerStun takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local Socket s = GetTimerData(t)
     
        set s.ignoreOrder = true
        call IssueImmediateOrderById(s.plug, 851973)//order stunned
        set s.ignoreOrder = false
        call ReleaseTimer(t)
     
        set t = null
    endmethod
 
 
    // core undocking method.
    static method undock takes unit plug returns nothing
     
        local integer id_P = GetUnitUserData(plug)
        local Socket s = DS_SocketId[id_P]
        local integer id_S
     
        // if s is 0 that means the unit is not docked and thus nothing happens.
        if s != 0 then
            set id_S = GetUnitUserData(s.station)
            set DS_PlugCount[id_S] = DS_PlugCount[id_S] - 1
            call SetUnitPathing(plug, true)
            set s.plug = null
            call DestroyTrigger(s.trig)
            set s.trig = null
         
            static if LIBRARY_ListT then
                call DS_Instance[id_S].busySockets.removeElem(s)
                call DS_Instance[id_S].freeSockets.push(s)
            endif
         
            call FireEvent(plug, s.station, EVENT_ON_UNDOCK)
            set DS_SocketId[id_P] = 0
        endif
     
    endmethod
 
 
    // this function cycles through all of a station's sockets and, if occupied, undocks them.
    static method undockAll takes unit station returns nothing
     
        local integer id_S = GetUnitUserData(station)
        local thistype this = DS_Instance[id_S]
        local integer i
        local Socket s
     
        static if LIBRARY_ListT then
            local IntegerListItem node = this.busySockets.first
            local IntegerListItem nodeNext
        endif
     
        // if unit station is not an actual station or has no sockets, this does nothing.
        if this != 0 then
         
            static if LIBRARY_ListT then
             
                loop
                    exitwhen node == 0
                    set nodeNext = node.next
                    set s = node.data
                    call DockingSystem.undock(s.plug)
                    set node = nodeNext
                endloop
             
            else
                set Station.s = this.tableInstance
                set i = this.maxSockets // goes from top to bottom.
                loop
                    set s = Station.s.integer[i]
                    if s.plug != null then
                        call DockingSystem.undock(s.plug)
                    endif
                    set i = i - 1
                    exitwhen i <= 0
                endloop
            endif
         
        endif
     
    endmethod
 
 
    // this function handles the order events of docked unit (aka plugs). Right-clicking on the
    // unit the plug is currently docked at will do nothing - this is handled in dockPrep.
    private static method trackOrders takes nothing returns nothing
        local unit plug     = GetTriggerUnit()
        local unit station  = GetOrderTargetUnit()
        local Socket s = DS_SocketId[GetUnitUserData(plug)]
     
        if not s.ignoreOrder then
            if not (station == s.station and GetIssuedOrderId() == ORDER_SMART) then
                call DockingSystem.undock(plug)
            endif
        endif
     
        set plug = null
        set station = null
    endmethod
 
 
    // core dock method.
    static method dock takes unit plug, Socket s returns nothing
     
        local integer id_S = GetUnitUserData(s.station)
     
        call FireEvent(plug, s.station, EVENT_ON_PRE_DOCK)
     
        if not interruptDocking then
         
            call TimerStart(NewTimerEx(s), 0., false, function thistype.zeroTimerStun)
         
            call SetUnitPathing(plug, false)
            call SetUnitX(plug, s.x)
            call SetUnitY(plug, s.y)
         
            if GetUnitAbilityLevel(plug, 852155) > 0 then
                call UnitAddAbility(plug, 852155)
                call UnitRemoveAbility(plug, 852155)
            endif
         
            call SetUnitFlyHeight(plug, s.z, 0.)
            call SetUnitFacing(plug, s.facing)
         
            set s.plug = plug
            set DS_SocketId[GetUnitUserData(plug)] = s
         
            if not manualDocking then
                set s.trig = CreateTrigger()
                call TriggerRegisterUnitEvent(s.trig, plug, EVENT_UNIT_ISSUED_TARGET_ORDER)
                call TriggerRegisterUnitEvent(s.trig, plug, EVENT_UNIT_ISSUED_POINT_ORDER)
                call TriggerRegisterUnitEvent(s.trig, plug, EVENT_UNIT_ISSUED_ORDER)
                call TriggerAddCondition(s.trig, function thistype.trackOrders)
            else
                set manualDocking = false
            endif
         
            set DS_PlugCount[id_S] = DS_PlugCount[id_S] + 1
         
            static if LIBRARY_ListT then
                call DS_Instance[id_S].freeSockets.removeElem(s)
                call DS_Instance[id_S].busySockets.push(s)
            endif
         
            call FireEvent(plug, s.station, EVENT_ON_DOCK)
         
        endif
     
        set interruptDocking = false
     
    endmethod
 
 
    // this function runs when a right-clicker is in range of a station.
    static method dockPrep takes nothing returns nothing
        local unit      station = GetTracker()
        local unit      plug = GetSmartUnit()
        local boolean   firstPass = false
        local boolean   exitLoop = false
     
        local thistype  this
        local Socket s = DS_SocketId[GetUnitUserData(plug)]
         
        // if s has no value, then the unit is not docked anywhere.
        if s == 0 then
            set s = GetClosestSocketWithState(station, plug, false)
            if s != 0 then
                call DockingSystem.dock(plug, s)
            endif
         
        // if s has a value, then the unit is already docked somewhere.
        else
            // if the station you right-clicked is the same station the unit is docked at, issue a stunned order and fires a EVENT_ON_REDOCK event.
            if s.station == station then
                call TimerStart(NewTimerEx(s), 0., false, function thistype.zeroTimerStun)
                call FireEvent(plug, s.station, EVENT_ON_REDOCK)
            else // undock the plug and dock it in the other station
                call DockingSystem.undock(plug)
                set s = GetClosestSocketWithState(station, plug, false)
                if s != 0 then
                    call DockingSystem.dock(plug, s)
                endif
            endif
         
        endif
     
        set station = null
        set plug = null
    endmethod
 
 
    // removes a socket from a station. If the socket is occupied, the unit is first undocked.
    static method removeSocket takes Socket s returns nothing
     
        local thistype this = DS_Instance[GetUnitUserData(s.station)]
        local integer i = s.slot
     
        if this != 0 then
         
            set Station.s = this.tableInstance
         
            static if LIBRARY_ListT then
                if s.plug == null then
                    call this.freeSockets.removeElem(s)
                else
                    call this.busySockets.removeElem(s)
                endif
            endif
         
            call s.destroy()
            call Station.s.remove(i)
         
            loop
                set Station.s.integer[i] = Station.s.integer[i + 1]
                set i = i + 1
                exitwhen i >= this.maxSockets
            endloop
         
            set this.maxSockets = this.maxSockets - 1
         
        endif
     
    endmethod
 
 
    // changes the x/y coordinates of a socket. If there a unit docked into it, the unit moves as well.
    static method moveSocket takes Socket s, real x, real y, real z, real facing returns nothing
     
        set s.x = x
        set s.y = y
        set s.z = z
        set s.facing = facing
     
        static if LIBRARY_WorldBounds then
            call s.secureBounds()
        endif
     
        if s.plug != null then
            //Move plug
            call SetUnitX(s.plug, x)
            call SetUnitY(s.plug, y)
            call SetUnitFacing(s.plug, facing)
            call SetUnitFlyHeight(s.plug, z, 0.)
        endif
    endmethod
 
 
    // socket pointers are proxies that allow you to dock a unit to a specific socket. See documentation for more info.
    static method addSocketPointer takes unit pointer, real range, boolean killPointer, Socket s returns nothing
        if pointer != null then
            set s.socketPointer = pointer
            set s.killSocketPointer = killPointer
            set DS_SocketId[GetUnitUserData(pointer)] = s
            call CreateSmartOrderTracker(pointer, range)
        endif
    endmethod
 
 
    // adds a socket to a station.
    static method addSocket takes unit station, real x, real y, real z, real facingAngle returns Socket
 
        local thistype this = DS_Instance[GetUnitUserData(station)]
        local Socket s
     
        // if the unit is not a Station this function does nothing.
        if this != 0 then
            set Station.s = this.tableInstance
            set this.maxSockets = this.maxSockets + 1
         
            set s = Socket.create()
            set s.x = x
            set s.y = y
            set s.z = z
            set s.facing = facingAngle
            set s.station = station
            set s.slot = this.maxSockets
         
            set Station.s.integer[this.maxSockets] = s

            static if LIBRARY_WorldBounds then
                call s.secureBounds()
            endif
     
            static if LIBRARY_ListT then
                call this.freeSockets.push(s)
            endif
         
        endif
     
        return s
    endmethod
 
 
    // this terminates the Station instance bound to a unit. Will undock all currectly-docked units.
    static method terminateStation takes unit station returns nothing
     
        local integer id_S = GetUnitUserData(station)
        local thistype this = DS_Instance[id_S]
        local integer i
     
        if this != 0 then
            set Station.s = this.tableInstance
            set i = this.maxSockets
         
            loop
                call removeSocket(Station.s.integer[i])
                set i = i - 1
                exitwhen i <= 0
            endloop
         
            set this.maxSockets = 0
            call CancelSmartOrderTracker(station)
            set DS_Instance[id_S] = 0
            call Station.s.destroy()
         
            static if LIBRARY_ListT then
                call this.busySockets.destroy()
                call this.freeSockets.destroy()
            endif
         
            call this.deallocate()
        endif
     
    endmethod
 
 
    // establishes a unit as a Station. Sockets are added separately. Only 1 Station instance
    // allowed per unit.
    static method createStation takes unit station, real socketTriggerDistance returns nothing
 
        local integer id_S = GetUnitUserData(station)
        local thistype this = DS_Instance[id_S]
     
        if this == 0 then
            set Station.s = Table.create()
            set this = allocate()
            set this.tableInstance = Station.s
            set this.maxSockets = 0
            call CreateSmartOrderTracker(station, socketTriggerDistance)
            set DS_Instance[id_S] = this
            set DS_PlugCount[id_S] = 0
         
            static if LIBRARY_ListT then
                set this.freeSockets = IntegerList.create()
                set this.busySockets = IntegerList.create()
            endif
         
        endif
     
    endmethod
 
endstruct

// this function sets up all events related to DockingSystem. It also sets up SmartTrack to fire
// DockingSystem.dockPrep whenever a unit gets in range of a tracker.
private module DockingSystemInit
    private static method onInit takes nothing returns nothing
 
        static if LIBRARY_WorldBounds then
            set MAX_DISTANCE =  (WorldBounds.maxX - WorldBounds.minX)*(WorldBounds.maxX - WorldBounds.minX) + /*
            */                  (WorldBounds.maxY - WorldBounds.minY)*(WorldBounds.maxY - WorldBounds.minY)
        else
            set MAX_DISTANCE =  999999. * 999999.
        endif
     
        set EVENT_ON_PRE_DOCK  = CreateNativeEvent()
        set EVENT_ON_DOCK      = CreateNativeEvent()
        set EVENT_ON_UNDOCK    = CreateNativeEvent()
        set EVENT_ON_REDOCK    = CreateNativeEvent()
     
        call RegisterNativeEvent(EVENT_SMART_TRACK_IN_RANGE, function DockingSystem.dockPrep)
    endmethod
endmodule

private struct init
    implement DockingSystemInit
endstruct

endlibrary

JASS:
scope onDockEvent initializer init
 
    globals
        private effect array wispTail
        private constant string WISP_TAIL_FX = "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl"
    endglobals
 
    /*
        GetPlug() == Unit that is docking into the station
        GetStation() == The station with the docking ports
   
        I recommend storing those in a local variable if they will be called more than once.
        Do not forget to null the locals afterward to prevents leaks.
   
        Examples can be seen down below with the functions Actions_onDock, Actions_onUndock and
        Actions_onRedock.
    */
 
    private function onTrackStart takes nothing returns boolean
   
        /*
            The following are examples of how to filter out units with SmartTrack events.
        */
   
        local unit smarty = GetSmartUnit()
        local unit tracker = GetTracker()
        local integer smartyTypeId = GetUnitTypeId(smarty)
        local integer trackerTypeId = GetUnitTypeId(tracker)
   
        //Tauren Totem - Witch Doctors only
        if trackerTypeId == 'otto' then
            if smartyTypeId != 'odoc' then
                call SmartTrack.stop(smarty, true)
                call BJDebugMsg("    Witch Doctors only")
            endif
        endif
   
        //Chimera Roost - Archers only
        if trackerTypeId == 'edos' then
            if smartyTypeId != 'earc' then
                call SmartTrack.stop(smarty, false)
                call BJDebugMsg("    Archers only")
            endif
        endif
   
        //Spirit Tower - Wisps only
        if trackerTypeId == 'uzg1' then
            if smartyTypeId != 'ewsp' then
                call SmartTrack.stop(smarty, false)
                call BJDebugMsg("    Wisps only")
            endif
        endif
   
        set smarty = null
        set tracker = null
   
        return false
    endfunction
 
 
    private function Actions_onPreDock takes nothing returns boolean
   
        /*/*
       
            You can call /* InterruptDocking() */ in this function to prevent docking from
            happening.
       
            You can also call /* SetDockingManual() */ to prevent automatic undocking triggers from
            being created. For example, any orders that is NOT a right-click on the currently docked
            station will undock the plug. With SetDockingManual() you will have to undock the unit
            by youself. This can be useful if you wish to have docked units function as turrets, for
            example. Give them an ability upon docking that will undock them when used.
       
        */*/
   
        //Archer
        if GetUnitTypeId(GetPlug()) == 'earc' then
            call SetDockingManual()
        endif
   
        return false
    endfunction
 
 
    private function Actions_onDock takes nothing returns boolean
   
        local unit plug = GetPlug()
        local unit station = GetStation()
        local integer plugTypeId = GetUnitTypeId(plug)
        local integer stationTypeId = GetUnitTypeId(station)
        local integer plugId
   
        //Witch Doctor
        if plugTypeId == 'odoc' then
            call SetUnitAnimationWithRarity(plug, "stand victory", RARITY_FREQUENT)

        //Archer
        elseif plugTypeId == 'earc' then
            call SetUnitPropWindow(plug, 0.)
            call UnitAddAbility(plug, 'A000')
       
        //Wisp
        elseif plugTypeId == 'ewsp' then
            set plugId = GetUnitUserData(plug)
            if wispTail[plugId] == null then
                set wispTail[plugId] = AddSpecialEffectTarget(WISP_TAIL_FX, plug, "chest")
            endif
   
        endif
   
   
        //Chimera Roost
        if stationTypeId == 'edos' then
            if GetNumberOfOccupiedSockets(station) == 1 then
                call UnitAddAbility(station, 'A001')
            endif
        endif
   
        set plug = null
        set station = null
   
        return false
    endfunction
 
 
    private function Actions_onUndock takes nothing returns boolean
   
        /*/*
       
            You can call /* ResetUnitFlyHeight(plug) */ in this function to reset your undocking
            unit's flying height to its default value.
       
        */*/
   
        local unit plug = GetPlug()
        local unit station = GetStation()
        local integer plugTypeId = GetUnitTypeId(plug)
        local integer stationTypeId = GetUnitTypeId(station)
        local integer plugId
   
        //Witch Doctor
        if plugTypeId == 'odoc' then
            if not UnitAlive(plug) then
                call SetUnitAnimation(plug, "death")
            else
                call SetUnitAnimation(plug, "stand")
            endif
       
        //Archer
        elseif plugTypeId == 'earc' then
            call SetUnitPropWindow(plug, GetUnitDefaultPropWindow(plug))
            call UnitRemoveAbility(plug, 'A000')
            call SetUnitPosition(plug, GetUnitX(station) + Cos(270. * bj_DEGTORAD) * 150., /*
            */ GetUnitY(station) + Sin(270. * bj_DEGTORAD) * 150.)
            call ResetUnitFlyHeight(plug)
   
        //Wisp
        elseif plugTypeId == 'ewsp' then
            set plugId = GetUnitUserData(plug)
            if wispTail[plugId] != null then
                call DestroyEffect(wispTail[plugId])
                set wispTail[plugId] = null
            endif
   
        endif
   
        //Chimera Roost
        if stationTypeId == 'edos' then
            if GetNumberOfOccupiedSockets(station) == 0 then
                call UnitRemoveAbility(station, 'A001')
            endif
        endif
   
        set plug = null
        set station = null
   
        return false
    endfunction
 
 
    private function Actions_onRedock takes nothing returns boolean
   
        local unit plug = GetPlug()
        local unit station = GetStation()
        local integer plugTypeId = GetUnitTypeId(plug)
   
        //Witch Doctor
        if plugTypeId == 'odoc' then
            call SetUnitAnimation(plug, "stand")
            call SetUnitAnimationWithRarity(plug, "stand victory", RARITY_FREQUENT)
   
        //Wisp
        elseif plugTypeId == 'ewsp' then
            call BJDebugMsg("This wisp is already docked at this Spirit Tower")
           
        endif
   
        set plug = null
        set station = null
   
        return false
    endfunction
 
 
    private function init takes nothing returns nothing
        call RegisterNativeEvent(EVENT_ON_PRE_DOCK, function Actions_onPreDock)
        call RegisterNativeEvent(EVENT_ON_DOCK, function Actions_onDock)
        call RegisterNativeEvent(EVENT_ON_UNDOCK, function Actions_onUndock)
        call RegisterNativeEvent(EVENT_ON_REDOCK, function Actions_onRedock)
        //Filter - use SmartTrack
        call RegisterNativeEvent(EVENT_SMART_TRACK_STARTED, function onTrackStart)
    endfunction

endscope

- v1.05.3 Added WorldBounds and ListT as optional requirements to for safety checks and slight performance boost respectively. The socket struct is now names Socket, as it should have always been (instead of P for plug - I had been confusing my own terminologies >_>). Finally fixed a potential bug and made the coding style more closely follow camelCase.
- v1.05.2 Added backward compatibility with slightly older versions of DockingSystem and fixed some bugs.
- v1.05.1 Added GetSocketFromPlug to return the socket in which a unit is plugged in. Also split GetNumberOfSocketsWithState into GetNumberOfOccupiedSockets and GetNumberOfEmptySockets and optimised the code.
- v1.05 Fixed a bug with interruptDocking() not working. DockingSystem will now pick the closest Socket to dock into. Also added the function ResetUnitFlyHeight(your unit) to reset an undocking plug's height on Undocking.
- v1.04 updated to work with SmartTrack v1.05, which now uses RegisterPlayerUnitEvent and RegisterNativeEvent. Updated event names for standardisation.
- v1.03 Must now use wrapper functions GetPlug() and GetStation() to obtain the plug unit and station unit respectively during events. Demo code has been updated to demonstate their use. Also fixed an improperly classed member (tableInstance is now a Table rather than integer) and updated SmartTrack to 1.03.
- v1.02 cleaned up the code and added comments to explain most functions. Added a new event for redocking and updated the demo to explain how it can be used. Also updated SmartTrack to v1.02.1
- v1.01 added some additional API funcitonality
- v1.00 initial release
Contents

DockingSystem v1.05.3 (Map)

Reviews
MyPad
Nitpicks: In struct S, making it private would make more sense, since it isn't referred outside of the system. Notes: It is preferable to indicate private members. Same applies for globals. Struct naming would be more optimal. For example, P could...
Level 4
Joined
Mar 20, 2014
Messages
67
@Spellbound I'm currently filtering them on my own, and that's really how it should be. I think a trigger included to show how to filter would be nice for the greener people, but I'm comfortable setting them on my own.

I would argue the folders and triggers could use some more explanation (such as if I wanted to implement this for my own totem, what's the process?) for someone that doesn't know how to read all this.

Side-Note: Copying things from one map to another is easier with one folder ;)

I appreciate the system, it's great for me, personally I'm using it as lives in a Defend-Azeroth (Similar to Custom Castle Defense) type game, and it will stay and probably be used for other things
 
Hmm, personally I would be okay with just giving some examples and not having to make a dynamic filter system (it's work, lol). I made a little mock-up in SmartTracking. I'm need some feedback on it.

As for the folders, they're mostly there for organisation. I'll see about adding some more descriptions and explanations to the non-system triggers :)
 
Updated to v1.01. Added some more function calls that you can use:
JASS:
    call GetRandomSocket( takes unit station )
        returns the instance of a random socket.
      
    call GetRandomSocketWithState( takes unit station, boolean is_socket_occupied )
        returns the instance of a socket. If the boolean is true, returns an occupied socket. If
        fakse, returns a vacant one.
  
    call GetNumberOfSockets( takes unit station )
        returns the total number of sockets the station has.
  
    call GetNumberOfSocketsWithState( takes unit station, boolean is_socket_occupied )
        returns the total number of sockets the station has. If the boolean is true, only counts
        occupied sockets. If false, only counts vacant ones.
      
    call GetSocketPlug( takes P socket_instance )
        returns the unit currently docked in that socket. If the socket is empty, returns null.
 
Note to reviewer: I will be updating this system today. Please hold off any moderation until I do.

EDIT: Updated to v1.02:
- v1.02 cleaned up the code and added comments to explain most functions. Added a new event for redocking and updated the demo to explain how it can be used. Also updated SmartTrack to v1.02.1
 
Last edited:
Level 8
Joined
Jun 13, 2010
Messages
344
This is quite brilliant actually! I imagine this may inspire a lot of map creators trying to do some unique faction features. Well done.
 

Nitpicks:

  • In struct S, making it private would make more sense, since it isn't referred outside of the system.

Notes:

  • It is preferable to indicate private members. Same applies for globals.
  • Struct naming would be more optimal. For example, P could be renamed Plug, and S could be Socket.

Status:


Awaiting Update
 
Last edited:
Updated per suggestions and requirements.
- v1.03 Must now use wrapper functions GetPlug() and GetStation() to obtain the plug unit and station unit respectively during events. Demo code has been updated to demonstate their use. Also fixed an improperly classed member (tableInstance is now a Table rather than integer) and updated SmartTrack to 1.03.
Cheers!
 
Level 6
Joined
Oct 31, 2015
Messages
95
I like your resources a lot @Spellbound, but this one, despite being awesome, it's quite easy to imagine a mechanic abuse. If you take a Haunted Gold Mine as example here whenever you attempt to dock in the Acolyte will look for the closest spot to move in (teleport). In your docking system however seems kinda random. If I keep undocking then docking a single Witch Doctor (considering there are few WDs or only one WD docked) he will teleport wildly through all docking ports. Imagine now that my enemy is early rushing me with some melee units like militia or ghouls. I could easily abuse the random teleport to keep melee units out of range. I don't believe this to be intended but rather something that is not implemented yet.
 
Just an oversight that I spotted while building up my contest entry for the Techtree using this Docking System.

JASS:
// Fires one of the four custom events of DockingSystem. These are registered with RegisterDockEvent above.
private function FireEvent takes unit Plug, unit Station, integer ev returns nothing
    local integer playerId = GetPlayerId(GetOwningPlayer(Plug))
    local unit prevPlug = eventPlug
    local unit prevStation = eventStation
  
    set eventPlug = Plug
    set eventStation = Station
  
    call TriggerEvaluate(GetNativeEventTrigger(ev))
    if IsNativeEventRegistered(playerId, ev) then
        call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, ev))
    endif
  
    set eventPlug = prevPlug
    set eventStation = prevStation
    set prevPlug = null
    set prevStation = null
   
    // This part is an oversight - commented to ensure correct behavior
    // set interruptDocking = false
endfunction

In that function, assume we have EVENT_UNIT_PRE_DOCK firing. When we want to interrupt the docking, it proceeds anyway, because the value of interruptDocking is overridden afterwards.

Also, would system-support for resetting the unit's height to the original configuration by default be made available?
 
Thanks for noticing that. DockingSystem updated to v1.05
- v1.05 Fixed a bug with interruptDocking() not working. DockingSystem will now pick the closest Socket to dock into. Also added the function ResetUnitFlyHeight(your unit) to reset an undocking plug's height on Undocking.

PS: the reason why fly height isn't reset by default on undocking is that I wanted to allow user to decide if they want their, for eg, docked spaceship to leave port at the same height it was docked at, or reset to its original position. Granted, ResetUnitFlyHeight() could potentially be updated with a rate, but at this point it might seem a bit unnecessary to have.

If you have another reason for reseting height by default, though, I'd be interested in hearing about it. It was an idea that was on my mind while I was coding this.

Cheers!
 
So I just realised something... for GetNumberOfSocketsWithState, since I'm already counting the number of occupied sockets when a unit docks or undocks, I might as well reduce the following:
JASS:
function GetNumberOfSocketsWithState takes unit Station, boolean LookForOccupiedSocket returns integer
    local DockingSystem this = DS_Instance[GetUnitUserData(Station)]
    local integer occupiedCount = 0
    local integer i = 0
    local P p
  
    set S.SOCKETS = this.tableInstance
    loop
        set i = i + 1
        exitwhen i > this.maxSockets
        set p = S.SOCKETS.integer[i]
        if LookForOccupiedSocket then
            if p.plug != null then
                set occupiedCount = occupiedCount + 1
            endif
        else
            if p.plug == null then
                set occupiedCount = occupiedCount + 1
            endif
        endif
    endloop
  
    return occupiedCount
endfunction
to this:
JASS:
function GetNumberOfSocketsWithState takes unit Station, boolean LookForOccupiedSocket returns integer
    local integer id_S = GetUnitUserData(Station)
    local DockingSystem this = DS_Instance[id_S]
    if LookForOccupiedSocket then
        return DS_PlugCount[id_S]
    else
        return this.maxSockets - DS_PlugCount[id_S]
    endif
endfunction

EDIT: updated.
 
Last edited:
In that case, did you also notice that you could make a linked list out of the plugs? The linked lists are per instance, not per struct.

Two linked lists, one for unoccupied ones and another for occupied ones. Initially, you would place new plugs in the unoccupied linked list. When these get occupied via dock, they get removed from the unoccupied linked list and moved to the occupied linked list.

So, for example:

4 newly created plugs for a station:
Code:
- unoccupied -> P1, P2, P3, P4
- occupied    -> none

Now, say we plug two of them (at random, P4 being the first and P2 being the second).
Code:
- unoccupied -> P1, P3
- occupied     -> P4, P2

That way, when calling GetRandomSocket, it will internally call GetRandomSocketWithState, like this.

JASS:
function GetRandomSocket takes unit station returns P
    return GetRandomSocketWithState(station, GetRandomInt(0, 1) == 1)
endfunction
 
Last edited:
Just for backwards-compatibility:

JASS:
function GetNumberOfSocketsWithState takes unit Station, boolean occupied returns integer
    if occupied then
        return GetNumberOfOccupiedSockets(Station)
    endif
    return GetNumberOfEmptySockets(Station)
endfunction

The lack of it caused my script to not compile, so this may help those who have used the system since the first iteration.

EDIT:

Also,

JASS:
private function GetSocketFromPlug takes unit u returns P
    return DS_SocketId[GetUnitUserData(u)]
endfunction

is this supposed to be private in nature (you only want the system to utilize the function above, and nowhere else) or readonly (You can get the value but not set it anywhere else)? It doesn't seem to follow the visibility rule of the struct P. (P is visible, while the function isn't)

JASS:
static method addSocket takes unit Station, real x, real y, real z, real facing_angle returns P
   
        local thistype this = DS_Instance[GetUnitUserData(Station)]
        local P p
       
        // if the unit is not a Station this function does nothing.
        if this != 0 then
            set S.SOCKETS = this.tableInstance
            set this.maxSockets = this.maxSockets + 1
           
            // Correction here
            set p = allocate()    // Should be  -> set p = P.allocate()
            set p.x = x
            set p.y = y
            set p.z = z
            set p.facing = facing_angle
            set p.station = Station
            set p.slot = this.maxSockets
           
            set S.SOCKETS.integer[this.maxSockets] = p
        endif
endmethod

A simple correction above. Change allocate() to P.create(), since destroying those pesky P instances results in a double-free error, which is mechanically not, in the way you implemented it.
 
Last edited:
It never occured to me to have backward compatibility. That's a good idea, I'll implement that asap.

As for GetSocketFromPlug I made that function private by mistake xD Thanks for noticing that.

Huh, I'm unfamiliar with that issue. Will fix.

PS: I'll probably update it again once I figure how to code a linked list.
 
No need for that. Just square the number you already have there (999999^2).

Also, I wanted to point out this part

JASS:
function GetClosestSocketWithState takes unit Station, unit Plug, boolean LookForOccupiedSocket returns P
    local DockingSystem this = DS_Instance[GetUnitUserData(Station)]
    local real tempDist
    local real maxRange = 999999.
    local real xPlug = GetUnitX(Plug)
    local real yPlug = GetUnitY(Plug)
    local integer i = 0
   
    local P closestSocket
    local P p
   
    set S.SOCKETS = this.tableInstance
   
    if LookForOccupiedSocket then
        loop
            set i = i + 1
            exitwhen i > this.maxSockets
            set p = S.SOCKETS.integer[i]
            if not (p.plug == null) then
                set tempDist = GetDistanceSquared(p.x, p.y, xPlug, yPlug)
                if tempDist < maxRange then
                    set maxRange = tempDist
                    set closestSocket = p
                endif
            endif
        endloop
    else
        loop
            set i = i + 1
            exitwhen i > this.maxSockets
            set p = S.SOCKETS.integer[i]
            if p.plug == null then
                set tempDist = GetDistanceSquared(p.x, p.y, xPlug, yPlug)
                if tempDist < maxRange then
                    set maxRange = tempDist
                    set closestSocket = p
                endif
            endif
        endloop
    endif
   
    return closestSocket
endfunction

Just in case, set closestSocket first to 0. That way, there would be no thread interruption from the use of an uninitialized variable.

JASS:
loop
    set i = i + 1
    exitwhen i > this.maxSockets
    set p = S.SOCKETS.integer[i]
    if (p.plug == null) != LookForOccupiedSlots then
        ... // the rest of the code.

That would be quite the optimization.

Also, I have not pointed this out yet, but I think parameters are camelCase.
 
So I finally got around to updating this with ListT as optional. However, as I was going through it, it dawned on me that I could have had 2 tables per stations - one for occupied sockets and one for unoccupied sockets to shorten the loops. That beings said, this is basically what the linked lists are doing, except for when you need to pick a random socket, which is then easier with just having a table and rolling a random number. Idk.

JASS:
library DockingSystem /*

DockingSystem v1.05.3
By Spellbound

Special thanks to MyPad for finding out all the bugs and errors and helping me make this system better.

/*/*==== DESCRIPTION ====*/*/

DockingSystem intends to replicate the Undead Haunted Gold Mine system by allowing you to
'dock' units (aka plugs) into a stationary unit, usually a building. The system comes with
the ability to move docking ports (aka sockets) so if you want to dock units to another
unit that's in motion, you may do that as well. This system comes with a lot of features
so take a moment and read through the API.


/*/*==== INSTALLATION ====*/*/

• Copy-paste this trigger into your map.
• Copy-paste the required libraries into your map.
• The system is installed! Copy-paste the onDock Event trigger or make your own
  event trigger and start coding.

*/ requires /*
  
    */ SmartTrack   /* https://www.hiveworkshop.com/threads/smarttrack-v1-02-1.299957/
    */ Table        /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
  
    NB: SmartTrack requires a unit indexer that uses custom value. Either TriggerHappy's UnitDex or
    Bribe's GUI Unit Event/Unit Indexer  is recommended.
  
    Additionally, if you wish to dock units at different heights, you will need an Autofly
    library to allow ground units from changing height freely. The Autofly library in the
    DockingSystem demo map has been modified to work with UnitDex.
  
*/ optional /*

    */ WorldBounds /* https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
    */ ListT       /* https://www.hiveworkshop.com/threads/containers-list-t.249011/
  
    WorldBounds is really not necessary at all, but if you have it in your map (which you generally
    should), the constant MAX_DISTANCE can use its variabled to calculate the longest map distance.

/*/*==== API ====*/*/


/*SETUP and MODIFICATION________*/

DockingSystem.createStation( takes unit station, real socket_trigger_distance )
    This sets up a unit as the station that will then be given sockets for units to 'plug'
    into. Sockets are added separately.
  
DockingSystem.terminateStation( takes unit station )
    This terminates a station instance. Doing do will destroy all added sockets and undock
    any docked units.
  
DockingSystem.addSocket( takes unit station, real x, real y, real z, real facing_angle )
    Creates a socket and adds it to the desires station. Sockets can have any coordinates
    you desire them to have so they can be where ever you want them to be. The last argument,
    facing_angle, will determine the facing of the plug when it docks.
  
    IMPORTANT: This method will return the socket instance. If you wish to refer to this
    specific socket at a later date (eg: if you want to move it) you can do so by storing
    the instance in an integer variable.
  
DockingSystem.removeSocket( takes S socket_instance )
    Removes a socket from a station. Socket instances are obtained upon addSocket.
  
DockingSystem.moveSocket( takes S socket_instance, real x, real y, real z, real facing_angle )
    Alter the parameters of a socket. If a unit is currently docked in that socket, it will
    move with it. Socket instances are obtained upon addSocket.
  
DockingSystem.addSocketPointer( takes unit pointer, real range, boolean kill_pointer, S socket_instance )
    Creates a unit that acts as a pointer to a specific socket. When units dock into a station
    the chosen socket is done at random, but with a pointer you can tell the unit to dock into
    a specific socket. Pointers are proxies for the station itself with their own docking range,
    so placing one too far from the station will defacto teleport plugs around. If the
    socket_isntance is null, then they will teleport units to random open sockets. Socket
    instances are obtained upon addSocket.
  
DockinSystem.dock( takes unit plug, S socket_instance )
    Docks a unit into an empty plug. If the plug is currently occupied, nothing will happen.
  
DockinSystem.undock( takes unit plug )
    Undocks a unit from the station it is currently plugged into and fires an undocking event.
    Useful for when you want to handle the undocking manually.
  
DockingSystem.undockAll( takes unit station )
    Flushes all docked unit from a station. The sockets themselves remain intact.
  
RegisterNativeEvent( takes integer ev, code c )
    Sets up events for docking. The available events are EVENT_ON_PRE_DOCK, EVENT_ON_DOCK and
    EVENT_ON_UNDOCK. They should be fairly self-explanatory, but just in case I'm just
    projecting, here's what they do:
  
    EVENT_ON_PRE_DOCK  == BEFORE the plug has moved into place.
    EVENT_ON_DOCK      == After the plug has moved into place.
    EVENT_ON_UNDOCK    == When docking ends.
    EVENT_ON_REDOCK    == When a docked unit right-clicks its current station.
                       Can be used to issue error messages or reset animations.
                      
RegisterIndexNativeEvent( takes integer playerNumber, integer ev, code c )
    Same as above, but player-specific.
  
  
/*PRE-DOCKING________*/

InterruptDocking()
    call this on EVENT_ON_PRE_DOCK to stop docking from happening.

SetDockingManual()
    call this on EVENT_ON_PRE_DOCK to stop automatic undocking triggers from being created.
    This means that ordering plugs to move or to stop will no undock them, so you will have
    to undock them manually.


/*UNDOCKING________*/

ResetUnitFlyHeight()
    call this on EVENT_ON_UNDOCK to reset a unit's flying height to its default value.


/*REFERRENCE________*/

call GetPlug()
    returns the plug during a docking event
  
call GetStation()
    returns the station during a docking event

call GetRandomSocket( takes unit station )
    returns the instance of a random socket.
  
call GetRandomSocketWithState( takes unit station, boolean is_socket_occupied )
    returns the instance of a socket. If the boolean is true, returns an occupied socket. If
    false, returns a vacant one.
  
call GetClosestSocketWithState( takes unit station, unit plug, boolean is_socket_occupied )
    returns the station's socket that is nearest to the plug.

call GetNumberOfSockets( takes unit station )
    returns the total number of sockets the station has.

call GetNumberOfOccupiedSockets( takes unit station )
    returns the total number of a station's occupied sockets.
  
call GetNumberOfEmptySockets( takes unit station )
    returns the total number of a station's vacant sockets.
  
call GetSocketPlug( takes S socket_instance )
    returns the unit currently docked in that socket. If the socket is empty, returns null.

call GetSocketFromPlug( takes unit plug )
    returns the socket in which a unit is plugged into.

call GetSocketX( takes S socket_instance )
    returns the x co-ordinate of the socket.
  
call GetSocketY(  takes S socket_instance )
    returns the y co-ordinate of the socket.
  
call GetSocketZ( takes S socket_instance )
    returns the z co-ordinate of the socket.
  
call GetSocketFacing( takes S socket_instance )
    returns the facing angle of the socket in degrees.

call GetSocketAngleFromStation( takes S socket_instance, real offset)
    returns the angle between the socket and the station in radians. This is useful for when
    you want to rotate the socket around the station with DockinSystem.moveSocket(), for
    example. The offset value is for when you want to make sure your sockets are referrencing
    the actual center of the station model as some models tend to offset their center by -16
    both on their x and y coordinate. If you station center appears off, try setting the offset
    to 16 or modifying the collision size of the station unit in the object editor.
  
call GetStationAngleFromSocket( takes S socket_instance, real offset)
    Same as above, but the returns the angle from the station to the socket.
  
*/

globals

    private DockingSystem array DS_Instance
    private integer array DS_SocketId
    private integer array DS_PlugCount
  
    private unit eventPlug = null
    private unit eventStation = null
    private boolean interruptDocking = false
    private boolean manualDocking = false
  
    integer EVENT_ON_PRE_DOCK
    integer EVENT_ON_DOCK
    integer EVENT_ON_UNDOCK
    integer EVENT_ON_REDOCK
  
    private real MAX_DISTANCE = 0.
  
endglobals


private struct Station
    static Table s
endstruct


// picks a socket at random and check if it's occupied or not depending on the state of the boolean
// LookForOccupiedSocket. If that first random try doesn't return anything, cycle through the rest
// of the sockets.
function GetRandomSocketWithState takes unit station, boolean lookForOccupiedSocket returns S
  
    local DockingSystem this = DS_Instance[GetUnitUserData(station)]
    local integer i = GetRandomInt(1, this.maxSockets)
    local integer iLoop = i
    local boolean firstPass = false
    local S s
  
    set Station.s = this.tableInstance
    set s = Station.s.integer[i]
    if lookForOccupiedSocket then
        if not (s.plug == null) then
            return s
        endif
    else
        if s.plug == null then
            return s
        endif
    endif
  
    // this loop starts from integer i. Since i is unlikely to be zero, the loop must cycle back
    // to 1 to ensure all sockets are checked.
    loop
        set iLoop = iLoop + 1
        if iLoop > this.maxSockets then
            set firstPass = true // this tells the loop that all values above integer i have already been checked
            set iLoop = 1 // this sets iLoop to the first socket and moves upwards from there.
        endif
        exitwhen (iLoop >= i and firstPass) // if this returns true, then all sockets have been checked.
      
        set s = Station.s.integer[iLoop]
        // (s.plug != null) returns true if the socket is occupied and false if empty.
        // Eg: if true, and LookForOccupiedSocket is also true, then it returns s.
        // If false, but LookForOccupiedSocket is true, the loop moves on to the next socket.
        if (s.plug != null) == lookForOccupiedSocket then
            return s
        endif
    endloop
  
    return 0
endfunction

function GetRandomSocket takes unit station returns S
    local DockingSystem this = DS_Instance[GetUnitUserData(station)]
    local S s
    set Station.s = this.tableInstance
    set s = Station.s.integer[GetRandomInt(1, this.maxSockets)]
    return s
endfunction

function GetSocketFromPlug takes unit u returns S
    return DS_SocketId[GetUnitUserData(u)]
endfunction

// since the purpose of this is to only find which Socket is closest, there is no need to
// perform a costly square root operation since (x < y) is the same as (x*x < y*y).
private function GetDistanceSquared takes real ax, real ay, real bx, real by returns real
    local real dx = bx - ax
    local real dy = by - ay
    return dx * dx + dy * dy
endfunction

// returns the Socket closest to the unit that's about to dock into a Station. This loop through all
// of a Station's sockets and, depending on whether you are looking for an occupied or unoccupied
// Socket, will return the one with the closest unit distance from the Plug.
function GetClosestSocketWithState takes unit station, unit plug, boolean lookForOccupiedSocket returns S
    local DockingSystem this = DS_Instance[GetUnitUserData(station)]
    local real tempDist
    local real maxRange = MAX_DISTANCE
    local real xPlug = GetUnitX(plug)
    local real yPlug = GetUnitY(plug)
    local integer i = 0
  
    local S closestSocket = 0
    local S s
  
    static if LIBRARY_ListT then
      
        local IntegerListItem node
        local IntegerListItem nodeNext
      
        if lookForOccupiedSocket then
            set node = this.busySockets.first
        else
            set node = this.freeSockets.first
        endif
      
        loop
            exitwhen node == 0
            set s = node.data
            set nodeNext = node.next
          
            set tempDist = GetDistanceSquared(s.x, s.y, xPlug, yPlug)
            if tempDist < maxRange then
                set maxRange = tempDist
                set closestSocket = s
            endif
          
            set node = nodeNext
        endloop
      
    else
  
        set Station.s = this.tableInstance
      
        loop
            set i = i + 1
            exitwhen i > this.maxSockets
            set s = Station.s.integer[i]
          
            if (s.plug == null) != lookForOccupiedSocket then
                set tempDist = GetDistanceSquared(s.x, s.y, xPlug, yPlug)
                if tempDist < maxRange then
                    set maxRange = tempDist
                    set closestSocket = s
                endif
            endif
        endloop
      
    endif
  
    return closestSocket
endfunction

function GetNumberOfSockets takes unit station returns integer
    return DS_Instance[GetUnitUserData(station)].maxSockets
endfunction

function GetNumberOfOccupiedSockets takes unit station returns integer
    return DS_PlugCount[GetUnitUserData(station)]
endfunction

function GetNumberOfEmptySockets takes unit station returns integer
    local integer id_S = GetUnitUserData(station)
    return DS_Instance[id_S].maxSockets - DS_PlugCount[id_S]
endfunction

function GetPlug takes nothing returns unit
    return eventPlug
endfunction

function GetStation takes nothing returns unit
    return eventStation
endfunction

function GetSocketPlug takes S s returns unit
    return s.plug
endfunction

function GetSocketX takes S s returns real
    return s.x
endfunction

function GetSocketY takes S s returns real
    return s.y
endfunction

function GetSocketZ takes S s returns real
    return s.z
endfunction

function GetSocketFacing takes S s returns real
    return s.facing
endfunction

function GetSocketAngleFromStation takes S s, real Offset returns real
    return Atan2((GetUnitY(s.station) + Offset) - s.y, (GetUnitX(s.station) + Offset) - s.x)
endfunction

function GetStationAngleFromSocket takes S s, real Offset returns real
    return Atan2(s.y - (GetUnitY(s.station) + Offset), s.x - (GetUnitX(s.station) + Offset))
endfunction

// Backward compatibility

function GetNumberOfSocketsWithState takes unit station, boolean occupied returns integer
if occupied then
    return GetNumberOfOccupiedSockets(station)
endif
return GetNumberOfEmptySockets(station)
endfunction

// Pre-dock actions

// Units will not automatically un-dock when this is called on pre-dock.
function SetDockingManual takes nothing returns nothing
    set manualDocking = true
endfunction

// This will interrupt docking when called on pre-dock
function InterruptDocking takes nothing returns nothing
    set interruptDocking = true
endfunction

// This will interrupt docking when called on pre-dock
function ResetUnitFlyHeight takes unit u returns nothing
    call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0.)
endfunction

//S for Socket
struct S

    real x
    real y
    real z
    real facing
    unit plug
    unit station
    unit socketPointer
    boolean killSocketPointer
    boolean ignoreOrder
    integer slot
    trigger trig
  
    method destroy takes nothing returns nothing
        if this.plug != null then
            call DockingSystem.undock.evaluate(this.plug)
        endif
      
        set this.station = null
        set this.slot = 0
      
        if this.killSocketPointer then
            call UnitApplyTimedLife(this.socketPointer, 'BTLF', .01)
            set this.killSocketPointer = false
        endif
      
        if this.socketPointer != null then
            set DS_SocketId[GetUnitUserData(this.socketPointer)] = 0
            set this.socketPointer = null
        endif
      
        call this.deallocate()
    endmethod
  
endstruct


// Fires one of the four custom events of DockingSystem. These are registered with RegisterDockEvent above.
private function FireEvent takes unit plug, unit station, integer ev returns nothing
    local integer playerId = GetPlayerId(GetOwningPlayer(plug))
    local unit prevPlug = eventPlug
    local unit prevStation = eventStation
  
    set eventPlug = plug
    set eventStation = station
  
    call TriggerEvaluate(GetNativeEventTrigger(ev))
    if IsNativeEventRegistered(playerId, ev) then
        call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, ev))
    endif
  
    set eventPlug = prevPlug
    set eventStation = prevStation
    set prevPlug = null
    set prevStation = null
endfunction


struct DockingSystem
  
    readonly Table tableInstance
    readonly integer maxSockets
  
    static if LIBRARY_ListT then
        readonly IntegerList busySockets
        readonly IntegerList freeSockets
    endif
  
    // stuns the unit after 0 seconds. Needed to prevent docked units from keeping on walking
    // once they get in range of a station. Also used to stop plugs from walking into a building
    // when they right-click on their current station.
    private static method zeroTimerStun takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local S s = GetTimerData(t)
      
        set s.ignoreOrder = true
        call IssueImmediateOrderById(s.plug, 851973)//order stunned
        set s.ignoreOrder = false
        call ReleaseTimer(t)
      
        set t = null
    endmethod
  
  
    // core undocking method.
    static method undock takes unit plug returns nothing
      
        local integer id_P = GetUnitUserData(plug)
        local S s = DS_SocketId[id_P]
        local integer id_S
      
        // if s is 0 that means the unit is not docked and thus nothing happens.
        if s != 0 then
            set id_S = GetUnitUserData(s.station)
            set DS_PlugCount[id_S] = DS_PlugCount[id_S] - 1
            call SetUnitPathing(plug, true)
            set s.plug = null
            call DestroyTrigger(s.trig)
            set s.trig = null
          
            static if LIBRARY_ListT then
                call DS_Instance[id_S].busySockets.removeElem(s)
                call DS_Instance[id_S].freeSockets.push(s)
            endif
          
            call FireEvent(plug, s.station, EVENT_ON_UNDOCK)
            set DS_SocketId[id_P] = 0
        endif
      
    endmethod
  
  
    // this function cycles through all of a station's sockets and, if occupied, undocks them.
    static method undockAll takes unit station returns nothing
      
        local integer id_S = GetUnitUserData(station)
        local thistype this = DS_Instance[id_S]
        local integer i
        local S s
      
        static if LIBRARY_ListT then
            local IntegerListItem node = this.busySockets.first
            local IntegerListItem nodeNext
        endif
      
        // if unit station is not an actual station or has no sockets, this does nothing.
        if this != 0 then
          
            static if LIBRARY_ListT then
              
                loop
                    exitwhen node == 0
                    set nodeNext = node.next
                    set s = node.data
                    call DockingSystem.undock(s.plug)
                    set node = nodeNext
                endloop
              
            else
                set Station.s = this.tableInstance
                set i = this.maxSockets // goes from top to bottom.
                loop
                    set s = Station.s.integer[i]
                    if s.plug != null then
                        call DockingSystem.undock(s.plug)
                    endif
                    set i = i - 1
                    exitwhen i <= 0
                endloop
            endif
          
        endif
      
    endmethod
  
  
    // this function handles the order events of docked unit (aka plugs). Right-clicking on the
    // unit the plug is currently docked at will do nothing - this is handled in dockPrep.
    private static method trackOrders takes nothing returns nothing
        local unit plug     = GetTriggerUnit()
        local unit station  = GetOrderTargetUnit()
        local S s = DS_SocketId[GetUnitUserData(plug)]
      
        if not s.ignoreOrder then
            if not (station == s.station and GetIssuedOrderId() == ORDER_SMART) then
                call DockingSystem.undock(plug)
            endif
        endif
      
        set plug = null
        set station = null
    endmethod
  
  
    // core dock method.
    static method dock takes unit plug, S s returns nothing
      
        local integer id_S = GetUnitUserData(s.station)
      
        call FireEvent(plug, s.station, EVENT_ON_PRE_DOCK)
      
        if not interruptDocking then
          
            call TimerStart(NewTimerEx(s), 0., false, function thistype.zeroTimerStun)
          
            call SetUnitPathing(plug, false)
            call SetUnitX(plug, s.x)
            call SetUnitY(plug, s.y)
          
            if GetUnitAbilityLevel(plug, 852155) > 0 then
                call UnitAddAbility(plug, 852155)
                call UnitRemoveAbility(plug, 852155)
            endif
          
            call SetUnitFlyHeight(plug, s.z, 0.)
            call SetUnitFacing(plug, s.facing)
          
            set s.plug = plug
            set DS_SocketId[GetUnitUserData(plug)] = s
          
            if not manualDocking then
                set s.trig = CreateTrigger()
                call TriggerRegisterUnitEvent(s.trig, plug, EVENT_UNIT_ISSUED_TARGET_ORDER)
                call TriggerRegisterUnitEvent(s.trig, plug, EVENT_UNIT_ISSUED_POINT_ORDER)
                call TriggerRegisterUnitEvent(s.trig, plug, EVENT_UNIT_ISSUED_ORDER)
                call TriggerAddCondition(s.trig, function thistype.trackOrders)
            else
                set manualDocking = false
            endif
          
            set DS_PlugCount[id_S] = DS_PlugCount[id_S] + 1
          
            static if LIBRARY_ListT then
                call DS_Instance[id_S].freeSockets.removeElem(s)
                call DS_Instance[id_S].busySockets.push(s)
            endif
          
            call FireEvent(plug, s.station, EVENT_ON_DOCK)
          
        endif
      
        set interruptDocking = false
      
    endmethod
  
  
    // this function runs when a right-clicker is in range of a station.
    static method dockPrep takes nothing returns nothing
        local unit      station = GetTracker()
        local unit      plug = GetSmartUnit()
        local boolean   firstPass = false
        local boolean   exitLoop = false
      
        local thistype  this
        local S s = DS_SocketId[GetUnitUserData(plug)]
          
        // if s has no value, then the unit is not docked anywhere.
        if s == 0 then
            set s = GetClosestSocketWithState(station, plug, false)
            if s != 0 then
                call DockingSystem.dock(plug, s)
            endif
          
        // if s has a value, then the unit is already docked somewhere.
        else
            // if the station you right-clicked is the same station the unit is docked at, issue a stunned order and fires a EVENT_ON_REDOCK event.
            if s.station == station then
                call TimerStart(NewTimerEx(s), 0., false, function thistype.zeroTimerStun)
                call FireEvent(plug, s.station, EVENT_ON_REDOCK)
            else // undock the plug and dock it in the other station
                call DockingSystem.undock(plug)
                set s = GetClosestSocketWithState(station, plug, false)
                if s != 0 then
                    call DockingSystem.dock(plug, s)
                endif
            endif
          
        endif
      
        set station = null
        set plug = null
    endmethod
  
  
    // removes a socket from a station. If the socket is occupied, the unit is first undocked.
    static method removeSocket takes S s returns nothing
      
        local thistype this = DS_Instance[GetUnitUserData(s.station)]
        local integer i = s.slot
      
        if this != 0 then
          
            set Station.s = this.tableInstance
          
            static if LIBRARY_ListT then
                if s.plug == null then
                    call this.freeSockets.removeElem(s)
                else
                    call this.busySockets.removeElem(s)
                endif
            endif
          
            call s.destroy()
            call Station.s.remove(i)
          
            loop
                set Station.s.integer[i] = Station.s.integer[i + 1]
                set i = i + 1
                exitwhen i >= this.maxSockets
            endloop
          
            set this.maxSockets = this.maxSockets - 1
          
        endif
      
    endmethod
  
  
    // changes the x/y coordinates of a socket. If there a unit docked into it, the unit moves as well.
    static method moveSocket takes S s, real x, real y, real z, real facing returns nothing
        set s.x = x
        set s.y = y
        set s.z = z
        set s.facing = facing
      
        if s.plug != null then
            //Move plug
            call SetUnitX(s.plug, x)
            call SetUnitY(s.plug, y)
            call SetUnitFacing(s.plug, facing)
            call SetUnitFlyHeight(s.plug, z, 0.)
        endif
    endmethod
  
  
    // socket pointers are proxies that allow you to dock a unit to a specific socket. See documentation for more info.
    static method addSocketPointer takes unit pointer, real range, boolean killPointer, S s returns nothing
        if pointer != null then
            set s.socketPointer = pointer
            set s.killSocketPointer = killPointer
            set DS_SocketId[GetUnitUserData(pointer)] = s
            call CreateSmartOrderTracker(pointer, range)
        endif
    endmethod
  
  
    // adds a socket to a station.
    static method addSocket takes unit station, real x, real y, real z, real facingAngle returns S
  
        local thistype this = DS_Instance[GetUnitUserData(station)]
        local S s
      
        // if the unit is not a Station this function does nothing.
        if this != 0 then
            set Station.s = this.tableInstance
            set this.maxSockets = this.maxSockets + 1
          
            set s = S.create()
            set s.x = x
            set s.y = y
            set s.z = z
            set s.facing = facingAngle
            set s.station = station
            set s.slot = this.maxSockets
          
            set Station.s.integer[this.maxSockets] = s
          
            static if LIBRARY_ListT then
                call this.freeSockets.push(s)
            endif
          
        endif
      
        return s
    endmethod
  
  
    // this terminates the Station instance bound to a unit. Will undock all currectly-docked units.
    static method terminateStation takes unit station returns nothing
      
        local integer id_S = GetUnitUserData(station)
        local thistype this = DS_Instance[id_S]
        local integer i
      
        if this != 0 then
            set Station.s = this.tableInstance
            set i = this.maxSockets
          
            loop
                call removeSocket(Station.s.integer[i])
                set i = i - 1
                exitwhen i <= 0
            endloop
          
            set this.maxSockets = 0
            call CancelSmartOrderTracker(station)
            set DS_Instance[id_S] = 0
            call Station.s.destroy()
          
            static if LIBRARY_ListT then
                call this.busySockets.destroy()
                call this.freeSockets.destroy()
            endif
          
            call this.deallocate()
        endif
      
    endmethod
  
  
    // establishes a unit as a Station. Sockets are added separately. Only 1 Station instance
    // allowed per unit.
    static method createStation takes unit station, real socketTriggerDistance returns nothing
  
        local integer id_S = GetUnitUserData(station)
        local thistype this = DS_Instance[id_S]
      
        if this == 0 then
            set Station.s = Table.create()
            set this = allocate()
            set this.tableInstance = Station.s
            set this.maxSockets = 0
            call CreateSmartOrderTracker(station, socketTriggerDistance)
            set DS_Instance[id_S] = this
            set DS_PlugCount[id_S] = 0
          
            static if LIBRARY_ListT then
                set this.freeSockets = IntegerList.create()
                set this.busySockets = IntegerList.create()
            endif
          
        endif
      
    endmethod
  
endstruct

// this function sets up all events related to DockingSystem. It also sets up SmartTrack to fire
// DockingSystem.dockPrep whenever a unit gets in range of a tracker.
private module DockingSystemInit
    private static method onInit takes nothing returns nothing
  
        static if LIBRARY_WorldBounds then
            set MAX_DISTANCE =  (WorldBounds.maxX - WorldBounds.minX)*(WorldBounds.maxX - WorldBounds.minX) + /*
            */                  (WorldBounds.maxY - WorldBounds.minY)*(WorldBounds.maxY - WorldBounds.minY)
        else
            set MAX_DISTANCE =  999999. ^ 2
        endif
      
        set EVENT_ON_PRE_DOCK  = CreateNativeEvent()
        set EVENT_ON_DOCK      = CreateNativeEvent()
        set EVENT_ON_UNDOCK    = CreateNativeEvent()
        set EVENT_ON_REDOCK    = CreateNativeEvent()
      
        call RegisterNativeEvent(EVENT_SMART_TRACK_IN_RANGE, function DockingSystem.dockPrep)
    endmethod
endmodule

private struct init
    implement DockingSystemInit
endstruct

endlibrary

If there's no errors or new ways I could code/optimise this, then I'll go ahead an update DockingSystem to v1.05.3
 

Nitpicks:

  • Just to follow naming convention, rename the socket struct, which is S right now, to either just Socket or Plug.
  • I didn't see this when approving, but 999999^2 wouldn't compile. Try 999999*999999 or Pow(2, 128).

Notes:

  • None

Other than those, I think the system works well enough for approval.

Status:

  • Approved
 
Last edited:
Updated:
- v1.05.3 Added WorldBounds and ListT as optional requirements to for safety checks and slight performance boost respectively. The socket struct is now names Socket, as it should have always been (instead of P for plug - I had been confusing my own terminologies >_>). Finally fixed a potential bug and made the coding style more closely follow camelCase.
 
You need to start a periodic timer which calls DockingSystem.moveSocket( takes Socket socket_instance, real x, real y, real z, real facing_angle ) every .03125 seconds. End the timer when the ancient roots.

Refer to Demo > RotatingSockets for an example of how the code is used.

Thanks for reply and help, i feel like i will be messing around all day and not get anywhere lol i am new to the jass thing anyways, i understand sort of what i have to do, although i have no idea how to set it, my end goal really is to have one, so a hero can mount a dragon and still be functional as hero too, in a 2 player campaign im doing lol, i believe this system can do it, i just dont have to the working knowledge to do it. def using for tower docking but yea, dont suppose youd want to do up a little code of that movement for me? lol, i dont seem to know what im doing there lol, might even be tempted to make a little donation if so (shh), but dont feel like you gotta, but would be greatly appreciated lol
 
As Archaos was asking, how can you update the sockets to move with the station? Currently they stay in one spot circling and if the station moves they stop spinning and get stuck until the station moves back. Also can this be used in GUI with custom script?

Edit: Would like to keep the spinning while having it move with the station, currently can only get one or the other.
Edit2: never mind, figured it out.
one needs to update the origin points if anyone wanted the answer.

set angleNew = GetStationAngleFromSocket(s, this.offset) + RS.socketTable.real[2]
set xNew = this.x + Cos(angleNew) * RS.socketTable.real[3]
set yNew = this.y + Sin(angleNew) * RS.socketTable.real[3]
call DockingSystem.moveSocket(s, xNew, yNew, RS.socketTable.real[4], angleNew * bj_RADTODEG)
set this.x = this.offset + GetUnitX(gg_unit_uzg1_0036)
set this.y = this.offset + GetUnitY(gg_unit_uzg1_0036)
 
Last edited:
Top