RigsofRods
Soft-body Physics Simulation
GUI_MultiplayerClientList.cpp
Go to the documentation of this file.
1 /*
2  This source file is part of Rigs of Rods
3  Copyright 2005-2012 Pierre-Michel Ricordel
4  Copyright 2007-2012 Thomas Fischer
5  Copyright 2013-2020 Petr Ohlidal
6 
7  For more information, see http://www.rigsofrods.org/
8 
9  Rigs of Rods is free software: you can redistribute it and/or modify
10  it under the terms of the GNU General Public License version 3, as
11  published by the Free Software Foundation.
12 
13  Rigs of Rods is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20 */
21 
25 
27 
28 
30 
31 #include "Application.h"
32 #include "ActorManager.h"
33 #include "GameContext.h"
34 #include "GUIManager.h"
35 #include "GUIUtils.h"
36 #include "Language.h"
37 #include "Network.h"
38 
39 #include <vector>
40 
41 using namespace RoR;
42 using namespace GUI;
43 using namespace Ogre;
44 
46 {
47 #if USE_SOCKETW
48  // vectorpos may change, so we must look up ID again.
49  int peeropts_menu_active_user_uid = -1;
50  if (m_peeropts_menu_active_user_vectorpos != -1)
51  {
52  peeropts_menu_active_user_uid = (int)m_users[m_peeropts_menu_active_user_vectorpos].uniqueid;
53  }
54  m_peeropts_menu_active_user_vectorpos = -1;
55 
56  // Update the user data
57  m_users = App::GetNetwork()->GetUserInfos();
58  m_users.insert(m_users.begin(), App::GetNetwork()->GetLocalUserData());
59 
60  m_users_peeropts = App::GetNetwork()->GetAllUsersPeerOpts();
61  m_users_peeropts.insert(m_users_peeropts.begin(), BitMask_t(0));
62 
63  // Restore the vectorpos
64  for (int i = 0; i < (int)m_users.size(); i++)
65  {
66  if ((int)m_users[i].uniqueid == peeropts_menu_active_user_uid)
67  {
68  m_peeropts_menu_active_user_vectorpos = i;
69  }
70  }
71 #endif // USE_SOCKETW
72 }
73 
75 {
76 #if USE_SOCKETW
77  if (m_users.empty())
78  return; // UpdateClients() wasn't called yet.
79 
80  if (!m_icons_cached)
81  {
82  this->CacheIcons();
83  }
84 
86 
87  ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse |
88  ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar;
89  const float content_width = 225.f;
90  ImGui::SetNextWindowContentWidth(content_width);
91  ImGui::SetNextWindowPos(ImVec2(
92  ImGui::GetIO().DisplaySize.x - (content_width + (2*ImGui::GetStyle().WindowPadding.x) + theme.screen_edge_padding.x),
93  theme.screen_edge_padding.y));
94 
95  int y = 20 + (ImGui::GetTextLineHeightWithSpacing() * m_users.size());
96 
97  if (App::GetNetwork()->GetNetQuality() != 0)
98  {
99  y += 20;
100  }
101 
102  ImGui::SetNextWindowSize(ImVec2((content_width + (2*ImGui::GetStyle().WindowPadding.x)), y));
103  ImGui::PushStyleColor(ImGuiCol_WindowBg, theme.semitransparent_window_bg);
104  ImGui::Begin("Peers", nullptr, flags);
105 
106  const RoRnet::UserInfo& local_user = m_users[0]; // See `UpdateClients()`
107  int vectorpos = 0;
108  for (RoRnet::UserInfo const& user: m_users)
109  {
110  ImGui::PushID(user.uniqueid);
111  const ImVec2 hover_tl = ImGui::GetCursorScreenPos();
112 
113  // PeerOptions popup menu
114  if (user.uniqueid == local_user.uniqueid)
115  {
116  ImGui::Dummy(ImVec2(ImGui::CalcTextSize(" < ").x + ImGui::GetStyle().FramePadding.x*2 , ImGui::GetTextLineHeight()));
117  }
118  else if (ImGui::SmallButton(" < "))
119  {
120  if (m_peeropts_menu_active_user_vectorpos == vectorpos)
121  {
122  m_peeropts_menu_active_user_vectorpos = -1; // hide menu
123  }
124  else
125  {
126  m_peeropts_menu_active_user_vectorpos = vectorpos; // show menu
127  m_peeropts_menu_corner_tl = hover_tl - ImVec2(PEEROPTS_MENU_CONTENT_WIDTH + ImGui::GetStyle().WindowPadding.x*3 + PEEROPTS_MENU_MARGIN, 0);
128  }
129  }
130  ImGui::SameLine();
131 
132  // Icon sizes: flag(16x11), auth(16x16), up(16x16), down(16x16)
133  Ogre::TexturePtr flag_tex;
134  Ogre::TexturePtr auth_tex;
135  Ogre::TexturePtr down_tex;
136  Ogre::TexturePtr up_tex;
137 
138  // Stream state indicators
139  if (user.uniqueid != local_user.uniqueid &&
141  {
142  switch (App::GetGameContext()->GetActorManager()->CheckNetworkStreamsOk(user.uniqueid))
143  {
144  case 0: down_tex = m_icon_arrow_down_red; break;
145  case 1: down_tex = m_icon_arrow_down; break;
146  case 2: down_tex = m_icon_arrow_down_grey; break;
147  default:;
148  }
149 
150 
151  switch (App::GetGameContext()->GetActorManager()->CheckNetRemoteStreamsOk(user.uniqueid))
152  {
153  case 0: up_tex = m_icon_arrow_up_red; break;
154  case 1: up_tex = m_icon_arrow_up; break;
155  case 2: up_tex = m_icon_arrow_up_grey; break;
156  default:;
157  }
158  }
159  // Always invoke to keep usernames aligned
160  this->DrawIcon(down_tex, ImVec2(8.f, ImGui::GetTextLineHeight()));
161  this->DrawIcon(up_tex, ImVec2(8.f, ImGui::GetTextLineHeight()));
162 
163  // Auth icon
164  if (user.authstatus & RoRnet::AUTH_ADMIN ) { auth_tex = m_icon_flag_red; }
165  else if (user.authstatus & RoRnet::AUTH_MOD ) { auth_tex = m_icon_flag_blue; }
166  else if (user.authstatus & RoRnet::AUTH_RANKED) { auth_tex = m_icon_flag_green; }
167 
168  this->DrawIcon(auth_tex, ImVec2(14.f, ImGui::GetTextLineHeight()));
169 
170  // Country flag
171  StringVector parts = StringUtil::split(user.language, "_");
172  if (parts.size() == 2)
173  {
174  StringUtil::toLowerCase(parts[1]);
175  flag_tex = FetchIcon((parts[1] + ".png").c_str());
176  this->DrawIcon(flag_tex, ImVec2(16.f, ImGui::GetTextLineHeight()));
177  }
178 
179  // Player name
180  ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetStyle().ItemSpacing.x); // Some extra padding
181  ColourValue col = App::GetNetwork()->GetPlayerColor(user.colournum);
182  ImGui::TextColored(ImVec4(col.r, col.g, col.b, col.a), "%s", user.username);
183  const ImVec2 hover_br = hover_tl + ImVec2(content_width, ImGui::GetTextLineHeight());
184  const float HOVER_TL_SHIFTX = 20.f; // leave the [<] button (PeerOptions submenu) out of the hover check.
185  const bool hovered
186  = hover_br.x > ImGui::GetIO().MousePos.x
187  && hover_br.y > ImGui::GetIO().MousePos.y
188  && ImGui::GetIO().MousePos.x > (hover_tl.x + HOVER_TL_SHIFTX)
189  && ImGui::GetIO().MousePos.y > hover_tl.y;
190 
191  // Tooltip
192  if (hovered)
193  {
194  ImGui::BeginTooltip();
195 
196  // TextDisabled() are captions, Text() are values
197 
198  ImGui::TextDisabled("%s: ",_LC("MultiplayerClientList", "user name"));
199  ImGui::SameLine();
200  ImGui::TextColored(ImVec4(col.r, col.g, col.b, col.a), "%s", user.username);
201  ImGui::Separator();
202 
203  ImGui::TextDisabled("%s: ",_LC("MultiplayerClientList", "user uid"));
204  ImGui::SameLine();
205  ImGui::Text("%d", user.uniqueid);
206 
207  ImGui::TextDisabled("%s", _LC("MultiplayerClientList", "user language: "));
208  ImGui::SameLine();
209  ImGui::Text("%s", parts[0].c_str());
210 
211  ImGui::TextDisabled("%s", _LC("MultiplayerClientList", "user country: "));
212  ImGui::SameLine();
213  ImGui::Text("%s", parts[1].c_str());
214  if (flag_tex)
215  {
216  ImGui::SameLine();
217  ImGui::Image(reinterpret_cast<ImTextureID>(flag_tex->getHandle()),
218  ImVec2(flag_tex->getWidth(), flag_tex->getHeight()));
219  }
220 
221  ImGui::Separator();
222  ImGui::TextDisabled("%s", _LC("MultiplayerClientList", "user authentication level"));
223  if (auth_tex)
224  {
225  ImGui::Image(reinterpret_cast<ImTextureID>(auth_tex->getHandle()),
226  ImVec2(auth_tex->getWidth(), auth_tex->getHeight()));
227  ImGui::SameLine();
228  }
229 
230  ImGui::Text("%s", App::GetNetwork()->UserAuthToStringLong(user).c_str());
231 
232  // Stream state
233  if (user.uniqueid != local_user.uniqueid &&
235  {
236  ImGui::Separator();
237  ImGui::TextDisabled("%s", _LC("MultiplayerClientList", "truck loading state"));
238  if (down_tex)
239  {
240  ImGui::Image(reinterpret_cast<ImTextureID>(down_tex->getHandle()),
241  ImVec2(down_tex->getWidth(), down_tex->getHeight()));
242  ImGui::SameLine();
243  }
244  switch (App::GetGameContext()->GetActorManager()->CheckNetworkStreamsOk(user.uniqueid))
245  {
246  case 0: ImGui::Text("%s", _LC("MultiplayerClientList", "Truck loading errors")); break;
247  case 1: ImGui::Text("%s", _LC("MultiplayerClientList", "Truck loaded correctly, no errors")); break;
248  case 2: ImGui::Text("%s", _LC("MultiplayerClientList", "no truck loaded")); break;
249  default:; // never happens
250  }
251 
252  ImGui::TextDisabled("%s", _LC("MultiplayerClientList", "remote truck loading state"));
253  if (up_tex)
254  {
255  ImGui::Image(reinterpret_cast<ImTextureID>(up_tex->getHandle()),
256  ImVec2(up_tex->getWidth(), up_tex->getHeight()));
257  ImGui::SameLine();
258  }
259  switch (App::GetGameContext()->GetActorManager()->CheckNetRemoteStreamsOk(user.uniqueid))
260  {
261  case 0: ImGui::Text("%s", _LC("MultiplayerClientList", "Remote Truck loading errors")); break;
262  case 1: ImGui::Text("%s", _LC("MultiplayerClientList", "Remote Truck loaded correctly, no errors")); break;
263  case 2: ImGui::Text("%s", _LC("MultiplayerClientList", "No Trucks loaded")); break;
264  default:; // never happens
265  }
266  }
267 
268  ImGui::EndTooltip();
269  }
270  ImGui::PopID(); // user.uniqueid
271  vectorpos++;
272  }
273 
274  if (App::GetNetwork()->GetNetQuality() != 0)
275  {
276  ImGui::Separator();
277  ImGui::Image(reinterpret_cast<ImTextureID>(m_icon_warn_triangle->getHandle()),
278  ImVec2(m_icon_warn_triangle->getWidth(), m_icon_warn_triangle->getHeight()));
279  ImGui::SameLine();
280  ImGui::TextColored(App::GetGuiManager()->GetTheme().error_text_color, "%s", _LC("MultiplayerClientList", "Slow Network Download"));
281  }
282 
283  ImGui::End();
284  ImGui::PopStyleColor(1); // WindowBg
285 
286  this->DrawPeerOptionsMenu();
287 #endif // USE_SOCKETW
288 }
289 
290 void MpClientList::DrawPeerOptCheckbox(const BitMask_t flag, const std::string& label)
291 {
292  int uid = (int)m_users[m_peeropts_menu_active_user_vectorpos].uniqueid;
293  bool flagval = m_users_peeropts[m_peeropts_menu_active_user_vectorpos] & flag;
294 
295  if (ImGui::Checkbox(label.c_str(), &flagval))
296  {
298  App::GetGameContext()->PushMessage(Message(peeropt_msg, new PeerOptionsRequest{ uid, flag }));
300  }
301 }
302 
303 void RoR::GUI::MpClientList::DrawServerCommandBtn(const std::string& cmdfmt, const std::string& label)
304 {
305  std::string chatmsg = fmt::format(cmdfmt, m_users[m_peeropts_menu_active_user_vectorpos].uniqueid);
306  if (ImGui::Button(label.c_str()))
307  {
309  }
310 }
311 
313 {
314  if (m_peeropts_menu_active_user_vectorpos == -1)
315  return; // Menu not visible
316 
317  // Sanity check
318  const bool vectorpos_sane = (m_peeropts_menu_active_user_vectorpos >= 0
319  && m_peeropts_menu_active_user_vectorpos < (int)m_users_peeropts.size());
320  ROR_ASSERT(vectorpos_sane);
321  if (!vectorpos_sane)
322  return; // Minimize damage
323 
324  // Draw UI
325  ImGui::SetNextWindowPos(m_peeropts_menu_corner_tl);
326  ImGui::SetNextWindowContentWidth(PEEROPTS_MENU_CONTENT_WIDTH);
327  const int flags = ImGuiWindowFlags_NoDecoration;
328  if (ImGui::Begin("PeerOptions", nullptr, flags))
329  {
330  ImGui::TextDisabled("%s", _LC("MultiplayerClientList", "Local actions"));
331  this->DrawPeerOptCheckbox(RoRnet::PEEROPT_MUTE_CHAT, _LC("MultiplayerClientList", "Mute chat"));
332  this->DrawPeerOptCheckbox(RoRnet::PEEROPT_MUTE_ACTORS, _LC("MultiplayerClientList", "Mute actors"));
333  this->DrawPeerOptCheckbox(RoRnet::PEEROPT_HIDE_ACTORS, _LC("MultiplayerClientList", "Hide actors"));
334  ImGui::Separator();
335  ImGui::TextDisabled("%s", _LC("MultiplayerClientList", "Server commands"));
336  this->DrawServerCommandBtn("!report {} Please enter reason: ", _LC("MultiplayerClientList", "Report"));
337  const int32_t authstatus = m_users[0].authstatus; // User[0] is the local user
338  if (BITMASK_IS_1(authstatus, RoRnet::AUTH_ADMIN) || BITMASK_IS_1(authstatus, RoRnet::AUTH_MOD))
339  {
340  this->DrawServerCommandBtn("!kick {}", _LC("MultiplayerClientList", "Kick"));
341  this->DrawServerCommandBtn("!ban {}", _LC("MultiplayerClientList", "Ban"));
342  }
343  m_peeropts_menu_corner_br = m_peeropts_menu_corner_tl + ImGui::GetWindowContentRegionMax();
344 
345  ImGui::End();
346  }
347 
348  // Check hover and hide
349  const ImVec2 hoverbox_tl = m_peeropts_menu_corner_tl - ImVec2(PEEROPTS_HOVER_MARGIN, PEEROPTS_HOVER_MARGIN);
350  const ImVec2 hoverbox_br = m_peeropts_menu_corner_br + ImVec2(PEEROPTS_HOVER_MARGIN, PEEROPTS_HOVER_MARGIN);
351  const ImVec2 mousepos = ImGui::GetIO().MousePos;
352  if (mousepos.x < hoverbox_tl.x || mousepos.x > hoverbox_br.x
353  || mousepos.y < hoverbox_tl.y || mousepos.y > hoverbox_br.y)
354  {
355  m_peeropts_menu_active_user_vectorpos = -1;
356  }
357 }
358 
359 void MpClientList::DrawIcon(Ogre::TexturePtr tex, ImVec2 reference_box)
360 {
361  ImVec2 orig_pos = ImGui::GetCursorPos();
362  if (tex)
363  {
364  // TODO: moving the cursor somehow deforms the image
365  // ImGui::SetCursorPosX(orig_pos.x + (reference_box.x - tex->getWidth()) / 2.f);
366  // ImGui::SetCursorPosY(orig_pos.y + (reference_box.y - tex->getHeight()) / 2.f);
367  ImGui::Image(reinterpret_cast<ImTextureID>(tex->getHandle()), ImVec2(16, 16));
368  }
369  ImGui::SetCursorPosX(orig_pos.x + reference_box.x + ImGui::GetStyle().ItemSpacing.x);
370  ImGui::SetCursorPosY(orig_pos.y);
371 }
372 
374 {
375  m_icon_arrow_down = FetchIcon("arrow_down.png");
376  m_icon_arrow_down_grey = FetchIcon("arrow_down_grey.png");
377  m_icon_arrow_down_red = FetchIcon("arrow_down_red.png");
378  m_icon_arrow_up = FetchIcon("arrow_up.png");
379  m_icon_arrow_up_grey = FetchIcon("arrow_up_grey.png");
380  m_icon_arrow_up_red = FetchIcon("arrow_up_red.png");
381  m_icon_flag_red = FetchIcon("flag_red.png");
382  m_icon_flag_blue = FetchIcon("flag_blue.png");
383  m_icon_flag_green = FetchIcon("flag_green.png");
384  m_icon_warn_triangle = FetchIcon("error.png");
385 
386  m_icons_cached = true;
387 }
ROR_ASSERT
#define ROR_ASSERT(_EXPR)
Definition: Application.h:40
GameContext.h
Game state manager and message-queue provider.
GUI_MultiplayerClientList.h
RoR::App::GetNetwork
Network * GetNetwork()
Definition: Application.cpp:288
y
float y
Definition: (ValueTypes) quaternion.h:6
RoR::Network::GetUserInfos
std::vector< RoRnet::UserInfo > GetUserInfos()
Definition: Network.cpp:705
RoR::FetchIcon
Ogre::TexturePtr FetchIcon(const char *name)
Definition: GUIUtils.cpp:346
RoR::GUIManager::GuiTheme
Definition: GUIManager.h:79
RoR::App::GetGuiManager
GUIManager * GetGuiManager()
Definition: Application.cpp:273
RoRnet::UserInfo
Definition: RoRnet.h:178
RoR::AppState::MAIN_MENU
@ MAIN_MENU
format
Truck file format(technical spec)
GUIUtils.h
RoR::GUI::MpClientList::DrawIcon
void DrawIcon(Ogre::TexturePtr tex, ImVec2 reference_box)
Definition: GUI_MultiplayerClientList.cpp:359
RoR::AppState
AppState
Definition: Application.h:164
RoRnet::AUTH_ADMIN
@ AUTH_ADMIN
admin on the server
Definition: RoRnet.h:76
RoR::GUI::MpClientList::CacheIcons
void CacheIcons()
Definition: GUI_MultiplayerClientList.cpp:373
Language.h
RoR::GUIManager::GuiTheme::semitransparent_window_bg
ImVec4 semitransparent_window_bg
Definition: GUIManager.h:92
GUIManager.h
ActorManager.h
RoR::MSG_GUI_SHOW_CHATBOX_REQUESTED
@ MSG_GUI_SHOW_CHATBOX_REQUESTED
Description = message or server command to pre-fill in the chatbox (deleting whatever was there previ...
Definition: Application.h:147
RoR::GUIManager::GetTheme
GuiTheme & GetTheme()
Definition: GUIManager.h:166
RoR::GameContext::ChainMessage
void ChainMessage(Message m)
Add to last pushed message's chain.
Definition: GameContext.cpp:73
RoR::GUI::MpClientList::DrawPeerOptionsMenu
void DrawPeerOptionsMenu()
Definition: GUI_MultiplayerClientList.cpp:312
RoR::MSG_NET_ADD_PEEROPTIONS_REQUESTED
@ MSG_NET_ADD_PEEROPTIONS_REQUESTED
Payload = RoR::PeerOptionsRequest* (owner)
Definition: Application.h:113
RoR::Network::GetAllUsersPeerOpts
std::vector< BitMask_t > GetAllUsersPeerOpts()
Definition: Network.cpp:711
RoRnet::AUTH_MOD
@ AUTH_MOD
moderator status
Definition: RoRnet.h:78
BITMASK_IS_1
#define BITMASK_IS_1(VAR, FLAGS)
Definition: BitFlags.h:14
RoR::MSG_GUI_MP_CLIENTS_REFRESH
@ MSG_GUI_MP_CLIENTS_REFRESH
Definition: Application.h:141
RoR::GameContext::PushMessage
void PushMessage(Message m)
Doesn't guarantee order! Use ChainMessage() if order matters.
Definition: GameContext.cpp:66
RoR::App::app_state
CVar * app_state
Definition: Application.cpp:79
RoR::Network::GetNetQuality
int GetNetQuality()
Definition: Network.cpp:117
RoR::MSG_NET_REMOVE_PEEROPTIONS_REQUESTED
@ MSG_NET_REMOVE_PEEROPTIONS_REQUESTED
Payload = RoR::PeerOptionsRequest* (owner)
Definition: Application.h:114
RoR::MsgType
MsgType
Global gameplay message loop, see struct Message in GameContext.h.
Definition: Application.h:74
Application.h
Central state/object manager and communications hub.
RoR::Network::GetLocalUserData
RoRnet::UserInfo GetLocalUserData()
Definition: Network.cpp:699
RoR::App::GetGameContext
GameContext * GetGameContext()
Definition: Application.cpp:284
RoR::CVar::getEnum
T getEnum() const
Definition: CVar.h:99
RoRnet::AUTH_RANKED
@ AUTH_RANKED
ranked status
Definition: RoRnet.h:77
_LC
#define _LC(ctx, str)
Definition: Language.h:38
RoR::Network::GetPlayerColor
Ogre::ColourValue GetPlayerColor(int color_num)
Definition: Network.cpp:94
RoR::GUIManager::GuiTheme::screen_edge_padding
ImVec2 screen_edge_padding
Definition: GUIManager.h:96
RoRnet::UserInfo::uniqueid
uint32_t uniqueid
user unique id
Definition: RoRnet.h:180
RoR::PeerOptionsRequest
Payload of MSG_NET_{ADD/REMOVE}_PEEROPTIONS_REQUESTED.
Definition: Network.h:104
RoR::Message
Unified game event system - all requests and state changes are reported using a message.
Definition: GameContext.h:51
RoR::GUI::MpClientList::UpdateClients
void UpdateClients()
Definition: GUI_MultiplayerClientList.cpp:45
RoRnet::PEEROPT_HIDE_ACTORS
@ PEEROPT_HIDE_ACTORS
Spawn actors hidden and immediatelly hide existing actors.
Definition: RoRnet.h:131
BitMask_t
uint32_t BitMask_t
Definition: BitFlags.h:7
Ogre
Definition: ExtinguishableFireAffector.cpp:35
RoRnet::PEEROPT_MUTE_CHAT
@ PEEROPT_MUTE_CHAT
CHAT and PRIVCHAT messages will not be allowed through.
Definition: RoRnet.h:129
RoRnet::PEEROPT_MUTE_ACTORS
@ PEEROPT_MUTE_ACTORS
Spawn actors muted and immediatelly mute existing actors.
Definition: RoRnet.h:130
RoR::GUI::MpClientList::DrawPeerOptCheckbox
void DrawPeerOptCheckbox(const BitMask_t flag, const std::string &label)
Definition: GUI_MultiplayerClientList.cpp:290
RoR
Definition: AppContext.h:36
Network.h
x
float x
Definition: (ValueTypes) quaternion.h:5
RoR::GUI::MpClientList::Draw
void Draw()
Definition: GUI_MultiplayerClientList.cpp:74
RoR::GUI::MpClientList::DrawServerCommandBtn
void DrawServerCommandBtn(const std::string &cmdfmt, const std::string &label)
Definition: GUI_MultiplayerClientList.cpp:303