forked from Mirrors/openclonk
1500 lines
57 KiB
C++
1500 lines
57 KiB
C++
/*
|
|
* 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 <algorithm>
|
|
|
|
#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<CoordinateSpace> CoordSpaceNames[] =
|
|
{
|
|
{ "Game", COS_Game },
|
|
{ "Viewport", COS_Viewport },
|
|
{ NULL, COS_Game }
|
|
};
|
|
pComp->Value(mkNamingAdapt(mkEnumAdapt<CoordinateSpace, int32_t>(eCoordSpace, CoordSpaceNames), "CoordinateSpace", COS_Game));
|
|
pComp->Value(mkNamingAdapt(fSendCursorPos, "SendCursorPos", false));
|
|
const StdEnumEntry<Actions> 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<Actions, int32_t>(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<int32_t> 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<StdCompilerINIRead>(*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<StdCompilerINIWrite>(*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<uint8_t>(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<uint8_t>(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; i<ControlDefs.GetCount(); ++i)
|
|
{
|
|
const CSync::ControlDownState *pControlDownState = Sync.GetControlDownState(i);
|
|
if (pControlDownState && pControlDownState->IsDown())
|
|
{
|
|
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<C4PlayerControl, C4KeyCodeEx>(*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;
|
|
}
|
|
}
|