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);
template<typename T>
struct Array {
u32 length;
T data[length];
};
It is assumed later in this document that the average total size of a String
is 32 bytes.
typedef Array<u8> String;
#include <bitset>
template<unsigned N>
struct Bitset {
std::bitset<N> value;
}
struct Vec2 {
f32 x, y;
};
struct Vec3 {
f32 x, y, z;
};
struct Vec4 {
f32 x, y, z, w;
};
enum MessageType {
MT_HELLO_REQUEST,
MT_HELLO_RESPONSE,
MT_SPAWN_REQUEST,
MT_SPAWN_RESPONSE,
MT_INPUTS,
MT_RECORD
};
enum ServerState {
SS_WAITING_FOR_HELLO_REQUEST,
SS_WAITING_FOR_SPAWN_REQUEST,
SS_PLAYING,
SS_CLOSE_ON_MESSAGE
};
The initial server state. The server will only accept MT_HELLO_REQUEST
. Other messages will cause the server to close the connection.
The server will only accept MT_SPAWN_REQUEST
. Other messages will cause the server to close the connection.
TODO
The server will close the connection upon receiving a message.
struct Message {
MessageType type;
};
// 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.
// 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.
// 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.
// 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;
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;
};
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;
};
struct Orb: Entity {
// static (4+4+4=12)
f32 radius;
f32 color;
f32 random;
// dynamic (2*4+4=12)
Vec2 position;
f32 angle;
};
struct Bullet: Entity {
// static (4+1+4=9)
f32 ownerRadius;
u8 weaponIndex;
f32 random;
// dynamic (2*4+2*4=16)
Vec2 position;
Vec2 direction;
};