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