Remote menus - client side part

This commit is contained in:
tovjemam 2026-03-28 16:40:15 +01:00
parent d43ca2a45a
commit b69fe6d744
17 changed files with 638 additions and 74 deletions

View File

@ -84,6 +84,8 @@ set(CLIENT_ONLY_SOURCES
"src/gameview/entityview.cpp" "src/gameview/entityview.cpp"
"src/gameview/mapinstanceview.hpp" "src/gameview/mapinstanceview.hpp"
"src/gameview/mapinstanceview.cpp" "src/gameview/mapinstanceview.cpp"
"src/gameview/remote_menu_view.hpp"
"src/gameview/remote_menu_view.cpp"
"src/gameview/simple_entity_view.hpp" "src/gameview/simple_entity_view.hpp"
"src/gameview/simple_entity_view.cpp" "src/gameview/simple_entity_view.cpp"
"src/gameview/skinning_ubo.hpp" "src/gameview/skinning_ubo.hpp"

View File

@ -6,6 +6,7 @@
#include "net/outmessage.hpp" #include "net/outmessage.hpp"
#include "assets/cache.hpp" #include "assets/cache.hpp"
#include "gameview/worldview.hpp" #include "gameview/worldview.hpp"
#include "gameview/utils.hpp"
App::App() : App::App() :
gui_(dlist_, assets::CacheManager::GetFont("data/comic32.font")) gui_(dlist_, assets::CacheManager::GetFont("data/comic32.font"))
@ -64,7 +65,7 @@ void App::Frame()
if (menu_) if (menu_)
{ {
auto menu_size = menu_->MeasureSize(); auto menu_size = menu_->MeasureSize();
menu_->Draw(gui_, glm::vec2(viewport_size_) - menu_size - 10.0f); menu_->Draw(gui_, (glm::vec2(viewport_size_) - menu_size) * 0.5f);
} }
gui_.Render(); gui_.Render();
@ -114,20 +115,6 @@ void App::Disconnected(const std::string& reason)
session_.reset(); session_.reset();
} }
static bool InputToMenuInput(game::PlayerInputType in, gui::MenuInput& mi)
{
switch (in)
{
case game::IN_FORWARD: mi = gui::MI_UP; return true;
case game::IN_BACKWARD: mi = gui::MI_DOWN; return true;
case game::IN_LEFT: mi = gui::MI_LEFT; return true;
case game::IN_RIGHT: mi = gui::MI_RIGHT; return true;
case game::IN_JUMP: mi = gui::MI_ENTER; return true;
case game::IN_CROUCH: mi = gui::MI_BACK; return true;
default: return false;
}
};
void App::Input(game::PlayerInputType in, bool pressed, bool repeated) void App::Input(game::PlayerInputType in, bool pressed, bool repeated)
{ {
if (in == game::IN_MENU && pressed) if (in == game::IN_MENU && pressed)
@ -215,7 +202,7 @@ static void AddSlider(gui::Menu& menu, std::string text, int& value, int min, in
else if (value > max) else if (value > max)
value = max; value = max;
slider.SetSelectionText(std::to_string(value)); slider.SetSelectionText(std::to_string(value) + " %");
changed(); changed();
}; };
@ -226,6 +213,7 @@ static void AddSlider(gui::Menu& menu, std::string text, int& value, int min, in
void App::OpenSettings() void App::OpenSettings()
{ {
menu_ = std::make_unique<gui::Menu>(); menu_ = std::make_unique<gui::Menu>();
menu_->SetTitle("nastavení");
AddSlider(*menu_, "jak moc to řve", volume_, 0, 100, [this]{ AddSlider(*menu_, "jak moc to řve", volume_, 0, 100, [this]{
ApplyVolume(); ApplyVolume();

View File

@ -86,6 +86,10 @@ void game::Game::PlayerInput(Player& player, PlayerInputType type, bool enabled)
break; break;
} }
case IN_DEBUG3:
DisplayTestMenu(player);
break;
default: { default: {
auto world = FindPlayerWorld(player); auto world = FindPlayerWorld(player);
if (world) if (world)
@ -201,3 +205,36 @@ game::EnterableWorld* game::Game::FindPlayerWorld(Player& player) const
return it->second.world; 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);
});
}

View File

@ -42,6 +42,8 @@ private:
PlayerGameInfo& GetPlayerInfo(Player& player); PlayerGameInfo& GetPlayerInfo(Player& player);
EnterableWorld* FindPlayerWorld(Player& player) const; EnterableWorld* FindPlayerWorld(Player& player) const;
void DisplayTestMenu(Player& player);
private: private:
std::shared_ptr<OpenWorld> openworld_; std::shared_ptr<OpenWorld> openworld_;
std::shared_ptr<EnterableWorld> testworld_; std::shared_ptr<EnterableWorld> testworld_;

View File

@ -23,6 +23,9 @@ bool game::Player::ProcessMsg(net::MessageType type, net::InMessage& msg)
case net::MSG_VIEWANGLES: case net::MSG_VIEWANGLES:
return ProcessViewAnglesMsg(msg); return ProcessViewAnglesMsg(msg);
case net::MSG_MENUACTION:
return ProcessMenuActionMsg(msg);
default: default:
return false; return false;
} }
@ -30,20 +33,8 @@ bool game::Player::ProcessMsg(net::MessageType type, net::InMessage& msg)
void game::Player::Update() void game::Player::Update()
{ {
if (world_ != known_world_) SyncWorld();
{ SendMenuMsgs();
SendWorldMsg();
known_world_ = world_;
known_ents_.clear();
return; // send updates next frame
}
if (world_)
{
SendWorldUpdateMsg();
SyncEntities();
}
} }
void game::Player::SetWorld(World* world) void game::Player::SetWorld(World* world)
@ -77,11 +68,58 @@ void game::Player::SetUseTarget(const std::string& text, const std::string& erro
msg.Write<net::UseDelayQ>(delay); msg.Write<net::UseDelayQ>(delay);
} }
game::RemoteMenu& game::Player::DisplayMenu(std::string title)
{
if (remote_menu_)
throw std::runtime_error("cannot display multiple menus atm");
net::MenuId id = ++menu_id_;
remote_menu_ = std::make_unique<RemoteMenu>(id, std::move(title));
// send msg
auto msg = BeginMsg(net::MSG_REMOTEMENU);
msg.Write(id);
msg.Write(net::MMSG_CREATE);
return *remote_menu_;
}
void game::Player::CloseMenu(const RemoteMenu& menu)
{
if (&menu != remote_menu_.get())
return;
// send msg
auto msg = BeginMsg(net::MSG_REMOTEMENU);
msg.Write(menu.GetId());
msg.Write(net::MMSG_CLOSE);
remote_menu_.reset();
}
game::Player::~Player() game::Player::~Player()
{ {
game_.PlayerLeft(*this); game_.PlayerLeft(*this);
} }
void game::Player::SyncWorld()
{
if (world_ != known_world_)
{
SendWorldMsg();
known_world_ = world_;
known_ents_.clear();
return; // send updates next frame
}
if (world_)
{
SendWorldUpdateMsg();
SyncEntities();
}
}
void game::Player::SendWorldMsg() void game::Player::SendWorldMsg()
{ {
MSGDEBUG(std::cout << "seding CHWORLD" << std::endl;) MSGDEBUG(std::cout << "seding CHWORLD" << std::endl;)
@ -255,6 +293,20 @@ bool game::Player::ProcessViewAnglesMsg(net::InMessage& msg)
return true; return true;
} }
bool game::Player::ProcessMenuActionMsg(net::InMessage& msg)
{
net::MenuId id;
net::MenuActionType type;
if (!msg.Read(id) || !msg.Read(type))
return false;
if (!remote_menu_ || remote_menu_->GetId() != id)
return true; // not illegal, might be just a late message
return remote_menu_->ProcessActionMsg(msg, type);
}
void game::Player::Input(PlayerInputType type, bool enabled) void game::Player::Input(PlayerInputType type, bool enabled)
{ {
if (enabled) if (enabled)
@ -265,3 +317,20 @@ void game::Player::Input(PlayerInputType type, bool enabled)
game_.PlayerInput(*this, type, enabled); game_.PlayerInput(*this, type, enabled);
} }
void game::Player::SendMenuMsgs()
{
if (!remote_menu_)
return;
remote_menu_->Update();
auto menu_msg = remote_menu_->GetMsg();
if (menu_msg.empty())
return;
auto msg = BeginMsg();
msg.Write(menu_msg);
remote_menu_->ResetMsg();
}

View File

@ -12,6 +12,8 @@
#include "player_input.hpp" #include "player_input.hpp"
#include "remote_menu.hpp"
namespace game namespace game
{ {
@ -34,6 +36,10 @@ public:
void SendChat(const std::string& text); void SendChat(const std::string& text);
void SetUseTarget(const std::string& text, const std::string& error_text, float delay); void SetUseTarget(const std::string& text, const std::string& error_text, float delay);
RemoteMenu& DisplayMenu(std::string title);
void CloseMenu(const RemoteMenu& menu);
bool HasOpenMenu() const { return (bool)remote_menu_; }
const std::string& GetName() const { return name_; } const std::string& GetName() const { return name_; }
PlayerInputFlags GetInput() const { return in_; } PlayerInputFlags GetInput() const { return in_; }
@ -44,6 +50,7 @@ public:
private: private:
// world sync // world sync
void SyncWorld();
void SendWorldMsg(); void SendWorldMsg();
void SendWorldUpdateMsg(); void SendWorldUpdateMsg();
@ -56,10 +63,14 @@ private:
// msg handlers // msg handlers
bool ProcessInputMsg(net::InMessage& msg); bool ProcessInputMsg(net::InMessage& msg);
bool ProcessViewAnglesMsg(net::InMessage& msg); bool ProcessViewAnglesMsg(net::InMessage& msg);
bool ProcessMenuActionMsg(net::InMessage& msg);
// events // events
void Input(PlayerInputType type, bool enabled); void Input(PlayerInputType type, bool enabled);
// menu sync
void SendMenuMsgs();
private: private:
Game& game_; Game& game_;
std::string name_; std::string name_;
@ -73,6 +84,11 @@ private:
net::EntNum cam_ent_ = 0; net::EntNum cam_ent_ = 0;
glm::vec3 cull_pos_ = glm::vec3(0.0f); glm::vec3 cull_pos_ = glm::vec3(0.0f);
// menus
// TODO: allow more menus
net::MenuId menu_id_ = 0;
std::unique_ptr<RemoteMenu> remote_menu_;
}; };
} }

