Add markers

This commit is contained in:
tovjemam 2026-05-22 22:36:31 +02:00
parent cd3e1aab4f
commit 308c39b8f8
21 changed files with 470 additions and 110 deletions

View File

@ -82,6 +82,8 @@ set(CLIENT_ONLY_SOURCES
"src/gameview/entityview.cpp"
"src/gameview/mapinstanceview.hpp"
"src/gameview/mapinstanceview.cpp"
"src/gameview/markerview.hpp"
"src/gameview/markerview.cpp"
"src/gameview/remote_menu_view.hpp"
"src/gameview/remote_menu_view.cpp"
"src/gameview/simple_entity_view.hpp"
@ -142,6 +144,8 @@ set(SERVER_ONLY_SOURCES
"src/game/game.cpp"
"src/game/mapinstance.hpp"
"src/game/mapinstance.cpp"
"src/game/marker.hpp"
"src/game/marker.cpp"
"src/game/npc_character.hpp"
"src/game/npc_character.cpp"
"src/game/openworld.hpp"

View File

@ -1,8 +1,20 @@
#include "entity.hpp"
#include "world.hpp"
#include "player.hpp"
game::Entity::Entity(World& world, net::EntType viewtype) : Scheduler(world.GetTime()), world_(world), entnum_(world.GetNewEntnum()), viewtype_(viewtype) {}
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
game::Entity::Entity(World& world, net::EntType viewtype) : Scheduler(world.GetTime()), world_(world), entnum_(world.GetNewEntnum()), viewtype_(viewtype)
{
if (viewtype == net::ET_NONE)
{
visible_ = false;
}
}
void game::Entity::SendInitData(Player& player, net::OutMessage& msg) const
{
@ -69,6 +81,18 @@ void game::Entity::PlaySound(const std::string& name, float volume, float pitch)
msg.Write<net::SoundPitchQ>(pitch);
}
bool game::Entity::IsVisibleTo(const Player& player) const
{
if (!visible_)
return false;
// max distance check
if (glm::distance2(root_.GetGlobalPosition(), player.GetCullPos()) > (max_distance_ * max_distance_))
return false;
return true;
}
void game::Entity::WriteNametag(net::OutMessage& msg) const
{
msg.Write(net::NameTag{nametag_});

View File

@ -49,6 +49,7 @@ public:
const TransformNode& GetRoot() const { return root_; }
const Transform& GetRootTransform() const { return root_.local; }
bool IsVisibleTo(const Player& player) const;
float GetMaxDistance() const { return max_distance_; }
virtual ~Entity() = default;
@ -73,6 +74,7 @@ protected:
TransformNode root_;
Entity* parent_ = nullptr;
bool visible_ = true;
float max_distance_ = 700.0f;
bool removed_ = false;

View File

@ -18,14 +18,8 @@ static uint32_t GetRandomColor24()
game::Game::Game()
{
openworld_ = std::make_shared<OpenWorld>();
all_worlds_.push_back(openworld_.get());
testworld_ = std::make_shared<EnterableWorld>("testarena");
all_worlds_.push_back(testworld_.get());
garage_ = std::make_shared<TuningWorld>(*this, *openworld_, glm::vec3(0.0f), 0.0f, "garage");
all_worlds_.push_back(garage_.get());
openworld_ = std::make_shared<OpenWorld>(*this);
AddWorld(openworld_.get());
}
void game::Game::Update()
@ -44,6 +38,11 @@ void game::Game::FinishFrame()
}
}
void game::Game::AddWorld(World* world)
{
all_worlds_.push_back(world);
}
void game::Game::PlayerJoined(Player& player)
{
BroadcastChat(player.GetName() + "^r se připoojil jupí jupí jupííí");
@ -71,29 +70,29 @@ void game::Game::PlayerInput(Player& player, PlayerInputType type, bool enabled)
{
switch (type)
{
case IN_DEBUG2: {
if (!enabled)
return;
// case IN_DEBUG2: {
// if (!enabled)
// return;
// auto& player_info = players_.at(&player);
// // auto& player_info = players_.at(&player);
// if (player_info.world == openworld_.get())
// {
// MovePlayerToWorld(player_info, testworld_.get(), test_spawn, 0.0f, true);
// }
// else
// {
// MovePlayerToWorld(player_info, openworld_.get(), openworld_spawn, 0.0f, true);
// // if (player_info.world == openworld_.get())
// // {
// // MovePlayerToWorld(player_info, testworld_.get(), test_spawn, 0.0f, true);
// // }
// // else
// // {
// // MovePlayerToWorld(player_info, openworld_.get(), openworld_spawn, 0.0f, true);
// // }
// MovePlayerToTuning(player);
// break;
// }
MovePlayerToTuning(player);
break;
}
case IN_DEBUG3:
DisplayTestMenu(player);
break;
// case IN_DEBUG3:
// // DisplayTestMenu(player);
// break;
default: {
auto world = FindPlayerWorld(player);
@ -224,69 +223,3 @@ game::EnterableWorld* game::Game::FindPlayerWorld(Player& player) const
return it->second.world;
}
void game::Game::DisplayTestMenu(Player& player)
{
if (player.HasOpenMenu())
return;
auto& menu = player.DisplayMenu("test");
auto& btn_echo = menu.AddItem(RM_BUTTON, "echo");
btn_echo.SetOnClick([&player] {
player.SendChat("echo test");
});
auto& btn_bc = menu.AddItem(RM_BUTTON, "broadcast");
btn_bc.SetOnClick([this, &player] {
BroadcastChat(player.GetName() + "^r mele hovna");
});
int test = 0;
auto& sel_test = menu.AddItem(RM_SELECT, "výběr");
sel_test.SetOnSelect([test, &sel_test] (int dir) mutable {
test += dir;
sel_test.SetSelection(std::to_string(test));
});
sel_test.SetSelection(std::to_string(test));
auto& btn_close = menu.AddItem(RM_BUTTON, "zavřít");
btn_close.SetOnClick([&menu, &player] {
player.CloseMenu(menu);
});
}
void game::Game::MovePlayerToTuning(Player& player)
{
auto& player_info = GetPlayerInfo(player);
if (player_info.world != openworld_.get())
return;
if (garage_->IsOccupied())
{
player.SendChat("bohužel tam teď oxiduje nějakej píčus " + garage_->GetOccupantName() + "^r!");
return;
}
auto character = player_info.world->GetPlayerCharacter(player);
if (!character)
return;
auto vehicle = character->GetVehicle();
if (!vehicle)
{
player.SendChat("nemáš vehikl!!!");
return;
}
if (vehicle->GetPassenger(0) != character)
{
player.SendChat("nejsi ridič!!");
return;
}
MovePlayerToWorld(player_info, garage_.get(), glm::vec3(0.0f), 0.0f, true);
}

View File

@ -27,6 +27,8 @@ public:
void Update();
void FinishFrame();
void AddWorld(World* world);
void PlayerJoined(Player& player);
void PlayerViewAnglesChanged(Player& player, float yaw, float pitch);
void PlayerInput(Player& player, PlayerInputType type, bool enabled);
@ -45,9 +47,6 @@ private:
PlayerGameInfo& GetPlayerInfo(Player& player);
EnterableWorld* FindPlayerWorld(Player& player) const;
void DisplayTestMenu(Player& player);
void MovePlayerToTuning(Player& player);
private:
std::shared_ptr<OpenWorld> openworld_;
std::shared_ptr<EnterableWorld> testworld_;

77
src/game/marker.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "marker.hpp"
#include "world.hpp"
#include "net/utils.hpp"
static const glm::mat4 identity(1.0f);
game::Marker::Marker(World& world, const MarkerInfo& info) : Super(world, net::ET_MARKER), Usable(identity), info_(info)
{
root_.local.position = info_.position;
root_.UpdateMatrix();
}
void game::Marker::SendInitData(Player& player, net::OutMessage& msg) const
{
Super::SendInitData(player, msg);
net::PositionQ pos_q;
net::EncodePosition(info_.position, pos_q);
msg.Write(info_.type);
net::WritePositionQ(msg, pos_q);
net::WriteRGB(msg, info_.color);
}
void game::Marker::Update()
{
Super::Update();
}
bool game::Marker::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res)
{
if (!query_cb_)
return false;
return query_cb_(character, res);
}
void game::Marker::Use(PlayerCharacter& character, uint32_t target_id)
{
if (!use_cb_)
return;
use_cb_(character);
}
void game::Marker::SetUseTarget(const std::string& name, MarkerQueryCallback query, MarkerUseCallback use)
{
if (!query_obj_)
{
query_obj_ = std::make_unique<btCollisionObject>();
static btSphereShape query_sphere(1.0f);
query_obj_->setCollisionShape(&query_sphere);
query_obj_->setWorldTransform(root_.local.ToBtTransform());
query_obj_->setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE);
collision::SetObjectInfo(query_obj_.get(), collision::OT_ENTITY, collision::OF_USABLE, this);
world_.GetBtWorld().addCollisionObject(query_obj_.get());
}
query_cb_ = query;
use_cb_ = use;
use_targets_.clear();
use_targets_.emplace_back(this, 0, root_.GetGlobalPosition(), name);
}
game::Marker::~Marker()
{
if (query_obj_)
{
world_.GetBtWorld().removeCollisionObject(query_obj_.get());
}
}

41
src/game/marker.hpp Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include "entity.hpp"
#include "marker_info.hpp"
#include "usable.hpp"
#include <memory>
namespace game
{
using MarkerQueryCallback = std::function<bool(PlayerCharacter&, UseTargetQueryResult&)>;
using MarkerUseCallback = std::function<void(PlayerCharacter&)>;
class Marker : public Entity, public Usable
{
public:
using Super = Entity;
Marker(World& world, const MarkerInfo& info);
virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
virtual void Update() override;
virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
void SetUseTarget(const std::string& name, MarkerQueryCallback query, MarkerUseCallback use);
virtual ~Marker() override;
private:
MarkerInfo info_;
std::unique_ptr<btCollisionObject> query_obj_;
MarkerQueryCallback query_cb_;
MarkerUseCallback use_cb_;
};
}

24
src/game/marker_info.hpp Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
#include <string>
#include <glm/glm.hpp>
namespace game
{
enum MarkerType : uint8_t
{
MARKER_FOOT,
MARKER_VEHICLE,
};
struct MarkerInfo
{
glm::vec3 position;
MarkerType type;
uint32_t color;
std::string icon;
};
}

View File

@ -4,11 +4,13 @@
#include "player.hpp"
#include "vehicle.hpp"
#include "player_character.hpp"
#include "npc_character.hpp"
#include "drivable_vehicle.hpp"
#include "destroyed_object.hpp"
#include "marker.hpp"
#include "tuning_world.hpp"
#include "game.hpp"
namespace game
{
@ -45,7 +47,7 @@ static uint32_t GetRandomColor24()
return (b << 16) | (g << 8) | r;
}
game::OpenWorld::OpenWorld() : EnterableWorld("openworld")
game::OpenWorld::OpenWorld(Game& game) : EnterableWorld("openworld"), game_(game)
{
// spawn bots
for (size_t i = 0; i < 100; ++i)
@ -80,6 +82,8 @@ game::OpenWorld::OpenWorld() : EnterableWorld("openworld")
daytime_offset_ = static_cast<float>(rand() % 24);
CreateTuningGarage(glm::vec3(0.0f, 0.0f, 0.0f), 0.0f);
}
void game::OpenWorld::Update(int64_t delta_time)
@ -90,6 +94,17 @@ void game::OpenWorld::Update(int64_t delta_time)
SetDayTime(static_cast<float>(GetTime()) * 0.001f * timespeed + daytime_offset_);
}
void game::OpenWorld::PlayerInput(Player& player, PlayerInputType type, bool enabled)
{
if (type == IN_DEBUG2 && enabled)
{
RecoverPlayer(player);
return;
}
Super::PlayerInput(player, type, enabled);
}
game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle()
{
game::VehicleTuning tuning;
@ -157,3 +172,131 @@ void game::OpenWorld::SpawnBot()
auto& driver = Spawn<NpcCharacter>(npc_tuning);
driver.SetVehicle(&vehicle, 0);
}
void game::OpenWorld::CreateTuningGarage(const glm::vec3& position, float yaw)
{
auto garage = std::make_shared<TuningWorld>(game_, *this, position, yaw, "garage");
game_.AddWorld(garage.get());
MarkerInfo marker_info{};
marker_info.position = position;
marker_info.type = MARKER_VEHICLE;
marker_info.color = 0x884400;
marker_info.icon = "tuning";
auto& marker = Spawn<Marker>(marker_info);
marker.SetUseTarget("vject do tunírny",
[garage](PlayerCharacter& character, UseTargetQueryResult& res) {
auto player = character.GetPlayer();
auto vehicle = character.GetVehicle();
if (!vehicle)
{
res.enabled = false;
res.error_text = "nemáš vehikl";
return true;
}
if (vehicle->GetPassenger(0) != &character)
{
return false; // not driver
}
if (garage->IsOccupied())
{
res.enabled = false;
res.error_text = "někdo tam už oxiduje";
return true;
}
res.enabled = true;
res.error_text = nullptr;
res.delay = 0.2f;
return true;
},
[this, garage, &marker](PlayerCharacter& character) {
auto player = character.GetPlayer();
game_.MovePlayerToWorld(*player, *garage, true, glm::vec3(0.0f), 0.0f);
marker.SetNametag(player->GetName());
}
);
garage->SetOnExit([&marker]() {
marker.SetNametag(std::string());
});
}
void game::OpenWorld::RecoverPlayer(Player& player)
{
auto character = GetPlayerCharacter(player);
if (!character)
return;
auto vehicle = character->GetVehicle();
if (!vehicle)
{
auto pos = character->GetRoot().GetGlobalPosition();
glm::vec3 recovery;
if (GetRecoveryPosition(pos, recovery))
{
character->SetPosition(recovery);
}
else
{
player.SendChat("nejsi pod zemí");
}
return;
}
if (vehicle->GetPassenger(0) != character)
return; // not driver
auto pos = vehicle->GetRoot().GetGlobalPosition();
glm::vec3 recovery;
if (GetRecoveryPosition(pos, recovery))
{
vehicle->SetPosition(recovery);
}
else
{
player.SendChat("nejsi pod zemí");
}
}
static bool RecoveryRaycast(btCollisionWorld& bt_world, const glm::vec3& pos, glm::vec3& hit)
{
btVector3 bt_from(pos.x, pos.y, 100.0f);
btVector3 bt_to(pos.x, pos.y, -100.0f);
btCollisionWorld::ClosestRayResultCallback cb(bt_from, bt_to);
bt_world.rayTest(bt_from, bt_to, cb);
if (!cb.hasHit())
return false;
hit = glm::vec3(cb.m_hitPointWorld.x(), cb.m_hitPointWorld.y(), cb.m_hitPointWorld.z());
return true;
}
bool game::OpenWorld::GetRecoveryPosition(const glm::vec3& current, glm::vec3& recovery)
{
glm::vec3 start = current;
start = glm::max(start, glm::vec3(-2500.0f, -2500.0f, -1000.0f));
start = glm::min(start, glm::vec3(3400.0f, 3100.0f, 1000.0f));
if (!RecoveryRaycast(GetBtWorld(), start, recovery))
{
recovery = glm::vec3(0.0f, 0.0f, 5.0f);
return true;
}
if (recovery.z - 5.0f < current.z)
{
return false; // already above ground
}
recovery.z += 5.0f;
return true;
}

View File

@ -6,20 +6,29 @@
namespace game
{
class Game;
class OpenWorld : public EnterableWorld
{
public:
using Super = EnterableWorld;
OpenWorld();
OpenWorld(Game& game);
virtual void Update(int64_t delta_time) override;
virtual void PlayerInput(Player& player, PlayerInputType type, bool enabled) override;
private:
game::DrivableVehicle& SpawnRandomVehicle();
void SpawnBot();
void CreateTuningGarage(const glm::vec3& position, float yaw);
void RecoverPlayer(Player& player);
bool GetRecoveryPosition(const glm::vec3& current, glm::vec3& recovery);
private:
Game& game_;
std::vector<NpcCharacter*> npcs_;
float daytime_offset_ = 0.0f;
};

View File

@ -249,14 +249,7 @@ void game::Player::SyncEntities()
bool game::Player::ShouldSeeEntity(const Entity& entity) const
{
// max distance check
float max_dist = entity.GetMaxDistance();
if (glm::distance2(entity.GetRoot().GetGlobalPosition(), cull_pos_) > (max_dist * max_dist))
return false;
// TODO: custom callback
return true;
return entity.IsVisibleTo(*this);
}
void game::Player::SendInitEntity(const Entity& entity)

View File

@ -46,6 +46,8 @@ public:
float GetViewYaw() const { return view_yaw_; }
float GetViewPitch() const { return view_pitch_; }
const glm::vec3 GetCullPos() const { return cull_pos_; }
~Player();
private:

View File

@ -219,6 +219,9 @@ void game::TuningWorld::Reset()
{
player_->CloseMenu(*menu_);
game_.MovePlayerToWorld(*player_, exit_world_, true, exit_pos_, exit_yaw_);
if (exit_cb_)
exit_cb_();
}
player_ = nullptr;

View File

@ -20,6 +20,8 @@ public:
virtual void OnVehicleJoined(DrivableVehicle& vehicle);
virtual void OnPlayerLeaving(Player& player);
void SetOnExit(std::function<void()> cb) { exit_cb_ = cb; }
bool IsOccupied() const { return player_ != nullptr; }
const std::string& GetOccupantName() const;
@ -46,6 +48,8 @@ private:
VehicleTuning tuning_;
std::function<void()> exit_cb_;
};

View File

@ -44,6 +44,8 @@ public:
protected:
std::vector<UseTarget> use_targets_;
private:
const glm::mat4& matrix_;
};

