1
0
mirror of https://github.com/Tha14/toxic.git synced 2024-11-13 07:33:02 +01:00

Add networking to game engine / add multiplayer chess

This commit is contained in:
jfreegman 2021-01-17 13:29:13 -05:00
parent 97cb43a218
commit 787d081e74
No known key found for this signature in database
GPG Key ID: 3627F3144076AE63
18 changed files with 1186 additions and 234 deletions

View File

@ -13,8 +13,8 @@ LDFLAGS ?=
LDFLAGS += ${USER_LDFLAGS}
OBJ = autocomplete.o avatars.o bootstrap.o chat.o chat_commands.o configdir.o curl_util.o execute.o
OBJ += file_transfers.o friendlist.o game_base.o game_centipede.o game_chess.o game_util.o game_snake.o
OBJ += global_commands.o conference_commands.o conference.o help.o input.o line_info.o log.o message_queue.o
OBJ += file_transfers.o friendlist.o game_base.o game_centipede.o game_chess.o game_util.o game_snake.o
OBJ += global_commands.o conference_commands.o conference.o help.o input.o line_info.o log.o message_queue.o
OBJ += misc_tools.o name_lookup.o notify.o prompt.o qr_code.o settings.o term_mplex.o toxic.o toxic_strings.o windows.o
# Check if debug build is enabled

View File

@ -35,6 +35,7 @@
#include "execute.h"
#include "file_transfers.h"
#include "friendlist.h"
#include "game_base.h"
#include "help.h"
#include "input.h"
#include "line_info.h"
@ -88,6 +89,7 @@ static const char *chat_cmd_list[] = {
"/nick",
"/note",
"/nospam",
"/play",
"/quit",
"/savefile",
"/sendfile",
@ -757,6 +759,75 @@ static void chat_onConferenceInvite(ToxWindow *self, Tox *m, int32_t friendnumbe
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type \"/join\" to join the chat.");
}
void chat_onGameInvite(ToxWindow *self, Tox *m, uint32_t friend_number, const uint8_t *data, size_t length)
{
if (!self || self->num != friend_number) {
return;
}
if (length < GAME_PACKET_HEADER_SIZE) {
return;
}
uint8_t version = data[0];
if (version != GAME_NETWORKING_VERSION) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Game invite failed. Friend has network protocol version %d, you have version %d.", version, GAME_NETWORKING_VERSION);
return;
}
GameType type = data[1];
if (!game_type_is_multiplayer(type)) {
return;
}
uint32_t id;
game_util_unpack_u32(data + 2, &id);
const char *game_string = game_get_name_string(type);
if (game_string == NULL) {
return;
}
Friends.list[friend_number].game_invite.type = type;
Friends.list[friend_number].game_invite.id = id;
Friends.list[friend_number].game_invite.pending = true;
uint32_t data_length = length - GAME_PACKET_HEADER_SIZE;
Friends.list[friend_number].game_invite.data_length = data_length;
if (data_length > 0) {
free(Friends.list[friend_number].game_invite.data);
uint8_t *buf = calloc(1, data_length);
if (buf == NULL) {
return;
}
memcpy(buf, data + GAME_PACKET_HEADER_SIZE, data_length);
Friends.list[friend_number].game_invite.data = buf;
}
char name[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, name, friend_number);
if (self->active_box != -1) {
box_notify2(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, self->active_box,
"invites you to play %s", game_string);
} else {
box_notify(self, generic_message, NT_WNDALERT_2 | user_settings->bell_on_invite, &self->active_box, name,
"invites you to play %s", game_string);
}
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s has invited you to a game of %s.", name, game_string);
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Type \"/play\" to join the game.");
}
/* AV Stuff */
#ifdef AUDIO
@ -1475,6 +1546,7 @@ ToxWindow *new_chat(Tox *m, uint32_t friendnum)
ret->onFileControl = &chat_onFileControl;
ret->onFileRecv = &chat_onFileRecv;
ret->onReadReceipt = &chat_onReadReceipt;
ret->onGameInvite = &chat_onGameInvite;
#ifdef AUDIO
ret->onInvite = &chat_onInvite;

