749 lines
17 KiB
C
749 lines
17 KiB
C
#include <menu.h>
|
|
#include <menu_private.h>
|
|
#include <game.h>
|
|
#include <gui.h>
|
|
#include <gui/button.h>
|
|
#include <gui/checkbox.h>
|
|
#include <gui/container.h>
|
|
#include <gui/label.h>
|
|
#include <gui/line_edit.h>
|
|
#include <gui/rounded_rect.h>
|
|
#include <net.h>
|
|
#include <packet.h>
|
|
#include <util.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
enum
|
|
{
|
|
CHAT_ENTRIES = 6,
|
|
TOTAL_MESSAGE_LEN = GAME_PLAYER_NAME_LEN
|
|
+ sizeof ": " - 1
|
|
+ PACKET_MAX_MESSAGE_LEN
|
|
};
|
|
|
|
struct gamecfg_menu
|
|
{
|
|
char chat_in[PACKET_MAX_MESSAGE_LEN],
|
|
chat[CHAT_ENTRIES][TOTAL_MESSAGE_LEN];
|
|
char chat_label[sizeof "Chat (255)"];
|
|
uint8_t chat_i, unread_messages;
|
|
struct gui_container bcnt;
|
|
struct gui_button start, back;
|
|
struct net_host *host;
|
|
struct packet_ctx ctx;
|
|
bool update_page;
|
|
|
|
enum
|
|
{
|
|
PAGE_1,
|
|
PAGE_2,
|
|
|
|
N_PAGES
|
|
} page_i;
|
|
|
|
struct player_info
|
|
{
|
|
struct player_info_net
|
|
{
|
|
net_peer peer;
|
|
struct packet_input in;
|
|
} *net;
|
|
|
|
char name[GAME_PLAYER_NAME_LEN];
|
|
|
|
enum
|
|
{
|
|
OPEN,
|
|
CLOSED,
|
|
CONNECTING,
|
|
NOT_READY,
|
|
READY
|
|
} state;
|
|
} pl[GAME_MAX_PLAYERS];
|
|
|
|
union
|
|
{
|
|
struct gui_container root;
|
|
|
|
struct gamecfg_page1
|
|
{
|
|
struct gui_container root, cnt, ready_cnt;
|
|
struct gui_rounded_rect r;
|
|
struct gui_label ready;
|
|
struct gui_checkbox ready_ch;
|
|
struct gui_button chat_page;
|
|
|
|
struct player_info_gui
|
|
{
|
|
struct gui_container cnt;
|
|
struct gui_label name, state_label;
|
|
} pl[GAME_MAX_PLAYERS];
|
|
} page1;
|
|
|
|
struct gamecfg_page2
|
|
{
|
|
struct gui_container root, chat_cnt, bcnt;
|
|
struct gui_rounded_rect r;
|
|
struct gui_line_edit chat_le;
|
|
struct gui_button send, player_page;
|
|
struct gui_label entries[CHAT_ENTRIES];
|
|
} page2;
|
|
} u;
|
|
};
|
|
|
|
static void update_players(struct gamecfg_menu *const m)
|
|
{
|
|
if (m->page_i != PAGE_1)
|
|
return;
|
|
|
|
for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
|
|
{
|
|
struct player_info *const pi = &m->pl[i];
|
|
struct player_info_gui *const g = &m->u.page1.pl[i];
|
|
|
|
static const char *const states[] =
|
|
{
|
|
[OPEN] = "Open",
|
|
[CLOSED] = "Closed",
|
|
[NOT_READY] = "Not ready",
|
|
[CONNECTING] = "Connecting",
|
|
[READY] = "Ready"
|
|
};
|
|
|
|
g->state_label.text = states[pi->state];
|
|
}
|
|
}
|
|
|
|
static void on_next_page_pressed(void *const arg)
|
|
{
|
|
struct gamecfg_menu *const m = arg;
|
|
|
|
m->update_page = true;
|
|
}
|
|
|
|
static int send_ready(const net_peer p, const struct packet_ctx *const c,
|
|
const struct player_info *const pi)
|
|
{
|
|
const struct packet_ready r =
|
|
{
|
|
.common =
|
|
{
|
|
.type = PACKET_TYPE_READY
|
|
},
|
|
|
|
.ready = pi->state == READY
|
|
};
|
|
|
|
return packet_send(c, p, (const union packet *)&r);
|
|
}
|
|
|
|
static void on_ready_pressed(const bool active, void *const arg)
|
|
{
|
|
struct gamecfg_menu *const m = arg;
|
|
struct player_info *const self = m->pl;
|
|
|
|
self->state = active ? READY : NOT_READY;
|
|
|
|
for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
|
|
{
|
|
struct player_info_net *const net = m->pl[i].net;
|
|
|
|
if (net && net->peer)
|
|
send_ready(net->peer, &m->ctx, self);
|
|
}
|
|
}
|
|
|
|
static void init_page1(struct gamecfg_menu *const m)
|
|
{
|
|
struct gamecfg_page1 *const p = &m->u.page1;
|
|
|
|
{
|
|
struct gui_rounded_rect *const r = &p->r;
|
|
|
|
gui_rounded_rect_init(r);
|
|
r->adjust = true;
|
|
r->common.hcentered = true;
|
|
gui_add_child(&p->root.common, &r->common);
|
|
}
|
|
|
|
{
|
|
struct gui_container *const c = &p->cnt;
|
|
|
|
gui_container_init(c);
|
|
c->mode = GUI_CONTAINER_MODE_V;
|
|
c->spacing = 4;
|
|
gui_add_child(&p->r.common, &c->common);
|
|
}
|
|
|
|
for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
|
|
{
|
|
struct player_info *const pi = &m->pl[i];
|
|
struct player_info_gui *const g = &p->pl[i];
|
|
|
|
{
|
|
struct gui_container *const c = &g->cnt;
|
|
|
|
gui_container_init(c);
|
|
c->mode = GUI_CONTAINER_MODE_H;
|
|
c->spacing = 8;
|
|
gui_add_child(&p->cnt.common, &c->common);
|
|
}
|
|
|
|
{
|
|
struct gui_label *const l = &g->name;
|
|
|
|
gui_label_init(l);
|
|
l->text = pi->name;
|
|
l->common.vcentered = true;
|
|
gui_add_child(&g->cnt.common, &l->common);
|
|
}
|
|
|
|
{
|
|
struct gui_label *const l = &g->state_label;
|
|
|
|
gui_label_init(l);
|
|
l->common.vcentered = true;
|
|
gui_add_child(&g->cnt.common, &l->common);
|
|
}
|
|
}
|
|
|
|
{
|
|
struct gui_container *const c = &p->ready_cnt;
|
|
|
|
gui_container_init(c);
|
|
c->mode = GUI_CONTAINER_MODE_H;
|
|
c->spacing = 4;
|
|
c->common.hcentered = true;
|
|
gui_add_child(&p->cnt.common, &c->common);
|
|
}
|
|
|
|
{
|
|
struct gui_checkbox *const c = &p->ready_ch;
|
|
|
|
gui_checkbox_init(c);
|
|
c->common.vcentered = true;
|
|
c->on_pressed = on_ready_pressed;
|
|
c->arg = m;
|
|
gui_add_child(&p->ready_cnt.common, &c->common);
|
|
}
|
|
|
|
{
|
|
struct gui_label *const l = &p->ready;
|
|
|
|
gui_label_init(l);
|
|
l->font = FONT;
|
|
l->text = "Ready";
|
|
l->common.vcentered = true;
|
|
gui_add_child(&p->ready_cnt.common, &l->common);
|
|
}
|
|
|
|
{
|
|
struct gui_button *const b = &p->chat_page;
|
|
|
|
gui_button_init(b, GUI_BUTTON_TYPE_1);
|
|
b->u.type1.w = 140;
|
|
b->common.hcentered = true;
|
|
b->on_pressed = on_next_page_pressed;
|
|
b->arg = m;
|
|
b->u.type1.label.text = "Chat";
|
|
gui_add_child(&m->u.root.common, &b->common);
|
|
}
|
|
}
|
|
|
|
static int broadcast(struct gamecfg_menu *const m, const union packet *const p)
|
|
{
|
|
for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
|
|
{
|
|
struct player_info_net *const net = m->pl[i].net;
|
|
|
|
if (net)
|
|
{
|
|
const net_peer peer = net->peer;
|
|
|
|
if (peer && packet_send(&m->ctx, peer, p))
|
|
{
|
|
fprintf(stderr, "%s: net_write failed\n", __func__);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void append_msg(struct gamecfg_menu *const m,
|
|
const char *const player, const char *const msg)
|
|
{
|
|
static const size_t sz = sizeof m->chat / sizeof *m->chat;
|
|
char *dst;
|
|
|
|
if (m->chat_i < sz)
|
|
dst = m->chat[m->chat_i++];
|
|
else
|
|
{
|
|
for (size_t i = 0; i < sz - 1; i++)
|
|
strcpy(m->chat[i], m->chat[i + 1]);
|
|
|
|
dst = m->chat[sz - 1];
|
|
*dst = '\0';
|
|
}
|
|
|
|
strcat(dst, player);
|
|
strcat(dst, ": ");
|
|
strcat(dst, msg);
|
|
}
|
|
|
|
static void on_send_pressed(void *const arg)
|
|
{
|
|
struct gamecfg_menu *const m = arg;
|
|
struct gui_line_edit *const le = &m->u.page2.chat_le;
|
|
char *const src = le->text;
|
|
|
|
if (!*src)
|
|
return;
|
|
|
|
const struct packet_chat c =
|
|
{
|
|
.common =
|
|
{
|
|
.type = PACKET_TYPE_CHAT
|
|
},
|
|
|
|
.msg = src
|
|
};
|
|
|
|
if (broadcast(m, (const union packet *)&c))
|
|
return;
|
|
|
|
append_msg(m, m->pl->name, src);
|
|
*src = '\0';
|
|
le->i = 0;
|
|
}
|
|
|
|
static void init_page2(struct gamecfg_menu *const m)
|
|
{
|
|
struct gamecfg_page2 *const p = &m->u.page2;
|
|
|
|
{
|
|
struct gui_rounded_rect *const r = &p->r;
|
|
|
|
gui_rounded_rect_init(r);
|
|
r->adjust = true;
|
|
r->common.hcentered = true;
|
|
gui_add_child(&m->u.root.common, &r->common);
|
|
}
|
|
|
|
{
|
|
struct gui_container *const c = &p->chat_cnt;
|
|
|
|
gui_container_init(c);
|
|
c->mode = GUI_CONTAINER_MODE_V;
|
|
c->spacing = 2;
|
|
gui_add_child(&p->r.common, &c->common);
|
|
}
|
|
|
|
for (size_t i = 0; i < sizeof p->entries / sizeof *p->entries; i++)
|
|
{
|
|
struct gui_label *const l = &p->entries[i];
|
|
|
|
gui_label_init(l);
|
|
l->text = m->chat[i];
|
|
l->font = FONT;
|
|
gui_add_child(&p->chat_cnt.common, &l->common);
|
|
}
|
|
|
|
{
|
|
struct gui_line_edit *const le = &p->chat_le;
|
|
|
|
gui_line_edit_init(le, m->chat_in, sizeof m->chat_in);
|
|
le->common.hcentered = true;
|
|
le->w = 240;
|
|
gui_add_child(&m->u.root.common, &le->common);
|
|
}
|
|
|
|
{
|
|
struct gui_container *const c = &p->bcnt;
|
|
|
|
gui_container_init(c);
|
|
c->common.hcentered = true;
|
|
c->mode = GUI_CONTAINER_MODE_H;
|
|
c->spacing = 2;
|
|
gui_add_child(&m->u.root.common, &c->common);
|
|
}
|
|
|
|
{
|
|
struct gui_button *const b = &p->send;
|
|
|
|
gui_button_init(b, GUI_BUTTON_TYPE_1);
|
|
b->common.vcentered = true;
|
|
b->u.type1.w = 100;
|
|
b->u.type1.label.text = "Send";
|
|
b->on_pressed = on_send_pressed;
|
|
b->arg = m;
|
|
gui_add_child(&p->bcnt.common, &b->common);
|
|
}
|
|
|
|
{
|
|
struct gui_button *const b = &p->player_page;
|
|
|
|
gui_button_init(b, GUI_BUTTON_TYPE_1);
|
|
b->common.vcentered = true;
|
|
b->u.type1.w = 150;
|
|
b->u.type1.label.text = "Player info";
|
|
b->on_pressed = on_next_page_pressed;
|
|
b->arg = m;
|
|
gui_add_child(&p->bcnt.common, &b->common);
|
|
}
|
|
|
|
m->unread_messages = 0;
|
|
}
|
|
|
|
static void init_page_gui(struct gamecfg_menu *const m)
|
|
{
|
|
{
|
|
struct gui_container *const c = &m->u.root;
|
|
|
|
gui_container_init(c);
|
|
c->common.hcentered = true;
|
|
c->common.vcentered = true;
|
|
c->mode = GUI_CONTAINER_MODE_V;
|
|
c->spacing = 4;
|
|
}
|
|
|
|
static void (*const init[])(struct gamecfg_menu *) =
|
|
{
|
|
[PAGE_1] = init_page1,
|
|
[PAGE_2] = init_page2
|
|
};
|
|
|
|
init[m->page_i](m);
|
|
}
|
|
|
|
static void update_send(struct menu_common *const c,
|
|
struct gamecfg_menu *const m)
|
|
{
|
|
if (m->page_i != PAGE_2)
|
|
return;
|
|
|
|
switch (c->p.common.type)
|
|
{
|
|
case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
|
|
/* Fall through. */
|
|
case PERIPHERAL_TYPE_TOUCH:
|
|
{
|
|
struct keyboard *const k = &c->p.kbm.keyboard;
|
|
|
|
if (m->u.page2.chat_le.focus
|
|
&& keyboard_justreleased(k,
|
|
&KEYBOARD_COMBO(KEYBOARD_KEY_RETURN)))
|
|
on_send_pressed(m);
|
|
}
|
|
break;
|
|
|
|
case PERIPHERAL_TYPE_PAD:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void update_page(struct gamecfg_menu *const m)
|
|
{
|
|
if (m->update_page)
|
|
{
|
|
if (++m->page_i >= N_PAGES)
|
|
m->page_i = 0;
|
|
|
|
init_page_gui(m);
|
|
m->update_page = false;
|
|
}
|
|
}
|
|
|
|
static int update(struct menu_common *const c, void *const arg)
|
|
{
|
|
struct gamecfg_menu *const m = arg;
|
|
|
|
m->bcnt.common.y = screen_h - 40;
|
|
|
|
if (m->host)
|
|
net_update(m->host);
|
|
|
|
update_players(m);
|
|
update_send(c, m);
|
|
update_page(m);
|
|
|
|
if (gui_update(&m->u.root.common, &c->p, &c->cam, &c->in)
|
|
|| gui_update(&m->bcnt.common, &c->p, &c->cam, &c->in))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int render(const struct menu_common *const c, void *const arg)
|
|
{
|
|
const struct gamecfg_menu *const m = arg;
|
|
|
|
if (gui_render(&m->u.root.common)
|
|
|| gui_render(&m->bcnt.common))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_peer(struct gamecfg_menu *const m, const net_peer p)
|
|
{
|
|
bool found = false;
|
|
|
|
for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
|
|
{
|
|
struct player_info *const pi = &m->pl[i];
|
|
struct player_info_net *const net = pi->net;
|
|
|
|
if (net && !net->peer)
|
|
{
|
|
net->peer = p;
|
|
pi->state = CONNECTING;
|
|
strcpy(pi->name, "<new player>");
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return !found;
|
|
}
|
|
|
|
static int send_name(const net_peer p, const struct packet_ctx *const c,
|
|
const struct player_info *const pi)
|
|
{
|
|
const struct packet_name n =
|
|
{
|
|
.common =
|
|
{
|
|
.type = PACKET_TYPE_NAME
|
|
},
|
|
|
|
.name = pi->name
|
|
};
|
|
|
|
return packet_send(c, p, (const union packet *)&n);
|
|
}
|
|
|
|
static int broadcast_player_info(const net_peer p,
|
|
const struct gamecfg_menu *const m)
|
|
{
|
|
for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
|
|
{
|
|
const struct player_info *const pi = &m->pl[i];
|
|
|
|
if (*pi->name)
|
|
{
|
|
const struct packet_ctx *const c = &m->ctx;
|
|
|
|
if (send_name(p, c, pi) || send_ready(p, c, pi))
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void on_connected(const net_peer p, void *const arg)
|
|
{
|
|
struct gamecfg_menu *const m = arg;
|
|
|
|
if (net_role(m->host) == NET_ROLE_SERVER)
|
|
broadcast_player_info(p, m);
|
|
|
|
add_peer(m, p);
|
|
}
|
|
|
|
static void on_disconnected(const net_peer p, void *const arg)
|
|
{
|
|
}
|
|
|
|
static struct player_info *get_pl_from_peer(struct gamecfg_menu *const m,
|
|
const net_peer peer)
|
|
{
|
|
for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
|
|
{
|
|
struct player_info *const pi = &m->pl[i];
|
|
|
|
if (pi->net && pi->net->peer == peer)
|
|
return pi;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void on_received(const net_peer p, const void *const buf,
|
|
const size_t n, void *const arg)
|
|
{
|
|
struct gamecfg_menu *const m = arg;
|
|
struct player_info *const pi = get_pl_from_peer(m, p);
|
|
|
|
if (pi && pi->net && packet_feed(&m->ctx, p, &pi->net->in, buf, n))
|
|
fprintf(stderr, "%s: packet_feed failed\n", __func__);
|
|
}
|
|
|
|
static void init_footer_gui(struct gamecfg_menu *const m,
|
|
bool *const start, bool *const back)
|
|
{
|
|
{
|
|
struct gui_container *const c = &m->bcnt;
|
|
|
|
gui_container_init(c);
|
|
c->common.hcentered = true;
|
|
c->mode = GUI_CONTAINER_MODE_H;
|
|
c->spacing = 8;
|
|
}
|
|
|
|
if (net_role(m->host) == NET_ROLE_SERVER)
|
|
{
|
|
struct gui_button *const b = &m->start;
|
|
|
|
gui_button_init(b, GUI_BUTTON_TYPE_1);
|
|
b->u.type1.label.text = "Start";
|
|
b->u.type1.w = 140;
|
|
b->common.vcentered = true;
|
|
b->on_pressed = menu_on_pressed;
|
|
b->arg = start;
|
|
b->common.vcentered = true;
|
|
gui_add_child(&m->bcnt.common, &b->common);
|
|
}
|
|
|
|
{
|
|
struct gui_button *const b = &m->back;
|
|
|
|
gui_button_init(b, GUI_BUTTON_TYPE_1);
|
|
b->u.type1.label.text = "Back";
|
|
b->u.type1.w = 100;
|
|
b->common.vcentered = true;
|
|
b->on_pressed = menu_on_pressed;
|
|
b->arg = back;
|
|
b->common.vcentered = true;
|
|
gui_add_child(&m->bcnt.common, &b->common);
|
|
}
|
|
}
|
|
|
|
static void init_own(const struct menu_common *const c,
|
|
struct player_info *const pi)
|
|
{
|
|
strcpy(pi->name, c->s.name);
|
|
pi->state = NOT_READY;
|
|
}
|
|
|
|
static void on_packet_received(const net_peer peer,
|
|
const union packet *const pkt, void *const arg)
|
|
{
|
|
struct gamecfg_menu *const m = arg;
|
|
|
|
switch (pkt->common.type)
|
|
{
|
|
case PACKET_TYPE_CHAT:
|
|
{
|
|
struct player_info *const pi = get_pl_from_peer(m, peer);
|
|
|
|
if (pi)
|
|
{
|
|
append_msg(m, pi->name, pkt->chat.msg);
|
|
|
|
if (m->page_i == PAGE_1)
|
|
{
|
|
if (m->unread_messages < CHAT_ENTRIES)
|
|
m->unread_messages++;
|
|
|
|
snprintf(m->chat_label, sizeof m->chat_label,
|
|
"Chat (%" PRIu8 ")", m->unread_messages);
|
|
m->u.page1.chat_page.u.type1.label.text = m->chat_label;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PACKET_TYPE_NAME:
|
|
{
|
|
struct player_info *const pi = get_pl_from_peer(m, peer);
|
|
|
|
if (pi)
|
|
strcpy(pi->name, pkt->name.name);
|
|
}
|
|
break;
|
|
|
|
case PACKET_TYPE_READY:
|
|
{
|
|
struct player_info *const pi = get_pl_from_peer(m, peer);
|
|
|
|
if (pi)
|
|
pi->state = pkt->ready.ready ? READY : NOT_READY;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int menu_gamecfg(struct menu_common *const c, struct net_host *const h,
|
|
const net_peer p)
|
|
{
|
|
struct gamecfg_menu m =
|
|
{
|
|
.host = h,
|
|
.ctx =
|
|
{
|
|
.arg = &m,
|
|
.cb = on_packet_received,
|
|
.host = h
|
|
}
|
|
};
|
|
|
|
const struct net_event ev =
|
|
{
|
|
.connected = on_connected,
|
|
.disconnected = on_disconnected,
|
|
.received = on_received,
|
|
.arg = &m
|
|
};
|
|
|
|
struct player_info_net n[sizeof m.pl / sizeof *m.pl - 1] = {0};
|
|
bool start = false, back = false;
|
|
|
|
for (size_t i = 0; i < sizeof n / sizeof *n; i++)
|
|
m.pl[i + 1].net = &n[i];
|
|
|
|
if (net_set_event(m.host, &ev))
|
|
return -1;
|
|
|
|
init_own(c, m.pl);
|
|
init_page_gui(&m);
|
|
init_footer_gui(&m, &start, &back);
|
|
|
|
if (p)
|
|
{
|
|
const struct packet_ctx *const c = &m.ctx;
|
|
|
|
if (add_peer(&m, p)
|
|
|| send_name(p, c, m.pl)
|
|
|| send_ready(p, c, m.pl))
|
|
return -1;
|
|
}
|
|
|
|
while (!back && !c->p.common.exit && !start)
|
|
if (menu_update(c, update, render, &m))
|
|
return -1;
|
|
|
|
if (start)
|
|
{
|
|
struct game_cfg cfg =
|
|
{
|
|
.p = &c->p
|
|
};
|
|
|
|
return game(&cfg);
|
|
}
|
|
|
|
return 0;
|
|
}
|