master uint/magnolia / source / main.c
  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}