From 35c7cf611df4c76df28d30f858987cb263a1ed1f Mon Sep 17 00:00:00 2001 From: Lizzy Fleckenstein Date: Sun, 16 Jun 2024 23:32:51 +0200 Subject: init --- .gitignore | 38 ++++++++++ content.h | 45 ++++++++++++ ideas | 18 +++++ map_central.png | Bin 0 -> 1602 bytes map_valley.png | Bin 0 -> 1602 bytes meson.build | 15 ++++ peer.c | 152 ++++++++++++++++++++++++++++++++++++++ peer.h | 36 +++++++++ server | Bin 0 -> 22352 bytes server.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ str.c | 79 ++++++++++++++++++++ str.h | 53 +++++++++++++ 12 files changed, 661 insertions(+) create mode 100644 .gitignore create mode 100644 content.h create mode 100644 ideas create mode 100644 map_central.png create mode 100644 map_valley.png create mode 100644 meson.build create mode 100644 peer.c create mode 100644 peer.h create mode 100755 server create mode 100644 server.c create mode 100644 str.c create mode 100644 str.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7602735 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +.mypy_cache/ +.pytest_cache/ +/.project +/.pydevproject +/.settings +/.cproject +/.idea +/.vscode + +__pycache__ +/.coverage/ +/.coveragerc +/install dir +/work area + +/meson-test-run.txt +/meson-test-run.xml +/meson-cross-test-run.txt +/meson-cross-test-run.xml + +.DS_Store +*~ +*.swp +packagecache +/MANIFEST +/build* +/dist +/meson.egg-info + +/docs/built_docs +/docs/hotdoc-private* + +*.pyc +/*venv* + +/subprojects/* +!/subprojects/packagefiles/* +!/subprojects/*.wrap diff --git a/content.h b/content.h new file mode 100644 index 0000000..eaf448d --- /dev/null +++ b/content.h @@ -0,0 +1,45 @@ +#ifndef CONTENT_H +#define CONTENT_H + +typedef enum { + N_VALLEY_FLOWER, + N_MOUNTAIN_FLOWER, + N_BIG_TREE, + N_NEEDLE_TREE, + N_ROCK, + N_WATER, + N_PLANK, + N_PATH, +} node_type; + +typedef struct { + uint8_t r, g, b; +} color; + +typedef struct { + bool present; + node_type type; + int8_t z; // for rocks, indicates rock level + color col; +} node; + +typedef enum { + MOVE_UP, + MOVE_DOWN, + MOVE_LEFT, + MOVE_RIGHT, +} move_dir; + +typedef enum { + CPKT_HI, // len motd + CPKT_PLAYERS, // len + CPKT_MOVE, // player_id remove x y z + CPKT_NODES, // z x y w h [node] +} client_pkt; + +typedef enum { + SPKT_HI, // len name len password + SPKT_MOVE, // move_dir +} server_pkt; + +#endif diff --git a/ideas b/ideas new file mode 100644 index 0000000..0f2075b --- /dev/null +++ b/ideas @@ -0,0 +1,18 @@ +wood, stone, flowers +red chisels, white chisels, green chisels, blue chisels +acorns - stamina / mana +axe, pickaxe +boat to sail +islands: coconuts, shells, palm trees, sand +treasure chests (locked, unlocked) +keys +bundle + +sound, JSON +perlin noise +daylight cycle, lantern, wax +bees, honey, wax +caves!!, darkness + +minimap (contains explored areas) +sight? diff --git a/map_central.png b/map_central.png new file mode 100644 index 0000000..1a65150 Binary files /dev/null and b/map_central.png differ diff --git a/map_valley.png b/map_valley.png new file mode 100644 index 0000000..595ab82 Binary files /dev/null and b/map_valley.png differ diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..9ce7950 --- /dev/null +++ b/meson.build @@ -0,0 +1,15 @@ +project('new_game', 'c', default_options: ['warning_level=2']) + +cc = meson.get_compiler('c') + +server = executable('server', + sources: [ + 'server.c', + 'str.c', + 'peer.c', + ], + dependencies: [dependency('libpng')], + install: true, +) + +run_target('run', command: server) diff --git a/peer.c b/peer.c new file mode 100644 index 0000000..8690279 --- /dev/null +++ b/peer.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include "peer.h" + +void peer_init(peer *p, int socket) +{ + p->socket = socket; + p->disco = false; + + p->in.header = true; + p->in.len = 0; + p->in.promised = 0; + p->in.buffer = malloc(PEER_INBUFFER_SIZE); + + p->out.cursor = 0; + p->out.avail = 0; + p->out.buffer = malloc(PEER_OUTBUFFER_SIZE); +} + +void peer_free(peer *p) +{ + close(p->socket); + free(p->in.buffer); + free(p->out.buffer); +} + +// in + +static void next_in(peer *p, bool header, size_t len) +{ + p->in.header = header; + p->in.len = 0; + p->in.buffer = malloc(p->in.promised = len); +} + +static bool peer_in_ready(peer *p) +{ + ssize_t got = read(p->socket, p->in.buffer + p->in.len, p->in.promised - p->in.len); + if (got < 0) { + switch (errno) { + case ECONNRESET: + p->disco = true; + return true; + case EINTR: + return peer_in_ready(p); // retry + default: + perror("read"); + return false; + } + } + + p->in.len += got; + if (p->in.len == p->in.promised && p->in.len != 0) { + if (!p->in.header) + return true; + + size_t len = *(pkt_header *) p->in.buffer; + if (len > PEER_INBUFFER_SIZE) + // TODO: figure out what to do if packet too large (disconnect?) + next_in(p, true, sizeof(pkt_header)); + else + next_in(p, false, len); + } + + return false; +} + +// out + +static void send_raw(peer *p, uint8_t *data, size_t len) +{ + memcpy(p->out.buffer + p->out.cursor + p->out.avail, data, len); + p->out.avail += len; +} + +static bool out_space(peer *p, size_t len) +{ + if (len + p->out.avail > PEER_OUTBUFFER_SIZE) + return false; + + if (p->out.cursor + p->out.avail + len > PEER_OUTBUFFER_SIZE) { + memmove(p->out.buffer, p->out.buffer + p->out.cursor, p->out.avail); + p->out.cursor = 0; + } + + return true; +} + +bool peer_send(peer *p, uint8_t *data, size_t len) +{ + if (len > PEER_INBUFFER_SIZE) + return false; + + pkt_header hdr = (pkt_header) len; + + if (!out_space(p, sizeof hdr + len)) + return false; + + send_raw(p, (uint8_t *) &hdr, sizeof hdr); + send_raw(p, data, len); + + return true; +} + +static bool peer_out_ready(peer *p) +{ + ssize_t written = write(p->socket, p->out.buffer + p->out.cursor, p->out.avail); + if (written < 0) { + switch (errno) { + case ECONNRESET: + p->disco = true; + return true; + case EINTR: + return peer_out_ready(p); + default: + perror("write"); + return false; + } + } + + p->out.avail -= written; + if (p->out.avail == 0) + p->out.cursor = 0; + + return false; +} + +// poll + +short peer_prepare(peer *p) +{ + if (p->in.len == p->in.promised) + next_in(p, true, sizeof(pkt_header)); + + return POLLIN | (p->out.avail ? POLLOUT : 0); +} + +bool peer_ready(peer *p, short revents) +{ + bool x = false; + + if (revents & POLLIN) + x = x || peer_in_ready(p); + if (revents & POLLOUT) + x = x || peer_out_ready(p); + + return x; +} diff --git a/peer.h b/peer.h new file mode 100644 index 0000000..f6b9b50 --- /dev/null +++ b/peer.h @@ -0,0 +1,36 @@ +#ifndef PEER_H +#define PEER_H + +#include +#include +#include + +#define PEER_INBUFFER_SIZE 0x100000 // 1MB +#define PEER_OUTBUFFER_SIZE 0x200000 // 2MB + +typedef uint32_t pkt_header; + +typedef struct { + int socket; + bool disco; + struct { + bool header; + size_t len; + size_t promised; + uint8_t *buffer; + } in; + // TODO: ring buffer + struct { + size_t avail; + size_t cursor; + uint8_t *buffer; + } out; +} peer; + +void peer_init(peer *p, int socket); +void peer_free(peer *p); +short peer_prepare(peer *p); +bool peer_ready(peer *p, short revents); +bool peer_send(peer *p, uint8_t *data, size_t len); + +#endif diff --git a/server b/server new file mode 100755 index 0000000..da83a0d Binary files /dev/null and b/server differ diff --git a/server.c b/server.c new file mode 100644 index 0000000..6369581 --- /dev/null +++ b/server.c @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "content.h" +#include "peer.h" +#include "str.h" + +typedef enum { + MAP_VALLEY, + MAP_ISLAND, + MAP_MOUNTAIN, + MAP_CAVE, + MAP_NUM, +} map_type; + +typedef struct { + const char *filename; + str name; + map_type type; + uint32_t width; + uint32_t height; + int32_t off_x; + int32_t off_y; + int8_t z; // caves: -1 + node *nodes; +} map; + +typedef struct { + str motd; + array(map) maps; +} game; + +typedef struct { + str name; + int32_t x, y; + peer net; +} player; + +node *map_local_node(map *m, int32_t x, int32_t y) +{ + if (x < 0 || y < 0 || x >= m->width || y >= m->height) + return NULL; + + node *n = &m->nodes[x*m->width+y]; + if (!n->present) + return NULL; + + return n; +} + +node *map_node(game *g, int32_t x, int32_t y, int8_t z) +{ + for (size_t i = 0; i < g->maps.len; i++) { + map *m = &g->maps.data[i]; + if (m->z != z) + continue; + node *n = map_local_node(m, x - m->off_x, y - m->off_y); + if (n != NULL) + return n; + } +} + +/* +void map_load_node(map *m, uint32_t x, uint32_t y, uint32_t color) +{ + +}*/ + +bool map_load(map *m) +{ +#define TRY(expr, ...) if (!(expr)) { \ + fprintf(stderr, __VA_ARGS__); \ + if (file != NULL) fclose(file); \ + png_destroy_read_struct(&png, &info, NULL); \ + return false; } + + FILE *file = NULL; + png_structp png = NULL; + png_infop info = NULL; + + TRY((file = fopen(m->filename, "r")) != NULL, "failed to open %s\n", m->filename); + + TRY(png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL), + "png_create_read_struct failed\n"); + + TRY(info = png_create_info_struct(png), "png_create_info_struct failed\n"); + + png_init_io(png, file); + png_read_info(png, info); + + png_uint_32 width = png_get_image_width(png, info); + png_uint_32 height = png_get_image_height(png, info); + png_byte color_type = png_get_color_type(png, info); + + TRY(width == m->width, "%s: width mismatch\n", m->filename); + TRY(height == m->height, "%s: height mismatch\n", m->filename); + TRY(color_type == PNG_COLOR_TYPE_RGB, "%s: color type is not RGB\n", m->filename); + + uint32_t colors[100] = {0}; + + png_uint_32 pitch = png_get_rowbytes(png, info); + png_byte row[pitch]; + for (png_uint_32 y = 0; y < height; y++) { + png_read_row(png, row, NULL); + + for (png_uint_32 x = 0; x < width; x++) { + png_bytep p = &row[x*3]; + + uint32_t color = ((uint32_t) p[2]) | (((uint32_t) p[1]) << 8) | (((uint32_t) p[0]) << 16); + for (size_t i = 0; i < 100; i++) { + if (colors[i] == color) { + break; + } else if (colors[i] == 0) { + colors[i] = color; + printf("#%06x\n", color); + break; + } + } + } + } + + fclose(file); + png_destroy_read_struct(&png, &info, NULL); + return true; +#undef TRY +} + +int net_listen(const char *host, const char *port) +{ + struct addrinfo *info = NULL, hints = {0}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + int err; + if ((err = getaddrinfo(host, port, &hints, &info))) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err)); + return -1; + } + +#define TRY(op, val) if ((val) < 0) { perror(op); freeaddrinfo(info); return -1; } + + int fd; + TRY("socket", fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol)) + + int flag = 1; + TRY("setsockopt", setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &flag, sizeof flag)); + + TRY("bind", bind(fd, info->ai_addr, info->ai_addrlen)) + TRY("listen", listen(fd, 3)) + +#undef TRY + + freeaddrinfo(info); + return fd; + +} + +int game_exit(game *g, int ret) +{ + free(g->motd.data); + + if (g->maps.data) + for (size_t i = 0; i < g->maps.len; i++) { + free(g->maps.data[i].name.data); + free(g->maps.data[i].nodes); + } + + free(g->maps.data); + + exit(ret); +} + +int main() +{ + game g = {0}; + + g.motd = str_clone(S("Welcome to test server")); + + g.maps.len = 1; + g.maps.data = malloc(g.maps.len * sizeof *g.maps.data); + g.maps.data[0] = (map) { + .filename = "map_valley.png", + .name = str_clone(S("Valley")), + .type = MAP_VALLEY, + .off_x = -50, + .off_y = -50, + .z = 0, + .width = 100, + .height = 100, + .nodes = NULL, + }; + + if (!map_load(&g.maps.data[0])) + game_exit(&g, EXIT_FAILURE); + + int accept_fd = net_listen("0.0.0.0", "4560"); + if (accept_fd < 0) + game_exit(&g, EXIT_FAILURE); + + for (;;) { + int clt_sock = accept(accept_fd, NULL, NULL); + if (clt_sock < 0) { + perror("accept"); + continue; + } + + if (fcntl(clt_sock, F_SETFL, fcntl(clt_sock, F_GETFL, 0) | O_NONBLOCK) < 0) { + perror("fcntl"); + continue; + } + } + +} diff --git a/str.c b/str.c new file mode 100644 index 0000000..84436af --- /dev/null +++ b/str.c @@ -0,0 +1,79 @@ +#include +#include +#include "str.h" + +int str_cmp(str s1, str s2) +{ + if (s1.len != s2.len) + return (int) s1.len - (int) s2.len; + + return memcmp(s1.data, s2.data, s1.len); +} + +static bool match_char(char c, str tokens) +{ + for (size_t t = 0; t < tokens.len; t++) + if (c == tokens.data[t]) + return true; + + return false; +} + +size_t str_find(str s, str tokens) +{ + for (size_t i = 0; i < s.len; i++) + if (match_char(s.data[i], tokens)) + return i; + + return s.len; +} + +str str_walk(str *s, str sep) +{ + if (s->len == 0) + return NILS; + + size_t x = str_find(*s, sep); + size_t o = x + (x < s->len); + + *s = str_advance(*s, o); + + if (x == 0) + return str_walk(s, sep); + else + return (str) { x, s->data - o }; +} + +str str_eat(str s, str tokens) +{ + while (s.len > 0 && match_char(s.data[0], tokens)) + s = str_advance(s, 1); + return s; +} + +str str_advance(str s, size_t x) +{ + s.len -= x; + s.data += x; + return s; +} + +bool str_start(str s, str start) +{ + if (s.len < start.len) + return false; + s.len = start.len; + return str_cmp(s, start) == 0; +} + +str str_intro(char *c) +{ + return (str) { strlen(c), c }; +} + +str str_clone(str s) +{ + str c = { s.len, malloc(s.len) }; + memcpy(c.data, s.data, s.len); + return c; +} diff --git a/str.h b/str.h new file mode 100644 index 0000000..2dbd697 --- /dev/null +++ b/str.h @@ -0,0 +1,53 @@ +#ifndef STR_H +#define STR_H + +#include +#include + +// string library taken from cuddlesOS: +// https://github.com/cuddlesOS/cuddles/blob/master/stage3/string.c + +// arr +#define len(X) (sizeof X / sizeof *X) +#define array(T) struct { size_t len; T *data; } + +typedef array(char) str; +#define S(X) ((str) { len(X)-1, X }) +#define NILS ((str) { 0, NULL }) + +// compares two strings by length and ASCII values. return value: +// < 0 if s1 < s2 +// = 0 if s1 = s2 +// > 0 if s1 > s2 +int str_cmp(str s1, str s2); + +// returns index of first of occurrence in s of any of the chars in tokens +// returns length of s if not found +size_t str_find(str s, str tokens); + +// this is a splitting function +// returns the next non-empty substring of *s that is delimited by the tokens in sep +// the returned string does not contain any of the separators +// returns an emtpy string when end is reached +// advances s to after the substring plus first delimiting token +str str_walk(str *s, str sep); + +// advances the string while its first token matches any of the chars in tokens +// this can be used to consume whitespace, for example +str str_eat(str s, str tokens); + +// advances the string s by x chars, increasing the data pointer and decreasing the length +// note: this is not bounds checked +str str_advance(str s, size_t x); + +// returns true if s starts with start +bool str_start(str s, str start); + +// construct a str from a \0 terminated string at runtime +// avoid this for literals: use the S macro instead without a runtime cost +str str_intro(char *c); + +// copy a string to the heap +str str_clone(str s); + +#endif -- cgit v1.2.3