From d35b4381ecc0b7b670411c01004c2cd5177b3525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Wed, 8 Sep 2021 16:12:38 +0200 Subject: [PATCH] Implement networking layer --- README.md | 1 - code/game/src/debug_draw.c | 2 + code/game/src/debug_ui.c | 8 +- code/game/src/game.c | 41 +++++- code/game/src/network.c | 127 +++++++++++++++---- code/game/src/network.h | 13 +- code/game/src/packet_utils.h | 4 - code/game/src/packets/pkt_00_init.c | 12 +- code/game/src/packets/pkt_send_keystate.c | 6 +- code/game/src/world/world.c | 2 +- code/game/src/world/worldgen/worldgen_test.c | 26 ++++ run_client.bat | 3 + run_client_remote.bat | 5 + 13 files changed, 205 insertions(+), 45 deletions(-) create mode 100644 run_client.bat create mode 100644 run_client_remote.bat diff --git a/README.md b/README.md index a3105cd..ac1188b 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ In the abstract sense, we call the Server the game master hosting all gameplay r ## Major things to do * More believable world generation. -* Networking implementation, the communication part is done, but there's currently no way to start a headless server or connect to it anyhow. * Improved rendering - the current world structure does not allow for layered blocks, but it's something worth looking into. * UI and visual effects diff --git a/code/game/src/debug_draw.c b/code/game/src/debug_draw.c index 6eab0c1..6ca1c57 100644 --- a/code/game/src/debug_draw.c +++ b/code/game/src/debug_draw.c @@ -1,4 +1,5 @@ #include "debug_draw.h" +#include "game.h" static debug_draw_queue draw_queue = {0}; @@ -26,6 +27,7 @@ bool debug_draw_state(void) { inline void debug_push_entry(debug_draw_entry entry) { if (!draw_is_enabled) return; + if (game_get_kind() == GAMEKIND_HEADLESS) return; ZPL_ASSERT(draw_queue.num_entries < DEBUG_DRAW_MAX_ENTRIES); draw_queue.entries[draw_queue.num_entries++] = entry; } diff --git a/code/game/src/debug_ui.c b/code/game/src/debug_ui.c index 1069afb..ac382f2 100644 --- a/code/game/src/debug_ui.c +++ b/code/game/src/debug_ui.c @@ -57,6 +57,7 @@ typedef struct debug_item { struct { struct debug_item *items; uint8_t is_collapsed; + uint8_t is_sp_only; } list; struct { @@ -118,7 +119,8 @@ static debug_item items[] = { } }, { .kind = DITEM_END }, - } + }, + .is_sp_only = true, } }, { @@ -153,7 +155,8 @@ static debug_item items[] = { { .kind = DITEM_BUTTON, .name = "save as...", .on_click = ActReplaySaveAs }, { .kind = DITEM_END }, - } + }, + .is_sp_only = true, } }, { @@ -209,6 +212,7 @@ debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool UIDrawText(it->name, xpos, ypos, DBG_FONT_SIZE, color); ypos += DBG_FONT_SPACING; if (it->list.is_collapsed) break; + if (it->list.is_sp_only && game_get_kind() != GAMEKIND_SINGLE) break; debug_draw_result res = debug_draw_list(it->list.items, xpos+DBG_LIST_XPOS_OFFSET, ypos, is_shadow); ypos = res.y; }break; diff --git a/code/game/src/game.c b/code/game/src/game.c index 95fe1db..ea2887c 100644 --- a/code/game/src/game.c +++ b/code/game/src/game.c @@ -47,12 +47,21 @@ static WORLD_PKT_WRITER(sp_pkt_writer) { } static WORLD_PKT_WRITER(mp_pkt_writer) { - (void)udata; if (pkt->is_reliable) { - return network_msg_send(pkt->data, pkt->datalen); + return network_msg_send(udata, pkt->data, pkt->datalen); } else { - return network_msg_send_unreliable(pkt->data, pkt->datalen); + return network_msg_send_unreliable(udata, pkt->data, pkt->datalen); + } +} + +static WORLD_PKT_WRITER(mp_cli_pkt_writer) { + (void)udata; + if (pkt->is_reliable) { + return network_msg_send(0, pkt->data, pkt->datalen); + } + else { + return network_msg_send_unreliable(0, pkt->data, pkt->datalen); } } @@ -130,17 +139,25 @@ void game_init(game_kind play_mode, uint32_t num_viewers, int32_t seed, uint16_t } if (game_mode == GAMEKIND_CLIENT) { - world_setup_pkt_handlers(pkt_reader, mp_pkt_writer); + world_setup_pkt_handlers(pkt_reader, mp_cli_pkt_writer); +#ifndef _DEBUG + network_client_connect("lab.zakto.pw", 27000); +#else network_client_connect("127.0.0.1", 27000); +#endif } else { stdcpp_set_os_api(); - world_setup_pkt_handlers(pkt_reader, sp_pkt_writer); + world_setup_pkt_handlers(pkt_reader, game_mode == GAMEKIND_SINGLE ? sp_pkt_writer : mp_pkt_writer); world_init(seed, chunk_size, chunk_amount); if (is_dash_enabled) flecs_dash_init(); //ecs_set_target_fps(world_ecs(), 60); + + if (game_mode == GAMEKIND_HEADLESS) { + network_server_start(0, 27000); + } } - if (game_mode != GAMEKIND_HEADLESS) { + if (game_mode == GAMEKIND_SINGLE) { for (uint32_t i = 0; i < num_viewers; i++) { pkt_00_init_send(i); } @@ -157,6 +174,10 @@ void game_shutdown() { network_client_disconnect(); } else { world_destroy(); + + if (game_mode == GAMEKIND_HEADLESS) { + network_server_stop(); + } } if (game_mode != GAMEKIND_SINGLE) { @@ -187,7 +208,13 @@ void game_update() { if (game_mode == GAMEKIND_CLIENT) { network_client_tick(); } - else world_update(); + else { + world_update(); + + if (game_mode == GAMEKIND_HEADLESS) { + network_server_tick(); + } + } if (game_mode != GAMEKIND_HEADLESS) { game_world_cleanup_entities(); diff --git a/code/game/src/network.c b/code/game/src/network.c index 6b5d2d1..15b809d 100644 --- a/code/game/src/network.c +++ b/code/game/src/network.c @@ -11,10 +11,13 @@ #include "network.h" #include "packet.h" #include "world/world.h" +#include "game.h" +#include "player.h" #define NETWORK_UPDATE_DELAY 0.100 static ENetHost *host = NULL; +static ENetHost *server = NULL; static ENetPeer *peer = NULL; static librg_world *world = NULL; @@ -25,73 +28,69 @@ int32_t network_init() { int32_t network_destroy() { enet_deinitialize(); return 0; - + } +//~ NOTE(zaklaus): client int32_t network_client_connect(const char *hostname, uint16_t port) { ENetAddress address = {0}; address.port = port; enet_address_set_host(&address, hostname); - + host = enet_host_create(NULL, 1, 2, 0, 0); peer = enet_host_connect(host, &address, 2, 0); - + if (peer == NULL) { zpl_printf("[ERROR] Cannot connect to specicied server: %s:%d\n", hostname, port); return 1; } - + world = librg_world_create(); librg_world_userdata_set(world, peer); - -#if 0 - librg_event_set(world, LIBRG_READ_CREATE, client_read_create); - librg_event_set(world, LIBRG_READ_UPDATE, client_read_update); - librg_event_set(world, LIBRG_READ_REMOVE, client_read_remove); -#endif - + return 0; } int32_t network_client_disconnect() { enet_peer_disconnect_now(peer, 0); enet_host_destroy(host); - + librg_world_destroy(world); - + peer = NULL; host = NULL; world = NULL; - + return 0; } int32_t network_client_tick() { ENetEvent event = {0}; - + while (enet_host_service(host, &event, 1) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: { zpl_printf("[INFO] We connected to the server.\n"); + pkt_00_init_send(0); } break; case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { zpl_printf("[INFO] We disconnected from server.\n"); } break; - + case ENET_EVENT_TYPE_RECEIVE: { - if (!world_read(event.packet->data, event.packet->dataLength, NULL)) { + if (!world_read(event.packet->data, event.packet->dataLength, event.peer)) { zpl_printf("[INFO] Server sent us an unsupported packet.\n"); } - + /* Clean up the packet now that we're done using it. */ enet_packet_destroy(event.packet); } break; - + case ENET_EVENT_TYPE_NONE: break; } } - + return 0; } @@ -99,15 +98,91 @@ bool network_client_is_connected() { return peer ? enet_peer_get_state(peer) == ENET_PEER_STATE_CONNECTED : false; } -static int32_t network_msg_send_raw(uint16_t peer_id, void *data, size_t datalen, uint32_t flags) { +//~ NOTE(zaklaus): server + +int32_t network_server_start(const char *host, uint16_t port) { + (void)host; + + ENetAddress address = {0}; + + address.host = ENET_HOST_ANY; + address.port = port; + + server = enet_host_create(&address, 100, 2, 0, 0); + + if (server == NULL) { + zpl_printf("[ERROR] An error occured while trying to create a server host.\n"); + return 1; + } + + return 0; +} + +int32_t network_server_stop(void) { + enet_host_destroy(server); + server = 0; + return 0; +} + +int32_t network_server_tick(void) { + ENetEvent event = {0}; + while (enet_host_service(server, &event, 1) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: { + zpl_printf("[INFO] A new user %d connected.\n", event.peer->incomingPeerID); + } break; + case ENET_EVENT_TYPE_DISCONNECT: + case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { + zpl_printf("[INFO] A user %d disconnected.\n", event.peer->incomingPeerID); + + if (event.peer->data) { + player_despawn((ecs_entity_t)event.peer->data); + event.peer->data = 0; + } + } break; + + case ENET_EVENT_TYPE_RECEIVE: { + if (!world_read(event.packet->data, event.packet->dataLength, event.peer)) { + zpl_printf("[INFO] User %d sent us a malformed packet.\n", event.peer->incomingPeerID); + } + + /* Clean up the packet now that we're done using it. */ + enet_packet_destroy(event.packet); + } break; + + case ENET_EVENT_TYPE_NONE: break; + } + } + + return 0; +} + +void network_server_assign_entity(void *peer_id, uint64_t ent_id) { + ENetPeer *peer = (ENetPeer *)peer_id; + peer->data = (void*)ent_id; +} + +uint64_t network_server_get_entity(void *peer_id) { + if (game_get_kind() == GAMEKIND_SINGLE) { + return (uint64_t)peer_id; + } + ENetPeer *peer = (ENetPeer *)peer_id; + ZPL_ASSERT(peer->data); + return (uint64_t)peer->data; +} + +//~ NOTE(zaklaus): messaging + +static int32_t network_msg_send_raw(ENetPeer *peer_id, void *data, size_t datalen, uint32_t flags) { + if (peer_id == 0) peer_id = peer; ENetPacket *packet = enet_packet_create(data, datalen, flags); - return enet_peer_send(peer, 0, packet); + return enet_peer_send(peer_id, 0, packet); } -int32_t network_msg_send(void *data, size_t datalen) { - return network_msg_send_raw(0, data, datalen, ENET_PACKET_FLAG_RELIABLE); +int32_t network_msg_send(void *peer_id, void *data, size_t datalen) { + return network_msg_send_raw(peer_id, data, datalen, ENET_PACKET_FLAG_RELIABLE); } -int32_t network_msg_send_unreliable(void *data, size_t datalen) { - return network_msg_send_raw(0, data, datalen, 0); +int32_t network_msg_send_unreliable(void *peer_id, void *data, size_t datalen) { + return network_msg_send_raw(peer_id, data, datalen, 0); } diff --git a/code/game/src/network.h b/code/game/src/network.h index 27d842e..f913498 100644 --- a/code/game/src/network.h +++ b/code/game/src/network.h @@ -4,11 +4,20 @@ int32_t network_init(void); int32_t network_destroy(void); +// NOTE(zaklaus): client int32_t network_client_connect(const char *host, uint16_t port); int32_t network_client_disconnect(void); int32_t network_client_tick(void); void network_client_update(void *data); bool network_client_is_connected(); -int32_t network_msg_send(void *data, size_t datalen); -int32_t network_msg_send_unreliable(void *data, size_t datalen); +// NOTE(zaklaus): server +int32_t network_server_start(const char *host, uint16_t port); +int32_t network_server_stop(void); +int32_t network_server_tick(void); +void network_server_assign_entity(void *peer_id, uint64_t ent_id); +uint64_t network_server_get_entity(void *peer_id); + +// NOTE(zaklaus): messaging +int32_t network_msg_send(void *peer_id, void *data, size_t datalen); +int32_t network_msg_send_unreliable(void *peer_id, void *data, size_t datalen); diff --git a/code/game/src/packet_utils.h b/code/game/src/packet_utils.h index e522661..3e2971b 100644 --- a/code/game/src/packet_utils.h +++ b/code/game/src/packet_utils.h @@ -109,10 +109,6 @@ static inline int32_t pkt_world_write(pkt_messages id, size_t pkt_size, int8_t i #define PKT_END .type = CWP_NOT_AN_ITEM #endif -#ifndef PKT_GET_ENT -#define PKT_GET_ENT(h) (ecs_entity_t)(h->udata) -#endif - typedef struct pkt_desc { const char *name; cwpack_item_types type; diff --git a/code/game/src/packets/pkt_00_init.c b/code/game/src/packets/pkt_00_init.c index 8f18b52..7b47f5a 100644 --- a/code/game/src/packets/pkt_00_init.c +++ b/code/game/src/packets/pkt_00_init.c @@ -3,6 +3,7 @@ #include "packet.h" #include "world/world.h" #include "game.h" +#include "network.h" #include "entity_view.h" #include "camera.h" #include "player.h" @@ -33,7 +34,16 @@ int32_t pkt_00_init_handler(pkt_header *header) { uint64_t peer_id = (uint64_t)header->udata; uint64_t ent_id = player_spawn(NULL); - ecs_set(world_ecs(), ent_id, ClientInfo, {.peer = ent_id, .view_id = header->view_id }); + + Position *pos = ecs_get_mut(world_ecs(), ent_id, Position, NULL); + pos->x = world_dim()/2.0f + rand()%15*15.0f; + pos->y = world_dim()/2.0f + rand()%15*15.0f; + + + if (game_get_kind() == GAMEKIND_SINGLE) peer_id = ent_id; + else network_server_assign_entity(header->udata, ent_id); + + ecs_set(world_ecs(), ent_id, ClientInfo, {.peer = peer_id, .view_id = header->view_id }); pkt_01_welcome_send(world_seed(), peer_id, header->view_id, ent_id, world_chunk_size(), world_chunk_amount()); return 0; } diff --git a/code/game/src/packets/pkt_send_keystate.c b/code/game/src/packets/pkt_send_keystate.c index 019da1b..3b465f4 100644 --- a/code/game/src/packets/pkt_send_keystate.c +++ b/code/game/src/packets/pkt_send_keystate.c @@ -1,4 +1,5 @@ #include "packet_utils.h" +#include "network.h" #include "packets/pkt_send_keystate.h" #include "modules/components.h" #include "modules/systems.h" @@ -49,7 +50,10 @@ size_t pkt_send_keystate_encode(pkt_send_keystate *table) { int32_t pkt_send_keystate_handler(pkt_header *header) { pkt_send_keystate table; PKT_IF(pkt_msg_decode(header, pkt_send_keystate_desc, pkt_pack_desc_args(pkt_send_keystate_desc), PKT_STRUCT_PTR(&table))); - ecs_entity_t e = PKT_GET_ENT(header); + ecs_entity_t e = network_server_get_entity(header->udata); + + if (!world_entity_valid(e)) + return 1; Input *i = ecs_get_mut(world_ecs(), e, Input, NULL); if (i && !i->is_blocked) { diff --git a/code/game/src/world/world.c b/code/game/src/world/world.c index 98da76b..d14f3ab 100644 --- a/code/game/src/world/world.c +++ b/code/game/src/world/world.c @@ -261,7 +261,7 @@ static void world_tracker_update(uint8_t ticker, uint32_t freq, uint8_t radius) librg_entity_radius_set(world_tracker(), p[i].peer, radius); } // TODO(zaklaus): push radius once librg patch comes in - int32_t result = librg_world_write(world_tracker(), p[i].peer, buffer, &datalen, NULL); + int32_t result = librg_world_write(world_tracker(), it.entities[i], buffer, &datalen, NULL); if (result > 0) { zpl_printf("[info] buffer size was not enough, please increase it by at least: %d\n", result); diff --git a/code/game/src/world/worldgen/worldgen_test.c b/code/game/src/world/worldgen/worldgen_test.c index 85dbf68..e7e370d 100644 --- a/code/game/src/world/worldgen/worldgen_test.c +++ b/code/game/src/world/worldgen/worldgen_test.c @@ -7,6 +7,10 @@ #include "world/blocks.h" #include "world/perlin.h" +#include "modules/components.h" +#include "vehicle.h" +#include "items.h" + #define WORLD_BLOCK_OBSERVER(name) uint8_t name(uint8_t id, uint32_t block_idx) typedef WORLD_BLOCK_OBSERVER(world_block_observer_proc); @@ -173,5 +177,27 @@ int32_t worldgen_test(world_data *wld) { } #endif + // vehicles +#if 1 + for (int i=0; ix = RAND_RANGE(0, world->dim*WORLD_BLOCK_SIZE); + dest->y = RAND_RANGE(0, world->dim*WORLD_BLOCK_SIZE); + } +#endif + + // items +#if 1 + for (int i=0; ix = RAND_RANGE(0, world->dim*WORLD_BLOCK_SIZE); + dest->y = RAND_RANGE(0, world->dim*WORLD_BLOCK_SIZE); + } +#endif + return WORLD_ERROR_NONE; } diff --git a/run_client.bat b/run_client.bat new file mode 100644 index 0000000..8f7acca --- /dev/null +++ b/run_client.bat @@ -0,0 +1,3 @@ +@echo off + +build\Debug\eco2d.exe -v %* diff --git a/run_client_remote.bat b/run_client_remote.bat new file mode 100644 index 0000000..7a125cb --- /dev/null +++ b/run_client_remote.bat @@ -0,0 +1,5 @@ +@echo off + +call package.bat + +pkg\eco2d.exe -v %*