rts/src/menu/src/gamecfg_menu.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;
}