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)