network module

Network package.

I’m too lazy to write more.

network.builders

Builders package.

These builders are used to build packets based on their nature. For instance, the player builder will only build player-related packets. The base builder is the base of all builders.

network.builders.base_builder

Module name: base_builder

class game.network.builders.base_builder.BaseBuilder

Bases: object

Base class for packet building

COMMAND_ID_KEY = 'id'
COMMAND_ID_MAX = 1
COMMAND_ID_OFFSET = 29
COMMAND_ID_RANGE = range(0, 2)
PLAYER_POSITION_UPDATE_COMMAND = 0
PLAYER_TILE_BREAK_COMMAND = 1
TIMESTAMP_KEY = 'timestamp'

network.builders.player_builder

Module name: player_builder

class game.network.builders.player_builder.PlayerBuilder

Bases: object

Class for the player packet builder.

BROKEN_TILE_X = 'broken_tile_x'
BROKEN_TILE_Y = 'broken_tile_y'
HEALTH_KEY = 'health'
LERP_KEY = 'lerp'
NAME_KEY = 'name'
PREV_X_POS_KEY = 'previous_x'
PREV_Y_POS_KEY = 'previous_y'
X_POS_KEY = 'x'
Y_POS_KEY = 'y'
static compress_player(player: dict) bytes

Compress a player dict into small, compact bytes object. TODO: Use UUIDs instead of names

static create_player() dict

Create a player packet.

static decompress_player(bytes_obj: bytes) dict

Decompress a player bytes object back to a player dict.

static get_compressed_player_packet(command_id: int, player) bytes

Return a compressed update player packet from the attributes of the given player object.

static get_decompressed_player_packet(bytes_obj: bytes) dict

Return a player dict from a player bytes object.

static update_player_from_packet(bytes_obj: bytes, player) None

Automatically update a player object from a player bytes object.

network.packet

Module name: packet

This module defines a set of methods for hashing, compressing and altering data packets.

class game.network.packet.Compressor

Bases: object

Class for compressing objects. Used for sending and receiving game data from the client/server.

static compress(obj: any) bytes

Compress an object to bytes.

static decompress(data: bytes) any

Decompress byte data to an object, if valid. Otherwise, return None.

class game.network.packet.Hasher

Bases: object

Class for hashing packets. Used for sending and receiving protocol commands from the client/server.

static enhash(packet: str) bytes

Hash and encode (Protocol.ENCODING) a packet. Return hash of size BUFFER_SIZE.

static hash(packet: str) str

Hash a packet and return hash of size BUFFER_SIZE.

class game.network.packet.Packet

Bases: object

Class for determining the properties of the packet header.

DATA_SIZE = 2
game.network.packet.fill(data: bytes) bytes

Fill the packet with empty data if its size is not a multiple of BUFFER_SIZE.

game.network.packet.from_hex_len(length: bytes) int

Return the length as an integer from a byte.

game.network.packet.hex_len(data: bytes) bytes

Return length of data in hex. Used in packet header for determining data size.

game.network.packet.to_bytes(data: str) bytes

Return string as bytes encoded with protocol’s chosen encoding.

network.protocol

Module name: protocol

class game.network.protocol.Protocol

Bases: object

Class for defining the protocol along with its request and response constants.

Client-server connection process:

The interactions between the client and the server are not entirely API-based. Here are the interactions between the two in chronological order:

  • The client asks for recognition by the server, and the server sends a response back to the client - this ensures that the client is valid

  • The client requests the server’s map data, and the server sends a response to indicate the start of the map’s data. Then, the map data itself is sent, along with a response to indicate the end of the data stream

  • The client requests to join the server, the latter sending back a response, after which the client sends their local player’s in-game name and listening for any potential errors from the server (name already taken, server full, etc).

  • The server requests the client’s local player state to be added, and at which point the client sends the requested data - the server finally responds to confirm that the data was well received

  • The client then requests the global game state, which the server subsequently sends - this is to ensure the client-side knows about all the other players on the server

Client-server update process:

The update process between the client and server allows for the server’s game state to be synchronised with the clients’ throughout the lifespan of the connection. As mentioned prior to this section, the API-based architecture does not necessarily apply. The server will repeatedly send to all clients, at a strict defined rate, its current game state. Here are the different update tasks in order:

  • If the client requests to disconnect from the server, the server will receive the request and disconnect the client

  • The client will first request for the global game state, which the server will provide

  • The server will then request right after the client’s local game state, which the client will provide

The client and server follow a strict delay in TPS (Ticks Per Second), allowing for only a limited amount of calls in a short time frame.

The idea of client predictions and server reconciliation are convoluted features to implement, and though I am eager to explore those ideas further, the genre that this game falls under makes these particular features very complex to add. It’s very fast-paced which requires sending thousands upon thousands of data packets, just to keep track of a single player’s position and movements (as opposed to slower-paced games where player movement is limited). This means the API-based architecture cannot be completely applied. The aforementioned features may be implemented in the future, but for now it’s no longer a priority.

BUFFER_SIZE = 32
CPACKET_MAGIC = 'CGPKT'
DISCONNECT_REQ = 'BYEBYE'
ENCODING = 'utf-8'
GLGAME_EOS = 'OKGOTGLOBALGAMESTATE'
GLGAME_REQ = 'CANISENDGLOBALGAMESTATE'
GLGAME_RES = 'YESSENDGLOBALGAMESTATE'
HIT_REQ = 'HIT'
HIT_RES = 'GOTHIT'
LCGAME_EOS = 'OKGOTLOCALGAMESTATE'
LCGAME_REQ = 'CANISENDLOCALGAMESTATE'
LCGAME_RES = 'YESSENDLOCALGAMESTATE'
MAPDATA_EOS = 'MAPREADY'
MAPDATA_REQ = 'MAPDATA'
MAPDATA_RES = 'DATAMAP'
MAXPLAYERS_ERR = 'REACHEDMAXPLAYERS'
NAMEALREXIST_ERR = 'USERNAMEALREADYEXISTS'
PACKETRECV_RES = 'PACKETRECEIVED'
PACKET_EOS = 'PACKETEND'
PLAYERJOIN_REQ = 'IWANTTOJOIN'
PLAYERJOIN_RES = 'OKGIVEMEYOURNAME'
PLAYEROBJ_RES = 'HEREISPLAYER'
RECOGNITION_REQ = 'TILEGAME'
RECOGNITION_RES = 'GAMETILE'
SPACKET_MAGIC = 'SGPKT'