Rigs of Rods 2023.09
Soft-body Physics Simulation
Loading...
Searching...
No Matches
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
41using namespace RoR;
42using namespace GUI;
43using 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;
51 {
52 peeropts_menu_active_user_uid = (int)m_users[m_peeropts_menu_active_user_vectorpos].uniqueid;
53 }
55
56 // Update the user data
58 m_users.insert(m_users.begin(), App::GetNetwork()->GetLocalUserData());
59
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 {
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 {
121 {
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 {
147 default:;
148 }
149
150
151 switch (App::GetGameContext()->GetActorManager()->CheckNetRemoteStreamsOk(user.uniqueid))
152 {
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 RoRnet::UiStreamsHealth::MISMATCHES: ImGui::Text("%s", _LC("MultiplayerClientList", "Truck loading errors")); break;
247 case RoRnet::UiStreamsHealth::ALL_OK: ImGui::Text("%s", _LC("MultiplayerClientList", "Truck loaded correctly, no errors")); break;
248 case RoRnet::UiStreamsHealth::IDLE: 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 RoRnet::UiStreamsHealth::MISMATCHES: ImGui::Text("%s", _LC("MultiplayerClientList", "Remote Truck loading errors")); break;
262 case RoRnet::UiStreamsHealth::ALL_OK: ImGui::Text("%s", _LC("MultiplayerClientList", "Remote Truck loaded correctly, no errors")); break;
263 case RoRnet::UiStreamsHealth::IDLE: 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
290void MpClientList::DrawPeerOptCheckbox(const BitMask_t flag, const std::string& label)
291{
292 int uid = (int)m_users[m_peeropts_menu_active_user_vectorpos].uniqueid;
294
295 if (ImGui::Checkbox(label.c_str(), &flagval))
296 {
298 App::GetGameContext()->PushMessage(Message(peeropt_msg, new PeerOptionsRequest{ uid, flag }));
300 }
301}
302
303void 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{
315 return; // Menu not visible
316
317 // Sanity check
318 const bool vectorpos_sane = (m_peeropts_menu_active_user_vectorpos >= 0
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 {
356 }
357}
358
359void 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}
Central state/object manager and communications hub.
#define ROR_ASSERT(_EXPR)
Definition Application.h:40
#define BITMASK_IS_1(VAR, FLAGS)
Definition BitFlags.h:14
uint32_t BitMask_t
Definition BitFlags.h:7
Game state manager and message-queue provider.
#define _LC(ctx, str)
Definition Language.h:38
T getEnum() const
Definition CVar.h:99
void DrawServerCommandBtn(const std::string &cmdfmt, const std::string &label)
std::vector< BitMask_t > m_users_peeropts
void DrawPeerOptCheckbox(const BitMask_t flag, const std::string &label)
void DrawIcon(Ogre::TexturePtr tex, ImVec2 reference_box)
std::vector< RoRnet::UserInfo > m_users
GuiTheme & GetTheme()
Definition GUIManager.h:168
void PushMessage(Message m)
Doesn't guarantee order! Use ChainMessage() if order matters.
void ChainMessage(Message m)
Add to last pushed message's chain.
Ogre::ColourValue GetPlayerColor(int color_num)
Definition Network.cpp:94
int GetNetQuality()
Definition Network.cpp:117
std::vector< BitMask_t > GetAllUsersPeerOpts()
Definition Network.cpp:719
RoRnet::UserInfo GetLocalUserData()
Definition Network.cpp:707
std::vector< RoRnet::UserInfo > GetUserInfos()
Definition Network.cpp:713
MsgType
Global gameplay message loop, see struct Message in GameContext.h.
Definition Application.h:76
@ MSG_NET_REMOVE_PEEROPTIONS_REQUESTED
Payload = RoR::PeerOptionsRequest* (owner)
@ MSG_GUI_MP_CLIENTS_REFRESH
@ MSG_GUI_SHOW_CHATBOX_REQUESTED
Description = message or server command to pre-fill in the chatbox (deleting whatever was there previ...
@ MSG_NET_ADD_PEEROPTIONS_REQUESTED
Payload = RoR::PeerOptionsRequest* (owner)
GUIManager * GetGuiManager()
GameContext * GetGameContext()
CVar * app_state
Network * GetNetwork()
Ogre::TexturePtr FetchIcon(const char *name)
Definition GUIUtils.cpp:372
@ PEEROPT_HIDE_ACTORS
Spawn actors hidden and immediatelly hide existing actors.
Definition RoRnet.h:133
@ PEEROPT_MUTE_ACTORS
Spawn actors muted and immediatelly mute existing actors.
Definition RoRnet.h:132
@ PEEROPT_MUTE_CHAT
CHAT and PRIVCHAT messages will not be allowed through.
Definition RoRnet.h:131
@ AUTH_MOD
moderator status
Definition RoRnet.h:80
@ AUTH_RANKED
ranked status
Definition RoRnet.h:79
@ AUTH_ADMIN
admin on the server
Definition RoRnet.h:78
@ ALL_OK
Stream is OK - no errors loading the mods.
@ MISMATCHES
Loading errors - some mods could not be loaded (probably not installed)
@ IDLE
Player has no active streams.
Unified game event system - all requests and state changes are reported using a message.
Definition GameContext.h:52
Payload of MSG_NET_{ADD/REMOVE}_PEEROPTIONS_REQUESTED.
Definition Network.h:105
uint32_t uniqueid
user unique id
Definition RoRnet.h:191