VoxelEngine/src/objects/Player.cpp
2024-12-17 19:40:00 +03:00

373 lines
9.3 KiB
C++

#include "Player.hpp"
#include <algorithm>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <utility>
#include "content/ContentReport.hpp"
#include "items/Inventory.hpp"
#include "Entities.hpp"
#include "rigging.hpp"
#include "physics/Hitbox.hpp"
#include "physics/PhysicsSolver.hpp"
#include "voxels/Chunks.hpp"
#include "window/Camera.hpp"
#include "window/Events.hpp"
#include "world/Level.hpp"
#include "data/dv_util.hpp"
constexpr float CROUCH_SPEED_MUL = 0.35f;
constexpr float RUN_SPEED_MUL = 1.5f;
constexpr float PLAYER_GROUND_DAMPING = 10.0f;
constexpr float PLAYER_AIR_DAMPING = 8.0f;
constexpr float FLIGHT_SPEED_MUL = 4.0f;
constexpr float CHEAT_SPEED_MUL = 5.0f;
constexpr float JUMP_FORCE = 8.0f;
constexpr int SPAWN_ATTEMPTS_PER_UPDATE = 64;
Player::Player(
Level* level,
int64_t id,
const std::string& name,
glm::vec3 position,
float speed,
std::shared_ptr<Inventory> inv,
entityid_t eid
)
: level(level),
id(id),
name(name),
speed(speed),
chosenSlot(0),
position(position),
inventory(std::move(inv)),
eid(eid),
chunks(std::make_unique<Chunks>(
3, 3, 0, 0, level->events.get(), level->content->getIndices()
)),
fpCamera(level->getCamera("core:first-person")),
spCamera(level->getCamera("core:third-person-front")),
tpCamera(level->getCamera("core:third-person-back")),
currentCamera(fpCamera) {
fpCamera->setFov(glm::radians(90.0f));
spCamera->setFov(glm::radians(90.0f));
tpCamera->setFov(glm::radians(90.0f));
}
Player::~Player() = default;
void Player::updateEntity() {
if (eid == 0) {
auto& def = level->content->entities.require("base:player");
eid = level->entities->spawn(def, getPosition());
} else if (auto entity = level->entities->get(eid)) {
position = entity->getTransform().pos;
} else {
// TODO: check if chunk loaded
}
}
Hitbox* Player::getHitbox() {
if (auto entity = level->entities->get(eid)) {
return &entity->getRigidbody().hitbox;
}
return nullptr;
}
void Player::updateInput(PlayerInput& input, float delta) {
auto hitbox = getHitbox();
if (hitbox == nullptr) {
return;
}
bool crouch = input.shift && hitbox->grounded && !input.sprint;
float speed = this->speed;
if (flight) {
speed *= FLIGHT_SPEED_MUL;
}
if (input.cheat) {
speed *= CHEAT_SPEED_MUL;
}
hitbox->crouching = crouch;
if (crouch) {
speed *= CROUCH_SPEED_MUL;
} else if (input.sprint) {
speed *= RUN_SPEED_MUL;
}
glm::vec3 dir(0, 0, 0);
if (input.moveForward) {
dir += fpCamera->dir;
}
if (input.moveBack) {
dir -= fpCamera->dir;
}
if (input.moveRight) {
dir += fpCamera->right;
}
if (input.moveLeft) {
dir -= fpCamera->right;
}
if (glm::length(dir) > 0.0f) {
dir = glm::normalize(dir);
hitbox->velocity += dir * speed * delta * 9.0f;
}
hitbox->linearDamping = PLAYER_GROUND_DAMPING;
hitbox->verticalDamping = flight;
hitbox->gravityScale = flight ? 0.0f : 1.0f;
if (flight) {
hitbox->linearDamping = PLAYER_AIR_DAMPING;
if (input.jump) {
hitbox->velocity.y += speed * delta * 9;
}
if (input.shift) {
hitbox->velocity.y -= speed * delta * 9;
}
}
if (!hitbox->grounded) {
hitbox->linearDamping = PLAYER_AIR_DAMPING;
}
if (input.jump && hitbox->grounded) {
hitbox->velocity.y = JUMP_FORCE;
}
if ((input.flight && !noclip) || (input.noclip && flight == noclip)) {
flight = !flight;
if (flight) {
hitbox->velocity.y += 1.0f;
}
}
hitbox->type = noclip ? BodyType::KINEMATIC : BodyType::DYNAMIC;
if (input.noclip) {
noclip = !noclip;
}
input.noclip = false;
input.flight = false;
}
void Player::updateSelectedEntity() {
selectedEid = selection.entity;
}
void Player::postUpdate() {
auto entity = level->entities->get(eid);
if (!entity.has_value()) {
return;
}
auto& hitbox = entity->getRigidbody().hitbox;
position = hitbox.position;
if (flight && hitbox.grounded && !noclip) {
flight = false;
}
if (spawnpoint.y <= 0.1) {
for (int i = 0; i < SPAWN_ATTEMPTS_PER_UPDATE; i++) {
attemptToFindSpawnpoint();
}
}
auto& skeleton = entity->getSkeleton();
skeleton.visible = currentCamera != fpCamera;
auto body = skeleton.config->find("body");
auto head = skeleton.config->find("head");
if (body) {
skeleton.pose.matrices[body->getIndex()] = glm::rotate(
glm::mat4(1.0f), glm::radians(cam.x), glm::vec3(0, 1, 0)
);
}
if (head) {
skeleton.pose.matrices[head->getIndex()] = glm::rotate(
glm::mat4(1.0f), glm::radians(cam.y), glm::vec3(1, 0, 0)
);
}
}
void Player::teleport(glm::vec3 position) {
this->position = position;
if (auto entity = level->entities->get(eid)) {
entity->getRigidbody().hitbox.position = position;
entity->getTransform().setPos(position);
}
}
void Player::attemptToFindSpawnpoint() {
glm::vec3 newpos(
position.x + (rand() % 200 - 100),
rand() % 80 + 100,
position.z + (rand() % 200 - 100)
);
while (newpos.y > 0 &&
!chunks->isObstacleBlock(newpos.x, newpos.y - 2, newpos.z)) {
newpos.y--;
}
voxel* headvox = chunks->get(newpos.x, newpos.y + 1, newpos.z);
if (chunks->isObstacleBlock(newpos.x, newpos.y, newpos.z) ||
headvox == nullptr || headvox->id != 0) {
return;
}
spawnpoint = newpos + glm::vec3(0.5f, 0.0f, 0.5f);
teleport(spawnpoint);
}
void Player::setChosenSlot(int index) {
chosenSlot = index;
}
int Player::getChosenSlot() const {
return chosenSlot;
}
float Player::getSpeed() const {
return speed;
}
bool Player::isFlight() const {
return flight;
}
void Player::setFlight(bool flag) {
this->flight = flag;
}
bool Player::isNoclip() const {
return noclip;
}
void Player::setNoclip(bool flag) {
this->noclip = flag;
}
bool Player::isInfiniteItems() const {
return infiniteItems;
}
void Player::setInfiniteItems(bool flag) {
infiniteItems = flag;
}
bool Player::isInstantDestruction() const {
return instantDestruction;
}
void Player::setInstantDestruction(bool flag) {
instantDestruction = flag;
}
entityid_t Player::getEntity() const {
return eid;
}
void Player::setEntity(entityid_t eid) {
this->eid = eid;
}
entityid_t Player::getSelectedEntity() const {
return selectedEid;
}
void Player::setName(const std::string& name) {
this->name = name;
}
const std::string& Player::getName() const {
return name;
}
const std::shared_ptr<Inventory>& Player::getInventory() const {
return inventory;
}
void Player::setSpawnPoint(glm::vec3 spawnpoint) {
this->spawnpoint = spawnpoint;
}
glm::vec3 Player::getSpawnPoint() const {
return spawnpoint;
}
dv::value Player::serialize() const {
auto root = dv::object();
root["id"] = id;
root["name"] = name;
root["position"] = dv::to_value(position);
root["rotation"] = dv::to_value(cam);
root["spawnpoint"] = dv::to_value(spawnpoint);
root["flight"] = flight;
root["noclip"] = noclip;
root["infinite-items"] = infiniteItems;
root["instant-destruction"] = instantDestruction;
root["chosen-slot"] = chosenSlot;
root["entity"] = eid;
root["inventory"] = inventory->serialize();
auto found =
std::find(level->cameras.begin(), level->cameras.end(), currentCamera);
if (found != level->cameras.end()) {
root["camera"] = level->content->getIndices(ResourceType::CAMERA)
.getName(found - level->cameras.begin());
}
return root;
}
void Player::deserialize(const dv::value& src) {
src.at("id").get(id);
src.at("name").get(name);
const auto& posarr = src["position"];
dv::get_vec(posarr, position);
fpCamera->position = position;
const auto& rotarr = src["rotation"];
dv::get_vec(rotarr, cam);
const auto& sparr = src["spawnpoint"];
setSpawnPoint(glm::vec3(
sparr[0].asNumber(), sparr[1].asNumber(), sparr[2].asNumber()));
flight = src["flight"].asBoolean();
noclip = src["noclip"].asBoolean();
src.at("infinite-items").get(infiniteItems);
src.at("instant-destruction").get(instantDestruction);
setChosenSlot(src["chosen-slot"].asInteger());
eid = src["entity"].asNumber();
if (src.has("inventory")) {
getInventory()->deserialize(src["inventory"]);
}
if (src.has("camera")) {
std::string name = src["camera"].asString();
if (auto camera = level->getCamera(name)) {
currentCamera = camera;
}
}
}
void Player::convert(dv::value& data, const ContentReport* report) {
if (data.has("players")) {
auto& players = data["players"];
for (uint i = 0; i < players.size(); i++) {
auto& playerData = players[i];
if (playerData.has("inventory")) {
Inventory::convert(playerData["inventory"], report);
}
}
} else if (data.has("inventory")){
Inventory::convert(data["inventory"], report);
}
}