View File

@ -1,9 +1,8 @@
#include "remote_menu.hpp" #include "remote_menu.hpp"
#include "net/defs.hpp" #include "net/defs.hpp"
game::RemoteMenuItem::RemoteMenuItem(RemoteMenu& menu, RemoteMenuItemType type, std::string text) : menu_(menu), type_(type) game::RemoteMenuItem::RemoteMenuItem(RemoteMenu& menu, RemoteMenuItemType type, std::string text) : menu_(menu), type_(type), text_(std::move(text))
{ {
} }
void game::RemoteMenuItem::SetText(std::string text) void game::RemoteMenuItem::SetText(std::string text)
@ -26,7 +25,7 @@ void game::RemoteMenuItem::SetSelection(std::string selection)
menu_.items_synced_ = false; menu_.items_synced_ = false;
} }
game::RemoteMenu::RemoteMenu(net::MenuId id) : id_(id) game::RemoteMenu::RemoteMenu(net::MenuId id, std::string title) : id_(id), title_(std::move(title))
{ {
} }
@ -41,6 +40,20 @@ game::RemoteMenuItem& game::RemoteMenu::AddItem(RemoteMenuItemType type, std::st
return item_ref; return item_ref;
} }
bool game::RemoteMenu::ProcessActionMsg(net::InMessage& msg, net::MenuActionType type)
{
switch (type)
{
case net::MA_CLICK:
return ProcessClickMsg(msg);
case net::MA_SELECT:
return ProcessSelectMsg(msg);
case net::MA_HOVER:
return ProcessHoverMsg(msg);
default:
return false;
}
}
void game::RemoteMenu::Update() void game::RemoteMenu::Update()
{ {
@ -51,7 +64,7 @@ void game::RemoteMenu::Update()
if (!synced_) if (!synced_)
{ {
auto msg = BeginMenuMsg(net::MMSG_UPDATE); auto msg = BeginMenuMsg(net::MMSG_UPDATE);
msg.Write(net::MenuTitle(title_));
msg.Write<net::MenuItemCount>(items_.size()); msg.Write<net::MenuItemCount>(items_.size());
for (auto& item : items_) for (auto& item : items_)
{ {
@ -103,3 +116,62 @@ net::OutMessage game::RemoteMenu::BeginMenuMsg(net::MenuMessageType type)
msg.Write(type); msg.Write(type);
return msg; return msg;
} }
bool game::RemoteMenu::ProcessClickMsg(net::InMessage& msg)
{
net::MenuItemId id;
if (!msg.Read(id))
return false;
if (id > items_.size())
return true; // not illegal
auto& cb = items_[id]->on_click_;
if (cb)
cb();
return true;
}
bool game::RemoteMenu::ProcessSelectMsg(net::InMessage& msg)
{
net::MenuItemId id;
net::MenuSelectDir dir;
if (!msg.Read(id) || !msg.Read(dir))
return false;
if (id > items_.size())
return true; // not illegal
int idir = dir ? 1 : -1;
auto& cb = items_[id]->on_select_;
if (cb)
cb(idir);
return true;
}
bool game::RemoteMenu::ProcessHoverMsg(net::InMessage& msg)
{
net::MenuItemId id;
if (!msg.Read(id))
return false;
if (id > items_.size())
return true; // not illegal
if (id == hovered_)
return true; // already hovered
hovered_ = id;
auto& cb = items_[id]->on_hovered_;
if (cb)
cb();
return true;
}

View File

@ -6,6 +6,7 @@
#include "net/defs.hpp" #include "net/defs.hpp"
#include "net/msg_producer.hpp" #include "net/msg_producer.hpp"
#include "net/outmessage.hpp" #include "net/outmessage.hpp"
#include "net/inmessage.hpp"
namespace game namespace game
{ {
@ -54,17 +55,27 @@ private:
class RemoteMenu : public net::MsgProducer class RemoteMenu : public net::MsgProducer
{ {
public: public:
RemoteMenu(net::MenuId id); RemoteMenu(net::MenuId id, std::string title);
RemoteMenuItem& AddItem(RemoteMenuItemType type, std::string text); RemoteMenuItem& AddItem(RemoteMenuItemType type, std::string text);
net::MenuId GetId() const { return id_; }
bool ProcessActionMsg(net::InMessage& msg, net::MenuActionType type);
void Update(); void Update();
private: private:
net::OutMessage BeginMenuMsg(net::MenuMessageType type); net::OutMessage BeginMenuMsg(net::MenuMessageType type);
// action handlers
bool ProcessClickMsg(net::InMessage& msg);
bool ProcessSelectMsg(net::InMessage& msg);
bool ProcessHoverMsg(net::InMessage& msg);
private: private:
net::MenuId id_; net::MenuId id_;
std::string title_;
bool synced_ = false; bool synced_ = false;
bool items_synced_ = false; bool items_synced_ = false;
std::vector<std::unique_ptr<RemoteMenuItem>> items_; std::vector<std::unique_ptr<RemoteMenuItem>> items_;

View File

@ -4,15 +4,16 @@
#include <iostream> #include <iostream>
// #include <glm/gtx/common.hpp> // #include <glm/gtx/common.hpp>
#include "vehicleview.hpp"
#include "utils/version.hpp" #include "utils/version.hpp"
#include "utils.hpp"
#include "vehicleview.hpp"
game::view::ClientSession::ClientSession(App& app) : app_(app), use_target_hud_(app.GetTime()) game::view::ClientSession::ClientSession(App& app) : app_(app), use_target_hud_(app.GetTime())
{ {
// send login // send login
auto msg = BeginMsg(net::MSG_ID); auto msg = BeginMsg(net::MSG_ID);
msg.Write<net::Version>(FEKAL_VERSION); msg.Write<net::Version>(FEKAL_VERSION);
msg.Write(net::PlayerName(app.GetUserName())); msg.Write(net::PlayerName(app.GetUserName()));
} }
bool game::view::ClientSession::ProcessMessage(net::InMessage& msg) bool game::view::ClientSession::ProcessMessage(net::InMessage& msg)
@ -49,6 +50,9 @@ bool game::view::ClientSession::ProcessSingleMessage(net::MessageType type, net:
case net::MSG_USETARGET: case net::MSG_USETARGET:
return ProcessUseTargetMsg(msg); return ProcessUseTargetMsg(msg);
case net::MSG_REMOTEMENU:
return ProcessMenuMsg(msg);
default: default:
// try pass the msg to world // try pass the msg to world
if (world_ && world_->ProcessMsg(type, msg)) if (world_ && world_->ProcessMsg(type, msg))
@ -60,13 +64,13 @@ bool game::view::ClientSession::ProcessSingleMessage(net::MessageType type, net:
void game::view::ClientSession::Input(game::PlayerInputType in, bool pressed, bool repeated) void game::view::ClientSession::Input(game::PlayerInputType in, bool pressed, bool repeated)
{ {
if (pressed && ProcessMenuInput(in))
return;
if (repeated) if (repeated)
return; return;
SendInput(in, pressed); SendInput(in, pressed);
} }
void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch) void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch)
@ -102,6 +106,8 @@ void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams&
} }
use_target_hud_.Draw(gui); use_target_hud_.Draw(gui);
DrawMenus(gui);
} }
void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) const void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) const
@ -129,7 +135,7 @@ void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) con
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
eye = world_->CameraSweep(start, end); eye = world_->CameraSweep(start, end);
view = glm::lookAt(eye, eye + dir, glm::vec3(0, 0, 1)); view = glm::lookAt(eye, eye + dir, glm::vec3(0, 0, 1));
} }
@ -183,6 +189,39 @@ bool game::view::ClientSession::ProcessUseTargetMsg(net::InMessage& msg)
return true; return true;
} }
bool game::view::ClientSession::ProcessMenuMsg(net::InMessage& msg)
{
net::MenuId id;
net::MenuMessageType type;
if (!msg.Read(id) || !msg.Read(type))
return false;
switch (type)
{
case net::MMSG_CREATE: {
if (FindMenu(id))
return false;
remote_menus_.push_back(std::make_unique<RemoteMenuView>(*this, id));
return true;
}
case net::MMSG_CLOSE: {
std::erase_if(remote_menus_, [id](auto& menu) { return menu->GetId() == id; });
return true;
}
default: {
auto menu = FindMenu(id);
if (!menu)
return false;
return menu->ProcessMessage(type, msg);
}
}
}
void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui) void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui)
{ {
// glm::mat4 view = glm::lookAt(glm::vec3(15.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, -13.0f), glm::vec3(0.0f, // glm::mat4 view = glm::lookAt(glm::vec3(15.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, -13.0f), glm::vec3(0.0f,
@ -199,7 +238,8 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar
// glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view; // glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view;
game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, eye, glm::ivec2(params.screen_width, params.screen_height), 500.0f); game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, eye,
glm::ivec2(params.screen_width, params.screen_height), 500.0f);
world_->Draw(draw_args); world_->Draw(draw_args);
glm::mat4 camera_world = glm::inverse(view); glm::mat4 camera_world = glm::inverse(view);
@ -209,10 +249,10 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar
void game::view::ClientSession::SendInput(game::PlayerInputType type, bool enable) void game::view::ClientSession::SendInput(game::PlayerInputType type, bool enable)
{ {
auto msg = BeginMsg(net::MSG_IN); auto msg = BeginMsg(net::MSG_IN);
uint8_t val = type; uint8_t val = type;
if (enable) if (enable)
val |= 128; val |= 128;
msg.Write(val); msg.Write(val);
} }
void game::view::ClientSession::SendViewAngles(float time) void game::view::ClientSession::SendViewAngles(float time)
@ -236,3 +276,37 @@ void game::view::ClientSession::SendViewAngles(float time)
view_pitch_q_.value = pitch_q.value; view_pitch_q_.value = pitch_q.value;
last_send_time_ = time; last_send_time_ = time;
} }
void game::view::ClientSession::DrawMenus(gui::Context& gui) const
{
if (remote_menus_.empty())
return;
auto& top_menu = *remote_menus_.back();
top_menu.Draw(gui, glm::vec2(20.0f, 100.0f));
}
bool game::view::ClientSession::ProcessMenuInput(game::PlayerInputType in)
{
if (remote_menus_.empty())
return false;
gui::MenuInput mi;
if (!InputToMenuInput(in, mi))
return false;
remote_menus_.back()->Input(mi);
return true;
}
game::view::RemoteMenuView* game::view::ClientSession::FindMenu(net::MenuId id) const
{
for (auto& menu : remote_menus_)
{
if (menu->GetId() == id)
return menu.get();
}
return nullptr;
}

