OpenClonk
C4PlayerControl.cpp
Go to the documentation of this file.
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"
21 
23 #include "control/C4Control.h"
24 #include "control/C4Record.h"
25 #include "game/C4GraphicsSystem.h"
26 #include "game/C4Viewport.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 
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 
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 
106 {
112 }
113 
115 {
116  clear_previous = false;
117  Defs.clear();
119 }
120 
122 {
123  pComp->Value(mkNamingAdapt(clear_previous, "ClearPrevious", false));
125  if (pComp->isDeserializer()) UpdateInternalCons();
126 }
127 
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  }
148 }
149 
151 {
152  // safe index
153  if (idx<0 || idx>=int32_t(Defs.size())) return nullptr;
154  return &(Defs[idx]);
155 }
156 
157 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 
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 
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 
200 void C4PlayerControlAssignment::KeyComboItem::UpdateKeyName()
201 {
202  // update key name from key
203  sKeyName.Copy(Key.ToString(false, false));
204 }
205 
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 
234 {
235  if (inherited_assignment) CopyKeyFrom(*inherited_assignment);
236 }
237 
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 
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 
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 
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 
364 {
365  assert(HasCombo());
366  // check if combo is currently fulfilled (assuming TriggerKey is already matched)
367  if (fComboIsSequence)
368  {
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
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 
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 
422 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 
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 
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 
468 {
469  return fGUIDisabled;
470 }
471 
473 {
474  return iGUIGroup;
475 }
476 
477 /* C4PlayerControlAssignmentSet */
478 
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 
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 
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 
566 {
567  Assignments.emplace_back();
568  Assignments.back().SetControlName(control_name);
569  return &Assignments.back();
570 }
571 
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 
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 
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 
603 {
604  if (index<0 || index>=int32_t(Assignments.size())) return nullptr;
605  return &Assignments[index];
606 }
607 
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 
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 
630 {
631  return Assignments == cmp.Assignments
632  && sName == cmp.sName;
633 }
634 
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 
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 
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
697 // if (HasMouse()) return ::GraphicsResource.fctMouse; // might be useful again with changing control sets
699  return C4Facet();
700 }
701 
703 {
704  // TODO
705  return true;
706 }
707 
708 
709 /* C4PlayerControlAssignmentSets */
710 
712 {
713  Sets.clear();
714 }
715 
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));
724 }
725 
727 {
728  return Sets == cmp.Sets && clear_previous == cmp.clear_previous;
729 }
730 
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 
773 {
774  for (auto & Set : Sets)
775  if (!Set.ResolveRefs(pDefs)) return false;
776  return true;
777 }
778 
780 {
781  for (auto & Set : Sets)
782  Set.SortAssignments();
783 }
784 
786 {
787  for (auto & Set : Sets)
788  if (WildcardMatch(szName, Set.GetName()))
789  return &Set;
790  return nullptr;
791 }
792 
794 {
795  // default set is first defined control set
796  if (Sets.empty()) return nullptr; // nothing defined :(
797  return &Sets.front();
798 }
799 
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 
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 
821 {
822  Sets.emplace_back();
823  Sets.back().InitEmptyFromTemplate(template_set);
824  return &Sets.back();
825 }
826 
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 
841 {
842  pComp->Value(mkNamingAdapt(ControlDefs, "ControlDefs", C4PlayerControlDefs()));
843  pComp->Value(mkNamingAdapt(AssignmentSets, "ControlSets", C4PlayerControlAssignmentSets()));
844 }
845 
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 
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 
869 {
870  ControlDefs.Clear();
871  AssignmentSets.Clear();
872 }
873 
874 
875 /* C4PlayerControl */
876 
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 
891 {
892  return DownState == cmp.DownState && MovedState == cmp.MovedState && iDownFrame == cmp.iDownFrame && iMovedFrame == cmp.iMovedFrame && fDownByUser == cmp.fDownByUser;
893 }
894 
896 {
897  // safe access
898  if (iControl < 0 || iControl >= int32_t(ControlDownStates.size())) return nullptr;
899  return &ControlDownStates[iControl];
900 }
901 
902 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 
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 
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 
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 
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 
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 
966 {
967  ControlDownStates.clear();
968  ControlDisableStates.clear();
969 }
970 
972 {
975 }
976 
978 {
981 }
982 
984 {
985  // defaultdisabled controls
986  Sync.InitDefaults(ControlDefs);
987 }
988 
990 {
991  // compile sync values only
992  CSync DefaultSync;
993  DefaultSync.InitDefaults(ControlDefs);
994  pComp->Value(mkNamingAdapt(Sync, "PlayerControl", DefaultSync));
995 }
996 
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
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 
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 
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 
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 
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 
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
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 
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
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 
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);
1303  {
1304  x = rKeyExtraData.vp_x; y = rKeyExtraData.vp_y;
1305  }
1306  else
1307  {
1308  x = rKeyExtraData.game_x; y = rKeyExtraData.game_y;
1309  }
1312  // exec control function
1313  C4AulParSet Pars(iPlr, iControl, C4Id2Def(idControlExtraData), vx, vy, rKeyExtraData.iStrength, fRepeated, C4VInt(state));
1315 }
1316 
1317 
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
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 
1355 C4PlayerControl::C4PlayerControl() : ControlDefs(Game.PlayerControlDefs)
1356 {
1357 }
1358 
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 
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 
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),
1393 }
1394 
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 
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 
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);
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 }
C4Config Config
Definition: C4Config.cpp:930
const BYTE COM_MenuSelect
Definition: C4Constants.h:132
const BYTE COM_MenuLeft
Definition: C4Constants.h:128
const BYTE COM_MenuUp
Definition: C4Constants.h:130
const BYTE COM_MenuClose
Definition: C4Constants.h:126
const BYTE COM_MenuRight
Definition: C4Constants.h:129
const BYTE COM_MenuDown
Definition: C4Constants.h:131
const BYTE COM_MenuEnterAll
Definition: C4Constants.h:125
const BYTE COM_MenuEnter
Definition: C4Constants.h:124
const BYTE COM_MenuShowText
Definition: C4Constants.h:127
const int NO_OWNER
Definition: C4Constants.h:137
C4Def * C4Id2Def(C4ID id)
Definition: C4DefList.h:84
#define PSF_PlayerControl
Definition: C4GameScript.h:118
C4Game Game
Definition: C4Globals.cpp:52
C4AulScriptEngine ScriptEngine
Definition: C4Globals.cpp:43
C4MouseControl MouseControl
Definition: C4Globals.cpp:47
C4GraphicsResource GraphicsResource
const C4KeyCode KEY_MOUSE_ButtonMax
const C4KeyCode KEY_Default
@ KEYSCOPE_Control
@ KEYS_Shift
@ KEYS_Control
const C4KeyCode KEY_MOUSE_Button1
bool Key_IsGamepad(C4KeyCode key)
const C4KeyCode KEY_MOUSE_Move
bool Key_IsMouse(C4KeyCode key)
uint8_t Key_GetMouseEvent(C4KeyCode key)
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
bool LogFatal(const char *szMessage)
Definition: C4Log.cpp:239
@ CID_PlrControl
Definition: C4PacketBase.h:163
std::vector< C4KeyCodeEx > C4KeyCodeExVec
std::list< C4PlayerControlRecentKey > C4PlayerControlRecentKeyList
std::vector< const C4PlayerControlAssignment * > C4PlayerControlAssignmentPVec
@ CON_None
const float C4GFX_ZoomStep
C4PlayerList Players
void CompileFunc(C4Real &rValue, StdCompiler *pComp)
Definition: C4Real.cpp:9033
void AddDbgRec(C4RecordChunkType eType, const void *pData, int iSize)
Definition: C4Record.cpp:32
@ RCT_PlrCom
Definition: C4Record.h:76
const C4Value C4VNull
Definition: C4Value.cpp:30
C4Value C4VInt(int32_t i)
Definition: C4Value.h:239
C4ViewportList Viewports
uint32_t DWORD
bool SEqual2(const char *szStr1, const char *szStr2)
Definition: Standard.cpp:204
int SCharLastPos(char cTarget, const char *szInStr)
Definition: Standard.cpp:253
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:93
StdSTLContainerAdapt< C > mkSTLContainerAdapt(C &rTarget, StdCompiler::Sep eSep=StdCompiler::SEP_SEP)
Definition: StdAdaptors.h:713
StdParameterAdapt< T, P > mkParAdapt(T &&rObj, P &&rPar)
Definition: StdAdaptors.h:490
StdNamingAdapt< T > mkNamingAdapt(T &&rValue, const char *szName)
Definition: StdAdaptors.h:92
Definition: StdAdaptors.h:883
Definition: StdAdaptors.h:795
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
bool WildcardMatch(const char *szWildcard, const char *szString)
Definition: StdFile.cpp:396
C4PropListStatic * GetPropList()
Definition: C4Aul.h:151
void RegisterGlobalConstant(const char *szName, const C4Value &rValue)
Definition: C4Aul.cpp:123
int32_t DebugRec
Definition: C4Config.h:63
C4ConfigGeneral General
Definition: C4Config.h:255
void Add(C4PacketType eType, C4ControlPacket *pCtrl)
Definition: C4Control.h:82
void AddHead(C4PacketType eType, C4ControlPacket *pCtrl)
Definition: C4Control.h:81
const C4KeyEventData & GetExtraData() const
Definition: C4Control.h:229
C4PlayerControl::ControlState GetState() const
Definition: C4Control.h:228
DECLARE_C4CONTROL_VIRTUALS void AddControl(int32_t iControl, int32_t iTriggerMode)
Definition: C4Control.h:225
const ControlItemVec & GetControlItems() const
Definition: C4Control.h:227
void SetExtraData(const C4KeyEventData &new_extra_data)
Definition: C4Control.h:230
C4Facet GetPhase(int iPhaseX=0, int iPhaseY=0)
Definition: C4Facet.cpp:59
float Y
Definition: C4Facet.h:118
float X
Definition: C4Facet.h:118
C4KeyboardInput & KeyboardInput
Definition: C4Game.h:96
C4PlayerControlAssignmentSets PlayerControlUserAssignmentSets
Definition: C4Game.h:93
C4Control & Input
Definition: C4Game.h:82
int32_t FrameCounter
Definition: C4Game.h:129
bool Add(const char *filename, const char *entry_name)
Definition: C4Group.cpp:1621
bool LoadEntryString(const char *entry_name, StdStrBuf *buffer)
Definition: C4Group.cpp:2430
Definition: C4Id.h:26
static const C4ID None
Definition: C4Id.h:39
void SetLastKeyExtraData(const C4KeyEventData &data)
const C4KeyEventData & GetLastKeyExtraData() const
void ReplaceStrings(StdStrBuf &rBuf)
Definition: C4Menu.h:123
bool IsTextProgressing() const
Definition: C4Menu.h:201
bool TryClose(bool fOK, bool fControl)
Definition: C4Menu.cpp:280
bool Control(BYTE byCom, int32_t iData)
Definition: C4Menu.cpp:420
bool IsActive()
Definition: C4Menu.cpp:480
bool GetLastCursorPos(int32_t *x_out_gui, int32_t *y_out_gui, int32_t *x_out_game, int32_t *y_out_game) const
int32_t GetX() const
Definition: C4Object.h:285
class C4ObjectMenu * Menu
Definition: C4Object.h:138
int32_t GetY() const
Definition: C4Object.h:286
void SetKey(const C4KeyCodeEx &key)
bool IsComboMatched(const C4PlayerControlRecentKeyList &DownKeys, const C4PlayerControlRecentKeyList &RecentKeys) const
const char * GetControlName() const
const char * GetGUIName(const C4PlayerControlDefs &defs) const
StdStrBuf GetKeysAsString(bool human_readable, bool short_name) const
void CopyKeyFrom(const C4PlayerControlAssignment &src_assignment)
const char * GetGUIDesc(const C4PlayerControlDefs &defs) const
void CompileFunc(StdCompiler *pComp)
int32_t GetTriggerMode() const
bool ResolveRefs(class C4PlayerControlAssignmentSet *pParentSet, C4PlayerControlDefs *pControlDefs)
bool operator==(const C4PlayerControlAssignment &cmp) const
C4PlayerControlAssignment * GetAssignmentByControlName(const char *szControlName)
bool operator==(const C4PlayerControlAssignmentSet &cmp) const
bool ResolveRefs(C4PlayerControlDefs *pControlDefs)
void RemoveAssignmentByControlName(const char *control_name)
void MergeFrom(const C4PlayerControlAssignmentSet &Src, MergeMode merge_mode)
void GetAssignmentsByKey(const C4PlayerControlDefs &rDefs, const C4KeyCodeEx &key, bool fHoldKeysOnly, C4PlayerControlAssignmentPVec *pOutVec, const C4PlayerControlRecentKeyList &DownKeys, const C4PlayerControlRecentKeyList &RecentKeys) const
bool IsMouseControlAssigned(int32_t mouseevent) const
C4PlayerControlAssignment * GetAssignmentByControl(int32_t control)
void InitEmptyFromTemplate(const C4PlayerControlAssignmentSet &template_set)
C4PlayerControlAssignment * GetAssignmentByIndex(int32_t index)
C4PlayerControlAssignment * CreateAssignmentForControl(const char *control_name)
void GetTriggerKeys(const C4PlayerControlDefs &rDefs, C4KeyCodeExVec *pRegularKeys, C4KeyCodeExVec *pHoldKeys) const
void CompileFunc(StdCompiler *pComp)
const char * GetName() const
C4PlayerControlAssignmentSet * GetDefaultSet()
bool ResolveRefs(C4PlayerControlDefs *pControlDefs)
C4PlayerControlAssignmentSet * GetSetByName(const char *szName)
C4PlayerControlAssignmentSet * CreateEmptySetByTemplate(const C4PlayerControlAssignmentSet &template_set)
void MergeFrom(const C4PlayerControlAssignmentSets &Src, C4PlayerControlAssignmentSet::MergeMode merge_mode)
int32_t GetSetIndex(const C4PlayerControlAssignmentSet *set) const
bool operator==(const C4PlayerControlAssignmentSets &cmp) const
void CompileFunc(StdCompiler *pComp)
void RemoveSetByName(const char *set_name)
C4PlayerControlAssignmentSet * GetSetByIndex(int32_t index)
void CompileFunc(StdCompiler *pComp)
bool IsGlobal() const
C4ID GetExtraData() const
int32_t GetRepeatDelay() const
bool IsValid() const
int32_t GetInitialRepeatDelay() const
bool IsDefaultDisabled() const
const char * GetGUIName() const
bool IsAsync() const
bool IsSync() const
bool operator==(const C4PlayerControlDef &cmp) const
CoordinateSpace GetCoordinateSpace() const
bool IsHoldKey() const
Actions GetAction() const
bool IsSendCursorPos() const
const char * GetGUIDesc() const
void MergeFrom(const C4PlayerControlDefs &Src)
int32_t GetControlIndexByIdentifier(const char *szIdentifier) const
void CompileFunc(StdCompiler *pComp)
const C4PlayerControlDef * GetControlByIndex(int32_t idx) const
struct C4PlayerControlDefs::CInternalCons InternalCons
bool Load(C4Group &hGroup, const char *szFilename, C4LangStringTable *pLang)
bool Save(C4Group &hGroup, const char *szFilename)
void CompileFunc(StdCompiler *pComp)
void ExecuteControlPacket(const class C4ControlPlayerControl *pCtrl)
bool DoMouseInput(uint8_t mouse_id, int32_t mouseevent, float game_x, float game_y, float gui_x, float gui_y, DWORD flags)
bool IsGlobal() const
void RegisterKeyset(int32_t iPlr, C4PlayerControlAssignmentSet *pKeyset)
void CompileFunc(StdCompiler *pComp)
C4ObjectPtr Cursor
Definition: C4Player.h:130
C4MainMenu Menu
Definition: C4Player.h:103
void CountControl(ControlType eType, int32_t iID, int32_t iCntAdd=1)
Definition: C4Player.cpp:1632
int32_t Eliminated
Definition: C4Player.h:83
bool ShowStartup
Definition: C4Player.h:109
@ PCID_DirectCom
Definition: C4Player.h:64
bool ActivateMenuMain()
Definition: C4Player.cpp:1761
std::shared_ptr< class C4GamePadOpener > pGamepad
Definition: C4Player.h:132
C4Player * Get(int iPlayer) const
C4Value Call(C4PropertyName k, C4AulParSet *pPars=nullptr, bool fPassErrors=false)
Definition: C4PropList.h:114
float TargetY
Definition: C4Facet.h:165
float TargetX
Definition: C4Facet.h:165
static C4TimeMilliseconds Now()
bool getBool() const
Definition: C4Value.h:113
void ChangeZoom(float by_factor)
Definition: C4Viewport.cpp:519
C4TargetFacet last_game_draw_cgo
Definition: C4Viewport.h:43
float GetZoom()
Definition: C4Viewport.h:48
C4Viewport * GetViewport(int32_t player_nr, C4Viewport *prev=nullptr)
virtual bool Separator(Sep eSep=SEP_SEP)
Definition: StdCompiler.h:119
virtual bool Default(const char *szName)
Definition: StdCompiler.h:88
void Value(const T &rStruct)
Definition: StdCompiler.h:161
virtual void NameEnd(bool fBreak=false)
Definition: StdCompiler.h:78
bool isSerializer()
Definition: StdCompiler.h:54
void excNotFound(const char *szMessage,...)
Definition: StdCompiler.h:233
virtual bool isDeserializer()
Definition: StdCompiler.h:53
virtual bool isRegistry()
Definition: StdCompiler.h:65
virtual bool Name(const char *szName)
Definition: StdCompiler.h:77
const char * getData() const
Definition: StdBuf.h:442
void AppendChar(char cChar)
Definition: StdBuf.h:588
void Copy()
Definition: StdBuf.h:467
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
size_t getLength() const
Definition: StdBuf.h:445
void Take(char *pnData)
Definition: StdBuf.h:457
float GetZoom()
Definition: C4Gui.h:2831
static C4KeyCodeEx FromC4MC(int8_t mouse_id, int32_t button, DWORD param, bool *is_down=nullptr)
bool IsRepeated() const
C4KeyCode Key
int32_t deviceId
bool operator==(const ControlDownState &cmp) const
bool SetControlDisabled(int32_t iControl, int32_t iVal)
void SetControlMovedState(int32_t iControl, const C4KeyEventData &rMovedState, int32_t iMovedFrame)
std::vector< int32_t > DisableStateVec
int32_t GetControlDisabled(int32_t iControl) const
void SetControlDownState(int32_t iControl, const C4KeyEventData &rDownState, int32_t iDownFrame, bool fDownByUser)
DownStateVec ControlDownStates
bool operator==(const CSync &cmp) const
void ResetControlDownState(int32_t iControl)
const ControlDownState * GetControlDownState(int32_t iControl) const
void CompileFunc(StdCompiler *pComp)
std::vector< ControlDownState > DownStateVec
DisableStateVec ControlDisableStates
void InitDefaults(const C4PlayerControlDefs &ControlDefs)
C4TimeMilliseconds tTime