Sitting in vehicles
This commit is contained in:
parent
42e0f56c6c
commit
9b1be68bcd
@ -112,14 +112,23 @@ set(CLIENT_ONLY_SOURCES
|
|||||||
set(SERVER_ONLY_SOURCES
|
set(SERVER_ONLY_SOURCES
|
||||||
"src/game/character.hpp"
|
"src/game/character.hpp"
|
||||||
"src/game/character.cpp"
|
"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.hpp"
|
||||||
"src/game/entity.cpp"
|
"src/game/entity.cpp"
|
||||||
"src/game/game.hpp"
|
"src/game/game.hpp"
|
||||||
"src/game/game.cpp"
|
"src/game/game.cpp"
|
||||||
|
"src/game/npc_character.hpp"
|
||||||
|
"src/game/npc_character.cpp"
|
||||||
"src/game/openworld.hpp"
|
"src/game/openworld.hpp"
|
||||||
"src/game/openworld.cpp"
|
"src/game/openworld.cpp"
|
||||||
|
"src/game/player_character.hpp"
|
||||||
|
"src/game/player_character.cpp"
|
||||||
"src/game/player.hpp"
|
"src/game/player.hpp"
|
||||||
"src/game/player.cpp"
|
"src/game/player.cpp"
|
||||||
|
"src/game/usable.hpp"
|
||||||
"src/game/vehicle.hpp"
|
"src/game/vehicle.hpp"
|
||||||
"src/game/vehicle.cpp"
|
"src/game/vehicle.cpp"
|
||||||
"src/game/world.hpp"
|
"src/game/world.hpp"
|
||||||
|
|||||||
54
src/game/controllable_character.cpp
Normal file
54
src/game/controllable_character.cpp
Normal file
@ -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<float>());
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/game/controllable_character.hpp
Normal file
32
src/game/controllable_character.hpp
Normal file
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
74
src/game/drivable_vehicle.cpp
Normal file
74
src/game/drivable_vehicle.cpp
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/game/drivable_vehicle.hpp
Normal file
34
src/game/drivable_vehicle.hpp
Normal file
@ -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<VehicleSeat> seats_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,8 +12,6 @@ void game::Entity::SendInitData(Player& player, net::OutMessage& msg) const
|
|||||||
|
|
||||||
void game::Entity::Update()
|
void game::Entity::Update()
|
||||||
{
|
{
|
||||||
ResetMsg();
|
|
||||||
|
|
||||||
upd_time_ = world_.GetTime();
|
upd_time_ = world_.GetTime();
|
||||||
|
|
||||||
// ensure parent is updated
|
// ensure parent is updated
|
||||||
|
|||||||
@ -14,6 +14,11 @@ void game::Game::Update()
|
|||||||
default_world_->Update(40);
|
default_world_->Update(40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void game::Game::FinishFrame()
|
||||||
|
{
|
||||||
|
default_world_->FinishFrame();
|
||||||
|
}
|
||||||
|
|
||||||
void game::Game::PlayerJoined(Player& player)
|
void game::Game::PlayerJoined(Player& player)
|
||||||
{
|
{
|
||||||
player.SetWorld(default_world_);
|
player.SetWorld(default_world_);
|
||||||
@ -21,7 +26,6 @@ void game::Game::PlayerJoined(Player& player)
|
|||||||
|
|
||||||
void game::Game::PlayerLeft(Player& player)
|
void game::Game::PlayerLeft(Player& player)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool game::Game::PlayerInput(Player& player, PlayerInputType type, bool enabled)
|
bool game::Game::PlayerInput(Player& player, PlayerInputType type, bool enabled)
|
||||||
|
|||||||
@ -15,6 +15,7 @@ public:
|
|||||||
Game();
|
Game();
|
||||||
|
|
||||||
void Update();
|
void Update();
|
||||||
|
void FinishFrame();
|
||||||
|
|
||||||
void PlayerJoined(Player& player);
|
void PlayerJoined(Player& player);
|
||||||
void PlayerLeft(Player& player);
|
void PlayerLeft(Player& player);
|
||||||
|
|||||||
246
src/game/npc_character.cpp
Normal file
246
src/game/npc_character.cpp
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
#include "npc_character.hpp"
|
||||||
|
#include "openworld.hpp"
|
||||||
|
#include "drivable_vehicle.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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<float>().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<glm::vec3, 8> 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<float>() / 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);
|
||||||
|
}
|
||||||
44
src/game/npc_character.hpp
Normal file
44
src/game/npc_character.hpp
Normal file
@ -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<size_t> path_;
|
||||||
|
bool gas_ = false;
|
||||||
|
size_t stuck_counter_ = 0;
|
||||||
|
glm::vec3 last_pos_ = glm::vec3(0.0f);
|
||||||
|
float speed_limit_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -6,388 +6,13 @@
|
|||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
#include "vehicle.hpp"
|
#include "vehicle.hpp"
|
||||||
|
|
||||||
|
#include "player_character.hpp"
|
||||||
|
#include "npc_character.hpp"
|
||||||
|
#include "drivable_vehicle.hpp"
|
||||||
|
|
||||||
namespace game
|
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<size_t> 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<float>().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<glm::vec3, 8> 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<float>() / 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<VehicleSeat> 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
|
} // namespace game
|
||||||
|
|
||||||
game::OpenWorld::OpenWorld() : World("openworld")
|
game::OpenWorld::OpenWorld() : World("openworld")
|
||||||
@ -421,8 +46,8 @@ void game::OpenWorld::PlayerInput(Player& player, PlayerInputType type, bool ena
|
|||||||
case IN_DEBUG1:
|
case IN_DEBUG1:
|
||||||
if (enabled)
|
if (enabled)
|
||||||
{
|
{
|
||||||
if (character->vehicle)
|
if (character->GetVehicle())
|
||||||
character->vehicle->SetPosition({100.0f, 100.0f, 5.0f});
|
character->GetVehicle()->SetPosition({100.0f, 100.0f, 5.0f});
|
||||||
else
|
else
|
||||||
character->SetPosition({100.0f, 100.0f, 5.0f});
|
character->SetPosition({100.0f, 100.0f, 5.0f});
|
||||||
}
|
}
|
||||||
@ -434,7 +59,7 @@ void game::OpenWorld::PlayerInput(Player& player, PlayerInputType type, bool ena
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
character->UpdateInputs();
|
character->ProcessInput(type, enabled);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,6 +75,37 @@ void game::OpenWorld::PlayerLeft(Player& player)
|
|||||||
RemovePlayerCharacter(player);
|
RemovePlayerCharacter(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<game::Usable&, const game::UseTarget&>> game::OpenWorld::GetBestUseTarget(const glm::vec3& pos) const
|
||||||
|
{
|
||||||
|
std::optional<std::pair<Usable*, const UseTarget*>> best_target;
|
||||||
|
float best_dist = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
// TODO: spatial query
|
||||||
|
for (const auto& [entnum, ent] : GetEntities())
|
||||||
|
{
|
||||||
|
auto usable = dynamic_cast<Usable*>(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()
|
static glm::vec3 GetRandomColor()
|
||||||
{
|
{
|
||||||
@ -466,9 +122,9 @@ static glm::vec3 GetRandomColor()
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class T, typename... TArgs>
|
template <class T, typename... TArgs>
|
||||||
static T& SpawnRandomCharacter(game::World& world, TArgs&&... args)
|
static T& SpawnRandomCharacter(game::OpenWorld& world, TArgs&&... args)
|
||||||
{
|
{
|
||||||
auto& character = world.Spawn<T>(std::forward<TArgs>(args)...);
|
auto& character = world.Spawn<T>(world, std::forward<TArgs>(args)...);
|
||||||
|
|
||||||
// add clothes
|
// add clothes
|
||||||
character.AddClothes("tshirt", GetRandomColor());
|
character.AddClothes("tshirt", GetRandomColor());
|
||||||
@ -484,9 +140,6 @@ void game::OpenWorld::CreatePlayerCharacter(Player& player)
|
|||||||
auto& character = SpawnRandomCharacter<PlayerCharacter>(*this, player);
|
auto& character = SpawnRandomCharacter<PlayerCharacter>(*this, player);
|
||||||
character.SetNametag("player (" + std::to_string(character.GetEntNum()) + ")");
|
character.SetNametag("player (" + std::to_string(character.GetEntNum()) + ")");
|
||||||
character.SetPosition({100.0f, 100.0f, 5.0f});
|
character.SetPosition({100.0f, 100.0f, 5.0f});
|
||||||
character.EnablePhysics(true);
|
|
||||||
|
|
||||||
player.SetCamera(character.GetEntNum());
|
|
||||||
|
|
||||||
player_characters_[&player] = &character;
|
player_characters_[&player] = &character;
|
||||||
}
|
}
|
||||||
@ -531,17 +184,5 @@ void game::OpenWorld::SpawnBot()
|
|||||||
auto& vehicle = SpawnRandomVehicle(*this);
|
auto& vehicle = SpawnRandomVehicle(*this);
|
||||||
auto& driver = SpawnRandomCharacter<NpcCharacter>(*this);
|
auto& driver = SpawnRandomCharacter<NpcCharacter>(*this);
|
||||||
|
|
||||||
driver.Attach(vehicle.GetEntNum());
|
driver.SetVehicle(&vehicle, 0);
|
||||||
|
|
||||||
if (vehicle.seats.size() > 0)
|
|
||||||
{
|
|
||||||
driver.SetPosition(vehicle.seats[0].position);
|
|
||||||
}
|
|
||||||
|
|
||||||
driver.SetMainAnim("vehicle_drive");
|
|
||||||
driver.SetYaw(0.5f * glm::pi<float>());
|
|
||||||
|
|
||||||
driver.vehicle = &vehicle;
|
|
||||||
driver.is_driver = true;
|
|
||||||
driver.VehicleChanged();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,9 @@
|
|||||||
#include "world.hpp"
|
#include "world.hpp"
|
||||||
#include "vehicle.hpp"
|
#include "vehicle.hpp"
|
||||||
#include "character.hpp"
|
#include "character.hpp"
|
||||||
|
#include "usable.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
@ -22,6 +25,8 @@ public:
|
|||||||
virtual void PlayerViewAnglesChanged(Player& player, float yaw, float pitch) override;
|
virtual void PlayerViewAnglesChanged(Player& player, float yaw, float pitch) override;
|
||||||
virtual void PlayerLeft(Player& player) override;
|
virtual void PlayerLeft(Player& player) override;
|
||||||
|
|
||||||
|
std::optional<std::pair<Usable&, const UseTarget&>> GetBestUseTarget(const glm::vec3& pos) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreatePlayerCharacter(Player& player);
|
void CreatePlayerCharacter(Player& player);
|
||||||
void RemovePlayerCharacter(Player& player);
|
void RemovePlayerCharacter(Player& player);
|
||||||
|
|||||||
111
src/game/player_character.cpp
Normal file
111
src/game/player_character.cpp
Normal file
@ -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() {}
|
||||||
34
src/game/player_character.hpp
Normal file
34
src/game/player_character.hpp
Normal file
@ -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_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
32
src/game/usable.hpp
Normal file
32
src/game/usable.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
struct UseTarget
|
||||||
|
{
|
||||||
|
uint32_t id = 0;
|
||||||
|
glm::vec3 position;
|
||||||
|
std::string desc;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PlayerCharacter;
|
||||||
|
|
||||||
|
class Usable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const std::vector<UseTarget>& GetUseTargets() const { return use_targets_; }
|
||||||
|
|
||||||
|
virtual void Use(PlayerCharacter& character, uint32_t target_id) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<UseTarget> use_targets_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -51,7 +51,9 @@ public:
|
|||||||
|
|
||||||
void SetSteering(bool analog, float value = 0.0f);
|
void SetSteering(bool analog, float value = 0.0f);
|
||||||
|
|
||||||
|
const std::string& GetModelName() const { return model_name_; }
|
||||||
const std::shared_ptr<const assets::VehicleModel>& GetModel() const { return model_; }
|
const std::shared_ptr<const assets::VehicleModel>& GetModel() const { return model_; }
|
||||||
|
const glm::vec3& GetColor() const { return color_; }
|
||||||
|
|
||||||
virtual ~Vehicle();
|
virtual ~Vehicle();
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
game::Entity* game::World::GetEntity(net::EntNum entnum)
|
||||||
{
|
{
|
||||||
auto it = ents_.find(entnum);
|
auto it = ents_.find(entnum);
|
||||||
|
|||||||
@ -31,6 +31,7 @@ public:
|
|||||||
void RegisterEntity(std::unique_ptr<Entity> ent);
|
void RegisterEntity(std::unique_ptr<Entity> ent);
|
||||||
|
|
||||||
virtual void Update(int64_t delta_time);
|
virtual void Update(int64_t delta_time);
|
||||||
|
void FinishFrame();
|
||||||
|
|
||||||
// events
|
// events
|
||||||
virtual void PlayerJoined(Player& player) {}
|
virtual void PlayerJoined(Player& player) {}
|
||||||
|
|||||||
@ -46,6 +46,10 @@ bool game::view::CharacterView::ProcessMsg(net::EntMsgType type, net::InMessage&
|
|||||||
case net::EMSG_UPDATE:
|
case net::EMSG_UPDATE:
|
||||||
return ProcessUpdateMsg(msg);
|
return ProcessUpdateMsg(msg);
|
||||||
|
|
||||||
|
case net::EMSG_ATTACH:
|
||||||
|
skip_lerps_ = 1;
|
||||||
|
return Super::ProcessMsg(type, msg);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Super::ProcessMsg(type, msg);
|
return Super::ProcessMsg(type, msg);
|
||||||
}
|
}
|
||||||
@ -190,6 +194,12 @@ bool game::view::CharacterView::ReadState(net::InMessage& msg)
|
|||||||
states_[0].loco_phase -= 1.0f;
|
states_[0].loco_phase -= 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skip_lerps_ > 0)
|
||||||
|
{
|
||||||
|
states_[0] = states_[1];
|
||||||
|
skip_lerps_--;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -63,6 +63,7 @@ private:
|
|||||||
CharacterSyncState sync_;
|
CharacterSyncState sync_;
|
||||||
CharacterViewState states_[2];
|
CharacterViewState states_[2];
|
||||||
float update_time_ = 0.0f;
|
float update_time_ = 0.0f;
|
||||||
|
size_t skip_lerps_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
// #include <glm/gtx/common.hpp>
|
// #include <glm/gtx/common.hpp>
|
||||||
|
|
||||||
|
#include "vehicleview.hpp"
|
||||||
|
|
||||||
game::view::ClientSession::ClientSession(App& app) : app_(app) {}
|
game::view::ClientSession::ClientSession(App& app) : app_(app) {}
|
||||||
|
|
||||||
bool game::view::ClientSession::ProcessMessage(net::InMessage& msg)
|
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
|
void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) const
|
||||||
{
|
{
|
||||||
glm::vec3 start(0.0f, 0.0f, 2.0f);
|
glm::vec3 start(0.0f, 0.0f, 2.0f);
|
||||||
|
float distance = 5.0f;
|
||||||
|
|
||||||
if (follow_ent_)
|
if (follow_ent_)
|
||||||
{
|
{
|
||||||
auto ent = world_->GetEntity(follow_ent_);
|
auto ent = world_->GetEntity(follow_ent_);
|
||||||
if (ent)
|
if (ent)
|
||||||
start += ent->GetRoot().local.position;
|
{
|
||||||
|
start += ent->GetRoot().GetGlobalPosition();
|
||||||
|
|
||||||
|
if (dynamic_cast<const VehicleView*>(ent))
|
||||||
|
distance = 8.0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float yaw_cos = glm::cos(yaw_);
|
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_);
|
float pitch_sin = glm::sin(pitch_);
|
||||||
glm::vec3 dir(yaw_cos * pitch_cos, yaw_sin * pitch_cos, pitch_sin);
|
glm::vec3 dir(yaw_cos * pitch_cos, yaw_sin * pitch_cos, pitch_sin);
|
||||||
|
|
||||||
float distance = 5.0f;
|
|
||||||
glm::vec3 end = start - dir * distance;
|
glm::vec3 end = start - dir * distance;
|
||||||
|
|
||||||
//start.z -= 0.5f; // shift this a bit to make it better when occluded
|
//start.z -= 0.5f; // shift this a bit to make it better when occluded
|
||||||
|
|||||||
@ -88,7 +88,7 @@ void game::view::EntityView::DrawNametag(const DrawArgs& args)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// calc screen position
|
// 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;
|
glm::vec4 clip_pos = args.view_proj * world_pos;
|
||||||
if (clip_pos.w == 0.0f)
|
if (clip_pos.w == 0.0f)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -120,4 +120,6 @@ void sv::Server::Update()
|
|||||||
{
|
{
|
||||||
client->Update();
|
client->Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
game_.FinishFrame();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user