Skip to content

Instantly share code, notes, and snippets.

@CobaltXII
Last active Jun 11, 2021
Embed
What would you like to do?
C4RN4G3 protocol

C4RN4G3 protocol

Data Types

Signed integers use 2's complement for negative values. Floating-point numbers must fulfill the requirements of IEEE 754. All data types are little-endian.

#include <cstdint>
#include <limits>

typedef i8 int8_t;
typedef i16 int16_t;
typedef i32 int32_t;
typedef i64 int64_t;

typedef u8 uint8_t;
typedef u16 uint16_t;
typedef u32 uint32_t;
typedef u64 uint64_t;

typedef f32 float;
typedef f64 double;

static_assert(std::numeric_limits<f32>::is_iec559);
static_assert(std::numeric_limits<f64>::is_iec559);

Arrays

template<typename T>
struct Array {
	u32 length;
	T data[length];
};

Strings

It is assumed later in this document that the average total size of a String is 32 bytes.

typedef Array<u8> String;

Bitsets

#include <bitset>

template<unsigned N> 
struct Bitset {
	std::bitset<N> value;
}

Vectors

struct Vec2 {
	f32 x, y;
};

struct Vec3 {
	f32 x, y, z;
};

struct Vec4 {
	f32 x, y, z, w;
};

Message types

enum MessageType {
	MT_HELLO_REQUEST,
	MT_HELLO_RESPONSE,
	MT_SPAWN_REQUEST,
	MT_SPAWN_RESPONSE,
	MT_INPUTS,
	MT_RECORD
};

States

Server states

enum ServerState {
	SS_WAITING_FOR_HELLO_REQUEST,
	SS_WAITING_FOR_SPAWN_REQUEST,
	SS_PLAYING,
	SS_CLOSE_ON_MESSAGE
};

SS_WAITING_FOR_HELLO_REQUEST

The initial server state. The server will only accept MT_HELLO_REQUEST. Other messages will cause the server to close the connection.

SS_WAITING_FOR_SPAWN_REQUEST

The server will only accept MT_SPAWN_REQUEST. Other messages will cause the server to close the connection.

SS_PLAYING

TODO

SS_CLOSE_ON_MESSAGE

The server will close the connection upon receiving a message.

Messages

struct Message {
	MessageType type;
};

MT_HELLO_REQUEST, MT_HELLO_RESPONSE

// client -> server
struct HelloRequest: Message {
	u32 version;
};

// server -> client
struct HelloResponse: Message {
	u8 okay;
	if (okay) {
		String serverGlobals;
		String weapons;
		String classes;
		Array<u8> levelData;
	}
};

MT_HELLO_REQUEST is sent by a client to a server immediately after establishing a connection. The version field is set to VERSION. Upon receiving MT_HELLO_REQUEST, a server responds with MT_HELLO_RESPONSE. The okay field is set to version == VERSION. If okay, the server sets the other fields accordingly and switches states to SS_WAITING_FOR_SPAWN_REQUEST. Otherwise, the server switches states to SS_CLOSE_ON_MESSAGE. Upon receiving MT_HELLO_RESPONSE, a client checks if okay. If so, the client stores the data. If not, the client must close the connection.

MT_SPAWN_REQUEST, MT_SPAWN_RESPONSE

// client -> server
struct SpawnRequest: Message {
	String name;
	u8 weaponIndex;
	u8 classIndex;
};

// server -> client
struct SpawnResponse: Message {
	u8 status;
};

MT_SPAWN_REQUEST is sent by a client to a server when the user tries to spawn. Upon receiving MT_SPAWN_REQUEST, a server creates a MT_SPAWN_RESPONSE and then checks the incoming fields. If name is invalid, status is set to 1. If weaponIndex is invalid, status is set to 2. If classIndex is invalid, status is set to 3. If nothing is invalid, status is set to 0 and the server switches states to SS_PLAYING. Upon receiving MT_SPAWN_RESPONSE, a client checks if status. If so, the client quietly informs the user of the error but continues as if nothing happened. If not, the client exits the main menu.

MT_INPUTS