View File

@ -333,7 +333,7 @@ void game::Vehicle::UpdateCrash()
}
else
{
if (crash_intensity_ > 300.0f)
if (crash_intensity_ > 1000.0f)
{
float volume = RandomFloat(0.9f, 1.2f);
float pitch = RandomFloat(1.0f, 1.3f);
@ -355,7 +355,7 @@ void game::Vehicle::UpdateCrash()
}
PlaySound("crash", volume, pitch);
no_crash_frames_ = 3 + rand() % 10;
no_crash_frames_ = 7 + rand() % 10;
}
}

View File

@ -136,9 +136,11 @@ struct UseTargetAabbCallback : public btBroadphaseAabbCallback
float dist = glm::distance(pos, pos_world);
if (dist < 2.0f && dist < best_dist)
{
if (!usable->QueryUseTarget(character, target.id, best_res))
game::UseTargetQueryResult res{};
if (!usable->QueryUseTarget(character, target.id, res))
continue;
best_res = res;
best_dist = dist;
best_target = &target;
}

View File

@ -0,0 +1,58 @@
#include "markerview.hpp"
#include "net/utils.hpp"
#include "assets/cache.hpp"
game::view::MarkerView::MarkerView(WorldView& world, net::InMessage& msg) : Super(world, msg)
{
if (!Init(msg))
{
throw EntityInitError();
}
}
void game::view::MarkerView::Update(const UpdateInfo& info)
{
root_.local.rotation = glm::quat(glm::vec3(0.0f, 0.0f, info.time * 0.5f));
root_.UpdateMatrix();
}
void game::view::MarkerView::Draw(const DrawArgs& args)
{
Super::Draw(args);
if (!model_)
return;
const auto& mesh = *model_->GetMesh();
for (const auto& surface : mesh.surfaces)
{
gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface;
cmd.matrices = &root_.matrix;
cmd.color = &color_;
args.dlist.AddSurface(cmd);
}
}
bool game::view::MarkerView::Init(net::InMessage& msg)
{
net::PositionQ pos_q;
uint32_t color;
if (!msg.Read(marker_type_) || !net::ReadPositionQ(msg, pos_q) || !net::ReadRGB(msg, color))
return false;
net::DecodePosition(pos_q, root_.local.position);
root_.UpdateMatrix();
color_ = glm::unpackUnorm4x8(color | 0xFF000000);
if (marker_type_ == MARKER_FOOT || marker_type_ == MARKER_VEHICLE)
{
model_ = assets::CacheManager::GetModel("data/marker.mdl");
}
return true;
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "entityview.hpp"
#include "game/marker_info.hpp"
#include "assets/model.hpp"
namespace game::view
{
class MarkerView : public EntityView
{
public:
using Super = EntityView;
MarkerView(WorldView& world, net::InMessage& msg);
// virtual bool ProcessMsg(net::EntMsgType type, net::InMessage& msg) override;
// virtual bool ProcessUpdateMsg(net::InMessage* msg) override;
virtual void Update(const UpdateInfo& info) override;
virtual void Draw(const DrawArgs& args) override;
private:
bool Init(net::InMessage& msg);
private:
MarkerType marker_type_;
glm::vec4 color_;
std::shared_ptr<const assets::Model> model_;
};
}

View File

@ -5,6 +5,7 @@
#include "simple_entity_view.hpp"
#include "characterview.hpp"
#include "vehicleview.hpp"
#include "markerview.hpp"
#include "client_session.hpp"
#include "draw_args.hpp"
@ -232,6 +233,10 @@ bool game::view::WorldView::ProcessEntSpawnMsg(net::InMessage& msg)
entslot = std::make_unique<VehicleView>(*this, msg);
break;
case net::ET_MARKER:
entslot = std::make_unique<MarkerView>(*this, msg);
break;
default:
ents_.erase(entnum);
return false; // unknown type

View File

@ -93,6 +93,7 @@ enum EntType : uint8_t
ET_SIMPLE,
ET_CHARACTER,
ET_VEHICLE,
ET_MARKER,
ET_COUNT,
};