mirror of
https://github.com/Tha14/toxic.git
synced 2024-11-22 04:53:02 +01:00
Add networking to game engine / add multiplayer chess
This commit is contained in:
parent
60bdcf0ba5
commit
7aeb1a0aac
4
Makefile
4
Makefile
@ -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
|
||||
|
72
src/chat.c
72
src/chat.c
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
229
src/game_base.c
229
src/game_base.c
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
804
src/game_chess.c
804
src/game_chess.c
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user