mirror of
https://github.com/Tha14/toxic.git
synced 2024-11-14 07:23:01 +01:00
Add networking to game engine / add multiplayer chess
This commit is contained in:
parent
97cb43a218
commit
787d081e74
4
Makefile
4
Makefile
@ -13,8 +13,8 @@ LDFLAGS ?=
|
|||||||
LDFLAGS += ${USER_LDFLAGS}
|
LDFLAGS += ${USER_LDFLAGS}
|
||||||
|
|
||||||
OBJ = autocomplete.o avatars.o bootstrap.o chat.o chat_commands.o configdir.o curl_util.o execute.o
|
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 += 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 += 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
|
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
|
# Check if debug build is enabled
|
||||||
|
72
src/chat.c
72
src/chat.c
@ -35,6 +35,7 @@
|
|||||||
#include "execute.h"
|
#include "execute.h"
|
||||||
#include "file_transfers.h"
|
#include "file_transfers.h"
|
||||||
#include "friendlist.h"
|
#include "friendlist.h"
|
||||||
|
#include "game_base.h"
|
||||||
#include "help.h"
|
#include "help.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "line_info.h"
|
#include "line_info.h"
|
||||||
@ -88,6 +89,7 @@ static const char *chat_cmd_list[] = {
|
|||||||
"/nick",
|
"/nick",
|
||||||
"/note",
|
"/note",
|
||||||
"/nospam",
|
"/nospam",
|
||||||
|
"/play",
|
||||||
"/quit",
|
"/quit",
|
||||||
"/savefile",
|
"/savefile",
|
||||||
"/sendfile",
|
"/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.");
|
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 */
|
/* AV Stuff */
|
||||||
#ifdef AUDIO
|
#ifdef AUDIO
|
||||||
|
|
||||||
@ -1475,6 +1546,7 @@ ToxWindow *new_chat(Tox *m, uint32_t friendnum)
|
|||||||
ret->onFileControl = &chat_onFileControl;
|
ret->onFileControl = &chat_onFileControl;
|
||||||
ret->onFileRecv = &chat_onFileRecv;
|
ret->onFileRecv = &chat_onFileRecv;
|
||||||
ret->onReadReceipt = &chat_onReadReceipt;
|
ret->onReadReceipt = &chat_onReadReceipt;
|
||||||
|
ret->onGameInvite = &chat_onGameInvite;
|
||||||
|
|
||||||
#ifdef AUDIO
|
#ifdef AUDIO
|
||||||
ret->onInvite = &chat_onInvite;
|
ret->onInvite = &chat_onInvite;
|
||||||
|
@ -172,6 +172,59 @@ void cmd_conference_join(WINDOW *window, ToxWindow *self, Tox *m, int argc, char
|
|||||||
#endif
|
#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])
|
void cmd_savefile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE])
|
||||||
{
|
{
|
||||||
UNUSED_VAR(window);
|
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_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_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_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_savefile(WINDOW *, ToxWindow *, Tox *, int argc, char (*argv)[MAX_STR_SIZE]);
|
||||||
void cmd_sendfile(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 },
|
{ "/cancel", cmd_cancelfile },
|
||||||
{ "/invite", cmd_conference_invite },
|
{ "/invite", cmd_conference_invite },
|
||||||
{ "/join", cmd_conference_join },
|
{ "/join", cmd_conference_join },
|
||||||
|
{ "/play", cmd_game_join },
|
||||||
{ "/savefile", cmd_savefile },
|
{ "/savefile", cmd_savefile },
|
||||||
{ "/sendfile", cmd_sendfile },
|
{ "/sendfile", cmd_sendfile },
|
||||||
#ifdef AUDIO
|
#ifdef AUDIO
|
||||||
|
@ -116,8 +116,9 @@ static void realloc_blocklist(int n)
|
|||||||
void kill_friendlist(ToxWindow *self)
|
void kill_friendlist(ToxWindow *self)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < Friends.max_idx; ++i) {
|
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].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,
|
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)
|
uint64_t file_size, const char *filename, size_t name_length)
|
||||||
{
|
{
|
||||||
@ -1368,6 +1397,7 @@ ToxWindow *new_friendlist(void)
|
|||||||
ret->onStatusMessageChange = &friendlist_onStatusMessageChange;
|
ret->onStatusMessageChange = &friendlist_onStatusMessageChange;
|
||||||
ret->onFileRecv = &friendlist_onFileRecv;
|
ret->onFileRecv = &friendlist_onFileRecv;
|
||||||
ret->onConferenceInvite = &friendlist_onConferenceInvite;
|
ret->onConferenceInvite = &friendlist_onConferenceInvite;
|
||||||
|
ret->onGameInvite = &friendlist_onGameInvite;
|
||||||
|
|
||||||
#ifdef AUDIO
|
#ifdef AUDIO
|
||||||
ret->onInvite = &friendlist_onAV;
|
ret->onInvite = &friendlist_onAV;
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "file_transfers.h"
|
#include "file_transfers.h"
|
||||||
|
#include "game_base.h"
|
||||||
#include "toxic.h"
|
#include "toxic.h"
|
||||||
#include "windows.h"
|
#include "windows.h"
|
||||||
|
|
||||||
@ -42,6 +43,14 @@ struct ConferenceInvite {
|
|||||||
bool pending;
|
bool pending;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GameInvite {
|
||||||
|
uint8_t *data;
|
||||||
|
size_t data_length;
|
||||||
|
GameType type;
|
||||||
|
uint32_t id;
|
||||||
|
bool pending;
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[TOXIC_MAX_NAME_LENGTH + 1];
|
char name[TOXIC_MAX_NAME_LENGTH + 1];
|
||||||
uint16_t namelength;
|
uint16_t namelength;
|
||||||
@ -58,6 +67,7 @@ typedef struct {
|
|||||||
|
|
||||||
struct LastOnline last_online;
|
struct LastOnline last_online;
|
||||||
struct ConferenceInvite conference_invite;
|
struct ConferenceInvite conference_invite;
|
||||||
|
struct GameInvite game_invite;
|
||||||
|
|
||||||
struct FileTransfer file_receiver[MAX_FILES];
|
struct FileTransfer file_receiver[MAX_FILES];
|
||||||
struct FileTransfer file_sender[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 <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "friendlist.h"
|
||||||
#include "game_centipede.h"
|
#include "game_centipede.h"
|
||||||
#include "game_base.h"
|
#include "game_base.h"
|
||||||
#include "game_chess.h"
|
#include "game_chess.h"
|
||||||
@ -32,6 +33,8 @@
|
|||||||
#include "line_info.h"
|
#include "line_info.h"
|
||||||
#include "misc_tools.h"
|
#include "misc_tools.h"
|
||||||
|
|
||||||
|
extern struct Winthread Winthread;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determines the base rate at which game objects should update their state.
|
* Determines the base rate at which game objects should update their state.
|
||||||
* Inversely correlated with frame rate.
|
* Inversely correlated with frame rate.
|
||||||
@ -60,7 +63,7 @@
|
|||||||
&& ((max_x) >= (GAME_MAX_RECT_X_SMALL)))
|
&& ((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 {
|
struct GameList {
|
||||||
const char *name;
|
const char *name;
|
||||||
@ -74,13 +77,6 @@ static struct GameList game_list[] = {
|
|||||||
{ NULL, GT_Invalid },
|
{ 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`.
|
* Returns the GameType associated with `game_string`.
|
||||||
*/
|
*/
|
||||||
@ -97,8 +93,7 @@ GameType game_get_type(const char *game_string)
|
|||||||
return GT_Invalid;
|
return GT_Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Returns the string name associated with `type`. */
|
const char *game_get_name_string(GameType type)
|
||||||
static const char *game_get_name_string(GameType type)
|
|
||||||
{
|
{
|
||||||
GameType match_type;
|
GameType match_type;
|
||||||
|
|
||||||
@ -111,9 +106,6 @@ static const char *game_get_name_string(GameType type)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Prints all available games to window associated with `self`.
|
|
||||||
*/
|
|
||||||
void game_list_print(ToxWindow *self)
|
void game_list_print(ToxWindow *self)
|
||||||
{
|
{
|
||||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Available games:");
|
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 */
|
/* Returns the current wall time in milliseconds */
|
||||||
TIME_MS get_time_millis(void)
|
TIME_MS get_time_millis(void)
|
||||||
{
|
{
|
||||||
struct timespec t;
|
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;
|
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) {
|
switch (game->type) {
|
||||||
case GT_Snake: {
|
case GT_Snake: {
|
||||||
@ -180,7 +177,7 @@ static int game_initialize_type(GameData *game)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case GT_Chess: {
|
case GT_Chess: {
|
||||||
ret = chess_initialize(game);
|
ret = chess_initialize(game, data, length);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,18 +189,14 @@ static int game_initialize_type(GameData *game)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int game_initialize(const ToxWindow *parent, Tox *m, GameType type, uint32_t id, const uint8_t *multiplayer_data,
|
||||||
* Initializes game instance.
|
size_t length, bool force_small_window)
|
||||||
*
|
|
||||||
* 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 max_x;
|
int max_x;
|
||||||
int max_y;
|
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_x = GAME_MAX_SQUARE_X;
|
||||||
int max_game_window_y = GAME_MAX_SQUARE_Y;
|
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) {
|
if (self == NULL) {
|
||||||
return -2;
|
return -4;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameData *game = self->game;
|
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) {
|
if (window_id == -1) {
|
||||||
free(game);
|
free(game);
|
||||||
free(self);
|
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->window_shape = GW_ShapeSquare;
|
||||||
game->game_max_x = max_game_window_x;
|
game->game_max_x = max_game_window_x;
|
||||||
game->game_max_y = max_game_window_y;
|
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->update_interval = GAME_DEFAULT_UPDATE_INTERVAL;
|
||||||
game->type = type;
|
game->type = type;
|
||||||
game->window_id = window_id;
|
game->window_id = window_id;
|
||||||
game->level = 0;
|
|
||||||
game->window = subwin(self->window, max_y, max_x, 0, 0);
|
game->window = subwin(self->window, max_y, max_x, 0, 0);
|
||||||
|
game->id = id;
|
||||||
|
game->friend_number = parent->num;
|
||||||
|
|
||||||
if (game->window == NULL) {
|
if (game->window == NULL) {
|
||||||
game_kill(self);
|
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);
|
game_kill(self);
|
||||||
return -1;
|
return init_ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
game->status = GS_Running;
|
game->status = GS_Running;
|
||||||
@ -283,11 +296,8 @@ int game_set_window_shape(GameData *game, GameWindowShape shape)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToxWindow *parent = game->parent;
|
const int max_x = game->parent_max_x;
|
||||||
|
const int max_y = game->parent_max_y;
|
||||||
int max_x;
|
|
||||||
int max_y;
|
|
||||||
game_get_parent_max_x_y(parent, &max_x, &max_y);
|
|
||||||
|
|
||||||
if (WINDOW_SIZE_LARGE_RECT_VALID(max_x, max_y)) {
|
if (WINDOW_SIZE_LARGE_RECT_VALID(max_x, max_y)) {
|
||||||
game->game_max_x = GAME_MAX_RECT_X;
|
game->game_max_x = GAME_MAX_RECT_X;
|
||||||
@ -439,14 +449,14 @@ static int game_restart(GameData *game)
|
|||||||
|
|
||||||
game_clear_all_messages(game);
|
game_clear_all_messages(game);
|
||||||
|
|
||||||
if (game_initialize_type(game) == -1) {
|
if (game_initialize_type(game, NULL, 0) == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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_x;
|
||||||
int max_y;
|
int max_y;
|
||||||
@ -456,10 +466,12 @@ static void game_draw_help_bar(WINDOW *win)
|
|||||||
|
|
||||||
wmove(win, max_y - 1, 1);
|
wmove(win, max_y - 1, 1);
|
||||||
|
|
||||||
wprintw(win, "Pause: ");
|
if (!game->is_multiplayer) {
|
||||||
wattron(win, A_BOLD);
|
wprintw(win, "Pause: ");
|
||||||
wprintw(win, "F2 ");
|
wattron(win, A_BOLD);
|
||||||
wattroff(win, A_BOLD);
|
wprintw(win, "F2 ");
|
||||||
|
wattroff(win, A_BOLD);
|
||||||
|
}
|
||||||
|
|
||||||
wprintw(win, "Quit: ");
|
wprintw(win, "Quit: ");
|
||||||
wattron(win, A_BOLD);
|
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`
|
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);
|
draw_window_bar(self);
|
||||||
|
|
||||||
curs_set(0);
|
curs_set(0);
|
||||||
|
|
||||||
GameData *game = self->game;
|
|
||||||
|
|
||||||
int max_x;
|
int max_x;
|
||||||
int max_y;
|
int max_y;
|
||||||
getmaxyx(game->window, max_y, max_x);
|
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)
|
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(is_printable);
|
||||||
|
UNUSED_VAR(m);
|
||||||
|
|
||||||
GameData *game = self->game;
|
GameData *game = self->game;
|
||||||
|
|
||||||
@ -688,12 +700,12 @@ bool game_onKey(ToxWindow *self, Tox *m, wint_t key, bool is_printable)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == KEY_F(2)) {
|
if (!game->is_multiplayer && key == KEY_F(2)) {
|
||||||
game_toggle_pause(self->game);
|
game_toggle_pause(self->game);
|
||||||
return true;
|
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) {
|
if (game_restart(self->game) == -1) {
|
||||||
fprintf(stderr, "Warning: game_restart() failed\n");
|
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->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);
|
game->cb_game_key_press(game, key, game->cb_game_key_press_data);
|
||||||
|
|
||||||
|
if (game->is_multiplayer) {
|
||||||
|
pthread_mutex_unlock(&Winthread.lock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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);
|
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);
|
const char *window_name = game_get_name_string(type);
|
||||||
|
|
||||||
@ -737,11 +805,13 @@ static ToxWindow *game_new_window(GameType type)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret->num = friendnumber;
|
||||||
ret->type = WINDOW_TYPE_GAME;
|
ret->type = WINDOW_TYPE_GAME;
|
||||||
|
|
||||||
ret->onInit = &game_onInit;
|
ret->onInit = &game_onInit;
|
||||||
ret->onDraw = &game_onDraw;
|
ret->onDraw = &game_onDraw;
|
||||||
ret->onKey = &game_onKey;
|
ret->onKey = &game_onKey;
|
||||||
|
ret->onGameData = &game_onPacket;
|
||||||
|
|
||||||
ret->game = calloc(1, sizeof(GameData));
|
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;
|
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() */
|
/* Maximum length of a game message set with game_set_message() */
|
||||||
#define GAME_MAX_MESSAGE_SIZE 64
|
#define GAME_MAX_MESSAGE_SIZE 64
|
||||||
|
|
||||||
|
/* Default number of seconds a game message is displayed for */
|
||||||
#define GAME_MESSAGE_DEFAULT_TIMEOUT 3
|
#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_update_state(GameData *game, void *cb_data);
|
||||||
typedef void cb_game_render_window(GameData *game, WINDOW *window, 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_kill(GameData *game, void *cb_data);
|
||||||
typedef void cb_game_pause(GameData *game, bool is_paused, 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_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 {
|
typedef enum GameWindowShape {
|
||||||
GW_ShapeSquare = 0u,
|
GW_ShapeSquare = 0u,
|
||||||
@ -105,6 +126,7 @@ struct GameData {
|
|||||||
size_t level;
|
size_t level;
|
||||||
GameStatus status;
|
GameStatus status;
|
||||||
GameType type;
|
GameType type;
|
||||||
|
bool is_multiplayer;
|
||||||
|
|
||||||
bool show_lives;
|
bool show_lives;
|
||||||
bool show_score;
|
bool show_score;
|
||||||
@ -116,11 +138,20 @@ struct GameData {
|
|||||||
|
|
||||||
int game_max_x; // max dimensions of game window
|
int game_max_x; // max dimensions of game window
|
||||||
int game_max_y;
|
int game_max_y;
|
||||||
|
|
||||||
|
int parent_max_x; // max dimensions of parent window
|
||||||
|
int parent_max_y;
|
||||||
|
|
||||||
int window_id;
|
int window_id;
|
||||||
WINDOW *window;
|
WINDOW *window;
|
||||||
const ToxWindow *parent;
|
|
||||||
|
Tox *tox; // must be locked with Winthread mutex
|
||||||
|
|
||||||
GameWindowShape window_shape;
|
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;
|
cb_game_update_state *cb_game_update_state;
|
||||||
void *cb_game_update_state_data;
|
void *cb_game_update_state_data;
|
||||||
|
|
||||||
@ -135,6 +166,9 @@ struct GameData {
|
|||||||
|
|
||||||
cb_game_key_press *cb_game_key_press;
|
cb_game_key_press *cb_game_key_press;
|
||||||
void *cb_game_key_press_data;
|
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);
|
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.
|
* 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 0 on success.
|
||||||
* Return -1 if screen is too small.
|
* 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.
|
* 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);
|
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`.
|
* Prints all available games to window associated with `self`.
|
||||||
*/
|
*/
|
||||||
void game_list_print(ToxWindow *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.
|
* 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);
|
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
|
#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"
|
#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
|
#endif // GAME_CHESS
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "game_util.h"
|
#include "game_util.h"
|
||||||
#include "toxic.h"
|
#include "toxic.h"
|
||||||
@ -156,3 +157,38 @@ int game_util_random_colour(void)
|
|||||||
return RED;
|
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);
|
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
|
#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) {
|
switch (ret) {
|
||||||
case 0: {
|
case 0: {
|
||||||
@ -386,6 +387,18 @@ void cmd_game(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA
|
|||||||
return;
|
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: {
|
default: {
|
||||||
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
|
line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Game failed to initialize (error %d)", ret);
|
||||||
return;
|
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_chunk_request(m, on_file_chunk_request);
|
||||||
tox_callback_file_recv_control(m, on_file_recv_control);
|
tox_callback_file_recv_control(m, on_file_recv_control);
|
||||||
tox_callback_file_recv_chunk(m, on_file_recv_chunk);
|
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)
|
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);
|
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_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_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 */
|
#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 */
|
/* CALLBACKS END */
|
||||||
|
|
||||||
int add_window(Tox *m, ToxWindow *w)
|
int add_window(Tox *m, ToxWindow *w)
|
||||||
|
@ -43,6 +43,13 @@
|
|||||||
#define TOP_BAR_HEIGHT 1
|
#define TOP_BAR_HEIGHT 1
|
||||||
#define WINDOW_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. */
|
/* ncurses colour pairs as FOREGROUND_BACKGROUND. No background defaults to black. */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
WHITE,
|
WHITE,
|
||||||
@ -160,6 +167,10 @@ struct ToxWindow {
|
|||||||
void(*onTypingChange)(ToxWindow *, Tox *, uint32_t, bool);
|
void(*onTypingChange)(ToxWindow *, Tox *, uint32_t, bool);
|
||||||
void(*onReadReceipt)(ToxWindow *, Tox *, uint32_t, uint32_t);
|
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
|
#ifdef AUDIO
|
||||||
|
|
||||||
void(*onInvite)(ToxWindow *, ToxAV *, uint32_t, int);
|
void(*onInvite)(ToxWindow *, ToxAV *, uint32_t, int);
|
||||||
|
Loading…
Reference in New Issue
Block a user