Using State Machines To Mitigate Teleport Exploits

There have been a lot of threads asking “how do I patch exploiters”. A lot of common methods keep on getting mentioned, but one I haven’t seen used may be able to catch the most common time-saving exploit: teleport exploits. Since some games benefit by teleporting, like The Labyrinth where you could teleport from the lobby to the end and save between 20 minutes and 1 hour, it is an incentive for exploiters to get resources faster. Depending on how the game is set up, teleport exploits can be mitigated by visualizing the game as a state machine.
As a note going forward, the goal is to stop players from drastically speeding up resource gathering beyond what was intended; not stop them from teleporting altogether. Teleport can still be done at a slow enough pace and over short enough distances where it can be indistinguishable from a player with a lot of practice on a slow connection.

What is a State Machine?
When designing software, you may need to have specific function work in only certain conditions, or a “state”. As an example, a plane’s landing gear should only be able to go up if they are flying. It is possible to do it with just basic inputs, but it can be difficult. As an example, here is where the state is defined implicitly (needs to be calculated):

local IsTaxiing = (Speed <= STALL_SPEED and VerticalSpeed == 0)
local IsStalling = (Speed <= STALL_SPEED and VerticalSpeed ~= 0)
local IsFlying = (Speed > STALL_SPEED and not IsStalling)

This example doesn’t seem that bad, but more modifications will be required if more states are needed, like IsReversing and IsDescending. The solution to this is a “Finite State Machine” (or just “State Machine”), which is a system that contains several explicit states with specific conditions that allow the state to move from one state to another. At any given time, the system has only one state. For the original example, checking the states become a lot easier with explicitly defined states:

local IsTaxiing = (PlaneState == “Taxiing”)
local IsStalling = (PlaneState == “Stalling”)
local IsFlying = (PlaneState == “Flying”)

Diagrams
There are several different methods of diagramming state machines. For this tutorial, I will be using the UML State Diagram method. States are defined as rectangles with the state name, with the arrows (aka transitions) using the format of action that causes state change [condition] / additional action. A circle is also used to define the initial state. For the plane example, here is a possible state machine to represent it:

Tools For Diagrams
There are tools for creating various UML diagrams. If you have an EDU email account or are willing to pay for a premium plan, I would recommend LucidChart for the user experience and support. If you don’t get a premium account, you will be limited to 60 objects (rectangles, text, and arrows). If you want something that a bit inferior in terms of user experience for creating diagrams, but is accountless and can interface with many cloud storage options, I would recommend draw.io. For this thread, I used draw.io.

Using State Machines To Catch Teleporters
Character Position States
The simplest method that you can use to catch these exploiters is to use either region checks or part collisions. As an example with zones A, B, and C, where A is connected to B and B is connected to C, A to B and B to C are valid transitions while A to C is not. If a transition from A to C is attempted, the player can be considered exploiting. The valid transitions can be pictured as the following, with invalid transitions (dashed) considered as exploits:

Additional Checks
The simple approach may work for a while, but eventually, exploiters will figure out a way around it (ex: teleport from A to B and B to C). To combat this, additional requirements for transitions can be added. These include:

  • Time requirements: If a zone takes time to cross, being lower than this time can detect teleporters. Be aware that if the player gets flung, this may cause a false positive.
  • Path requirements: If zone C has a door, the door being open can be a condition to check for.
  • Tasks requirements: If zone C requires a button to be pressed, checking if the player activated a button can be a condition to check for. This may be redundant with the path check since tasks typically unblock a path.
  • Tool requirements: If zone C requires a key, checking if the player has a key can be a condition to check for.

More options exist depending on what the game or system is.

Planning Intended Teleports
If your game intentionally teleports players for specific events, it will need to be worked into your system. A simple way to get around this is having a temporary state that allows teleportation and having the transition out of the state being the teleportation.

Example: Escaping in The Labyrinth by Nitenity Studios
The Labyrinth is the first game I thought of for applying this type of system because of how common teleporting was. When I entered one day, the changelog showed that the coins reward was removed from escaping the maze (commonly 20 minutes - 1 hour) because of people teleporting and causing an increase in the number of lootbox items into the trading system.

Overview of Escaping in The Labyrinth
The Labyrinth is composed of four main sections: the lobby, the maze, the labyrinth, and the escapes (4 different escapes exist, each with different obstacles). To escape the maze, you need to exit the lobby through a door into the maze, exit the maze through a door into the labyrinth, exit the labyrinth through a door to the exit sections, and getting to an escape. The first door opens during the day, and the others open at night, with days and nights being 6 minutes apart. Each in-game night, only 1 of the 4 doors for the exits open. If you die, you return to the lobby.

Escaping in The Labyrinth as a State Machine
Since the sections involve passing through doors, those can be used as transactions rather than having to do constant region checks on a non-rectangular shape. The map is also very large, so a time requirement can also be built into the sections. Using this, the state machine looks like this (ignoring player deaths):

Example: Jewelry Store Robbery in Jailbreak by Badimo

Jailbreak is a very well known game on the Roblox platform based on being a criminal or a police officer. There are several different robbery types, but the Jewelry store has been targeted a lot recently. Typically it takes 2-3 minutes, but current teleport exploits allow it to be done in less than 10 seconds. This doesn’t have an effect on the game’s economy since there is no trading and donations are very limited, but it does prevent other players from being able to complete the robberies.

