summaryrefslogtreecommitdiff
path: root/game.ha
diff options
context:
space:
mode:
authorLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:11:21 +0200
committerLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:51:35 +0200
commitae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch)
tree5f462459ae4b47d22114eed717d1382d08cf4dfe /game.ha
Initial commit (import old repo)HEADmain
Diffstat (limited to 'game.ha')
-rw-r--r--game.ha238
1 files changed, 238 insertions, 0 deletions
diff --git a/game.ha b/game.ha
new file mode 100644
index 0000000..c44ef89
--- /dev/null
+++ b/game.ha
@@ -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...");
+};