1#include <stdio.h>
2#include <stdlib.h>
3
4#include "config.h"
5#include "mgmath.h"
6#include "util.h"
7#include "client/camera.h"
8#include "client/player.h"
9#include "client/world.h"
10#include "net/mc_chunk.h"
11#include "net/mc_proto.h"
12#include "platform/platform.h"
13#include "renderer/renderer.h"
14#include "world/block.h"
15#include "world/chunk.h"
16
17#define LOCAL_WORLD_WIDTH 50
18#define LOCAL_WORLD_DEPTH 50
19#define PACKETS_PER_FRAME 64
20
21typedef struct {
22 MGContext ctx;
23 MGCamera cam;
24 Player player;
25 ClientWorld world;
26 MCClient* client;
27} Magnolia;
28
29static Magnolia game;
30
31static void create_local_world(void);
32static void game_dies(void);
33static void game_init(int argc, char** argv);
34static void game_run(void);
35static void handle_chunk_packet(const MCPacket* packet);
36static void handle_input(void);
37static i8 handle_server_packets(void);
38static void render_world(void);
39
40/* create local fallback world */
41static void create_local_world(void)
42{
43 Chunk* chunk;
44 i32 chunk_index;
45 i32 x;
46 i32 z;
47
48 for (chunk_index = 0; chunk_index < CLIENT_WORLD_MAX_CHUNKS; chunk_index++) {
49 x = chunk_index % LOCAL_WORLD_WIDTH;
50 z = chunk_index / LOCAL_WORLD_WIDTH;
51 if (z >= LOCAL_WORLD_DEPTH)
52 break;
53
54 chunk = &game.world.chunks[chunk_index];
55 chunk->used = 1;
56 chunk->x = x;
57 chunk->z = z;
58 chunk->sections[0] = calloc(1, sizeof(*chunk->sections[0]));
59 if (!chunk->sections[0])
60 mg_die(MG_EC_MESH_CREATE, "Failed to allocate local chunk section");
61 chunk_section_create(chunk->sections[0]);
62 chunk->section_mask = 1;
63 }
64
65 if (!world_mesh_create(&game.world))
66 mg_die(MG_EC_MESH_CREATE, "Failed to create world mesh");
67}
68
69/* destroy game resources */
70static void game_dies(void)
71{
72 if (game.client)
73 mc_client_destroy(game.client);
74
75 world_destroy(&game.world);
76 block_textures_destroy();
77 renderer_destroy();
78 platform_shutdown(&game.ctx);
79}
80
81/* initialise game */
82static void game_init(int argc, char** argv)
83{
84 MCAccount account;
85 char error[1024];
86
87 if (argc != 1 && argc != 3)
88 mg_die(MG_EC_INVALID_ARGS, "usage: %s [server[:port] username]\n", argv[0]);
89
90 if (argc == 3) {
91 account.username = argv[2];
92 account.profile_uuid = getenv("MANGOLIA_PROFILE_UUID");
93 account.access_token = getenv("MANGOLIA_ACCESS_TOKEN");
94
95 if (!mc_client_login(&game.client, argv[1], &account, error, sizeof(error)))
96 mg_die(MG_EC_MCCLIENT_LOGIN, "%s\n", error);
97 }
98
99 config_default();
100
101 game.ctx.title = "Magnolia";
102 game.ctx.width = mg_config.display_width;
103 game.ctx.height = mg_config.display_height;
104
105 platform_init(&game.ctx);
106 platform_window_create(&game.ctx);
107 renderer_create(&game.ctx);
108 platform_set_mouse_relative(1);
109
110 if (!block_textures_create("assets/resourcepacks/default"))
111 mg_die(MG_EC_TEXTURE_LOAD, "Failed to load block textures");
112
113 camera_init(&game.cam, MG_V3(8.0f, 38.0f, 24.0f));
114 game.cam.yaw = MG_AngleDeg(-180.0f);
115 game.cam.pitch = MG_AngleDeg(-25.0f);
116
117 world_init(&game.world);
118
119 game.player.x = 0.0;
120 game.player.y = 0.0;
121 game.player.z = 0.0;
122 game.player.yaw = 0.0f;
123 game.player.pitch = 0.0f;
124 game.player.positioned = 0;
125
126 if (!game.client)
127 create_local_world();
128}
129
130/* run main loop */
131static void game_run(void)
132{
133 while (platform_poll(&game.ctx)) {
134 if (!handle_server_packets())
135 break;
136
137 handle_input();
138
139 if (game.client)
140 player_server_send_position(&game.player, &game.cam, game.client);
141
142 renderer_begin();
143 render_world();
144 renderer_end();
145
146 platform_present(&game.ctx);
147 platform_wait(1);
148 }
149}
150
151/* handle decoded chunk packet */
152static void handle_chunk_packet(const MCPacket* packet)
153{
154 MCChunkPacket chunk;
155 size_t offset;
156 size_t size;
157 i32 update;
158
159 if (!mc_chunk_decode(&chunk, packet))
160 return;
161
162 if (chunk.unload) {
163 world_remove_chunk(&game.world, chunk.unload_x, chunk.unload_z);
164 mc_chunk_destroy(&chunk);
165 return;
166 }
167
168 offset = 0;
169
170 for (update = 0; update < chunk.update_count; update++) {
171 size = mc_chunk_update_size(chunk.updates[update].mask, chunk.skylight, chunk.bulk);
172 if (offset > chunk.data_size || size > chunk.data_size - offset) {
173 mg_log(LOG_ERR, "Chunk data is incomplete");
174 break;
175 }
176
177 if (!world_update_chunk(&game.world, &chunk.updates[update], chunk.data + offset))
178 break;
179
180 offset += size;
181 }
182
183 mc_chunk_destroy(&chunk);
184}
185
186/* handle local input */
187static void handle_input(void)
188{
189 static u32 last;
190
191 MGVec3 move;
192 u64 now;
193 float sensitivity;
194 float dt;
195 i32 mx;
196 i32 my;
197
198 move = MG_V3(0.0f, 0.0f, 0.0f);
199 now = platform_get_ticks();
200 sensitivity = mg_config.input_sensitivity;
201
202 if (!last) {
203 last = now;
204 return;
205 }
206
207 dt = (now - last) / 1000.0f;
208 last = now;
209
210 /* TODO normalise */
211 if (platform_key_down(&game.ctx, MGK_W))
212 move.z += 1.0f;
213 if (platform_key_down(&game.ctx, MGK_A))
214 move.x -= 1.0f;
215 if (platform_key_down(&game.ctx, MGK_S))
216 move.z -= 1.0f;
217 if (platform_key_down(&game.ctx, MGK_D))
218 move.x += 1.0f;
219 if (platform_key_down(&game.ctx, MGK_SPACE))
220 move.y += 1.0f;
221 if (platform_key_down(&game.ctx, MGK_LCTRL))
222 move.y -= 1.0f;
223
224 if (platform_key_down(&game.ctx, MGK_L)) {
225 mg_config.debug_wireframe = !mg_config.debug_wireframe;
226 renderer_debug_wireframe(mg_config.debug_wireframe);
227 }
228
229 move = MG_MulV3F(move, mg_config.temp_movement_speed * dt);
230 camera_move(&game.cam, move);
231 platform_get_mouse_state(&mx, &my);
232 camera_rotate(&game.cam, mx * sensitivity, -my * sensitivity);
233}
234
235/* handle pending server packets
236 returns 1 to continue, 0 to stop */
237static i8 handle_server_packets(void)
238{
239 MCPacket packet;
240 char error[1024];
241 int poll_result;
242 int packets;
243
244 if (!game.client)
245 return 1;
246
247 for (packets = 0; packets < PACKETS_PER_FRAME; packets++) {
248 poll_result = mc_client_poll(game.client, &packet, error, sizeof(error));
249 if (poll_result <= 0)
250 break;
251
252 switch (packet.id) {
253 case 0x00:
254 if (!mc_client_write(game.client, 0x00, packet.data, packet.size))
255 mg_log(LOG_ERR, "Failed to echo keepalive");
256 break;
257
258 case 0x08:
259 player_server_handle_position(&game.player, &game.cam, &packet);
260 break;
261
262 case 0x21:
263 case 0x26:
264 /* both single and bulk changes
265 handled in handle_chunk_packet */
266 handle_chunk_packet(&packet);
267 break;
268 }
269
270 mc_packet_free(&packet);
271 }
272
273 if (poll_result < 0) {
274 mg_log(LOG_ERR, "%s", error);
275 return 0;
276 }
277
278 return 1;
279}
280
281/* render loaded world */
282static void render_world(void)
283{
284 MGMat4 proj;
285 MGMat4 view;
286 MGMat4 mvp;
287 MGMat4 model;
288 i32 width;
289 i32 height;
290 float aspect;
291 i32 texture;
292
293 Chunk* chunk;
294 ChunkSection* section;
295 i32 section_index;
296 i32 chunk_index;
297
298 platform_get_drawable_size(&game.ctx, &width, &height);
299 if (width <= 0 || height <= 0)
300 return;
301
302 aspect = (float)width / (float)height;
303 proj = camera_get_proj(&game.cam, aspect);
304 view = camera_get_view(&game.cam);
305
306 for (chunk_index = 0; chunk_index < CLIENT_WORLD_MAX_CHUNKS; chunk_index++) {
307 chunk = &game.world.chunks[chunk_index];
308 if (!chunk->used)
309 continue;
310
311 for (section_index = 0; section_index < CHUNK_SECTION_COUNT; section_index++) {
312 if (!(chunk->section_mask & (1U << section_index)))
313 continue;
314
315 section = chunk->sections[section_index];
316 if (!section)
317 continue;
318 model = MG_Translate(MG_V3(chunk->x * CHUNK_WIDTH, section_index * CHUNK_SECTION_HEIGHT,
319 chunk->z * CHUNK_DEPTH));
320 mvp = MG_MulM4(proj, MG_MulM4(view, model));
321
322 for (texture = 0; texture < BLOCK_TEXTURE_COUNT; texture++) {
323 if (section->meshes[texture] != MGMESH_INVALID)
324 renderer_mesh_draw(section->meshes[texture], block_texture_get(texture), &mvp);
325 }
326 }
327 }
328}
329
330int main(int argc, char** argv)
331{
332 game_init(argc, argv);
333 game_run();
334 game_dies();
335
336 return MG_EC_EXIT_SUCCESS;
337}