-
-
Notifications
You must be signed in to change notification settings - Fork 46
[How To] Create Your Own Level Spy
By understanding how objects (items and creatures) are stored on the map, and how creatures are moved on the map, we can use the power of TibiaAPI to closely mimic the infamous level spy feature of bots.
While this information isn't fully needed to implement our level spy, it is helpful for understanding limitations.
The game window of the official Tibia client shows a grid of 15x11 (x/y) tiles, but the client stores 18x14 tiles. There is 1 hidden row above the top of the game window, 2 hidden rows below the bottom of the game window, 1 hidden column to the left of the game window, and 2 hidden columns to the right of the game window. The client caches, and draws, these hidden tiles so that, when you walk around, there's no delay between the off-screen set of tiles appearing in the game window.
When above ground (floors 0-7; 7 = ground), the server sends map data for all 8 floors; 18x14x8 = 2016 tiles. When below ground (floors 8-15), the server sends map data for the player's current floor, two above it, and two below it; 18x14x5 = 1260 tiles.
The server uses the MoveCreature
packet to tell the client that a creature has moved from one tile to another. The MoveCreature
packet has two ways of doing this:
- By indicating the "from" position (xyz), and stack position, of where the client knows the creature to be, and the "to" position where the creature is now.
- By indicating the creature's ID and the "to" position where the creature is now. The client will only use this method if the X-coordinate of the "from" position is equal to the max value of an unsigned short (0xFFFF).
The second option is preferable for us because we don't have to keep track of our player's position and stack position.
First, create your client and start the proxy:
using var client = new OXGaming.TibiaAPI.Client();
client.StartConnection();
Next, create a MoveCreature
packet and set the FromPosition
to have an X-coordinate equal to 0xFFFF so that the client will always use the second method:
var moveCreature = new OXGaming.TibiaAPI.Network.ServerPackets.MoveCreature(client)
{
FromPosition = new Position(ushort.MaxValue, 0, 0)
};
The CreatureId
for moveCreature
needs to be set, but we won't know our player's ID until we log in. Luckily, we can listen for the LoginSuccess
server packet to do just that:
client.Connection.OnReceivedServerLoginSuccessPacket += (packet) =>
{
var p = (OXGaming.TibiaAPI.Network.ServerPackets.LoginSuccess)packet;
moveCreature.CreatureId = p.PlayerId;
return true;
};
Remember, from learning about the in-memory map, when we tell the client to move a creature we have to keep track of that new position for the next time we try to move that creature because that's where the client thinks it is:
var fakePos = new OXGaming.TibiaAPI.Utilities.Position(0, 0, 0);
Now all that's left to do is set the ToPosition
for moveCreature
and send it to the client each time we want to move up and down with our level spy. We'll use simple console input to dictate when we want to move up and when we want to move down:
while (true)
{
var input = System.Console.ReadLine();
switch (input)
{
case "up":
// levels are in descending order (0 is the highest, 15 is the lowest)
// subtract 1 to move up a floor
fakePos.Z -= 1;
moveCreature.ToPosition = fakePos;
client.Connection.SendToClient(moveCreature);
break;
case "down":
// levels are in descending order (0 is the highest, 15 is the lowest)
// add 1 to move down a floor
fakePos.Z += 1;
moveCreature.ToPosition = fakePos;
client.Connection.SendToClient(moveCreature);
break;
default:
break;
}
}
If you haven't noticed, there's one last thing to do; set fakePos
to our player's real position. In this example we'll use a simple console input, but, for an exercise, see if you can make it automated:
// this needs to be called, from your player's real position, each time before level spying
case "refresh":
fakePos = client.Player.Position;
break;
- This WILL crash your client, unless you're really careful. Level spy, made for the old, official Tibia clients, modified values in the client's memory in order to make the camera jump floors, and was basically unable to crash the client. Because this method tells the client your player is somewhere it's not, there's a risk that the tile you jump to is invalid. For example, if you're on a roof and you try to spy up a level, your client will crash.
- If you move while level spying, your character will move and the client will jump back to the proper floor.
- Using the code in this example, you must input "refresh" into the console before inputting "up" and "down". However, you only need to input "refresh" when you've moved to a new tile; otherwise you can input "up" and "down" as much as you want. For example, you log in and go to Thais depot ground floor and want to level spy the upper floors; you must input "refresh", then you can input "up" to see the floor above you, then input "up" again to see the next floor, then input "down" twice to get back to the ground floor. Next, you go down to the Thais depot basement and want to see underneath it; you must input "refresh" first before inputting "down" so that your current position is updated in the code and you level spy to the proper location.
using var client = new OXGaming.TibiaAPI.Client();
client.StartConnection();
var moveCreature = new OXGaming.TibiaAPI.Network.ServerPackets.MoveCreature(client)
{
FromPosition = new Position(ushort.MaxValue, 0, 0)
};
client.Connection.OnReceivedServerLoginSuccessPacket += (packet) =>
{
var p = (OXGaming.TibiaAPI.Network.ServerPackets.LoginSuccess)packet;
moveCreature.CreatureId = p.PlayerId;
return true;
};
var fakePos = new OXGaming.TibiaAPI.Utilities.Position(0, 0, 0);
while (true)
{
var input = System.Console.ReadLine();
switch (input)
{
// this needs to be called, from your player's real position, each time before level spying
case "refresh":
fakePos = client.Player.Position;
break;
case "up":
// levels are in descending order (0 is the highest, 15 is the lowest)
// subtract 1 to move up a floor
fakePos.Z -= 1;
moveCreature.ToPosition = fakePos;
client.Connection.SendToClient(moveCreature);
break;
case "down":
// levels are in descending order (0 is the highest, 15 is the lowest)
// add 1 to move down a floor
fakePos.Z += 1;
moveCreature.ToPosition = fakePos;
client.Connection.SendToClient(moveCreature);
break;
default:
break;
}
}