library CustomWaygate/*
CustomWaygate v1.04
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, /* https://www.hiveworkshop.com/threads/smarttrack-v1-02-1.299957/
*/ TimerUtils, /* http://www.wc3c.net/showthread.php?t=101322
*/ Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
*/ optional /*
*/ BlizzardMessage /* https://www.hiveworkshop.com/threads/snippet-blizzardmessage.237845/
/*/*==== 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 is 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________*/
RegisterNativeEvent( integer whichEvent, code whichFunction )
This uses RegisterNativeEvent's event-creation to register CustomWaygate's many events. They
are as followed:
EVENT_WAYGATE_PRE_TELEPORT ---- Fires right before a unit is moved.
EVENT_WAYGATE_POST_TELEPORT --- Fires directly after moving a unit.
EVENT_WAYGATE_DURATION_START -- When teleportation has a duration, this fires as that duration
starts. It ends EVENT_WAYGATE_PRE_TELEPORT
EVENT_WAYGATE_NETWORK_PRIMED -- When teleportation is primed and ready to execute in a network.
Useful if you want to attach special effects to your units to
indicate readiness, for example.
EVENT_WAYGATE_CANCELLED ------- When teleportation has been cancelled, either manually or
through StopTeleport(). See below.
RegisterIndexNativeEvent( integer index, integer whichEvent, code whichFunction )
Same as above, but per-player or per-unit. If you wish for CustomWaygate to only work for
specific players, set index to the player id. For units, set index to the handle of the unit.
GetWaygateSource()
The waygate unit itself. Origin waygate.
GetWaygateTarget()
The waygate your units teleport to. Destination waygate.
GetWaygateTraveller()
The units that are using the waygate.
CountEntrances( unit whichGate )
Returns an integer of how many gates have whichGate as a destination.
CountExits( unit whichGate )
Returns an integer of how many destination gates whichGate has.
StopTeleport()
Call this when WAYGATE_EVENT == 0.50 (pre-teleportaton) if you wish to interrupt the
process and handle teleportation yourself. Such situations are, eg, if you want to have
your teleporter shoot your traveller as a missile towards the destination instead of just
instantly displacing them. It's also useful if your teleporter has 'charges' so that you can
prevent teleportation altogether if the charges are insufficient.
*/
globals
private constant real NETWORK_TIMEOUT = .03125
private constant real DESTINATION_MESSAGE_COOLDOWN = 5. // see PreventMessageSpam below
private constant string MSG_INVALID = "Invalid target." // the message that shows up if you click on a unit that's not part of the network your unit is currently in.
private constant string MSG_PICK_DESTINATION = "Right-click on your destination to teleport."
private integer array NumberOfEntrances
private integer array NumberOfExits
private integer array EntrancesNetworkInstance
private integer array ExitsNetworkInstance
private integer array TeleportTimeInstance
private integer array TravellerInstance
private integer array NumberOfInstances
private boolean array isPlayMessageOnCooldown
//Events
integer EVENT_WAYGATE_PRE_TELEPORT
integer EVENT_WAYGATE_POST_TELEPORT
integer EVENT_WAYGATE_DURATION_START
integer EVENT_WAYGATE_NETWORK_PRIMED
integer EVENT_WAYGATE_CANCELLED
private unit waygateSource = null
private unit waygateTarget = null
private unit waygateTraveller = null
private boolean interruptTeleport = false
endglobals
// G stands for Gate
private struct G
static Table EXITS
static Table ENTRANCES
static Table TIMEOUT
endstruct
// Getters and interruption
function GetWaygateSource takes nothing returns unit
return waygateSource
endfunction
function GetWaygateTarget takes nothing returns unit
return waygateTarget
endfunction
function GetWaygateTraveller takes nothing returns unit
return waygateTraveller
endfunction
function StopTeleport takes nothing returns nothing
set interruptTeleport = true
endfunction
function CountEntrances takes unit u returns integer
return NumberOfEntrances[GetUnitUserData(u)]
endfunction
function CountExits takes unit u returns integer
return NumberOfExits[GetUnitUserData(u)]
endfunction
// This is the function that controls what trigger is run.
private function FireEvent takes unit source, unit target, unit traveller, integer ev returns nothing
local integer playerId = GetPlayerId(GetOwningPlayer(traveller))
local integer id = GetHandleId(traveller)
local unit prevSource = waygateSource
local unit prevTarget = waygateTarget
local unit prevTraveller = waygateTraveller
set waygateSource = source
set waygateTarget = target
set waygateTraveller = traveller
call TriggerEvaluate(GetNativeEventTrigger(ev))
if IsNativeEventRegistered(playerId, ev) then
call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, ev))
endif
if IsNativeEventRegistered(id, ev) then
call TriggerEvaluate(GetIndexNativeEventTrigger(id, ev))
endif
set waygateSource = prevSource
set waygateTarget = prevTarget
set waygateTraveller = prevTraveller
set prevSource = null
set prevTarget = null
set prevTraveller = null
endfunction
// A little quality of life struct that prevents an error message from showing up for every single
// invalid teleportation. Instead, it shows up once and then not for another DESTINATION_MESSAGE_COOLDOWN seconds.
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
// ST_IGNORE_ORDERS is specific to SmartTrack that prevents orders given by this library to
// interfere with it.
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 SmartTrack_ignoreOrders = this.b
call IssueImmediateOrderById(this.u, 851973)//order stunned
set SmartTrack_ignoreOrders = 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
// destruction method for teleport instances, not tunnels.
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
// core teleporter method. Relocates units to the destination by keeping their relative x/y
// position to the source.
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
// this function handles triggers created for each teleport instance that catches orders
// given. This is exclusively for non-network teleportations.
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 there is a source mismatch (aka you've right-clicked on another teleporter while
// already in a teleport instance) or the order was not a right-click, cancel teleportation.
if not (source == this.source and GetIssuedOrderId() == ORDER_SMART) then
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_CANCELLED)
if this.clock == null then
call this.destroy()
else
set this.isTeleportCancelled = true
//This instance is bound to isTeleportCancelled, which doesn't repeat and will
//terminate on its own when it expires.
endif
endif
set source = null
set traveller = null
endmethod
// this function handles the timer for non-network teleportations.
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
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_PRE_TELEPORT)
if not interruptTeleport then
call CustomWaygate.simpleTeleport(this.source, this.target, this.traveller)
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_POST_TELEPORT)
else
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_CANCELLED)
endif
set interruptTeleport = false
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
// if the teleporter has a timeout, create a trigger to track orders given and fire an initiate teleport event.
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
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_DURATION_START)
else
//Events
call FireEvent(source, target, traveller, EVENT_WAYGATE_PRE_TELEPORT)
if not interruptTeleport then
call CustomWaygate.simpleTeleport(source, target, traveller)
call FireEvent(source, target, traveller, EVENT_WAYGATE_POST_TELEPORT)
else
call FireEvent(source, target, traveller, EVENT_WAYGATE_CANCELLED)
endif
set interruptTeleport = false
endif
endmethod
//NETWORK TELEPORT
// this function allows units to right-click on possible destinations in a network without
// terminating their teleport instance. It catches orders exclusively for network teleportations.
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 player play
// this.ignoredOrder is used in orderStunTimer.start() to prevent the stun order from
// affecting the unit and thus accidentally interrupting the teleportation.
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 the destination unit is viable, the right-clicker is stunned to prevent
// it from moving.
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
set traveller = null
set target = null
endmethod
// this function handles the countdown for network-type teleporter. Right-click orders are
private static method networkTeleportCountdown takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local integer id_T = GetUnitUserData(this.traveller)
// this.teleportReady will turn to true after the countdown has elapsed. When it turns
// true, a network-teleport-is-event will fire.
if not this.teleportReady then
set this.countdown = this.countdown - NETWORK_TIMEOUT
if this.countdown <= 0. then
set this.teleportReady = true
//Events
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_NETWORK_PRIMED)
endif
endif
// if the teleportation has been cancelled, terminate the teleportation isntance.
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
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_CANCELLED)
call this.destroy()
// if there is a teleport target (aka a destination) and this.teleportReady is true, call
// pre-teleportation, terminate the teleport instance and if no interruption is called,
// proceed with the teleportation.
elseif this.target != null and this.teleportReady then
//Events
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_PRE_TELEPORT)
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 interruptTeleport then
call CustomWaygate.simpleTeleport(this.source, this.target, this.traveller)
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_POST_TELEPORT)
else
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_CANCELLED)
endif
set interruptTeleport = false
call this.destroy()
endif
set t = null
endmethod
// This is the first method that is called when a right-clicker comes in range of a teleporter.
static method preTeleportation takes nothing returns nothing
local unit source = GetTracker()
local unit traveller = GetSmartUnit()
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 there's only one exit connected to this source, then it's not a network. Initiate teleportation directly.
if NumberOfExits[id_S] == 1 then
call initiateTeleportation(source, G.EXITS.unit[1], traveller, G.TIMEOUT.real[1], G.EXITS)
// if ther are multiple exits, the unit has to then chose the destination with another right-click. Queued orders work well for that.
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
call FireEvent(this.source, this.target, this.traveller, EVENT_WAYGATE_DURATION_START)
endif
endif
set source = null
set traveller = null
endmethod
// this method destroys a SINGLE tunnel going from unit A to unit B. If there's another
// tunnel that goes from B to A, this tunnel is unaffected.
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 // loop through all of the source's exits and return true on isUnitValid if there's a match.
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 // if isUnitValid, clear all associated variables and cascade all elements of the list down by 1.
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 the source no longer has any exits, the Tables are destroyed.
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 // just like above loop checks if the source matches any registered entrances.
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
// This method will establish a ONE-WAY connection between two units called a tunnel. The
// tunnel is not automatically destroyed on death so it's up to the user to determine how
// they are removed.
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
// if either the source unit or the destination unit are null, this trigger does nothing.
if source != null and target != null then
//Source
set G.EXITS = ExitsNetworkInstance[id_S]
if G.EXITS == 0 then // this identifies whether the source has any prior connections
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)
else
// if the source already has at least 1 tunnel, check if the destination unit
// is already one of its registered targets.
set i = 1
loop
// if destination is already registered, end the function.
if target == G.EXITS.unit[i] then
return
endif
exitwhen i > NumberOfExits[id_S]
set i = i + 1
endloop
endif
set NumberOfExits[id_S] = NumberOfExits[id_S] + 1
set G.EXITS.unit[NumberOfExits[id_S]] = target
set G.TIMEOUT.real[NumberOfExits[id_S]] = timeout
//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
// here a SmartTrack event is registered to detect units that come in range of a teleporter.
// Whenever SmartTrack fires a ST_UNIT_IN_RANGE, CustomWaygate.preTeleportation will run.
private module CustomWaygateInit
private static method onInit takes nothing returns nothing
set EVENT_WAYGATE_PRE_TELEPORT = CreateNativeEvent()
set EVENT_WAYGATE_POST_TELEPORT = CreateNativeEvent()
set EVENT_WAYGATE_DURATION_START = CreateNativeEvent()
set EVENT_WAYGATE_NETWORK_PRIMED = CreateNativeEvent()
set EVENT_WAYGATE_CANCELLED = CreateNativeEvent()
call RegisterNativeEvent(EVENT_SMART_TRACK_IN_RANGE, function CustomWaygate.preTeleportation)
endmethod
endmodule
private struct init
implement CustomWaygateInit
endstruct
endlibrary
[LEFT]