summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLizzy Fleckenstein <lizzy@vlhl.dev>2024-06-16 23:32:51 +0200
committerLizzy Fleckenstein <lizzy@vlhl.dev>2024-06-16 23:32:55 +0200
commit35c7cf611df4c76df28d30f858987cb263a1ed1f (patch)
treecd1267839df47db8766a32338cabb92a15411057
init
-rw-r--r--.gitignore38
-rw-r--r--content.h45
-rw-r--r--ideas18
-rw-r--r--map_central.pngbin0 -> 1602 bytes
-rw-r--r--map_valley.pngbin0 -> 1602 bytes
-rw-r--r--meson.build15
-rw-r--r--peer.c152
-rw-r--r--peer.h36
-rwxr-xr-xserverbin0 -> 22352 bytes
-rw-r--r--server.c225
-rw-r--r--str.c79
-rw-r--r--str.h53
12 files changed, 661 insertions, 0 deletions
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
--- /dev/null
+++ b/map_central.png
Binary files differ
diff --git a/map_valley.png b/map_valley.png
new file mode 100644
index 0000000..595ab82
--- /dev/null
+++ b/map_valley.png
Binary files 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+#include <errno.h>
+#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 <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#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
--- /dev/null
+++ b/server
Binary files 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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/select.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#include <png.h>
+#include <endian.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#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 <stdlib.h>
+#include <string.h>
+#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 <stddef.h>
+#include <stdbool.h>
+
+// 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