View File

@ -11,6 +11,7 @@
#include "net/msg_producer.hpp" #include "net/msg_producer.hpp"
#include "game/player_input.hpp" #include "game/player_input.hpp"
#include "gui/use_target_hud.hpp" #include "gui/use_target_hud.hpp"
#include "remote_menu_view.hpp"
class App; class App;
@ -41,12 +42,17 @@ private:
bool ProcessCameraMsg(net::InMessage& msg); bool ProcessCameraMsg(net::InMessage& msg);
bool ProcessChatMsg(net::InMessage& msg); bool ProcessChatMsg(net::InMessage& msg);
bool ProcessUseTargetMsg(net::InMessage& msg); bool ProcessUseTargetMsg(net::InMessage& msg);
bool ProcessMenuMsg(net::InMessage& msg);
void DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui); void DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui);
void SendInput(game::PlayerInputType type, bool enable); void SendInput(game::PlayerInputType type, bool enable);
void SendViewAngles(float time); void SendViewAngles(float time);
void DrawMenus(gui::Context& gui) const;
bool ProcessMenuInput(game::PlayerInputType in);
RemoteMenuView* FindMenu(net::MenuId id) const;
private: private:
App& app_; App& app_;
@ -60,6 +66,8 @@ private:
float last_send_time_ = 0.0f; float last_send_time_ = 0.0f;
gui::UseTargetHud use_target_hud_; gui::UseTargetHud use_target_hud_;
std::vector<std::unique_ptr<RemoteMenuView>> remote_menus_;
}; };
} // namespace game::view } // namespace game::view

