commit 17cb258
uint
·
2026-08-02 20:23:25 +0000 UTC
parent bf83f21
add multiplayer very dirty bc SOMEONE asked me to js push smh
6 files changed,
+528,
-50
+11,
-0
1@@ -28,8 +28,19 @@ void mc_client_destroy(MCClient* client);
2
3 int mc_client_read(MCClient* client, MCPacket* packet, char* error, size_t error_size);
4 int mc_client_write(MCClient* client, i32 packet_id, const void* data, size_t size);
5+
6+/* poll for packets.
7+ returns 1: packet recieved,
8+ 0: no packet awaiting,
9+ -1: connection error */
10+int mc_client_poll(MCClient* client, MCPacket* packet, char* error, size_t error_size);
11+
12 void mc_packet_free(MCPacket* packet);
13
14+/* send server player look data */
15+int mc_client_send_position_look(MCClient* client, double x, double feet_y,
16+ double z, float yaw, float pitch, i8 grounded);
17+
18 const char* mc_client_username(const MCClient* client);
19 const char* mc_client_uuid(const MCClient* client);
20
+382,
-28
1@@ -3,47 +3,352 @@
2
3 #include "mgmath.h"
4 #include "util.h"
5+#include "mcproto/mcproto.h"
6+#include "mcproto/packet.h"
7 #include "client/camera.h"
8+#include "client/player.h"
9 #include "renderer/renderer.h"
10 #include "world/block.h"
11 #include "world/chunk.h"
12
13+/* big TODO. most of this stuff belongs elsewhere !!! */
14+
15+typedef struct {
16+ i32 x;
17+ i32 z;
18+
19+ Chunk sections[16]; /* a 16x256x16 chunk */
20+ u16 section_mask;
21+
22+ i8 used;
23+} ServerChunk;
24+
25+typedef struct {
26+ i32 x;
27+ i32 z;
28+ u16 mask;
29+} BulkChunkMeta;
30+
31 static MGTexture block_textures[BLOCK_LAST];
32-static Chunk chunks[WORLD_WIDTH][WORLD_DEPTH];
33 static MGCamera cam;
34+static Chunk chunks[WORLD_WIDTH][WORLD_DEPTH];
35+
36+#define NCHUNKS 128
37+static ServerChunk server_chunks[NCHUNKS];
38+static i8 server_mode;
39+
40+ServerChunk* server_chunk_find(i32 x, i32 z)
41+{
42+ i32 i;
43+
44+ for (i = 0; i < NCHUNKS; i++) {
45+ if (!server_chunks[i].used)
46+ continue;
47+
48+ if (server_chunks[i].x == x && server_chunks[i].z == z)
49+ return &server_chunks[i];
50+ }
51+
52+ return NULL;
53+}
54+
55+ServerChunk* server_chunk_get(i32 x, i32 z)
56+{
57+ ServerChunk* col;
58+ i32 i;
59+
60+ col = server_chunk_find(x, z);
61+ if (col)
62+ return col;
63+
64+ for (i = 0; i < NCHUNKS; i++) {
65+ if (server_chunks[i].used)
66+ continue;
67+
68+ col = &server_chunks[i];
69+
70+ memset(col, 0, sizeof(*col));
71+
72+ col->used = 1;
73+ col->x = x;
74+ col->z = z;
75+
76+ return col;
77+ }
78+
79+ mg_log(LOG_ERR, "Server chunk storage is full");
80+ return NULL;
81+}
82+
83+void server_chunk_clear(ServerChunk* column)
84+{
85+ i32 section;
86+
87+ if (!column)
88+ return;
89+
90+ for (section = 0; section < 16; section++) {
91+ chunk_mesh_destroy(&column->sections[section]);
92+ memset(&column->sections[section], 0, sizeof(column->sections[section]));
93+ }
94+
95+ column->section_mask = 0;
96+}
97
98-void test_scene_create(void)
99+void server_chunk_remove(ServerChunk* column)
100 {
101+ if (!column)
102+ return;
103+
104+ server_chunk_clear(column);
105+ memset(column, 0, sizeof(*column));
106+}
107+
108+i32 section_count(u16 mask)
109+{
110+ i32 section;
111+ i32 count;
112+
113+ count = 0;
114+
115+ for (section = 0; section < 16; section++) {
116+ if (mask & (1U << section))
117+ count++;
118+ }
119+
120+ return count;
121+}
122+
123+i8 load_server_section(Chunk* section, const u8* data, size_t size)
124+{
125+ i32 index;
126 i32 x;
127+ i32 y;
128 i32 z;
129+ u16 state;
130+
131+ if (size < 8192)
132+ return 0;
133+
134+ chunk_mesh_destroy(section);
135+ memset(section, 0, sizeof(*section));
136+
137+ for (index = 0; index < 4096; index++) {
138+ state = (u16)data[index * 2] |
139+ ((u16)data[index * 2 + 1] << 8);
140+
141+ x = index & 15;
142+ z = (index >> 4) & 15;
143+ y = (index >> 8) & 15;
144+
145+ if (state != 0)
146+ chunk_set(section, x, y, z, BLOCK_BRICK);
147+ }
148+
149+ if (!chunk_mesh_create(section)) {
150+ mg_log(LOG_ERR, "Failed to create server section mesh");
151+ return 0;
152+ }
153+
154+ return 1;
155+}
156+
157+void test_scene_chunk(const MCPacket* packet)
158+{
159+ PacketReader reader = packet_reader_init(packet->data, packet->size);
160+ ServerChunk* column;
161+ const u8* data;
162+ size_t required_size;
163+
164+ i32 chunk_x;
165+ i32 chunk_z;
166+ i32 data_size;
167+ i32 included;
168+ i32 section;
169+ i32 data_section;
170+ u8 ground_up;
171+ u16 mask;
172+
173+ if (!packet_read_i32(&reader, &chunk_x) ||
174+ !packet_read_i32(&reader, &chunk_z) ||
175+ !packet_read_u8(&reader, &ground_up) ||
176+ !packet_read_u16(&reader, &mask) ||
177+ !packet_read_varint(&reader, &data_size)) {
178+ mg_log(LOG_ERR, "Malformed Chunk Data packet");
179+ return;
180+ }
181+
182+ if (data_size < 0)
183+ return;
184+
185+ if ((size_t)data_size > reader.size - reader.cursor)
186+ return;
187+
188+ if (ground_up && mask == 0) {
189+ column = server_chunk_find(chunk_x, chunk_z);
190+ if (column)
191+ server_chunk_remove(column);
192+ return;
193+ }
194+
195+ included = section_count(mask);
196+ required_size = included * 8192;
197
198- block_textures[BLOCK_BRICK] =
199- renderer_texture_load("assets/resourcepacks/default/blocks/brick.png");
200+ if (required_size > (size_t)data_size) {
201+ mg_log(LOG_ERR, "Chunk packet block data is imcomplete");
202+ return;
203+ }
204+
205+ column = server_chunk_get(chunk_x, chunk_z);
206+
207+ if (!column)
208+ return;
209+
210+ if (ground_up)
211+ server_chunk_clear(column);
212+
213+ data = reader.data + reader.cursor;
214+ data_section = 0;
215+
216+ for (section = 0; section < 16; section++) {
217+ if (!(mask & (1U << section)))
218+ continue;
219+
220+ if (!load_server_section(&column->sections[section], data + data_section * 8192, 8192))
221+ return;
222+
223+ column->section_mask |= (u16)(1U << section);
224+ data_section++;
225+ }
226+}
227+
228+void test_scene_bulk_chunks(const MCPacket* packet)
229+{
230+ PacketReader reader = packet_reader_init(packet->data, packet->size);
231+ BulkChunkMeta* metadata;
232+ ServerChunk* column;
233+ const u8* data;
234+ size_t offset;
235+ size_t block_size;
236+ size_t column_size;
237+ i32 count;
238+ i32 index;
239+ i32 sections;
240+ i32 section;
241+ i32 data_section;
242+ u8 skylight;
243+
244+ reader.data = packet->data;
245+ reader.size = packet->size;
246+ reader.cursor = 0;
247+
248+ if (!packet_read_u8(&reader, &skylight) || !packet_read_varint(&reader, &count)) {
249+ mg_log(LOG_ERR, "Malformed Map Chunk Bulk packet");
250+ return;
251+ }
252+
253+ if (count <= 0 || count > 4096)
254+ return;
255+
256+ metadata = malloc(count * sizeof(*metadata));
257+
258+ if (!metadata)
259+ return;
260+
261+ for (index = 0; index < count; index++) {
262+ if (!packet_read_i32(&reader, &metadata[index].x) ||
263+ !packet_read_i32( &reader, &metadata[index].z) ||
264+ !packet_read_u16( &reader, &metadata[index].mask)) {
265+ free(metadata);
266+ return;
267+ }
268+ }
269+
270+ data = reader.data + reader.cursor;
271+ offset = 0;
272+
273+ for (index = 0; index < count; index++) {
274+ sections = section_count(metadata[index].mask);
275+ block_size = sections * 8192;
276+ column_size = block_size + sections * 2048 + (skylight ? sections * 2048 : 0) + 256;
277+
278+ if (offset > reader.size - reader.cursor)
279+ break;
280+
281+ if (column_size > reader.size - reader.cursor - offset) {
282+ mg_log(LOG_ERR, "Bulk chunk data is incomplete");
283+ break;
284+ }
285+
286+ column = server_chunk_get(metadata[index].x, metadata[index].z);
287+ if (!column) {
288+ offset += column_size;
289+ continue;
290+ }
291+
292+ server_chunk_clear(column);
293+ data_section = 0;
294+
295+ for (section = 0; section < 16; section++) {
296+ if (!(metadata[index].mask & (1U << section)))
297+ continue;
298+
299+ if (!load_server_section(&column->sections[section], data + offset + data_section * 8192, 8192))
300+ break;
301+
302+ column->section_mask |= (u16)(1U << section);
303+ data_section++;
304+ }
305+
306+ offset += column_size;
307+ }
308+
309+ free(metadata);
310+}
311+
312+void test_scene_create(Player* player, i8 server)
313+{
314+ i32 x;
315+ i32 z;
316+
317+ block_textures[BLOCK_BRICK] = renderer_texture_load("assets/resourcepacks/default/blocks/bedrock.png");
318 if (block_textures[BLOCK_BRICK] == MGTEXTURE_INVALID)
319 mg_die(MG_ERR_TEXTURE_LOAD, "Failed to load brick texture");
320
321- block_textures[BLOCK_DIRT] =
322- renderer_texture_load("assets/resourcepacks/default/blocks/dirt.png");
323+ block_textures[BLOCK_DIRT] = renderer_texture_load("assets/resourcepacks/default/blocks/dirt.png");
324 if (block_textures[BLOCK_DIRT] == MGTEXTURE_INVALID)
325 mg_die(MG_ERR_TEXTURE_LOAD, "Failed to load dirt texture");
326
327- block_textures[BLOCK_STONE] =
328- renderer_texture_load("assets/resourcepacks/default/blocks/stone.png");
329+ block_textures[BLOCK_STONE] = renderer_texture_load("assets/resourcepacks/default/blocks/stone.png");
330 if (block_textures[BLOCK_STONE] == MGTEXTURE_INVALID)
331 mg_die(MG_ERR_TEXTURE_LOAD, "Failed to load stone texture");
332
333- for (z = 0; z < WORLD_DEPTH; z++) {
334- for (x = 0; x < WORLD_WIDTH; x++) {
335- chunk_create(&chunks[x][z]);
336-
337- if (!chunk_mesh_create(&chunks[x][z]))
338- mg_die(MG_ERR_MESH_CREATE, "Failed to create chunk mesh");
339- }
340- }
341-
342 camera_init(&cam, MG_V3(8.0f, 38.0f, 24.0f));
343 cam.yaw = MG_AngleDeg(-180.0f);
344 cam.pitch = MG_AngleDeg(-25.0f);
345+
346+ server_mode = server;
347+ memset(server_chunks, 0, sizeof(server_chunks));
348+ player->x = 0.0;
349+ player->y = 0.0;
350+ player->z = 0.0;
351+ player->yaw = 0.0f;
352+ player->pitch = 0.0f;
353+ player->positioned = 0;
354+
355+ if (server_mode) {
356+ cam.far = 512.0f;
357+ }
358+ else { /* test world */
359+ for (z = 0; z < WORLD_DEPTH; z++) {
360+ for (x = 0; x < WORLD_WIDTH; x++) {
361+ chunk_create(&chunks[x][z]);
362+
363+ if (!chunk_mesh_create(&chunks[x][z]))
364+ mg_die(MG_ERR_MESH_CREATE, "Failed to create chunk mesh");
365+ }
366+ }
367+ }
368 }
369
370 void test_scene_draw(MGContext* ctx)
371@@ -59,6 +364,11 @@ void test_scene_draw(MGContext* ctx)
372 float aspect;
373 BlockType block;
374
375+ ServerChunk* column;
376+ Chunk* section_chunk;
377+ i32 section;
378+ i32 i;
379+
380 platform_get_drawable_size(ctx, &width, &height);
381
382 if (width <= 0 || height <= 0)
383@@ -69,16 +379,46 @@ void test_scene_draw(MGContext* ctx)
384 proj = camera_get_proj(&cam, aspect);
385 view = camera_get_view(&cam);
386
387- for (z = 0; z < WORLD_DEPTH; z++) {
388- for (x = 0; x < WORLD_WIDTH; x++) {
389- model = MG_Translate(MG_V3(x * CHUNK_WIDTH, 0.0f, z * CHUNK_DEPTH));
390- mvp = MG_MulM4(proj, MG_MulM4(view, model));
391+ if (server_mode) {
392+ for (i = 0; i < NCHUNKS; i++) {
393+ column = &server_chunks[i];
394
395- for (block = BLOCK_BRICK; block < BLOCK_LAST; block++) {
396- if (chunks[x][z].meshes[block] == MGMESH_INVALID)
397+ if (!column->used)
398+ continue;
399+
400+ for (section = 0; section < 16; section++) {
401+ if (!(column->section_mask & (1U << section)))
402+ continue;
403+
404+ section_chunk = &column->sections[section];
405+ if (section_chunk->meshes[BLOCK_BRICK] == MGMESH_INVALID)
406 continue;
407- renderer_mesh_draw(chunks[x][z].meshes[block],
408- block_textures[block], &mvp);
409+
410+ model = MG_Translate(MG_V3(
411+ column->x * CHUNK_WIDTH,
412+ section * CHUNK_HEIGHT,
413+ column->z * CHUNK_DEPTH
414+ ));
415+ mvp = MG_MulM4(proj, MG_MulM4(view, model));
416+ renderer_mesh_draw(
417+ section_chunk ->meshes[BLOCK_BRICK],
418+ block_textures[BLOCK_BRICK], &mvp
419+ );
420+ }
421+ }
422+ }
423+ else { /* test world */
424+ for (z = 0; z < WORLD_DEPTH; z++) {
425+ for (x = 0; x < WORLD_WIDTH; x++) {
426+ model = MG_Translate(MG_V3(x * CHUNK_WIDTH, 0.0f, z * CHUNK_DEPTH));
427+ mvp = MG_MulM4(proj, MG_MulM4(view, model));
428+
429+ for (block = BLOCK_BRICK; block < BLOCK_LAST; block++) {
430+ if (chunks[x][z].meshes[block] == MGMESH_INVALID)
431+ continue;
432+ renderer_mesh_draw(chunks[x][z].meshes[block],
433+ block_textures[block], &mvp);
434+ }
435 }
436 }
437 }
438@@ -89,9 +429,23 @@ void test_scene_destroy(void)
439 i32 x;
440 i32 z;
441
442- for (z = 0; z < WORLD_DEPTH; z++) {
443- for (x = 0; x < WORLD_WIDTH; x++)
444- chunk_mesh_destroy(&chunks[x][z]);
445+ i32 section;
446+ i32 i;
447+
448+ if (server_mode) {
449+ for (i = 0; i < NCHUNKS; i++) {
450+ if (!server_chunks[i].used)
451+ continue;
452+
453+ for (section = 0; section < 16; section++)
454+ chunk_mesh_destroy(&server_chunks[i] .sections[section]);
455+ }
456+ }
457+ else {
458+ for (z = 0; z < WORLD_DEPTH; z++) {
459+ for (x = 0; x < WORLD_WIDTH; x++)
460+ chunk_mesh_destroy(&chunks[x][z]);
461+ }
462 }
463
464 renderer_texture_destroy(block_textures[BLOCK_BRICK]);
+19,
-15
1@@ -4,29 +4,33 @@
2 #define RGBA(r, g, b, a) (((u32)(r)) | ((u32)(g) << 8) | ((u32)(b) << 16) | ((u32)(a) << 24))
3
4 typedef enum { /* TODO: rename to MG_EC_... */
5- MG_ERR_EXIT_SUCCESS,
6- MG_ERR_EXIT_FAILURE,
7+ MG_ERR_EXIT_SUCCESS,
8+ MG_ERR_EXIT_FAILURE,
9
10- MG_ERR_SDL_INIT,
11- MG_ERR_SDL_CREATE_WIN,
12+ MG_ERR_INVALID_ARGS,
13
14- MG_ERR_GL_CTX_CREATE,
15- MG_ERR_GL_CTX_ACTIVATE,
16+ MG_ERR_MCCLIENT_LOGIN,
17
18- MG_ERR_SHADER_CREATE,
19+ MG_ERR_SDL_INIT,
20+ MG_ERR_SDL_CREATE_WIN,
21
22- MG_ERR_PIPELINE_CREATE,
23+ MG_ERR_GL_CTX_CREATE,
24+ MG_ERR_GL_CTX_ACTIVATE,
25
26- MG_ERR_MESH_CREATE,
27+ MG_ERR_SHADER_CREATE,
28
29- MG_ERR_TEXTURE_LOAD,
30- MG_ERR_TEXTURE_CREATE,
31- MG_ERR_SAMPLER_CREATE,
32- MG_ERR_VIEW_CREATE,
33+ MG_ERR_PIPELINE_CREATE,
34
35- MG_ERR_REN_CREATE,
36+ MG_ERR_MESH_CREATE,
37
38- MG_ERR_LAST
39+ MG_ERR_TEXTURE_LOAD,
40+ MG_ERR_TEXTURE_CREATE,
41+ MG_ERR_SAMPLER_CREATE,
42+ MG_ERR_VIEW_CREATE,
43+
44+ MG_ERR_REN_CREATE,
45+
46+ MG_ERR_LAST
47 } MGErrorCodes;
48
49 typedef enum {
+1,
-1
1@@ -24,5 +24,5 @@ void config_default(void)
2 mg_config.debug_wireframe = 0;
3
4 /* temp */
5- mg_config.temp_movement_speed = 4.0f;
6+ mg_config.temp_movement_speed = 16.0f;
7 }
+51,
-6
1@@ -3,6 +3,7 @@
2
3 #include "config.h"
4 #include "util.h"
5+#include "client/player.h"
6 #include "mcproto/mcproto.h"
7 #include "platform/platform.h"
8 #include "renderer/renderer.h"
9@@ -97,12 +98,24 @@ static int protocol_poc(const char* address, const char* username)
10
11 int main(int argc, char** argv)
12 {
13- if (argc != 1 && argc != 3) {
14- fprintf(stderr, "usage: %s [server[:port] username]\n", argv[0]);
15- return MG_ERR_EXIT_FAILURE;
16+ MCClient* client = NULL;
17+ MCAccount account;
18+ MCPacket packet;
19+ char error[1024];
20+ int poll_result;
21+
22+ Player player;
23+
24+ if (argc != 1 && argc != 3)
25+ mg_die(MG_ERR_INVALID_ARGS, "usage: %s [server[:port] username]\n", argv[0]);
26+ if (argc == 3) {
27+ account.username = argv[2];
28+ account.profile_uuid = getenv("MANGOLIA_PROFILE_UUID");
29+ account.access_token = getenv("MANGOLIA_ACCESS_TOKEN");
30+
31+ if (!mc_client_login(&client, argv[1], &account, error, sizeof(error)))
32+ mg_die(MG_ERR_MCCLIENT_LOGIN, "%s\n", error);
33 }
34- if (argc == 3)
35- return protocol_poc(argv[1], argv[2]);
36
37 config_default();
38
39@@ -112,11 +125,41 @@ int main(int argc, char** argv)
40 platform_init(&ctx);
41 platform_window_create(&ctx);
42 renderer_create(&ctx);
43- test_scene_create();
44+ test_scene_create(&player, client != NULL);
45 SDL_SetRelativeMouseMode(SDL_TRUE);
46
47 while (platform_poll(&ctx)) {
48+ if (client) {
49+ for (;;) {
50+ poll_result = mc_client_poll(client, &packet, error, sizeof(error));
51+ if (poll_result <= 0)
52+ break;
53+
54+ printf("packet 0x%02x size %lu\n", packet.id, packet.size);
55+ switch (packet.id) {
56+ case 0x00:
57+ if (!mc_client_write(client, 0x00, packet.data, packet.size))
58+ mg_log(LOG_ERR, "Failed to echo keepalive");
59+ break;
60+ case 0x08: player_server_handle_position(&player, &cam, &packet); break;
61+ case 0x21: test_scene_chunk(&packet); break;
62+ case 0x26: test_scene_bulk_chunks(&packet); break;
63+ }
64+
65+ mc_packet_free(&packet);
66+ }
67+
68+ if (poll_result < 0) {
69+ mg_log(LOG_ERR, "%s", error);
70+ break;
71+ }
72+ }
73+
74 input();
75+
76+ if (client)
77+ player_server_send_position(&player, &cam, client);
78+
79 renderer_begin();
80
81 /* ... */
82@@ -127,6 +170,8 @@ int main(int argc, char** argv)
83 SDL_Delay(1); /* TODO: replace with target fps */
84 }
85
86+ if (client)
87+ mc_client_destroy(client);
88 test_scene_destroy();
89 renderer_destroy();
90 platform_shutdown(&ctx);
+64,
-0
1@@ -2,6 +2,7 @@
2
3 #include <errno.h>
4 #include <netdb.h>
5+#include <poll.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9@@ -525,6 +526,69 @@ void mc_client_destroy(MCClient* client)
10 free(client);
11 }
12
13+int mc_client_poll(MCClient* client, MCPacket* packet, char* error, size_t error_size)
14+{
15+ struct pollfd fd;
16+ int result;
17+
18+ if (!client || client->socket_fd < 0) {
19+ mc_set_error(error, error_size, "invalid Minecraft client");
20+ return -1;
21+ }
22+
23+ fd.fd = client->socket_fd;
24+ fd.events = POLLIN;
25+ fd.revents = 0;
26+
27+ do {
28+ result = poll(&fd, 1, 0);
29+ } while (result < 0 && errno == EINTR);
30+
31+ if (result < 0) {
32+ mc_set_error(error, error_size, "socket poll failed: %s", strerror(errno));
33+ return -1;
34+ }
35+
36+ if (result == 0)
37+ return 0;
38+
39+ if (fd.revents & POLLIN) {
40+ if (!mc_client_read(client, packet, error, error_size))
41+ return -1;
42+
43+ return 1;
44+ }
45+
46+ if (fd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
47+ mc_set_error(error, error_size, "Minecraft connection closed");
48+ return -1;
49+ }
50+
51+ return 0;
52+}
53+
54+int mc_client_send_position_look(MCClient* client, double x, double feet_y,
55+ double z, float yaw, float pitch, i8 grounded)
56+{
57+ MCBuffer payload;
58+ int ok;
59+
60+ bufinit(&payload);
61+
62+ ok =
63+ bufwrite_f64(&payload, x) &&
64+ bufwrite_f64(&payload, feet_y) &&
65+ bufwrite_f64(&payload, z) &&
66+ bufwrite_f32(&payload, yaw) &&
67+ bufwrite_f32(&payload, pitch) &&
68+ bufwrite_u8(&payload, grounded ? 1 : 0) &&
69+ /* player (look) position */
70+ mc_client_write(client, 0x06, payload.data, payload.size);
71+
72+ buffree(&payload);
73+ return ok;
74+}
75+
76 const char* mc_client_username(const MCClient* client) { return client ? client->username : ""; }
77
78 const char* mc_client_uuid(const MCClient* client) { return client ? client->uuid : ""; }