diff options
Diffstat (limited to 'game.ha')
-rw-r--r-- | game.ha | 238 |
1 files changed, 238 insertions, 0 deletions
@@ -0,0 +1,238 @@ +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..."); +}; |