- Joined
- Jan 9, 2005
- Messages
- 2,126
09/10/2017
SmartTrack has been really helpful in helping me recreate my Portal system as well as make the Docking System I made in one of my Techtree Contest submissions shareable. I've updated the demo map for SmartTrack to include all these libraries, and I have to say, they work pretty damn well. It was also an opportunity for me to learn how to use Table, so that's another bonus.
I do have one question, however. The only method I've known for creating custom events before I started this project was to use the real variable method, but that has proven to not be MUI in all situations. I'm not sure what those situations are, but I've been having significant overlap while testing
SmartTrack, which prompted me to use the TriggerEvaluate method. Currently, SmartTrack and DockingSystem are both using that, but CustomWaygate is still using the variable method. Should I just change CustomWaygate's event style for the sake of consistency?
Download Demo: SmartTrack
----------------------------------------------------------------
30/09/2017
So I've been looking for a way for the game to keep track of which unit right-clicked which unit for some resources I've been trying to code (like a custom Waygate), so I figured I'd create a library designed specifically for that. However, I'm not entirely certain as to its implementation.
The way this works is that you initially identify which units will be Trackers. The units will have a range associated with them so that any other unit that is given the Smart order will be added to an array variable. Upon reaching the defined range of the Tracker, a real variable event fires. Issuing any order other than right-clicking on another Tracker will remove the unit from the array variable. I call these units Smarties because creativity is hard and I just wanted to get on with coding the thing.
A demo map is attached if you wish to the system in action.
There are 2 things that are bugging me about this:
1. I have preset events (eg:
2. I'm having to track the Tracker itself by binding it to the handleId of the trigger with a hashtable. Is there any other way to do this?
If you have a moment, could you review the code? It's quite simple and initially started out with the ability to create multiple instances of range-detection, but I couldn't really find a use for it so I scrapped it.
SmartTrack has been really helpful in helping me recreate my Portal system as well as make the Docking System I made in one of my Techtree Contest submissions shareable. I've updated the demo map for SmartTrack to include all these libraries, and I have to say, they work pretty damn well. It was also an opportunity for me to learn how to use Table, so that's another bonus.
I do have one question, however. The only method I've known for creating custom events before I started this project was to use the real variable method, but that has proven to not be MUI in all situations. I'm not sure what those situations are, but I've been having significant overlap while testing
SmartTrack, which prompted me to use the TriggerEvaluate method. Currently, SmartTrack and DockingSystem are both using that, but CustomWaygate is still using the variable method. Should I just change CustomWaygate's event style for the sake of consistency?
Download Demo: SmartTrack
JASS:
library CustomWaygate initializer init /*
By Spellbound
==== DESCRIPTION ====
Custom Waygate allows two units to be connected via one-way 'tunnels'. A unit can have
multiple tunnels, which creates a network that allows the player to chose, through
right-clicking, the destination portal.
==== INSTALLATION ====
• Copy-paste this trigger into your map.
• Copy-paste the required libraries into your map.
• The system is installed! Copy-paste the onCustomWaygate Event trigger or make your own
event trigger and start coding.
*/ requires /*
*/ SmartTrack, /* Comes with this map
*/ Table, /* [URL='https://www.hiveworkshop.com/threads/snippet-new-table.188084/'][Snippet] New Table[/URL]
*/ TimerUtils /* [URL='http://www.wc3c.net/showthread.php?t=101322']Database Error[/URL]
*/ optional /*
*/ BlizzardMessage /* [URL='https://www.hiveworkshop.com/threads/snippet-blizzardmessage.237845/'][Snippet] BlizzardMessage[/URL]
==== API ====
SETUP________
CustomWaygate.createTunnel( takes unit source, unit target, real teleport_distance, real timeout )
Sets up a tunnel that will teleport unit from the source to the target. The teleport_dsitance
determines how close to the source the teleporting units have to be to use it. The timeout
determines if there's a channeling time before teleportation itself occurs.
To create a two-way teleporter, simply repeat this function but set the target as the source
and the source as the target.
A waygate become a network the moment it has more than one tunnel.
CustomWaygate.destroyTunnel( takes unit source, unit target )
Destroys a tunnel. This will only destroy the tunnel going from the source to the target.
If the target has a tunnel going to the source, it will be unaffected.
EVENTS________
WAYGATE_EVENT
The events that fire on pre-teleportation, post-teleportation, teleport prepping and
teleport cancellation
0.50 == right before a unit it moved.
1.00 == right after a unit is moved.
1.50 == When teleportation has a duration, this fires as that duration starts. It ends
at pre-teleportation, or 0.50.
2.00 == When teleportation has been cancelled, either manually or through INTERRUPT_TELEPORT.
See below.
WAYGATE_SOURCE
The waygate unit itself. Origin waygate.
WAYGATE_TARGET
The waygate your units teleport to. Destination.
WAYGATE_TRAVELLER
The units that are using the waygate.
INTERRUPT_TELEPORT
Set this to true on 0.50 (pre-teleportaton) if you wish to interrupt the process and handle
teleportation yourself. Such situations would be if you want to have your teleporter shoot
your traveller as a missile towards the destination instead of just instantly displacing them.
*/
globals
private constant real NETWORK_TIMEOUT = .03125
private constant real DESTINATION_MESSAGE_COOLDOWN = 5.
private constant string MSG_INVALID = "Invalid target."
private constant string MSG_PICK_DESTINATION = "Right-click on your destination to teleport."
integer array NumberOfEntrances
integer array NumberOfExits
integer array EntrancesNetworkInstance
integer array ExitsNetworkInstance
integer array TeleportTimeInstance
private integer array TravellerInstance
private integer array NumberOfInstances
private boolean array isPlayMessageOnCooldown
//Events
real WAYGATE_EVENT = 0.
unit WAYGATE_SOURCE = null
unit WAYGATE_TARGET = null
unit WAYGATE_TRAVELLER = null
boolean INTERRUPT_TELEPORT = false
endglobals
//G stands for Gate
private struct G extends array
static Table EXITS
static Table ENTRANCES
static Table TIMEOUT
endstruct
private struct PreventMessageSpam
integer play_num
private method destroy takes nothing returns nothing
call this.deallocate()
endmethod
private static method Timer takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
set isPlayMessageOnCooldown[this.play_num] = false
call ReleaseTimer(t)
call this.destroy()
set t = null
endmethod
static method start takes integer player_number returns nothing
local thistype this = allocate()
set this.play_num = player_number
set isPlayMessageOnCooldown[player_number] = true
call TimerStart(NewTimerEx(this), DESTINATION_MESSAGE_COOLDOWN, false, function thistype.Timer)
endmethod
endstruct
struct orderStunTimer
unit u
boolean b
private static method zeroTimerStun takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local CustomWaygate ti = TravellerInstance[GetUnitUserData(this.u)]
set ti.ignoreOrder = true
set ST_IGNORE_ORDERS = this.b
call IssueImmediateOrderById(this.u, 851973)//order stunned
set ST_IGNORE_ORDERS = false
set ti.ignoreOrder = false
set this.u = null
set this.b = false
call ReleaseTimer(t)
call this.deallocate()
set t = null
endmethod
static method start takes unit u, boolean b returns nothing
local thistype this = allocate()
set this.u = u
set this.b = b
call TimerStart(NewTimerEx(this), 0., false, function thistype.zeroTimerStun)
endmethod
endstruct
struct CustomWaygate
unit source
unit target
unit traveller
real countdown
trigger trig
trigger networkTrig
boolean isTeleportCancelled
boolean ignoreOrder
boolean teleportReady
integer timeoutSlot
integer tunnelInstance
timer clock
private method destroy takes nothing returns nothing
set this.source = null
set this.target = null
set this.traveller = null
if this.trig != null then
call DestroyTrigger(this.trig)
set this.trig = null
endif
if this.clock != null then
call ReleaseTimer(this.clock)
set this.clock = null
endif
set this.networkTrig = null
call this.deallocate()
endmethod
private static method simpleTeleport takes unit source, unit target, unit traveller returns nothing
local real xSource
local real ySource
local real xTraveller
local real yTraveller
local real xDestination
local real yDestination
local real angle
local real distance
local thistype this = allocate()
set xTraveller = GetUnitX(traveller)
set yTraveller = GetUnitY(traveller)
set xSource = GetUnitX(source)
set ySource = GetUnitY(source)
set angle = Atan2(yTraveller - ySource, xTraveller - xSource)
set distance = SquareRoot( (xTraveller - xSource)*(xTraveller - xSource) + (yTraveller - ySource)*(yTraveller - ySource) )
set xDestination = GetUnitX(target) + Cos(angle) * distance
set yDestination = GetUnitY(target) + Sin(angle) * distance
call SetUnitX(traveller, xDestination)
call SetUnitY(traveller, yDestination)
call orderStunTimer.start(traveller, true)
endmethod
private static method cancelTeleport takes nothing returns nothing
local unit traveller = GetTriggerUnit()
local unit source = GetOrderTargetUnit()
local integer id_T = GetUnitUserData(traveller)
local thistype this = TravellerInstance[id_T]
if not (source == this.source and GetIssuedOrderId() == ORDER_SMART) then
//Events
set WAYGATE_SOURCE = this.source
set WAYGATE_TARGET = this.target
set WAYGATE_TRAVELLER = this.traveller
set WAYGATE_EVENT = 2.
set WAYGATE_SOURCE = null
set WAYGATE_TARGET = null
set WAYGATE_TRAVELLER = null
set INTERRUPT_TELEPORT = false
set WAYGATE_EVENT = 0.
if this.clock == null then
call this.destroy()
else
set this.isTeleportCancelled = true
endif
endif
set source = null
set traveller = null
//This instance is bound to isTeleportCancelled, which doesn't repeat and will
//terminate on its own when it expires.
endmethod
private static method teleportTimer takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local integer id_T = GetUnitUserData(this.traveller)
call DestroyTrigger(this.trig)
set this.trig = null
set G.EXITS = this.tunnelInstance
if not this.isTeleportCancelled and UnitAlive(this.source) and UnitAlive(this.traveller) and G.EXITS.unit[1] != null and TravellerInstance[id_T] == this then
//Events
set WAYGATE_SOURCE = this.source
set WAYGATE_TARGET = this.target
set WAYGATE_TRAVELLER = this.traveller
set WAYGATE_EVENT = .5
if not INTERRUPT_TELEPORT then
call CustomWaygate.simpleTeleport(this.source, this.target, this.traveller)
set WAYGATE_EVENT = 1.
else
set WAYGATE_EVENT = 2.
endif
set WAYGATE_SOURCE = null
set WAYGATE_TARGET = null
set WAYGATE_TRAVELLER = null
set INTERRUPT_TELEPORT = false
set WAYGATE_EVENT = 0.
endif
set NumberOfInstances[id_T] = NumberOfInstances[id_T] - 1
if NumberOfInstances[id_T] == 0 then
set TravellerInstance[id_T] = 0
endif
call this.destroy()
set t = null
endmethod
static method initiateTeleportation takes unit source, unit target, unit traveller, real timeout, integer tunnel_id returns nothing
local integer id_T
local thistype this
if timeout > 0. then
set this = allocate()
set id_T = GetUnitUserData(traveller)
set NumberOfInstances[id_T] = NumberOfInstances[id_T] + 1
set TravellerInstance[id_T] = this
set this.source = source
set this.target = target
set this.traveller = traveller
set this.isTeleportCancelled = false
set this.tunnelInstance = tunnel_id
set this.trig = CreateTrigger()
call TriggerRegisterUnitEvent(this.trig, traveller, EVENT_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterUnitEvent(this.trig, traveller, EVENT_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterUnitEvent(this.trig, traveller, EVENT_UNIT_ISSUED_ORDER)
call TriggerAddCondition(this.trig, function thistype.cancelTeleport)
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, timeout, false, function thistype.teleportTimer)
//Events
set WAYGATE_SOURCE = source
set WAYGATE_TARGET = target
set WAYGATE_TRAVELLER = traveller
set WAYGATE_EVENT = 1.5
set WAYGATE_SOURCE = null
set WAYGATE_TARGET = null
set WAYGATE_TRAVELLER = null
set INTERRUPT_TELEPORT = false
set WAYGATE_EVENT = 0.
else
//Events
set WAYGATE_SOURCE = source
set WAYGATE_TARGET = target
set WAYGATE_TRAVELLER = traveller
set WAYGATE_EVENT = .5
if not INTERRUPT_TELEPORT then
call CustomWaygate.simpleTeleport(source, target, traveller)
set WAYGATE_EVENT = 1.
else
set WAYGATE_EVENT = 2.
endif
set WAYGATE_SOURCE = null
set WAYGATE_TARGET = null
set WAYGATE_TRAVELLER = null
set INTERRUPT_TELEPORT = false
set WAYGATE_EVENT = 0.
endif
endmethod
//NETWORK TELEPORT
static method networkTeleport takes nothing returns nothing
local unit traveller = GetTriggerUnit()
local unit target = GetOrderTargetUnit()
local thistype this = TravellerInstance[GetUnitUserData(traveller)]
local integer id_S = GetUnitUserData(this.source)
local integer i = 0
local boolean isUnitValid = false
local timer t
local player play
if not this.ignoreOrder then
if target != null and GetIssuedOrderId() == ORDER_SMART then
if target != this.source then
set G.EXITS = ExitsNetworkInstance[id_S]
loop
set i = i + 1
if target == G.EXITS.unit[I] then
set isUnitValid = true
endif
exitwhen i > NumberOfExits[id_S] or isUnitValid
endloop
if isUnitValid then
set this.target = target
set this.timeoutSlot = i
call orderStunTimer.start(traveller, false)
else
if SmartTrack_IsATracker[GetUnitUserData(target)] then
set this.isTeleportCancelled = true
else
static if LIBRARY_BlizzardMessage then
call BlizzardMessage(MSG_INVALID, "|cffffcc00", 31, GetOwningPlayer(traveller))
else
set play = GetOwningPlayer(traveller)
if play == GetLocalPlayer() then
call DisplayTimedTextToPlayer(play, 0, 0, 5., MSG_INVALID)
endif
endif
call orderStunTimer.start(traveller, false)
endif
endif
endif
else
set this.isTeleportCancelled = true
endif
endif
endmethod
private static method networkTeleportCountdown takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local integer id_T = GetUnitUserData(this.traveller)
if not this.teleportReady then
set this.countdown = this.countdown - NETWORK_TIMEOUT
if this.countdown <= 0. then
set this.teleportReady = true
//Events
set WAYGATE_SOURCE = this.source
set WAYGATE_TARGET = this.target
set WAYGATE_TRAVELLER = this.traveller
set WAYGATE_EVENT = 1.75
set WAYGATE_SOURCE = null
set WAYGATE_TARGET = null
set WAYGATE_TRAVELLER = null
set INTERRUPT_TELEPORT = false
set WAYGATE_EVENT = 0.
endif
endif
if this.isTeleportCancelled then
set NumberOfInstances[id_T] = NumberOfInstances[id_T] - 1
if NumberOfInstances[id_T] == 0 then
set TravellerInstance[id_T] = 0
endif
call DestroyTrigger(this.networkTrig)
set this.networkTrig = null
//Events
set WAYGATE_SOURCE = this.source
set WAYGATE_TARGET = this.target
set WAYGATE_TRAVELLER = this.traveller
set WAYGATE_EVENT = 2.
set WAYGATE_SOURCE = null
set WAYGATE_TARGET = null
set WAYGATE_TRAVELLER = null
set INTERRUPT_TELEPORT = false
set WAYGATE_EVENT = 0.
call this.destroy()
elseif this.target != null and this.teleportReady then
//Events
set WAYGATE_SOURCE = this.source
set WAYGATE_TARGET = this.target
set WAYGATE_TRAVELLER = this.traveller
set WAYGATE_EVENT = .5
set NumberOfInstances[id_T] = NumberOfInstances[id_T] - 1
if NumberOfInstances[id_T] == 0 then
set TravellerInstance[id_T] = 0
endif
call DestroyTrigger(this.networkTrig)
set this.networkTrig = null
if not INTERRUPT_TELEPORT then
call CustomWaygate.simpleTeleport(this.source, this.target, this.traveller)
set WAYGATE_EVENT = 1.
else
set WAYGATE_EVENT = 2.
endif
set WAYGATE_SOURCE = null
set WAYGATE_TARGET = null
set WAYGATE_TRAVELLER = null
set INTERRUPT_TELEPORT = false
set WAYGATE_EVENT = 0.
call this.destroy()
endif
set t = null
endmethod
static method preTeleportation takes nothing returns nothing
local unit source = ST_EVENT_TRACKER
local unit traveller = ST_EVENT_UNIT
local integer id_S = GetUnitUserData(source) //The Waygate
local integer id_T = GetUnitUserData(traveller)
local integer player_number
local integer i
local player play
local thistype this = TravellerInstance[id_T]
if this.source != source then
if this != 0 then
set this.isTeleportCancelled = true
endif
set G.EXITS = ExitsNetworkInstance[id_S]
set G.TIMEOUT = TeleportTimeInstance[id_S]
if NumberOfExits[id_S] == 1 then
call initiateTeleportation(source, G.EXITS.unit[1], traveller, G.TIMEOUT.real[1], G.EXITS)
elseif NumberOfExits[id_S] > 1 then
set this = allocate()
set NumberOfInstances[id_T] = NumberOfInstances[id_T] + 1
set TravellerInstance[id_T] = this
set this.source = source
set this.target = null
set this.traveller = traveller
set this.isTeleportCancelled = false
set this.teleportReady = false
set this.ignoreOrder = false
set this.countdown = G.TIMEOUT.real[1]
set this.networkTrig = CreateTrigger()
call TriggerRegisterUnitEvent(this.networkTrig, traveller, EVENT_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterUnitEvent(this.networkTrig, traveller, EVENT_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterUnitEvent(this.networkTrig, traveller, EVENT_UNIT_ISSUED_ORDER)
call TriggerAddCondition(this.networkTrig, function thistype.networkTeleport)
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, NETWORK_TIMEOUT, true, function thistype.networkTeleportCountdown)
set play = GetOwningPlayer(traveller)
set player_number = GetPlayerId(play)
if not isPlayMessageOnCooldown[player_number] then
static if LIBRARY_BlizzardMessage then
call BlizzardMessage(MSG_PICK_DESTINATION, "|cffffcc00", 132, play)
else
if play == GetLocalPlayer() then
call DisplayTimedTextToPlayer(play, 0, 0, 5., MSG_PICK_DESTINATION)
endif
endif
call PreventMessageSpam.start(player_number)
endif
//Events
set WAYGATE_SOURCE = source
set WAYGATE_TRAVELLER = traveller
set WAYGATE_EVENT = 1.5
set WAYGATE_SOURCE = null
set WAYGATE_TRAVELLER = null
set INTERRUPT_TELEPORT = false
set WAYGATE_EVENT = 0.
endif
endif
set source = null
set traveller = null
endmethod
static method destroyTunnel takes unit source, unit target returns nothing
local integer id_S = GetUnitUserData(source)
local integer id_T = GetUnitUserData(target)
local integer i = 0
local boolean isUnitValid = false
//Source
if ExitsNetworkInstance[id_S] != 0 then
set G.EXITS = ExitsNetworkInstance[id_S]
set G.TIMEOUT = TeleportTimeInstance[id_S]
loop
set i = i + 1
if target == G.EXITS.unit[I] then
set isUnitValid = true
endif
exitwhen i > NumberOfExits[id_S] or isUnitValid
endloop
if isUnitValid then
call G.EXITS.remove(i)
call G.TIMEOUT.remove(i)
loop
exitwhen i > NumberOfExits[id_S]
set G.EXITS.unit[I] = G.EXITS.unit[i + 1]
set G.TIMEOUT.real[I] = G.TIMEOUT.real[i + 1]
set i = i + 1
endloop
call G.EXITS.remove(NumberOfExits[id_S])
call G.TIMEOUT.remove(NumberOfExits[id_S])
set NumberOfExits[id_S] = NumberOfExits[id_S] - 1
endif
if NumberOfExits[id_S] < 1 then
call G.EXITS.destroy()
call G.TIMEOUT.destroy()
set ExitsNetworkInstance[id_S] = 0
set TeleportTimeInstance[id_S] = 0
endif
set i = 0
set isUnitValid = false
endif
//Target
if EntrancesNetworkInstance[id_T] != 0 then
set G.ENTRANCES = EntrancesNetworkInstance[id_T]
loop
set i = i + 1
if source == G.ENTRANCES.unit[I] then
set isUnitValid = true
endif
exitwhen i > NumberOfEntrances[id_T] or isUnitValid
endloop
if isUnitValid then
call G.ENTRANCES.remove(i)
loop
exitwhen i > NumberOfEntrances[id_T]
set G.ENTRANCES.unit[I] = G.ENTRANCES.unit[i + 1]
set i = i + 1
endloop
call G.ENTRANCES.remove(NumberOfEntrances[id_T])
set NumberOfEntrances[id_T] = NumberOfEntrances[id_T] - 1
endif
if NumberOfEntrances[id_T] < 1 then
call G.ENTRANCES.destroy()
set EntrancesNetworkInstance[id_T] = 0
endif
endif
endmethod
static method createTunnel takes unit source, unit target, real teleport_distance, real timeout returns nothing
local integer id_S = GetUnitUserData(source)
local integer id_T = GetUnitUserData(target)
local integer i
local boolean isDuplicate = false
if not (source == null and target == null) then
//Source
set G.EXITS = ExitsNetworkInstance[id_S]
if G.EXITS == 0 then
set G.EXITS = Table.create()
set G.TIMEOUT = Table.create()
set ExitsNetworkInstance[id_S] = G.EXITS
set TeleportTimeInstance[id_S] = G.TIMEOUT
call CreateSmartOrderTracker(source, teleport_distance)
//Death Event?
else
set i = 1
loop
if target == G.EXITS.unit[I] then
set isDuplicate = true
endif
exitwhen i > NumberOfExits[id_S] or isDuplicate
set i = i + 1
endloop
endif
if not isDuplicate then
set NumberOfExits[id_S] = NumberOfExits[id_S] + 1
set G.EXITS.unit[NumberOfExits[id_S]] = target
set G.TIMEOUT.real[NumberOfExits[id_S]] = timeout
else
return
endif
//Target
set NumberOfEntrances[id_T] = NumberOfEntrances[id_T] + 1
set G.ENTRANCES = EntrancesNetworkInstance[id_T]
if G.ENTRANCES == 0 then
set G.ENTRANCES = Table.create()
set EntrancesNetworkInstance[id_T] = G.ENTRANCES
endif
set G.ENTRANCES.unit[NumberOfEntrances[id_T]] = source
endif
endmethod
endstruct
private function init takes nothing returns nothing
call RegisterSmartTrackEvent(ST_UNIT_IN_RANGE, function CustomWaygate.preTeleportation)
endfunction
endlibrary
JASS:
library DockingSystem initializer init /*
By Spellbound
==== 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, /* Comes with this map
*/ Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
==== 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 plug's facing 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 P socket_instance )
Removes a socket from a station. Socket instances are obtained upon addSocket.
DockingSystem.moveSocket( takes P 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, P 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, P 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's currently plug to 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.
RegisterDockEvent( takes integer ev, code c )
Sets up events for docking. The available events are DS_ON_PRE_DOCK, DS_ON_DOCK and
DS_ON_UNDOCK. They should be fairly self-explanatory, but just in case I'm just
projecting, here's what they do:
DS_ON_PRE_DOCK == BEFORE the plug has moved into place.
DS_ON_DOCK == After the plug has moved into place.
DS_ON_UNDOCK == When docking ends.
EVENTS________
DS_EVENT_PLUG
the unit that is about to dock/has docked/has undocked.
DS_EVENT_STATION
the station
DS_INTERRUPT_DOCKING
set this to true on DS_ON_PRE_DOCK to stop docking from happening.
DS_MANUAL_UNDOCKING
set his to trur on DS_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.
call GetSocketX(socket_instance)
returns the x co-ordinate of the socket.
call GetSocketY(socket_instance)
returns the y co-ordinate of the socket.
call GetSocketZ(socket_instance)
returns the z co-ordinate of the socket.
call GetSocketFacing(socket_instance)
returns the facing angle of the socket in degrees.
call GetSocketAngleFromStation(socket_instance, 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 in the object editor.
*/
globals
integer array DS_Instance
integer array DS_SocketId
integer array DS_PlugCount
unit DS_EVENT_PLUG = null
unit DS_EVENT_STATION = null
boolean DS_INTERRUPT_DOCKING = false
boolean DS_MANUAL_UNDOCKING = false
integer DS_ON_PRE_DOCK = 1
integer DS_ON_DOCK = 2
integer DS_ON_UNDOCK = 3
private trigger array EventTrig
endglobals
//P for Plug
struct P
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(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
//S for Station
private struct S
static Table SOCKETS
endstruct
private function FireEvent takes unit plug, unit station, integer ev returns nothing
set DS_EVENT_PLUG = plug
set DS_EVENT_STATION = station
call TriggerEvaluate(EventTrig[ev])
set DS_EVENT_PLUG = null
set DS_EVENT_STATION = null
set DS_INTERRUPT_DOCKING = false
endfunction
struct DockingSystem
integer tableInstance
integer maxSockets
private static method zeroTimerStun takes nothing returns nothing
local timer t = GetExpiredTimer()
local P p = GetTimerData(t)
set p.ignoreOrder = true
set ST_IGNORE_ORDERS = true
call IssueImmediateOrderById(p.plug, 851973)//order stunned
set ST_IGNORE_ORDERS = false
set p.ignoreOrder = false
call ReleaseTimer(t)
set t = null
endmethod
static method undock takes unit plug returns nothing
local integer id_P = GetUnitUserData(plug)
local P p = DS_SocketId[id_P]
local integer id_S = GetUnitUserData(p.station)
set DS_PlugCount[id_S] = DS_PlugCount[id_S] - 1
call SetUnitPathing(plug, true)
set p.plug = null
call DestroyTrigger(p.trig)
set p.trig = null
call FireEvent(plug, p.station, DS_ON_UNDOCK)
set DS_SocketId[id_P] = 0
endmethod
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 P p
if this != 0 then
set S.SOCKETS = this.tableInstance
set i = this.maxSockets
loop
set p = S.SOCKETS.integer[i]
if p.plug != null then
call undock(p.plug)
endif
set i = i - 1
exitwhen i <= 0
endloop
endif
endmethod
private static method trackOrders takes nothing returns nothing
local unit plug = GetTriggerUnit()
local unit station = GetOrderTargetUnit()
local P p = DS_SocketId[GetUnitUserData(plug)]
if not p.ignoreOrder then
if station == p.station and GetIssuedOrderId() == ORDER_SMART then
call DockingSystem.undock(plug)
call DockingSystem.dock(plug, p)
else
call DockingSystem.undock(plug)
endif
endif
set plug = null
set station = null
endmethod
static method dock takes unit plug, P p returns nothing
local integer id_S = GetUnitUserData(p.station)
call FireEvent(plug, p.station, DS_ON_PRE_DOCK)
if not DS_INTERRUPT_DOCKING then
call SetUnitPathing(plug, false)
call SetUnitX(plug, p.x)
call SetUnitY(plug, p.y)
if GetUnitAbilityLevel(plug, 852155) > 0 then
call UnitAddAbility(plug, 852155)
call UnitRemoveAbility(plug, 852155)
endif
call SetUnitFlyHeight(plug, p.z, 0.)
call SetUnitFacing(plug, p.facing)
call TimerStart(NewTimerEx(p), 0., false, function thistype.zeroTimerStun)
set p.plug = plug
set DS_SocketId[GetUnitUserData(plug)] = p
if not DS_MANUAL_UNDOCKING then
set p.trig = CreateTrigger()
call TriggerRegisterUnitEvent(p.trig, plug, EVENT_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterUnitEvent(p.trig, plug, EVENT_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterUnitEvent(p.trig, plug, EVENT_UNIT_ISSUED_ORDER)
call TriggerAddCondition(p.trig, function thistype.trackOrders)
else
set DS_MANUAL_UNDOCKING = false
endif
set DS_PlugCount[id_S] = DS_PlugCount[id_S] + 1
call FireEvent(plug, p.station, DS_ON_DOCK)
else
set DS_INTERRUPT_DOCKING = false
endif
endmethod
static method dockPrep takes nothing returns nothing
local unit station = ST_EVENT_TRACKER
local unit plug = ST_EVENT_UNIT
local boolean firstPass = false
local boolean exitLoop = false
//local integer player_number
//local player play
local thistype this
local integer i
local P p
set this = DS_Instance[GetUnitUserData(station)]
set S.SOCKETS = this.tableInstance
set i = GetRandomInt(0, this.maxSockets)
loop
set i = i + 1
if i > this.maxSockets then
set i = 1
set firstPass = true
endif
exitwhen (i > this.maxSockets and firstPass) or exitLoop
set p = S.SOCKETS.integer[i]
if p.plug == null then
call DockingSystem.dock(plug, p)
set exitLoop = true
endif
endloop
set station = null
set plug = null
endmethod
static method GetSocketX takes P p returns real
return p.x
endmethod
static method GetSocketY takes P p returns real
return p.y
endmethod
static method GetSocketZ takes P p returns real
return p.z
endmethod
static method GetSocketFacing takes P p returns real
return p.facing
endmethod
static method GetSocketAngleFromStation takes P p, real offset returns real
return Atan2(p.y - (GetUnitY(p.station) + offset), p.x - (GetUnitX(p.station) + offset))
endmethod
static method removeSocket takes P p returns nothing
local thistype this = DS_Instance[GetUnitUserData(p.station)]
local integer i = p.slot
if this != 0 then
set S.SOCKETS = this.tableInstance
call p.destroy()
call S.SOCKETS.remove(i)
loop
set S.SOCKETS.integer[i] = S.SOCKETS.integer[i + 1]
set i = i + 1
exitwhen i >= this.maxSockets
endloop
set this.maxSockets = this.maxSockets - 1
endif
endmethod
static method moveSocket takes P p, real x, real y, real z, real facing returns nothing
set p.x = x
set p.y = y
set p.z = z
set p.facing = facing
if p.plug != null then
//Move plug
call SetUnitX(p.plug, x)
call SetUnitY(p.plug, y)
call SetUnitFacing(p.plug, facing)
call SetUnitFlyHeight(p.plug, z, 0.)
endif
endmethod
static method addSocketPointer takes unit pointer, real range, boolean kill_pointer, P p returns nothing
if pointer != null then
set p.socketPointer = pointer
set p.killSocketPointer = kill_pointer
set DS_SocketId[GetUnitUserData(pointer)] = p
call CreateSmartOrderTracker(pointer, range)
endif
endmethod
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 this != 0 then
set S.SOCKETS = this.tableInstance
set this.maxSockets = this.maxSockets + 1
set 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
return p
endmethod
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 S.SOCKETS = this.tableInstance
set i = this.maxSockets
loop
call removeSocket(S.SOCKETS.integer[i])
set i = i - 1
exitwhen i <= 0
endloop
set this.maxSockets = 0
call CancelSmartOrderTracker(station)
set DS_Instance[id_S] = 0
call S.SOCKETS.destroy()
call this.deallocate()
endif
endmethod
static method createStation takes unit station, real socket_trigger_distance returns nothing
local integer id_S = GetUnitUserData(station)
local thistype this = DS_Instance[id_S]
if this == 0 then
set S.SOCKETS = Table.create()
set this = allocate()
set this.tableInstance = S.SOCKETS
set this.maxSockets = 0
call CreateSmartOrderTracker(station, socket_trigger_distance)
set DS_Instance[id_S] = this
set DS_PlugCount[id_S] = 0
endif
endmethod
endstruct
function RegisterDockEvent takes integer ev, code c returns nothing
call TriggerAddCondition(EventTrig[ev], Condition(c))
endfunction
private function init takes nothing returns nothing
set EventTrig[DS_ON_PRE_DOCK] = CreateTrigger()
set EventTrig[DS_ON_DOCK] = CreateTrigger()
set EventTrig[DS_ON_UNDOCK] = CreateTrigger()
call RegisterSmartTrackEvent(ST_UNIT_IN_RANGE, function DockingSystem.dockPrep)
endfunction
endlibrary
----------------------------------------------------------------
30/09/2017
So I've been looking for a way for the game to keep track of which unit right-clicked which unit for some resources I've been trying to code (like a custom Waygate), so I figured I'd create a library designed specifically for that. However, I'm not entirely certain as to its implementation.
The way this works is that you initially identify which units will be Trackers. The units will have a range associated with them so that any other unit that is given the Smart order will be added to an array variable. Upon reaching the defined range of the Tracker, a real variable event fires. Issuing any order other than right-clicking on another Tracker will remove the unit from the array variable. I call these units Smarties because creativity is hard and I just wanted to get on with coding the thing.
A demo map is attached if you wish to the system in action.
JASS:
library SmartTrack initializer init
/*
==== Description ====
SmartTrack will track when a unit is ordered to right-click on another unit. For this to
happen, the 'Tracker' will have to first be identified with CreateSmartOrderTracker, which
takes the unit itself (usually a building) and the range with which it will detect a unit
that right-clicked it.
SmartTrack can thus be used in conjunction with other systems like a custom waygate system
or docking system.
==== Requirements ====
Any custom value unit indexer such as https://www.hiveworkshop.com/threads/gui-unit-event-v2-4-0-0.201641/
==== API ====
function CreateSmartOrderTracker takes unit source, real range returns nothing
^ this sets up a unit as a Tracker that will fire an event whenever another unit that
has right-clicked on it comes into range.
function CancelSmartOrderTracker takes unit source returns nothing
^ this undoes the setup. Call this function when the unit dies or is de-indexed.
*/
globals
constant integer ORDER_SMART = 851971
unit ST_EVENT_UNIT = null
unit ST_EVENT_TRACKER = null
boolean ST_IGNORE_ORDERS = false
constant integer ST_UNIT_IN_RANGE = 1
constant integer ST_TRACKING_STARTED = 2
constant integer ST_TRACKING_TERMINATED = 3
private hashtable TRIGGER_TRACK = InitHashtable()
private trigger SmartyTrack = CreateTrigger()
private integer TrackerCount = 0
private integer MaxSmarties = 0
private integer array SmartySlot
private unit array Smarty
private unit array Tracker
private real array TrackerRange
private trigger array TrackerTrig
private trigger array SmartyTrig
private trigger array EventTrig
public boolean array IsATracker
endglobals
native UnitAlive takes unit u returns boolean
private function FireEvent takes unit tracker, unit u, integer ev returns nothing
set ST_EVENT_UNIT = u
set ST_EVENT_TRACKER = tracker
call TriggerEvaluate(EventTrig[ev])
set ST_EVENT_UNIT = null
set ST_EVENT_TRACKER = null
endfunction
function RegisterSmartTrackEvent takes integer ev, code c returns nothing
call TriggerAddCondition(EventTrig[ev], Condition(c))
endfunction
private function RemoveSmarty takes unit u returns nothing
local integer id = GetUnitUserData(u)
local integer i = SmartySlot[id]
local unit tracker = Tracker[id]
set Tracker[id] = null
set SmartySlot[id] = 0
loop
set Smarty[i] = Smarty[i + 1]
set SmartySlot[GetUnitUserData(Smarty[i])] = i
set i = i + 1
exitwhen i > MaxSmarties
endloop
set MaxSmarties = MaxSmarties - 1
call FireEvent(tracker, u, ST_TRACKING_TERMINATED)
set tracker = null
endfunction
private function FilterSmarties takes nothing returns boolean
local unit tracker = LoadUnitHandle(TRIGGER_TRACK, GetHandleId(GetTriggeringTrigger()), 1)
local unit u = GetTriggerUnit()
local integer idU = GetUnitUserData(u)
local integer i = 0
loop
set i = i + 1
exitwhen i > MaxSmarties
if u == Smarty[i] and Tracker[idU] == tracker then
//Events
call FireEvent(tracker, u, ST_UNIT_IN_RANGE)
endif
endloop
set u = null
return false
endfunction
private function TrackOrders takes nothing returns nothing
local unit orderSource = GetTriggerUnit()
local unit orderTarget = GetOrderTargetUnit()
local integer idSource = GetUnitUserData(orderSource)
local integer idTarget = GetUnitUserData(orderTarget)
local integer i
if not ST_IGNORE_ORDERS then
if orderTarget != null and GetIssuedOrderId() == ORDER_SMART then
if IsATracker[idTarget] then
if Tracker[idSource] == null then //the unit is not in the Smarty list
if IsUnitInRange(orderSource, orderTarget, TrackerRange[idTarget]) then
//Events
call FireEvent(orderTarget, orderSource, ST_TRACKING_STARTED)
call FireEvent(orderTarget, orderSource, ST_UNIT_IN_RANGE)
else
set MaxSmarties = MaxSmarties + 1
set Smarty[MaxSmarties] = orderSource
set SmartySlot[idSource] = MaxSmarties
set Tracker[idSource] = orderTarget
call FireEvent(orderTarget, orderSource, ST_TRACKING_STARTED)
endif
else
if IsUnitInRange(orderSource, orderTarget, TrackerRange[idTarget]) then
call RemoveSmarty(orderSource)
//Events
call FireEvent(orderTarget, orderSource, ST_TRACKING_STARTED)
call FireEvent(orderTarget, orderSource, ST_UNIT_IN_RANGE)
else
set Tracker[idSource] = orderTarget
endif
endif
endif
else
if Tracker[idSource] != null then
call RemoveSmarty(orderSource)
endif
endif
endif
endfunction
function CancelSmartOrderTracker takes unit source returns nothing
local integer id = GetUnitUserData(source)
local integer i
call FlushChildHashtable(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]))
call DestroyTrigger(TrackerTrig[id])
set IsATracker[id] = false
set TrackerCount = TrackerCount - 1
if TrackerCount < 1 then
call DisableTrigger(SmartyTrack)
set i = 0
loop
set i = i + 1
exitwhen i > MaxSmarties
set Tracker[GetUnitUserData(Smarty[i])] = null
set Smarty[i] = null
endloop
set MaxSmarties = 0
endif
endfunction
function CreateSmartOrderTracker takes unit source, real range returns nothing
local integer id = GetUnitUserData(source)
if TrackerTrig[id] == null then
set TrackerTrig[id] = CreateTrigger()
else
call DestroyTrigger(TrackerTrig[id])
set TrackerTrig[id] = CreateTrigger()
endif
call TriggerRegisterUnitInRange(TrackerTrig[id], source, range, null)
call TriggerAddCondition(TrackerTrig[id], function FilterSmarties)
call SaveUnitHandle(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]), 1, source)
set TrackerCount = TrackerCount + 1
set TrackerRange[id] = range
set IsATracker[id] = true
if not IsTriggerEnabled(SmartyTrack) then
call EnableTrigger(SmartyTrack)
endif
endfunction
private function init takes nothing returns nothing
set EventTrig[ST_UNIT_IN_RANGE] = CreateTrigger()
set EventTrig[ST_TRACKING_STARTED] = CreateTrigger()
set EventTrig[ST_TRACKING_TERMINATED] = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerAddCondition(SmartyTrack, function TrackOrders)
call DisableTrigger(SmartyTrack)
endfunction
endlibrary
There are 2 things that are bugging me about this:
1. I have preset events (eg:
call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
) that will track the Smarties whenever they are given an order... but those will also fire for every unit in the game that's given an order. I've wanted to create a trigger and assign those orders specifically for that unit, but wouldn't that just be inefficient and an unnecessary performance hog? How costly is it to create triggers with three events on the fly like that? I know things like CreateUnit() have high overhead, but I have no idea what the performance cost of CreateTrigger() is.2. I'm having to track the Tracker itself by binding it to the handleId of the trigger with a hashtable. Is there any other way to do this?
local unit tracker = LoadUnitHandle(TRIGGER_TRACK, GetHandleId(GetTriggeringTrigger()), 1)
.If you have a moment, could you review the code? It's quite simple and initially started out with the ability to create multiple instances of range-detection, but I couldn't really find a use for it so I scrapped it.
Last edited: