From 9b1be68bcddaf78d6a601ab9f33e984e9d0d0d97 Mon Sep 17 00:00:00 2001 From: tovjemam Date: Sat, 21 Feb 2026 20:59:59 +0100 Subject: [PATCH] Sitting in vehicles --- CMakeLists.txt | 9 + src/game/controllable_character.cpp | 54 ++++ src/game/controllable_character.hpp | 32 ++ src/game/drivable_vehicle.cpp | 74 +++++ src/game/drivable_vehicle.hpp | 34 +++ src/game/entity.cpp | 2 - src/game/game.cpp | 6 +- src/game/game.hpp | 1 + src/game/npc_character.cpp | 246 ++++++++++++++++ src/game/npc_character.hpp | 44 +++ src/game/openworld.cpp | 441 +++------------------------- src/game/openworld.hpp | 5 + src/game/player_character.cpp | 111 +++++++ src/game/player_character.hpp | 34 +++ src/game/usable.hpp | 32 ++ src/game/vehicle.hpp | 2 + src/game/world.cpp | 10 + src/game/world.hpp | 1 + src/gameview/characterview.cpp | 10 + src/gameview/characterview.hpp | 1 + src/gameview/client_session.cpp | 11 +- src/gameview/entityview.cpp | 2 +- src/server/server.cpp | 2 + 23 files changed, 758 insertions(+), 406 deletions(-) create mode 100644 src/game/controllable_character.cpp create mode 100644 src/game/controllable_character.hpp create mode 100644 src/game/drivable_vehicle.cpp create mode 100644 src/game/drivable_vehicle.hpp create mode 100644 src/game/npc_character.cpp create mode 100644 src/game/npc_character.hpp create mode 100644 src/game/player_character.cpp create mode 100644 src/game/player_character.hpp create mode 100644 src/game/usable.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3529aa6..4a37ed7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,14 +112,23 @@ set(CLIENT_ONLY_SOURCES set(SERVER_ONLY_SOURCES "src/game/character.hpp" "src/game/character.cpp" + "src/game/controllable_character.hpp" + "src/game/controllable_character.cpp" + "src/game/drivable_vehicle.hpp" + "src/game/drivable_vehicle.cpp" "src/game/entity.hpp" "src/game/entity.cpp" "src/game/game.hpp" "src/game/game.cpp" + "src/game/npc_character.hpp" + "src/game/npc_character.cpp" "src/game/openworld.hpp" "src/game/openworld.cpp" + "src/game/player_character.hpp" + "src/game/player_character.cpp" "src/game/player.hpp" "src/game/player.cpp" + "src/game/usable.hpp" "src/game/vehicle.hpp" "src/game/vehicle.cpp" "src/game/world.hpp" diff --git a/src/game/controllable_character.cpp b/src/game/controllable_character.cpp new file mode 100644 index 0000000..7217284 --- /dev/null +++ b/src/game/controllable_character.cpp @@ -0,0 +1,54 @@ +#include "controllable_character.hpp" +#include "drivable_vehicle.hpp" + +game::ControllableCharacter::ControllableCharacter(World& world) : Character(world, CharacterInfo{}) {} + +void game::ControllableCharacter::SetVehicle(DrivableVehicle* vehicle, uint32_t seat) +{ + if ((vehicle && vehicle_) || (!vehicle && !vehicle_)) + return; + + if (vehicle) + { + if (!vehicle->SetPassenger(seat, this)) + return; + + auto seat_loc = vehicle->GetModel()->GetLocation("seat" + std::to_string(seat)); + if (seat_loc) + SetPosition(seat_loc->position); + + EnablePhysics(false); + Attach(vehicle->GetEntNum()); + SetMainAnim(seat == 0 ? "vehicle_drive" : "vehicle_passenger"); + SetYaw(0.5f * glm::pi()); + } + else + { + vehicle_->SetPassenger(seat_idx_, nullptr); + + EnablePhysics(true); + + glm::vec3 seat_loc = vehicle_->GetModel()->GetLocation("seat" + std::to_string(seat_idx_))->position; + seat_loc.x += glm::sign(seat_loc.x) * 0.5f; // to the side + glm::vec3 pos = vehicle_->GetRoot().matrix * glm::vec4(seat_loc, 1.0f); + pos.z += 0.5f; + SetPosition(pos); + + Attach(0); + SetMainAnim("idle"); + // SetYaw(0.0f); + } + + vehicle_ = vehicle; + seat_idx_ = seat; + is_driver_ = vehicle && seat == 0; + VehicleChanged(); +} + +game::ControllableCharacter::~ControllableCharacter() +{ + if (vehicle_) + { + vehicle_->SetPassenger(seat_idx_, nullptr); + } +} diff --git a/src/game/controllable_character.hpp b/src/game/controllable_character.hpp new file mode 100644 index 0000000..17de9e4 --- /dev/null +++ b/src/game/controllable_character.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "character.hpp" + +namespace game +{ + +class DrivableVehicle; + +class ControllableCharacter : public Character +{ +public: + using Super = Character; + + ControllableCharacter(World& world); + + void SetVehicle(DrivableVehicle* vehicle, uint32_t seat); + + DrivableVehicle* GetVehicle() const { return vehicle_; } + bool IsDriver() const { return is_driver_; } + + ~ControllableCharacter() override; + +protected: + virtual void VehicleChanged() = 0; + + size_t seat_idx_ = 0; + bool is_driver_ = false; + DrivableVehicle* vehicle_ = nullptr; +}; +} + diff --git a/src/game/drivable_vehicle.cpp b/src/game/drivable_vehicle.cpp new file mode 100644 index 0000000..5fb0aee --- /dev/null +++ b/src/game/drivable_vehicle.cpp @@ -0,0 +1,74 @@ +#include "drivable_vehicle.hpp" +#include "player_character.hpp" + +game::DrivableVehicle::DrivableVehicle(World& world, std::string model_name, const glm::vec3& color) + : Vehicle(world, std::move(model_name), color) +{ + InitSeats(); +} + +void game::DrivableVehicle::Use(PlayerCharacter& character, uint32_t target_id) +{ + if (target_id >= seats_.size()) + return; + + character.SetVehicle(this, target_id); // seat idx is same as target_id +} + +bool game::DrivableVehicle::SetPassenger(uint32_t seat_idx, ControllableCharacter* character) +{ + if (seat_idx >= seats_.size()) + return false; + + auto& seat_info = seats_[seat_idx]; + + if (seat_info.occupant == character) + return true; // already sitting here + + if (seat_info.occupant && character) + { + seat_info.occupant->SetVehicle(nullptr, 0); // remove current occupant + } + + seat_info.occupant = character; + + if (seat_idx == 0 && !character) + { + // clear inputs + SetInputs(0); + SetSteering(false, 0.0f); + } + + return true; +} + +game::DrivableVehicle::~DrivableVehicle() +{ + // remove occupants + for (auto& seat : seats_) + { + if (seat.occupant) + seat.occupant->SetVehicle(nullptr, 0); + } +} + +void game::DrivableVehicle::InitSeats() +{ + const auto& veh = *GetModel(); + for (char c = '0'; c <= '9'; ++c) + { + auto trans = veh.GetLocation(std::string("seat") + c); + if (!trans) + break; + + VehicleSeat seat{}; + seat.position = trans->position; + seats_.emplace_back(seat); + + UseTarget use_target{}; + use_target.id = seats_.size() - 1; + use_target.position = seat.position; + use_target.desc = "vlízt do " + GetModelName() + " (místo " + std::to_string(use_target.id) + ")"; + use_targets_.emplace_back(use_target); + } +} diff --git a/src/game/drivable_vehicle.hpp b/src/game/drivable_vehicle.hpp new file mode 100644 index 0000000..5104e41 --- /dev/null +++ b/src/game/drivable_vehicle.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "vehicle.hpp" +#include "usable.hpp" +#include "controllable_character.hpp" + +namespace game +{ + +struct VehicleSeat +{ + glm::vec3 position; + ControllableCharacter* occupant; +}; + +class DrivableVehicle : public Vehicle, public Usable +{ +public: + DrivableVehicle(World& world, std::string model_name, const glm::vec3& color); + + virtual void Use(PlayerCharacter& character, uint32_t target_id) override; + + bool SetPassenger(uint32_t seat_idx, ControllableCharacter* character); + + ~DrivableVehicle() override; + +private: + void InitSeats(); + +private: + std::vector seats_; +}; + +} \ No newline at end of file diff --git a/src/game/entity.cpp b/src/game/entity.cpp index f464ff2..193ec65 100644 --- a/src/game/entity.cpp +++ b/src/game/entity.cpp @@ -12,8 +12,6 @@ void game::Entity::SendInitData(Player& player, net::OutMessage& msg) const void game::Entity::Update() { - ResetMsg(); - upd_time_ = world_.GetTime(); // ensure parent is updated diff --git a/src/game/game.cpp b/src/game/game.cpp index 14ed505..74e6cf4 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -14,6 +14,11 @@ void game::Game::Update() default_world_->Update(40); } +void game::Game::FinishFrame() +{ + default_world_->FinishFrame(); +} + void game::Game::PlayerJoined(Player& player) { player.SetWorld(default_world_); @@ -21,7 +26,6 @@ void game::Game::PlayerJoined(Player& player) void game::Game::PlayerLeft(Player& player) { - } bool game::Game::PlayerInput(Player& player, PlayerInputType type, bool enabled) diff --git a/src/game/game.hpp b/src/game/game.hpp index 8974872..c37bd27 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -15,6 +15,7 @@ public: Game(); void Update(); + void FinishFrame(); void PlayerJoined(Player& player); void PlayerLeft(Player& player); diff --git a/src/game/npc_character.cpp b/src/game/npc_character.cpp new file mode 100644 index 0000000..16273b5 --- /dev/null +++ b/src/game/npc_character.cpp @@ -0,0 +1,246 @@ +#include "npc_character.hpp" +#include "openworld.hpp" +#include "drivable_vehicle.hpp" + +#include +#include + +game::NpcCharacter::NpcCharacter(World& world, OpenWorld& openworld) : Super(world) { + VehicleChanged(); +} + +void game::NpcCharacter::VehicleChanged() +{ + roads_ = nullptr; + path_.clear(); + gas_ = false; + stuck_counter_ = 0; + speed_limit_ = 0.0f; + + if (GetVehicle() && IsDriver()) + { + roads_ = world_.GetMap().GetGraph("roads"); + + seg_start_ = GetVehicle()->GetPosition(); + + size_t start_node = 0; + float min_dist = std::numeric_limits().infinity(); + + for (size_t i = 0; i < roads_->nodes.size(); ++i) + { + auto& node = roads_->nodes[i]; + float dist = glm::distance(node.position, seg_start_); + if (dist < min_dist) + { + min_dist = dist; + start_node = i; + } + } + + path_.push_back(start_node); + + } +} + +void game::NpcCharacter::SelectNextNode() +{ + size_t node = path_.back(); + size_t num_nbs = roads_->nodes[node].num_nbs; + + if (num_nbs < 1) + { + const auto& pos = roads_->nodes[node].position; + std::cout << "node " << node << " has no neighbors!!!1 position: " << pos.x << " " << pos.y << " " << pos.z + << std::endl; + throw std::runtime_error("no neighbors"); + } + + path_.push_back(roads_->nbs[roads_->nodes[node].nbs + (rand() % num_nbs)]); +} + + +static float GetTurnAngle2D(const glm::vec2& forward, const glm::vec2& to_target) +{ + glm::vec2 forward_xy = glm::normalize(forward); + glm::vec2 to_target_xy = glm::normalize(to_target); + float dot = glm::dot(forward_xy, to_target_xy); + float cross = forward_xy.x * to_target_xy.y - forward_xy.y * to_target_xy.x; + float angle = acosf(glm::clamp(dot, -1.0f, 1.0f)); // in [0, pi] + + if (cross < 0) + angle = -angle; + + return angle; // in [-pi, pi] +} + +static float GetTurnAngle(const glm::vec3& pos, const glm::quat& rot, const glm::vec3& target) +{ + glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f}; + glm::vec3 to_target = target - pos; + glm::vec2 forward_xy = glm::vec2{forward.x, forward.y}; + glm::vec2 to_target_xy = glm::vec2{to_target.x, to_target.y}; + return GetTurnAngle2D(forward_xy, to_target_xy); +} + +void game::NpcCharacter::VehicleThink() +{ + if (!IsDriver() || !GetVehicle() || !roads_) + return; + + glm::vec3 pos = GetVehicle()->GetPosition(); + glm::quat rot = GetVehicle()->GetRotation(); + glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f}; + + // glm::vec3 target = s->roads.nodes[s->node].position; + + // + std::array waypoints; + + while (path_.size() < waypoints.size() - 1) + { + SelectNextNode(); + } + + glm::vec3 node_pos = roads_->nodes[path_.front()].position; + if (glm::distance(glm::vec2(pos), glm::vec2(node_pos)) < 6.0f && path_.size() > 1) + { + seg_start_ = node_pos; + path_.pop_front(); + SelectNextNode(); + } + + glm::vec3 target_node_pos = roads_->nodes[path_.front()].position; + + waypoints[0] = pos - glm::normalize(forward) * 3.0f; + waypoints[1] = pos; + + // find closest point on segment [seg_start -> target_node_pos] + glm::vec3 seg_end = target_node_pos; + glm::vec3 seg_dir = seg_end - seg_start_; + float seg_len = glm::length(seg_dir); + if (seg_len > 5.0f) + { + glm::vec3 seg_dir_norm = seg_dir / seg_len; + float t = glm::clamp(glm::dot(pos - seg_start_, seg_dir_norm) / seg_len, 0.0f, 1.0f); + waypoints[2] = seg_start_ + t * seg_dir; + if (glm::distance(waypoints[1], target_node_pos) > 10.0f) + { + waypoints[2] += seg_dir_norm * 10.0f; // look a bit ahead on segment + } + else + { + waypoints[2] = target_node_pos; + } + } + else + { + waypoints[2] = target_node_pos; + } + + for (size_t i = 3; i < waypoints.size(); ++i) + { + size_t path_idx = glm::min(i - 3, path_.size() - 1); + waypoints[i] = roads_->nodes[path_[path_idx]].position; + } + + // decrease speed based on curvature + const float base_speed = 100.0f; + float target_speed = base_speed; + float dist_accum = 0.0f; + for (size_t i = 1; i < waypoints.size() - 1; ++i) + { + glm::vec3 dir1 = waypoints[i] - waypoints[i - 1]; + glm::vec3 dir2 = waypoints[i + 1] - waypoints[i]; + float dist = glm::length(dir1); + dist_accum += dist; + + glm::vec2 dir1_xy = glm::vec2{dir1.x, dir1.y}; + glm::vec2 dir2_xy = glm::vec2{dir2.x, dir2.y}; + + const float min_dir_length = 0.001f; + float angle = glm::length(dir1_xy) > min_dir_length && glm::length(dir2_xy) > min_dir_length + ? GetTurnAngle2D(dir1_xy, dir2_xy) + : 0.0f; + // std::cout << "angle: " << angle << "\n"; + float abs_angle = fabsf(angle); + if (abs_angle > glm::radians(7.0f)) + { + // float speed_limit = 50.0f / abs_angle; // sharper turn -> lower speed + // speed_limit *= dist_accum / 20.0f; // more distance to turn -> higher speed + // speed_limit = glm::max(speed_limit, 20.0f); + // max_speed = glm::min(max_speed, speed_limit); + target_speed -= + abs_angle * (base_speed / glm::pi() / 2.0f) * 50.0f / glm::max(dist_accum - 1.0f, 1.0f); + } + + if (dist_accum > 200.0f) + break; + } + + target_speed = glm::clamp(target_speed, 25.0f, 100.0f); + speed_limit_ = target_speed; + + // std::cout << "target speed: " << target_speed << "\n"; + + float angle = GetTurnAngle(pos, rot, waypoints[2]); + + if (glm::distance(pos, last_pos_) < 2.0f) + { + stuck_counter_++; + if (stuck_counter_ > 100) + { + //s->state_str = "stuck (reverse)"; + //s->stuck_counter = 0; + //s->vehicle.SetSteering(true, -angle); // try turn away + + //s->vehicle.SetInputs(0); // stop + //// stuck, go reverse for a while + //s->vehicle.SetInput(game::VIN_BACKWARD, true); + //s->vehicle.Schedule(2000, [s]() { + // s->vehicle.SetInput(game::VIN_BACKWARD, false); + // BotThink(s); + //}); + + GetVehicle()->SetInputs(0); // stop + is_driver_ = false; // TODO: fix + return; + } + } + else + { + stuck_counter_ = 0; + last_pos_ = pos; + } + + GetVehicle()->SetSteering(true, angle); + + game::VehicleInputFlags vin = 0; + + float speed = GetVehicle()->GetSpeed(); + + // if (glm::distance(pos, target) < 10.0f) + // { + // target_speed = 20.0f; + // } + + if (speed < target_speed * 0.9f && !gas_) + { + gas_ = true; + } + else if (speed > target_speed * 1.1f && gas_) + { + gas_ = false; + } + + if (gas_) + { + vin |= 1 << game::VIN_FORWARD; + } + + if (speed > target_speed * 1.4f) + { + vin |= 1 << game::VIN_BACKWARD; + } + + GetVehicle()->SetInputs(vin); +} diff --git a/src/game/npc_character.hpp b/src/game/npc_character.hpp new file mode 100644 index 0000000..2fc4c5f --- /dev/null +++ b/src/game/npc_character.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "assets/map.hpp" +#include "controllable_character.hpp" + +namespace game +{ + +class OpenWorld; + +class NpcCharacter : public ControllableCharacter +{ +public: + using Super = ControllableCharacter; + + NpcCharacter(World& world, OpenWorld& openworld); + + virtual void VehicleChanged() override; + + + virtual void Update() override + { + Super::Update(); + + VehicleThink(); + } + +private: + void SelectNextNode(); + void VehicleThink(); + +private: + + // driver + const assets::MapGraph* roads_; + glm::vec3 seg_start_; + std::deque path_; + bool gas_ = false; + size_t stuck_counter_ = 0; + glm::vec3 last_pos_ = glm::vec3(0.0f); + float speed_limit_ = 0.0f; +}; + +} \ No newline at end of file diff --git a/src/game/openworld.cpp b/src/game/openworld.cpp index 4a4da8b..57ef0da 100644 --- a/src/game/openworld.cpp +++ b/src/game/openworld.cpp @@ -6,388 +6,13 @@ #include "player.hpp" #include "vehicle.hpp" +#include "player_character.hpp" +#include "npc_character.hpp" +#include "drivable_vehicle.hpp" + namespace game { -struct ControllableCharacter : public Character -{ - using Super = Character; - - Vehicle* vehicle = nullptr; - bool is_driver = false; - - ControllableCharacter(World& world) : Character(world, CharacterInfo{}) {} - - virtual void VehicleChanged() = 0; -}; - -struct PlayerCharacter : public ControllableCharacter -{ - using Super = ControllableCharacter; - - Player& player; - - PlayerCharacter(World& world, Player& player) : ControllableCharacter(world), player(player) { - VehicleChanged(); - } - - virtual void VehicleChanged() override - { - if (vehicle) - { - player.SetCamera(vehicle->GetEntNum()); - } - else - { - player.SetCamera(GetEntNum()); - } - } - - void UpdateInputs() - { - auto in = player.GetInput(); - CharacterInputFlags c_in = 0; - VehicleInputFlags v_in = 0; - - if (in & (1 << IN_FORWARD)) - { - c_in |= 1 << CIN_FORWARD; - v_in |= 1 << VIN_FORWARD; - } - - if (in & (1 << IN_BACKWARD)) - { - c_in |= 1 << CIN_BACKWARD; - v_in |= 1 << VIN_BACKWARD; - } - - if (in & (1 << IN_LEFT)) - { - c_in |= 1 << CIN_LEFT; - v_in |= 1 << VIN_LEFT; - } - - if (in & (1 << IN_RIGHT)) - { - c_in |= 1 << CIN_RIGHT; - v_in |= 1 << VIN_RIGHT; - } - - if (in & (1 << IN_JUMP)) - { - c_in |= 1 << CIN_JUMP; - } - - if (vehicle && is_driver) - { - SetInputs(0); - vehicle->SetInputs(v_in); - } - else - { - SetInputs(c_in); - } - - } -}; - - -static float GetTurnAngle2D(const glm::vec2& forward, const glm::vec2& to_target) -{ - glm::vec2 forward_xy = glm::normalize(forward); - glm::vec2 to_target_xy = glm::normalize(to_target); - float dot = glm::dot(forward_xy, to_target_xy); - float cross = forward_xy.x * to_target_xy.y - forward_xy.y * to_target_xy.x; - float angle = acosf(glm::clamp(dot, -1.0f, 1.0f)); // in [0, pi] - - if (cross < 0) - angle = -angle; - - return angle; // in [-pi, pi] -} - -static float GetTurnAngle(const glm::vec3& pos, const glm::quat& rot, const glm::vec3& target) -{ - glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f}; - glm::vec3 to_target = target - pos; - glm::vec2 forward_xy = glm::vec2{forward.x, forward.y}; - glm::vec2 to_target_xy = glm::vec2{to_target.x, to_target.y}; - return GetTurnAngle2D(forward_xy, to_target_xy); -} - -struct NpcCharacter : public ControllableCharacter -{ - using Super = ControllableCharacter; - - // driver - const assets::MapGraph* roads; - glm::vec3 seg_start; - std::deque path; - bool gas = false; - size_t stuck_counter = 0; - glm::vec3 last_pos = glm::vec3(0.0f); - float speed_limit = 0.0f; - - NpcCharacter(World& world) : ControllableCharacter(world) - { - VehicleChanged(); - } - - virtual void VehicleChanged() override - { - roads = nullptr; - path.clear(); - gas = false; - stuck_counter = 0; - speed_limit = 0.0f; - - if (vehicle && is_driver) - { - roads = world_.GetMap().GetGraph("roads"); - - seg_start = vehicle->GetPosition(); - - size_t start_node = 0; - float min_dist = std::numeric_limits().infinity(); - - for (size_t i = 0; i < roads->nodes.size(); ++i) - { - auto& node = roads->nodes[i]; - float dist = glm::distance(node.position, seg_start); - if (dist < min_dist) - { - min_dist = dist; - start_node = i; - } - } - - path.push_back(start_node); - - } - else - { - } - } - - void SelectNextNode() - { - size_t node = path.back(); - size_t num_nbs = roads->nodes[node].num_nbs; - - if (num_nbs < 1) - { - const auto& pos = roads->nodes[node].position; - std::cout << "node " << node << " has no neighbors!!!1 position: " << pos.x << " " << pos.y << " " << pos.z - << std::endl; - throw std::runtime_error("no neighbors"); - } - - path.push_back(roads->nbs[roads->nodes[node].nbs + (rand() % num_nbs)]); - } - - virtual void Update() override - { - Super::Update(); - - VehicleThink(); - } - - void VehicleThink() - { - if (!is_driver || !vehicle || !roads) - return; - - glm::vec3 pos = vehicle->GetPosition(); - glm::quat rot = vehicle->GetRotation(); - glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f}; - - // glm::vec3 target = s->roads.nodes[s->node].position; - - // - std::array waypoints; - - while (path.size() < waypoints.size() - 1) - { - SelectNextNode(); - } - - glm::vec3 node_pos = roads->nodes[path[0]].position; - if (glm::distance(glm::vec2(pos), glm::vec2(node_pos)) < 6.0f && path.size() > 1) - { - seg_start = node_pos; - path.pop_front(); - SelectNextNode(); - } - - glm::vec3 target_node_pos = roads->nodes[path[0]].position; - - waypoints[0] = pos - glm::normalize(forward) * 3.0f; - waypoints[1] = pos; - - // find closest point on segment [seg_start -> target_node_pos] - glm::vec3 seg_end = target_node_pos; - glm::vec3 seg_dir = seg_end - seg_start; - float seg_len = glm::length(seg_dir); - if (seg_len > 5.0f) - { - glm::vec3 seg_dir_norm = seg_dir / seg_len; - float t = glm::clamp(glm::dot(pos - seg_start, seg_dir_norm) / seg_len, 0.0f, 1.0f); - waypoints[2] = seg_start + t * seg_dir; - if (glm::distance(waypoints[1], target_node_pos) > 10.0f) - { - waypoints[2] += seg_dir_norm * 10.0f; // look a bit ahead on segment - } - else - { - waypoints[2] = target_node_pos; - } - } - else - { - waypoints[2] = target_node_pos; - } - - for (size_t i = 3; i < waypoints.size(); ++i) - { - size_t path_idx = glm::min(i - 3, path.size() - 1); - waypoints[i] = roads->nodes[path[path_idx]].position; - } - - // decrease speed based on curvature - const float base_speed = 100.0f; - float target_speed = base_speed; - float dist_accum = 0.0f; - for (size_t i = 1; i < waypoints.size() - 1; ++i) - { - glm::vec3 dir1 = waypoints[i] - waypoints[i - 1]; - glm::vec3 dir2 = waypoints[i + 1] - waypoints[i]; - float dist = glm::length(dir1); - dist_accum += dist; - - glm::vec2 dir1_xy = glm::vec2{dir1.x, dir1.y}; - glm::vec2 dir2_xy = glm::vec2{dir2.x, dir2.y}; - - const float min_dir_length = 0.001f; - float angle = glm::length(dir1_xy) > min_dir_length && glm::length(dir2_xy) > min_dir_length - ? GetTurnAngle2D(dir1_xy, dir2_xy) - : 0.0f; - // std::cout << "angle: " << angle << "\n"; - float abs_angle = fabsf(angle); - if (abs_angle > glm::radians(7.0f)) - { - // float speed_limit = 50.0f / abs_angle; // sharper turn -> lower speed - // speed_limit *= dist_accum / 20.0f; // more distance to turn -> higher speed - // speed_limit = glm::max(speed_limit, 20.0f); - // max_speed = glm::min(max_speed, speed_limit); - target_speed -= - abs_angle * (base_speed / glm::pi() / 2.0f) * 50.0f / glm::max(dist_accum - 1.0f, 1.0f); - } - - if (dist_accum > 200.0f) - break; - } - - target_speed = glm::clamp(target_speed, 25.0f, 100.0f); - speed_limit = target_speed; - - // std::cout << "target speed: " << target_speed << "\n"; - - float angle = GetTurnAngle(pos, rot, waypoints[2]); - - if (glm::distance(pos, last_pos) < 2.0f) - { - stuck_counter++; - if (stuck_counter > 20) - { - //s->state_str = "stuck (reverse)"; - //s->stuck_counter = 0; - //s->vehicle.SetSteering(true, -angle); // try turn away - - //s->vehicle.SetInputs(0); // stop - //// stuck, go reverse for a while - //s->vehicle.SetInput(game::VIN_BACKWARD, true); - //s->vehicle.Schedule(2000, [s]() { - // s->vehicle.SetInput(game::VIN_BACKWARD, false); - // BotThink(s); - //}); - - vehicle->SetInputs(0); // stop - is_driver = false; // TODO: fix - return; - } - } - else - { - stuck_counter = 0; - last_pos = pos; - } - - vehicle->SetSteering(true, angle); - - game::VehicleInputFlags vin = 0; - - float speed = vehicle->GetSpeed(); - - // if (glm::distance(pos, target) < 10.0f) - // { - // target_speed = 20.0f; - // } - - if (speed < target_speed * 0.9f && !gas) - { - gas = true; - } - else if (speed > target_speed * 1.1f && gas) - { - gas = false; - } - - if (gas) - { - vin |= 1 << game::VIN_FORWARD; - } - - if (speed > target_speed * 1.4f) - { - vin |= 1 << game::VIN_BACKWARD; - } - - vehicle->SetInputs(vin); - } -}; - -struct VehicleSeat -{ - glm::vec3 position; - ControllableCharacter* occupant; -}; - -struct DrivableVehicle : public Vehicle -{ - std::vector seats; - - DrivableVehicle(World& world, std::string model_name, const glm::vec3& color) - : Vehicle(world, std::move(model_name), color) - { - InitSeats(); - } - - void InitSeats() - { - const auto& veh = *GetModel(); - for (char c = '0'; c <= '9'; ++c) - { - auto trans = veh.GetLocation(std::string("seat") + c); - if (!trans) - break; - - VehicleSeat seat{}; - seat.position = trans->position; - seats.emplace_back(seat); - } - } -}; - } // namespace game game::OpenWorld::OpenWorld() : World("openworld") @@ -421,8 +46,8 @@ void game::OpenWorld::PlayerInput(Player& player, PlayerInputType type, bool ena case IN_DEBUG1: if (enabled) { - if (character->vehicle) - character->vehicle->SetPosition({100.0f, 100.0f, 5.0f}); + if (character->GetVehicle()) + character->GetVehicle()->SetPosition({100.0f, 100.0f, 5.0f}); else character->SetPosition({100.0f, 100.0f, 5.0f}); } @@ -434,7 +59,7 @@ void game::OpenWorld::PlayerInput(Player& player, PlayerInputType type, bool ena break; default: - character->UpdateInputs(); + character->ProcessInput(type, enabled); break; } } @@ -450,6 +75,37 @@ void game::OpenWorld::PlayerLeft(Player& player) RemovePlayerCharacter(player); } +std::optional> game::OpenWorld::GetBestUseTarget(const glm::vec3& pos) const +{ + std::optional> best_target; + float best_dist = std::numeric_limits::max(); + + // TODO: spatial query + for (const auto& [entnum, ent] : GetEntities()) + { + auto usable = dynamic_cast(ent.get()); + if (!usable) + continue; + + for (const auto& target : usable->GetUseTargets()) + { + glm::vec3 pos_world = ent->GetRoot().matrix * glm::vec4(target.position, 1.0f); + + float dist = glm::distance(pos, pos_world); + if (dist < 3.0f && dist < best_dist) + { + best_dist = dist; + best_target = std::make_pair(usable, &target); + } + } + } + + if (best_target) + return std::make_pair(std::ref(*best_target->first), *best_target->second); + else + return std::nullopt; + +} static glm::vec3 GetRandomColor() { @@ -466,9 +122,9 @@ static glm::vec3 GetRandomColor() } template -static T& SpawnRandomCharacter(game::World& world, TArgs&&... args) +static T& SpawnRandomCharacter(game::OpenWorld& world, TArgs&&... args) { - auto& character = world.Spawn(std::forward(args)...); + auto& character = world.Spawn(world, std::forward(args)...); // add clothes character.AddClothes("tshirt", GetRandomColor()); @@ -484,9 +140,6 @@ void game::OpenWorld::CreatePlayerCharacter(Player& player) auto& character = SpawnRandomCharacter(*this, player); character.SetNametag("player (" + std::to_string(character.GetEntNum()) + ")"); character.SetPosition({100.0f, 100.0f, 5.0f}); - character.EnablePhysics(true); - - player.SetCamera(character.GetEntNum()); player_characters_[&player] = &character; } @@ -531,17 +184,5 @@ void game::OpenWorld::SpawnBot() auto& vehicle = SpawnRandomVehicle(*this); auto& driver = SpawnRandomCharacter(*this); - driver.Attach(vehicle.GetEntNum()); - - if (vehicle.seats.size() > 0) - { - driver.SetPosition(vehicle.seats[0].position); - } - - driver.SetMainAnim("vehicle_drive"); - driver.SetYaw(0.5f * glm::pi()); - - driver.vehicle = &vehicle; - driver.is_driver = true; - driver.VehicleChanged(); + driver.SetVehicle(&vehicle, 0); } diff --git a/src/game/openworld.hpp b/src/game/openworld.hpp index a2940a8..4caff0d 100644 --- a/src/game/openworld.hpp +++ b/src/game/openworld.hpp @@ -3,6 +3,9 @@ #include "world.hpp" #include "vehicle.hpp" #include "character.hpp" +#include "usable.hpp" + +#include namespace game { @@ -22,6 +25,8 @@ public: virtual void PlayerViewAnglesChanged(Player& player, float yaw, float pitch) override; virtual void PlayerLeft(Player& player) override; + std::optional> GetBestUseTarget(const glm::vec3& pos) const; + private: void CreatePlayerCharacter(Player& player); void RemovePlayerCharacter(Player& player); diff --git a/src/game/player_character.cpp b/src/game/player_character.cpp new file mode 100644 index 0000000..c12a355 --- /dev/null +++ b/src/game/player_character.cpp @@ -0,0 +1,111 @@ +#include "player_character.hpp" +#include "openworld.hpp" + +game::PlayerCharacter::PlayerCharacter(World& world, OpenWorld& openworld, Player& player) : Super(world), world_(openworld), player_(player) +{ + EnablePhysics(true); + VehicleChanged(); +} + +void game::PlayerCharacter::Update() +{ + Super::Update(); + + UpdateUseTarget(); +} + +void game::PlayerCharacter::VehicleChanged() +{ + if (vehicle_) + { + player_.SetCamera(vehicle_->GetEntNum()); + } + else + { + player_.SetCamera(GetEntNum()); + } + + UpdateInputs(); +} + +void game::PlayerCharacter::ProcessInput(PlayerInputType type, bool enabled) +{ + switch (type) + { + case IN_USE: + if (enabled) + { + if (!vehicle_) + { + auto use_target_opt = world_.GetBestUseTarget(GetRootTransform().position); + if (use_target_opt) + { + auto& [usable, use_target] = *use_target_opt; + usable.Use(*this, use_target.id); + } + } + else + { + SetVehicle(nullptr, 0); + } + + } + break; + + default: + UpdateInputs(); + break; + } +} + +void game::PlayerCharacter::UpdateInputs() +{ + auto in = player_.GetInput(); + CharacterInputFlags c_in = 0; + VehicleInputFlags v_in = 0; + + if (in & (1 << IN_FORWARD)) + { + c_in |= 1 << CIN_FORWARD; + v_in |= 1 << VIN_FORWARD; + } + + if (in & (1 << IN_BACKWARD)) + { + c_in |= 1 << CIN_BACKWARD; + v_in |= 1 << VIN_BACKWARD; + } + + if (in & (1 << IN_LEFT)) + { + c_in |= 1 << CIN_LEFT; + v_in |= 1 << VIN_LEFT; + } + + if (in & (1 << IN_RIGHT)) + { + c_in |= 1 << CIN_RIGHT; + v_in |= 1 << VIN_RIGHT; + } + + if (in & (1 << IN_JUMP)) + { + c_in |= 1 << CIN_JUMP; + } + + if (vehicle_) + { + SetInputs(0); + + if (is_driver_) + { + vehicle_->SetInputs(v_in); + } + } + else + { + SetInputs(c_in); + } +} + +void game::PlayerCharacter::UpdateUseTarget() {} diff --git a/src/game/player_character.hpp b/src/game/player_character.hpp new file mode 100644 index 0000000..9c36b21 --- /dev/null +++ b/src/game/player_character.hpp @@ -0,0 +1,34 @@ +#include "controllable_character.hpp" + +#include "drivable_vehicle.hpp" +#include "player.hpp" + +namespace game +{ + +class OpenWorld; + +class PlayerCharacter : public ControllableCharacter +{ +public: + using Super = ControllableCharacter; + + PlayerCharacter(World& world, OpenWorld& openworld, Player& player); + + virtual void Update() override; + + virtual void VehicleChanged() override; + + void ProcessInput(PlayerInputType type, bool enabled); + +private: + void UpdateInputs(); + void UpdateUseTarget(); + +private: + OpenWorld& world_; + Player& player_; +}; + + +} \ No newline at end of file diff --git a/src/game/usable.hpp b/src/game/usable.hpp new file mode 100644 index 0000000..e54e89d --- /dev/null +++ b/src/game/usable.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +namespace game +{ + +struct UseTarget +{ + uint32_t id = 0; + glm::vec3 position; + std::string desc; +}; + +class PlayerCharacter; + +class Usable +{ +public: + const std::vector& GetUseTargets() const { return use_targets_; } + + virtual void Use(PlayerCharacter& character, uint32_t target_id) = 0; + +protected: + std::vector use_targets_; + +}; + + +} \ No newline at end of file diff --git a/src/game/vehicle.hpp b/src/game/vehicle.hpp index 221de8e..796f99f 100644 --- a/src/game/vehicle.hpp +++ b/src/game/vehicle.hpp @@ -51,7 +51,9 @@ public: void SetSteering(bool analog, float value = 0.0f); + const std::string& GetModelName() const { return model_name_; } const std::shared_ptr& GetModel() const { return model_; } + const glm::vec3& GetColor() const { return color_; } virtual ~Vehicle(); diff --git a/src/game/world.cpp b/src/game/world.cpp index 1efa86a..42cd632 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -50,6 +50,16 @@ void game::World::Update(int64_t delta_time) } } +void game::World::FinishFrame() +{ + // reset ent msgs + for (auto& [entnum, ent] : ents_) + { + ent->ResetMsg(); + } + +} + game::Entity* game::World::GetEntity(net::EntNum entnum) { auto it = ents_.find(entnum); diff --git a/src/game/world.hpp b/src/game/world.hpp index 9fe3424..5680d0c 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -31,6 +31,7 @@ public: void RegisterEntity(std::unique_ptr ent); virtual void Update(int64_t delta_time); + void FinishFrame(); // events virtual void PlayerJoined(Player& player) {} diff --git a/src/gameview/characterview.cpp b/src/gameview/characterview.cpp index 1419554..b894e0b 100644 --- a/src/gameview/characterview.cpp +++ b/src/gameview/characterview.cpp @@ -46,6 +46,10 @@ bool game::view::CharacterView::ProcessMsg(net::EntMsgType type, net::InMessage& case net::EMSG_UPDATE: return ProcessUpdateMsg(msg); + case net::EMSG_ATTACH: + skip_lerps_ = 1; + return Super::ProcessMsg(type, msg); + default: return Super::ProcessMsg(type, msg); } @@ -190,6 +194,12 @@ bool game::view::CharacterView::ReadState(net::InMessage& msg) states_[0].loco_phase -= 1.0f; } + if (skip_lerps_ > 0) + { + states_[0] = states_[1]; + skip_lerps_--; + } + return true; } diff --git a/src/gameview/characterview.hpp b/src/gameview/characterview.hpp index 47ca836..f66a1e8 100644 --- a/src/gameview/characterview.hpp +++ b/src/gameview/characterview.hpp @@ -63,6 +63,7 @@ private: CharacterSyncState sync_; CharacterViewState states_[2]; float update_time_ = 0.0f; + size_t skip_lerps_ = 0; }; } diff --git a/src/gameview/client_session.cpp b/src/gameview/client_session.cpp index 7fe3930..921e922 100644 --- a/src/gameview/client_session.cpp +++ b/src/gameview/client_session.cpp @@ -4,6 +4,8 @@ #include // #include +#include "vehicleview.hpp" + game::view::ClientSession::ClientSession(App& app) : app_(app) {} bool game::view::ClientSession::ProcessMessage(net::InMessage& msg) @@ -81,12 +83,18 @@ void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams& void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) const { glm::vec3 start(0.0f, 0.0f, 2.0f); + float distance = 5.0f; if (follow_ent_) { auto ent = world_->GetEntity(follow_ent_); if (ent) - start += ent->GetRoot().local.position; + { + start += ent->GetRoot().GetGlobalPosition(); + + if (dynamic_cast(ent)) + distance = 8.0f; + } } float yaw_cos = glm::cos(yaw_); @@ -95,7 +103,6 @@ void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) con float pitch_sin = glm::sin(pitch_); glm::vec3 dir(yaw_cos * pitch_cos, yaw_sin * pitch_cos, pitch_sin); - float distance = 5.0f; glm::vec3 end = start - dir * distance; //start.z -= 0.5f; // shift this a bit to make it better when occluded diff --git a/src/gameview/entityview.cpp b/src/gameview/entityview.cpp index ca03531..91da02c 100644 --- a/src/gameview/entityview.cpp +++ b/src/gameview/entityview.cpp @@ -88,7 +88,7 @@ void game::view::EntityView::DrawNametag(const DrawArgs& args) return; // calc screen position - glm::vec4 world_pos = glm::vec4(root_.local.position + glm::vec3(0.0f, 0.0f, 2.0f), 1.0f); + glm::vec4 world_pos = GetRoot().matrix * glm::vec4(glm::vec3(0.0f, 0.0f, 2.0f), 1.0f); glm::vec4 clip_pos = args.view_proj * world_pos; if (clip_pos.w == 0.0f) return; diff --git a/src/server/server.cpp b/src/server/server.cpp index 39b6f93..e257048 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -120,4 +120,6 @@ void sv::Server::Update() { client->Update(); } + + game_.FinishFrame(); }