diff --git a/CMakeLists.txt b/CMakeLists.txt
index f9f9b57..7111a57 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,6 +75,10 @@ add_executable(cavex
source/block/block_workbench.c
source/block/face_occlusion.c
+ source/entity/entity.c
+ source/entity/entity_local_player.c
+ source/entity/entity_item.c
+
source/cNBT/buffer.c
source/cNBT/nbt_loading.c
source/cNBT/nbt_parsing.c
diff --git a/Makefile b/Makefile
index 81e0796..c658dfb 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ include $(DEVKITPPC)/wii_rules
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
-SOURCES := source source/block source/graphics source/network source/game source/game/gui source/platform source/item source/cNBT source/lodepng source/parson
+SOURCES := source source/block source/entity source/graphics source/network source/game source/game/gui source/platform source/item source/cNBT source/lodepng source/parson
DATA :=
TEXTURES := textures
INCLUDES :=
diff --git a/source/block/blocks_data.h b/source/block/blocks_data.h
index 1004e28..d17ae56 100644
--- a/source/block/blocks_data.h
+++ b/source/block/blocks_data.h
@@ -59,6 +59,7 @@ enum block_type {
BLOCK_DOUBLE_SLAB = 43,
BLOCK_MOSSY_COBBLE = 48,
BLOCK_OBSIDIAN = 49,
+ BLOCK_LADDER = 65,
BLOCK_SNOW = 78,
BLOCK_ICE = 79,
BLOCK_CACTUS = 81,
diff --git a/source/entity/entity.c b/source/entity/entity.c
new file mode 100644
index 0000000..a2ce0c3
--- /dev/null
+++ b/source/entity/entity.c
@@ -0,0 +1,301 @@
+/*
+ Copyright (c) 2023 ByteBit/xtreme8000
+
+ This file is part of CavEX.
+
+ CavEX is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ CavEX is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with CavEX. If not, see .
+*/
+
+#include "entity.h"
+#include "../network/server_world.h"
+#include "../platform/gfx.h"
+#include "../world.h"
+
+void entity_default_init(struct entity* e, bool server, void* world) {
+ e->on_server = server;
+ e->world = world;
+ e->on_ground = true;
+ e->delay_destroy = -1;
+
+ glm_vec3_zero(e->pos);
+ glm_vec3_zero(e->pos_old);
+ glm_vec3_zero(e->network_pos);
+ glm_vec3_zero(e->vel);
+ glm_vec2_zero(e->orient);
+ glm_vec2_zero(e->orient_old);
+}
+
+void entity_default_teleport(struct entity* e, vec3 pos) {
+ e->on_ground = false;
+
+ glm_vec3_copy(pos, e->pos);
+ glm_vec3_copy(pos, e->pos_old);
+ glm_vec3_copy(pos, e->network_pos);
+}
+
+bool entity_default_client_tick(struct entity* e) {
+ assert(e);
+
+ glm_vec3_copy(e->pos, e->pos_old);
+ glm_vec3_copy(e->network_pos, e->pos);
+ glm_vec2_copy(e->orient, e->orient_old);
+ return false;
+}
+
+bool entity_get_block(struct entity* e, w_coord_t x, w_coord_t y, w_coord_t z,
+ struct block_data* blk) {
+ assert(e && blk);
+
+ if(e->on_server) {
+ return server_world_get_block(e->world, x, y, z, blk);
+ } else {
+ *blk = world_get_block(e->world, x, y, z);
+ return true;
+ }
+}
+
+void entity_shadow(struct entity* e, struct AABB* a, mat4 view) {
+ assert(e && a && view);
+
+ w_coord_t min_x = floorf(a->x1);
+ w_coord_t min_y = floorf(a->y1);
+ w_coord_t min_z = floorf(a->z1);
+
+ w_coord_t max_x = ceilf(a->x2) + 1;
+ w_coord_t max_y = ceilf(a->y2) + 1;
+ w_coord_t max_z = ceilf(a->z2) + 1;
+
+ gfx_matrix_modelview(view);
+ gfx_blending(MODE_BLEND);
+ gfx_alpha_test(false);
+ gfx_bind_texture(&texture_shadow);
+ gfx_lighting(false);
+
+ float offset = 0.01F;
+ float du = 1.0F / (a->x2 - a->x1);
+ float dv = 1.0F / (a->z2 - a->z1);
+
+ for(w_coord_t x = min_x; x < max_x; x++) {
+ for(w_coord_t z = min_z; z < max_z; z++) {
+ for(w_coord_t y = min_y; y < max_y; y++) {
+ struct block_data blk;
+
+ if(entity_get_block(e, x, y, z, &blk) && blocks[blk.type]) {
+ struct AABB b;
+ if(blocks[blk.type]->getBoundingBox(
+ &(struct block_info) {.block = &blk,
+ .neighbours = NULL,
+ .x = x,
+ .y = y,
+ .z = z},
+ true, &b)) {
+ aabb_translate(&b, x, y, z);
+ if(a->y2 > b.y2 && aabb_intersection(a, &b)) {
+ float u1 = (b.x1 - a->x1) * du;
+ float u2 = (b.x2 - a->x1) * du;
+ float v1 = (b.z1 - a->z1) * dv;
+ float v2 = (b.z2 - a->z1) * dv;
+
+ gfx_draw_quads_flt(
+ 4,
+ (float[]) {b.x1, b.y2 + offset, b.z1, b.x2,
+ b.y2 + offset, b.z1, b.x2,
+ b.y2 + offset, b.z2, b.x1,
+ b.y2 + offset, b.z2},
+ (uint8_t[]) {0xFF, 0xFF, 0xFF, 0x60, 0xFF, 0xFF,
+ 0xFF, 0x60, 0xFF, 0xFF, 0xFF, 0x60,
+ 0xFF, 0xFF, 0xFF, 0x60},
+ (float[]) {u1, v1, u2, v1, u2, v2, u1, v2});
+ }
+ }
+ }
+ }
+ }
+ }
+
+ gfx_blending(MODE_OFF);
+ gfx_alpha_test(true);
+ gfx_lighting(true);
+}
+
+#define max(a, b) (((a) > (b)) ? (a) : (b))
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+bool entity_aabb_intersection(struct entity* e, struct AABB* a,
+ bool (*test)(struct block_data*, w_coord_t,
+ w_coord_t, w_coord_t)) {
+ assert(e && a);
+
+ w_coord_t min_x = floorf(a->x1);
+ // need to look one further, otherwise fence block breaks
+ w_coord_t min_y = max((w_coord_t)(floorf(a->y1) - 1), 0);
+ w_coord_t min_z = floorf(a->z1);
+
+ w_coord_t max_x = ceilf(a->x2) + 1;
+ w_coord_t max_y = min((w_coord_t)(ceilf(a->y2) + 1), WORLD_HEIGHT - 1);
+ w_coord_t max_z = ceilf(a->z2) + 1;
+
+ for(w_coord_t x = min_x; x < max_x; x++) {
+ for(w_coord_t z = min_z; z < max_z; z++) {
+ for(w_coord_t y = min_y; y < max_y; y++) {
+ struct block_data blk;
+
+ if(entity_get_block(e, x, y, z, &blk) && blocks[blk.type]) {
+ struct AABB b;
+ if(blocks[blk.type]->getBoundingBox(
+ &(struct block_info) {.block = &blk,
+ .neighbours = NULL,
+ .x = x,
+ .y = y,
+ .z = z},
+ true, &b)
+ || (test && test(&blk, x, y, z))) {
+ aabb_translate(&b, x, y, z);
+ if(aabb_intersection(a, &b)
+ && (!test || test(&blk, x, y, z)))
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool entity_intersection_threshold(struct entity* e, struct AABB* aabb,
+ vec3 old_pos, vec3 new_pos,
+ float* threshold) {
+ assert(e && aabb && old_pos && new_pos && threshold);
+
+ struct AABB tmp = *aabb;
+ aabb_translate(&tmp, old_pos[0], old_pos[1], old_pos[2]);
+ bool a = entity_aabb_intersection(e, &tmp, NULL);
+
+ tmp = *aabb;
+ aabb_translate(&tmp, new_pos[0], new_pos[1], new_pos[2]);
+ bool b = entity_aabb_intersection(e, &tmp, NULL);
+
+ if(!a && b) {
+ float range_min = 0.0F;
+ float range_max = 1.0F;
+
+ while(1) {
+ vec3 pos_min, pos_max;
+ glm_vec3_lerp(old_pos, new_pos, range_min, pos_min);
+ glm_vec3_lerp(old_pos, new_pos, range_max, pos_max);
+
+ if(glm_vec3_distance2(pos_min, pos_max) < glm_pow2(0.01F))
+ break;
+
+ float mid = (range_max + range_min) / 2.0F;
+
+ vec3 pos_mid;
+ glm_vec3_lerp(old_pos, new_pos, mid, pos_mid);
+
+ struct AABB dest = *aabb;
+ aabb_translate(&dest, pos_mid[0], pos_mid[1], pos_mid[2]);
+
+ if(entity_aabb_intersection(e, &dest, NULL)) {
+ range_max = mid;
+ } else {
+ range_min = mid;
+ }
+ }
+
+ *threshold = range_min;
+ return true;
+ } else if(a) {
+ *threshold = 0.0F;
+ return true;
+ } else {
+ *threshold = 1.0F;
+ return false;
+ }
+}
+
+void entity_try_move(struct entity* e, vec3 pos, vec3 vel, struct AABB* bbox,
+ size_t coord, bool* collision_xz, bool* on_ground) {
+ assert(e && pos && vel && bbox && collision_xz && on_ground);
+
+ vec3 tmp;
+ glm_vec3_copy(pos, tmp);
+ tmp[coord] += vel[coord];
+
+ float threshold;
+ if(entity_intersection_threshold(e, bbox, pos, tmp, &threshold)) {
+ if(coord == 1 && vel[1] < 0.0F)
+ *on_ground = true;
+
+ if(coord == 0 || coord == 2)
+ *collision_xz = true;
+
+ vel[coord] = 0.0F;
+ } else if(coord == 1) {
+ *on_ground = false;
+ }
+
+ pos[coord] = pos[coord] * (1.0F - threshold) + tmp[coord] * threshold;
+}
+
+uint32_t entity_gen_id(dict_entity_t dict) {
+ assert(dict);
+
+ dict_entity_it_t it;
+ dict_entity_it(it, dict);
+
+ // id = 0 is reserved for local player
+
+ uint32_t id = 0;
+
+ while(!dict_entity_end_p(it)) {
+ uint32_t key = dict_entity_ref(it)->key;
+
+ if(key > id)
+ id = key;
+
+ dict_entity_next(it);
+ }
+
+ return id + 1;
+}
+
+void entities_client_tick(dict_entity_t dict) {
+ dict_entity_it_t it;
+ dict_entity_it(it, dict);
+
+ while(!dict_entity_end_p(it)) {
+ struct entity* e = &dict_entity_ref(it)->value;
+
+ if(e->tick_client)
+ e->tick_client(e);
+
+ dict_entity_next(it);
+ }
+}
+
+void entities_client_render(dict_entity_t dict, struct camera* c,
+ float tick_delta) {
+ dict_entity_it_t it;
+ dict_entity_it(it, dict);
+
+ while(!dict_entity_end_p(it)) {
+ struct entity* e = &dict_entity_ref(it)->value;
+ if(e->render
+ && glm_vec3_distance2(e->pos, (vec3) {c->x, c->y, c->z})
+ < glm_pow2(32.0F))
+ e->render(e, c->view, tick_delta);
+ dict_entity_next(it);
+ }
+}
\ No newline at end of file
diff --git a/source/entity/entity.h b/source/entity/entity.h
new file mode 100644
index 0000000..084e9df
--- /dev/null
+++ b/source/entity/entity.h
@@ -0,0 +1,99 @@
+/*
+ Copyright (c) 2023 ByteBit/xtreme8000
+
+ This file is part of CavEX.
+
+ CavEX is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ CavEX is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with CavEX. If not, see .
+*/
+
+#ifndef ENTITY_H
+#define ENTITY_H
+
+#include
+#include
+
+#include "../cglm/cglm.h"
+#include "../item/items.h"
+
+enum entity_type {
+ ENTITY_LOCAL_PLAYER,
+ ENTITY_ITEM,
+};
+
+struct server_local;
+
+struct entity {
+ uint32_t id;
+ bool on_server;
+ void* world;
+ int delay_destroy;
+
+ vec3 pos;
+ vec3 pos_old;
+ vec3 vel;
+ vec2 orient;
+ vec2 orient_old;
+ bool on_ground;
+
+ vec3 network_pos;
+
+ bool (*tick_client)(struct entity*);
+ bool (*tick_server)(struct entity*, struct server_local*);
+ void (*render)(struct entity*, mat4, float);
+ void (*teleport)(struct entity*, vec3);
+
+ enum entity_type type;
+ union entity_data {
+ struct entity_local_player {
+ int jump_ticks;
+ bool capture_input;
+ } local_player;
+ struct entity_item {
+ struct item_data item;
+ int age;
+ } item;
+ } data;
+};
+
+DICT_DEF2(dict_entity, uint32_t, M_BASIC_OPLIST, struct entity, M_POD_OPLIST)
+
+#include "../world.h"
+
+void entity_local_player(uint32_t id, struct entity* e, struct world* w);
+void entity_item(uint32_t id, struct entity* e, bool server, void* world,
+ struct item_data it);
+
+uint32_t entity_gen_id(dict_entity_t dict);
+void entities_client_tick(dict_entity_t dict);
+void entities_client_render(dict_entity_t dict, struct camera* c,
+ float tick_delta);
+
+void entity_default_init(struct entity* e, bool server, void* world);
+void entity_default_teleport(struct entity* e, vec3 pos);
+bool entity_default_client_tick(struct entity* e);
+
+void entity_shadow(struct entity* e, struct AABB* a, mat4 view);
+
+bool entity_get_block(struct entity* e, w_coord_t x, w_coord_t y, w_coord_t z,
+ struct block_data* blk);
+bool entity_intersection_threshold(struct entity* e, struct AABB* aabb,
+ vec3 old_pos, vec3 new_pos,
+ float* threshold);
+bool entity_aabb_intersection(struct entity* e, struct AABB* a,
+ bool (*test)(struct block_data*, w_coord_t,
+ w_coord_t, w_coord_t));
+void entity_try_move(struct entity* e, vec3 pos, vec3 vel, struct AABB* bbox,
+ size_t coord, bool* collision_xz, bool* on_ground);
+
+#endif
\ No newline at end of file
diff --git a/source/entity/entity_item.c b/source/entity/entity_item.c
new file mode 100644
index 0000000..2692e90
--- /dev/null
+++ b/source/entity/entity_item.c
@@ -0,0 +1,205 @@
+/*
+ Copyright (c) 2023 ByteBit/xtreme8000
+
+ This file is part of CavEX.
+
+ CavEX is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ CavEX is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with CavEX. If not, see .
+*/
+
+#include "../block/blocks_data.h"
+#include "../game/game_state.h"
+#include "../network/client_interface.h"
+#include "../network/server_local.h"
+#include "../platform/gfx.h"
+#include "entity.h"
+
+static bool entity_client_tick(struct entity* e) {
+ entity_default_client_tick(e);
+ e->data.item.age++;
+ return false;
+}
+
+static bool entity_server_tick(struct entity* e, struct server_local* s) {
+ assert(e);
+
+ glm_vec3_copy(e->pos, e->pos_old);
+ glm_vec2_copy(e->orient, e->orient_old);
+
+ for(int k = 0; k < 3; k++)
+ if(fabsf(e->vel[k]) < 0.005F)
+ e->vel[k] = 0.0F;
+
+ struct AABB bbox;
+ aabb_setsize_centered(&bbox, 0.25F, 0.25F, 0.25F);
+
+ struct AABB tmp = bbox;
+ aabb_translate(&tmp, e->pos[0], e->pos[1], e->pos[2]);
+
+ if(entity_aabb_intersection(e, &tmp, NULL)) { // is item stuck in block?
+ // find possible new position, try top/bottom last
+ enum side sides[6] = {SIDE_LEFT, SIDE_RIGHT, SIDE_FRONT,
+ SIDE_BACK, SIDE_TOP, SIDE_BOTTOM};
+ for(int k = 0; k < 6; k++) {
+ int x, y, z;
+ blocks_side_offset(sides[k], &x, &y, &z);
+
+ vec3 new_pos;
+ glm_vec3_add(e->pos, (vec3) {x, y, z}, new_pos);
+
+ struct AABB tmp2 = tmp;
+ aabb_translate(&tmp2, x, y, z);
+
+ if(!entity_aabb_intersection(e, &tmp2, NULL)) {
+ float threshold;
+ entity_intersection_threshold(e, &bbox, new_pos, e->pos,
+ &threshold);
+ glm_vec3_lerp(new_pos, e->pos, threshold, e->pos);
+ e->vel[0] = x * 0.1F;
+ e->vel[1] = y * 0.1F;
+ e->vel[2] = z * 0.1F;
+
+ break;
+ }
+ }
+ }
+
+ bool collision_xz = false;
+
+ for(int k = 0; k < 3; k++)
+ entity_try_move(e, e->pos, e->vel, &bbox, (size_t[]) {1, 0, 2}[k],
+ &collision_xz, &e->on_ground);
+
+ e->vel[1] -= 0.04F;
+ e->vel[0] *= (e->on_ground ? 0.6F : 1.0F) * 0.98F;
+ e->vel[2] *= (e->on_ground ? 0.6F : 1.0F) * 0.98F;
+ e->vel[1] *= 0.98F;
+
+ e->data.item.age++;
+
+ if(e->delay_destroy > 0) {
+ e->delay_destroy--;
+ } else if(e->data.item.age >= 2 * 20
+ && glm_vec3_distance2(
+ e->pos,
+ (vec3) {s->player.x, s->player.y - 0.6F, s->player.z})
+ < glm_pow2(2.0F)) { // allow pickup after 2s
+ bool slots_changed[INVENTORY_SIZE];
+ memset(slots_changed, false, sizeof(slots_changed));
+
+ // TODO: case where item cannot be picked up completely
+ inventory_collect_inventory(&s->player.inventory, &e->data.item.item,
+ slots_changed);
+
+ for(size_t k = 0; k < INVENTORY_SIZE; k++) {
+ if(slots_changed[k])
+ clin_rpc_send(&(struct client_rpc) {
+ .type = CRPC_INVENTORY_SLOT,
+ .payload.inventory_slot.window = WINDOWC_INVENTORY,
+ .payload.inventory_slot.slot = k,
+ .payload.inventory_slot.item = s->player.inventory.items[k],
+ });
+ }
+
+ clin_rpc_send(&(struct client_rpc) {
+ .type = CRPC_PICKUP_ITEM,
+ .payload.pickup_item.entity_id = e->id,
+ .payload.pickup_item.collector_id = 0, // local player
+ });
+
+ e->delay_destroy = 1;
+ }
+
+ return e->data.item.age >= 5 * 60 * 20; // destroy after 5 min
+}
+
+static void entity_render(struct entity* e, mat4 view, float tick_delta) {
+ struct item* it = item_get(&e->data.item.item);
+
+ if(it) {
+ vec3 pos_lerp;
+ glm_vec3_lerp(e->pos_old, e->pos, tick_delta, pos_lerp);
+
+ struct block_data in_block;
+ entity_get_block(e, floorf(pos_lerp[0]), floorf(pos_lerp[1]),
+ floorf(pos_lerp[2]), &in_block);
+ render_item_update_light((in_block.torch_light << 4)
+ | in_block.sky_light);
+
+ float ticks = e->data.item.age + tick_delta;
+
+ mat4 model;
+ glm_translate_make(model, pos_lerp);
+ glm_translate_y(model, sinf(ticks / 30.0F * GLM_PIf) * 0.1F + 0.1F);
+ glm_rotate_y(model, glm_rad(ticks * 3.0F), model);
+ glm_scale_uni(model, 0.25F);
+ glm_translate(model, (vec3) {-0.5F, -0.5F, -0.5F});
+
+ mat4 mv;
+ glm_mat4_mul(view, model, mv);
+
+ int amount = 1;
+ if(e->data.item.item.count > 1) {
+ amount = 2;
+ } else if(e->data.item.item.count > 5) {
+ amount = 3;
+ } else if(e->data.item.item.count > 20) {
+ amount = 4;
+ }
+
+ vec3 displacement[4] = {
+ {0.0F, 0.0F, 0.0F},
+ {-0.701F, -0.331F, -0.239F},
+ {0.139F, -0.276F, 0.211F},
+ {0.443F, 0.512F, -0.101F},
+ };
+
+ for(int k = 0; k < amount; k++) {
+ mat4 final;
+ glm_translate_make(final, displacement[k]);
+ glm_mat4_mul(mv, final, final);
+
+ it->renderItem(it, &e->data.item.item, final, false,
+ R_ITEM_ENV_ENTITY);
+ }
+
+ struct AABB bbox;
+ aabb_setsize_centered(&bbox, 0.25F, 0.25F, 0.25F);
+ aabb_translate(&bbox, pos_lerp[0], pos_lerp[1] - 0.04F, pos_lerp[2]);
+ entity_shadow(e, &bbox, view);
+ }
+}
+
+void entity_item(uint32_t id, struct entity* e, bool server, void* world,
+ struct item_data it) {
+ assert(e && world);
+
+ e->id = id;
+ e->tick_server = entity_server_tick;
+ e->tick_client = entity_client_tick;
+ e->render = entity_render;
+ e->teleport = entity_default_teleport;
+ e->type = ENTITY_ITEM;
+ e->data.item.age = 0;
+ e->data.item.item = it;
+
+ entity_default_init(e, server, world);
+
+ if(server) {
+ glm_vec3_copy(
+ (vec3) {rand_flt() - 0.5F, rand_flt() - 0.5F, rand_flt() - 0.5F},
+ e->vel);
+ glm_vec3_normalize(e->vel);
+ glm_vec3_scale(e->vel, (2.0F * rand_flt() + 0.5F) * 0.1F, e->vel);
+ }
+}
diff --git a/source/entity/entity_local_player.c b/source/entity/entity_local_player.c
new file mode 100644
index 0000000..c57a153
--- /dev/null
+++ b/source/entity/entity_local_player.c
@@ -0,0 +1,201 @@
+/*
+ Copyright (c) 2023 ByteBit/xtreme8000
+
+ This file is part of CavEX.
+
+ CavEX is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ CavEX is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with CavEX. If not, see .
+*/
+
+#include "../block/blocks_data.h"
+#include "../platform/input.h"
+#include "entity.h"
+
+static bool test_in_lava(struct block_data* blk, w_coord_t x, w_coord_t y,
+ w_coord_t z) {
+ return blk->type == BLOCK_LAVA_FLOW || blk->type == BLOCK_LAVA_STILL;
+}
+
+static bool test_in_water(struct block_data* blk, w_coord_t x, w_coord_t y,
+ w_coord_t z) {
+ return blk->type == BLOCK_WATER_FLOW || blk->type == BLOCK_WATER_STILL;
+}
+
+static bool test_in_liquid(struct block_data* blk, w_coord_t x, w_coord_t y,
+ w_coord_t z) {
+ return test_in_water(blk, x, y, z) || test_in_lava(blk, x, y, z);
+}
+
+static bool entity_tick(struct entity* e) {
+ assert(e);
+
+ glm_vec3_copy(e->pos, e->pos_old);
+ glm_vec2_copy(e->orient, e->orient_old);
+
+ for(int k = 0; k < 3; k++)
+ if(fabsf(e->vel[k]) < 0.005F)
+ e->vel[k] = 0.0F;
+
+ struct AABB bbox;
+ aabb_setsize_centered(&bbox, 0.6F, 1.0F, 0.6F);
+ aabb_translate(&bbox, e->pos[0], e->pos[1] + 1.8F / 2.0F - 1.62F,
+ e->pos[2]);
+
+ bool in_water = entity_aabb_intersection(e, &bbox, test_in_water);
+ bool in_lava = entity_aabb_intersection(e, &bbox, test_in_lava);
+
+ float slipperiness
+ = (in_lava || in_water) ? 1.0F : (e->on_ground ? 0.6F : 1.0F);
+
+ int forward = 0;
+ int strafe = 0;
+ bool jumping = false;
+
+ if(e->data.local_player.capture_input) {
+ if(input_held(IB_FORWARD))
+ forward++;
+
+ if(input_held(IB_BACKWARD))
+ forward--;
+
+ if(input_held(IB_RIGHT))
+ strafe++;
+
+ if(input_held(IB_LEFT))
+ strafe--;
+
+ jumping = input_held(IB_JUMP);
+ }
+
+ int dist = forward * forward + strafe * strafe;
+
+ if(dist > 0) {
+ float distf = fmaxf(sqrtf(dist), 1.0F);
+ float dx = (forward * sinf(e->orient[0]) - strafe * cosf(e->orient[0]))
+ / distf;
+ float dy = (strafe * sinf(e->orient[0]) + forward * cosf(e->orient[0]))
+ / distf;
+
+ e->vel[0] += 0.1F * powf(0.6F / slipperiness, 3.0F) * dx;
+ e->vel[2] += 0.1F * powf(0.6F / slipperiness, 3.0F) * dy;
+ }
+
+ if(e->data.local_player.jump_ticks > 0)
+ e->data.local_player.jump_ticks--;
+
+ if(jumping) {
+ if(in_water || in_lava) {
+ e->vel[1] += 0.04F;
+ } else if(e->on_ground && e->data.local_player.jump_ticks == 0) {
+ e->vel[1] = 0.42F;
+ e->data.local_player.jump_ticks = 10;
+ }
+ } else {
+ e->data.local_player.jump_ticks = 0;
+ }
+
+ float eye_height = 1.62F;
+
+ aabb_setsize_centered(&bbox, 0.6F, 1.8F, 0.6F);
+ aabb_translate(&bbox, 0.0F, 1.8F / 2.0F - eye_height, 0.0F);
+
+ vec3 new_pos, new_vel;
+ glm_vec3_copy(e->pos, new_pos);
+ glm_vec3_copy(e->vel, new_vel);
+
+ bool collision_xz = false;
+
+ for(int k = 0; k < 3; k++)
+ entity_try_move(e, e->pos, e->vel, &bbox, (size_t[]) {1, 0, 2}[k],
+ &collision_xz, &e->on_ground);
+
+ if(e->on_ground) {
+ bool collision = false;
+ bool ground = e->on_ground;
+
+ new_vel[1] = 0.6F;
+ entity_try_move(e, new_pos, new_vel, &bbox, 1, &collision, &ground);
+
+ new_vel[1] = 0.0F;
+ entity_try_move(e, new_pos, new_vel, &bbox, 0, &collision, &ground);
+ entity_try_move(e, new_pos, new_vel, &bbox, 2, &collision, &ground);
+
+ new_vel[1] = -0.6F;
+ entity_try_move(e, new_pos, new_vel, &bbox, 1, &collision, &ground);
+
+ if(glm_vec3_distance2(e->pos_old, e->pos)
+ < glm_vec3_distance2(e->pos_old, new_pos)) {
+ collision_xz = collision;
+ e->on_ground = ground;
+ glm_vec3_copy(new_pos, e->pos);
+ glm_vec3_copy(new_vel, e->vel);
+ }
+ }
+
+ if(in_lava) {
+ e->vel[0] *= 0.5F;
+ e->vel[2] *= 0.5F;
+ e->vel[1] = e->vel[1] * 0.5F - 0.02F;
+ } else if(in_water) {
+ e->vel[0] *= 0.8F;
+ e->vel[2] *= 0.8F;
+ e->vel[1] = e->vel[1] * 0.8F - 0.02F;
+ } else {
+ e->vel[0] *= slipperiness * 0.91F;
+ e->vel[2] *= slipperiness * 0.91F;
+ e->vel[1] -= 0.08F;
+
+ struct block_data blk;
+ if(entity_get_block(e, floorf(e->pos[0]),
+ floorf(e->pos[1] - eye_height), floorf(e->pos[2]),
+ &blk)
+ && blk.type == BLOCK_LADDER) {
+ if(collision_xz)
+ e->vel[1] = 0.12F;
+
+ e->vel[0] = fmaxf(fminf(e->vel[0], 0.15F), -0.15F);
+ e->vel[1] = fmaxf(e->vel[1], -0.15F);
+ e->vel[2] = fmaxf(fminf(e->vel[2], 0.15F), -0.15F);
+ }
+
+ e->vel[1] *= 0.98F;
+ }
+
+ if(collision_xz && (in_lava || in_water)) {
+ struct AABB tmp;
+ aabb_setsize_centered(&tmp, 0.6F, 1.8F, 0.6F);
+ aabb_translate(&tmp, e->pos[0] + e->vel[0],
+ e->pos[1] + e->vel[1] + 1.8F / 2.0F - 1.62F + 0.6F,
+ e->pos[2] + e->vel[2]);
+
+ if(!entity_aabb_intersection(e, &tmp, test_in_liquid))
+ e->vel[1] = 0.3F;
+ }
+
+ return false;
+}
+
+void entity_local_player(uint32_t id, struct entity* e, struct world* w) {
+ assert(e && w);
+
+ e->id = id;
+ e->tick_server = NULL;
+ e->tick_client = entity_tick;
+ e->render = NULL;
+ e->teleport = entity_default_teleport;
+ e->type = ENTITY_LOCAL_PLAYER;
+ e->data.local_player.capture_input = false;
+
+ entity_default_init(e, false, w);
+ e->data.local_player.jump_ticks = 0;
+}
diff --git a/source/game/camera.c b/source/game/camera.c
index 261fbf9..6e3afba 100644
--- a/source/game/camera.c
+++ b/source/game/camera.c
@@ -195,3 +195,23 @@ void camera_update(struct camera* c) {
glm_mat4_mul(c->projection, c->view, view_proj);
glm_frustum_planes(view_proj, c->frustum_planes);
}
+
+void camera_attach(struct camera* c, struct entity* e, float tick_delta,
+ float dt) {
+ vec3 pos_lerp;
+ glm_vec3_lerp(e->pos_old, e->pos, tick_delta, pos_lerp);
+ c->x = pos_lerp[0];
+ c->y = pos_lerp[1];
+ c->z = pos_lerp[2];
+
+ float jdx, jdy;
+ if(input_joystick(dt, &jdx, &jdy)) {
+ c->rx -= jdx * 2.0F;
+ c->ry -= jdy * 2.0F;
+ }
+
+ c->ry = glm_clamp(c->ry, glm_rad(0.5F), GLM_PI - glm_rad(0.5F));
+
+ e->orient[0] = c->rx;
+ e->orient[1] = c->ry;
+}
\ No newline at end of file
diff --git a/source/game/camera.h b/source/game/camera.h
index 4fa667d..a57f7e4 100644
--- a/source/game/camera.h
+++ b/source/game/camera.h
@@ -34,6 +34,7 @@ struct camera {
} controller;
};
+#include "../entity/entity.h"
#include "../world.h"
struct camera_ray_result {
@@ -47,5 +48,7 @@ void camera_ray_pick(struct world* w, float gx0, float gy0, float gz0,
struct camera_ray_result* res);
void camera_physics(struct camera* c, float dt);
void camera_update(struct camera* c);
+void camera_attach(struct camera* c, struct entity* e, float tick_delta,
+ float dt);
#endif
diff --git a/source/game/game_state.h b/source/game/game_state.h
index d6aed68..4dbe768 100644
--- a/source/game/game_state.h
+++ b/source/game/game_state.h
@@ -25,6 +25,7 @@
#include
#include "../config.h"
+#include "../entity/entity.h"
#include "../item/window_container.h"
#include "../platform/time.h"
#include "../world.h"
@@ -53,6 +54,8 @@ struct game_state {
struct camera camera;
struct camera_ray_result camera_hit;
struct world world;
+ struct entity* local_player;
+ dict_entity_t entities;
uint64_t world_time;
ptime_t world_time_start;
struct window_container* windows[256];
diff --git a/source/game/gui/screen_ingame.c b/source/game/gui/screen_ingame.c
index 9d72f00..a65f29e 100644
--- a/source/game/gui/screen_ingame.c
+++ b/source/game/gui/screen_ingame.c
@@ -33,6 +33,9 @@
static void screen_ingame_reset(struct screen* s, int width, int height) {
input_pointer_enable(false);
+
+ if(gstate.local_player)
+ gstate.local_player->data.local_player.capture_input = true;
}
void screen_ingame_render3D(struct screen* s, mat4 view) {
@@ -106,7 +109,6 @@ void screen_ingame_render3D(struct screen* s, mat4 view) {
uint8_t light = (in_block.torch_light << 4) | in_block.sky_light;
gfx_depth_range(0.0F, 0.1F);
- gfx_write_buffers(true, true, true);
struct item_data item;
if(inventory_get_slot(windowc_get_latest(gstate.windows[WINDOWC_INVENTORY]),
@@ -132,13 +134,10 @@ void screen_ingame_render3D(struct screen* s, mat4 view) {
gfx_lookup_light(light));
}
- gfx_write_buffers(true, false, false);
gfx_depth_range(0.0F, 1.0F);
}
static void screen_ingame_update(struct screen* s, float dt) {
- camera_physics(&gstate.camera, dt);
-
if(gstate.camera_hit.hit && input_pressed(IB_ACTION2)
&& !gstate.digging.active) {
struct item_data item;
@@ -393,7 +392,6 @@ static void screen_ingame_render2D(struct screen* s, int width, int height) {
* inventory_get_hotbar(windowc_get_latest(
gstate.windows[WINDOWC_INVENTORY])),
height - 32 * 8 / 5 - 23 * 2, 208, 0, 24, 24, 24 * 2, 24 * 2);
- gfx_blending(MODE_OFF);
}
struct screen screen_ingame = {
diff --git a/source/game/gui/screen_inventory.c b/source/game/gui/screen_inventory.c
index 3aa2864..4a919fb 100644
--- a/source/game/gui/screen_inventory.c
+++ b/source/game/gui/screen_inventory.c
@@ -45,6 +45,10 @@ static size_t selected_slot;
static void screen_inventory_reset(struct screen* s, int width, int height) {
input_pointer_enable(true);
+
+ if(gstate.local_player)
+ gstate.local_player->data.local_player.capture_input = false;
+
s->render3D = screen_ingame.render3D;
pointer_available = false;
diff --git a/source/game/gui/screen_load_world.c b/source/game/gui/screen_load_world.c
index f2813f9..b2765fa 100644
--- a/source/game/gui/screen_load_world.c
+++ b/source/game/gui/screen_load_world.c
@@ -25,6 +25,9 @@
static void screen_lworld_reset(struct screen* s, int width, int height) {
input_pointer_enable(false);
+
+ if(gstate.local_player)
+ gstate.local_player->data.local_player.capture_input = false;
}
static void screen_lworld_update(struct screen* s, float dt) {
diff --git a/source/game/gui/screen_select_world.c b/source/game/gui/screen_select_world.c
index 0dd3916..eefd507 100644
--- a/source/game/gui/screen_select_world.c
+++ b/source/game/gui/screen_select_world.c
@@ -55,6 +55,9 @@ struct world_option {
static void screen_sworld_reset(struct screen* s, int width, int height) {
input_pointer_enable(true);
+ if(gstate.local_player)
+ gstate.local_player->data.local_player.capture_input = false;
+
if(worlds) {
while(!stack_empty(worlds)) {
struct world_option opt;
diff --git a/source/graphics/render_item.c b/source/graphics/render_item.c
index 5161b48..bc2d560 100644
--- a/source/graphics/render_item.c
+++ b/source/graphics/render_item.c
@@ -55,9 +55,9 @@ void render_item_update_light(uint8_t light) {
memset(vertex_light, light, sizeof(vertex_light));
}
-void render_item_flat(struct item* item, struct item_data* stack, mat4 model,
+void render_item_flat(struct item* item, struct item_data* stack, mat4 view,
bool fullbright, enum render_item_env env) {
- assert(item && stack && model);
+ assert(item && stack && view);
uint8_t s, t;
@@ -89,7 +89,7 @@ void render_item_flat(struct item* item, struct item_data* stack, mat4 model,
}
if(env == R_ITEM_ENV_INVENTORY) {
- gfx_matrix_modelview(model);
+ gfx_matrix_modelview(view);
gutil_texquad(0, 0, s, t, 16, 16, 16 * 2, 16 * 2);
} else {
displaylist_reset(&dl);
@@ -200,21 +200,42 @@ void render_item_flat(struct item* item, struct item_data* stack, mat4 model,
displaylist_texcoord(&dl, s + 16, t + k + 1);
}
- if(env == R_ITEM_ENV_THIRDPERSON) {
- glm_translate(model, (vec3) {-4.0F, -8.0F, -7.0F});
- glm_rotate_x(model, glm_rad(60.0F), model);
- glm_scale(model, (vec3) {9.0F, 9.0F, 9.0F});
+ mat4 model;
+
+ switch(env) {
+ case R_ITEM_ENV_THIRDPERSON:
+ glm_translate_make(model, (vec3) {-4.0F, -8.0F, -7.0F});
+ glm_rotate_x(model, glm_rad(60.0F), model);
+ glm_scale(model, (vec3) {9.0F, 9.0F, 9.0F});
+
+ glm_translate(model, (vec3) {0.5F, 0.2F, 0.5F});
+ glm_scale_uni(model, 1.5F);
+ glm_rotate_y(model, glm_rad(90.0F), model);
+ glm_rotate_z(model, glm_rad(335.0F), model);
+ glm_translate(
+ model, (vec3) {1.0F / 16.0F - 1.0F, -1.0F / 16.0F, 0.0F});
+ break;
+ case R_ITEM_ENV_ENTITY:
+ glm_mat4_identity(model);
+ glm_scale_uni(model, 1.5F);
+ glm_translate_make(model,
+ (vec3) {0.0F, 0.0F, 0.5F + 1.0F / 32.0F});
+ break;
+ case R_ITEM_ENV_FIRSTPERSON:
+ glm_translate_make(model, (vec3) {0.5F, 0.2F, 0.5F});
+ glm_scale_uni(model, 1.5F);
+ glm_rotate_y(model, glm_rad(50.0F), model);
+ glm_rotate_z(model, glm_rad(335.0F), model);
+ glm_translate(
+ model, (vec3) {1.0F / 16.0F - 1.0F, -1.0F / 16.0F, 0.0F});
+ break;
+ default: break;
}
- glm_translate(model, (vec3) {0.5F, 0.2F, 0.5F});
- glm_scale_uni(model, 1.5F);
- glm_rotate_y(model,
- glm_rad(env == R_ITEM_ENV_FIRSTPERSON ? 50.0F : 90.0F),
- model);
- glm_rotate_z(model, glm_rad(335.0F), model);
- glm_translate(model, (vec3) {1.0F / 16.0F - 1.0F, -1.0F / 16.0F, 0.0F});
+ mat4 modelview;
+ glm_mat4_mul(view, model, modelview);
+ gfx_matrix_modelview(modelview);
- gfx_matrix_modelview(model);
gfx_lighting(true);
displaylist_render_immediate(&dl, (2 + 16 * 4) * 4);
gfx_lighting(false);
@@ -223,9 +244,9 @@ void render_item_flat(struct item* item, struct item_data* stack, mat4 model,
gfx_matrix_modelview(GLM_MAT4_IDENTITY);
}
-void render_item_block(struct item* item, struct item_data* stack, mat4 model,
+void render_item_block(struct item* item, struct item_data* stack, mat4 view,
bool fullbright, enum render_item_env env) {
- assert(item && stack && model);
+ assert(item && stack && view);
assert(item_is_block(stack));
struct block* b = blocks[stack->id];
@@ -282,33 +303,33 @@ void render_item_block(struct item* item, struct item_data* stack, mat4 model,
fullbright ? vertex_light_inv : vertex_light, false);
}
- mat4 view;
+ mat4 model;
if(env == R_ITEM_ENV_INVENTORY) {
- glm_translate_make(view, (vec3) {3 * 2, 3 * 2, -16});
- glm_scale(view, (vec3) {20, 20, -20});
- glm_translate(view, (vec3) {0.5F, 0.5F, 0.5F});
- glm_rotate_z(view, glm_rad(180.0F), view);
- glm_rotate_x(view, glm_rad(-30.0F), view);
+ glm_translate_make(model, (vec3) {3 * 2, 3 * 2, -16});
+ glm_scale(model, (vec3) {20, 20, -20});
+ glm_translate(model, (vec3) {0.5F, 0.5F, 0.5F});
+ glm_rotate_z(model, glm_rad(180.0F), model);
+ glm_rotate_x(model, glm_rad(-30.0F), model);
glm_rotate_y(
- view,
+ model,
glm_rad((item->render_data.block.has_default ?
item->render_data.block.default_rotation * 90.0F :
0)
- 45.0F),
- view);
- glm_translate(view, (vec3) {-0.5F, -0.5F, -0.5F});
+ model);
+ glm_translate(model, (vec3) {-0.5F, -0.5F, -0.5F});
} else if(env == R_ITEM_ENV_THIRDPERSON) {
- glm_translate_make(view, (vec3) {-4.0F, -14.0F, 3.0F});
- glm_rotate_x(view, glm_rad(22.5F), view);
- glm_rotate_y(view, glm_rad(45.0F), view);
- glm_scale(view, (vec3) {6.0F, 6.0F, 6.0F});
+ glm_translate_make(model, (vec3) {-4.0F, -14.0F, 3.0F});
+ glm_rotate_x(model, glm_rad(22.5F), model);
+ glm_rotate_y(model, glm_rad(45.0F), model);
+ glm_scale(model, (vec3) {6.0F, 6.0F, 6.0F});
} else {
- glm_mat4_identity(view);
+ glm_mat4_identity(model);
}
mat4 modelview;
- glm_mat4_mul(model, view, modelview);
+ glm_mat4_mul(view, model, modelview);
gfx_matrix_modelview(modelview);
gfx_bind_texture(b->transparent ? &texture_anim : &texture_terrain);
diff --git a/source/graphics/render_item.h b/source/graphics/render_item.h
index 70483de..22e5777 100644
--- a/source/graphics/render_item.h
+++ b/source/graphics/render_item.h
@@ -26,9 +26,9 @@
void render_item_init(void);
void render_item_update_light(uint8_t light);
-void render_item_flat(struct item* item, struct item_data* stack, mat4 model,
+void render_item_flat(struct item* item, struct item_data* stack, mat4 view,
bool fullbright, enum render_item_env env);
-void render_item_block(struct item* item, struct item_data* stack, mat4 model,
+void render_item_block(struct item* item, struct item_data* stack, mat4 view,
bool fullbright, enum render_item_env env);
#endif
diff --git a/source/item/inventory.c b/source/item/inventory.c
index dab1209..c826852 100644
--- a/source/item/inventory.c
+++ b/source/item/inventory.c
@@ -85,7 +85,7 @@ void inventory_consume(struct inventory* inv, size_t slot) {
#define min(a, b) ((a) < (b) ? (a) : (b))
bool inventory_collect(struct inventory* inv, struct item_data* item,
- size_t slot_start, size_t slot_length, bool* mask) {
+ uint8_t* slot_priority, size_t slot_length, bool* mask) {
assert(inv && item && item->id != 0 && mask);
struct item* it = item_get(item);
@@ -99,18 +99,20 @@ bool inventory_collect(struct inventory* inv, struct item_data* item,
bool has_canidate_empty = false;
size_t candidate_empty = 0;
- for(size_t k = slot_start; k < slot_start + slot_length; k++) {
- if(inv->items[k].id == item->id
- && inv->items[k].durability == item->durability
- && inv->items[k].count < it->max_stack) {
+ for(size_t k = 0; k < slot_length; k++) {
+ uint8_t slot = slot_priority[k];
+
+ if(inv->items[slot].id == item->id
+ && inv->items[slot].durability == item->durability
+ && inv->items[slot].count < it->max_stack) {
has_canidate_equal = true;
- candidate_equal = k;
+ candidate_equal = slot;
break;
}
- if(!has_canidate_empty && inv->items[k].id == 0) {
+ if(!has_canidate_empty && inv->items[slot].id == 0) {
has_canidate_empty = true;
- candidate_empty = k;
+ candidate_empty = slot;
}
}
@@ -132,6 +134,20 @@ bool inventory_collect(struct inventory* inv, struct item_data* item,
return true;
}
+bool inventory_collect_inventory(struct inventory* inv, struct item_data* item,
+ bool* mask) {
+ uint8_t priorities[INVENTORY_SIZE_HOTBAR + INVENTORY_SIZE_MAIN];
+
+ for(size_t k = 0; k < INVENTORY_SIZE_HOTBAR; k++)
+ priorities[k] = k + INVENTORY_SLOT_HOTBAR;
+
+ for(size_t k = 0; k < INVENTORY_SIZE_MAIN; k++)
+ priorities[k + INVENTORY_SIZE_HOTBAR] = k + INVENTORY_SLOT_MAIN;
+
+ return inventory_collect(inv, item, priorities,
+ sizeof(priorities) / sizeof(*priorities), mask);
+}
+
size_t inventory_get_hotbar(struct inventory* inv) {
assert(inv);
return inv->hotbar_slot;
diff --git a/source/item/inventory.h b/source/item/inventory.h
index c4f3cd6..05d84dc 100644
--- a/source/item/inventory.h
+++ b/source/item/inventory.h
@@ -59,7 +59,9 @@ void inventory_destroy(struct inventory* inv);
void inventory_clear(struct inventory* inv);
void inventory_consume(struct inventory* inv, size_t slot);
bool inventory_collect(struct inventory* inv, struct item_data* item,
- size_t slot_start, size_t slot_length, bool* mask);
+ uint8_t* slot_priority, size_t slot_length, bool* mask);
+bool inventory_collect_inventory(struct inventory* inv, struct item_data* item,
+ bool* mask);
size_t inventory_get_hotbar(struct inventory* inv);
void inventory_set_hotbar(struct inventory* inv, size_t slot);
bool inventory_get_hotbar_item(struct inventory* inv, struct item_data* item);
diff --git a/source/main.c b/source/main.c
index f66ceee..c2dcdda 100644
--- a/source/main.c
+++ b/source/main.c
@@ -82,6 +82,9 @@ int main(void) {
chunk_mesher_init();
particle_init();
+ dict_entity_init(gstate.entities);
+ gstate.local_player = NULL;
+
struct server_local server;
server_local_create(&server);
@@ -109,8 +112,13 @@ int main(void) {
last_tick = time_add_ms(last_tick, 50);
tick_delta -= 1.0F;
particle_update();
+ entities_client_tick(gstate.entities);
}
+ if(gstate.local_player)
+ camera_attach(&gstate.camera, gstate.local_player, tick_delta,
+ gstate.stats.dt);
+
bool render_world
= gstate.current_screen->render_world && gstate.world_loaded;
@@ -171,11 +179,6 @@ int main(void) {
gstate.stats.chunks_rendered
= world_render(&gstate.world, &gstate.camera, false);
-
- particle_render(
- gstate.camera.view,
- (vec3) {gstate.camera.x, gstate.camera.y, gstate.camera.z},
- tick_delta);
} else {
gstate.stats.chunks_rendered = 0;
}
@@ -187,6 +190,14 @@ int main(void) {
}
if(render_world) {
+ gfx_fog(false);
+ particle_render(
+ gstate.camera.view,
+ (vec3) {gstate.camera.x, gstate.camera.y, gstate.camera.z},
+ tick_delta);
+ entities_client_render(gstate.entities, &gstate.camera, tick_delta);
+ gfx_fog(true);
+
world_render(&gstate.world, &gstate.camera, true);
if(gstate.world.dimension == WORLD_DIM_OVERWORLD)
diff --git a/source/network/client_interface.c b/source/network/client_interface.c
index 761de78..dd5334b 100644
--- a/source/network/client_interface.c
+++ b/source/network/client_interface.c
@@ -94,12 +94,15 @@ void clin_process(struct client_rpc* call) {
call->payload.unload_chunk.z);
break;
case CRPC_PLAYER_POS:
- gstate.camera.x = call->payload.player_pos.position[0];
- gstate.camera.y = call->payload.player_pos.position[1];
- gstate.camera.z = call->payload.player_pos.position[2];
gstate.camera.rx = glm_rad(-call->payload.player_pos.rotation[0]);
gstate.camera.ry = glm_rad(glm_clamp(
call->payload.player_pos.rotation[1] + 90.0F, 0.0F, 180.0F));
+ if(gstate.local_player)
+ gstate.local_player->teleport(
+ gstate.local_player,
+ (vec3) {call->payload.player_pos.position[0],
+ call->payload.player_pos.position[1],
+ call->payload.player_pos.position[2]});
gstate.world_loaded = true;
break;
case CRPC_WORLD_RESET:
@@ -113,6 +116,8 @@ void clin_process(struct client_rpc* call) {
}
}
+ dict_entity_reset(gstate.entities);
+
gstate.windows[WINDOWC_INVENTORY]
= malloc(sizeof(struct window_container));
assert(gstate.windows[WINDOWC_INVENTORY]);
@@ -122,6 +127,11 @@ void clin_process(struct client_rpc* call) {
gstate.world_loaded = false;
gstate.world.dimension = call->payload.world_reset.dimension;
+ gstate.local_player = dict_entity_safe_get(
+ gstate.entities, call->payload.world_reset.local_entity);
+ entity_local_player(call->payload.world_reset.local_entity,
+ gstate.local_player, &gstate.world);
+
if(gstate.current_screen == &screen_ingame)
screen_set(&screen_load_world);
break;
@@ -178,6 +188,36 @@ void clin_process(struct client_rpc* call) {
call->payload.set_block.block, true);
break;
+ case CRPC_SPAWN_ITEM: {
+ struct entity* e = dict_entity_safe_get(
+ gstate.entities, call->payload.spawn_item.entity_id);
+ entity_item(call->payload.spawn_item.entity_id, e, false,
+ &gstate.world, call->payload.spawn_item.item);
+ e->teleport(e, call->payload.spawn_item.pos);
+ } break;
+ case CRPC_PICKUP_ITEM: {
+ if(gstate.local_player
+ && call->payload.pickup_item.collector_id
+ == gstate.local_player->id) {
+ struct entity* e = dict_entity_get(
+ gstate.entities, call->payload.pickup_item.entity_id);
+ if(e)
+ glm_vec3_copy((vec3) {gstate.camera.x,
+ gstate.camera.y - 0.2F,
+ gstate.camera.z},
+ e->network_pos);
+ }
+ } break;
+ case CRPC_ENTITY_DESTROY:
+ dict_entity_erase(gstate.entities,
+ call->payload.entity_destroy.entity_id);
+ break;
+ case CRPC_ENTITY_MOVE: {
+ struct entity* e = dict_entity_get(
+ gstate.entities, call->payload.entity_move.entity_id);
+ if(e)
+ glm_vec3_copy(call->payload.entity_move.pos, e->network_pos);
+ } break;
}
}
diff --git a/source/network/client_interface.h b/source/network/client_interface.h
index e7f1d64..992aa78 100644
--- a/source/network/client_interface.h
+++ b/source/network/client_interface.h
@@ -34,6 +34,10 @@ enum client_rpc_type {
CRPC_WORLD_RESET,
CRPC_SET_BLOCK,
CRPC_WINDOW_TRANSACTION,
+ CRPC_SPAWN_ITEM,
+ CRPC_PICKUP_ITEM,
+ CRPC_ENTITY_DESTROY,
+ CRPC_ENTITY_MOVE,
};
struct client_rpc {
@@ -62,6 +66,7 @@ struct client_rpc {
uint64_t time_set;
struct {
enum world_dim dimension;
+ uint32_t local_entity;
} world_reset;
struct {
w_coord_t x, y, z;
@@ -72,6 +77,22 @@ struct client_rpc {
uint16_t action_id;
bool accepted;
} window_transaction;
+ struct {
+ uint32_t entity_id;
+ struct item_data item;
+ vec3 pos;
+ } spawn_item;
+ struct {
+ uint32_t entity_id;
+ uint32_t collector_id;
+ } pickup_item;
+ struct {
+ uint32_t entity_id;
+ } entity_destroy;
+ struct {
+ uint32_t entity_id;
+ vec3 pos;
+ } entity_move;
} payload;
};
diff --git a/source/network/server_local.c b/source/network/server_local.c
index cb664f5..dbe3f6f 100644
--- a/source/network/server_local.c
+++ b/source/network/server_local.c
@@ -32,6 +32,31 @@
#define CHUNK_DIST2(x1, x2, z1, z2) \
(((x1) - (x2)) * ((x1) - (x2)) + ((z1) - (z2)) * ((z1) - (z2)))
+static struct entity* spawn_item(vec3 pos, struct item_data* it, bool throw,
+ struct server_local* s) {
+ uint32_t entity_id = entity_gen_id(s->entities);
+ struct entity* e = dict_entity_safe_get(s->entities, entity_id);
+ entity_item(entity_id, e, true, &s->world, *it);
+ e->teleport(e, pos);
+
+ if(throw) {
+ float rx = glm_rad(-s->player.rx);
+ float ry = glm_rad(s->player.ry + 90.0F);
+ e->vel[0] = sinf(rx) * sinf(ry) * 0.25F;
+ e->vel[1] = cosf(ry) * 0.25F;
+ e->vel[2] = cosf(rx) * sinf(ry) * 0.25F;
+ }
+
+ clin_rpc_send(&(struct client_rpc) {
+ .type = CRPC_SPAWN_ITEM,
+ .payload.spawn_item.entity_id = e->id,
+ .payload.spawn_item.item = e->data.item.item,
+ .payload.spawn_item.pos = {e->pos[0], e->pos[1], e->pos[2]},
+ });
+
+ return e;
+}
+
static void server_local_process(struct server_rpc* call, void* user) {
assert(call && user);
@@ -92,10 +117,10 @@ static void server_local_process(struct server_rpc* call, void* user) {
if(item.id != 0) {
inventory_clear_slot(&s->player.inventory, k);
- inventory_collect(&s->player.inventory, &item,
- INVENTORY_SLOT_MAIN,
- INVENTORY_SIZE_MAIN, slots_changed);
slots_changed[k] = true;
+ spawn_item(
+ (vec3) {s->player.x, s->player.y, s->player.z},
+ &item, true, s);
}
}
@@ -116,46 +141,25 @@ static void server_local_process(struct server_rpc* call, void* user) {
&& call->payload.block_dig.y < WORLD_HEIGHT
&& call->payload.block_dig.finished) {
struct block_data blk;
- bool slots_changed[INVENTORY_SIZE];
- memset(slots_changed, false, sizeof(slots_changed));
-
if(server_world_get_block(&s->world, call->payload.block_dig.x,
call->payload.block_dig.y,
call->payload.block_dig.z, &blk)) {
- struct item_data drop = (struct item_data) {
- .id = blk.type,
- .durability = blk.metadata,
- .count = 1,
- };
+ server_world_set_block(&s->world, call->payload.block_dig.x,
+ call->payload.block_dig.y,
+ call->payload.block_dig.z,
+ (struct block_data) {
+ .type = BLOCK_AIR,
+ .metadata = 0,
+ });
- // TODO: correct priority
- inventory_collect(&s->player.inventory, &drop,
- INVENTORY_SLOT_HOTBAR,
- INVENTORY_SIZE_HOTBAR, slots_changed);
- inventory_collect(&s->player.inventory, &drop,
- INVENTORY_SLOT_MAIN, INVENTORY_SIZE_MAIN,
- slots_changed);
-
- for(size_t k = 0; k < INVENTORY_SIZE; k++) {
- if(slots_changed[k])
- clin_rpc_send(&(struct client_rpc) {
- .type = CRPC_INVENTORY_SLOT,
- .payload.inventory_slot.window
- = WINDOWC_INVENTORY,
- .payload.inventory_slot.slot = k,
- .payload.inventory_slot.item
- = s->player.inventory.items[k],
- });
- }
+ spawn_item((vec3) {call->payload.block_dig.x + 0.5F,
+ call->payload.block_dig.y + 0.5F,
+ call->payload.block_dig.z + 0.5F},
+ &(struct item_data) {.id = blk.type,
+ .durability = blk.metadata,
+ .count = 1},
+ false, s);
}
-
- server_world_set_block(&s->world, call->payload.block_dig.x,
- call->payload.block_dig.y,
- call->payload.block_dig.z,
- (struct block_data) {
- .type = BLOCK_AIR,
- .metadata = 0,
- });
}
break;
case SRPC_BLOCK_PLACE:
@@ -222,7 +226,8 @@ static void server_local_process(struct server_rpc* call, void* user) {
// save chunks here, then destroy all
clin_rpc_send(&(struct client_rpc) {
.type = CRPC_WORLD_RESET,
- .payload.world_reset.dimension = WORLD_DIM_OVERWORLD,
+ .payload.world_reset.dimension = s->player.dimension,
+ .payload.world_reset.local_entity = 0,
});
level_archive_write_player(
@@ -231,6 +236,7 @@ static void server_local_process(struct server_rpc* call, void* user) {
level_archive_write_inventory(&s->level, &s->player.inventory);
+ dict_entity_reset(s->entities);
server_world_destroy(&s->world);
level_archive_destroy(&s->level);
@@ -262,9 +268,12 @@ static void server_local_process(struct server_rpc* call, void* user) {
if(level_archive_read(&s->level, LEVEL_TIME, &s->world_time, 0))
s->world_time_start = time_get();
+ dict_entity_reset(s->entities);
+
clin_rpc_send(&(struct client_rpc) {
.type = CRPC_WORLD_RESET,
.payload.world_reset.dimension = dim,
+ .payload.world_reset.local_entity = 0,
});
}
break;
@@ -279,6 +288,37 @@ static void server_local_update(struct server_local* s) {
if(!s->player.has_pos)
return;
+ dict_entity_it_t it;
+ dict_entity_it(it, s->entities);
+
+ while(!dict_entity_end_p(it)) {
+ uint32_t key = dict_entity_ref(it)->key;
+ struct entity* e = &dict_entity_ref(it)->value;
+
+ if(e->tick_server) {
+ bool remove = (e->delay_destroy == 0) || e->tick_server(e, s);
+ dict_entity_next(it);
+
+ if(remove) {
+ clin_rpc_send(&(struct client_rpc) {
+ .type = CRPC_ENTITY_DESTROY,
+ .payload.entity_destroy.entity_id = key,
+ });
+
+ dict_entity_erase(s->entities, key);
+ } else if(e->delay_destroy < 0) {
+ clin_rpc_send(&(struct client_rpc) {
+ .type = CRPC_ENTITY_MOVE,
+ .payload.entity_move.entity_id = key,
+ .payload.entity_move.pos
+ = {e->pos[0], e->pos[1], e->pos[2]},
+ });
+ }
+ } else {
+ dict_entity_next(it);
+ }
+ }
+
w_coord_t px = WCOORD_CHUNK_OFFSET(floor(s->player.x));
w_coord_t pz = WCOORD_CHUNK_OFFSET(floor(s->player.z));
@@ -392,6 +432,7 @@ void server_local_create(struct server_local* s) {
s->player.finished_loading = false;
string_init(s->level_name);
inventory_create(&s->player.inventory, INVENTORY_SIZE);
+ dict_entity_init(s->entities);
struct thread t;
thread_create(&t, server_local_thread, s, 8);
diff --git a/source/network/server_local.h b/source/network/server_local.h
index 8c48f30..8e3c08b 100644
--- a/source/network/server_local.h
+++ b/source/network/server_local.h
@@ -43,6 +43,7 @@ struct server_local {
struct inventory inventory;
} player;
struct server_world world;
+ dict_entity_t entities;
uint64_t world_time;
ptime_t world_time_start;
string_t level_name;
diff --git a/source/platform/pc/texture.c b/source/platform/pc/texture.c
index 17418ab..d80d08e 100644
--- a/source/platform/pc/texture.c
+++ b/source/platform/pc/texture.c
@@ -42,8 +42,8 @@ void tex_gfx_load(struct tex_gfx* tex, void* img, size_t width, size_t height,
linear ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
linear ? GL_LINEAR : GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glBindTexture(GL_TEXTURE_2D, 0);
}
diff --git a/source/platform/texture.c b/source/platform/texture.c
index fab1a11..cb2741f 100644
--- a/source/platform/texture.c
+++ b/source/platform/texture.c
@@ -39,6 +39,7 @@ struct tex_gfx texture_pointer;
struct tex_gfx texture_clouds;
struct tex_gfx texture_sun;
struct tex_gfx texture_moon;
+struct tex_gfx texture_shadow;
struct tex_gfx texture_mob_char;
@@ -94,6 +95,7 @@ void tex_init() {
false);
tex_gfx_load_file(&texture_sun, "terrain/sun.png", TEX_FMT_RGB16, false);
tex_gfx_load_file(&texture_moon, "terrain/moon.png", TEX_FMT_RGB16, false);
+ tex_gfx_load_file(&texture_shadow, "misc/shadow.png", TEX_FMT_IA4, false);
tex_gfx_load_file(&texture_armor_chain1, "armor/chain_1.png",
TEX_FMT_RGBA16, false);
diff --git a/source/platform/texture.h b/source/platform/texture.h
index 0b627f4..6860a9b 100644
--- a/source/platform/texture.h
+++ b/source/platform/texture.h
@@ -64,6 +64,7 @@ extern struct tex_gfx texture_pointer;
extern struct tex_gfx texture_clouds;
extern struct tex_gfx texture_sun;
extern struct tex_gfx texture_moon;
+extern struct tex_gfx texture_shadow;
extern struct tex_gfx texture_mob_char;
diff --git a/source/world.c b/source/world.c
index b5dfd11..0dc6c6b 100644
--- a/source/world.c
+++ b/source/world.c
@@ -548,9 +548,10 @@ size_t world_render(struct world* w, struct camera* c, bool pass) {
bool world_aabb_intersection(struct world* w, struct AABB* a) {
assert(w && a);
- w_coord_t min_x = floorf(a->x1) - 1;
+ w_coord_t min_x = floorf(a->x1);
+ // need to look one further, otherwise fence block breaks
w_coord_t min_y = floorf(a->y1) - 1;
- w_coord_t min_z = floorf(a->z1) - 1;
+ w_coord_t min_z = floorf(a->z1);
w_coord_t max_x = ceilf(a->x2) + 1;
w_coord_t max_y = ceilf(a->y2) + 1;
diff --git a/source/world.h b/source/world.h
index a432c48..6e40bac 100644
--- a/source/world.h
+++ b/source/world.h
@@ -48,7 +48,8 @@ struct world_section {
struct chunk* column[COLUMN_HEIGHT];
};
-#define SECTION_TO_ID(x, z) (((int64_t)(z) << 32) | (((int64_t)(x)&0xFFFFFFFF)))
+#define SECTION_TO_ID(x, z) \
+ (((int64_t)(z) << 32) | (((int64_t)(x) & 0xFFFFFFFF)))
DICT_DEF2(dict_wsection, int64_t, M_BASIC_OPLIST, struct world_section,
M_POD_OPLIST)