/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // Input to player control mapping #include "C4Include.h" #include "control/C4PlayerControl.h" #include "object/C4DefList.h" #include "c4group/C4LangStringTable.h" #include "player/C4Player.h" #include "player/C4PlayerList.h" #include "control/C4Control.h" #include "game/C4Game.h" #include "platform/C4GamePadCon.h" #include "lib/C4Log.h" #include "graphics/C4GraphicsResource.h" #include "gui/C4MouseControl.h" #include "game/C4GraphicsSystem.h" #include "game/C4Viewport.h" #include "object/C4Object.h" #include "object/C4ObjectMenu.h" #include "script/C4Aul.h" #include #include "control/C4Record.h" /* C4PlayerControlDef */ void C4PlayerControlDef::CompileFunc(StdCompiler *pComp) { if (!pComp->Name("ControlDef")) { pComp->NameEnd(); pComp->excNotFound("ControlDef"); } pComp->Value(mkNamingAdapt(mkParAdapt(sIdentifier, StdCompiler::RCT_Idtf), "Identifier", "None")); pComp->Value(mkNamingAdapt(mkParAdapt(sGUIName, StdCompiler::RCT_All), "GUIName", "")); pComp->Value(mkNamingAdapt(mkParAdapt(sGUIDesc, StdCompiler::RCT_All), "GUIDesc", "")); pComp->Value(mkNamingAdapt(fGlobal, "Global", false)); pComp->Value(mkNamingAdapt(fIsHoldKey, "Hold", false)); pComp->Value(mkNamingAdapt(iRepeatDelay, "RepeatDelay", 0)); pComp->Value(mkNamingAdapt(iInitialRepeatDelay, "InitialRepeatDelay", 0)); pComp->Value(mkNamingAdapt(fDefaultDisabled, "DefaultDisabled", false)); pComp->Value(mkNamingAdapt(idControlExtraData, "ExtraData", C4ID::None)); const StdEnumEntry CoordSpaceNames[] = { { "Game", COS_Game }, { "Viewport", COS_Viewport }, { NULL, COS_Game } }; pComp->Value(mkNamingAdapt(mkEnumAdapt(eCoordSpace, CoordSpaceNames), "CoordinateSpace", COS_Game)); pComp->Value(mkNamingAdapt(fSendCursorPos, "SendCursorPos", false)); const StdEnumEntry ActionNames[] = { { "None", CDA_None }, { "Script", CDA_Script }, { "Menu", CDA_Menu }, { "MenuOK", CDA_MenuOK }, { "MenuCancel", CDA_MenuCancel }, { "MenuLeft", CDA_MenuLeft }, { "MenuUp", CDA_MenuUp }, { "MenuRight", CDA_MenuRight }, { "MenuDown", CDA_MenuDown }, { "ObjectMenuTextComplete", CDA_ObjectMenuTextComplete }, { "ObjectMenuOK", CDA_ObjectMenuOK }, { "ObjectMenuOKAll", CDA_ObjectMenuOKAll }, { "ObjectMenuSelect",CDA_ObjectMenuSelect }, { "ObjectMenuCancel",CDA_ObjectMenuCancel }, { "ObjectMenuLeft", CDA_ObjectMenuLeft }, { "ObjectMenuUp", CDA_ObjectMenuUp }, { "ObjectMenuRight", CDA_ObjectMenuRight }, { "ObjectMenuDown", CDA_ObjectMenuDown }, { "ZoomIn", CDA_ZoomIn }, { "ZoomOut", CDA_ZoomOut }, { NULL, CDA_None } }; pComp->Value(mkNamingAdapt(mkEnumAdapt(eAction, ActionNames), "Action", CDA_Script)); pComp->NameEnd(); } bool C4PlayerControlDef::operator ==(const C4PlayerControlDef &cmp) const { return sIdentifier == cmp.sIdentifier && sGUIName == cmp.sGUIName && sGUIDesc == cmp.sGUIDesc && fGlobal == cmp.fGlobal && fIsHoldKey == cmp.fIsHoldKey && iRepeatDelay == cmp.iRepeatDelay && iInitialRepeatDelay == cmp.iInitialRepeatDelay && fDefaultDisabled == cmp.fDefaultDisabled && idControlExtraData == cmp.idControlExtraData && fSendCursorPos == cmp.fSendCursorPos && eAction == cmp.eAction; } /* C4PlayerControlDefs */ void C4PlayerControlDefs::UpdateInternalCons() { InternalCons.CON_ObjectMenuSelect = GetControlIndexByIdentifier("ObjectMenuSelect"); InternalCons.CON_ObjectMenuOK = GetControlIndexByIdentifier("ObjectMenuOK"); InternalCons.CON_ObjectMenuOKAll = GetControlIndexByIdentifier("ObjectMenuOKAll"); InternalCons.CON_ObjectMenuCancel = GetControlIndexByIdentifier("ObjectMenuCancel"); InternalCons.CON_CursorPos = GetControlIndexByIdentifier("CursorPos"); } void C4PlayerControlDefs::Clear() { clear_previous = false; Defs.clear(); UpdateInternalCons(); } void C4PlayerControlDefs::CompileFunc(StdCompiler *pComp) { pComp->Value(mkNamingAdapt(clear_previous, "ClearPrevious", false)); pComp->Value(mkSTLContainerAdapt(Defs, StdCompiler::SEP_NONE)); if (pComp->isCompiler()) UpdateInternalCons(); } void C4PlayerControlDefs::MergeFrom(const C4PlayerControlDefs &Src) { // Clear previous defs if specified in merge set if (Src.clear_previous) Defs.clear(); // copy all defs from source file; overwrite defs of same name if found for (DefVecImpl::const_iterator i = Src.Defs.begin(); i != Src.Defs.end(); ++i) { const C4PlayerControlDef &SrcDef = *i; // overwrite if def of same name existed int32_t iPrevIdx = GetControlIndexByIdentifier(SrcDef.GetIdentifier()); if (iPrevIdx != CON_None) { Defs[iPrevIdx] = SrcDef; } else { // new def: Append a copy Defs.push_back(SrcDef); } } UpdateInternalCons(); } const C4PlayerControlDef *C4PlayerControlDefs::GetControlByIndex(int32_t idx) const { // safe index if (idx<0 || idx>=int32_t(Defs.size())) return NULL; return &(Defs[idx]); } int32_t C4PlayerControlDefs::GetControlIndexByIdentifier(const char *szIdentifier) const { for (DefVecImpl::const_iterator i = Defs.begin(); i != Defs.end(); ++i) if (SEqual((*i).GetIdentifier(), szIdentifier)) return i-Defs.begin(); return CON_None; } void C4PlayerControlDefs::FinalInit() { // Assume all defs have been loaded // Register scritp constants for (DefVecImpl::const_iterator i = Defs.begin(); i != Defs.end(); ++i) { const char *szIdtf = (*i).GetIdentifier(); if (szIdtf && *szIdtf && !SEqual(szIdtf, "None")) { ::ScriptEngine.RegisterGlobalConstant(FormatString("CON_%s", szIdtf).getData(), C4VInt(i-Defs.begin())); } } } /* C4PlayerControlAssignment */ void C4PlayerControlAssignment::KeyComboItem::CompileFunc(StdCompiler *pComp) { // if key is compiled, also store as a string into KeyName for later resolving if (pComp->isCompiler()) { Key.dwShift = 0; sKeyName.Clear(); pComp->Value(mkParAdapt(Key, &sKeyName)); } else { // decompiler: If there's a stored key name, just write it. Regardless of whether it's a key, undefined or a reference // If no key name is stored, it was probably assigned at runtime and sKeyName needs to be recreated if (!sKeyName) UpdateKeyName(); pComp->Value(mkParAdapt(sKeyName, StdCompiler::RCT_Idtf)); } } void C4PlayerControlAssignment::KeyComboItem::UpdateKeyName() { // update key name from key sKeyName.Copy(Key.ToString(false, false)); if (Key.dwShift) sKeyName.Take(FormatString("%s+%s", C4KeyCodeEx::KeyShift2String((C4KeyShiftState) Key.dwShift).getData(), sKeyName.getData())); } void C4PlayerControlAssignment::CompileFunc(StdCompiler *pComp) { if (!pComp->Name("Assignment")) { pComp->NameEnd(); pComp->excNotFound("Assignment"); } pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(KeyCombo), "Key", KeyComboVec())); pComp->Value(mkNamingAdapt(fComboIsSequence, "ComboIsSequence", false)); pComp->Value(mkNamingAdapt(mkParAdapt(sControlName, StdCompiler::RCT_Idtf), "Control", "None")); pComp->Value(mkNamingAdapt(mkParAdapt(sGUIName, StdCompiler::RCT_All), "GUIName", "")); pComp->Value(mkNamingAdapt(mkParAdapt(sGUIDesc, StdCompiler::RCT_All), "GUIDesc", "")); pComp->Value(mkNamingAdapt(iGUIGroup,"GUIGroup",0)); pComp->Value(mkNamingAdapt(fGUIDisabled, "GUIDisabled", false)); pComp->Value(mkNamingAdapt(iPriority, "Priority", 0)); const StdBitfieldEntry TriggerModeNames[] = { { "Default", CTM_Default }, { "Hold", CTM_Hold }, { "Release", CTM_Release }, { "AlwaysUnhandled", CTM_AlwaysUnhandled }, { "ClearRecentKeys", CTM_ClearRecentKeys }, { NULL, 0 } }; pComp->Value(mkNamingAdapt(mkBitfieldAdapt< int32_t>(iTriggerMode, TriggerModeNames), "TriggerMode", CTM_Default)); pComp->Value(mkNamingAdapt(fOverrideAssignments, "OverrideAssignments", false)); pComp->NameEnd(); // newly loaded structures are not resolved if (pComp->isCompiler()) fRefsResolved = false; } void C4PlayerControlAssignment::ResetKeyToInherited() { if (inherited_assignment) CopyKeyFrom(*inherited_assignment); } bool C4PlayerControlAssignment::IsKeyChanged() const { // no inherited assignment? Then the key is always custom if (!inherited_assignment) return true; // otherwise, compare return KeyCombo != inherited_assignment->KeyCombo || fComboIsSequence != inherited_assignment->fComboIsSequence; } void C4PlayerControlAssignment::SetKey(const C4KeyCodeEx &key) { // set as one-key-combo KeyCombo.resize(1); KeyCombo[0].Key = key; KeyCombo[0].Key.fRepeated = false; KeyCombo[0].sKeyName.Clear(); fComboIsSequence = false; TriggerKey = key; } void C4PlayerControlAssignment::CopyKeyFrom(const C4PlayerControlAssignment &src_assignment) { // just copy key settings; keep control and priorities KeyCombo = src_assignment.KeyCombo; TriggerKey = src_assignment.TriggerKey; fComboIsSequence = src_assignment.fComboIsSequence; if (!src_assignment.fRefsResolved) fRefsResolved = false; } bool C4PlayerControlAssignment::ResolveRefs(C4PlayerControlAssignmentSet *pParentSet, C4PlayerControlDefs *pControlDefs) { // avoid circular chains static int32_t recursion_check = 0; if (recursion_check > 10) { 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()); return false; } ++recursion_check; // resolve control name iControl = pControlDefs->GetControlIndexByIdentifier(sControlName.getData()); // resolve keys KeyComboVec NewCombo; for (KeyComboVec::iterator i = KeyCombo.begin(); i != KeyCombo.end(); ++i) { KeyComboItem &rKeyComboItem = *i; const char *szKeyName = rKeyComboItem.sKeyName.getData(); // check if this is a key reference. A key reference must be preceded by CON_ // it may also be preceded by modifiers (Shift+), which are already set in rKeyComboItem.Key.dwShift bool is_key_reference = false; int last_shift_delim_pos; if (szKeyName && *szKeyName) { if ((last_shift_delim_pos=SCharLastPos('+', szKeyName)) > -1) szKeyName += last_shift_delim_pos+1; if (SEqual2(szKeyName, "CON_")) { is_key_reference = true; szKeyName +=4; } } if (is_key_reference) { // this is a key reference // - find referenced target assignment C4PlayerControlAssignment *pRefAssignment = pParentSet->GetAssignmentByControlName(szKeyName); if (pRefAssignment) { // resolve itself if necessary if (!pRefAssignment->IsRefsResolved()) if (!pRefAssignment->ResolveRefs(pParentSet, pControlDefs)) { --recursion_check; return false; } // insert all keys of that combo into own combo // add any extra shift states from reference DWORD ref_shift = rKeyComboItem.Key.dwShift; if (ref_shift) { for (KeyComboVec::iterator j = pRefAssignment->KeyCombo.begin(); j != pRefAssignment->KeyCombo.end(); ++j) { KeyComboItem assignment_combo_item = *j; assignment_combo_item.Key.dwShift |= ref_shift; NewCombo.push_back(assignment_combo_item); } } else { NewCombo.insert(NewCombo.end(), pRefAssignment->KeyCombo.begin(), pRefAssignment->KeyCombo.end()); } } else { // undefined reference? Not fatal, but inform user LogF("WARNING: Control %s of set %s contains reference to unassigned control %s.", GetControlName(), pParentSet->GetName(), rKeyComboItem.sKeyName.getData()); NewCombo.clear(); } } else { // non-reference: check if the assignment was valid #ifndef USE_CONSOLE if (rKeyComboItem.Key == KEY_Default) LogF("WARNING: Control %s of set %s contains undefined key \"%s\".", GetControlName(), pParentSet->GetName(), szKeyName); #endif // ...and just keep this item. NewCombo.push_back(rKeyComboItem); } } KeyCombo = NewCombo; // adjust Control and Shift into key states for non-sequence combo keys // e.g. LeftControl,A should become LeftControl,Ctrl+A. if (KeyCombo.size() > 1 && !fComboIsSequence) { int32_t shift = 0; for (KeyComboVec::iterator i = KeyCombo.begin(); i != KeyCombo.end(); ++i) { if (i->Key.Key == K_CONTROL_L || i->Key.Key == K_CONTROL_R) shift |= KEYS_Control; if (i->Key.Key == K_SHIFT_L || i->Key.Key == K_SHIFT_R) shift |= KEYS_Shift; shift |= i->Key.dwShift; } for (KeyComboVec::iterator i = KeyCombo.begin(); i != KeyCombo.end(); ++i) i->Key.dwShift |= shift; } // remove control/shift duplications for (KeyComboVec::iterator i = KeyCombo.begin(); i != KeyCombo.end(); ++i) i->Key.FixShiftKeys(); // the trigger key is always last of the chain if (KeyCombo.size()) TriggerKey = KeyCombo.back().Key; else TriggerKey = C4KeyCodeEx(); // done fRefsResolved = true; --recursion_check; return true; } bool C4PlayerControlAssignment::IsComboMatched(const C4PlayerControlRecentKeyList &DownKeys, const C4PlayerControlRecentKeyList &RecentKeys) const { assert(HasCombo()); // check if combo is currently fulfilled (assuming TriggerKey is already matched) if (fComboIsSequence) { C4TimeMilliseconds tKeyLast = C4TimeMilliseconds::Now(); // combo is a sequence: The last keys of RecentKeys must match the sequence // the last ComboKey is the TriggerKey, which is omitted because it has already been matched and is not to be found in RecentKeys yet KeyComboVec::const_reverse_iterator i = KeyCombo.rbegin()+1; for (C4PlayerControlRecentKeyList::const_reverse_iterator ri = RecentKeys.rbegin(); i!=KeyCombo.rend(); ++ri) { // no more keys pressed but combo didn't end? -> no combo match if (ri == RecentKeys.rend()) return false; const C4PlayerControlRecentKey &rk = *ri; // user waited for too long? C4TimeMilliseconds tKeyRecent = rk.tTime; if (tKeyLast - tKeyRecent > C4PlayerControl::MaxSequenceKeyDelay) return false; // key doesn't match? const KeyComboItem &k = *i; if (!(rk.matched_key == k.Key)) { // mouse movement commands do not break sequences if (Key_IsMouse(rk.matched_key.Key) && Key_GetMouseEvent(rk.matched_key.Key) == KEY_MOUSE_Move) continue; return false; } // key OK ++i; } } else { // combo requires keys to be down simultanuously: check that all keys of the combo are in the down-list for (KeyComboVec::const_iterator i = KeyCombo.begin(); i!=KeyCombo.end(); ++i) { const KeyComboItem &k = *i; bool fFound = false; for (C4PlayerControlRecentKeyList::const_iterator di = DownKeys.begin(); di!=DownKeys.end(); ++di) { const C4PlayerControlRecentKey &dk = *di; if (dk.matched_key == k.Key) { fFound = true; break; } } if (!fFound) return false; } } // combo OK! return true; } bool C4PlayerControlAssignment::operator ==(const C4PlayerControlAssignment &cmp) const { // doesn't compare resolved TriggerKey/iControl return KeyCombo == cmp.KeyCombo && sControlName == cmp.sControlName && sGUIName == cmp.sGUIName && sGUIDesc == cmp.sGUIDesc && fGUIDisabled == cmp.fGUIDisabled && iTriggerMode == cmp.iTriggerMode && iPriority == cmp.iPriority; } StdStrBuf C4PlayerControlAssignment::GetKeysAsString(bool human_readable, bool short_name) const { // create a short, human-readable string of the assigned key // to be displayed e.g. in tutorial messages explaining controls StdStrBuf result; if (!KeyCombo.size()) return result; // trigger key KeyComboVec::const_iterator i=KeyCombo.begin(); result.Take(i->Key.ToString(human_readable, short_name)); // extra keys of combo while (++i != KeyCombo.end()) { result.AppendChar(fComboIsSequence ? ',' : '+'); result.Append(i->Key.ToString(human_readable, short_name)); } return result; } const char *C4PlayerControlAssignment::GetGUIName(const C4PlayerControlDefs &defs) const { // local name? if (sGUIName.getLength()) { // special: None defaults to empty name if (sGUIName == "None") return ""; return sGUIName.getData(); } // otherwise, fall back to def const C4PlayerControlDef *def = defs.GetControlByIndex(GetControl()); if (def) return def->GetGUIName(); // no def and no name... return NULL; } const char *C4PlayerControlAssignment::GetGUIDesc(const C4PlayerControlDefs &defs) const { // local desc? if (sGUIDesc.getLength()) return sGUIDesc.getData(); // otherwise, fall back to def const C4PlayerControlDef *def = defs.GetControlByIndex(GetControl()); if (def) return def->GetGUIDesc(); // no def and no desc... return NULL; } bool C4PlayerControlAssignment::IsGUIDisabled() const { return fGUIDisabled; } int32_t C4PlayerControlAssignment::GetGUIGroup() const { return iGUIGroup; } /* C4PlayerControlAssignmentSet */ void C4PlayerControlAssignmentSet::InitEmptyFromTemplate(const C4PlayerControlAssignmentSet &template_set) { // copy all fields except assignments sName.Copy(template_set.sName); sGUIName.Copy(template_set.sGUIName); sParentSetName.Copy(template_set.sParentSetName); has_keyboard = template_set.has_keyboard; has_mouse = template_set.has_mouse; has_gamepad = template_set.has_gamepad; } void C4PlayerControlAssignmentSet::CompileFunc(StdCompiler *pComp) { if (!pComp->Name("ControlSet")) { pComp->NameEnd(); pComp->excNotFound("ControlSet"); } pComp->Value(mkNamingAdapt(mkParAdapt(sName, StdCompiler::RCT_All), "Name", "None")); // can't do RCT_Idtf because of wildcards pComp->Value(mkNamingAdapt(mkParAdapt(sGUIName, StdCompiler::RCT_All), "GUIName", "undefined")); pComp->Value(mkNamingAdapt(mkParAdapt(sParentSetName, StdCompiler::RCT_Idtf), "Parent", "")); pComp->Value(mkNamingAdapt(has_keyboard, "Keyboard", true)); pComp->Value(mkNamingAdapt(has_mouse, "Mouse", true)); pComp->Value(mkNamingAdapt(has_gamepad, "Gamepad", false)); pComp->Value(mkSTLContainerAdapt(Assignments, StdCompiler::SEP_NONE)); pComp->NameEnd(); } void C4PlayerControlAssignmentSet::MergeFrom(const C4PlayerControlAssignmentSet &Src, MergeMode merge_mode) { // take over all assignments defined in Src for (C4PlayerControlAssignmentVec::const_iterator i = Src.Assignments.begin(); i != Src.Assignments.end(); ++i) { const C4PlayerControlAssignment &SrcAssignment = *i; bool fIsReleaseKey = !!(SrcAssignment.GetTriggerMode() & C4PlayerControlAssignment::CTM_Release); // overwrite same def and release key state if (merge_mode != MM_LowPrio && SrcAssignment.IsOverrideAssignments()) { // high priority override control clears all previous (very inefficient method...might as well recreate the whole list) bool any_remaining = true; while (any_remaining) { any_remaining = false; for (C4PlayerControlAssignmentVec::iterator j = Assignments.begin(); j != Assignments.end(); ++j) if (SEqual((*j).GetControlName(), SrcAssignment.GetControlName())) { bool fSelfIsReleaseKey = !!((*j).GetTriggerMode() & C4PlayerControlAssignment::CTM_Release); if (fSelfIsReleaseKey == fIsReleaseKey) { Assignments.erase(j); any_remaining = true; break; } } } } else if (merge_mode == MM_LowPrio || merge_mode == MM_ConfigOverload) { // if this is low priority, another override control kills this bool any_override = false; for (C4PlayerControlAssignmentVec::iterator j = Assignments.begin(); j != Assignments.end(); ++j) if (SEqual((*j).GetControlName(), SrcAssignment.GetControlName())) { bool fSelfIsReleaseKey = !!((*j).GetTriggerMode() & C4PlayerControlAssignment::CTM_Release); if (fSelfIsReleaseKey == fIsReleaseKey) { any_override = true; // config overloads just change the key of the inherited assignment if (merge_mode == MM_ConfigOverload) { (*j).CopyKeyFrom(SrcAssignment); (*j).SetInherited(false); } break; } } if (any_override) continue; } // new def: Append a copy Assignments.push_back(SrcAssignment); // inherited marker if (merge_mode == MM_Inherit) { Assignments.back().SetInherited(true); Assignments.back().SetInheritedAssignment(&SrcAssignment); } } } C4PlayerControlAssignment *C4PlayerControlAssignmentSet::CreateAssignmentForControl(const char *control_name) { Assignments.push_back(C4PlayerControlAssignment()); Assignments.back().SetControlName(control_name); return &Assignments.back(); } void C4PlayerControlAssignmentSet::RemoveAssignmentByControlName(const char *control_name) { for (C4PlayerControlAssignmentVec::iterator i = Assignments.begin(); i != Assignments.end(); ++i) if (SEqual((*i).GetControlName(), control_name)) { Assignments.erase(i); return; } } bool C4PlayerControlAssignmentSet::ResolveRefs(C4PlayerControlDefs *pDefs) { // reset all resolved flags to allow re-resolve after overloads for (C4PlayerControlAssignmentVec::iterator i = Assignments.begin(); i != Assignments.end(); ++i) (*i).ResetRefsResolved(); // resolve in order; ignore already resolved because they might have been resolved by cross reference for (C4PlayerControlAssignmentVec::iterator i = Assignments.begin(); i != Assignments.end(); ++i) if (!(*i).IsRefsResolved()) if (!(*i).ResolveRefs(this, pDefs)) return false; return true; } void C4PlayerControlAssignmentSet::SortAssignments() { // final init: sort assignments by priority // note this screws up sorting for config dialog std::sort(Assignments.begin(), Assignments.end()); } C4PlayerControlAssignment *C4PlayerControlAssignmentSet::GetAssignmentByIndex(int32_t index) { if (index<0 || index>=int32_t(Assignments.size())) return NULL; return &Assignments[index]; } C4PlayerControlAssignment *C4PlayerControlAssignmentSet::GetAssignmentByControlName(const char *szControlName) { for (C4PlayerControlAssignmentVec::iterator i = Assignments.begin(); i != Assignments.end(); ++i) if (SEqual((*i).GetControlName(), szControlName)) // We don't like release keys... (2do) if (!((*i).GetTriggerMode() & C4PlayerControlAssignment::CTM_Release)) return &*i; return NULL; } C4PlayerControlAssignment *C4PlayerControlAssignmentSet::GetAssignmentByControl(int32_t control) { // TODO: Might want to stuff this into a vector indexed by control for faster lookup for (C4PlayerControlAssignmentVec::iterator i = Assignments.begin(); i != Assignments.end(); ++i) if ((*i).GetControl() == control) // We don't like release keys... (2do) if (!((*i).GetTriggerMode() & C4PlayerControlAssignment::CTM_Release)) return &*i; return NULL; } bool C4PlayerControlAssignmentSet::operator ==(const C4PlayerControlAssignmentSet &cmp) const { return Assignments == cmp.Assignments && sName == cmp.sName; } void C4PlayerControlAssignmentSet::GetAssignmentsByKey(const C4PlayerControlDefs &rDefs, const C4KeyCodeEx &key, bool fHoldKeysOnly, C4PlayerControlAssignmentPVec *pOutVec, const C4PlayerControlRecentKeyList &DownKeys, const C4PlayerControlRecentKeyList &RecentKeys) const { assert(pOutVec); // primary match by TriggerKey (todo: Might use a hash map here if matching speed becomes an issue due to large control sets) for (C4PlayerControlAssignmentVec::const_iterator i = Assignments.begin(); i != Assignments.end(); ++i) { const C4PlayerControlAssignment &rAssignment = *i; const C4KeyCodeEx &rAssignmentTriggerKey = rAssignment.GetTriggerKey(); if (!(rAssignmentTriggerKey.Key == key.Key)) continue; // special: hold-keys-only ignore shift, because shift state might have been release during hold if (!fHoldKeysOnly) if (rAssignmentTriggerKey.dwShift != key.dwShift) continue; // check linked control def const C4PlayerControlDef *pCtrl = rDefs.GetControlByIndex(rAssignment.GetControl()); if (!pCtrl) continue; // only want hold keys? if (fHoldKeysOnly) { // a hold/release-trigger key is not a real hold key, even if the underlying control is if (!pCtrl->IsHoldKey() || (rAssignment.GetTriggerMode() & (C4PlayerControlAssignment::CTM_Hold | C4PlayerControlAssignment::CTM_Release))) continue; } else if (rAssignment.HasCombo()) { // hold-only events match the trigger key only (i.e., Release-events are generated as soon as the trigger key goes up) // other events must match either the sequence or the down-key-combination if (!rAssignment.IsComboMatched(DownKeys, RecentKeys)) continue; } // we got match! Store it pOutVec->push_back(&rAssignment); } } void C4PlayerControlAssignmentSet::GetTriggerKeys(const C4PlayerControlDefs &rDefs, C4KeyCodeExVec *pRegularKeys, C4KeyCodeExVec *pHoldKeys) const { // put all trigger keys of keyset into output vectors // first all hold keys for (C4PlayerControlAssignmentVec::const_iterator i = Assignments.begin(); i != Assignments.end(); ++i) { const C4PlayerControlAssignment &rAssignment = *i; const C4PlayerControlDef *pDef = rDefs.GetControlByIndex(rAssignment.GetControl()); if (pDef && pDef->IsHoldKey()) { const C4KeyCodeEx &rKey = rAssignment.GetTriggerKey(); if (std::find(pHoldKeys->begin(), pHoldKeys->end(), rKey) == pHoldKeys->end()) pHoldKeys->push_back(rKey); } } // then all regular keys that aren't in the hold keys list yet for (C4PlayerControlAssignmentVec::const_iterator i = Assignments.begin(); i != Assignments.end(); ++i) { const C4PlayerControlAssignment &rAssignment = *i; const C4PlayerControlDef *pDef = rDefs.GetControlByIndex(rAssignment.GetControl()); if (pDef && !pDef->IsHoldKey()) { const C4KeyCodeEx &rKey = rAssignment.GetTriggerKey(); if (std::find(pHoldKeys->begin(), pHoldKeys->end(), rKey) == pHoldKeys->end()) if (std::find(pRegularKeys->begin(), pRegularKeys->end(), rKey) == pRegularKeys->end()) pRegularKeys->push_back(rKey); } } } C4Facet C4PlayerControlAssignmentSet::GetPicture() const { // get image to be drawn to represent this control set // picture per set not implemented yet. So just default to out standard images if (HasGamepad()) return ::GraphicsResource.fctGamepad.GetPhase(0); // if (HasMouse()) return ::GraphicsResource.fctMouse; // might be useful again with changing control sets if (HasKeyboard()) return ::GraphicsResource.fctKeyboard.GetPhase(Game.PlayerControlUserAssignmentSets.GetSetIndex(this)); return C4Facet(); } bool C4PlayerControlAssignmentSet::IsMouseControlAssigned(int32_t mouseevent) const { // TODO return true; } /* C4PlayerControlAssignmentSets */ void C4PlayerControlAssignmentSets::Clear() { Sets.clear(); } void C4PlayerControlAssignmentSets::CompileFunc(StdCompiler *pComp) { if (pComp->isDecompiler() && pComp->isRegistry()) { pComp->Default("ControlSets"); // special registry compiler: Clean out everything before } pComp->Value(mkNamingAdapt(clear_previous, "ClearPrevious", false)); pComp->Value(mkSTLContainerAdapt(Sets, StdCompiler::SEP_NONE)); } bool C4PlayerControlAssignmentSets::operator ==(const C4PlayerControlAssignmentSets &cmp) const { return Sets == cmp.Sets && clear_previous == cmp.clear_previous; } void C4PlayerControlAssignmentSets::MergeFrom(const C4PlayerControlAssignmentSets &Src, C4PlayerControlAssignmentSet::MergeMode merge_mode) { // if source set is flagged to clear previous, do this! if (Src.clear_previous) Sets.clear(); // take over all assignments in known sets and new sets defined in Src for (AssignmentSetList::const_iterator i = Src.Sets.begin(); i != Src.Sets.end(); ++i) { const C4PlayerControlAssignmentSet &SrcSet = *i; // overwrite if def of same name existed if it's not low priority anyway bool fIsWildcardSet = SrcSet.IsWildcardName(); if (!fIsWildcardSet) { C4PlayerControlAssignmentSet *pPrevSet = GetSetByName(SrcSet.GetName()); if (!pPrevSet && merge_mode == C4PlayerControlAssignmentSet::MM_Inherit) { // inherited sets must go through merge procedure to set inherited links pPrevSet = CreateEmptySetByTemplate(SrcSet); } if (pPrevSet) { pPrevSet->MergeFrom(SrcSet, merge_mode); } else { // new def: Append a copy Sets.push_back(SrcSet); } } else { // source is a wildcard: Merge with all matching sets for (AssignmentSetList::iterator j = Sets.begin(); j != Sets.end(); ++j) { C4PlayerControlAssignmentSet &DstSet = *j; if (WildcardMatch(SrcSet.GetName(), DstSet.GetName())) { DstSet.MergeFrom(SrcSet, merge_mode); } } } } } bool C4PlayerControlAssignmentSets::ResolveRefs(C4PlayerControlDefs *pDefs) { for (AssignmentSetList::iterator i = Sets.begin(); i != Sets.end(); ++i) if (!(*i).ResolveRefs(pDefs)) return false; return true; } void C4PlayerControlAssignmentSets::SortAssignments() { for (AssignmentSetList::iterator i = Sets.begin(); i != Sets.end(); ++i) (*i).SortAssignments(); } C4PlayerControlAssignmentSet *C4PlayerControlAssignmentSets::GetSetByName(const char *szName) { for (AssignmentSetList::iterator i = Sets.begin(); i != Sets.end(); ++i) if (WildcardMatch(szName, (*i).GetName())) return &*i; return NULL; } C4PlayerControlAssignmentSet *C4PlayerControlAssignmentSets::GetDefaultSet() { // default set is first defined control set if (Sets.empty()) return NULL; // nothing defined :( return &Sets.front(); } int32_t C4PlayerControlAssignmentSets::GetSetIndex(const C4PlayerControlAssignmentSet *set) const { // find set in list; return index int32_t index = 0; for (AssignmentSetList::const_iterator i = Sets.begin(); i != Sets.end(); ++i,++index) if (&*i == set) return index; return -1; // not found } C4PlayerControlAssignmentSet *C4PlayerControlAssignmentSets::GetSetByIndex(int32_t index) { // bounds check if (index < 0 || index >= (int32_t)Sets.size()) return NULL; // return indexed set AssignmentSetList::iterator i = Sets.begin(); while (index--) ++i; return &*i; } C4PlayerControlAssignmentSet *C4PlayerControlAssignmentSets::CreateEmptySetByTemplate(const C4PlayerControlAssignmentSet &template_set) { Sets.push_back(C4PlayerControlAssignmentSet()); Sets.back().InitEmptyFromTemplate(template_set); return &Sets.back(); } void C4PlayerControlAssignmentSets::RemoveSetByName(const char *set_name) { for (AssignmentSetList::iterator i = Sets.begin(); i != Sets.end(); ++i) if (SEqual(set_name, (*i).GetName())) { Sets.erase(i); return; } } /* C4PlayerControlFile */ void C4PlayerControlFile::CompileFunc(StdCompiler *pComp) { pComp->Value(mkNamingAdapt(ControlDefs, "ControlDefs", C4PlayerControlDefs())); pComp->Value(mkNamingAdapt(AssignmentSets, "ControlSets", C4PlayerControlAssignmentSets())); } bool C4PlayerControlFile::Load(C4Group &hGroup, const char *szFilename, C4LangStringTable *pLang) { // clear previous Clear(); // load and prepare file contents StdStrBuf Buf; if (!hGroup.LoadEntryString(szFilename, &Buf)) return false; if (pLang) pLang->ReplaceStrings(Buf); // parse it! if (!CompileFromBuf_LogWarn(*this, Buf, szFilename)) return false; return true; } bool C4PlayerControlFile::Save(C4Group &hGroup, const char *szFilename) { // decompile to buffer and save buffer to group StdStrBuf Buf; if (!DecompileToBuf_Log(*this, &Buf, szFilename)) return false; hGroup.Add(szFilename, Buf, false, true); return true; } void C4PlayerControlFile::Clear() { ControlDefs.Clear(); AssignmentSets.Clear(); } /* C4PlayerControl */ void C4PlayerControl::CSync::ControlDownState::CompileFunc(StdCompiler *pComp) { pComp->Value(DownState); pComp->Separator(); pComp->Value(MovedState); pComp->Separator(); pComp->Value(iDownFrame); pComp->Separator(); pComp->Value(iMovedFrame); pComp->Separator(); pComp->Value(fDownByUser); } bool C4PlayerControl::CSync::ControlDownState::operator ==(const ControlDownState &cmp) const { return DownState == cmp.DownState && MovedState == cmp.MovedState && iDownFrame == cmp.iDownFrame && iMovedFrame == cmp.iMovedFrame && fDownByUser == cmp.fDownByUser; } const C4PlayerControl::CSync::ControlDownState *C4PlayerControl::CSync::GetControlDownState(int32_t iControl) const { // safe access if (iControl < 0 || iControl >= int32_t(ControlDownStates.size())) return NULL; return &ControlDownStates[iControl]; } int32_t C4PlayerControl::CSync::GetControlDisabled(int32_t iControl) const { // safe access if (iControl < 0 || iControl >= int32_t(ControlDisableStates.size())) return 0; return ControlDisableStates[iControl]; } void C4PlayerControl::CSync::SetControlDownState(int32_t iControl, const C4KeyEventData &rDownState, int32_t iDownFrame, bool fDownByUser) { // update state if (iControl < 0) return; if (iControl >= int32_t(ControlDownStates.size())) ControlDownStates.resize(iControl+1); ControlDownState &rState = ControlDownStates[iControl]; rState.DownState = rDownState; rState.iDownFrame = iDownFrame; rState.fDownByUser = fDownByUser; } void C4PlayerControl::CSync::SetControlMovedState(int32_t iControl, const C4KeyEventData &rMovedState, int32_t iMovedFrame) { // update state if (iControl < 0) return; if (iControl >= int32_t(ControlDownStates.size())) ControlDownStates.resize(iControl+1); ControlDownState &rState = ControlDownStates[iControl]; rState.MovedState = rMovedState; rState.iMovedFrame = iMovedFrame; } bool C4PlayerControl::CSync::SetControlDisabled(int32_t iControl, int32_t iVal) { // disable control if (iControl < 0) return false; if (iControl >= int32_t(ControlDisableStates.size())) ControlDisableStates.resize(iControl+1); ControlDisableStates[iControl] = iVal; // if a control is disabled, its down-state is reset silently ResetControlDownState(iControl); return true; } void C4PlayerControl::CSync::ResetControlDownState(int32_t iControl) { // silently reset down state of control const ControlDownState *pDownState = GetControlDownState(iControl); if (pDownState && pDownState->IsDown()) { C4KeyEventData KeyDownState = pDownState->DownState; KeyDownState.iStrength = 0; SetControlDownState(iControl, KeyDownState, 0, false); SetControlMovedState(iControl, KeyDownState, 0); } } void C4PlayerControl::CSync::InitDefaults(const C4PlayerControlDefs &ControlDefs) { const C4PlayerControlDef *def; int32_t i=0; while ((def = ControlDefs.GetControlByIndex(i))) { if (def->IsDefaultDisabled()) SetControlDisabled(i, true); ++i; } } void C4PlayerControl::CSync::Clear() { ControlDownStates.clear(); ControlDisableStates.clear(); } void C4PlayerControl::CSync::CompileFunc(StdCompiler *pComp) { pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlDownStates), "Down", DownStateVec())); pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlDisableStates), "Disabled", DisableStateVec())); } bool C4PlayerControl::CSync::operator ==(const CSync &cmp) const { return ControlDownStates == cmp.ControlDownStates && ControlDisableStates == cmp.ControlDisableStates; } void C4PlayerControl::Init() { // defaultdisabled controls Sync.InitDefaults(ControlDefs); } void C4PlayerControl::CompileFunc(StdCompiler *pComp) { // compile sync values only CSync DefaultSync; DefaultSync.InitDefaults(ControlDefs); pComp->Value(mkNamingAdapt(Sync, "PlayerControl", DefaultSync)); } 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) { if (Key_IsGamepad(pressed_key.Key)) { // We have to filter gamepad events here. C4Player *plr = ::Players.Get(iPlr); if (!plr || !plr->pGamepad || plr->pGamepad->GetID() != pressed_key.deviceId) return false; } // collect all matching keys C4PlayerControlAssignmentPVec Matches; assert(pControlSet); // shouldn't get this callback for players without control set pControlSet->GetAssignmentsByKey(ControlDefs, matched_key, state != CONS_Down, &Matches, DownKeys, RecentKeys); // process async controls C4ControlPlayerControl *pControlPacket = NULL; for (C4PlayerControlAssignmentPVec::const_iterator i = Matches.begin(); i != Matches.end(); ++i) { const C4PlayerControlAssignment *pAssignment = *i; assert(pAssignment); int32_t iControlIndex = pAssignment->GetControl(); const C4PlayerControlDef *pControlDef = ControlDefs.GetControlByIndex(iControlIndex); if (pControlDef && pControlDef->IsValid() && !Sync.IsControlDisabled(iControlIndex) && (state == CONS_Down || pControlDef->IsHoldKey())) { // clear RecentKeys if requested by this assignment. Must be done before sync queue, so multiple combos can be issued in a single control frame. if (clear_recent_keys && (pAssignment->GetTriggerMode() & C4PlayerControlAssignment::CTM_ClearRecentKeys)) *clear_recent_keys = true; // extra data from key or overwrite by current cursor pos if definition requires it if (pControlDef->IsAsync() && !pControlPacket) { if (pControlDef->IsSendCursorPos()) IsCursorPosRequested = true; // async cursor pos request - doesn't really make sense to set this flag for async controls if (ExecuteControl(iControlIndex, state, rKeyExtraData, pAssignment->GetTriggerMode(), pressed_key.IsRepeated(), reset_down_states_only)) return true; } else { // sync control // ignore key repeats, because we do our own key repeat for sync controls if (pressed_key.IsRepeated()) return false; // sync control has higher priority - no more async execution then // 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 if (!pControlPacket) pControlPacket = new C4ControlPlayerControl(iPlr, state, rKeyExtraData); int32_t extra_trigger_mode = 0; if (reset_down_states_only) extra_trigger_mode |= C4PlayerControlAssignment::CTM_HandleDownStatesOnly; pControlPacket->AddControl(iControlIndex, pAssignment->GetTriggerMode() | extra_trigger_mode); // sync cursor pos request; pos will be added to control before it is synced/executed if (pControlDef->IsSendCursorPos()) IsCursorPosRequested = true; } } } // push sync control to input if (pControlPacket) { Game.Input.Add(CID_PlrControl, pControlPacket); // assume processed (although we can't really know that yet) return true; } return false; } bool C4PlayerControl::ProcessKeyDown(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key) { // add key to local "down" list if it's not already in there // except for some mouse events for which a down state does not make sense C4PlayerControlRecentKey RKey(pressed_key,matched_key,C4TimeMilliseconds::Now()); if (!Key_IsMouse(pressed_key.Key) || Inside(Key_GetMouseEvent(pressed_key.Key), KEY_MOUSE_Button1, KEY_MOUSE_ButtonMax)) { if (std::find(DownKeys.begin(), DownKeys.end(), pressed_key) == DownKeys.end()) DownKeys.push_back(RKey); } // process! bool clear_recent_keys = false; bool fResult = ProcessKeyEvent(pressed_key, matched_key, CONS_Down, Game.KeyboardInput.GetLastKeyExtraData(), false, &clear_recent_keys); // unless assignment requests a clear, always add keys to recent list even if not handled if (clear_recent_keys) RecentKeys.clear(); 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) RecentKeys.push_back(RKey); return fResult; } bool C4PlayerControl::ProcessKeyUp(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key) { // remove key from "down" list // except for some mouse events for which a down state does not make sense if (!Key_IsMouse(pressed_key.Key) || Inside(Key_GetMouseEvent(pressed_key.Key), KEY_MOUSE_Button1, KEY_MOUSE_ButtonMax)) { C4PlayerControlRecentKeyList::iterator i = find(DownKeys.begin(), DownKeys.end(), pressed_key); if (i != DownKeys.end()) DownKeys.erase(i); } // process! return ProcessKeyEvent(pressed_key, matched_key, CONS_Up, Game.KeyboardInput.GetLastKeyExtraData()); } bool C4PlayerControl::ProcessKeyMoved(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key) { // process! return ProcessKeyEvent(pressed_key, matched_key, CONS_Moved, Game.KeyboardInput.GetLastKeyExtraData()); } void C4PlayerControl::ExecuteControlPacket(const class C4ControlPlayerControl *pCtrl) { // callback from control queue. Execute controls in packet until one of them gets processed // assume async packets always as not processed to ensure sync safety (usually, sync commands should better not ovberride async commands anyway) bool fHandleDownStateOnly = false; for (C4ControlPlayerControl::ControlItemVec::const_iterator i = pCtrl->GetControlItems().begin(); i != pCtrl->GetControlItems().end(); ++i) { const C4ControlPlayerControl::ControlItem &rItem = *i; const C4PlayerControlDef *pCtrlDef = ControlDefs.GetControlByIndex(rItem.iControl); if (pCtrlDef) { if (Config.General.DebugRec) { if (pCtrlDef->IsSync()) { AddDbgRec(RCT_PlrCom, &rItem.iControl, sizeof(rItem.iControl)); } } if (ExecuteControl(rItem.iControl, pCtrl->GetState(), pCtrl->GetExtraData(), rItem.iTriggerMode, false, fHandleDownStateOnly)) if (pCtrlDef->IsSync()) { if (pCtrl->GetState() == CONS_Up) { // control processed. however, for key releases, overriden keys are released silently so following down events aren't handled as key repeats // note this does not affect CTM_Hold/CTM_Release, because they ignore release controls anyway fHandleDownStateOnly = true; } else { break; } } } } } bool C4PlayerControl::ExecuteControl(int32_t iControl, ControlState state, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly) { // execute single control. return if handled const C4PlayerControlDef *pControlDef = ControlDefs.GetControlByIndex(iControl); if (!pControlDef || Sync.IsControlDisabled(iControl)) return false; C4PlayerControlDef::Actions eAction = pControlDef->GetAction(); C4KeyEventData KeyExtraData(rKeyExtraData); const CSync::ControlDownState *pCtrlDownState = Sync.GetControlDownState(iControl); bool fWasDown = pCtrlDownState ? pCtrlDownState->IsDown() : false; // global controls only in global context if (IsGlobal() != pControlDef->IsGlobal()) return false; // down state handling only? if (iTriggerMode & C4PlayerControlAssignment::CTM_HandleDownStatesOnly) fHandleDownStateOnly = true; // hold-actions only work on script controls with the hold flag if (iTriggerMode & (C4PlayerControlAssignment::CTM_Hold | C4PlayerControlAssignment::CTM_Release)) { if (eAction != C4PlayerControlDef::CDA_Script) return false; if (!pControlDef->IsHoldKey()) return false; if (state == CONS_Up) return false; // hold triggers have no "up"-event // perform hold/release if (fWasDown) { // control is currently down: release? if (iTriggerMode & C4PlayerControlAssignment::CTM_Release) { KeyExtraData.iStrength = 0; Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, false); // now process as a regular "Up" event state = CONS_Up; fRepeated = false; } else { assert(iTriggerMode & C4PlayerControlAssignment::CTM_Hold); // control is down but trigger key is pressed again: Refresh down state // (this will restart the KeyRepeat time) Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, false); // now process as a regular, repeated "down" event fRepeated = true; } } else { // control is currently up. Put into hold-down-state if this is a hold key if (iTriggerMode & C4PlayerControlAssignment::CTM_Hold) { Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, false); // now process as a regular "down" event fRepeated = false; } else { //. Ignore if it's only a release key return false; } } } else if (state == CONS_Up) { // regular ControlUp: Only valid if that control was down if (!fWasDown) return false; Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, true); } else if (pControlDef->IsHoldKey()) { if (state == CONS_Moved) { Sync.SetControlMovedState(iControl, KeyExtraData, Game.FrameCounter); fRepeated = true; } else { // regular ControlDown on Hold Key: Set in down list Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, true); fRepeated = fWasDown; } } // down state handling done if (fHandleDownStateOnly) return false; // perform action for this control bool fHandled = ExecuteControlAction(iControl, eAction, pControlDef->GetExtraData(), state, KeyExtraData, fRepeated); // handled controls hide control display C4Player *pPlr; if ((pPlr = ::Players.Get(iPlr))) if (pPlr->ShowStartup) pPlr->ShowStartup = false; // return if handled, unless control is defined as always unhandled return fHandled && !(iTriggerMode & C4PlayerControlAssignment::CTM_AlwaysUnhandled); } bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated) { // moved events don't make sense for menus and are only handled by script if (state == CONS_Moved && eAction != C4PlayerControlDef::CDA_Script) return false; // get affected player C4Player *pPlr = NULL; C4Viewport *pVP; C4Object *pCursor = NULL; C4Menu *pCursorMenu = NULL; if (iPlr > -1) { pPlr = ::Players.Get(iPlr); if (!pPlr) return false; pCursor = pPlr->Cursor; if (pCursor && pCursor->Menu && pCursor->Menu->IsActive()) pCursorMenu = pCursor->Menu; } bool fUp = state == CONS_Up; // exec action (on player) switch (eAction) { // scripted player control case C4PlayerControlDef::CDA_Script: return ExecuteControlScript(iControl, idControlExtraData, state, rKeyExtraData, fRepeated); // menu controls case C4PlayerControlDef::CDA_Menu: if (!pPlr || fUp) return false; if (pPlr->Menu.IsActive()) pPlr->Menu.TryClose(false, true); else pPlr->ActivateMenuMain(); return true; // toggle case C4PlayerControlDef::CDA_MenuOK: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuEnter,0); return true; // ok on item case C4PlayerControlDef::CDA_MenuCancel: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuClose,0); return true; // close menu case C4PlayerControlDef::CDA_MenuLeft: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuLeft ,0); return true; // navigate case C4PlayerControlDef::CDA_MenuUp: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuUp ,0); return true; // navigate case C4PlayerControlDef::CDA_MenuRight: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuRight,0); return true; // navigate case C4PlayerControlDef::CDA_MenuDown: if (!pPlr || !pPlr->Menu.IsActive() || fUp) return false; pPlr->Menu.Control(COM_MenuDown,0); return true; // navigate case C4PlayerControlDef::CDA_ObjectMenuTextComplete: if (!pCursorMenu || fUp || !pCursorMenu->IsTextProgressing()) return false; pCursorMenu->Control(COM_MenuShowText,0); return true; // fast-foward text display case C4PlayerControlDef::CDA_ObjectMenuOK: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuEnter,0); return true; // ok on item case C4PlayerControlDef::CDA_ObjectMenuOKAll: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuEnterAll,0); return true; // alt ok on item case C4PlayerControlDef::CDA_ObjectMenuSelect: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuSelect,rKeyExtraData.iStrength); return true; // select an item directly case C4PlayerControlDef::CDA_ObjectMenuCancel: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuClose,0); return true; // close menu case C4PlayerControlDef::CDA_ObjectMenuLeft: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuLeft ,0); return true; // navigate case C4PlayerControlDef::CDA_ObjectMenuUp: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuUp ,0); return true; // navigate case C4PlayerControlDef::CDA_ObjectMenuRight: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuRight,0); return true; // navigate case C4PlayerControlDef::CDA_ObjectMenuDown: if (!pCursorMenu || fUp) return false; pCursorMenu->Control(COM_MenuDown ,0); return true; // navigate case C4PlayerControlDef::CDA_ZoomIn: if (!pPlr || fUp || !(pVP = ::Viewports.GetViewport(iPlr))) return false; pVP->ChangeZoom(C4GFX_ZoomStep); return true; // viewport zoom case C4PlayerControlDef::CDA_ZoomOut: if (!pPlr || fUp || !(pVP = ::Viewports.GetViewport(iPlr))) return false; pVP->ChangeZoom(1.0f/C4GFX_ZoomStep); return true; // viewport zoom //unknown action default: return false; } } bool C4PlayerControl::ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated) { C4Player *pPlr = ::Players.Get(iPlr); if (pPlr) { // Not for eliminated (checked again in DirectCom, but make sure no control is generated for eliminated players!) if (pPlr->Eliminated) return false; // control count for statistics (but don't count analog stick wiggles) if (state != CONS_Moved) pPlr->CountControl(C4Player::PCID_DirectCom, iControl*2+state); } else if (iPlr > -1) { // player lost? return false; } // get coordinates int32_t x,y; const C4PlayerControlDef *def = ControlDefs.GetControlByIndex(iControl); if (def && def->GetCoordinateSpace() == C4PlayerControlDef::COS_Viewport) { x = rKeyExtraData.vp_x; y = rKeyExtraData.vp_y; } else { x = rKeyExtraData.game_x; y = rKeyExtraData.game_y; } C4Value vx = (x == C4KeyEventData::KeyPos_None) ? C4VNull : C4VInt(x); C4Value vy = (y == C4KeyEventData::KeyPos_None) ? C4VNull : C4VInt(y); // exec control function C4AulParSet Pars(iPlr, iControl, C4Id2Def(idControlExtraData), vx, vy, rKeyExtraData.iStrength, fRepeated, C4VInt(state)); return ::ScriptEngine.GetPropList()->Call(PSF_PlayerControl, &Pars).getBool(); } void C4PlayerControl::Execute() { // sync execution: Do keyrepeat for (size_t i=0; iIsDown()) { const C4PlayerControlDef *pCtrlDef = ControlDefs.GetControlByIndex(i); assert(pCtrlDef); int32_t iCtrlRepeatDelay = pCtrlDef->GetRepeatDelay(); if (iCtrlRepeatDelay) { int32_t iFrameDiff = Game.FrameCounter - pControlDownState->iDownFrame; int32_t iCtrlInitialRepeatDelay = pCtrlDef->GetInitialRepeatDelay(); if (iFrameDiff && iFrameDiff >= iCtrlInitialRepeatDelay) { if (!((iFrameDiff-iCtrlInitialRepeatDelay) % iCtrlRepeatDelay)) { // it's RepeatTime for this key! ExecuteControlAction(i, pCtrlDef->GetAction(), pCtrlDef->GetExtraData(), CONS_Down, pControlDownState->DownState, true); } } } } } // cleanup old recent keys C4TimeMilliseconds tNow = C4TimeMilliseconds::Now(); C4PlayerControlRecentKeyList::iterator irk; for (irk = RecentKeys.begin(); irk != RecentKeys.end(); ++irk) { C4PlayerControlRecentKey &rk = *irk; if (rk.tTime + MaxRecentKeyLookback > tNow) break; } if (irk != RecentKeys.begin()) RecentKeys.erase(RecentKeys.begin(), irk); } C4PlayerControl::C4PlayerControl() : ControlDefs(Game.PlayerControlDefs), iPlr(-1), pControlSet(NULL), IsCursorPosRequested(false) { } void C4PlayerControl::Clear() { iPlr = NO_OWNER; pControlSet = NULL; for (KeyBindingList::iterator i = KeyBindings.begin(); i != KeyBindings.end(); ++i) delete *i; KeyBindings.clear(); RecentKeys.clear(); DownKeys.clear(); Sync.Clear(); IsCursorPosRequested = false; } void C4PlayerControl::RegisterKeyset(int32_t iPlr, C4PlayerControlAssignmentSet *pKeyset) { // setup pControlSet = pKeyset; this->iPlr = iPlr; // register all keys into Game.KeyboardInput creating KeyBindings if (pControlSet) { C4KeyCodeExVec RegularKeys, HoldKeys; pControlSet->GetTriggerKeys(ControlDefs, &RegularKeys, &HoldKeys); int32_t idx=0; for (C4KeyCodeExVec::const_iterator i = RegularKeys.begin(); i != RegularKeys.end(); ++i) AddKeyBinding(*i, false, idx++); for (C4KeyCodeExVec::const_iterator i = HoldKeys.begin(); i != HoldKeys.end(); ++i) AddKeyBinding(*i, true, idx++); } } void C4PlayerControl::AddKeyBinding(const C4KeyCodeEx &key, bool fHoldKey, int32_t idx) { KeyBindings.push_back(new C4KeyBinding( key, FormatString("PlrKey%02d", idx).getData(), KEYSCOPE_Control, new C4KeyCBExPassKey(*this, key, &C4PlayerControl::ProcessKeyDown, fHoldKey ? &C4PlayerControl::ProcessKeyUp : NULL, NULL, fHoldKey ? &C4PlayerControl::ProcessKeyMoved : NULL), C4CustomKey::PRIO_PlrControl)); } bool C4PlayerControl::DoMouseInput(uint8_t mouse_id, int32_t mouseevent, float game_x, float game_y, float gui_x, float gui_y, bool is_ctrl_down, bool is_shift_down, bool is_alt_down, int wheel_dir) { // convert moueevent to key code uint8_t mouseevent_code; C4KeyCodeEx mouseevent_keycode; bool is_down = true; switch (mouseevent) { case C4MC_Button_None: mouseevent_code = KEY_MOUSE_Move; break; case C4MC_Button_LeftUp: is_down = false; // nobreak case C4MC_Button_LeftDown: mouseevent_code = KEY_MOUSE_ButtonLeft; break; case C4MC_Button_RightUp: is_down = false; // nobreak case C4MC_Button_RightDown: mouseevent_code = KEY_MOUSE_ButtonRight; break; case C4MC_Button_LeftDouble: mouseevent_code = KEY_MOUSE_ButtonLeftDouble; break; case C4MC_Button_RightDouble: mouseevent_code = KEY_MOUSE_ButtonRightDouble; break; case C4MC_Button_MiddleUp: is_down = false; // nobreak case C4MC_Button_MiddleDown: mouseevent_code = KEY_MOUSE_ButtonMiddle; break; case C4MC_Button_Wheel: if (!wheel_dir) return false; mouseevent_code = (wheel_dir > 0) ? KEY_MOUSE_Wheel1Up : KEY_MOUSE_Wheel1Down; break; default: assert(false); return false; } // compose keycode if (is_ctrl_down) mouseevent_keycode.dwShift |= KEYS_Control; if (is_shift_down) mouseevent_keycode.dwShift |= KEYS_Shift; if (is_alt_down) mouseevent_keycode.dwShift |= KEYS_Alt; mouseevent_keycode.Key = KEY_Mouse(mouse_id, mouseevent_code); // first, try processing it as GUI mouse event. if not assigned, process as Game mous event // TODO: May route this through Game.DoKeyboardInput instead - would allow assignment of mouse events in CustomConfig // and would get rid of the Game.KeyboardInput.SetLastKeyExtraData-hack C4KeyEventData mouseevent_data; mouseevent_data.iStrength = 100*is_down; // TODO: May get pressure from tablet here mouseevent_data.vp_x = uint32_t(gui_x); mouseevent_data.vp_y = uint32_t(gui_y); mouseevent_data.game_x = uint32_t(game_x); mouseevent_data.game_y = uint32_t(game_y); Game.KeyboardInput.SetLastKeyExtraData(mouseevent_data); // ProcessKeyDown/Up queries it from there... bool result; if (is_down) result = ProcessKeyDown(mouseevent_keycode, mouseevent_keycode); else result = ProcessKeyUp(mouseevent_keycode, mouseevent_keycode); return result; } bool C4PlayerControl::GetCurrentPlayerCursorPos(int32_t *x_out, int32_t *y_out, int32_t *game_x_out, int32_t *game_y_out) { // prefer mouse position if this is a mouse control if (pControlSet && pControlSet->HasMouse()) { if (MouseControl.GetLastGUIPos(x_out, y_out)) return true; // if getting the mouse position failed, better fall back to cursor pos } // no mouse position known. Use cursor. C4Player *plr = Players.Get(iPlr); if (!plr) return false; C4Object *cursor_obj = plr->Cursor; if (!cursor_obj) return false; C4Viewport *vp = ::Viewports.GetViewport(iPlr); if (!vp) return false; int32_t game_x = cursor_obj->GetX(), game_y=cursor_obj->GetY(); *game_x_out = game_x; *game_y_out = game_y; // game coordinate to screen coordinates... float screen_x = (float(game_x) - vp->last_game_draw_cgo.TargetX - vp->last_game_draw_cgo.X) * vp->GetZoom(); float screen_y = (float(game_y) - vp->last_game_draw_cgo.TargetY - vp->last_game_draw_cgo.Y) * vp->GetZoom(); // ...and screen coordinates to GUI coordinates (might push this into a helper function of C4Viewport?) float gui_x = (screen_x - vp->last_game_draw_cgo.X) / C4GUI::GetZoom() + vp->last_game_draw_cgo.X; float gui_y = (screen_y - vp->last_game_draw_cgo.Y) / C4GUI::GetZoom() + vp->last_game_draw_cgo.Y; *x_out = int32_t(gui_x); *y_out = int32_t(gui_y); return true; } void C4PlayerControl::PrepareInput() { if (IsCursorPosRequested) { int32_t x, y, game_x, game_y; // add current cursor pos in GUI coordinates to input if (GetCurrentPlayerCursorPos(&x, &y, &game_x, &game_y)) { // CON_CursorPos might not have been defined in definition file if (ControlDefs.InternalCons.CON_CursorPos != CON_None) { C4KeyEventData ev; ev.iStrength = 0; ev.vp_x = x; ev.vp_y = y; ev.game_x = game_x; ev.game_y = game_y; C4ControlPlayerControl *pControlPacket = new C4ControlPlayerControl(iPlr, CONS_Down, ev); pControlPacket->AddControl(ControlDefs.InternalCons.CON_CursorPos, C4PlayerControlAssignment::CTM_Default); // make sure it's added at head, because controls that have SendCursorPos=1 set will follow, which will rely // on the cursor pos being known Game.Input.AddHead(CID_PlrControl, pControlPacket); } } else { // no cursor is known (e.g.: Cursor Clonk dead, etc.). Don't create a control. // Script will probably fall back to last known cursor pos } IsCursorPosRequested = false; } }