use fmt; use gl::*; use glm; use mcproto; use time; type Gamemode = enum u8 { SURVIVAL = 0, CREATIVE = 1, ADVENTURE = 2, SPECTATOR = 3, COUNT, }; fn strgamemode(gamemode: Gamemode) str = { switch (gamemode) { case Gamemode::SURVIVAL => return "survival"; case Gamemode::CREATIVE => return "creative"; case Gamemode::ADVENTURE => return "adventure"; case Gamemode::SPECTATOR => return "spectator"; case => abort("unknown gamemode"); }; }; let GAMEMODE = Gamemode::SURVIVAL; let DIM_TYPE = 0z; let VIEW_DISTANCE = 0i32; let LOADING_READY = false; let LOADING_RECEIVED_SPAWN_POSITION = false; def TICK_INTERVAL = time::SECOND / 20; def TICK_MAX_PER_FRAME = 40u64; let TICK_EPOCH = time::instant { ... }; let TICK_REAL_TIME_COUNT = 0u64; let TICK_COUNT = 0u64; type DimType = struct { name: str, min_y: i8, // in chunks height: u8, // in chunks }; let DIM_TYPES: []DimType = []; fn game_init() void = { TICK_EPOCH = frame_timestamp(); let out: []u8 = []; defer free(out); mcproto::encode_string(&out, "en_US"); append(out, 40); mcproto::encode_varint(&out, 0); mcproto::encode_bool(&out, true); append(out, 0); mcproto::encode_varint(&out, 1); mcproto::encode_bool(&out, false); mcproto::encode_bool(&out, true); network_send(0x07, out); game_respawn(); }; fn game_respawn() void = { chunks_respawn(); }; fn game_despawn() void = { death_close(); chunks_despawn(); LOADING_READY = false; LOADING_RECEIVED_SPAWN_POSITION = false; player_despawn(); }; fn game_destroy() void = { game_despawn(); VIEW_DISTANCE = 0; TICK_REAL_TIME_COUNT = 0; TICK_COUNT = 0; }; fn game_frame() void = { if (!LOADING_READY && loading_is_ready()) { LOADING_READY = true; }; death_frame(); control_frame(); const ts = frame_timestamp(); const ticks = (time::diff(TICK_EPOCH, ts) / TICK_INTERVAL): u64; const frame_ticks = ticks - TICK_REAL_TIME_COUNT; const frame_ticks = if (frame_ticks < TICK_MAX_PER_FRAME) frame_ticks else TICK_MAX_PER_FRAME; TICK_REAL_TIME_COUNT = ticks; for (let i = 0u64; i < frame_ticks; i += 1) { TICK_COUNT += 1; game_tick(); }; render_chunks_frame(); }; fn game_tick() void = { player_tick(); }; fn loading_is_ready() bool = { if (!LOADING_RECEIVED_SPAWN_POSITION) { return false; }; if (GAMEMODE == Gamemode::SPECTATOR || DEATH_SHOWN) { return true; }; const min_y = CHUNKS_MIN_Y: f32 * 16.0; const max_y = min_y + CHUNKS_HEIGHT: f32 * 16.0 + 1.0; if (PLAYER_POS[1] < min_y || PLAYER_POS[1] >= max_y) { return true; }; const player_chunk_pos = ChunkPos { x = PLAYER_POS[0]: i32 >> 4, z = PLAYER_POS[2]: i32 >> 4, }; if (getchunk(player_chunk_pos) is *Chunk) { // TODO: query render status. infeasible at the moment due to // dumb chunk update order. return true; }; return false; }; const LAYER_GAME = Layer { blocks_input = &layer_game_blocks_input, input = &layer_game_input, is_opaque = &layer_game_is_opaque, render = &layer_game_render, }; fn layer_game_blocks_input() bool = { return true; }; fn layer_game_input(event: InputEvent) bool = { control_input(event); return true; }; fn layer_game_is_opaque() bool = { return true; }; fn layer_game_render() void = { if (CLIENT_STATE != ClientState::GAME) { return; }; const fovy = 70.0f32; let (width, height) = drawable_size(); glViewport(0, 0, width: i32, height: i32); let view = glm::m4_new_ident(); glm::translate(&view, &({ let neg_pos = PLAYER_POS; neg_pos = glm::v3_sub(&neg_pos, &[ CHUNKS_POS.x: f32 * 16.0, 0.0, CHUNKS_POS.z: f32 * 16.0, ]); glm::v3_negate(&neg_pos); yield neg_pos; })); // the angle here should be negated, as the view transform is the // inverse of the camera transform, except for, contrary to mathematical // convention, Minecraft's "yaw" is positive-clockwise (as seen from // above), so we get two negations. glm::rotate(&view, glm::rad(PLAYER_YAW), &glm::v3_new(0.0, 1.0, 0.0)); // then we need to rotate by 180°, since a yaw of 0 means the player is // looking towards the +Z direction, but forward in view space is // actually -Z. glm::scale(&view, &glm::v3_new(-1.0, 1.0, -1.0)); // whether or not pitch goes against convention too is dependent on your // perspective, but since we've already rotated by 180° about Y, in our // case it does. glm::rotate(&view, glm::rad(PLAYER_PITCH), &glm::v3_new(1.0, 0.0, 0.0)); glClearColor(0.033, 0.01, 1.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); let projection = glm::m4_new_zero(); glm::perspective(&projection, glm::rad(fovy), width: f32 / height: f32, 0.1, 4096.0, ); const view_proj = glm::m4_mul(&projection, &view); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); render_chunks_render(&view_proj); }; const LAYER_GAME_WAITING = Layer { blocks_input = &layer_game_waiting_blocks_input, input = &layer_game_waiting_input, is_opaque = &layer_game_waiting_is_opaque, render = &layer_game_waiting_render, }; fn layer_game_waiting_blocks_input() bool = { return CLIENT_STATE == ClientState::GAME && !LOADING_READY; }; fn layer_game_waiting_input(event: InputEvent) bool = { return false; }; fn layer_game_waiting_is_opaque() bool = { return CLIENT_STATE == ClientState::GAME && !LOADING_READY; }; fn layer_game_waiting_render() void = { if (CLIENT_STATE != ClientState::GAME || LOADING_READY) { return; }; render_wait_screen("Loading terrain..."); };