Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
InputDevice.cpp
Go to the documentation of this file.
1/*
2Olympe Engine V2 - 2026
3Input System Refactor - Implementation
4
5Implementation of the new input device abstraction layer.
6*/
7
8#include "InputDevice.h"
10#include <algorithm>
11#include <unordered_set>
12
13//==============================================================================
14// InputProfile Implementation
15//==============================================================================
16
19 return true; // Only validate keyboard profiles when enabled
20 }
21
22 std::unordered_set<int> usedKeys;
23 bool hasOverlap = false;
24
25 for (const auto& entry : actionMappings)
26 {
27 const auto& actionName = entry.first;
28 const auto& binding = entry.second;
29
30 if (binding.type == InputType::Key) {
31 // Check primary key
32 if (binding.primaryInput != -1) {
33 if (usedKeys.count(binding.primaryInput) > 0) {
34 SYSTEM_LOG << "[InputProfile][Error] Keyboard profile '" << profileName
35 << "' has overlapping keys for action: " << actionName << "\n";
36 hasOverlap = true;
37 } else {
38 usedKeys.insert(binding.primaryInput);
39 }
40 }
41
42 // Check alternate key
43 if (binding.alternateInput != -1) {
44 if (usedKeys.count(binding.alternateInput) > 0) {
45 SYSTEM_LOG << "[InputProfile][Error] Keyboard profile '" << profileName
46 << "' has overlapping alternate key for action: " << actionName << "\n";
47 hasOverlap = true;
48 } else {
49 usedKeys.insert(binding.alternateInput);
50 }
51 }
52 }
53 }
54
55 if (hasOverlap) {
56 SYSTEM_LOG << "[InputProfile][Error] Profile validation failed: " << profileName << "\n";
57 return false;
58 }
59
60 SYSTEM_LOG << "[InputProfile][Info] Profile validation passed: " << profileName << "\n";
61 return true;
62}
63
65 // Initialize default bindings based on device type
67 // Default keyboard bindings (WASD + Arrows)
70 moveUp.primaryInput = SDL_SCANCODE_W;
71 moveUp.alternateInput = SDL_SCANCODE_UP;
72 AddAction("move_up", moveUp);
73
76 moveDown.primaryInput = SDL_SCANCODE_S;
77 moveDown.alternateInput = SDL_SCANCODE_DOWN;
78 AddAction("move_down", moveDown);
79
82 moveLeft.primaryInput = SDL_SCANCODE_A;
83 moveLeft.alternateInput = SDL_SCANCODE_LEFT;
84 AddAction("move_left", moveLeft);
85
88 moveRight.primaryInput = SDL_SCANCODE_D;
89 moveRight.alternateInput = SDL_SCANCODE_RIGHT;
90 AddAction("move_right", moveRight);
91
94 jump.primaryInput = SDL_SCANCODE_SPACE;
95 AddAction("jump", jump);
96
99 shoot.primaryInput = SDL_SCANCODE_LCTRL;
100 AddAction("shoot", shoot);
101
104 interact.primaryInput = SDL_SCANCODE_E;
105 AddAction("interact", interact);
106 }
108 // Default gamepad bindings
111 jump.primaryInput = 0; // A button
112 AddAction("jump", jump);
113
116 shoot.primaryInput = 1; // B button
117 AddAction("shoot", shoot);
118
121 interact.primaryInput = 2; // X button
122 AddAction("interact", interact);
123 }
124
125 SYSTEM_LOG << "[InputProfile][Info] Initialized default bindings for profile: " << profileName << "\n";
126}
127
128//==============================================================================
129// InputDeviceManager Implementation
130//==============================================================================
131
133 m_deviceSlots[slot.deviceIndex] = slot;
134
135 // Assign default profile if available
136 auto defaultProfileIt = m_defaultProfiles.find(slot.type);
137 if (defaultProfileIt != m_defaultProfiles.end()) {
138 auto profile = GetProfile(defaultProfileIt->second);
139 if (profile) {
140 m_deviceSlots[slot.deviceIndex].profile = profile;
141 if (m_logLevel >= 2) {
142 SYSTEM_LOG << "[InputDevice][Info] Assigned profile '" << profile->profileName
143 << "' to device: " << slot.deviceName << " (ID: " << slot.deviceIndex << ")\n";
144 }
145 }
146 }
147
148 if (m_logLevel >= 2) {
149 SYSTEM_LOG << "[InputDevice][Info] Device registered: " << slot.deviceName
150 << " (Type: " << (slot.type == InputDeviceType::Joystick ? "Joystick" : "KeyboardMouse")
151 << ", ID: " << slot.deviceIndex << ")\n";
152 }
153}
154
156 auto it = m_deviceSlots.find(deviceIndex);
157 if (it != m_deviceSlots.end()) {
158 if (m_logLevel >= 2) {
159 SYSTEM_LOG << "[InputDevice][Info] Device unregistered: " << it->second.deviceName
160 << " (ID: " << deviceIndex << ")\n";
161 }
162
163 // Remove player assignment if any
164 if (it->second.IsAssigned()) {
165 m_playerAssignments.erase(it->second.assignedPlayerID);
166 }
167
168 m_deviceSlots.erase(it);
169 }
170}
171
173 // Check if player already has a device
175 if (assignmentIt != m_playerAssignments.end()) {
176 auto deviceIt = m_deviceSlots.find(assignmentIt->second);
177 if (deviceIt != m_deviceSlots.end() && deviceIt->second.isConnected) {
178 if (m_logLevel >= 1) {
179 SYSTEM_LOG << "[InputDevice][Warning] Player " << playerID
180 << " already has device assigned: " << deviceIt->second.deviceName << "\n";
181 }
182 return &deviceIt->second;
183 }
184 }
185
186 // Find first available device (prefer joysticks)
188
189 if (availableDevice) {
190 availableDevice->assignedPlayerID = playerID;
192
193 if (m_logLevel >= 2) {
194 SYSTEM_LOG << "[InputDevice][Info] Auto-assigned "
195 << (availableDevice->type == InputDeviceType::Joystick ? "Joystick" : "KeyboardMouse")
196 << " " << availableDevice->deviceIndex << " to Player " << playerID
197 << " (" << availableDevice->deviceName << ")\n";
198 }
199
200 return availableDevice;
201 }
202
203 if (m_logLevel >= 1) {
204 SYSTEM_LOG << "[InputDevice][Warning] No available devices to assign to Player " << playerID << "\n";
205 }
206 return nullptr;
207}
208
210 auto deviceIt = m_deviceSlots.find(deviceIndex);
211 if (deviceIt == m_deviceSlots.end()) {
212 if (m_logLevel >= 0) {
213 SYSTEM_LOG << "[InputDevice][Error] Cannot assign device " << deviceIndex
214 << " to Player " << playerID << ": device not found\n";
215 }
216 return false;
217 }
218
220
221 if (!device.isConnected) {
222 if (m_logLevel >= 0) {
223 SYSTEM_LOG << "[InputDevice][Error] Cannot assign device " << deviceIndex
224 << " to Player " << playerID << ": device not connected\n";
225 }
226 return false;
227 }
228
229 if (device.IsAssigned() && device.assignedPlayerID != playerID) {
230 if (m_logLevel >= 1) {
231 SYSTEM_LOG << "[InputDevice][Warning] Device " << deviceIndex
232 << " already assigned to Player " << device.assignedPlayerID
233 << ", reassigning to Player " << playerID << "\n";
234 }
235 m_playerAssignments.erase(device.assignedPlayerID);
236 }
237
238 device.assignedPlayerID = playerID;
239 m_playerAssignments[playerID] = deviceIndex;
240
241 if (m_logLevel >= 2) {
242 SYSTEM_LOG << "[InputDevice][Info] Assigned device " << deviceIndex
243 << " (" << device.deviceName << ") to Player " << playerID << "\n";
244 }
245
246 return true;
247}
248
251 if (assignmentIt == m_playerAssignments.end()) {
252 if (m_logLevel >= 1) {
253 SYSTEM_LOG << "[InputDevice][Warning] Player " << playerID << " has no device assigned\n";
254 }
255 return false;
256 }
257
258 int deviceIndex = assignmentIt->second;
259 auto deviceIt = m_deviceSlots.find(deviceIndex);
260 if (deviceIt != m_deviceSlots.end()) {
261 deviceIt->second.assignedPlayerID = -1;
262 }
263
265
266 if (m_logLevel >= 2) {
267 SYSTEM_LOG << "[InputDevice][Info] Unassigned device from Player " << playerID << "\n";
268 }
269
270 return true;
271}
272
275 if (assignmentIt != m_playerAssignments.end()) {
276 auto deviceIt = m_deviceSlots.find(assignmentIt->second);
277 if (deviceIt != m_deviceSlots.end()) {
278 return &deviceIt->second;
279 }
280 }
281 return nullptr;
282}
283
286 if (assignmentIt != m_playerAssignments.end()) {
287 auto deviceIt = m_deviceSlots.find(assignmentIt->second);
288 if (deviceIt != m_deviceSlots.end()) {
289 return &deviceIt->second;
290 }
291 }
292 return nullptr;
293}
294
295std::vector<InputDeviceSlot*> InputDeviceManager::GetAvailableDevices() {
296 std::vector<InputDeviceSlot*> available;
297 for (auto& entry : m_deviceSlots) {
298 auto& slot = entry.second;
299 if (slot.IsAvailable()) {
300 available.push_back(&slot);
301 }
302 }
303 return available;
304}
305
306std::vector<InputDeviceSlot*> InputDeviceManager::GetAllDevices() {
307 std::vector<InputDeviceSlot*> all;
308 for (auto& entry : m_deviceSlots) {
309 auto& slot = entry.second;
310 all.push_back(&slot);
311 }
312 return all;
313}
314
315void InputDeviceManager::AddProfile(std::shared_ptr<InputProfile> profile) {
316 m_profiles[profile->profileName] = profile;
317 if (m_logLevel >= 2) {
318 SYSTEM_LOG << "[InputProfile][Info] Added profile: " << profile->profileName << "\n";
319 }
320}
321
322std::shared_ptr<InputProfile> InputDeviceManager::GetProfile(const std::string& profileName) {
323 auto it = m_profiles.find(profileName);
324 if (it != m_profiles.end()) {
325 return it->second;
326 }
327 return nullptr;
328}
329
330void InputDeviceManager::SetDefaultProfile(InputDeviceType deviceType, const std::string& profileName) {
331 m_defaultProfiles[deviceType] = profileName;
332 if (m_logLevel >= 2) {
333 SYSTEM_LOG << "[InputProfile][Info] Set default profile for "
334 << (deviceType == InputDeviceType::Joystick ? "Joystick" : "KeyboardMouse")
335 << ": " << profileName << "\n";
336 }
337}
338
340 m_actionMaps.push_back(actionMap);
341 if (m_logLevel >= 2) {
342 SYSTEM_LOG << "[InputContext][Info] Added action map: " << actionMap.mapName
343 << " (Priority: " << actionMap.priority << ")\n";
344 }
345}
346
347ActionMap* InputDeviceManager::GetActionMap(const std::string& mapName) {
348 for (auto& map : m_actionMaps) {
349 if (map.mapName == mapName) {
350 return &map;
351 }
352 }
353 return nullptr;
354}
355
357 std::vector<ActionMap*> maps;
358 for (auto& map : m_actionMaps) {
359 if (map.context == context) {
360 maps.push_back(&map);
361 }
362 }
363
364 // Sort by priority (highest first)
365 std::sort(maps.begin(), maps.end(), [](ActionMap* a, ActionMap* b) {
366 return a->priority > b->priority;
367 });
368
369 return maps;
370}
371
372void InputDeviceManager::SetLogLevel(const std::string& level) {
373 if (level == "error") m_logLevel = 0;
374 else if (level == "warning") m_logLevel = 1;
375 else if (level == "info") m_logLevel = 2;
376 else if (level == "debug") m_logLevel = 3;
377
378 SYSTEM_LOG << "[InputDevice][Info] Log level set to: " << level << "\n";
379}
380
382 SYSTEM_LOG << "[InputDevice][Info] === Device Status ===\n";
383 SYSTEM_LOG << "[InputDevice][Info] Total devices: " << m_deviceSlots.size() << "\n";
384
385 for (const auto& entry : m_deviceSlots) {
386 const auto& idx = entry.first;
387 const auto& slot = entry.second;
388 SYSTEM_LOG << "[InputDevice][Info] Device " << idx << ": " << slot.deviceName
389 << " (Type: " << (slot.type == InputDeviceType::Joystick ? "Joystick" : "KeyboardMouse")
390 << ", Connected: " << (slot.isConnected ? "Yes" : "No")
391 << ", Assigned: " << (slot.IsAssigned() ? "Player " + std::to_string(slot.assignedPlayerID) : "None")
392 << ", Profile: " << (slot.profile ? slot.profile->profileName : "None") << ")\n";
393 }
394
395 SYSTEM_LOG << "[InputDevice][Info] ====================\n";
396}
397
399 // First, try to find available joystick (preferred)
400 for (auto& entry : m_deviceSlots) {
401 auto& slot = entry.second;
402 if (slot.type == InputDeviceType::Joystick && slot.IsAvailable()) {
403 return &slot;
404 }
405 }
406
407 // If no joystick available, try keyboard-mouse
408 for (auto& entry : m_deviceSlots) {
409 auto& slot = entry.second;
410 if (slot.type == InputDeviceType::KeyboardMouse && slot.IsAvailable()) {
411 return &slot;
412 }
413 }
414
415 return nullptr;
416}
417
418//==============================================================================
419// InputContextManager Implementation
420//==============================================================================
421
423 m_contextStack.push_back(ctx);
424 SYSTEM_LOG << "[InputContext][Info] Pushed context: "
425 << (ctx == ActionMapContext::Gameplay ? "Gameplay" :
426 ctx == ActionMapContext::UI ? "UI" :
427 ctx == ActionMapContext::Editor ? "Editor" : "System")
428 << " (Stack size: " << m_contextStack.size() << ")\n";
429}
430
432 if (m_contextStack.size() > 1) {
434 m_contextStack.pop_back();
435 SYSTEM_LOG << "[InputContext][Info] Popped context: "
436 << (poppedContext == ActionMapContext::Gameplay ? "Gameplay" :
438 poppedContext == ActionMapContext::Editor ? "Editor" : "System")
439 << " (Stack size: " << m_contextStack.size() << ")\n";
440 } else {
441 SYSTEM_LOG << "[InputContext][Warning] Cannot pop context: stack has only one element\n";
442 }
443}
444
446 if (!m_contextStack.empty()) {
447 return m_contextStack.back();
448 }
450}
451
453 m_editorEnabled = enabled;
454 SYSTEM_LOG << "[InputContext][Info] Editor mode " << (enabled ? "enabled" : "disabled") << "\n";
455
456 if (enabled) {
457 // Push editor context if not already there
460 }
461 } else {
462 // Pop editor context if it's active
464 PopContext();
465 }
466 }
467}
468
471 SYSTEM_LOG << "[InputContext][Info] Initialized with Gameplay context\n";
472}
473
475 // This will be implemented in InputConfigLoader
476 SYSTEM_LOG << "[InputContext][Info] Loading config from: " << configPath << "\n";
477}
478
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
InputDeviceType
Definition InputDevice.h:28
ActionMapContext
Definition InputDevice.h:44
void PushContext(ActionMapContext ctx)
std::vector< ActionMapContext > m_contextStack
ActionMapContext GetActiveContext() const
void SetEditorEnabled(bool enabled)
void LoadConfig(const std::string &configPath)
std::shared_ptr< InputProfile > GetProfile(const std::string &profileName)
std::unordered_map< std::string, std::shared_ptr< InputProfile > > m_profiles
std::vector< ActionMap * > GetActionMapsForContext(ActionMapContext context)
void RegisterDevice(const InputDeviceSlot &slot)
std::unordered_map< short, int > m_playerAssignments
std::unordered_map< int, InputDeviceSlot > m_deviceSlots
void AddProfile(std::shared_ptr< InputProfile > profile)
std::vector< ActionMap > m_actionMaps
std::vector< InputDeviceSlot * > GetAvailableDevices()
std::unordered_map< InputDeviceType, std::string > m_defaultProfiles
InputDeviceSlot * AutoAssignDevice(short playerID)
void LogDeviceStatus() const
bool UnassignDevice(short playerID)
void SetLogLevel(const std::string &level)
void UnregisterDevice(int deviceIndex)
bool AssignDeviceToPlayer(int deviceIndex, short playerID)
InputDeviceSlot * FindFirstAvailableDevice()
ActionMap * GetActionMap(const std::string &mapName)
void AddActionMap(const ActionMap &actionMap)
std::vector< InputDeviceSlot * > GetAllDevices()
InputDeviceSlot * GetDeviceForPlayer(short playerID)
void SetDefaultProfile(InputDeviceType deviceType, const std::string &profileName)
bool ValidateNoOverlaps() const
InputDeviceType deviceType
Definition InputDevice.h:88
bool validateOverlaps
Definition InputDevice.h:98
std::string profileName
Definition InputDevice.h:87
void InitializeDefaults()
void AddAction(const std::string &actionName, const InputBinding &binding)
std::unordered_map< std::string, InputBinding > actionMappings
Definition InputDevice.h:92
InputType type
Definition InputDevice.h:56
std::string deviceName
#define SYSTEM_LOG