View File

@ -0,0 +1,144 @@
#include "remote_menu_view.hpp"
#include "game/remote_menu.hpp"
#include "client_session.hpp"
game::view::RemoteMenuView::RemoteMenuView(ClientSession& session, net::MenuId id) : session_(session), id_(id) {}
bool game::view::RemoteMenuView::ProcessMessage(net::MenuMessageType type, net::InMessage& msg)
{
switch (type)
{
case net::MMSG_UPDATE:
return ProcessUpdateMsg(msg);
case net::MMSG_ITEM_UPDATE_TEXT:
return ProcessItemUpdateTextMsg(msg);
case net::MMSG_ITEM_UPDATE_SELECTION:
return ProcessItemUpdateSelectionMsg(msg);
default:
return false;
}
}
bool game::view::RemoteMenuView::ProcessUpdateMsg(net::InMessage& msg)
{
net::MenuTitle title;
net::MenuItemCount itemcount;
if (!msg.Read(title) || !msg.Read(itemcount))
return false;
SetTitle(title);
Clear();
for (net::MenuItemId i = 0; i < itemcount; ++i)
{
game::RemoteMenuItemType type;
net::MenuItemText text;
net::MenuItemSelection selection;
if (!msg.Read(type) || !msg.Read(text) || !msg.Read(selection))
return false;
switch (type)
{
case RM_BUTTON:
{
auto& btn = Add<gui::ButtonMenuItem>(text);
btn.SetClickCallback([this, i] { OnItemClick(i); });
break;
}
case RM_SELECT:
{
auto& select = Add<gui::SelectMenuItem>(text);
select.SetSelectionText(selection);
select.SetClickCallback([this, i] { OnItemClick(i); });
select.SetSwitchCallback([this, i] (int dir) { OnItemSelectionChange(i, dir); });
break;
}
default:
break;
}
}
return true;
}
bool game::view::RemoteMenuView::ProcessItemUpdateTextMsg(net::InMessage& msg)
{
net::MenuItemId idx;
net::MenuItemText text;
if (!msg.Read(idx) || !msg.Read(text))
return false;
if (idx >= GetNumItems())
return false;
auto& item = GetItem(idx);
if (auto btn = dynamic_cast<gui::ButtonMenuItem*>(&item); btn)
{
btn->SetText(text);
}
else
{
return false;
}
return true;
}
bool game::view::RemoteMenuView::ProcessItemUpdateSelectionMsg(net::InMessage& msg)
{
net::MenuItemId idx;
net::MenuItemSelection selection;
if (!msg.Read(idx) || !msg.Read(selection))
return false;
if (idx >= GetNumItems())
return false;
auto& item = GetItem(idx);
auto select = dynamic_cast<gui::SelectMenuItem*>(&item);
if (!select)
return false;
select->SetSelectionText(selection);
return true;
}
void game::view::RemoteMenuView::OnItemClick(net::MenuItemId idx)
{
auto msg = BeginActionMsg(net::MA_CLICK);
msg.Write(idx);
}
void game::view::RemoteMenuView::OnItemSelectionChange(net::MenuItemId idx, int dir)
{
if (dir == 0)
return;
auto msg = BeginActionMsg(net::MA_SELECT);
msg.Write(idx);
msg.Write<net::MenuSelectDir>(dir < 0 ? 0 : 1);
}
void game::view::RemoteMenuView::OnFocusChanged()
{
auto msg = BeginActionMsg(net::MA_HOVER);
msg.Write<net::MenuItemId>(GetFocusedItemIndex());
}
net::OutMessage game::view::RemoteMenuView::BeginActionMsg(net::MenuActionType type)
{
auto msg = session_.BeginMsg(net::MSG_MENUACTION);
msg.Write(id_);
msg.Write(type);
return msg;
}

