Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
InputConfigLoader.cpp
Go to the documentation of this file.
1/*
2Olympe Engine V2 - 2026
3Input Configuration Loader - Implementation
4
5Loads input configuration from JSON files.
6*/
7
8#include "InputConfigLoader.h"
9#include "InputDevice.h"
10#include "system/system_utils.h"
11#include "third_party/nlohmann/json.hpp"
12#include <fstream>
13#include <unordered_map>
14
16
17//==============================================================================
18// Key Name to SDL_Scancode Mapping
19//==============================================================================
20
21static std::unordered_map<std::string, SDL_Scancode> s_keyNameMap = {
22 // Letters
23 {"A", SDL_SCANCODE_A}, {"B", SDL_SCANCODE_B}, {"C", SDL_SCANCODE_C}, {"D", SDL_SCANCODE_D},
24 {"E", SDL_SCANCODE_E}, {"F", SDL_SCANCODE_F}, {"G", SDL_SCANCODE_G}, {"H", SDL_SCANCODE_H},
25 {"I", SDL_SCANCODE_I}, {"J", SDL_SCANCODE_J}, {"K", SDL_SCANCODE_K}, {"L", SDL_SCANCODE_L},
26 {"M", SDL_SCANCODE_M}, {"N", SDL_SCANCODE_N}, {"O", SDL_SCANCODE_O}, {"P", SDL_SCANCODE_P},
27 {"Q", SDL_SCANCODE_Q}, {"R", SDL_SCANCODE_R}, {"S", SDL_SCANCODE_S}, {"T", SDL_SCANCODE_T},
28 {"U", SDL_SCANCODE_U}, {"V", SDL_SCANCODE_V}, {"W", SDL_SCANCODE_W}, {"X", SDL_SCANCODE_X},
29 {"Y", SDL_SCANCODE_Y}, {"Z", SDL_SCANCODE_Z},
30
31 // Numbers
32 {"0", SDL_SCANCODE_0}, {"1", SDL_SCANCODE_1}, {"2", SDL_SCANCODE_2}, {"3", SDL_SCANCODE_3},
33 {"4", SDL_SCANCODE_4}, {"5", SDL_SCANCODE_5}, {"6", SDL_SCANCODE_6}, {"7", SDL_SCANCODE_7},
34 {"8", SDL_SCANCODE_8}, {"9", SDL_SCANCODE_9},
35
36 // Function keys
37 {"F1", SDL_SCANCODE_F1}, {"F2", SDL_SCANCODE_F2}, {"F3", SDL_SCANCODE_F3}, {"F4", SDL_SCANCODE_F4},
38 {"F5", SDL_SCANCODE_F5}, {"F6", SDL_SCANCODE_F6}, {"F7", SDL_SCANCODE_F7}, {"F8", SDL_SCANCODE_F8},
39 {"F9", SDL_SCANCODE_F9}, {"F10", SDL_SCANCODE_F10}, {"F11", SDL_SCANCODE_F11}, {"F12", SDL_SCANCODE_F12},
40
41 // Special keys
42 {"SPACE", SDL_SCANCODE_SPACE}, {"RETURN", SDL_SCANCODE_RETURN}, {"ESCAPE", SDL_SCANCODE_ESCAPE},
43 {"TAB", SDL_SCANCODE_TAB}, {"BACKSPACE", SDL_SCANCODE_BACKSPACE}, {"DELETE", SDL_SCANCODE_DELETE},
44
45 // Modifiers
46 {"LSHIFT", SDL_SCANCODE_LSHIFT}, {"RSHIFT", SDL_SCANCODE_RSHIFT},
47 {"LCTRL", SDL_SCANCODE_LCTRL}, {"RCTRL", SDL_SCANCODE_RCTRL},
48 {"LALT", SDL_SCANCODE_LALT}, {"RALT", SDL_SCANCODE_RALT},
49
50 // Arrow keys
51 {"UP", SDL_SCANCODE_UP}, {"DOWN", SDL_SCANCODE_DOWN},
52 {"LEFT", SDL_SCANCODE_LEFT}, {"RIGHT", SDL_SCANCODE_RIGHT},
53
54 // Navigation
55 {"HOME", SDL_SCANCODE_HOME}, {"END", SDL_SCANCODE_END},
56 {"PAGEUP", SDL_SCANCODE_PAGEUP}, {"PAGEDOWN", SDL_SCANCODE_PAGEDOWN},
57 {"INSERT", SDL_SCANCODE_INSERT}
58};
59
61 auto it = s_keyNameMap.find(keyName);
62 if (it != s_keyNameMap.end()) {
63 return it->second;
64 }
65
66 if (m_logLevel >= 1) {
67 SYSTEM_LOG << "[InputConfig][Warning] Unknown key name: " << keyName << ", using UNKNOWN\n";
68 }
70}
71
72//==============================================================================
73// Configuration Loading
74//==============================================================================
75
76bool InputConfigLoader::LoadEngineConfig(const std::string& path) {
77 SYSTEM_LOG << "[InputConfig][Info] Loading engine config from: " << path << "\n";
78
79 try {
80 std::ifstream file(path);
81 if (!file.is_open()) {
82 SYSTEM_LOG << "[InputConfig][Warning] Could not open engine config file: " << path << "\n";
83 return false;
84 }
85
86 json j;
87 file >> j;
88
89 // Read editor_enabled flag
90 if (j.contains("editor_enabled")) {
91 bool editorEnabled = j["editor_enabled"].get<bool>();
93 }
94
95 // Read log level
96 if (j.contains("input_log_level")) {
97 std::string logLevel = j["input_log_level"].get<std::string>();
99
100 if (logLevel == "error") m_logLevel = 0;
101 else if (logLevel == "warning") m_logLevel = 1;
102 else if (logLevel == "info") m_logLevel = 2;
103 else if (logLevel == "debug") m_logLevel = 3;
104 }
105
106 SYSTEM_LOG << "[InputConfig][Info] Engine config loaded successfully\n";
107 return true;
108 }
109 catch (const std::exception& e) {
110 SYSTEM_LOG << "[InputConfig][Error] Failed to parse engine config: " << e.what() << "\n";
111 return false;
112 }
113}
114
115bool InputConfigLoader::LoadInputConfig(const std::string& path) {
116 SYSTEM_LOG << "[InputConfig][Info] Loading input config from: " << path << "\n";
117
118 try {
119 std::ifstream file(path);
120 if (!file.is_open()) {
121 SYSTEM_LOG << "[InputConfig][Warning] Could not open input config file: " << path << "\n";
122 return false;
123 }
124
125 json j;
126 file >> j;
127
128 // Parse sections
129 if (j.contains("profiles")) {
130 if (!ParseProfiles(j["profiles"])) {
131 SYSTEM_LOG << "[InputConfig][Warning] Failed to parse profiles section\n";
132 }
133 }
134
135 if (j.contains("action_maps")) {
136 if (!ParseActionMaps(j["action_maps"])) {
137 SYSTEM_LOG << "[InputConfig][Warning] Failed to parse action_maps section\n";
138 }
139 }
140
141 if (j.contains("default_profile_assignment")) {
142 if (!ParseDefaultAssignments(j["default_profile_assignment"])) {
143 SYSTEM_LOG << "[InputConfig][Warning] Failed to parse default_profile_assignment section\n";
144 }
145 }
146
147 if (j.contains("settings")) {
148 if (!ParseGlobalSettings(j["settings"])) {
149 SYSTEM_LOG << "[InputConfig][Warning] Failed to parse settings section\n";
150 }
151 }
152
153 SYSTEM_LOG << "[InputConfig][Info] Input config loaded successfully\n";
154 return true;
155 }
156 catch (const std::exception& e) {
157 SYSTEM_LOG << "[InputConfig][Error] Failed to parse input config: " << e.what() << "\n";
158 return false;
159 }
160}
161
163 if (!j.is_array()) {
164 SYSTEM_LOG << "[InputConfig][Error] Profiles must be an array\n";
165 return false;
166 }
167
168 for (const auto& profileJson : j) {
169 try {
170 std::string name = profileJson["name"].get<std::string>();
171 std::string deviceTypeStr = profileJson["device_type"].get<std::string>();
172
174 if (deviceTypeStr == "Joystick") deviceType = InputDeviceType::Joystick;
175 else if (deviceTypeStr == "KeyboardMouse") deviceType = InputDeviceType::KeyboardMouse;
176
177 auto profile = std::make_shared<InputProfile>(name, deviceType);
178
179 // Parse settings
180 if (profileJson.contains("settings")) {
181 const auto& settings = profileJson["settings"];
182 if (settings.contains("deadzone")) {
183 profile->deadzone = settings["deadzone"].get<float>();
184 }
185 if (settings.contains("sensitivity")) {
186 profile->sensitivity = settings["sensitivity"].get<float>();
187 }
188 if (settings.contains("validate_overlaps")) {
189 profile->validateOverlaps = settings["validate_overlaps"].get<bool>();
190 }
191 }
192
193 // Parse actions
194 if (profileJson.contains("actions")) {
195 const auto& actions = profileJson["actions"];
196 for (auto it = actions.begin(); it != actions.end(); ++it) {
197 std::string actionName = it.key();
199 profile->AddAction(actionName, binding);
200 }
201 }
202
203 // Validate profile
204 if (profile->validateOverlaps) {
205 profile->ValidateNoOverlaps();
206 }
207
208 // Add profile to manager
210
211 if (m_logLevel >= 2) {
212 SYSTEM_LOG << "[InputConfig][Info] Loaded profile: " << name << " (" << deviceTypeStr << ")\n";
213 }
214 }
215 catch (const std::exception& e) {
216 SYSTEM_LOG << "[InputConfig][Error] Failed to parse profile: " << e.what() << "\n";
217 }
218 }
219
220 return true;
221}
222
225
226 std::string typeStr = j["type"].get<std::string>();
227
228 if (typeStr == "key") {
230 if (j.contains("primary")) {
231 std::string keyName = j["primary"].get<std::string>();
232 binding.primaryInput = static_cast<int>(ParseKeyName(keyName));
233 }
234 if (j.contains("alternate")) {
235 std::string keyName = j["alternate"].get<std::string>();
236 binding.alternateInput = static_cast<int>(ParseKeyName(keyName));
237 }
238 }
239 else if (typeStr == "button") {
241 if (j.contains("button")) {
242 binding.primaryInput = j["button"].get<int>();
243 }
244 }
245 else if (typeStr == "axis") {
247 if (j.contains("axis")) {
248 binding.primaryInput = j["axis"].get<int>();
249 }
250 if (j.contains("deadzone")) {
251 binding.axisDeadzone = j["deadzone"].get<float>();
252 }
253 if (j.contains("sensitivity")) {
254 binding.axisScale = j["sensitivity"].get<float>();
255 }
256 }
257 else if (typeStr == "stick") {
259 // Stick binding stores stick name in comment for reference
260 if (j.contains("stick")) {
261 binding.comment = j["stick"].get<std::string>();
262 }
263 if (j.contains("deadzone")) {
264 binding.axisDeadzone = j["deadzone"].get<float>();
265 }
266 if (j.contains("sensitivity")) {
267 binding.axisScale = j["sensitivity"].get<float>();
268 }
269 }
270 else if (typeStr == "trigger") {
272 if (j.contains("trigger")) {
273 binding.comment = j["trigger"].get<std::string>();
274 }
275 if (j.contains("threshold")) {
276 binding.triggerThreshold = j["threshold"].get<float>();
277 }
278 }
279 else if (typeStr == "mouse_button") {
281 if (j.contains("button")) {
282 binding.primaryInput = j["button"].get<int>();
283 }
284 }
285
286 return binding;
287}
288
290 if (!j.is_array()) {
291 SYSTEM_LOG << "[InputConfig][Error] Action maps must be an array\n";
292 return false;
293 }
294
295 for (const auto& mapJson : j) {
296 try {
297 std::string name = mapJson["name"].get<std::string>();
298 std::string contextStr = mapJson["context"].get<std::string>();
299
301 if (contextStr == "Gameplay") context = ActionMapContext::Gameplay;
302 else if (contextStr == "UI") context = ActionMapContext::UI;
303 else if (contextStr == "Editor") context = ActionMapContext::Editor;
304 else if (contextStr == "System") context = ActionMapContext::System;
305
306 int priority = mapJson.value("priority", 0);
307
308 ActionMap actionMap(name, context, priority);
309 actionMap.exclusive = mapJson.value("exclusive", false);
310
311 if (mapJson.contains("actions") && mapJson["actions"].is_array()) {
312 for (const auto& action : mapJson["actions"]) {
313 actionMap.AddAction(action.get<std::string>());
314 }
315 }
316
318
319 if (m_logLevel >= 2) {
320 SYSTEM_LOG << "[InputConfig][Info] Loaded action map: " << name
321 << " (Context: " << contextStr << ", Priority: " << priority << ")\n";
322 }
323 }
324 catch (const std::exception& e) {
325 SYSTEM_LOG << "[InputConfig][Error] Failed to parse action map: " << e.what() << "\n";
326 }
327 }
328
329 return true;
330}
331
333 try {
334 if (j.contains("Joystick")) {
335 std::string profileName = j["Joystick"].get<std::string>();
337 }
338
339 if (j.contains("KeyboardMouse")) {
340 std::string profileName = j["KeyboardMouse"].get<std::string>();
342 }
343
344 return true;
345 }
346 catch (const std::exception& e) {
347 SYSTEM_LOG << "[InputConfig][Error] Failed to parse default assignments: " << e.what() << "\n";
348 return false;
349 }
350}
351
353 try {
354 if (j.contains("log_level")) {
355 std::string logLevel = j["log_level"].get<std::string>();
357 }
358
359 return true;
360 }
361 catch (const std::exception& e) {
362 SYSTEM_LOG << "[InputConfig][Error] Failed to parse global settings: " << e.what() << "\n";
363 return false;
364 }
365}
366
367bool InputConfigLoader::LoadProfileOverride(const std::string& path) {
368 SYSTEM_LOG << "[InputConfig][Info] Loading profile overrides from: " << path << "\n";
369
370 try {
371 std::ifstream file(path);
372 if (!file.is_open()) {
373 if (m_logLevel >= 2) {
374 SYSTEM_LOG << "[InputConfig][Info] No user override file found (this is normal): " << path << "\n";
375 }
376 return false; // Not an error, just no overrides
377 }
378
379 json j;
380 file >> j;
381
382 // Parse overrides
383 if (j.contains("profile_overrides") && j["profile_overrides"].is_array()) {
384 for (const auto& overrideJson : j["profile_overrides"]) {
385 std::string profileName = overrideJson["profile"].get<std::string>();
386 auto profile = InputDeviceManager::Get().GetProfile(profileName);
387
388 if (!profile) {
389 SYSTEM_LOG << "[InputConfig][Warning] Cannot override profile '" << profileName << "': not found\n";
390 continue;
391 }
392
393 if (overrideJson.contains("actions")) {
394 const auto& actions = overrideJson["actions"];
395 for (auto it = actions.begin(); it != actions.end(); ++it) {
396 std::string actionName = it.key();
398 profile->AddAction(actionName, binding); // Overwrites existing binding
399
400 if (m_logLevel >= 3) {
401 SYSTEM_LOG << "[InputConfig][Debug] Overrode action '" << actionName
402 << "' in profile '" << profileName << "'\n";
403 }
404 }
405 }
406 }
407
408 SYSTEM_LOG << "[InputConfig][Info] Applied user overrides successfully\n";
409 }
410
411 return true;
412 }
413 catch (const std::exception& e) {
414 SYSTEM_LOG << "[InputConfig][Error] Failed to parse profile overrides: " << e.what() << "\n";
415 return false;
416 }
417}
418
419bool InputConfigLoader::SaveInputConfig(const std::string& path) {
420 SYSTEM_LOG << "[InputConfig][Info] Saving input config to: " << path << " (not implemented yet)\n";
421 // TODO: Implement config saving
422 return false;
423}
424
nlohmann::json json
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
static std::unordered_map< std::string, SDL_Scancode > s_keyNameMap
nlohmann::json json
InputDeviceType
Definition InputDevice.h:28
ActionMapContext
Definition InputDevice.h:44
SDL_Scancode ParseKeyName(const std::string &keyName)
bool SaveInputConfig(const std::string &path)
bool ParseDefaultAssignments(const json &j)
InputBinding ParseInputBinding(const json &j)
bool ParseGlobalSettings(const json &j)
bool ParseProfiles(const json &j)
bool LoadInputConfig(const std::string &path)
bool LoadProfileOverride(const std::string &path)
bool ParseActionMaps(const json &j)
bool LoadEngineConfig(const std::string &path)
void SetEditorEnabled(bool enabled)
static InputContextManager & Get()
std::shared_ptr< InputProfile > GetProfile(const std::string &profileName)
void AddProfile(std::shared_ptr< InputProfile > profile)
static InputDeviceManager & Get()
void SetLogLevel(const std::string &level)
void AddActionMap(const ActionMap &actionMap)
void SetDefaultProfile(InputDeviceType deviceType, const std::string &profileName)
nlohmann::json json
InputType type
Definition InputDevice.h:56
#define SYSTEM_LOG