Teleporting and Hovering (Unbearable Revenge)
TL;DR
We use SetPosition
to set the character's position to anywhere in the game by specifying the location's coordinates in 3D space. Additionally, we'll complete the Unbearable Revenge challenge.
Video
Context
Before we jump right into the meat of this post, we'll have a bit of a context, so that we can understand things in detail and it also helps people who might need a refresher. So far in the series, we’ve hooked ourselves into the game using LD_PRELOAD, and we’ve been able to do some cool things like speeding through the land and flying like a bird. This could mean one thing, that the game server doesn’t really have a hand on verifying the position sent by the game client, which pretty much means we can place ourselves anywhere in the game, ergo teleportation.
The Idea
“Now, How do we teleport!?”
By taking a closer look at the methods of the Player
class, or more specifically the Actor
class which the Player
class inherits from, you'd see that there are a couple of interesting functions. For example
void SetPosition(const Vector3 &);
The function SetPosition
is self-explanatory, it can set the position of a game object, or in our case the playable character. This function takes in a Vector3
which is basically the X, Y and Z coordinates in a 3D space.
struct Vector3 {
float x;
float y;
float z;
...
}
Now using SetPosition
and specifying the values for the X, Y & Z coordinates, we can change the location of the character in an instant, achieving the power of teleportation. Now the idea is to figure out a fun way to teleport in-game conveniently. The initial approach was to use a global tick function.
void World::Tick(float f) {
/*
* Called multiple times a second
* Can check if a teleport command arrived
*/
}
We could write a client which takes the X, Y & Z coordinates as inputs and send it to the server. Inside the Tick
function, the server would then check for the coordinates and update the changes in the game.
However, there's a much cooler Idea!, wonder what it is and how I got it?
As you know, I try to solve these challenges on my own and don't read any writeups or solutions to the problem. However, when I was researching some issues I had, I got an accidental spoiler. I didn't spoil myself the solution, but I did get a glimpse at the code where I found this fantastic idea. Also, I can't credit where I got this idea from, because I don't remember and I'm scared to look it up again. Instead of the traditional client/server architecture that we were thinking of, we can use the "Chat Messages"!
virtual void Chat(const char *);
The Player
class has a method called Chat
which takes a string as a parameter. When we override this method by adding a simple print statement like the following,
void Player::Chat(const char *msg) {
printf("[chat] msg=\"%s\"\n", msg);
}
We start seeing our messages from the chat being printed out in the terminal, Awesome!
Implementation and Testing
Now that we can hook into the Chat Messaging feature, we can take the input coordinates and update the position of the character.
void Player::Chat(const char *msg) {
printf("[chat] msg=\"%s\"\n", msg);
if(strncmp("tp ", msg, 3) == 0) {
Vector3* new_pos = new Vector3();
sscanf(msg+3, "%f %f %f", &(new_pos->x), &(new_pos->y), &(new_pos->z));
this->SetPosition(*new_pos);
}
}
First, we check if the msg
starts with the string "tp " using strncmp
. Next, we create a new 3D vector and then use sscanf
to scan the input chat message for 3 float values and assign them to the vector's X, Y and Z attribute. Lastly, we call the SetPosition
function with the newly created 3D vector as the parameter. Since we are inside the Player
class, we can reference the current object by using the keyword this
.
So let's try this out. First, we compile the code as a shared library and LD_PRELOAD it. Now start the game and open the chat window, type tp 0 0 0
where tp
is the command we created for teleportation, and the 3 numbers are X, Y and Z coordinates. Now when we hit Enter ...
Boom! We have successfully teleported ourselves to 0 0 0
. Apparently, this is below the surface level and can be seen in the image above. If you look closely at the blocks after teleportation, it looks very familiar, and maybe these are the corridors of the Blocky Dungeon challenge. If you remember, there was a big crazy room with some complex logic gates, and also there was a chest in a separate room, but an invisible wall was placed right in front of us, and we couldn't get in. Maybe we can just teleport into it. Let's add another convenient command to our chat control called tpz which will just teleport in the vertical Z axis.
void Player::Chat(const char *msg) {
printf("[chat] msg=\"%s\"\n", msg);
if(strncmp("tp ", msg, 3) == 0) {
Vector3* new_pos = new Vector3();
sscanf(msg+3, "%f %f %f", &(new_pos->x), &(new_pos->y), &(new_pos->z));
this->SetPosition(*new_pos);
}
//Teleport along Z-Axis
if(strncmp("tpz ", msg, 4) == 0) {
Vector3* new_pos = this->GetPosition();
sscanf(msg+4, "%f", &(new_pos.z));
this->SetPosition(new_pos);
}
}
This command is similar, but there are small changes. We first try to get the current position by using the GetPosition
function and only change the value of the Z-axis using sscanf
. Then we update the position using SetPosition
as usual.
Now we can walk up to the invisible door, drop down by changing the Z-axis value to a lower value than the surface level, maybe something like tpz 1000, move a bit forward and set the Z-axis value to a higher value like tpz 2500 to get back up.
Now we are inside the room, Sweet! Can we open the chest worth 400 points? Nope, would've been too easy.
Unbearable Revenge
During the Let's play, we came across an interesting challenge, where we tried to open the chest, but it had a countdown timer which means we had to spend 5 minutes near the proximity of the chest to get it opened. However, as soon as the countdown starts, we are swarmed with big bears, and they try to attack us by hand. These bears are, and it's almost impossible to survive within the proximity of the chest while the bear tears us apart into pieces. One interesting approach is that there's a tree, right beside the chest, we can simply teleport ourselves on top of that tree and wait for the countdown to hit zero. Well, let's try that! We trigger the countdown timer, and we go underneath the tree, and we use tpz 5000 to sit on top of the tree, but wait, what?
We were killed!? Apparently, the bears get an AK47 rifle after a certain, and they killed us by shooting us! This game is crazy!
Now we can't be on top of the tree, but what if we go underneath the surface level? Bullets can't touch us. However, if we tried that out, we'd fall out of the proximity circle again and won't be able to get the flag. Now how do we stop falling down? The answer is simple. We need to hover. In order to hover, let's write some code to stop falling.
// Extra variables
bool frozen = false;
Vector3 frozen_pos;
void Player::Chat(const char *msg) {
printf("[chat] msg=\"%s\"\n", msg);
if(strncmp("tp ", msg, 3) == 0) {
Vector3* new_pos = new Vector3();
sscanf(msg+3, "%f %f %f", &(new_pos->x), &(new_pos->y), &(new_pos->z));
this->SetPosition(*new_pos);
} else if(strncmp("tpz ", msg, 4) == 0) {
Vector3* new_pos = this->GetPosition();
sscanf(msg+4, "%f", &(new_pos.z));
this->SetPosition(new_pos);
} else if(strncmp("!", msg, 1) == 0) {
// freeze/hover
frozen_pos = this->GetPosition();
frozen = !frozen;
}
}
First, we create 2 new variables frozen
and frozen_pos
. frozen
is a boolean which is used to know the current state of hovering and frozen_pos
is a 3D vector which holds the hover position. In the Chat
function, we add another check, which basically gets the current position of the character and stores it in the frozen_pos
variable. Since the initial state of freeze is false
, we change it to true
using the not logical operator.
void World::Tick(float f) {
...
if (frozen) {
player->SetPosition(frozen_pos);
}
}
In the Tick
function, which is called a couple of times a second, we check if the frozen
is set to true
and based upon that we set the player's position to the freeze/hover position. When we try to freeze using !
command, it's jerking around. Let's take a look at the Actor
class again and see if we can find some useful functions.
class Actor : public IActor {
...
void SetPosition(const Vector3 &);
void SetRotation(const Vector3 &);
void SetVelocity(const Vector3 &);
...
}
As we can see, there's SetVelocity
, maybe the velocity constantly pulls us down. So let's try setting the velocity to 0 and see if that helps.
void World::Tick(float f) {
...
if (frozen) {
player->SetPosition(frozen_pos);
player->SetVelocity(Vector(0, 0, 0));
}
}
Also, also to teleport while we are frozen in mid-air, we need to update the frozen position in the teleport snippet. So let's do that as well.
bool frozen = false;
Vector3 frozen_pos;
void Player::Chat(const char *msg) {
printf("[chat] msg=\"%s\"\n", msg);
if(strncmp("tp ", msg, 3) == 0) {
Vector3* new_pos = new Vector3();
sscanf(msg+3, "%f %f %f", &(new_pos->x), &(new_pos->y), &(new_pos->z));
this->SetPosition(*new_pos);
/* update frozen position */
frozen_pos = this->GetPosition();
} else if(strncmp("tpz ", msg, 4) == 0) {
Vector3* new_pos = this->GetPosition();
sscanf(msg+4, "%f", &(new_pos.z));
this->SetPosition(new_pos);
/* update frozen position */
frozen_pos = this->GetPosition();
} else if(strncmp("!", msg, 1) == 0) {
frozen_pos = this->GetPosition();
frozen = !frozen;
}
}
Now let's try this again, we activate the chest, quickly freeze and teleport right below the chest. But again this didn't work. Initially I was very confused on why this was happening, but when I looked at the hovering character from the other players's screen, it was clear what was causing this issue.
As you can see from the above image, on the client, everything seems soo smooth. However, on the other player's screen, we are jerking. The reason for this is that the server takes time to update the hover position until then it would still think that the character is falling. Due to this behavior sometimes the server drops the character too low which leads to the player going out of the proximity circle.
You can confirm this behavior, by printing the velocity. You can see that there is a negative value for the gravity that's pulling us down.
Setting the velocity of the Z-Axis to a positive number like player->SetVelocity(Vector(0, 0, 60));
doesn't help us either. However, I did find a way to get this working, but it's not very elegant. I noticed that when kept jumping while the character is frozen, it hovers perfectly. So I tried to invoke the jump action programmatically using the SetJumpState
command, but apparently, that's not enough. Additionally, SetRemotePositionAndRotation
didn't work either.
So If anyone does know a better way to solve this, lemme know, I would really appreciate it.
Since I really want this flag, I suck it up, activate the chest, freeze below the chest and started mashing the space bar until I get the carpal tunnel syndrome. It's stupid, but works!
After the countdown is over, I teleport above and then try to un-freeze myself. After the un-freeze, I tried pressing the button e
to pick up the flag before any of the bears nearby kill me. But instead, this happened.
Probably all these game objects in a tight space just launched me towards this "island with the cows", well there you have a new speedrun strategy.
After respawning, we can go near the chest and collect our rewards.
There you go, we have a flag and a weapon. Epic!