View File

@ -0,0 +1,46 @@
#pragma once
#include "gui/menu.hpp"
// #include "net/msg_producer.hpp"
#include "net/inmessage.hpp"
#include "net/outmessage.hpp"
#include "net/defs.hpp"
namespace game::view
{
class ClientSession;
class RemoteMenuView : public gui::Menu
{
public:
using Super = gui::Menu;
RemoteMenuView(ClientSession& session, net::MenuId id);
bool ProcessMessage(net::MenuMessageType type, net::InMessage& msg);
net::MenuId GetId() const { return id_; }
private:
bool ProcessUpdateMsg(net::InMessage& msg);
bool ProcessItemUpdateTextMsg(net::InMessage& msg);
bool ProcessItemUpdateSelectionMsg(net::InMessage& msg);
void OnItemClick(net::MenuItemId idx);
void OnItemSelectionChange(net::MenuItemId idx, int dir);
protected:
void OnFocusChanged() override;
net::OutMessage BeginActionMsg(net::MenuActionType type);
private:
ClientSession& session_;
net::MenuId id_;
};
}

19
src/gameview/utils.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include "game/player_input.hpp"
#include "gui/menu.hpp"
inline bool InputToMenuInput(game::PlayerInputType in, gui::MenuInput& mi)
{
switch (in)
{
case game::IN_FORWARD: mi = gui::MI_UP; return true;
case game::IN_BACKWARD: mi = gui::MI_DOWN; return true;
case game::IN_LEFT: mi = gui::MI_LEFT; return true;
case game::IN_RIGHT: mi = gui::MI_RIGHT; return true;
case game::IN_JUMP: mi = gui::MI_ENTER; return true;
case game::IN_USE: mi = gui::MI_ENTER; return true;
case game::IN_CROUCH: mi = gui::MI_BACK; return true;
default: return false;
}
};

