#include <stdio.h>
-#include "environment.h"
+#include <stdlib.h>
#include "client/camera.h"
#include "client/client.h"
-#include "client/client_map.h"
+#include "client/client_inventory.h"
#include "client/client_player.h"
+#include "client/client_terrain.h"
#include "client/cube.h"
#include "client/debug_menu.h"
#include "client/texture.h"
+#include "environment.h"
+#include "physics.h"
struct ClientPlayer client_player;
-// to be called whenever the player position changes
-// rwlock has to be read or write locked
+static ClientEntity *player_entity;
+static pthread_rwlock_t lock_player_entity;
+
+static Model *player_model;
+
+// updat epos/rot box/eye functions
+
+static void update_camera()
+{
+ vec4 dst, src = {0.0f, 0.0f, 0.0f, 1.0f};
+
+ ClientPlayerData *data = player_entity->extra;
+
+ if (data->bones.eyes)
+ mat4x4_mul_vec4(dst, data->bones.eyes->abs, src);
+ else
+ vec4_dup(dst, src);
+
+ camera_set_position((v3f32) {dst[0], dst[1], dst[2]});
+}
+
static void update_pos()
{
- camera_set_position((v3f32) {client_player.pos.x, client_player.pos.y + client_player.eye_height, client_player.pos.z});
- dragonnet_peer_send_ToServerPos(client, &(ToServerPos) {
- .pos = client_player.pos,
+ debug_menu_changed(ENTRY_POS);
+ debug_menu_changed(ENTRY_HUMIDITY);
+ debug_menu_changed(ENTRY_TEMPERATURE);
+}
+
+static void update_rot()
+{
+ camera_set_angle(M_PI / 2 - player_entity->data.rot.y, -player_entity->data.rot.x);
+ debug_menu_changed(ENTRY_YAW);
+ debug_menu_changed(ENTRY_PITCH);
+}
+
+static void update_transform()
+{
+ client_entity_transform(player_entity);
+ update_camera();
+}
+
+static void send_pos_rot()
+{
+ update_transform();
+
+ dragonnet_peer_send_ToServerPosRot(client, &(ToServerPosRot) {
+ .pos = player_entity->data.pos,
+ .rot = player_entity->data.rot,
});
+}
- client_player.obj->pos = (v3f32) {client_player.pos.x, client_player.pos.y, client_player.pos.z};
- object_transform(client_player.obj);
+static void recv_pos_rot()
+{
+ update_transform();
- debug_menu_update_pos();
- debug_menu_update_humidity();
- debug_menu_update_temperature();
+ update_pos();
+ update_rot();
}
-// get absolute player bounding box
-// rwlock has to be read- or write locked
-static aabb3f64 get_box()
+// entity callbacks
+
+static void on_add(ClientEntity *entity)
{
- return (aabb3f64) {
- {client_player.box.min.x + client_player.pos.x, client_player.box.min.y + client_player.pos.y, client_player.box.min.z + client_player.pos.z},
- {client_player.box.max.x + client_player.pos.x, client_player.box.max.y + client_player.pos.y, client_player.box.max.z + client_player.pos.z},
- };
+ entity->model = model_clone(player_model);
+ entity->model->extra = refcount_grb(&entity->rc);
+
+ ClientPlayerData *data = entity->extra = malloc(sizeof *data);
+ data->bones = (struct ClientPlayerBones) {NULL};
+
+ model_get_bones(entity->model, (ModelBoneMapping[9]) {
+ {"nametag", &data->bones.nametag },
+ {"neck", &data->bones.neck },
+ {"neck.head.eyes", &data->bones.eyes },
+ {"arm_left", &data->bones.arm_left },
+ {"arm_right", &data->bones.arm_right },
+ {"arm_left.hand", &data->bones.hand_left },
+ {"arm_right.hand", &data->bones.hand_right},
+ {"leg_left", &data->bones.leg_left },
+ {"leg_right", &data->bones.leg_right },
+ }, 9);
+
+ entity->nametag_offset = data->bones.nametag ? &data->bones.nametag->abs : NULL;
+ entity->box_collision = (aabb3f32) {{-0.45f, 0.0f, -0.45f}, {0.45f, 1.8f, 0.45f}};
+
+ client_inventory_init_player(entity);
+
+ model_scene_add(entity->model);
+ client_entity_transform(entity);
}
-// get absolute integer box that contains all nodes a float bounding box touches
-static aabb3s32 round_box(aabb3f64 box)
+static void on_remove(ClientEntity *entity)
{
- return (aabb3s32) {
- {floor(box.min.x + 0.5), floor(box.min.y + 0.5), floor(box.min.z + 0.5)},
- {ceil(box.max.x - 0.5), ceil(box.max.y - 0.5), ceil(box.max.z - 0.5)},
- };
+ entity->model->flags.delete = 1;
+ entity->model = NULL;
}
-// return true if node at x, y, z is solid (or unloaded)
-static bool is_solid(s32 x, s32 y, s32 z)
+static void on_free(ClientEntity *entity)
{
- Node node = map_get_node(client_map.map, (v3s32) {x, y, z}).type;
- return node == NODE_UNLOADED || node_definitions[node].solid;
+ client_inventory_init_player(entity);
+ free(entity->extra);
}
-// determine if player can jump currently (must be standing on a solid block)
-// rwlock has to be read- or write locked
-static bool can_jump()
+static void on_transform(ClientEntity *entity)
{
- if (client_player.velocity.y != 0.0)
- return false;
+ ClientPlayerData *data = entity->extra;
- aabb3f64 fbox = get_box();
- fbox.min.y -= 0.5;
+ entity->model->root->rot.x = entity->model->root->rot.z = 0.0f;
- aabb3s32 box = round_box(fbox);
+ if (data->bones.neck) {
+ data->bones.neck->rot.x = entity->data.rot.x;
+ model_node_transform(data->bones.neck);
+ }
+}
- if (fbox.min.y - (f64) box.min.y > 0.01)
- return false;
+static void local_on_add(ClientEntity *entity)
+{
+ pthread_rwlock_wrlock(&lock_player_entity);
+
+ if (player_entity) {
+ fprintf(stderr, "[error] attempt to re-add localplayer entity\n");
+ exit(EXIT_FAILURE);
+ }
- for (s32 x = box.min.x; x <= box.max.x; x++)
- for (s32 z = box.min.z; z <= box.max.z; z++)
- if (is_solid(x, box.min.y, z))
- return true;
+ on_add(entity);
- return false;
+ player_entity = refcount_grb(&entity->rc);
+ recv_pos_rot();
+
+ entity->type->update_nametag(entity);
+
+ pthread_rwlock_unlock(&lock_player_entity);
}
-// ClientPlayer singleton constructor
-void client_player_init()
+static void local_on_remove(ClientEntity *entity)
{
- client_player.pos = (v3f64) {0.0, 0.0, 0.0};
- client_player.velocity = (v3f64) {0.0, 0.0, 0.0};
- client_player.box = (aabb3f64) {{-0.3, 0.0, -0.3}, {0.3, 1.75, 0.3}};
- client_player.yaw = client_player.pitch = 0.0f;
- client_player.eye_height = 1.5;
- client_player.fly = false;
- client_player.collision = true;
- pthread_rwlock_init(&client_player.rwlock, NULL);
+ pthread_rwlock_wrlock(&lock_player_entity);
+ refcount_drp(&entity->rc);
+ player_entity = NULL;
+ pthread_rwlock_unlock(&lock_player_entity);
+
+ on_remove(entity);
}
-// ClientPlayer singleton destructor
-void client_player_deinit()
+static void local_on_update_pos_rot(__attribute__((unused)) ClientEntity *entity)
{
- pthread_rwlock_destroy(&client_player.rwlock);
+ recv_pos_rot();
}
-// create mesh object and info hud
-void client_player_add_to_scene()
+static void local_on_update_nametag(ClientEntity *entity)
{
- client_player.obj = object_create();
- client_player.obj->scale = (v3f32) {0.6, 1.75, 0.6};
- client_player.obj->visible = false;
+ if (entity->data.nametag) {
+ free(entity->data.nametag);
+ entity->data.nametag = NULL;
+ }
+}
- object_set_texture(client_player.obj, texture_load(RESSOURCE_PATH "textures/player.png", true));
+static void __attribute__((unused)) on_model_step(Model *model, __attribute__((unused)) f64 dtime)
+{
+ ClientPlayerData *data = ((ClientEntity *) model->extra)->extra;
+ (void) data; // ToDo: animations
+}
- for (int f = 0; f < 6; f++) {
- for (int v = 0; v < 6; v++) {
- Vertex3D vertex = cube_vertices[f][v];
- vertex.position.y += 0.5;
- object_add_vertex(client_player.obj, &vertex);
- }
- }
+static void on_model_delete(Model *model)
+{
+ if (model->extra)
+ refcount_drp(&((ClientEntity *) model->extra)->rc);
+}
- pthread_rwlock_rdlock(&client_player.rwlock);
- update_pos();
- pthread_rwlock_unlock(&client_player.rwlock);
+// called on startup
+void client_player_init()
+{
+ client_player.movement = (ToClientMovement) {
+ .flight = false,
+ .collision = true,
+ .speed = 0.0f,
+ .jump = 0.0f,
+ .gravity = 0.0f,
+ };
+
+ client_entity_types[ENTITY_PLAYER] = (ClientEntityType) {
+ .add = &on_add,
+ .remove = &on_remove,
+ .free = &on_free,
+ .update_pos_rot = NULL,
+ .update_nametag = NULL,
+ .transform = &on_transform,
+ };
+
+ client_entity_types[ENTITY_LOCALPLAYER] = (ClientEntityType) {
+ .add = &local_on_add,
+ .remove = &local_on_remove,
+ .free = &on_free,
+ .update_pos_rot = &local_on_update_pos_rot,
+ .update_nametag = &local_on_update_nametag,
+ .transform = &on_transform,
+ };
+
+ pthread_rwlock_init(&client_player.lock_movement, NULL);
- debug_menu_update_yaw();
- debug_menu_update_pitch();
+ player_entity = NULL;
+ pthread_rwlock_init(&lock_player_entity, NULL);
}
-// jump if possible
-void client_player_jump()
+// called on shutdown
+void client_player_deinit()
{
- pthread_rwlock_wrlock(&client_player.rwlock);
- if (can_jump())
- client_player.velocity.y += 10.0;
- pthread_rwlock_unlock(&client_player.rwlock);
+ pthread_rwlock_destroy(&client_player.lock_movement);
+ pthread_rwlock_destroy(&lock_player_entity);
}
-// get position (thread-safe)
-v3f64 client_player_get_position()
+void client_player_gfx_init()
{
- v3f64 pos;
+ player_model = model_load(
+ RESSOURCE_PATH "models/player.txt", RESSOURCE_PATH "textures/models/player",
+ &client_entity_cube, &client_entity_shader);
- pthread_rwlock_rdlock(&client_player.rwlock);
- pos = client_player.pos;
- pthread_rwlock_unlock(&client_player.rwlock);
+ player_model->callbacks.step = &on_model_step;
+ player_model->callbacks.delete = &on_model_delete;
+}
- return pos;
+void client_player_gfx_deinit()
+{
+ model_delete(player_model);
}
-// set position (thread-safe)
-void client_player_set_position(v3f64 pos)
+ClientEntity *client_player_entity(u64 id)
{
- pthread_rwlock_rdlock(&client_player.rwlock);
- client_player.pos = pos;
- pthread_rwlock_unlock(&client_player.rwlock);
+ ClientEntity *entity = client_entity_grab(id);
+
+ if (entity->type == &client_entity_types[ENTITY_LOCALPLAYER]
+ || entity->type == &client_entity_types[ENTITY_PLAYER])
+ return entity;
+
+ refcount_drp(&entity->rc);
+ return NULL;
}
-// to be called every frame
-void client_player_tick(f64 dtime)
+ClientEntity *client_player_entity_local()
{
- pthread_rwlock_wrlock(&client_player.rwlock);
-
- v3f64 old_pos = client_player.pos;
- v3f64 old_velocity = client_player.velocity;
-
- if (! client_player.fly)
- client_player.velocity.y -= 32.0 * dtime;
-
-#define GETS(vec, comp) *(s32 *) ((char *) &vec + offsetof(v3s32, comp))
-#define GETF(vec, comp) *(f64 *) ((char *) &vec + offsetof(v3f64, comp))
-#define PHYSICS(a, b, c) { \
- f64 v = (GETF(client_player.velocity, a) + GETF(old_velocity, a)) / 2.0f; \
- if (v == 0.0) \
- goto a ## _physics_done; \
- aabb3s32 box = round_box(get_box()); \
- v3f64 old_pos = client_player.pos; \
- GETF(client_player.pos, a) += v * dtime; \
- if (! client_player.collision) \
- goto a ## _physics_done; \
- s32 dir; \
- f64 offset; \
- if (v > 0.0) { \
- dir = +1; \
- offset = GETF(client_player.box.max, a); \
- GETS(box.min, a) = ceil(GETF(old_pos, a) + offset + 0.5); \
- GETS(box.max, a) = floor(GETF(client_player.pos, a) + offset + 0.5); \
- } else { \
- dir = -1; \
- offset = GETF(client_player.box.min, a); \
- GETS(box.min, a) = floor(GETF(old_pos, a) + offset - 0.5); \
- GETS(box.max, a) = ceil(GETF(client_player.pos, a) + offset - 0.5); \
- } \
- GETS(box.max, a) += dir; \
- for (s32 a = GETS(box.min, a); a != GETS(box.max, a); a += dir) { \
- for (s32 b = GETS(box.min, b); b <= GETS(box.max, b); b++) { \
- for (s32 c = GETS(box.min, c); c <= GETS(box.max, c); c++) { \
- if (is_solid(x, y, z)) { \
- GETF(client_player.pos, a) = (f64) a - offset - 0.5 * (f64) dir; \
- GETF(client_player.velocity, a) = 0.0; \
- goto a ## _physics_done; \
- } \
- } \
- } \
- } \
- a ## _physics_done: (void) 0; \
- }
+ ClientEntity *entity = NULL;
- PHYSICS(x, y, z)
- PHYSICS(y, x, z)
- PHYSICS(z, x, y)
+ pthread_rwlock_rdlock(&lock_player_entity);
+ if (player_entity)
+ entity = refcount_grb(&player_entity->rc);
+ pthread_rwlock_unlock(&lock_player_entity);
-#undef GETS
-#undef GETF
-#undef PHYSICS
+ return entity;
+}
+
+void client_player_update_pos(ClientEntity *entity)
+{
+ pthread_rwlock_rdlock(&lock_player_entity);
- if (! v3f64_equals(old_pos, client_player.pos))
+ if (entity == player_entity) {
update_pos();
+ send_pos_rot();
+ }
+
+ pthread_rwlock_unlock(&lock_player_entity);
+}
+
+void client_player_update_rot(ClientEntity *entity)
+{
+ pthread_rwlock_rdlock(&lock_player_entity);
+
+ if (entity == player_entity) {
+ update_rot();
+ send_pos_rot();
+ }
- pthread_rwlock_unlock(&client_player.rwlock);
+ pthread_rwlock_unlock(&lock_player_entity);
+}
+
+// jump if possible
+void client_player_jump()
+{
+ ClientEntity *entity = client_player_entity_local();
+ if (!entity)
+ return;
+
+ pthread_rwlock_rdlock(&entity->lock_pos_rot);
+ pthread_rwlock_rdlock(&entity->lock_box_off);
+
+ if (physics_ground(
+ client_terrain,
+ client_player.movement.collision,
+ entity->box_collision,
+ &entity->data.pos,
+ &client_player.velocity
+ ))
+ client_player.velocity.y += client_player.movement.jump;
+
+ pthread_rwlock_unlock(&entity->lock_box_off);
+ pthread_rwlock_unlock(&entity->lock_pos_rot);
+
+ refcount_drp(&entity->rc);
+}
+
+// to be called every frame
+void client_player_tick(f64 dtime)
+{
+ ClientEntity *entity = client_player_entity_local();
+ if (!entity)
+ return;
+
+ pthread_rwlock_rdlock(&client_player.lock_movement);
+ pthread_rwlock_wrlock(&entity->lock_pos_rot);
+ pthread_rwlock_rdlock(&entity->lock_box_off);
+
+ if (physics_step(
+ client_terrain,
+ client_player.movement.collision,
+ entity->box_collision,
+ &entity->data.pos,
+ &client_player.velocity,
+ &(v3f64) {
+ 0.0,
+ client_player.movement.flight ? 0.0 : -client_player.movement.gravity,
+ 0.0,
+ },
+ dtime
+ ))
+ client_player_update_pos(entity);
+
+ pthread_rwlock_unlock(&entity->lock_box_off);
+ pthread_rwlock_unlock(&entity->lock_pos_rot);
+ pthread_rwlock_unlock(&client_player.lock_movement);
+
+ refcount_drp(&entity->rc);
}