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