// client -> server
struct Inputs: Message {
	f32 angle;
	Bitset<6> inputs;
};

MT_INPUTS is sent periodically by a client to a server. Upon receiving MT_INPUTS, a server processes the data and takes no further action.

MT_RECORD

// server -> client
struct Record: Message {
	f32 time;
	Array<Entity> entities;
	// TODO: events
};

MT_RECORD is sent periodically by a server to a client. Upon receiving MT_RECORD, a client processes the data and takes no further action. MT_RECORD contains of an array of entities. All entities have a code (8-bit) which represents the type of entity they are (least significant 6 bits) as well as the type of data they contain (most significant 2 bits). Entities can contain static data (indicated by the most significant bit of code) and/or dynamic data (indicated by the second most significant bit of code). Static data is typically sent less often than dynamic data as it is used to represent information that rarely changes. All entities also have a unique id (32-bit).

#define ENTITY_CONTAINS_STATIC_DATA 0x80
#define ENTITY_CONTAINS_DYNAMIC_DATA 0x40

struct Entity {
	u8 code;
	u8 id;
	if (code & ENTITY_CONTAINS_STATIC_DATA) {
		// static data
	}
	if (code & ENTITY_CONTAINS_DYNAMIC_DATA) {
		// dynamic data
	}
};

The client contains a map which can translate any id to a pointer to it's associated entity. The map may be implemented as follows:

std::map<u32, void*> idToEntity;

Upon receiving an entity, the client first checks whether id is contained in idToEntity. This boolean is referred to as exists. If exists, the client checks if code & ENTITY_CONTAINS_STATIC_DATA. If so, the client updates the static data of idToEntity[id]. The client then checks if code & ENTITY_CONTAINS_DYNAMIC_DATA. If so, the client updates the dynamic data of idToEntity[id]. If neither code & ENTITY_CONTAINS_STATIC_DATA or code & ENTITY_CONTAINS_DYNAMIC_DATA are true, the client removes id from idToEntity. If !exists, the client checks if code & ENTITY_CONTAINS_STATIC_DATA && code & ENTITY_CONTAINS_DYNAMIC_DATA. If so, the client creates a new entry in idToEntity with the key id and sets it's values accordingly. If not, the client does nothing.

The entity type associated with an entity code can be retreived as follows:

enum EntityType {
	ET_PLAYER,
	ET_BARREL,
	ET_ORB,
	ET_BULLET
};

#define ENTITY_TYPE_MASK 0x3E
EntityType type = Entity.type & ENTITY_TYPE_MASK;

ET_PLAYER

struct Player: Entity {
	// static (1+32+4+1+1=39)
	u8 type;
	String name;
	f32 radius;
	u8 weaponIndex;
	u8 classIndex;
	// dynamic (2*4+4+4+4+4+4+4+4+4=40)
	Vec2 position;
	f32 angle;
	f32 iconAngle;
	f32 healthPercentage;
	f32 score;
	f32 timeWhenLastBulletFired;
	f32 timeWhenLastDamaged;
	f32 latestTimeWhenBelowMaxHealth;
	f32 latestTimeWhenNotBelowMaxHealth;
};

ET_BARREL

struct Barrel: Entity {
	// static (4+4+4+4=16)
	f32 radius;
	f32 color;
	f32 vertices;
	f32 bolts;
	// dynamic (2*4+4+4+4+4+4=28)
	Vec2 position;
	f32 angle;
	f32 healthPercentage;
	f32 timeWhenLastDamaged;
	f32 latestTimeWhenBelowMaxHealth;
	f32 latestTimeWhenNotBelowMaxHealth;
};

ET_ORB

struct Orb: Entity {
	// static (4+4+4=12)
	f32 radius;
	f32 color;
	f32 random;
	// dynamic (2*4+4=12)
	Vec2 position;
	f32 angle;
};

ET_BULLET

struct Bullet: Entity {
	// static (4+1+4=9)
	f32 ownerRadius;
	u8 weaponIndex;
	f32 random;
	// dynamic (2*4+2*4=16)
	Vec2 position;
	Vec2 direction;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment