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