Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
LobbyScene.cpp
Go to the documentation of this file.
1
10
11#include <cstddef>
12#include <functional>
13#include <list>
14#include <algorithm>
15#include <sstream>
16#include <iomanip>
17#include <string>
18
19namespace rtp::client {
20 namespace scenes {
21
22 namespace {
23 void hashCombine(std::size_t &seed, std::size_t value)
24 {
25 seed ^= value + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2);
26 }
27
28 std::size_t hashRoomList(const std::list<net::RoomInfo>& rooms)
29 {
30 std::size_t seed = 0;
31 for (const auto& room : rooms) {
32 hashCombine(seed, std::hash<uint32_t>{}(room.roomId));
33 hashCombine(seed, std::hash<uint32_t>{}(room.currentPlayers));
34 hashCombine(seed, std::hash<uint32_t>{}(room.maxPlayers));
35 hashCombine(seed, std::hash<uint8_t>{}(room.inGame));
36 hashCombine(seed, std::hash<float>{}(room.difficulty));
37 hashCombine(seed, std::hash<float>{}(room.speed));
38 hashCombine(seed, std::hash<uint32_t>{}(room.duration));
39 hashCombine(seed, std::hash<uint32_t>{}(room.seed));
40 hashCombine(seed, std::hash<uint32_t>{}(room.levelId));
41 hashCombine(seed, std::hash<std::string>{}(std::string(room.roomName)));
42 }
43 return seed;
44 }
45 }
46
48 // Public API
50
52 Settings& settings,
53 TranslationManager& translationManager,
54 NetworkSyncSystem& network,
55 graphics::UiFactory& uiFactory,
56 std::function<void(GameState)> changeState)
57 : _uiRegistry(UiRegistry),
58 _settings(settings),
59 _translationManager(translationManager),
60 _network(network),
61 _uiFactory(uiFactory),
62 _changeState(changeState)
63 {
64 }
65
67 {
68 log::info("Entering LobbyScene");
69
71 _roomsHash = hashRoomList(_network.getAvailableRooms());
72 buildUi();
73 _uiBuilt = true;
74 }
75
77 {
78 auto styleButton = [this](ecs::Entity entity,
79 const ecs::components::ui::Button &style) {
80 auto buttonsOpt = _uiRegistry.get<ecs::components::ui::Button>();
81 if (!buttonsOpt) {
82 return;
83 }
84 auto &buttons = buttonsOpt.value().get();
85 if (!buttons.has(entity)) {
86 return;
87 }
88 auto &button = buttons[entity];
89 std::copy(std::begin(style.idleColor), std::end(style.idleColor),
90 std::begin(button.idleColor));
91 std::copy(std::begin(style.hoverColor), std::end(style.hoverColor),
92 std::begin(button.hoverColor));
93 std::copy(std::begin(style.pressedColor), std::end(style.pressedColor),
94 std::begin(button.pressedColor));
95 };
96
97 auto formatFloat = [](float value) -> std::string {
98 std::ostringstream oss;
99 oss << std::fixed << std::setprecision(1) << value;
100 return oss.str();
101 };
102
104 panelStyle.idleColor[0] = 22;
105 panelStyle.idleColor[1] = 26;
106 panelStyle.idleColor[2] = 34;
107 std::copy(std::begin(panelStyle.idleColor), std::end(panelStyle.idleColor),
108 std::begin(panelStyle.hoverColor));
109 std::copy(std::begin(panelStyle.idleColor), std::end(panelStyle.idleColor),
110 std::begin(panelStyle.pressedColor));
111
112 ecs::components::ui::Button primaryStyle;
113 primaryStyle.idleColor[0] = 32;
114 primaryStyle.idleColor[1] = 140;
115 primaryStyle.idleColor[2] = 140;
116 primaryStyle.hoverColor[0] = 48;
117 primaryStyle.hoverColor[1] = 170;
118 primaryStyle.hoverColor[2] = 170;
119 primaryStyle.pressedColor[0] = 20;
120 primaryStyle.pressedColor[1] = 110;
121 primaryStyle.pressedColor[2] = 110;
122
123 ecs::components::ui::Button secondaryStyle;
124 secondaryStyle.idleColor[0] = 70;
125 secondaryStyle.idleColor[1] = 74;
126 secondaryStyle.idleColor[2] = 84;
127 secondaryStyle.hoverColor[0] = 95;
128 secondaryStyle.hoverColor[1] = 100;
129 secondaryStyle.hoverColor[2] = 112;
130 secondaryStyle.pressedColor[0] = 55;
131 secondaryStyle.pressedColor[1] = 60;
132 secondaryStyle.pressedColor[2] = 70;
133
135 joinStyle.idleColor[0] = 40;
136 joinStyle.idleColor[1] = 130;
137 joinStyle.idleColor[2] = 120;
138 joinStyle.hoverColor[0] = 60;
139 joinStyle.hoverColor[1] = 160;
140 joinStyle.hoverColor[2] = 150;
141 joinStyle.pressedColor[0] = 30;
142 joinStyle.pressedColor[1] = 100;
143 joinStyle.pressedColor[2] = 90;
144
146 specStyle.idleColor[0] = 140;
147 specStyle.idleColor[1] = 110;
148 specStyle.idleColor[2] = 40;
149 specStyle.hoverColor[0] = 180;
150 specStyle.hoverColor[1] = 140;
151 specStyle.hoverColor[2] = 60;
152 specStyle.pressedColor[0] = 120;
153 specStyle.pressedColor[1] = 90;
154 specStyle.pressedColor[2] = 30;
155
157 rowStyleA.idleColor[0] = 28;
158 rowStyleA.idleColor[1] = 30;
159 rowStyleA.idleColor[2] = 36;
160 std::copy(std::begin(rowStyleA.idleColor), std::end(rowStyleA.idleColor),
161 std::begin(rowStyleA.hoverColor));
162 std::copy(std::begin(rowStyleA.idleColor), std::end(rowStyleA.idleColor),
163 std::begin(rowStyleA.pressedColor));
164
166 rowStyleB.idleColor[0] = 32;
167 rowStyleB.idleColor[1] = 35;
168 rowStyleB.idleColor[2] = 42;
169 std::copy(std::begin(rowStyleB.idleColor), std::end(rowStyleB.idleColor),
170 std::begin(rowStyleB.hoverColor));
171 std::copy(std::begin(rowStyleB.idleColor), std::end(rowStyleB.idleColor),
172 std::begin(rowStyleB.pressedColor));
173
176 {404.0f, 44.0f},
177 "LOBBY - ROOMS",
178 "assets/fonts/title.ttf",
179 52,
180 9,
181 {12, 14, 18}
182 );
183
186 {400.0f, 40.0f},
187 "LOBBY - ROOMS",
188 "assets/fonts/title.ttf",
189 52,
190 10,
191 {255, 200, 100}
192 );
193
194 const auto &rooms = _network.getAvailableRooms();
197 {450.0f, 110.0f},
198 "Rooms available: " + std::to_string(rooms.size()),
199 "assets/fonts/main.ttf",
200 18,
201 10,
202 {170, 180, 190}
203 );
204
205 const float topY = 150.0f;
206 const float topX = 180.0f;
207 const float topBtnW = 200.0f;
208 const float topBtnH = 50.0f;
209 const float topGap = 20.0f;
210
211 auto refreshButton = _uiFactory.createButton(
213 {topX, topY},
214 {topBtnW, topBtnH},
215 "REFRESH",
216 [this]() {
219 }
220 );
221 styleButton(refreshButton, secondaryStyle);
222
223 auto createRoomButton = _uiFactory.createButton(
225 {topX + topBtnW + topGap, topY},
226 {topBtnW, topBtnH},
227 "CREATE ROOM",
228 [this]() {
230 }
231 );
232 styleButton(createRoomButton, primaryStyle);
233
234 auto backButton = _uiFactory.createButton(
236 {topX + (topBtnW + topGap) * 2.0f, topY},
237 {topBtnW, topBtnH},
238 "BACK",
239 [this]() {
241 }
242 );
243 styleButton(backButton, secondaryStyle);
244
245 const float panelX = 140.0f;
246 const float panelY = 220.0f;
247 const float panelW = 1000.0f;
248 const float panelH = 420.0f;
249
250 auto listPanel = _uiFactory.createButton(
252 {panelX, panelY},
253 {panelW, panelH},
254 "",
255 nullptr
256 );
257 styleButton(listPanel, panelStyle);
258
261 {panelX + 20.0f, panelY + 12.0f},
262 "ROOMS",
263 "assets/fonts/main.ttf",
264 16,
265 10,
266 {150, 160, 170}
267 );
268
271 {panelX + panelW - 250.0f, panelY + 12.0f},
272 "ACTIONS",
273 "assets/fonts/main.ttf",
274 16,
275 10,
276 {150, 160, 170}
277 );
278
279 float y = panelY + 40.0f;
280 int shown = 0;
281
282 for (const auto& room : rooms) {
283 if (shown >= 6) {
284 break;
285 }
286
287 const float rowY = y + static_cast<float>(shown) * 64.0f;
288 auto rowBg = _uiFactory.createButton(
290 {panelX + 20.0f, rowY},
291 {panelW - 40.0f, 56.0f},
292 "",
293 nullptr
294 );
295 styleButton(rowBg, (shown % 2 == 0) ? rowStyleA : rowStyleB);
296
297 std::string roomName = std::string(room.roomName);
298 if (roomName.empty()) {
299 roomName = "Room " + std::to_string(room.roomId);
300 }
301
302 std::string line1 = roomName + " • " +
303 std::to_string(room.currentPlayers) + "/" +
304 std::to_string(room.maxPlayers) + " players";
305
306 std::string line2 = "Difficulty " + formatFloat(room.difficulty) +
307 " • Speed " + formatFloat(room.speed) +
308 " • Level " + std::to_string(room.levelId);
309
312 {panelX + 40.0f, rowY + 8.0f},
313 line1,
314 "assets/fonts/main.ttf",
315 20,
316 10,
317 {220, 230, 240}
318 );
319
322 {panelX + 40.0f, rowY + 32.0f},
323 line2,
324 "assets/fonts/main.ttf",
325 16,
326 10,
327 {150, 160, 170}
328 );
329
330 const float joinW = 110.0f;
331 const float specW = 110.0f;
332 const float actionY = rowY + 10.0f;
333 const float joinX = panelX + panelW - 250.0f;
334 const float specX = joinX + joinW + 10.0f;
335
336 auto joinButton = _uiFactory.createButton(
338 {joinX, actionY},
339 {joinW, 36.0f},
340 "JOIN",
341 [this, room]() {
342 _uiSelectedRoomId = room.roomId;
343 log::info("Attempting to join Room ID {}", room.roomId);
344 _network.tryJoinRoom(room.roomId, false);
345 }
346 );
347 styleButton(joinButton, joinStyle);
348
349 auto specButton = _uiFactory.createButton(
351 {specX, actionY},
352 {specW, 36.0f},
353 "SPEC",
354 [this, room]() {
355 _uiSelectedRoomId = room.roomId;
356 log::info("Attempting to spectate Room ID {}", room.roomId);
357 _network.tryJoinRoom(room.roomId, true);
358 }
359 );
360 styleButton(specButton, specStyle);
361
362 if (room.inGame) {
365 {panelX + panelW - 360.0f, rowY + 18.0f},
366 "IN GAME",
367 "assets/fonts/main.ttf",
368 14,
369 10,
370 {220, 120, 90}
371 );
372 }
373
374 ++shown;
375 }
376
377 if (shown == 0) {
380 {panelX + 40.0f, panelY + 80.0f},
381 "No rooms found. Create one or refresh.",
382 "assets/fonts/main.ttf",
383 18,
384 10,
385 {150, 160, 170}
386 );
387 }
388 }
389
391 {
392 log::info("Exiting LobbyScene");
393 }
394
395 void LobbyScene::handleEvent(const sf::Event& event)
396 {
397 (void)event;
398 }
399
400 void LobbyScene::update(float dt)
401 {
402 (void)dt;
403 const auto rooms = _network.getAvailableRooms();
404 const std::size_t newHash = hashRoomList(rooms);
405 if (!_uiBuilt || newHash != _roomsHash) {
407 _roomsHash = newHash;
408 buildUi();
409 _uiBuilt = true;
410 }
415 }
416 }
417
418 } // namespace scenes
419} // namespace rtp::client
System to handle network-related operations on the client side.
State getState(void) const
Get the current state of the client.
void tryJoinRoom(uint32_t roomId, bool asSpectator=false)
Send a request to join an existing room on the server.
void requestListRooms(void)
Send a request to have the list of available rooms from the server.
std::list< net::RoomInfo > getAvailableRooms(void) const
Get a list of available rooms from the server.
Manages game settings and preferences.
Definition Settings.hpp:67
Manages game translations for all UI elements.
Factory class for creating UI components in the ECS registry.
Definition UiFactory.hpp:72
static ecs::Entity createText(ecs::Registry &registry, const position &position, const std::string &content, const std::string &fontPath, unsigned int fontSize, const std::uint8_t zIndex=0, const color &textColor={255, 255, 255})
Definition UiFactory.cpp:43
static ecs::Entity createButton(ecs::Registry &registry, const position &position, const size &size, const std::string &label, std::function< void()> onClick=nullptr)
Create a button UI component.
Definition UiFactory.cpp:17
NetworkSyncSystem & _network
Reference to the client network.
uint32_t _uiSelectedRoomId
Currently selected room ID in the UI.
ecs::Registry & _uiRegistry
Reference to the ECS registry.
ChangeStateFn _changeState
Function to change the game state.
bool _uiBuilt
Tracks if the UI has been built at least once.
void update(float dt) override
Update the scene state.
void onExit(void) override
Called when the scene is exited.
graphics::UiFactory & _uiFactory
UI Factory for creating UI components.
std::size_t _roomsHash
Hash of the last room list displayed.
void onEnter(void) override
Called when the scene is entered.
void handleEvent(const sf::Event &event) override
Handle an incoming event.
LobbyScene(ecs::Registry &UiRegistry, Settings &settings, TranslationManager &translationManager, NetworkSyncSystem &network, graphics::UiFactory &uiFactory, std::function< void(GameState)> changeState)
Constructor for LobbyScene.
Represents an entity in the ECS (Entity-Component-System) architecture.
Definition Entity.hpp:63
void clear(void) noexcept
Definition Registry.cpp:102
auto get(this const Self &self) -> std::expected< std::reference_wrapper< ConstLike< Self, SparseArray< T > > >, rtp::Error >
R-Type client namespace.
@ RoomWaiting
Room waiting state.
@ CreateRoom
Create room state.
@ Menu
Main menu state.
@ Playing
In-game playing state.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.
Component representing a clickable button.
Definition Button.hpp:30
uint8_t idleColor[3]
RGB color when idle.
Definition Button.hpp:38
uint8_t pressedColor[3]
RGB color when pressed.
Definition Button.hpp:40
uint8_t hoverColor[3]
RGB color when hovered.
Definition Button.hpp:39