View File

@ -96,6 +96,7 @@ glm::vec2 gui::Context::MeasureText(std::string_view text)
} }
else if (cp == '\n') else if (cp == '\n')
{ {
size.x = glm::max(size.x, cursor.x);
cursor.x = 0.0f; cursor.x = 0.0f;
cursor.y += line_height; cursor.y += line_height;
continue; continue;
@ -122,9 +123,9 @@ glm::vec2 gui::Context::MeasureText(std::string_view text)
continue; // Dont even have "missing" glyph, font is shit continue; // Dont even have "missing" glyph, font is shit
cursor.x += glyph->advance; cursor.x += glyph->advance;
size.x = glm::max(size.x, cursor.x);
} }
size.x = glm::max(size.x, cursor.x);
size.y = cursor.y + line_height; size.y = cursor.y + line_height;
return size; return size;

View File

@ -4,17 +4,36 @@
// Menu // Menu
static constexpr float menu_title_height = 50.0f;
void gui::Menu::Clear()
{
items_.clear();
focus_ = 0;
}
void gui::Menu::Draw(Context& ctx, const glm::vec2& pos) const void gui::Menu::Draw(Context& ctx, const glm::vec2& pos) const
{ {
// background // background
auto size = MeasureSize(); auto size = MeasureSize();
ctx.DrawRect(pos, pos + size, 0x55000000); ctx.DrawRect(pos, pos + size, 0x88000000);
// draw title
glm::vec2 title_size(size.x, menu_title_height);
ctx.DrawRect(pos, pos + title_size, 0x55000000);
ctx.DrawTextAligned(title_, pos + title_size * 0.5f, glm::vec2(-0.5f));
// draw items
DrawMenuItemArgs args(ctx);
args.size = itemsize_;
args.pos.x = pos.x;
glm::vec2 cursor = pos;
for (size_t i = 0; i < items_.size(); ++i) for (size_t i = 0; i < items_.size(); ++i)
{ {
items_[i]->Draw(DrawMenuItemArgs(ctx, cursor, focus_ == i)); args.focused = focus_ == i;
cursor.y += items_[i]->GetSize().y; args.pos.y = pos.y + menu_title_height + static_cast<float>(i) * itemsize_.y;
items_[i]->Draw(args);
} }
} }
@ -37,18 +56,14 @@ void gui::Menu::Input(MenuInput in)
} }
} }
void gui::Menu::SetTitle(std::string title)
{
title_ = std::move(title);
}
glm::vec2 gui::Menu::MeasureSize() const glm::vec2 gui::Menu::MeasureSize() const
{ {
glm::vec2 size(0.0f); return glm::vec2(itemsize_.x, menu_title_height + itemsize_.y * static_cast<float>(items_.size()));
for (const auto& item : items_)
{
const auto& itemsize = item->GetSize();
size.x = glm::max(size.x, itemsize.x);
size.y += itemsize.y;
}
return size;
} }
void gui::Menu::SwitchFocus(int dir) void gui::Menu::SwitchFocus(int dir)
@ -56,7 +71,17 @@ void gui::Menu::SwitchFocus(int dir)
if (items_.empty()) if (items_.empty())
return; return;
focus_ = (focus_ + items_.size() + dir) % items_.size(); size_t old_focus = focus_;
if (items_.empty())
focus_ = 0;
else
focus_ = (focus_ + items_.size() + dir) % items_.size();
if (focus_ != old_focus)
{
OnFocusChanged();
}
} }
// ButtonMenuItem // ButtonMenuItem
@ -64,13 +89,12 @@ void gui::Menu::SwitchFocus(int dir)
gui::ButtonMenuItem::ButtonMenuItem(std::string text) gui::ButtonMenuItem::ButtonMenuItem(std::string text)
: text_(std::move(text)) : text_(std::move(text))
{ {
size_ = glm::vec2(300.0f, 30.0f);
} }
void gui::ButtonMenuItem::Draw(const DrawMenuItemArgs& args) const void gui::ButtonMenuItem::Draw(const DrawMenuItemArgs& args) const
{ {
Super::Draw(args); Super::Draw(args);
glm::vec2 center = args.pos + glm::vec2(0.0f, size_.y * 0.5f); glm::vec2 center = args.pos + glm::vec2(10.0f, args.size.y * 0.5f);
args.ctx.DrawTextAligned(text_, center, glm::vec2(0.0f, -0.5f), args.focused ? COLOR_FOCUSED : COLOR_INACTIVE); args.ctx.DrawTextAligned(text_, center, glm::vec2(0.0f, -0.5f), args.focused ? COLOR_FOCUSED : COLOR_INACTIVE);
} }
@ -96,12 +120,30 @@ void gui::SelectMenuItem::Draw(const DrawMenuItemArgs& args) const
{ {
Super::Draw(args); Super::Draw(args);
char buffer[128]; auto text_size = args.ctx.MeasureText(select_text_);
size_t len = snprintf(buffer, sizeof(buffer), "< %s >", select_text_.c_str());
glm::vec2 center_right = args.pos + glm::vec2(size_.x, size_.y * 0.5f); glm::vec2 cursor = args.pos + glm::vec2(args.size.x - 10.0f, args.size.y * 0.5f);
args.ctx.DrawTextAligned(std::string_view(buffer, len), center_right, glm::vec2(-1.0f, -0.5f), cursor.y -= text_size.y * 0.5f; // centered
args.focused ? COLOR_FOCUSED : COLOR_INACTIVE);
uint32_t text_color = args.focused ? COLOR_FOCUSED : COLOR_INACTIVE;
uint32_t arrow_color = 0xFFFFFFFF;
float arrow_width = 0.0f;
if (args.focused)
{
arrow_width = args.ctx.MeasureText("<").x;
cursor.x -= arrow_width;
args.ctx.DrawText(">", cursor, arrow_color);
}
cursor.x -= text_size.x;
args.ctx.DrawText(select_text_, cursor, text_color);
if (args.focused)
{
cursor.x -= arrow_width;
args.ctx.DrawText("<", cursor, arrow_color);
}
} }
void gui::SelectMenuItem::Input(MenuInput in) void gui::SelectMenuItem::Input(MenuInput in)