View File

@ -172,6 +172,59 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
#endif
}
void cmd_game_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);
UNUSED_VAR(m);
bool force_small = false;
if (argc >= 2) {
force_small = strcasecmp(argv[2], "small") == 0;
}
if (!Friends.list[self->num].game_invite.pending) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending game invite.");
return;
}
if (get_num_active_windows() >= MAX_WINDOWS_NUM) {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, RED, " * Warning: Too many windows are open.");
return;
}
GameType type = Friends.list[self->num].game_invite.type;
uint32_t id = Friends.list[self->num].game_invite.id;
uint8_t *data = Friends.list[self->num].game_invite.data;
size_t length = Friends.list[self->num].game_invite.data_length;
int ret = game_initialize(self, m, type, id, data, length, force_small);
switch (ret) {
case 0: {
free(data);
Friends.list[self->num].game_invite.data = NULL;
Friends.list[self->num].game_invite.pending = false;
break;
}
case -1: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Window is too small. Try enlarging your window.");
return;
}
case -2: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (network error)");
return;
}
default: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
return;
}
}
}
void cmd_savefile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
{
UNUSED_VAR(window);

View File

@ -29,6 +29,7 @@
void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_invite(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_conference_join(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_game_join(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_savefile(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
void cmd_sendfile(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);

View File

@ -81,6 +81,7 @@ static struct cmd_func chat_commands[] = {
{ "/cancel", cmd_cancelfile },
{ "/invite", cmd_conference_invite },
{ "/join", cmd_conference_join },
{ "/play", cmd_game_join },
{ "/savefile", cmd_savefile },
{ "/sendfile", cmd_sendfile },
#ifdef AUDIO

View File

@ -116,8 +116,9 @@ static void realloc_blocklist(int n)
void kill_friendlist(ToxWindow *self)
{
for (size_t i = 0; i < Friends.max_idx; ++i) {
if (Friends.list[i].active && Friends.list[i].conference_invite.key != NULL) {
if (Friends.list[i].active) {
free(Friends.list[i].conference_invite.key);
free(Friends.list[i].game_invite.data);
}
}
@ -582,6 +583,34 @@ static void friendlist_add_blocked(uint32_t fnum, uint32_t bnum)
}
}
static void friendlist_onGameInvite(ToxWindow *self, Tox *m, uint32_t friend_number, const uint8_t *data, size_t length)
{
UNUSED_VAR(self);
UNUSED_VAR(data);
UNUSED_VAR(length);
if (friend_number >= Friends.max_idx) {
return;
}
if (Friends.list[friend_number].chatwin != -1) {
return;
}
if (get_num_active_windows() < MAX_WINDOWS_NUM) {
Friends.list[friend_number].chatwin = add_window(m, new_chat(m, Friends.list[friend_number].num));
return;
}
char nick[TOX_MAX_NAME_LENGTH];
get_nick_truncate(m, nick, friend_number);
line_info_add(prompt, false, NULL, NULL, SYS_MSG, 0, RED,
"* Game invite from %s failed: Too many windows are open.", nick);
sound_notify(prompt, notif_error, NT_WNDALERT_1, NULL);
}
static void friendlist_onFileRecv(ToxWindow *self, Tox *m, uint32_t num, uint32_t filenum,
uint64_t file_size, const char *filename, size_t name_length)
{
@ -1368,6 +1397,7 @@ ToxWindow *new_friendlist(void)
ret->onStatusMessageChange = &friendlist_onStatusMessageChange;
ret->onFileRecv = &friendlist_onFileRecv;
ret->onConferenceInvite = &friendlist_onConferenceInvite;
ret->onGameInvite = &friendlist_onGameInvite;
#ifdef AUDIO
ret->onInvite = &friendlist_onAV;

View File

@ -26,6 +26,7 @@
#include <time.h>
#include "file_transfers.h"
#include "game_base.h"
#include "toxic.h"
#include "windows.h"
@ -42,6 +43,14 @@ struct ConferenceInvite {
bool pending;
};
struct GameInvite {
uint8_t *data;
size_t data_length;
GameType type;
uint32_t id;
bool pending;
};
typedef struct {
char name[TOXIC_MAX_NAME_LENGTH + 1];
uint16_t namelength;
@ -58,6 +67,7 @@ typedef struct {
struct LastOnline last_online;
struct ConferenceInvite conference_invite;
struct GameInvite game_invite;
struct FileTransfer file_receiver[MAX_FILES];
struct FileTransfer file_sender[MAX_FILES];

View File

@ -25,6 +25,7 @@
#include <string.h>
#include <time.h>
#include "friendlist.h"
#include "game_centipede.h"
#include "game_base.h"
#include "game_chess.h"
@ -32,6 +33,8 @@
#include "line_info.h"
#include "misc_tools.h"
extern struct Winthread Winthread;
/*
* Determines the base rate at which game objects should update their state.
* Inversely correlated with frame rate.
@ -60,7 +63,7 @@
&& ((max_x) >= (GAME_MAX_RECT_X_SMALL)))
static ToxWindow *game_new_window(GameType type);
static ToxWindow *game_new_window(GameType type, uint32_t friendnumber);
struct GameList {
const char *name;
@ -74,13 +77,6 @@ static struct GameList game_list[] = {
{ NULL, GT_Invalid },
};
static void game_get_parent_max_x_y(const ToxWindow *parent, int *max_x, int *max_y)
{
getmaxyx(parent->window, *max_y, *max_x);
*max_y -= (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT);
}
/*
* Returns the GameType associated with `game_string`.
*/
@ -97,8 +93,7 @@ GameType game_get_type(const char *game_string)
return GT_Invalid;
}
/* Returns the string name associated with `type`. */
static const char *game_get_name_string(GameType type)
const char *game_get_name_string(GameType type)
{
GameType match_type;
@ -111,9 +106,6 @@ static const char *game_get_name_string(GameType type)
return NULL;
}
/*
* Prints all available games to window associated with `self`.
*/
void game_list_print(ToxWindow *self)
{
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Available games:");
@ -125,11 +117,16 @@ void game_list_print(ToxWindow *self)
}
}
bool game_type_is_multiplayer(GameType type)
{
return type == GT_Chess;
}
/* Returns the current wall time in milliseconds */
TIME_MS get_time_millis(void)
{
struct timespec t;
timespec_get(&t, TIME_UTC);
clock_gettime(CLOCK_MONOTONIC, &t);
return ((TIME_MS) t.tv_sec) * 1000 + ((TIME_MS) t.tv_nsec) / 1000000;
}
@ -164,9 +161,9 @@ static void game_toggle_pause(GameData *game)
}
}
static int game_initialize_type(GameData *game)
static int game_initialize_type(GameData *game, const uint8_t *data, size_t length)
{
int ret = -1;
int ret = -3;
switch (game->type) {
case GT_Snake: {
@ -180,7 +177,7 @@ static int game_initialize_type(GameData *game)
}
case GT_Chess: {
ret = chess_initialize(game);
ret = chess_initialize(game, data, length);
break;
}
@ -192,18 +189,14 @@ static int game_initialize_type(GameData *game)
return ret;
}
/*
* Initializes game instance.
*
* Return 0 on success.
* Return -1 if screen is too small.
* Return -2 on other failure.
*/
int game_initialize(const ToxWindow *parent, Tox *m, GameType type, bool force_small_window)
int game_initialize(const ToxWindow *parent, Tox *m, GameType type, uint32_t id, const uint8_t *multiplayer_data,
size_t length, bool force_small_window)
{
int max_x;
int max_y;
game_get_parent_max_x_y(parent, &max_x, &max_y);
getmaxyx(parent->window, max_y, max_x);
max_y -= (CHATBOX_HEIGHT + WINDOW_BAR_HEIGHT);
int max_game_window_x = GAME_MAX_SQUARE_X;
int max_game_window_y = GAME_MAX_SQUARE_Y;
@ -221,10 +214,10 @@ int game_initialize(const ToxWindow *parent, Tox *m, GameType type, bool force_s
}
}
ToxWindow *self = game_new_window(type);
ToxWindow *self = game_new_window(type, parent->num);
if (self == NULL) {
return -2;
return -4;
}
GameData *game = self->game;
@ -234,27 +227,47 @@ int game_initialize(const ToxWindow *parent, Tox *m, GameType type, bool force_s
if (window_id == -1) {
free(game);
free(self);
return -2;
return -4;
}
game->parent = parent;
game->is_multiplayer = game_type_is_multiplayer(type);
if (game->is_multiplayer) {
if (parent->type != WINDOW_TYPE_CHAT) {
game_kill(self);
return -3;
}
if (get_friend_connection_status(parent->num) == TOX_CONNECTION_NONE) {
return -2;
}
game->is_multiplayer = true;
}
game->tox = m;
game->window_shape = GW_ShapeSquare;
game->game_max_x = max_game_window_x;
game->game_max_y = max_game_window_y;
game->parent_max_x = max_x;
game->parent_max_y = max_y;
game->update_interval = GAME_DEFAULT_UPDATE_INTERVAL;
game->type = type;
game->window_id = window_id;
game->level = 0;
game->window = subwin(self->window, max_y, max_x, 0, 0);
game->id = id;
game->friend_number = parent->num;
if (game->window == NULL) {
game_kill(self);
return -2;
return -4;
}
if (game_initialize_type(game) == -1) {
int init_ret = game_initialize_type(game, multiplayer_data, length);
if (init_ret < 0) {
game_kill(self);
return -1;
return init_ret;
}
game->status = GS_Running;
@ -283,11 +296,8 @@ int game_set_window_shape(GameData *game, GameWindowShape shape)
return 0;
}
const ToxWindow *parent = game->parent;
int max_x;
int max_y;
game_get_parent_max_x_y(parent, &max_x, &max_y);
const int max_x = game->parent_max_x;
const int max_y = game->parent_max_y;
if (WINDOW_SIZE_LARGE_RECT_VALID(max_x, max_y)) {
game->game_max_x = GAME_MAX_RECT_X;
@ -439,14 +449,14 @@ static int game_restart(GameData *game)
game_clear_all_messages(game);
if (game_initialize_type(game) == -1) {
if (game_initialize_type(game, NULL, 0) == -1) {
return -1;
}
return 0;
}
static void game_draw_help_bar(WINDOW *win)
static void game_draw_help_bar(const GameData *game, WINDOW *win)
{
int max_x;
int max_y;
@ -456,10 +466,12 @@ static void game_draw_help_bar(WINDOW *win)
wmove(win, max_y - 1, 1);
wprintw(win, "Pause: ");
wattron(win, A_BOLD);
wprintw(win, "F2 ");
wattroff(win, A_BOLD);
if (!game->is_multiplayer) {
wprintw(win, "Pause: ");
wattron(win, A_BOLD);
wprintw(win, "F2 ");
wattroff(win, A_BOLD);
}
wprintw(win, "Quit: ");
wattron(win, A_BOLD);
@ -628,13 +640,13 @@ void game_onDraw(ToxWindow *self, Tox *m)
{
UNUSED_VAR(m); // Note: This function is not thread safe if we ever need to use `m`
game_draw_help_bar(self->window);
GameData *game = self->game;
game_draw_help_bar(game, self->window);
draw_window_bar(self);
curs_set(0);
GameData *game = self->game;
int max_x;
int max_y;
getmaxyx(game->window, max_y, max_x);
@ -678,8 +690,8 @@ void game_onDraw(ToxWindow *self, Tox *m)
bool game_onKey(ToxWindow *self, Tox *m, wint_t key, bool is_printable)
{
UNUSED_VAR(m); // Note: this function is not thread safe if we ever need to use `m`
UNUSED_VAR(is_printable);
UNUSED_VAR(m);
GameData *game = self->game;
@ -688,12 +700,12 @@ bool game_onKey(ToxWindow *self, Tox *m, wint_t key, bool is_printable)
return true;
}
if (key == KEY_F(2)) {
if (!game->is_multiplayer && key == KEY_F(2)) {
game_toggle_pause(self->game);
return true;
}
if (game->status == GS_Finished && key == KEY_F(5)) {
if (!game->is_multiplayer && game->status == GS_Finished && key == KEY_F(5)) {
if (game_restart(self->game) == -1) {
fprintf(stderr, "Warning: game_restart() failed\n");
}
@ -702,7 +714,15 @@ bool game_onKey(ToxWindow *self, Tox *m, wint_t key, bool is_printable)
}
if (game->cb_game_key_press) {
if (game->is_multiplayer) {
pthread_mutex_lock(&Winthread.lock); // we use the tox instance when we send packets
}
game->cb_game_key_press(game, key, game->cb_game_key_press_data);
if (game->is_multiplayer) {
pthread_mutex_unlock(&Winthread.lock);
}
}
return true;
@ -723,7 +743,55 @@ void game_onInit(ToxWindow *self, Tox *m)
self->window_bar = subwin(self->window, WINDOW_BAR_HEIGHT, max_x, max_y - 2, 0);
}
static ToxWindow *game_new_window(GameType type)
/*
* Byte 0: Game type
* Byte 1-4: Game ID
* Byte 5-* Game data
*/
void game_onPacket(ToxWindow *self, Tox *m, uint32_t friendnumber, const uint8_t *data, size_t length)
{
UNUSED_VAR(m);
GameData *game = self->game;
if (friendnumber != self->num) {
return;
}
if (data == NULL) {
return;
}
if (length < GAME_PACKET_HEADER_SIZE || length > GAME_MAX_PACKET_SIZE) {
return;
}
if (data[0] != GAME_NETWORKING_VERSION) {
return;
}
GameType type = (GameType)data[1];
if (game->type != type) {
return;
}
uint32_t id;
game_util_unpack_u32(data + 2, &id);
if (game->id != id) {
return;
}
data += GAME_PACKET_HEADER_SIZE;
length -= GAME_PACKET_HEADER_SIZE;
if (game->cb_game_on_packet) {
game->cb_game_on_packet(game, data, length, game->cb_game_on_packet_data);
}
}
static ToxWindow *game_new_window(GameType type, uint32_t friendnumber)
{
const char *window_name = game_get_name_string(type);
@ -737,11 +805,13 @@ static ToxWindow *game_new_window(GameType type)
return NULL;
}
ret->num = friendnumber;
ret->type = WINDOW_TYPE_GAME;
ret->onInit = &game_onInit;
ret->onDraw = &game_onDraw;
ret->onKey = &game_onKey;
ret->onGameData = &game_onPacket;
ret->game = calloc(1, sizeof(GameData));
@ -941,3 +1011,58 @@ void game_set_cb_on_pause(GameData *game, cb_game_pause *func, void *cb_data)
game->cb_game_pause_data = cb_data;
}
void game_set_cb_on_packet(GameData *game, cb_game_on_packet *func, void *cb_data)
{
game->cb_game_on_packet = func;
game->cb_game_on_packet_data = cb_data;
}
/*
* Wraps `packet` in a header comprised of the custom packet type, game type and game id.
*/
static int game_wrap_packet(const GameData *game, uint8_t *packet, size_t size, GamePacketType packet_type)
{
if (size < GAME_PACKET_HEADER_SIZE + 1) {
return -1;
}
if (packet_type != GP_Invite && packet_type != GP_Data) {
return -1;
}
packet[0] = packet_type == GP_Data ? CUSTOM_PACKET_GAME_DATA : CUSTOM_PACKET_GAME_INVITE;
packet[1] = GAME_NETWORKING_VERSION;
packet[2] = game->type;
game_util_pack_u32(packet + 3, game->id);
return 0;
}
int game_send_packet(const GameData *game, const uint8_t *data, size_t length, GamePacketType packet_type)
{
if (length > GAME_MAX_DATA_SIZE) {
return -1;
}
uint8_t packet[GAME_MAX_PACKET_SIZE];
if (game_wrap_packet(game, packet, sizeof(packet), packet_type) == -1) {
return -1;
}
size_t packet_length = 1 + GAME_PACKET_HEADER_SIZE; // 1 extra byte for custom packet type
memcpy(packet + 1 + GAME_PACKET_HEADER_SIZE, data, length);
packet_length += length;
TOX_ERR_FRIEND_CUSTOM_PACKET err;
if (!tox_friend_send_lossless_packet(game->tox, game->friend_number, packet, packet_length, &err)) {
fprintf(stderr, "failed to send game packet: error %d\n", err);
return -1;
}
return -0;
}

View File

@ -52,14 +52,35 @@
/* Maximum length of a game message set with game_set_message() */
#define GAME_MAX_MESSAGE_SIZE 64
/* Default number of seconds a game message is displayed for */
#define GAME_MESSAGE_DEFAULT_TIMEOUT 3
/***** NETWORKING DEFINES *****/
/* Header starts after custom packet type byte. Comprised of: NetworkVersion (1b) + GameType (1b) + id (4b) */
#define GAME_PACKET_HEADER_SIZE (1 + 1 + sizeof(uint32_t))
/* Max size of a game packet including the header */
#define GAME_MAX_PACKET_SIZE 1024
/* Max size of a game packet payload */
#define GAME_MAX_DATA_SIZE (GAME_MAX_PACKET_SIZE - GAME_PACKET_HEADER_SIZE - 1)
/* Current version of networking protocol */
#define GAME_NETWORKING_VERSION 0x01
typedef void cb_game_update_state(GameData *game, void *cb_data);
typedef void cb_game_render_window(GameData *game, WINDOW *window, void *cb_data);
typedef void cb_game_kill(GameData *game, void *cb_data);
typedef void cb_game_pause(GameData *game, bool is_paused, void *cb_data);
typedef void cb_game_key_press(GameData *game, int key, void *cb_data);
typedef void cb_game_on_packet(GameData *game, const uint8_t *data, size_t length, void *cb_data);
typedef enum GamePacketType {
GP_Invite = 0u,
GP_Data,
} GamePacketType;
typedef enum GameWindowShape {
GW_ShapeSquare = 0u,
@ -105,6 +126,7 @@ struct GameData {
size_t level;
GameStatus status;
GameType type;
bool is_multiplayer;
bool show_lives;
bool show_score;
@ -116,11 +138,20 @@ struct GameData {
int game_max_x; // max dimensions of game window
int game_max_y;
int parent_max_x; // max dimensions of parent window
int parent_max_y;
int window_id;
WINDOW *window;
const ToxWindow *parent;
Tox *tox; // must be locked with Winthread mutex
GameWindowShape window_shape;
uint32_t id; // indentifies multiplayer instance
uint32_t friend_number; // friendnumber associated with parent window
cb_game_update_state *cb_game_update_state;
void *cb_game_update_state_data;
@ -135,6 +166,9 @@ struct GameData {
cb_game_key_press *cb_game_key_press;
void *cb_game_key_press_data;
cb_game_on_packet *cb_game_on_packet;
void *cb_game_on_packet_data;
};
@ -163,14 +197,32 @@ void game_set_cb_on_pause(GameData *game, cb_game_pause *func, void *cb_data);
*/
void game_set_cb_on_keypress(GameData *game, cb_game_key_press *func, void *cb_data);
/*
* Sets the callback for the game packet event.
*/
void game_set_cb_on_packet(GameData *game, cb_game_on_packet *func, void *cb_data);
/*
* Initializes game instance.
*
* `type` must be a valid GameType.
*
* `id` should be a unique integer to indentify the game instance. If we're being invited to a game
* this identifier should be sent via the invite packet.
*
* `force_small_window` will make the game window small.
*
* if `multiplayer_data` is non-null this indicates that we accepted a game invite from a contact.
* The data contains any information we need to initialize the game state.
*
* Return 0 on success.
* Return -1 if screen is too small.
* Return -2 on other failure.
* Return -2 on network related error.
* Return -3 if multiplayer game is being initialized outside of a contact's window.
* Return -4 on other failure.
*/
int game_initialize(const ToxWindow *self, Tox *m, GameType type, bool force_small_window);
int game_initialize(const ToxWindow *self, Tox *m, GameType type, uint32_t id, const uint8_t *multiplayer_data,
size_t length, bool force_small_window);
/*
* Sets game window to `shape` and attempts to adjust size for best fit.
@ -188,11 +240,21 @@ int game_set_window_shape(GameData *game, GameWindowShape shape);
*/
GameType game_get_type(const char *game_string);
/*
* Returns the name represented as a string associated with `type`.
*/
const char *game_get_name_string(GameType type);
/*
* Prints all available games to window associated with `self`.
*/
void game_list_print(ToxWindow *self);
/*
* Return true if game `type` has a multiplayer mode.
*/
bool game_type_is_multiplayer(GameType type);
/*
* Returns true if coordinates designated by `x` and `y` are within the game window boundaries.
*/
@ -308,4 +370,15 @@ TIME_MS get_time_millis(void);
*/
void game_kill(ToxWindow *self);
/*
* Sends a packet containing payload `data` of size `length` to the friendnumber associated with the game's
* parent window.
*
* `length` must not exceed GAME_MAX_DATA_SIZE bytes.
*
* `packet_type` should be GP_Invite for an invite packet or GP_Data for all other game data.
*/
int game_send_packet(const GameData *game, const uint8_t *data, size_t length, GamePacketType packet_type);
#endif // GAME_BASE

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,20 @@
#include "game_base.h"
int chess_initialize(GameData *game);
/*
* Initializes chess game state.
*
* If `init_data` is non-null, this indicates that we were invited to the game.
*
* If we're the inviter, we send an invite packet after initialization. If we're the
* invitee, we send a handshake response packet to the inviter.
*
* Return 0 on success.
* Return -1 if window is too small.
* Return -2 on network related error.
* Return -3 on other error.
*/
int chess_initialize(GameData *game, const uint8_t *init_data, size_t length);
#endif // GAME_CHESS

View File

@ -22,6 +22,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "game_util.h"
#include "toxic.h"
@ -156,3 +157,38 @@ int game_util_random_colour(void)
return RED;
}
}
static size_t net_pack_u16(uint8_t *bytes, uint16_t v)
{
bytes[0] = (v >> 8) & 0xff;
bytes[1] = v & 0xff;
return sizeof(v);
}
static size_t net_unpack_u16(const uint8_t *bytes, uint16_t *v)
{
uint8_t hi = bytes[0];
uint8_t lo = bytes[1];
*v = ((uint16_t)hi << 8) | lo;
return sizeof(*v);
}
size_t game_util_pack_u32(uint8_t *bytes, uint32_t v)
{
uint8_t *p = bytes;
p += net_pack_u16(p, (v >> 16) & 0xffff);
p += net_pack_u16(p, v & 0xffff);
return p - bytes;
}
size_t game_util_unpack_u32(const uint8_t *bytes, uint32_t *v)
{
const uint8_t *p = bytes;
uint16_t hi;
uint16_t lo;
p += net_unpack_u16(p, &hi);
p += net_unpack_u16(p, &lo);
*v = ((uint32_t)hi << 16) | lo;
return p - bytes;
}

View File

@ -89,4 +89,15 @@ void game_util_move_coords(Direction direction, Coords *coords);
*/
int game_util_random_colour(void);
/*
* Packs an unsigned 32 bit integer `v` into `bytes`.
*/
size_t game_util_pack_u32(uint8_t *bytes, uint32_t v);
/*
* Unpacks an unsigned 32 bit integer in `bytes` to `v`.
*/
size_t game_util_unpack_u32(const uint8_t *bytes, uint32_t *v);
#endif // GAME_UTIL

View File

@ -373,7 +373,8 @@ void cmd_game(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
}
}
int ret = game_initialize(self, m, type, force_small);
uint32_t id = rand();
int ret = game_initialize(self, m, type, id, NULL, 0, force_small);
switch (ret) {
case 0: {
@ -386,6 +387,18 @@ void cmd_game(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
return;
}
case -2: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize: Network error.");
return;
}
case -3: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0,
"Game is multiplayer only. Try the command again in the chat window of the contact you wish to play with.");
return;
}
default: {
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
return;

View File

@ -806,6 +806,7 @@ static void init_tox_callbacks(Tox *m)
tox_callback_file_chunk_request(m, on_file_chunk_request);
tox_callback_file_recv_control(m, on_file_recv_control);
tox_callback_file_recv_chunk(m, on_file_recv_chunk);
tox_callback_friend_lossless_packet(m, on_lossless_custom_packet);
}
static void init_tox_options(struct Tox_Options *tox_opts)

View File

@ -135,5 +135,7 @@ void on_file_recv(Tox *m, uint32_t friendnumber, uint32_t filenumber, uint32_t k
const uint8_t *filename, size_t filename_length, void *userdata);
void on_friend_typing(Tox *m, uint32_t friendnumber, bool is_typing, void *userdata);
void on_friend_read_receipt(Tox *m, uint32_t friendnumber, uint32_t receipt, void *userdata);
void on_lossless_custom_packet(Tox *m, uint32_t friendnumber, const uint8_t *data, size_t length, void *userdata);
#endif /* TOXIC_H */

View File

@ -323,6 +323,49 @@ void on_friend_read_receipt(Tox *m, uint32_t friendnumber, uint32_t receipt, voi
}
}
}
void on_lossless_custom_packet(Tox *m, uint32_t friendnumber, const uint8_t *data, size_t length, void *userdata)
{
UNUSED_VAR(userdata);
if (length == 0 || data == NULL) {
return;
}
uint8_t type = data[0];
switch (type) {
case CUSTOM_PACKET_GAME_INVITE: {
for (size_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
ToxWindow *window = windows[i];
if (window != NULL && window->onGameInvite != NULL) {
window->onGameInvite(window, m, friendnumber, data + 1, length - 1);
}
}
break;
}
case CUSTOM_PACKET_GAME_DATA: {
for (size_t i = 0; i < MAX_WINDOWS_NUM; ++i) {
ToxWindow *window = windows[i];
if (window != NULL && window->onGameData != NULL) {
window->onGameData(window, m, friendnumber, data + 1, length - 1);
}
}
break;
}
default: {
fprintf(stderr, "Got unknown custom packet of type: %u\n", type);
return;
}
}
}
/* CALLBACKS END */
int add_window(Tox *m, ToxWindow *w)

View File

@ -43,6 +43,13 @@
#define TOP_BAR_HEIGHT 1
#define WINDOW_BAR_HEIGHT 1
typedef enum CustomPacket {
CUSTOM_PACKET_GAME_INVITE = 160,
CUSTOM_PACKET_GAME_DATA = 161,
} CustomPacket;
/* ncurses colour pairs as FOREGROUND_BACKGROUND. No background defaults to black. */
typedef enum {
WHITE,
@ -160,6 +167,10 @@ struct ToxWindow {
void(*onTypingChange)(ToxWindow *, Tox *, uint32_t, bool);
void(*onReadReceipt)(ToxWindow *, Tox *, uint32_t, uint32_t);
/* custom packets/games */
void(*onGameInvite)(ToxWindow *, Tox *, uint32_t, const uint8_t *, size_t);
void(*onGameData)(ToxWindow *, Tox *, uint32_t, const uint8_t *, size_t);
#ifdef AUDIO
void(*onInvite)(ToxWindow *, ToxAV *, uint32_t, int);