1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16 // Input to player control mapping
17
18 #include "C4Include.h"
19 #include "C4ForbidLibraryCompilation.h"
20 #include "control/C4PlayerControl.h"
21
22 #include "c4group/C4LangStringTable.h"
23 #include "control/C4Control.h"
24 #include "control/C4Record.h"
25 #include "game/C4GraphicsSystem.h"
26 #include "game/C4Viewport.h"
27 #include "graphics/C4GraphicsResource.h"
28 #include "gui/C4MouseControl.h"
29 #include "object/C4DefList.h"
30 #include "object/C4Object.h"
31 #include "object/C4ObjectMenu.h"
32 #include "platform/C4GamePadCon.h"
33 #include "player/C4Player.h"
34 #include "player/C4PlayerList.h"
35 #include "script/C4Aul.h"
36
37 /* C4PlayerControlDef */
38
CompileFunc(StdCompiler * pComp)39 void C4PlayerControlDef::CompileFunc(StdCompiler *pComp)
40 {
41 if (!pComp->Name("ControlDef")) { pComp->NameEnd(); pComp->excNotFound("ControlDef"); }
42 pComp->Value(mkNamingAdapt(mkParAdapt(sIdentifier, StdCompiler::RCT_Idtf), "Identifier", "None"));
43 pComp->Value(mkNamingAdapt(mkParAdapt(sGUIName, StdCompiler::RCT_All), "GUIName", ""));
44 pComp->Value(mkNamingAdapt(mkParAdapt(sGUIDesc, StdCompiler::RCT_All), "GUIDesc", ""));
45 pComp->Value(mkNamingAdapt(fGlobal, "Global", false));
46 pComp->Value(mkNamingAdapt(fIsHoldKey, "Hold", false));
47 pComp->Value(mkNamingAdapt(iRepeatDelay, "RepeatDelay", 0));
48 pComp->Value(mkNamingAdapt(iInitialRepeatDelay, "InitialRepeatDelay", 0));
49 pComp->Value(mkNamingAdapt(fDefaultDisabled, "DefaultDisabled", false));
50 pComp->Value(mkNamingAdapt(idControlExtraData, "ExtraData", C4ID::None));
51 const StdEnumEntry<CoordinateSpace> CoordSpaceNames[] =
52 {
53 { "Game", COS_Game },
54 { "Viewport", COS_Viewport },
55 { nullptr, COS_Game }
56 };
57 pComp->Value(mkNamingAdapt(mkEnumAdapt<CoordinateSpace, int32_t>(eCoordSpace, CoordSpaceNames), "CoordinateSpace", COS_Game));
58 pComp->Value(mkNamingAdapt(fSendCursorPos, "SendCursorPos", false));
59 const StdEnumEntry<Actions> ActionNames[] =
60 {
61 { "None", CDA_None },
62 { "Script", CDA_Script },
63 { "Menu", CDA_Menu },
64 { "MenuOK", CDA_MenuOK },
65 { "MenuCancel", CDA_MenuCancel },
66 { "MenuLeft", CDA_MenuLeft },
67 { "MenuUp", CDA_MenuUp },
68 { "MenuRight", CDA_MenuRight },
69 { "MenuDown", CDA_MenuDown },
70 { "ObjectMenuTextComplete", CDA_ObjectMenuTextComplete },
71 { "ObjectMenuOK", CDA_ObjectMenuOK },
72 { "ObjectMenuOKAll", CDA_ObjectMenuOKAll },
73 { "ObjectMenuSelect",CDA_ObjectMenuSelect },
74 { "ObjectMenuCancel",CDA_ObjectMenuCancel },
75 { "ObjectMenuLeft", CDA_ObjectMenuLeft },
76 { "ObjectMenuUp", CDA_ObjectMenuUp },
77 { "ObjectMenuRight", CDA_ObjectMenuRight },
78 { "ObjectMenuDown", CDA_ObjectMenuDown },
79 { "ZoomIn", CDA_ZoomIn },
80 { "ZoomOut", CDA_ZoomOut },
81 { nullptr, CDA_None }
82 };
83 pComp->Value(mkNamingAdapt(mkEnumAdapt<Actions, int32_t>(eAction, ActionNames), "Action", CDA_Script));
84 pComp->NameEnd();
85 }
86
operator ==(const C4PlayerControlDef & cmp) const87 bool C4PlayerControlDef::operator ==(const C4PlayerControlDef &cmp) const
88 {
89 return sIdentifier == cmp.sIdentifier
90 && sGUIName == cmp.sGUIName
91 && sGUIDesc == cmp.sGUIDesc
92 && fGlobal == cmp.fGlobal
93 && fIsHoldKey == cmp.fIsHoldKey
94 && iRepeatDelay == cmp.iRepeatDelay
95 && iInitialRepeatDelay == cmp.iInitialRepeatDelay
96 && fDefaultDisabled == cmp.fDefaultDisabled
97 && idControlExtraData == cmp.idControlExtraData
98 && fSendCursorPos == cmp.fSendCursorPos
99 && eAction == cmp.eAction;
100 }
101
102
103 /* C4PlayerControlDefs */
104
UpdateInternalCons()105 void C4PlayerControlDefs::UpdateInternalCons()
106 {
107 InternalCons.CON_ObjectMenuSelect = GetControlIndexByIdentifier("ObjectMenuSelect");
108 InternalCons.CON_ObjectMenuOK = GetControlIndexByIdentifier("ObjectMenuOK");
109 InternalCons.CON_ObjectMenuOKAll = GetControlIndexByIdentifier("ObjectMenuOKAll");
110 InternalCons.CON_ObjectMenuCancel = GetControlIndexByIdentifier("ObjectMenuCancel");
111 InternalCons.CON_CursorPos = GetControlIndexByIdentifier("CursorPos");
112 }
113
Clear()114 void C4PlayerControlDefs::Clear()
115 {
116 clear_previous = false;
117 Defs.clear();
118 UpdateInternalCons();
119 }
120
CompileFunc(StdCompiler * pComp)121 void C4PlayerControlDefs::CompileFunc(StdCompiler *pComp)
122 {
123 pComp->Value(mkNamingAdapt(clear_previous, "ClearPrevious", false));
124 pComp->Value(mkSTLContainerAdapt(Defs, StdCompiler::SEP_NONE));
125 if (pComp->isDeserializer()) UpdateInternalCons();
126 }
127
MergeFrom(const C4PlayerControlDefs & Src)128 void C4PlayerControlDefs::MergeFrom(const C4PlayerControlDefs &Src)
129 {
130 // Clear previous defs if specified in merge set
131 if (Src.clear_previous) Defs.clear();
132 // copy all defs from source file; overwrite defs of same name if found
133 for (const auto & SrcDef : Src.Defs)
134 {
135 // overwrite if def of same name existed
136 int32_t iPrevIdx = GetControlIndexByIdentifier(SrcDef.GetIdentifier());
137 if (iPrevIdx != CON_None)
138 {
139 Defs[iPrevIdx] = SrcDef;
140 }
141 else
142 {
143 // new def: Append a copy
144 Defs.push_back(SrcDef);
145 }
146 }
147 UpdateInternalCons();
148 }
149
GetControlByIndex(int32_t idx) const150 const C4PlayerControlDef *C4PlayerControlDefs::GetControlByIndex(int32_t idx) const
151 {
152 // safe index
153 if (idx<0 || idx>=int32_t(Defs.size())) return nullptr;
154 return &(Defs[idx]);
155 }
156
GetControlIndexByIdentifier(const char * szIdentifier) const157 int32_t C4PlayerControlDefs::GetControlIndexByIdentifier(const char *szIdentifier) const
158 {
159 for (DefVecImpl::const_iterator i = Defs.begin(); i != Defs.end(); ++i)
160 if (SEqual((*i).GetIdentifier(), szIdentifier))
161 return i-Defs.begin();
162 return CON_None;
163 }
164
FinalInit()165 void C4PlayerControlDefs::FinalInit()
166 {
167 // Assume all defs have been loaded
168 // Register scritp constants
169 for (DefVecImpl::const_iterator i = Defs.begin(); i != Defs.end(); ++i)
170 {
171 const char *szIdtf = (*i).GetIdentifier();
172 if (szIdtf && *szIdtf && !SEqual(szIdtf, "None"))
173 {
174 ::ScriptEngine.RegisterGlobalConstant(FormatString("CON_%s", szIdtf).getData(), C4VInt(i-Defs.begin()));
175 }
176 }
177 }
178
179
180 /* C4PlayerControlAssignment */
181
CompileFunc(StdCompiler * pComp)182 void C4PlayerControlAssignment::KeyComboItem::CompileFunc(StdCompiler *pComp)
183 {
184 // if key is compiled, also store as a string into KeyName for later resolving
185 if (pComp->isDeserializer())
186 {
187 Key.dwShift = 0;
188 sKeyName.Clear();
189 pComp->Value(mkParAdapt(Key, &sKeyName));
190 }
191 else
192 {
193 // decompiler: If there's a stored key name, just write it. Regardless of whether it's a key, undefined or a reference
194 // If no key name is stored, it was probably assigned at runtime and sKeyName needs to be recreated
195 if (!sKeyName) UpdateKeyName();
196 pComp->Value(mkParAdapt(sKeyName, StdCompiler::RCT_Idtf));
197 }
198 }
199
UpdateKeyName()200 void C4PlayerControlAssignment::KeyComboItem::UpdateKeyName()
201 {
202 // update key name from key
203 sKeyName.Copy(Key.ToString(false, false));
204 }
205
CompileFunc(StdCompiler * pComp)206 void C4PlayerControlAssignment::CompileFunc(StdCompiler *pComp)
207 {
208 if (!pComp->Name("Assignment")) { pComp->NameEnd(); pComp->excNotFound("Assignment"); }
209 pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(KeyCombo), "Key", KeyComboVec()));
210 pComp->Value(mkNamingAdapt(fComboIsSequence, "ComboIsSequence", false));
211 pComp->Value(mkNamingAdapt(mkParAdapt(sControlName, StdCompiler::RCT_Idtf), "Control", "None"));
212 pComp->Value(mkNamingAdapt(mkParAdapt(sGUIName, StdCompiler::RCT_All), "GUIName", ""));
213 pComp->Value(mkNamingAdapt(mkParAdapt(sGUIDesc, StdCompiler::RCT_All), "GUIDesc", ""));
214 pComp->Value(mkNamingAdapt(iGUIGroup,"GUIGroup",0));
215 pComp->Value(mkNamingAdapt(fGUIDisabled, "GUIDisabled", false));
216 pComp->Value(mkNamingAdapt(iPriority, "Priority", 0));
217 const StdBitfieldEntry<int32_t> TriggerModeNames[] =
218 {
219 { "Default", CTM_Default },
220 { "Hold", CTM_Hold },
221 { "Release", CTM_Release },
222 { "AlwaysUnhandled", CTM_AlwaysUnhandled },
223 { "ClearRecentKeys", CTM_ClearRecentKeys },
224 { nullptr, 0 }
225 };
226 pComp->Value(mkNamingAdapt(mkBitfieldAdapt< int32_t>(iTriggerMode, TriggerModeNames), "TriggerMode", CTM_Default));
227 pComp->Value(mkNamingAdapt(fOverrideAssignments, "OverrideAssignments", false));
228 pComp->NameEnd();
229 // newly loaded structures are not resolved
230 if (pComp->isDeserializer()) fRefsResolved = false;
231 }
232
ResetKeyToInherited()233 void C4PlayerControlAssignment::ResetKeyToInherited()
234 {
235 if (inherited_assignment) CopyKeyFrom(*inherited_assignment);
236 }
237
IsKeyChanged() const238 bool C4PlayerControlAssignment::IsKeyChanged() const
239 {
240 // no inherited assignment? Then the key is always custom
241 if (!inherited_assignment) return true;
242 // otherwise, compare
243 return KeyCombo != inherited_assignment->KeyCombo || fComboIsSequence != inherited_assignment->fComboIsSequence;
244 }
245
SetKey(const C4KeyCodeEx & key)246 void C4PlayerControlAssignment::SetKey(const C4KeyCodeEx &key)
247 {
248 // set as one-key-combo
249 KeyCombo.resize(1);
250 KeyCombo[0].Key = key;
251 KeyCombo[0].Key.fRepeated = false;
252 KeyCombo[0].sKeyName.Clear();
253 fComboIsSequence = false;
254 TriggerKey = key;
255 }
256
CopyKeyFrom(const C4PlayerControlAssignment & src_assignment)257 void C4PlayerControlAssignment::CopyKeyFrom(const C4PlayerControlAssignment &src_assignment)
258 {
259 // just copy key settings; keep control and priorities
260 KeyCombo = src_assignment.KeyCombo;
261 TriggerKey = src_assignment.TriggerKey;
262 fComboIsSequence = src_assignment.fComboIsSequence;
263 if (!src_assignment.fRefsResolved) fRefsResolved = false;
264 }
265
ResolveRefs(C4PlayerControlAssignmentSet * pParentSet,C4PlayerControlDefs * pControlDefs)266 bool C4PlayerControlAssignment::ResolveRefs(C4PlayerControlAssignmentSet *pParentSet, C4PlayerControlDefs *pControlDefs)
267 {
268 // avoid circular chains
269 static int32_t recursion_check = 0;
270 if (recursion_check > 10)
271 {
272 LogFatal(FormatString("Maximum recursion limit reached while resolving player control assignments of set %s in assignment for key %s. This is probably due to a circular control chain.", pParentSet->GetName(), GetControlName()).getData());
273 return false;
274 }
275 ++recursion_check;
276 // resolve control name
277 iControl = pControlDefs->GetControlIndexByIdentifier(sControlName.getData());
278 // resolve keys
279 KeyComboVec NewCombo;
280 for (auto & rKeyComboItem : KeyCombo)
281 {
282 const char *szKeyName = rKeyComboItem.sKeyName.getData();
283 // check if this is a key reference. A key reference must be preceded by CON_
284 // it may also be preceded by modifiers (Shift+), which are already set in rKeyComboItem.Key.dwShift
285 bool is_key_reference = false;
286 int last_shift_delim_pos;
287 if (szKeyName && *szKeyName)
288 {
289 if ((last_shift_delim_pos=SCharLastPos('+', szKeyName)) > -1) szKeyName += last_shift_delim_pos+1;
290 if (SEqual2(szKeyName, "CON_"))
291 {
292 is_key_reference = true;
293 szKeyName +=4;
294 }
295 }
296 if (is_key_reference)
297 {
298 // this is a key reference
299 // - find referenced target assignment
300 C4PlayerControlAssignment *pRefAssignment = pParentSet->GetAssignmentByControlName(szKeyName);
301 if (pRefAssignment)
302 {
303 // resolve itself if necessary
304 if (!pRefAssignment->IsRefsResolved()) if (!pRefAssignment->ResolveRefs(pParentSet, pControlDefs)) { --recursion_check; return false; }
305 // insert all keys of that combo into own combo
306 // add any extra shift states from reference
307 DWORD ref_shift = rKeyComboItem.Key.dwShift;
308 if (ref_shift)
309 {
310 for (auto assignment_combo_item : pRefAssignment->KeyCombo)
311 {
312 assignment_combo_item.Key.dwShift |= ref_shift;
313 NewCombo.push_back(assignment_combo_item);
314 }
315 }
316 else
317 {
318 NewCombo.insert(NewCombo.end(), pRefAssignment->KeyCombo.begin(), pRefAssignment->KeyCombo.end());
319 }
320 }
321 else
322 {
323 // undefined reference? Not fatal, but inform user
324 LogF("WARNING: Control %s of set %s contains reference to unassigned control %s.", GetControlName(), pParentSet->GetName(), rKeyComboItem.sKeyName.getData());
325 NewCombo.clear();
326 }
327 }
328 else
329 {
330 // non-reference: check if the assignment was valid
331 #ifndef USE_CONSOLE
332 if (rKeyComboItem.Key == KEY_Default)
333 LogF(R"(WARNING: Control %s of set %s contains undefined key "%s".)", GetControlName(), pParentSet->GetName(), szKeyName);
334 #endif
335 // ...and just keep this item.
336 NewCombo.push_back(rKeyComboItem);
337 }
338 }
339 KeyCombo = NewCombo;
340 // adjust Control and Shift into key states for non-sequence combo keys
341 // e.g. LeftControl,A should become LeftControl,Ctrl+A.
342 if (KeyCombo.size() > 1 && !fComboIsSequence)
343 {
344 int32_t shift = 0;
345 for (auto & i : KeyCombo)
346 {
347 if (i.Key.Key == K_CONTROL_L || i.Key.Key == K_CONTROL_R) shift |= KEYS_Control;
348 if (i.Key.Key == K_SHIFT_L || i.Key.Key == K_SHIFT_R) shift |= KEYS_Shift;
349 shift |= i.Key.dwShift;
350 }
351 for (auto & i : KeyCombo) i.Key.dwShift |= shift;
352 }
353 // remove control/shift duplications
354 for (auto & i : KeyCombo) i.Key.FixShiftKeys();
355 // the trigger key is always last of the chain
356 if (KeyCombo.size()) TriggerKey = KeyCombo.back().Key; else TriggerKey = C4KeyCodeEx();
357 // done
358 fRefsResolved = true;
359 --recursion_check;
360 return true;
361 }
362
IsComboMatched(const C4PlayerControlRecentKeyList & DownKeys,const C4PlayerControlRecentKeyList & RecentKeys) const363 bool C4PlayerControlAssignment::IsComboMatched(const C4PlayerControlRecentKeyList &DownKeys, const C4PlayerControlRecentKeyList &RecentKeys) const
364 {
365 assert(HasCombo());
366 // check if combo is currently fulfilled (assuming TriggerKey is already matched)
367 if (fComboIsSequence)
368 {
369 C4TimeMilliseconds tKeyLast = C4TimeMilliseconds::Now();
370 // combo is a sequence: The last keys of RecentKeys must match the sequence
371 // the last ComboKey is the TriggerKey, which is omitted because it has already been matched and is not to be found in RecentKeys yet
372 auto i = KeyCombo.rbegin()+1;
373 for (auto ri = RecentKeys.rbegin(); i!=KeyCombo.rend(); ++ri)
374 {
375 // no more keys pressed but combo didn't end? -> no combo match
376 if (ri == RecentKeys.rend()) return false;
377 const C4PlayerControlRecentKey &rk = *ri;
378 // user waited for too long?
379 C4TimeMilliseconds tKeyRecent = rk.tTime;
380 if (tKeyLast - tKeyRecent > C4PlayerControl::MaxSequenceKeyDelay) return false;
381 // key doesn't match?
382 const KeyComboItem &k = *i;
383 if (!(rk.matched_key == k.Key))
384 {
385 // mouse movement commands do not break sequences
386 if (Key_IsMouse(rk.matched_key.Key) && Key_GetMouseEvent(rk.matched_key.Key) == KEY_MOUSE_Move) continue;
387 return false;
388 }
389 // key OK
390 ++i;
391 }
392 }
393 else
394 {
395 // combo requires keys to be down simultanuously: check that all keys of the combo are in the down-list
396 for (const auto & k : KeyCombo)
397 {
398 bool fFound = false;
399 for (const auto & dk : DownKeys)
400 {
401 if (dk.matched_key == k.Key) { fFound = true; break; }
402 }
403 if (!fFound) return false;
404 }
405 }
406 // combo OK!
407 return true;
408 }
409
operator ==(const C4PlayerControlAssignment & cmp) const410 bool C4PlayerControlAssignment::operator ==(const C4PlayerControlAssignment &cmp) const
411 {
412 // doesn't compare resolved TriggerKey/iControl
413 return KeyCombo == cmp.KeyCombo
414 && sControlName == cmp.sControlName
415 && sGUIName == cmp.sGUIName
416 && sGUIDesc == cmp.sGUIDesc
417 && fGUIDisabled == cmp.fGUIDisabled
418 && iTriggerMode == cmp.iTriggerMode
419 && iPriority == cmp.iPriority;
420 }
421
GetKeysAsString(bool human_readable,bool short_name) const422 StdStrBuf C4PlayerControlAssignment::GetKeysAsString(bool human_readable, bool short_name) const
423 {
424 // create a short, human-readable string of the assigned key
425 // to be displayed e.g. in tutorial messages explaining controls
426 StdStrBuf result;
427 if (!KeyCombo.size()) return result;
428 // trigger key
429 KeyComboVec::const_iterator i=KeyCombo.begin();
430 result.Take(i->Key.ToString(human_readable, short_name));
431 // extra keys of combo
432 while (++i != KeyCombo.end())
433 {
434 result.AppendChar(fComboIsSequence ? ',' : '+');
435 result.Append(i->Key.ToString(human_readable, short_name));
436 }
437 return result;
438 }
439
GetGUIName(const C4PlayerControlDefs & defs) const440 const char *C4PlayerControlAssignment::GetGUIName(const C4PlayerControlDefs &defs) const
441 {
442 // local name?
443 if (sGUIName.getLength())
444 {
445 // special: None defaults to empty name
446 if (sGUIName == "None") return "";
447 return sGUIName.getData();
448 }
449 // otherwise, fall back to def
450 const C4PlayerControlDef *def = defs.GetControlByIndex(GetControl());
451 if (def) return def->GetGUIName();
452 // no def and no name...
453 return nullptr;
454 }
455
GetGUIDesc(const C4PlayerControlDefs & defs) const456 const char *C4PlayerControlAssignment::GetGUIDesc(const C4PlayerControlDefs &defs) const
457 {
458 // local desc?
459 if (sGUIDesc.getLength()) return sGUIDesc.getData();
460 // otherwise, fall back to def
461 const C4PlayerControlDef *def = defs.GetControlByIndex(GetControl());
462 if (def) return def->GetGUIDesc();
463 // no def and no desc...
464 return nullptr;
465 }
466
IsGUIDisabled() const467 bool C4PlayerControlAssignment::IsGUIDisabled() const
468 {
469 return fGUIDisabled;
470 }
471
GetGUIGroup() const472 int32_t C4PlayerControlAssignment::GetGUIGroup() const
473 {
474 return iGUIGroup;
475 }
476
477 /* C4PlayerControlAssignmentSet */
478
InitEmptyFromTemplate(const C4PlayerControlAssignmentSet & template_set)479 void C4PlayerControlAssignmentSet::InitEmptyFromTemplate(const C4PlayerControlAssignmentSet &template_set)
480 {
481 // copy all fields except assignments
482 sName.Copy(template_set.sName);
483 sGUIName.Copy(template_set.sGUIName);
484 sParentSetName.Copy(template_set.sParentSetName);
485 has_keyboard = template_set.has_keyboard;
486 has_mouse = template_set.has_mouse;
487 has_gamepad = template_set.has_gamepad;
488 }
489
CompileFunc(StdCompiler * pComp)490 void C4PlayerControlAssignmentSet::CompileFunc(StdCompiler *pComp)
491 {
492 if (!pComp->Name("ControlSet")) { pComp->NameEnd(); pComp->excNotFound("ControlSet"); }
493 pComp->Value(mkNamingAdapt(mkParAdapt(sName, StdCompiler::RCT_All), "Name", "None")); // can't do RCT_Idtf because of wildcards
494 pComp->Value(mkNamingAdapt(mkParAdapt(sGUIName, StdCompiler::RCT_All), "GUIName", "undefined"));
495 pComp->Value(mkNamingAdapt(mkParAdapt(sParentSetName, StdCompiler::RCT_Idtf), "Parent", ""));
496 pComp->Value(mkNamingAdapt(has_keyboard, "Keyboard", true));
497 pComp->Value(mkNamingAdapt(has_mouse, "Mouse", true));
498 pComp->Value(mkNamingAdapt(has_gamepad, "Gamepad", false));
499 pComp->Value(mkSTLContainerAdapt(Assignments, StdCompiler::SEP_NONE));
500 pComp->NameEnd();
501 }
502
503
504
MergeFrom(const C4PlayerControlAssignmentSet & Src,MergeMode merge_mode)505 void C4PlayerControlAssignmentSet::MergeFrom(const C4PlayerControlAssignmentSet &Src, MergeMode merge_mode)
506 {
507 // take over all assignments defined in Src
508 for (const auto & SrcAssignment : Src.Assignments)
509 {
510 bool fIsReleaseKey = !!(SrcAssignment.GetTriggerMode() & C4PlayerControlAssignment::CTM_Release);
511 // overwrite same def and release key state
512 if (merge_mode != MM_LowPrio && SrcAssignment.IsOverrideAssignments())
513 {
514 // high priority override control clears all previous (very inefficient method...might as well recreate the whole list)
515 bool any_remaining = true;
516 while (any_remaining)
517 {
518 any_remaining = false;
519 for (C4PlayerControlAssignmentVec::iterator j = Assignments.begin(); j != Assignments.end(); ++j)
520 if (SEqual((*j).GetControlName(), SrcAssignment.GetControlName()))
521 {
522 bool fSelfIsReleaseKey = !!((*j).GetTriggerMode() & C4PlayerControlAssignment::CTM_Release);
523 if (fSelfIsReleaseKey == fIsReleaseKey)
524 {
525 Assignments.erase(j);
526 any_remaining = true;
527 break;
528 }
529 }
530 }
531 }
532 else if (merge_mode == MM_LowPrio || merge_mode == MM_ConfigOverload)
533 {
534 // if this is low priority, another override control kills this
535 bool any_override = false;
536 for (auto & Assignment : Assignments)
537 if (SEqual(Assignment.GetControlName(), SrcAssignment.GetControlName()))
538 {
539 bool fSelfIsReleaseKey = !!(Assignment.GetTriggerMode() & C4PlayerControlAssignment::CTM_Release);
540 if (fSelfIsReleaseKey == fIsReleaseKey)
541 {
542 any_override = true;
543 // config overloads just change the key of the inherited assignment
544 if (merge_mode == MM_ConfigOverload)
545 {
546 Assignment.CopyKeyFrom(SrcAssignment);
547 Assignment.SetInherited(false);
548 }
549 break;
550 }
551 }
552 if (any_override) continue;
553 }
554 // new def: Append a copy
555 Assignments.push_back(SrcAssignment);
556 // inherited marker
557 if (merge_mode == MM_Inherit)
558 {
559 Assignments.back().SetInherited(true);
560 Assignments.back().SetInheritedAssignment(&SrcAssignment);
561 }
562 }
563 }
564
CreateAssignmentForControl(const char * control_name)565 C4PlayerControlAssignment *C4PlayerControlAssignmentSet::CreateAssignmentForControl(const char *control_name)
566 {
567 Assignments.emplace_back();
568 Assignments.back().SetControlName(control_name);
569 return &Assignments.back();
570 }
571
RemoveAssignmentByControlName(const char * control_name)572 void C4PlayerControlAssignmentSet::RemoveAssignmentByControlName(const char *control_name)
573 {
574 for (C4PlayerControlAssignmentVec::iterator i = Assignments.begin(); i != Assignments.end(); ++i)
575 if (SEqual((*i).GetControlName(), control_name))
576 {
577 Assignments.erase(i);
578 return;
579 }
580 }
581
ResolveRefs(C4PlayerControlDefs * pDefs)582 bool C4PlayerControlAssignmentSet::ResolveRefs(C4PlayerControlDefs *pDefs)
583 {
584 // reset all resolved flags to allow re-resolve after overloads
585 for (auto & Assignment : Assignments)
586 Assignment.ResetRefsResolved();
587 // resolve in order; ignore already resolved because they might have been resolved by cross reference
588 for (auto & Assignment : Assignments)
589 if (!Assignment.IsRefsResolved())
590 if (!Assignment.ResolveRefs(this, pDefs))
591 return false;
592 return true;
593 }
594
SortAssignments()595 void C4PlayerControlAssignmentSet::SortAssignments()
596 {
597 // final init: sort assignments by priority
598 // note this screws up sorting for config dialog
599 std::sort(Assignments.begin(), Assignments.end());
600 }
601
GetAssignmentByIndex(int32_t index)602 C4PlayerControlAssignment *C4PlayerControlAssignmentSet::GetAssignmentByIndex(int32_t index)
603 {
604 if (index<0 || index>=int32_t(Assignments.size())) return nullptr;
605 return &Assignments[index];
606 }
607
GetAssignmentByControlName(const char * szControlName)608 C4PlayerControlAssignment *C4PlayerControlAssignmentSet::GetAssignmentByControlName(const char *szControlName)
609 {
610 for (auto & Assignment : Assignments)
611 if (SEqual(Assignment.GetControlName(), szControlName))
612 // We don't like release keys... (2do)
613 if (!(Assignment.GetTriggerMode() & C4PlayerControlAssignment::CTM_Release))
614 return &Assignment;
615 return nullptr;
616 }
617
GetAssignmentByControl(int32_t control)618 C4PlayerControlAssignment *C4PlayerControlAssignmentSet::GetAssignmentByControl(int32_t control)
619 {
620 // TODO: Might want to stuff this into a vector indexed by control for faster lookup
621 for (auto & Assignment : Assignments)
622 if (Assignment.GetControl() == control)
623 // We don't like release keys... (2do)
624 if (!(Assignment.GetTriggerMode() & C4PlayerControlAssignment::CTM_Release))
625 return &Assignment;
626 return nullptr;
627 }
628
operator ==(const C4PlayerControlAssignmentSet & cmp) const629 bool C4PlayerControlAssignmentSet::operator ==(const C4PlayerControlAssignmentSet &cmp) const
630 {
631 return Assignments == cmp.Assignments
632 && sName == cmp.sName;
633 }
634
GetAssignmentsByKey(const C4PlayerControlDefs & rDefs,const C4KeyCodeEx & key,bool fHoldKeysOnly,C4PlayerControlAssignmentPVec * pOutVec,const C4PlayerControlRecentKeyList & DownKeys,const C4PlayerControlRecentKeyList & RecentKeys) const635 void C4PlayerControlAssignmentSet::GetAssignmentsByKey(const C4PlayerControlDefs &rDefs, const C4KeyCodeEx &key, bool fHoldKeysOnly, C4PlayerControlAssignmentPVec *pOutVec, const C4PlayerControlRecentKeyList &DownKeys, const C4PlayerControlRecentKeyList &RecentKeys) const
636 {
637 assert(pOutVec);
638 // primary match by TriggerKey (todo: Might use a hash map here if matching speed becomes an issue due to large control sets)
639 for (const auto & rAssignment : Assignments)
640 {
641 const C4KeyCodeEx &rAssignmentTriggerKey = rAssignment.GetTriggerKey();
642 if (!(rAssignmentTriggerKey.Key == key.Key)) continue;
643 // special: hold-keys-only ignore shift, because shift state might have been release during hold
644 if (!fHoldKeysOnly) if (rAssignmentTriggerKey.dwShift != key.dwShift) continue;
645 // check linked control def
646 const C4PlayerControlDef *pCtrl = rDefs.GetControlByIndex(rAssignment.GetControl());
647 if (!pCtrl) continue;
648 // only want hold keys?
649 if (fHoldKeysOnly)
650 {
651 // a hold/release-trigger key is not a real hold key, even if the underlying control is
652 if (!pCtrl->IsHoldKey() || (rAssignment.GetTriggerMode() & (C4PlayerControlAssignment::CTM_Hold | C4PlayerControlAssignment::CTM_Release))) continue;
653 }
654 else if (rAssignment.HasCombo())
655 {
656 // hold-only events match the trigger key only (i.e., Release-events are generated as soon as the trigger key goes up)
657 // other events must match either the sequence or the down-key-combination
658 if (!rAssignment.IsComboMatched(DownKeys, RecentKeys)) continue;
659 }
660 // we got match! Store it
661 pOutVec->push_back(&rAssignment);
662 }
663 }
664
GetTriggerKeys(const C4PlayerControlDefs & rDefs,C4KeyCodeExVec * pRegularKeys,C4KeyCodeExVec * pHoldKeys) const665 void C4PlayerControlAssignmentSet::GetTriggerKeys(const C4PlayerControlDefs &rDefs, C4KeyCodeExVec *pRegularKeys, C4KeyCodeExVec *pHoldKeys) const
666 {
667 // put all trigger keys of keyset into output vectors
668 // first all hold keys
669 for (const auto & rAssignment : Assignments)
670 {
671 const C4PlayerControlDef *pDef = rDefs.GetControlByIndex(rAssignment.GetControl());
672 if (pDef && pDef->IsHoldKey())
673 {
674 const C4KeyCodeEx &rKey = rAssignment.GetTriggerKey();
675 if (std::find(pHoldKeys->begin(), pHoldKeys->end(), rKey) == pHoldKeys->end()) pHoldKeys->push_back(rKey);
676 }
677 }
678 // then all regular keys that aren't in the hold keys list yet
679 for (const auto & rAssignment : Assignments)
680 {
681 const C4PlayerControlDef *pDef = rDefs.GetControlByIndex(rAssignment.GetControl());
682 if (pDef && !pDef->IsHoldKey())
683 {
684 const C4KeyCodeEx &rKey = rAssignment.GetTriggerKey();
685 if (std::find(pHoldKeys->begin(), pHoldKeys->end(), rKey) == pHoldKeys->end())
686 if (std::find(pRegularKeys->begin(), pRegularKeys->end(), rKey) == pRegularKeys->end())
687 pRegularKeys->push_back(rKey);
688 }
689 }
690 }
691
GetPicture() const692 C4Facet C4PlayerControlAssignmentSet::GetPicture() const
693 {
694 // get image to be drawn to represent this control set
695 // picture per set not implemented yet. So just default to out standard images
696 if (HasGamepad()) return ::GraphicsResource.fctGamepad.GetPhase(0);
697 // if (HasMouse()) return ::GraphicsResource.fctMouse; // might be useful again with changing control sets
698 if (HasKeyboard()) return ::GraphicsResource.fctKeyboard.GetPhase(Game.PlayerControlUserAssignmentSets.GetSetIndex(this));
699 return C4Facet();
700 }
701
IsMouseControlAssigned(int32_t mouseevent) const702 bool C4PlayerControlAssignmentSet::IsMouseControlAssigned(int32_t mouseevent) const
703 {
704 // TODO
705 return true;
706 }
707
708
709 /* C4PlayerControlAssignmentSets */
710
Clear()711 void C4PlayerControlAssignmentSets::Clear()
712 {
713 Sets.clear();
714 }
715
CompileFunc(StdCompiler * pComp)716 void C4PlayerControlAssignmentSets::CompileFunc(StdCompiler *pComp)
717 {
718 if (pComp->isSerializer() && pComp->isRegistry())
719 {
720 pComp->Default("ControlSets"); // special registry compiler: Clean out everything before
721 }
722 pComp->Value(mkNamingAdapt(clear_previous, "ClearPrevious", false));
723 pComp->Value(mkSTLContainerAdapt(Sets, StdCompiler::SEP_NONE));
724 }
725
operator ==(const C4PlayerControlAssignmentSets & cmp) const726 bool C4PlayerControlAssignmentSets::operator ==(const C4PlayerControlAssignmentSets &cmp) const
727 {
728 return Sets == cmp.Sets && clear_previous == cmp.clear_previous;
729 }
730
MergeFrom(const C4PlayerControlAssignmentSets & Src,C4PlayerControlAssignmentSet::MergeMode merge_mode)731 void C4PlayerControlAssignmentSets::MergeFrom(const C4PlayerControlAssignmentSets &Src, C4PlayerControlAssignmentSet::MergeMode merge_mode)
732 {
733 // if source set is flagged to clear previous, do this!
734 if (Src.clear_previous) Sets.clear();
735 // take over all assignments in known sets and new sets defined in Src
736 for (const auto & SrcSet : Src.Sets)
737 {
738 // overwrite if def of same name existed if it's not low priority anyway
739 bool fIsWildcardSet = SrcSet.IsWildcardName();
740 if (!fIsWildcardSet)
741 {
742 C4PlayerControlAssignmentSet *pPrevSet = GetSetByName(SrcSet.GetName());
743 if (!pPrevSet && merge_mode == C4PlayerControlAssignmentSet::MM_Inherit)
744 {
745 // inherited sets must go through merge procedure to set inherited links
746 pPrevSet = CreateEmptySetByTemplate(SrcSet);
747 }
748 if (pPrevSet)
749 {
750 pPrevSet->MergeFrom(SrcSet, merge_mode);
751 }
752 else
753 {
754 // new def: Append a copy
755 Sets.push_back(SrcSet);
756 }
757 }
758 else
759 {
760 // source is a wildcard: Merge with all matching sets
761 for (auto & DstSet : Sets)
762 {
763 if (WildcardMatch(SrcSet.GetName(), DstSet.GetName()))
764 {
765 DstSet.MergeFrom(SrcSet, merge_mode);
766 }
767 }
768 }
769 }
770 }
771
ResolveRefs(C4PlayerControlDefs * pDefs)772 bool C4PlayerControlAssignmentSets::ResolveRefs(C4PlayerControlDefs *pDefs)
773 {
774 for (auto & Set : Sets)
775 if (!Set.ResolveRefs(pDefs)) return false;
776 return true;
777 }
778
SortAssignments()779 void C4PlayerControlAssignmentSets::SortAssignments()
780 {
781 for (auto & Set : Sets)
782 Set.SortAssignments();
783 }
784
GetSetByName(const char * szName)785 C4PlayerControlAssignmentSet *C4PlayerControlAssignmentSets::GetSetByName(const char *szName)
786 {
787 for (auto & Set : Sets)
788 if (WildcardMatch(szName, Set.GetName()))
789 return &Set;
790 return nullptr;
791 }
792
GetDefaultSet()793 C4PlayerControlAssignmentSet *C4PlayerControlAssignmentSets::GetDefaultSet()
794 {
795 // default set is first defined control set
796 if (Sets.empty()) return nullptr; // nothing defined :(
797 return &Sets.front();
798 }
799
GetSetIndex(const C4PlayerControlAssignmentSet * set) const800 int32_t C4PlayerControlAssignmentSets::GetSetIndex(const C4PlayerControlAssignmentSet *set) const
801 {
802 // find set in list; return index
803 int32_t index = 0;
804 for (AssignmentSetList::const_iterator i = Sets.begin(); i != Sets.end(); ++i,++index)
805 if (&*i == set)
806 return index;
807 return -1; // not found
808 }
809
GetSetByIndex(int32_t index)810 C4PlayerControlAssignmentSet *C4PlayerControlAssignmentSets::GetSetByIndex(int32_t index)
811 {
812 // bounds check
813 if (index < 0 || index >= (int32_t)Sets.size()) return nullptr;
814 // return indexed set
815 AssignmentSetList::iterator i = Sets.begin();
816 while (index--) ++i;
817 return &*i;
818 }
819
CreateEmptySetByTemplate(const C4PlayerControlAssignmentSet & template_set)820 C4PlayerControlAssignmentSet *C4PlayerControlAssignmentSets::CreateEmptySetByTemplate(const C4PlayerControlAssignmentSet &template_set)
821 {
822 Sets.emplace_back();
823 Sets.back().InitEmptyFromTemplate(template_set);
824 return &Sets.back();
825 }
826
RemoveSetByName(const char * set_name)827 void C4PlayerControlAssignmentSets::RemoveSetByName(const char *set_name)
828 {
829 for (AssignmentSetList::iterator i = Sets.begin(); i != Sets.end(); ++i)
830 if (SEqual(set_name, (*i).GetName()))
831 {
832 Sets.erase(i);
833 return;
834 }
835 }
836
837
838 /* C4PlayerControlFile */
839
CompileFunc(StdCompiler * pComp)840 void C4PlayerControlFile::CompileFunc(StdCompiler *pComp)
841 {
842 pComp->Value(mkNamingAdapt(ControlDefs, "ControlDefs", C4PlayerControlDefs()));
843 pComp->Value(mkNamingAdapt(AssignmentSets, "ControlSets", C4PlayerControlAssignmentSets()));
844 }
845
Load(C4Group & hGroup,const char * szFilename,C4LangStringTable * pLang)846 bool C4PlayerControlFile::Load(C4Group &hGroup, const char *szFilename, C4LangStringTable *pLang)
847 {
848 // clear previous
849 Clear();
850 // load and prepare file contents
851 StdStrBuf Buf;
852 if (!hGroup.LoadEntryString(szFilename, &Buf)) return false;
853 if (pLang) pLang->ReplaceStrings(Buf);
854 // parse it!
855 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(*this, Buf, szFilename)) return false;
856 return true;
857 }
858
Save(C4Group & hGroup,const char * szFilename)859 bool C4PlayerControlFile::Save(C4Group &hGroup, const char *szFilename)
860 {
861 // decompile to buffer and save buffer to group
862 StdStrBuf Buf;
863 if (!DecompileToBuf_Log<StdCompilerINIWrite>(*this, &Buf, szFilename)) return false;
864 hGroup.Add(szFilename, Buf, false, true);
865 return true;
866 }
867
Clear()868 void C4PlayerControlFile::Clear()
869 {
870 ControlDefs.Clear();
871 AssignmentSets.Clear();
872 }
873
874
875 /* C4PlayerControl */
876
CompileFunc(StdCompiler * pComp)877 void C4PlayerControl::CSync::ControlDownState::CompileFunc(StdCompiler *pComp)
878 {
879 pComp->Value(DownState);
880 pComp->Separator();
881 pComp->Value(MovedState);
882 pComp->Separator();
883 pComp->Value(iDownFrame);
884 pComp->Separator();
885 pComp->Value(iMovedFrame);
886 pComp->Separator();
887 pComp->Value(fDownByUser);
888 }
889
operator ==(const ControlDownState & cmp) const890 bool C4PlayerControl::CSync::ControlDownState::operator ==(const ControlDownState &cmp) const
891 {
892 return DownState == cmp.DownState && MovedState == cmp.MovedState && iDownFrame == cmp.iDownFrame && iMovedFrame == cmp.iMovedFrame && fDownByUser == cmp.fDownByUser;
893 }
894
GetControlDownState(int32_t iControl) const895 const C4PlayerControl::CSync::ControlDownState *C4PlayerControl::CSync::GetControlDownState(int32_t iControl) const
896 {
897 // safe access
898 if (iControl < 0 || iControl >= int32_t(ControlDownStates.size())) return nullptr;
899 return &ControlDownStates[iControl];
900 }
901
GetControlDisabled(int32_t iControl) const902 int32_t C4PlayerControl::CSync::GetControlDisabled(int32_t iControl) const
903 {
904 // safe access
905 if (iControl < 0 || iControl >= int32_t(ControlDisableStates.size())) return 0;
906 return ControlDisableStates[iControl];
907 }
908
SetControlDownState(int32_t iControl,const C4KeyEventData & rDownState,int32_t iDownFrame,bool fDownByUser)909 void C4PlayerControl::CSync::SetControlDownState(int32_t iControl, const C4KeyEventData &rDownState, int32_t iDownFrame, bool fDownByUser)
910 {
911 // update state
912 if (iControl < 0) return;
913 if (iControl >= int32_t(ControlDownStates.size())) ControlDownStates.resize(iControl+1);
914 ControlDownState &rState = ControlDownStates[iControl];
915 rState.DownState = rDownState;
916 rState.iDownFrame = iDownFrame;
917 rState.fDownByUser = fDownByUser;
918 }
919
SetControlMovedState(int32_t iControl,const C4KeyEventData & rMovedState,int32_t iMovedFrame)920 void C4PlayerControl::CSync::SetControlMovedState(int32_t iControl, const C4KeyEventData &rMovedState, int32_t iMovedFrame)
921 {
922 // update state
923 if (iControl < 0) return;
924 if (iControl >= int32_t(ControlDownStates.size())) ControlDownStates.resize(iControl+1);
925 ControlDownState &rState = ControlDownStates[iControl];
926 rState.MovedState = rMovedState;
927 rState.iMovedFrame = iMovedFrame;
928 }
929
SetControlDisabled(int32_t iControl,int32_t iVal)930 bool C4PlayerControl::CSync::SetControlDisabled(int32_t iControl, int32_t iVal)
931 {
932 // disable control
933 if (iControl < 0) return false;
934 if (iControl >= int32_t(ControlDisableStates.size())) ControlDisableStates.resize(iControl+1);
935 ControlDisableStates[iControl] = iVal;
936 // if a control is disabled, its down-state is reset silently
937 ResetControlDownState(iControl);
938 return true;
939 }
940
ResetControlDownState(int32_t iControl)941 void C4PlayerControl::CSync::ResetControlDownState(int32_t iControl)
942 {
943 // silently reset down state of control
944 const ControlDownState *pDownState = GetControlDownState(iControl);
945 if (pDownState && pDownState->IsDown())
946 {
947 C4KeyEventData KeyDownState = pDownState->DownState;
948 KeyDownState.iStrength = 0;
949 SetControlDownState(iControl, KeyDownState, 0, false);
950 SetControlMovedState(iControl, KeyDownState, 0);
951 }
952 }
953
InitDefaults(const C4PlayerControlDefs & ControlDefs)954 void C4PlayerControl::CSync::InitDefaults(const C4PlayerControlDefs &ControlDefs)
955 {
956 const C4PlayerControlDef *def;
957 int32_t i=0;
958 while ((def = ControlDefs.GetControlByIndex(i)))
959 {
960 if (def->IsDefaultDisabled()) SetControlDisabled(i, true);
961 ++i;
962 }
963 }
964
Clear()965 void C4PlayerControl::CSync::Clear()
966 {
967 ControlDownStates.clear();
968 ControlDisableStates.clear();
969 }
970
CompileFunc(StdCompiler * pComp)971 void C4PlayerControl::CSync::CompileFunc(StdCompiler *pComp)
972 {
973 pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlDownStates), "Down", DownStateVec()));
974 pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlDisableStates), "Disabled", DisableStateVec()));
975 }
976
operator ==(const CSync & cmp) const977 bool C4PlayerControl::CSync::operator ==(const CSync &cmp) const
978 {
979 return ControlDownStates == cmp.ControlDownStates
980 && ControlDisableStates == cmp.ControlDisableStates;
981 }
982
Init()983 void C4PlayerControl::Init()
984 {
985 // defaultdisabled controls
986 Sync.InitDefaults(ControlDefs);
987 }
988
CompileFunc(StdCompiler * pComp)989 void C4PlayerControl::CompileFunc(StdCompiler *pComp)
990 {
991 // compile sync values only
992 CSync DefaultSync;
993 DefaultSync.InitDefaults(ControlDefs);
994 pComp->Value(mkNamingAdapt(Sync, "PlayerControl", DefaultSync));
995 }
996
ProcessKeyEvent(const C4KeyCodeEx & pressed_key,const C4KeyCodeEx & matched_key,ControlState state,const C4KeyEventData & rKeyExtraData,bool reset_down_states_only,bool * clear_recent_keys)997 bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, ControlState state, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only, bool *clear_recent_keys)
998 {
999 if (Key_IsGamepad(pressed_key.Key))
1000 {
1001 // We have to filter gamepad events here.
1002 C4Player *plr = ::Players.Get(iPlr);
1003 if (!plr || !plr->pGamepad || plr->pGamepad->GetID() != pressed_key.deviceId)
1004 return false;
1005 }
1006 // collect all matching keys
1007 C4PlayerControlAssignmentPVec Matches;
1008 assert(pControlSet); // shouldn't get this callback for players without control set
1009 pControlSet->GetAssignmentsByKey(ControlDefs, matched_key, state != CONS_Down, &Matches, DownKeys, RecentKeys);
1010 // process async controls
1011 bool cursor_pos_added = false;
1012 C4ControlPlayerControl *pControlPacket = nullptr;
1013 for (C4PlayerControlAssignmentPVec::const_iterator i = Matches.begin(); i != Matches.end(); ++i)
1014 {
1015 const C4PlayerControlAssignment *pAssignment = *i;
1016 assert(pAssignment);
1017 int32_t iControlIndex = pAssignment->GetControl();
1018 const C4PlayerControlDef *pControlDef = ControlDefs.GetControlByIndex(iControlIndex);
1019 if (pControlDef && pControlDef->IsValid() && !Sync.IsControlDisabled(iControlIndex) && (state == CONS_Down || pControlDef->IsHoldKey()))
1020 {
1021 // clear RecentKeys if requested by this assignment. Must be done before sync queue, so multiple combos can be issued in a single control frame.
1022 if (clear_recent_keys && (pAssignment->GetTriggerMode() & C4PlayerControlAssignment::CTM_ClearRecentKeys)) *clear_recent_keys = true;
1023 // extra data from key or overwrite by current cursor pos if definition requires it
1024 if (pControlDef->IsAsync() && !pControlPacket)
1025 {
1026 if (pControlDef->IsSendCursorPos()) IsCursorPosRequested = true; // async cursor pos request - doesn't really make sense to set this flag for async controls
1027 if (ExecuteControl(iControlIndex, state, rKeyExtraData, pAssignment->GetTriggerMode(), pressed_key.IsRepeated(), reset_down_states_only))
1028 return true;
1029 }
1030 else
1031 {
1032 // sync control
1033 // ignore key repeats, because we do our own key repeat for sync controls
1034 if (pressed_key.IsRepeated()) return false;
1035 // sync control has higher priority - no more async execution then
1036 // build a control packet and add control data instead. even for async controls later in chain, as they may be blocked by a sync handler
1037 if (!pControlPacket) pControlPacket = new C4ControlPlayerControl(iPlr, state, rKeyExtraData);
1038 int32_t extra_trigger_mode = 0;
1039 if (reset_down_states_only) extra_trigger_mode |= C4PlayerControlAssignment::CTM_HandleDownStatesOnly;
1040 pControlPacket->AddControl(iControlIndex, pAssignment->GetTriggerMode() | extra_trigger_mode);
1041 // sync cursor pos request; pos will be added to control before it is synced/executed
1042 if (pControlDef->IsSendCursorPos() && !cursor_pos_added)
1043 {
1044 int32_t x, y, game_x, game_y;
1045 // Add current cursor pos in GUI and game coordinates to input
1046 if (GetCurrentPlayerCursorPos(&x, &y, &game_x, &game_y))
1047 {
1048 C4KeyEventData cursor_key_data(rKeyExtraData);
1049 cursor_key_data.vp_x = x; cursor_key_data.vp_y = y;
1050 cursor_key_data.game_x = game_x; cursor_key_data.game_y = game_y;
1051 pControlPacket->SetExtraData(cursor_key_data);
1052 }
1053 // Will also send a CON_CursorPos packet separately
1054 IsCursorPosRequested = true;
1055 cursor_pos_added = true;
1056 }
1057 }
1058 }
1059 }
1060 // push sync control to input
1061 if (pControlPacket)
1062 {
1063 Game.Input.Add(CID_PlrControl, pControlPacket);
1064 // assume processed (although we can't really know that yet)
1065 return true;
1066 }
1067 return false;
1068 }
1069
ProcessKeyDown(const C4KeyCodeEx & pressed_key,const C4KeyCodeEx & matched_key)1070 bool C4PlayerControl::ProcessKeyDown(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key)
1071 {
1072 // add key to local "down" list if it's not already in there
1073 // except for some mouse events for which a down state does not make sense
1074 C4PlayerControlRecentKey RKey(pressed_key,matched_key,C4TimeMilliseconds::Now());
1075 if (!Key_IsMouse(pressed_key.Key) || Inside<uint8_t>(Key_GetMouseEvent(pressed_key.Key), KEY_MOUSE_Button1, KEY_MOUSE_ButtonMax))
1076 {
1077 if (std::find(DownKeys.begin(), DownKeys.end(), pressed_key) == DownKeys.end()) DownKeys.push_back(RKey);
1078 }
1079 // process!
1080 bool clear_recent_keys = false;
1081 bool fResult = ProcessKeyEvent(pressed_key, matched_key, CONS_Down, Game.KeyboardInput.GetLastKeyExtraData(), false, &clear_recent_keys);
1082 // unless assignment requests a clear, always add keys to recent list even if not handled
1083 if (clear_recent_keys)
1084 RecentKeys.clear();
1085 else if (!pressed_key.IsRepeated()) // events caused by holding down the key are not added to recent list (so you cannot cause "double-Q" just by holding down Q)
1086 RecentKeys.push_back(RKey);
1087 return fResult;
1088 }
1089
ProcessKeyUp(const C4KeyCodeEx & pressed_key,const C4KeyCodeEx & matched_key)1090 bool C4PlayerControl::ProcessKeyUp(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key)
1091 {
1092 // remove key from "down" list
1093 // except for some mouse events for which a down state does not make sense
1094 if (!Key_IsMouse(pressed_key.Key) || Inside<uint8_t>(Key_GetMouseEvent(pressed_key.Key), KEY_MOUSE_Button1, KEY_MOUSE_ButtonMax))
1095 {
1096 C4PlayerControlRecentKeyList::iterator i = find(DownKeys.begin(), DownKeys.end(), pressed_key);
1097 if (i != DownKeys.end()) DownKeys.erase(i);
1098 }
1099 // process!
1100 return ProcessKeyEvent(pressed_key, matched_key, CONS_Up, Game.KeyboardInput.GetLastKeyExtraData());
1101 }
1102
ProcessKeyMoved(const C4KeyCodeEx & pressed_key,const C4KeyCodeEx & matched_key)1103 bool C4PlayerControl::ProcessKeyMoved(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key)
1104 {
1105 // process!
1106 return ProcessKeyEvent(pressed_key, matched_key, CONS_Moved, Game.KeyboardInput.GetLastKeyExtraData());
1107 }
1108
ExecuteControlPacket(const class C4ControlPlayerControl * pCtrl)1109 void C4PlayerControl::ExecuteControlPacket(const class C4ControlPlayerControl *pCtrl)
1110 {
1111 // callback from control queue. Execute controls in packet until one of them gets processed
1112 // assume async packets always as not processed to ensure sync safety (usually, sync commands should better not ovberride async commands anyway)
1113 bool fHandleDownStateOnly = false;
1114 for (C4ControlPlayerControl::ControlItemVec::const_iterator i = pCtrl->GetControlItems().begin(); i != pCtrl->GetControlItems().end(); ++i)
1115 {
1116 const C4ControlPlayerControl::ControlItem &rItem = *i;
1117 const C4PlayerControlDef *pCtrlDef = ControlDefs.GetControlByIndex(rItem.iControl);
1118 if (pCtrlDef)
1119 {
1120 if (Config.General.DebugRec)
1121 {
1122 if (pCtrlDef->IsSync())
1123 {
1124 AddDbgRec(RCT_PlrCom, &rItem.iControl, sizeof(rItem.iControl));
1125 }
1126 }
1127 if (ExecuteControl(rItem.iControl, pCtrl->GetState(), pCtrl->GetExtraData(), rItem.iTriggerMode, false, fHandleDownStateOnly))
1128 if (pCtrlDef->IsSync())
1129 {
1130 if (pCtrl->GetState() == CONS_Up)
1131 {
1132 // control processed. however, for key releases, overriden keys are released silently so following down events aren't handled as key repeats
1133 // note this does not affect CTM_Hold/CTM_Release, because they ignore release controls anyway
1134 fHandleDownStateOnly = true;
1135 }
1136 else
1137 {
1138 break;
1139 }
1140 }
1141 }
1142 }
1143 }
1144
ExecuteControl(int32_t iControl,ControlState state,const C4KeyEventData & rKeyExtraData,int32_t iTriggerMode,bool fRepeated,bool fHandleDownStateOnly)1145 bool C4PlayerControl::ExecuteControl(int32_t iControl, ControlState state, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly)
1146 {
1147 // execute single control. return if handled
1148 const C4PlayerControlDef *pControlDef = ControlDefs.GetControlByIndex(iControl);
1149 if (!pControlDef || Sync.IsControlDisabled(iControl)) return false;
1150 C4PlayerControlDef::Actions eAction = pControlDef->GetAction();
1151 C4KeyEventData KeyExtraData(rKeyExtraData);
1152 const CSync::ControlDownState *pCtrlDownState = Sync.GetControlDownState(iControl);
1153 bool fWasDown = pCtrlDownState ? pCtrlDownState->IsDown() : false;
1154 // global controls only in global context
1155 if (IsGlobal() != pControlDef->IsGlobal()) return false;
1156 // down state handling only?
1157 if (iTriggerMode & C4PlayerControlAssignment::CTM_HandleDownStatesOnly) fHandleDownStateOnly = true;
1158 // hold-actions only work on script controls with the hold flag
1159 if (iTriggerMode & (C4PlayerControlAssignment::CTM_Hold | C4PlayerControlAssignment::CTM_Release))
1160 {
1161 if (eAction != C4PlayerControlDef::CDA_Script) return false;
1162 if (!pControlDef->IsHoldKey()) return false;
1163 if (state == CONS_Up) return false; // hold triggers have no "up"-event
1164 // perform hold/release
1165 if (fWasDown)
1166 {
1167 // control is currently down: release?
1168 if (iTriggerMode & C4PlayerControlAssignment::CTM_Release)
1169 {
1170 KeyExtraData.iStrength = 0;
1171 Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, false);
1172 // now process as a regular "Up" event
1173 state = CONS_Up;
1174 fRepeated = false;
1175 }
1176 else
1177 {
1178 assert(iTriggerMode & C4PlayerControlAssignment::CTM_Hold);
1179 // control is down but trigger key is pressed again: Refresh down state
1180 // (this will restart the KeyRepeat time)
1181 Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, false);
1182 // now process as a regular, repeated "down" event
1183 fRepeated = true;
1184 }
1185 }
1186 else
1187 {
1188 // control is currently up. Put into hold-down-state if this is a hold key
1189 if (iTriggerMode & C4PlayerControlAssignment::CTM_Hold)
1190 {
1191 Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, false);
1192 // now process as a regular "down" event
1193 fRepeated = false;
1194 }
1195 else
1196 {
1197 //. Ignore if it's only a release key
1198 return false;
1199 }
1200 }
1201 }
1202 else if (state == CONS_Up)
1203 {
1204 // regular ControlUp: Only valid if that control was down
1205 if (!fWasDown) return false;
1206 Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, true);
1207 }
1208 else if (pControlDef->IsHoldKey())
1209 {
1210 if (state == CONS_Moved)
1211 {
1212 Sync.SetControlMovedState(iControl, KeyExtraData, Game.FrameCounter);
1213 fRepeated = true;
1214 }
1215 else
1216 {
1217 // regular ControlDown on Hold Key: Set in down list
1218 Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, true);
1219 fRepeated = fWasDown;
1220 }
1221 }
1222 // down state handling done
1223 if (fHandleDownStateOnly) return false;
1224 // perform action for this control
1225 bool fHandled = ExecuteControlAction(iControl, eAction, pControlDef->GetExtraData(), state, KeyExtraData, fRepeated);
1226 // handled controls hide control display
1227 C4Player *pPlr;
1228 if ((pPlr = ::Players.Get(iPlr))) if (pPlr->ShowStartup) pPlr->ShowStartup = false;
1229 // return if handled, unless control is defined as always unhandled
1230 return fHandled && !(iTriggerMode & C4PlayerControlAssignment::CTM_AlwaysUnhandled);
1231 }
1232
ExecuteControlAction(int32_t iControl,C4PlayerControlDef::Actions eAction,C4ID idControlExtraData,ControlState state,const C4KeyEventData & rKeyExtraData,bool fRepeated)1233 bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated)
1234 {
1235 // moved events don't make sense for menus and are only handled by script
1236 if (state == CONS_Moved && eAction != C4PlayerControlDef::CDA_Script) return false;
1237 // get affected player
1238 C4Player *pPlr = nullptr;
1239 C4Viewport *pVP;
1240 C4Object *pCursor = nullptr;
1241 C4Menu *pCursorMenu = nullptr;
1242 if (iPlr > -1)
1243 {
1244 pPlr = ::Players.Get(iPlr);
1245 if (!pPlr) return false;
1246 pCursor = pPlr->Cursor;
1247 if (pCursor && pCursor->Menu && pCursor->Menu->IsActive()) pCursorMenu = pCursor->Menu;
1248 }
1249 bool fUp = state == CONS_Up;
1250 // exec action (on player)
1251 switch (eAction)
1252 {
1253 // scripted player control
1254 case C4PlayerControlDef::CDA_Script:
1255 return ExecuteControlScript(iControl, idControlExtraData, state, rKeyExtraData, fRepeated);
1256
1257 // menu controls
1258 case C4PlayerControlDef::CDA_Menu: if (!pPlr || fUp) return false; if (pPlr->Menu.IsActive()) pPlr->Menu.TryClose(false, true); else pPlr->ActivateMenuMain(); return true; // toggle
1259 case C4PlayerControlDef::CDA_MenuOK: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuEnter,0); return true; // ok on item
1260 case C4PlayerControlDef::CDA_MenuCancel: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuClose,0); return true; // close menu
1261 case C4PlayerControlDef::CDA_MenuLeft: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuLeft ,0); return true; // navigate
1262 case C4PlayerControlDef::CDA_MenuUp: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuUp ,0); return true; // navigate
1263 case C4PlayerControlDef::CDA_MenuRight: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuRight,0); return true; // navigate
1264 case C4PlayerControlDef::CDA_MenuDown: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuDown,0); return true; // navigate
1265 case C4PlayerControlDef::CDA_ObjectMenuTextComplete: if (!pCursorMenu || fUp || !pCursorMenu->IsTextProgressing()) return false; pCursorMenu->Control(COM_MenuShowText,0); return true; // fast-foward text display
1266 case C4PlayerControlDef::CDA_ObjectMenuOK: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuEnter,0); return true; // ok on item
1267 case C4PlayerControlDef::CDA_ObjectMenuOKAll: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuEnterAll,0); return true; // alt ok on item
1268 case C4PlayerControlDef::CDA_ObjectMenuSelect: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuSelect,rKeyExtraData.iStrength); return true; // select an item directly
1269 case C4PlayerControlDef::CDA_ObjectMenuCancel: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuClose,0); return true; // close menu
1270 case C4PlayerControlDef::CDA_ObjectMenuLeft: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuLeft ,0); return true; // navigate
1271 case C4PlayerControlDef::CDA_ObjectMenuUp: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuUp ,0); return true; // navigate
1272 case C4PlayerControlDef::CDA_ObjectMenuRight: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuRight,0); return true; // navigate
1273 case C4PlayerControlDef::CDA_ObjectMenuDown: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuDown ,0); return true; // navigate
1274
1275 case C4PlayerControlDef::CDA_ZoomIn: if (!pPlr || fUp || !(pVP = ::Viewports.GetViewport(iPlr))) return false; pVP->ChangeZoom(C4GFX_ZoomStep); return true; // viewport zoom
1276 case C4PlayerControlDef::CDA_ZoomOut: if (!pPlr || fUp || !(pVP = ::Viewports.GetViewport(iPlr))) return false; pVP->ChangeZoom(1.0f/C4GFX_ZoomStep); return true; // viewport zoom
1277
1278 //unknown action
1279 default: return false;
1280 }
1281 }
1282
ExecuteControlScript(int32_t iControl,C4ID idControlExtraData,ControlState state,const C4KeyEventData & rKeyExtraData,bool fRepeated)1283 bool C4PlayerControl::ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated)
1284 {
1285 C4Player *pPlr = ::Players.Get(iPlr);
1286 if (pPlr)
1287 {
1288 // Not for eliminated (checked again in DirectCom, but make sure no control is generated for eliminated players!)
1289 if (pPlr->Eliminated) return false;
1290 // control count for statistics (but don't count analog stick wiggles)
1291 if (state != CONS_Moved)
1292 pPlr->CountControl(C4Player::PCID_DirectCom, iControl*2+state);
1293 }
1294 else if (iPlr > -1)
1295 {
1296 // player lost?
1297 return false;
1298 }
1299 // get coordinates
1300 int32_t x,y;
1301 const C4PlayerControlDef *def = ControlDefs.GetControlByIndex(iControl);
1302 if (def && def->GetCoordinateSpace() == C4PlayerControlDef::COS_Viewport)
1303 {
1304 x = rKeyExtraData.vp_x; y = rKeyExtraData.vp_y;
1305 }
1306 else
1307 {
1308 x = rKeyExtraData.game_x; y = rKeyExtraData.game_y;
1309 }
1310 C4Value vx = (x == C4KeyEventData::KeyPos_None) ? C4VNull : C4VInt(x);
1311 C4Value vy = (y == C4KeyEventData::KeyPos_None) ? C4VNull : C4VInt(y);
1312 // exec control function
1313 C4AulParSet Pars(iPlr, iControl, C4Id2Def(idControlExtraData), vx, vy, rKeyExtraData.iStrength, fRepeated, C4VInt(state));
1314 return ::ScriptEngine.GetPropList()->Call(PSF_PlayerControl, &Pars).getBool();
1315 }
1316
1317
Execute()1318 void C4PlayerControl::Execute()
1319 {
1320 // sync execution: Do keyrepeat
1321 for (size_t i=0; i<ControlDefs.GetCount(); ++i)
1322 {
1323 const CSync::ControlDownState *pControlDownState = Sync.GetControlDownState(i);
1324 if (pControlDownState && pControlDownState->IsDown())
1325 {
1326 const C4PlayerControlDef *pCtrlDef = ControlDefs.GetControlByIndex(i);
1327 assert(pCtrlDef);
1328 int32_t iCtrlRepeatDelay = pCtrlDef->GetRepeatDelay();
1329 if (iCtrlRepeatDelay)
1330 {
1331 int32_t iFrameDiff = Game.FrameCounter - pControlDownState->iDownFrame;
1332 int32_t iCtrlInitialRepeatDelay = pCtrlDef->GetInitialRepeatDelay();
1333 if (iFrameDiff && iFrameDiff >= iCtrlInitialRepeatDelay)
1334 {
1335 if (!((iFrameDiff-iCtrlInitialRepeatDelay) % iCtrlRepeatDelay))
1336 {
1337 // it's RepeatTime for this key!
1338 ExecuteControlAction(i, pCtrlDef->GetAction(), pCtrlDef->GetExtraData(), CONS_Down, pControlDownState->DownState, true);
1339 }
1340 }
1341 }
1342 }
1343 }
1344 // cleanup old recent keys
1345 C4TimeMilliseconds tNow = C4TimeMilliseconds::Now();
1346 C4PlayerControlRecentKeyList::iterator irk;
1347 for (irk = RecentKeys.begin(); irk != RecentKeys.end(); ++irk)
1348 {
1349 C4PlayerControlRecentKey &rk = *irk;
1350 if (rk.tTime + MaxRecentKeyLookback > tNow) break;
1351 }
1352 if (irk != RecentKeys.begin()) RecentKeys.erase(RecentKeys.begin(), irk);
1353 }
1354
C4PlayerControl()1355 C4PlayerControl::C4PlayerControl() : ControlDefs(Game.PlayerControlDefs)
1356 {
1357 }
1358
Clear()1359 void C4PlayerControl::Clear()
1360 {
1361 iPlr = NO_OWNER;
1362 pControlSet = nullptr;
1363 for (auto & KeyBinding : KeyBindings) delete KeyBinding;
1364 KeyBindings.clear();
1365 RecentKeys.clear();
1366 DownKeys.clear();
1367 Sync.Clear();
1368 IsCursorPosRequested = false;
1369 }
1370
RegisterKeyset(int32_t iPlr,C4PlayerControlAssignmentSet * pKeyset)1371 void C4PlayerControl::RegisterKeyset(int32_t iPlr, C4PlayerControlAssignmentSet *pKeyset)
1372 {
1373 // setup
1374 pControlSet = pKeyset;
1375 this->iPlr = iPlr;
1376 // register all keys into Game.KeyboardInput creating KeyBindings
1377 if (pControlSet)
1378 {
1379 C4KeyCodeExVec RegularKeys, HoldKeys;
1380 pControlSet->GetTriggerKeys(ControlDefs, &RegularKeys, &HoldKeys);
1381 int32_t idx=0;
1382 for (C4KeyCodeExVec::const_iterator i = RegularKeys.begin(); i != RegularKeys.end(); ++i) AddKeyBinding(*i, false, idx++);
1383 for (C4KeyCodeExVec::const_iterator i = HoldKeys.begin(); i != HoldKeys.end(); ++i) AddKeyBinding(*i, true, idx++);
1384 }
1385 }
1386
AddKeyBinding(const C4KeyCodeEx & key,bool fHoldKey,int32_t idx)1387 void C4PlayerControl::AddKeyBinding(const C4KeyCodeEx &key, bool fHoldKey, int32_t idx)
1388 {
1389 KeyBindings.push_back(new C4KeyBinding(
1390 key, FormatString("PlrKey%02d", idx).getData(), KEYSCOPE_Control,
1391 new C4KeyCBExPassKey<C4PlayerControl, C4KeyCodeEx>(*this, key, &C4PlayerControl::ProcessKeyDown, fHoldKey ? &C4PlayerControl::ProcessKeyUp : nullptr, nullptr, fHoldKey ? &C4PlayerControl::ProcessKeyMoved : nullptr),
1392 C4CustomKey::PRIO_PlrControl));
1393 }
1394
DoMouseInput(uint8_t mouse_id,int32_t mouseevent,float game_x,float game_y,float gui_x,float gui_y,DWORD modifier_flags)1395 bool C4PlayerControl::DoMouseInput(uint8_t mouse_id, int32_t mouseevent, float game_x, float game_y, float gui_x, float gui_y, DWORD modifier_flags)
1396 {
1397 // convert moueevent to key code
1398 bool is_down;
1399 C4KeyCodeEx mouseevent_keycode = C4KeyCodeEx::FromC4MC(mouse_id, mouseevent, modifier_flags, &is_down);
1400 // first, try processing it as GUI mouse event. if not assigned, process as Game mous event
1401 // TODO: May route this through Game.DoKeyboardInput instead - would allow assignment of mouse events in CustomConfig
1402 // and would get rid of the Game.KeyboardInput.SetLastKeyExtraData-hack
1403 C4KeyEventData mouseevent_data;
1404 mouseevent_data.iStrength = 100*is_down; // TODO: May get pressure from tablet here
1405 mouseevent_data.vp_x = uint32_t(gui_x);
1406 mouseevent_data.vp_y = uint32_t(gui_y);
1407 mouseevent_data.game_x = uint32_t(game_x);
1408 mouseevent_data.game_y = uint32_t(game_y);
1409 Game.KeyboardInput.SetLastKeyExtraData(mouseevent_data); // ProcessKeyDown/Up queries it from there...
1410 bool result;
1411 if (is_down)
1412 result = ProcessKeyDown(mouseevent_keycode, mouseevent_keycode);
1413 else
1414 result = ProcessKeyUp(mouseevent_keycode, mouseevent_keycode);
1415 return result;
1416 }
1417
GetCurrentPlayerCursorPos(int32_t * x_out,int32_t * y_out,int32_t * game_x_out,int32_t * game_y_out)1418 bool C4PlayerControl::GetCurrentPlayerCursorPos(int32_t *x_out, int32_t *y_out, int32_t *game_x_out, int32_t *game_y_out)
1419 {
1420 // prefer mouse position if this is a mouse control
1421 if (pControlSet && pControlSet->HasMouse())
1422 {
1423 if (MouseControl.GetLastCursorPos(x_out, y_out, game_x_out, game_y_out))
1424 {
1425 return true;
1426 }
1427 // if getting the mouse position failed, better fall back to cursor pos
1428 }
1429 // no mouse position known. Use cursor.
1430 C4Player *plr = Players.Get(iPlr);
1431 if (!plr) return false;
1432 C4Object *cursor_obj = plr->Cursor;
1433 if (!cursor_obj) return false;
1434 C4Viewport *vp = ::Viewports.GetViewport(iPlr);
1435 if (!vp) return false;
1436 int32_t game_x = cursor_obj->GetX(), game_y=cursor_obj->GetY();
1437 *game_x_out = game_x; *game_y_out = game_y;
1438 // game coordinate to screen coordinates...
1439 float screen_x = (float(game_x) - vp->last_game_draw_cgo.TargetX - vp->last_game_draw_cgo.X) * vp->GetZoom();
1440 float screen_y = (float(game_y) - vp->last_game_draw_cgo.TargetY - vp->last_game_draw_cgo.Y) * vp->GetZoom();
1441 // ...and screen coordinates to GUI coordinates (might push this into a helper function of C4Viewport?)
1442 float gui_x = (screen_x - vp->last_game_draw_cgo.X) / C4GUI::GetZoom() + vp->last_game_draw_cgo.X;
1443 float gui_y = (screen_y - vp->last_game_draw_cgo.Y) / C4GUI::GetZoom() + vp->last_game_draw_cgo.Y;
1444 *x_out = int32_t(gui_x); *y_out = int32_t(gui_y);
1445 return true;
1446 }
1447
PrepareInput()1448 void C4PlayerControl::PrepareInput()
1449 {
1450 if (IsCursorPosRequested)
1451 {
1452 int32_t x, y, game_x, game_y;
1453 // add current cursor pos in GUI coordinates to input
1454 if (GetCurrentPlayerCursorPos(&x, &y, &game_x, &game_y))
1455 {
1456 // CON_CursorPos might not have been defined in definition file
1457 if (ControlDefs.InternalCons.CON_CursorPos != CON_None)
1458 {
1459 C4KeyEventData ev;
1460 ev.iStrength = 0;
1461 ev.vp_x = x; ev.vp_y = y;
1462 ev.game_x = game_x; ev.game_y = game_y;
1463 C4ControlPlayerControl *pControlPacket = new C4ControlPlayerControl(iPlr, CONS_Down, ev);
1464 pControlPacket->AddControl(ControlDefs.InternalCons.CON_CursorPos, C4PlayerControlAssignment::CTM_Default);
1465 // make sure it's added at head, because controls that have SendCursorPos=1 set will follow, which will rely
1466 // on the cursor pos being known
1467 Game.Input.AddHead(CID_PlrControl, pControlPacket);
1468 }
1469 }
1470 else
1471 {
1472 // no cursor is known (e.g.: Cursor Clonk dead, etc.). Don't create a control.
1473 // Script will probably fall back to last known cursor pos
1474 }
1475 IsCursorPosRequested = false;
1476 }
1477 }
1478