Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
Settings.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** Air-Trap
4** File description:
5** Settings
6 * implementation
7*/
8
9#include "Core/Settings.hpp"
11
12namespace rtp::client
13{
14
16 {
18
19 if (!load("config/settings.cfg")) {
21 "No settings.cfg found, using defaults from client.json");
22 loadFromClientJson("config/client.json");
23 } else {
24 log::info("Settings loaded from config/settings.cfg");
25 }
26 }
27
29 {
30 _keyBindings[KeyAction::MoveUp] = sf::Keyboard::Key::Z;
31 _keyBindings[KeyAction::MoveDown] = sf::Keyboard::Key::S;
32 _keyBindings[KeyAction::MoveLeft] = sf::Keyboard::Key::Q;
33 _keyBindings[KeyAction::MoveRight] = sf::Keyboard::Key::D;
34 _keyBindings[KeyAction::Shoot] = sf::Keyboard::Key::Space;
35 _keyBindings[KeyAction::Reload] = sf::Keyboard::Key::R;
36 _keyBindings[KeyAction::Pause] = sf::Keyboard::Key::Escape;
37 _keyBindings[KeyAction::Menu] = sf::Keyboard::Key::Tab;
38 }
39
40 bool Settings::loadFromClientJson(const std::string &filename)
41 {
42 std::ifstream file(filename);
43 if (!file.is_open()) {
44 log::error("Failed to load client.json from: {}", filename);
45 return false;
46 }
47
48 std::string line;
49 bool inAudio = false;
50 bool inControls = false;
51
52 while (std::getline(file, line)) {
53 if (line.find("\"audio\"") != std::string::npos) {
54 inAudio = true;
55 inControls = false;
56 continue;
57 } else if (line.find("\"controls\"") != std::string::npos) {
58 inControls = true;
59 inAudio = false;
60 continue;
61 } else if (line.find("}") !=
62 std::string::npos &&
63 line.find("{") == std::string::npos) {
64 inAudio = false;
65 inControls = false;
66 }
67
68 if (inAudio) {
69 if (line.find("\"masterVolume\"") != std::string::npos) {
70 size_t colonPos = line.find(':');
71 if (colonPos != std::string::npos) {
72 std::string valueStr = line.substr(colonPos + 1);
73 valueStr.erase(
74 std::remove(valueStr.begin(), valueStr.end(), ','),
75 valueStr.end());
76 _masterVolume = std::stof(valueStr) / 100.0f;
77 }
78 } else if (line.find("\"musicVolume\"") != std::string::npos) {
79 size_t colonPos = line.find(':');
80 if (colonPos != std::string::npos) {
81 std::string valueStr = line.substr(colonPos + 1);
82 valueStr.erase(
83 std::remove(valueStr.begin(), valueStr.end(), ','),
84 valueStr.end());
85 _musicVolume = std::stof(valueStr) / 100.0f;
86 }
87 } else if (line.find("\"sfxVolume\"") != std::string::npos) {
88 size_t colonPos = line.find(':');
89 if (colonPos != std::string::npos) {
90 std::string valueStr = line.substr(colonPos + 1);
91 valueStr.erase(
92 std::remove(valueStr.begin(), valueStr.end(), ','),
93 valueStr.end());
94 _sfxVolume = std::stof(valueStr) / 100.0f;
95 }
96 }
97 }
98
99 if (inControls) {
100 auto parseKeyBinding = [&](const std::string &jsonKey,
101 KeyAction action) {
102 if (line.find(jsonKey) != std::string::npos) {
103 size_t start = line.find('"', line.find(':')) + 1;
104 size_t end = line.find('"', start);
105 if (start !=
106 std::string::npos &&
107 end != std::string::npos) {
108 std::string keyName =
109 line.substr(start, end - start);
110 _keyBindings[action] = stringToKey(keyName);
111 }
112 }
113 };
114
115 parseKeyBinding("\"moveUp\"", KeyAction::MoveUp);
116 parseKeyBinding("\"moveDown\"", KeyAction::MoveDown);
117 parseKeyBinding("\"moveLeft\"", KeyAction::MoveLeft);
118 parseKeyBinding("\"moveRight\"", KeyAction::MoveRight);
119 parseKeyBinding("\"shoot\"", KeyAction::Shoot);
120 parseKeyBinding("\"reload\"", KeyAction::Reload);
121 }
122 }
123
124 log::info("Loaded default settings from client.json");
125 return true;
126 }
127
128 sf::Keyboard::Key Settings::stringToKey(const std::string &keyName) const
129 {
130 static const std::unordered_map<std::string, sf::Keyboard::Key> keyMap =
131 {
132 {"A", sf::Keyboard::Key::A },
133 {"B", sf::Keyboard::Key::B },
134 {"C", sf::Keyboard::Key::C },
135 {"D", sf::Keyboard::Key::D },
136 {"E", sf::Keyboard::Key::E },
137 {"F", sf::Keyboard::Key::F },
138 {"G", sf::Keyboard::Key::G },
139 {"H", sf::Keyboard::Key::H },
140 {"I", sf::Keyboard::Key::I },
141 {"J", sf::Keyboard::Key::J },
142 {"K", sf::Keyboard::Key::K },
143 {"L", sf::Keyboard::Key::L },
144 {"M", sf::Keyboard::Key::M },
145 {"N", sf::Keyboard::Key::N },
146 {"O", sf::Keyboard::Key::O },
147 {"P", sf::Keyboard::Key::P },
148 {"Q", sf::Keyboard::Key::Q },
149 {"R", sf::Keyboard::Key::R },
150 {"S", sf::Keyboard::Key::S },
151 {"T", sf::Keyboard::Key::T },
152 {"U", sf::Keyboard::Key::U },
153 {"V", sf::Keyboard::Key::V },
154 {"W", sf::Keyboard::Key::W },
155 {"X", sf::Keyboard::Key::X },
156 {"Y", sf::Keyboard::Key::Y },
157 {"Z", sf::Keyboard::Key::Z },
158 {"Space", sf::Keyboard::Key::Space },
159 {"Escape", sf::Keyboard::Key::Escape},
160 {"Tab", sf::Keyboard::Key::Tab },
161 {"Up", sf::Keyboard::Key::Up },
162 {"Down", sf::Keyboard::Key::Down },
163 {"Left", sf::Keyboard::Key::Left },
164 {"Right", sf::Keyboard::Key::Right }
165 };
166
167 auto it = keyMap.find(keyName);
168 return (it != keyMap.end()) ? it->second : sf::Keyboard::Key::Unknown;
169 }
170
171 sf::Keyboard::Key Settings::getKey(KeyAction action) const
172 {
173 auto it = _keyBindings.find(action);
174 if (it != _keyBindings.end()) {
175 return it->second;
176 }
177 return sf::Keyboard::Key::Unknown;
178 }
179
180 void Settings::setKey(KeyAction action, sf::Keyboard::Key key)
181 {
182 _keyBindings[action] = key;
183 }
184
185 std::string Settings::getKeyName(sf::Keyboard::Key key) const
186 {
187 static const std::unordered_map<sf::Keyboard::Key, std::string>
188 keyNames = {
189 {sf::Keyboard::Key::A, "A" },
190 {sf::Keyboard::Key::B, "B" },
191 {sf::Keyboard::Key::C, "C" },
192 {sf::Keyboard::Key::D, "D" },
193 {sf::Keyboard::Key::E, "E" },
194 {sf::Keyboard::Key::F, "F" },
195 {sf::Keyboard::Key::G, "G" },
196 {sf::Keyboard::Key::H, "H" },
197 {sf::Keyboard::Key::I, "I" },
198 {sf::Keyboard::Key::J, "J" },
199 {sf::Keyboard::Key::K, "K" },
200 {sf::Keyboard::Key::L, "L" },
201 {sf::Keyboard::Key::M, "M" },
202 {sf::Keyboard::Key::N, "N" },
203 {sf::Keyboard::Key::O, "O" },
204 {sf::Keyboard::Key::P, "P" },
205 {sf::Keyboard::Key::Q, "Q" },
206 {sf::Keyboard::Key::R, "R" },
207 {sf::Keyboard::Key::S, "S" },
208 {sf::Keyboard::Key::T, "T" },
209 {sf::Keyboard::Key::U, "U" },
210 {sf::Keyboard::Key::V, "V" },
211 {sf::Keyboard::Key::W, "W" },
212 {sf::Keyboard::Key::X, "X" },
213 {sf::Keyboard::Key::Y, "Y" },
214 {sf::Keyboard::Key::Z, "Z" },
215 {sf::Keyboard::Key::Space, "Space" },
216 {sf::Keyboard::Key::Escape, "Escape"},
217 {sf::Keyboard::Key::Tab, "Tab" },
218 {sf::Keyboard::Key::Left, "Left" },
219 {sf::Keyboard::Key::Right, "Right" },
220 {sf::Keyboard::Key::Up, "Up" },
221 {sf::Keyboard::Key::Down, "Down" }
222 };
223
224 auto it = keyNames.find(key);
225 return (it != keyNames.end()) ? it->second : "Unknown";
226 }
227
228 void Settings::setMasterVolume(float volume)
229 {
230 _masterVolume = std::clamp(volume, 0.0f, 1.0f);
231
232 for (auto &callback : _onMasterVolumeChanged) {
233 callback(_masterVolume);
234 }
235 }
236
237 void Settings::setMusicVolume(float volume)
238 {
239 _musicVolume = std::clamp(volume, 0.0f, 1.0f);
240
241 for (auto &callback : _onMusicVolumeChanged) {
242 callback(_musicVolume);
243 }
244 }
245
246 void Settings::setSfxVolume(float volume)
247 {
248 _sfxVolume = std::clamp(volume, 0.0f, 1.0f);
249
250 for (auto &callback : _onSfxVolumeChanged) {
251 callback(_sfxVolume);
252 }
253 }
254
256 {
257 if (_language != lang) {
258 _language = lang;
259
260 for (auto &callback : _onLanguageChanged) {
261 callback(_language);
262 }
263 }
264 }
265
266 std::string Settings::getLanguageName(Language lang) const
267 {
268 switch (lang) {
270 return "English";
271 case Language::French:
272 return "Français";
274 return "Español";
275 case Language::German:
276 return "Deutsch";
278 return "Italiano";
279 default:
280 return "Unknown";
281 }
282 }
283
285 {
286 switch (mode) {
288 return "None";
290 return "Protanopia (Red-blind)";
292 return "Deuteranopia (Green-blind)";
294 return "Tritanopia (Blue-blind)";
295 default:
296 return "Unknown";
297 }
298 }
299
300 std::string Settings::getDifficultyName(Difficulty difficulty) const
301 {
302 switch (difficulty) {
303 case Difficulty::Easy:
304 return "Easy";
306 return "Normal";
307 case Difficulty::Hard:
308 return "Hard";
310 return "Infernal";
311 default:
312 return "Unknown";
313 }
314 }
315
317 {
318 // Prefer displayName from config if available
320 auto name = rtp::config::getWeaponDisplayName(weapon);
321 if (!name.empty()) return name;
322 }
323
324 switch (weapon) {
326 return "Classic Laser";
328 return "Beam Cannon";
329 // Paddle (Reflector) removed
331 return "Auto-Tracker";
333 return "Boomerang";
334 default:
335 return "Unknown";
336 }
337 }
338
339 bool Settings::save(const std::string &filename)
340 {
341 std::filesystem::create_directories("config");
342
343 std::ofstream file(filename);
344 if (!file.is_open()) {
345 log::error("Failed to save settings to: {}", filename);
346 return false;
347 }
348
349 file << "master_volume=" << _masterVolume << "\n";
350 file << "music_volume=" << _musicVolume << "\n";
351 file << "sfx_volume=" << _sfxVolume << "\n";
352
353 file << "language=" << static_cast<int>(_language) << "\n";
354
355 file << "colorblind_mode=" << static_cast<int>(_colorBlindMode) << "\n";
356 file << "high_contrast=" << (_highContrast ? 1 : 0) << "\n";
357
358 file << "difficulty=" << static_cast<int>(_difficulty) << "\n";
359 file << "selected_weapon=" << static_cast<int>(_selectedWeapon) << "\n";
360
361 // Gamepad
362 file << "gamepad_enabled=" << (_gamepadEnabled ? 1 : 0) << "\n";
363 file << "gamepad_deadzone=" << _gamepadDeadzone << "\n";
364 file << "gamepad_shoot_button=" << _gamepadShootButton << "\n";
365 file << "gamepad_reload_button=" << _gamepadReloadButton << "\n";
366 file << "gamepad_validate_button=" << _gamepadValidateButton << "\n";
367 file << "gamepad_cursor_speed=" << _gamepadCursorSpeed << "\n";
368 file << "gamepad_pause_button=" << _gamepadPauseButton << "\n";
369
370 for (const auto &[action, key] : _keyBindings) {
371 file << "key_" << static_cast<int>(action) << "="
372 << static_cast<int>(key) << "\n";
373 }
374
375 log::info("Settings saved to: {}", filename);
376 return true;
377 }
378
379 bool Settings::load(const std::string &filename)
380 {
381 std::ifstream file(filename);
382 if (!file.is_open()) {
383 std::cerr << "Failed to load settings from " << filename
384 << ", using defaults" << std::endl;
385 return false;
386 }
387
388 std::string line;
389 while (std::getline(file, line)) {
390 auto delimPos = line.find('=');
391 if (delimPos == std::string::npos)
392 continue;
393
394 std::string key = line.substr(0, delimPos);
395 std::string value = line.substr(delimPos + 1);
396
397 try {
398 if (key == "master_volume") {
399 _masterVolume = std::stof(value);
400 } else if (key == "music_volume") {
401 _musicVolume = std::stof(value);
402 } else if (key == "sfx_volume") {
403 _sfxVolume = std::stof(value);
404 } else if (key == "language") {
405 _language = static_cast<Language>(std::stoi(value));
406 } else if (key == "colorblind_mode") {
408 static_cast<ColorBlindMode>(std::stoi(value));
409 } else if (key == "high_contrast") {
410 _highContrast = (std::stoi(value) != 0);
411 } else if (key == "difficulty") {
412 _difficulty = static_cast<Difficulty>(std::stoi(value));
413 } else if (key == "selected_weapon") {
414 _selectedWeapon = static_cast<ecs::components::WeaponKind>(std::stoi(value));
415 } else if (key == "gamepad_enabled") {
416 _gamepadEnabled = (std::stoi(value) != 0);
417 } else if (key == "gamepad_deadzone") {
418 _gamepadDeadzone = std::stof(value);
419 } else if (key == "gamepad_shoot_button") {
420 _gamepadShootButton = std::stoi(value);
421 } else if (key == "gamepad_reload_button") {
422 _gamepadReloadButton = std::stoi(value);
423 } else if (key == "gamepad_validate_button") {
424 _gamepadValidateButton = std::stoi(value);
425 } else if (key == "gamepad_cursor_speed") {
426 _gamepadCursorSpeed = std::stof(value);
427 } else if (key == "gamepad_pause_button") {
428 _gamepadPauseButton = std::stoi(value);
429 } else if (key.find("key_") == 0) {
430 int actionId = std::stoi(key.substr(4));
431 int keyCode = std::stoi(value);
432 _keyBindings[static_cast<KeyAction>(actionId)] =
433 static_cast<sf::Keyboard::Key>(keyCode);
434 }
435 } catch (const std::exception &e) {
436 std::cerr << "Error parsing setting: " << key << " = " << value
437 << std::endl;
438 }
439 }
440
441 return true;
442 }
443} // namespace rtp::client
std::string getKeyName(sf::Keyboard::Key key) const
Definition Settings.cpp:185
void setKey(KeyAction action, sf::Keyboard::Key key)
Definition Settings.cpp:180
void setMusicVolume(float volume)
Definition Settings.cpp:237
std::string getWeaponName(ecs::components::WeaponKind weapon) const
Definition Settings.cpp:316
unsigned int _gamepadReloadButton
Definition Settings.hpp:269
std::vector< VolumeCallback > _onMusicVolumeChanged
Definition Settings.hpp:275
sf::Keyboard::Key stringToKey(const std::string &keyName) const
Definition Settings.cpp:128
unsigned int _gamepadShootButton
Definition Settings.hpp:268
std::string getDifficultyName(Difficulty difficulty) const
Definition Settings.cpp:300
std::unordered_map< KeyAction, sf::Keyboard::Key > _keyBindings
Definition Settings.hpp:250
sf::Keyboard::Key getKey(KeyAction action) const
Definition Settings.cpp:171
void setSfxVolume(float volume)
Definition Settings.cpp:246
bool loadFromClientJson(const std::string &filename)
Definition Settings.cpp:40
void setLanguage(Language lang)
Definition Settings.cpp:255
std::vector< VolumeCallback > _onMasterVolumeChanged
Definition Settings.hpp:274
std::string getColorBlindModeName(ColorBlindMode mode) const
Definition Settings.cpp:284
bool save(const std::string &filename="config/settings.cfg")
Definition Settings.cpp:339
void initDefaultKeyBindings()
Definition Settings.cpp:28
void setMasterVolume(float volume)
Definition Settings.cpp:228
ColorBlindMode _colorBlindMode
Definition Settings.hpp:260
unsigned int _gamepadPauseButton
Definition Settings.hpp:272
std::string getLanguageName(Language lang) const
Definition Settings.cpp:266
std::vector< VolumeCallback > _onSfxVolumeChanged
Definition Settings.hpp:276
ecs::components::WeaponKind _selectedWeapon
Definition Settings.hpp:263
unsigned int _gamepadValidateButton
Definition Settings.hpp:270
std::vector< LanguageCallback > _onLanguageChanged
Definition Settings.hpp:277
bool load(const std::string &filename="config/settings.cfg")
Definition Settings.cpp:379
R-Type client namespace.
ColorBlindMode
Color blind assistance modes.
Definition Settings.hpp:45
KeyAction
Actions that can be bound to keys.
Definition KeyAction.hpp:16
@ Pause
Action for pausing the game.
@ Reload
Action for reloading.
@ MoveRight
Action for moving right.
@ MoveDown
Action for moving down.
@ MoveUp
Action for moving up.
@ Menu
Action for opening the menu.
@ Shoot
Action for shooting.
@ MoveLeft
Action for moving left.
Difficulty
Game difficulty levels (solo mode only)
Definition Settings.hpp:56
Language
Supported languages.
Definition Settings.hpp:33
std::string getWeaponDisplayName(rtp::ecs::components::WeaponKind kind)
bool hasWeaponConfigs()
WeaponKind
Types of player weapons.
@ Tracker
Weak auto-homing shots.
@ Boomerang
Single projectile that returns.
@ Classic
Default spam/charge laser.
@ Beam
Continuous beam 5s active, 5s cooldown.
void error(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an error message.
void warning(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a warning message.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.