View File

@ -24,9 +24,10 @@ struct DrawMenuItemArgs
{ {
Context& ctx; Context& ctx;
glm::vec2 pos; glm::vec2 pos;
glm::vec2 size;
bool focused; bool focused;
DrawMenuItemArgs(Context& ctx, const glm::vec2& pos, bool focused) : ctx(ctx), pos(pos), focused(focused) {} DrawMenuItemArgs(Context& ctx) : ctx(ctx) {}
}; };
class Menu class Menu
@ -43,16 +44,31 @@ public:
return item_ref; return item_ref;
} }
void Clear();
void Draw(Context& ctx, const glm::vec2& pos) const; void Draw(Context& ctx, const glm::vec2& pos) const;
void Input(MenuInput in); void Input(MenuInput in);
void SetTitle(std::string title);
void SetItemSize(const glm::vec2& itemsize) { itemsize_ = itemsize; }
size_t GetFocusedItemIndex() const { return focus_; }
size_t GetNumItems() const { return items_.size(); }
MenuItem& GetItem(size_t idx) const { return *items_[idx]; }
glm::vec2 MeasureSize() const; glm::vec2 MeasureSize() const;
protected:
virtual void OnFocusChanged() {}
private: private:
void SwitchFocus(int dir); void SwitchFocus(int dir);
private: private:
std::string title_;
std::vector<std::unique_ptr<MenuItem>> items_; std::vector<std::unique_ptr<MenuItem>> items_;
glm::vec2 itemsize_ = glm::vec2(300.0f, 40.0f);
size_t focus_ = 0; size_t focus_ = 0;
}; };
@ -73,7 +89,7 @@ public:
virtual ~MenuItem() = default; virtual ~MenuItem() = default;
protected: protected:
glm::vec2 size_; glm::vec2 size_ = glm::vec2(0.0f);
}; };
@ -89,6 +105,8 @@ public:
void SetClickCallback(std::function<void()> click_cb); void SetClickCallback(std::function<void()> click_cb);
void SetText(std::string text) { text_ = std::move(text); }
virtual ~ButtonMenuItem() = default; virtual ~ButtonMenuItem() = default;
private: private:

View File

@ -23,6 +23,9 @@ enum MessageType : uint8_t
// VIEWANGLES <ViewYawQ> <ViewPitchQ> // VIEWANGLES <ViewYawQ> <ViewPitchQ>
MSG_VIEWANGLES, MSG_VIEWANGLES,
// MENUACTION <MenuId> <MenuActionType> ...
MSG_MENUACTION,
/*~~~~~~~~ Session ~~~~~~~~*/ /*~~~~~~~~ Session ~~~~~~~~*/
// CHAT <ChatMessage> // CHAT <ChatMessage>
MSG_CHAT, MSG_CHAT,
@ -37,7 +40,7 @@ enum MessageType : uint8_t
// USETARGET ... // USETARGET ...
MSG_USETARGET, MSG_USETARGET,
// REMOTEMENU ... // REMOTEMENU <MenuId> <MenuMessageType> ...
MSG_REMOTEMENU, MSG_REMOTEMENU,
/*~~~~~~~~ Entity ~~~~~~~~*/ /*~~~~~~~~ Entity ~~~~~~~~*/
@ -159,6 +162,7 @@ enum MenuMessageType
}; };
using MenuId = uint8_t; using MenuId = uint8_t;
using MenuTitle = FixedStr<64>;
using MenuItemId = uint8_t; using MenuItemId = uint8_t;
using MenuItemCount = MenuItemId; using MenuItemCount = MenuItemId;
@ -166,5 +170,16 @@ using MenuItemCount = MenuItemId;
using MenuItemText = FixedStr<64>; using MenuItemText = FixedStr<64>;
using MenuItemSelection = FixedStr<64>; using MenuItemSelection = FixedStr<64>;
// menu actions
enum MenuActionType
{
MA_CLICK,
MA_SELECT,
MA_HOVER,
};
using MenuSelectDir = uint8_t; // 0=left, 1=right
} // namespace net } // namespace net