Overview of Robbing the Jewelry Store in Jailbreak
When the Jewelry Store opens, one of two glass windows needs to be breached. After entering the store and breaking a glass display with jewelry, a security wall will block off the window entrance. After the player collects as much as they want, they need to complete 3 floors of obstacles and exit through a door on the roof, then go to the criminal base to redeem the reward. If a player doesn’t enter the glass window before the robbery starts, they can enter through the roof exits but have to complete the obstacles.

Robbing the Jewelry Store as a State Machine
Depending on the implementation, there can be as many as 6 states (Outside, Floor 1, Floor 2, Floor 3, Floor 4, Roof). To keep the diagram small, only the 5 states will be used:

Example Implementation
About a month ago, I created a demo for The Labyrinth to decrease the number of exploiters flooding the trading system with coins. The code is now open sourced and can be demoed in Studio. It doesn’t fully match the state diagram above, but it is close.

51 Likes

So, I actually had a bit of a think on this not too long ago, and I think that it’s indeed very possible to stop these movement exploits in their tracks (fly exploits are a tad tricky).

You for instance described a state-machine system. While valid and easier to explain, I’d like to argue a different approach is better.

This approach is very simple; a verification run whenever the character is moved. First, like you said, if you will be teleporting or moving the player yourself, you will want a flag that you set when you do not want to verify player positions (do this on the server, of course). If this flag is unset, my first verification would be to raycast from A to B. If the ray finds a collidable object, it sets the flag and teleports the player there, unsetting the flag afterward. If you are following this “teleport the player where you think they should go” approach, you can also not worry about false positives and check distances (since at worst, the player will be teleported back a bit, potentially even stopping the fling. Of course, certain game archetypes might not want to do this, as this might completely mess up gameplay)

Rather than going into a complex state system model, I feel as though it can be done much more simply.

Still, a state system model is useful in thinking all this out and really should be taught as a starting point at a minimum.

This solution is implemented a lot, but it has two flaws:
1.

Depending on how often you poll, false positives can be very common. If the polling rate is low enough, players jumping on platforms can set off a false positive (VERY common in certain sections of The Labyrinth). Increasing the polling rate can compensate for this, but is more resource intensive and doesn’t fix slower clients from registering false positives.

2. Newer teleport methods used in Jailbreak involve teleporting really short distances, so it is possible that the path used by the exploiter may be valid by the server. Distance checks can be added but may result in a lot of false positives from clients that have a sudden spike in ping.

Using a state machine implementation mostly solves problem 1, and can solve problem 2 if a time requirement is used. There will still be cases of teleporters, although they will probably be closer to automating the system than speeding it up.

Hooking a GetPropertyChangedEvent to Position or Velocity or something of that nature would be better than polling all the time though. And I agree, in some cases the “teleport to place where you would be stopped normally” is bad; particularly when you are indeed dealing with jumping and such.

Unfortunately however, laggy clients are the big issue. No matter what, any leniency you add for laggy clients is going to be exploitable. This can somewhat be remedied by kicking for reliably high latency, but then you alienate a few players.

To address point 2, a rolling time check can also be implemented using tables and this method, so you could feasibly reject movement beyond a certain limit per unit of time. This would at most then allow cheaters to zip short distances, then be locked in place for the rest of that block of time, which might be an intriguing method of introducing counterplay to cheaters.

Also, by “not worry about false positives”, I meant that you don’t kick people arbitrarily.

The cons of this method, however, are obvious; if players are standing on top of something moving, you need to account for that movement, otherwise the player will just be yanked off the moving object. (In Jailbreak, they used to outright flag you as a cheater for doing this, despite the fact that all you were doing is standing on the hood of a car that was driving.)

In reality, it doesn’t matter how you do it, so long as you don’t neglect the contingencies possible when playing.

This is incredibly helpful! I never knew you could create state machines like this.

you use these states like a conditional, yes?

like

local IsFlying = (Speed > STALL_SPEED and not IsStalling)

if IsFlying then
-- do something
end

Awesome, great job! That labyrinth exemple is pretty good and very well explained. Thanks again for sharing, this will be helpful!

That would be the point of defining the variables in the example above. If you are doing a check if the state is current, you could ignore making the boolean value and just integrate it into the if statement.

if State == "Flying" then
--Code
end

You could do it for an implicitly defined system, but you may run into the problem of copying and pasting the same statement, and then needing to change it several places layer.

if Speed > STALL_SPEED and not IsStalling then
--Code
end

what is defining the flying state?

For a state machine, the state would be explicitly defined elsewhere, and when a specific action occurs (ex: speed becomes greater than stall speed), the state transitioned to a new state. Here is an example of how that would work:

local STALL_SPEED = 50
local State = "Taxiing"
local SpeedValue = script.Parent:WaitForChild("Speed")

Speed:GetPropertyChangedSignal("Value"):Connect(function()
	if State == "Taxiing" and Speed.Value > STALL_SPEED then
		State = "Flying"
	end
end)
2 Likes

I understand now, thanks for the quick replies

Interesting… really like the article. Never thought of it.