Skip to content

Commit 809c296

Browse files
authored
Fixes (#4036) - Preserve user key bindings when unbinding resource keys (#4440)
Implement context-aware command binding methods
1 parent f333bab commit 809c296

File tree

7 files changed

+212
-18
lines changed

7 files changed

+212
-18
lines changed

Client/core/CKeyBinds.cpp

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,18 @@ void CKeyBinds::RemoveDeletedBinds()
550550
void CKeyBinds::ClearCommandsAndControls()
551551
{
552552
const auto predicate = [](const KeyBindPtr& bind) {
553-
return !bind->isBeingDeleted && bind->type != KeyBindType::FUNCTION && bind->type != KeyBindType::CONTROL_FUNCTION;
553+
if (bind->isBeingDeleted)
554+
return false;
555+
556+
if (bind->type == KeyBindType::COMMAND)
557+
{
558+
auto commandBind = static_cast<const CCommandBind*>(bind.get());
559+
// Only remove resource bindings, preserve user bindings
560+
return commandBind->context == BindingContext::RESOURCE;
561+
}
562+
563+
// Remove all control bindings (GTA_CONTROL)
564+
return bind->type == KeyBindType::GTA_CONTROL;
554565
};
555566
RemoveBinds(m_binds, !m_bProcessingKeyStroke, predicate);
556567
}
@@ -612,9 +623,11 @@ bool CKeyBinds::AddCommand(const char* szKey, const char* szCommand, const char*
612623
CCommandBind* pUserAddedBind = FindCommandMatch(NULL, szCommand, szArguments, szResource, szKey, true, bState, true, false);
613624
if (pUserAddedBind)
614625
{
615-
// Upgrade
626+
// Upgrade user binding to resource binding
616627
pUserAddedBind->wasCreatedByScript = true;
617628
pUserAddedBind->isReplacingScriptKey = true;
629+
pUserAddedBind->context = BindingContext::RESOURCE;
630+
pUserAddedBind->sourceResource = szResource;
618631
assert(pUserAddedBind->originalScriptKey == szKey);
619632
return true;
620633
}
@@ -631,13 +644,20 @@ bool CKeyBinds::AddCommand(const char* szKey, const char* szCommand, const char*
631644
if (szResource)
632645
{
633646
bind->resource = szResource;
647+
bind->sourceResource = szResource;
634648
bind->wasCreatedByScript = bScriptCreated;
649+
bind->context = BindingContext::RESOURCE;
635650

636651
if (bScriptCreated)
637652
bind->originalScriptKey = szKey;
638653
else if (szOriginalScriptKey)
639654
bind->originalScriptKey = szOriginalScriptKey; // Will wait for script to addcommand before doing replace
640655
}
656+
else
657+
{
658+
// User-created binding (via /bind command)
659+
bind->context = BindingContext::USER;
660+
}
641661

642662
m_binds.emplace_back(bind.release());
643663
return true;
@@ -2632,3 +2652,147 @@ bool CKeyBinds::TriggerKeyStrokeHandler(const SString& strKey, bool bState, bool
26322652
}
26332653
return true;
26342654
}
2655+
2656+
bool CKeyBinds::CommandExistsInContext(const char* key, const char* command, BindingContext context, bool checkState, bool state, const char* arguments, const char* resource)
2657+
{
2658+
if (!key || !command)
2659+
return false;
2660+
2661+
for (const KeyBindPtr& bind : m_binds)
2662+
{
2663+
if (bind->isBeingDeleted || bind->type != KeyBindType::COMMAND)
2664+
continue;
2665+
2666+
auto commandBind = static_cast<const CCommandBind*>(bind.get());
2667+
2668+
if (commandBind->context != context)
2669+
continue;
2670+
2671+
if (stricmp(commandBind->boundKey->szKey, key) != 0)
2672+
continue;
2673+
2674+
if (stricmp(commandBind->command.c_str(), command) != 0)
2675+
continue;
2676+
2677+
if (checkState && commandBind->triggerState != state)
2678+
continue;
2679+
2680+
if (arguments && commandBind->arguments != arguments)
2681+
continue;
2682+
2683+
if (resource && commandBind->resource != resource)
2684+
continue;
2685+
2686+
return true;
2687+
}
2688+
2689+
return false;
2690+
}
2691+
2692+
bool CKeyBinds::RemoveCommandFromContext(const char* key, const char* command, BindingContext context, bool checkState, bool state, const char* arguments, const char* resource)
2693+
{
2694+
if (!key || !command)
2695+
return false;
2696+
2697+
const auto predicate = [&](const KeyBindPtr& bind) {
2698+
if (bind->isBeingDeleted || bind->type != KeyBindType::COMMAND)
2699+
return false;
2700+
2701+
auto commandBind = static_cast<const CCommandBind*>(bind.get());
2702+
2703+
if (commandBind->context != context)
2704+
return false;
2705+
2706+
if (stricmp(commandBind->boundKey->szKey, key) != 0)
2707+
return false;
2708+
2709+
if (stricmp(commandBind->command.c_str(), command) != 0)
2710+
return false;
2711+
2712+
if (checkState && commandBind->triggerState != state)
2713+
return false;
2714+
2715+
if (arguments && commandBind->arguments != arguments)
2716+
return false;
2717+
2718+
if (resource && commandBind->resource != resource)
2719+
return false;
2720+
2721+
return true;
2722+
};
2723+
2724+
return RemoveBinds(m_binds, !m_bProcessingKeyStroke, predicate);
2725+
}
2726+
2727+
bool CKeyBinds::HasAnyBindingForKey(const char* key, bool checkState, bool state)
2728+
{
2729+
if (!key)
2730+
return false;
2731+
2732+
for (const KeyBindPtr& bind : m_binds)
2733+
{
2734+
if (bind->isBeingDeleted)
2735+
continue;
2736+
2737+
if (bind->type == KeyBindType::COMMAND)
2738+
{
2739+
auto commandBind = static_cast<const CCommandBind*>(bind.get());
2740+
if (stricmp(commandBind->boundKey->szKey, key) == 0)
2741+
{
2742+
if (!checkState || commandBind->triggerState == state)
2743+
return true;
2744+
}
2745+
}
2746+
else if (bind->type == KeyBindType::FUNCTION)
2747+
{
2748+
auto functionBind = static_cast<const CKeyFunctionBind*>(bind.get());
2749+
if (stricmp(functionBind->boundKey->szKey, key) == 0)
2750+
{
2751+
if (!checkState || functionBind->triggerState == state)
2752+
return true;
2753+
}
2754+
}
2755+
else if (bind->type == KeyBindType::CONTROL_FUNCTION)
2756+
{
2757+
auto controlBind = static_cast<const CControlFunctionBind*>(bind.get());
2758+
if (stricmp(controlBind->boundKey->szKey, key) == 0)
2759+
{
2760+
if (!checkState || controlBind->triggerState == state)
2761+
return true;
2762+
}
2763+
}
2764+
else if (bind->type == KeyBindType::GTA_CONTROL)
2765+
{
2766+
auto gtaBind = static_cast<const CGTAControlBind*>(bind.get());
2767+
if (stricmp(gtaBind->boundKey->szKey, key) == 0)
2768+
return true;
2769+
}
2770+
}
2771+
2772+
return false;
2773+
}
2774+
2775+
bool CKeyBinds::HasBindingInContext(const char* key, BindingContext context, bool checkState, bool state)
2776+
{
2777+
if (!key)
2778+
return false;
2779+
2780+
for (const KeyBindPtr& bind : m_binds)
2781+
{
2782+
if (bind->isBeingDeleted || bind->type != KeyBindType::COMMAND)
2783+
continue;
2784+
2785+
auto commandBind = static_cast<const CCommandBind*>(bind.get());
2786+
2787+
if (commandBind->context != context)
2788+
continue;
2789+
2790+
if (stricmp(commandBind->boundKey->szKey, key) != 0)
2791+
continue;
2792+
2793+
if (!checkState || commandBind->triggerState == state)
2794+
return true;
2795+
}
2796+
2797+
return false;
2798+
}

Client/core/CKeyBinds.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ class CKeyBinds final : public CKeyBindsInterface
7575
bool bCheckState, bool bState, bool bCheckScriptCreated, bool bScriptCreated);
7676
void SortCommandBinds();
7777

78+
// Context-aware binding methods
79+
bool CommandExistsInContext(const char* key, const char* command, BindingContext context, bool checkState = false, bool state = true, const char* arguments = NULL, const char* resource = NULL);
80+
bool RemoveCommandFromContext(const char* key, const char* command, BindingContext context, bool checkState = false, bool state = true, const char* arguments = NULL, const char* resource = NULL);
81+
bool HasAnyBindingForKey(const char* key, bool checkState = false, bool state = true);
82+
bool HasBindingInContext(const char* key, BindingContext context, bool checkState = false, bool state = true);
83+
7884
// Control-bind funcs
7985
bool AddGTAControl(const char* szKey, const char* szControl);
8086
bool AddGTAControl(const SBindableKey* pKey, SBindableGTAControl* pControl);

Client/mods/deathmatch/logic/CResource.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,23 @@ CResource::~CResource()
114114

115115
// Remove all keybinds on this VM
116116
g_pClientGame->GetScriptKeyBinds()->RemoveAllKeys(m_pLuaVM);
117-
g_pCore->GetKeyBinds()->SetAllCommandsActive(m_strResourceName, false);
117+
118+
// Remove all resource-specific command bindings while preserving user bindings
119+
CKeyBindsInterface* pKeyBinds = g_pCore->GetKeyBinds();
120+
pKeyBinds->SetAllCommandsActive(m_strResourceName, false);
121+
122+
// Additional cleanup: remove any remaining resource bindings that weren't caught by SetAllCommandsActive
123+
for (auto& bind : *pKeyBinds)
124+
{
125+
if (bind->type == KeyBindType::COMMAND)
126+
{
127+
auto commandBind = static_cast<CCommandBind*>(bind.get());
128+
if (commandBind->context == BindingContext::RESOURCE && commandBind->resource == m_strResourceName)
129+
{
130+
pKeyBinds->Remove(commandBind);
131+
}
132+
}
133+
}
118134

119135
// Destroy the txd root so all dff elements are deleted except those moved out
120136
g_pClientGame->GetElementDeleter()->DeleteRecursive(m_pResourceTXDRoot);

Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7253,27 +7253,16 @@ bool CStaticFunctionDefinitions::UnbindKey(const char* szKey, const char* szHitS
72537253
}
72547254
}
72557255

7256-
pBind = g_pCore->GetKeyBinds()->GetBindFromCommand(szCommandName, NULL, false, szKey, bCheckHitState, bHitState);
7257-
7256+
// Use context-aware removal to only remove resource bindings
72587257
if ((!stricmp(szHitState, "down") || !stricmp(szHitState, "both")) &&
7259-
pKeyBinds->SetCommandActive(szKey, szCommandName, bHitState, NULL, szResource, false, true, true))
7258+
pKeyBinds->RemoveCommandFromContext(szKey, szCommandName, BindingContext::RESOURCE, bCheckHitState, bHitState, NULL, szResource))
72607259
{
7261-
pKeyBinds->SetAllCommandsActive(szResource, false, szCommandName, bHitState, NULL, true, szKey);
7262-
7263-
if (pBind)
7264-
pKeyBinds->Remove(pBind);
7265-
72667260
bSuccess = true;
72677261
}
72687262
bHitState = false;
72697263
if ((!stricmp(szHitState, "up") || !stricmp(szHitState, "both")) &&
7270-
pKeyBinds->SetCommandActive(szKey, szCommandName, bHitState, NULL, szResource, false, true, true))
7264+
pKeyBinds->RemoveCommandFromContext(szKey, szCommandName, BindingContext::RESOURCE, bCheckHitState, bHitState, NULL, szResource))
72717265
{
7272-
pKeyBinds->SetAllCommandsActive(szResource, false, szCommandName, bHitState, NULL, true, szKey);
7273-
7274-
if (pBind)
7275-
pKeyBinds->Remove(pBind);
7276-
72777266
bSuccess = true;
72787267
}
72797268
}

Client/mods/deathmatch/logic/rpc/CInputRPCs.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,15 @@ void CInputRPCs::UnbindKey(NetBitStreamInterface& bitStream)
126126
const SBindableKey* pKey = pKeyBinds->GetBindableFromKey(szKey);
127127
if (pKey)
128128
{
129+
// Only remove server-side function bindings, preserve user command bindings
129130
pKeyBinds->RemoveFunction(szKey, CClientGame::StaticProcessServerKeyBind, true, bState);
130131
}
131132
else
132133
{
133134
SBindableGTAControl* pControl = pKeyBinds->GetBindableFromControl(szKey);
134135
if (pControl)
135136
{
137+
// Only remove server-side control function bindings, preserve user command bindings
136138
pKeyBinds->RemoveControlFunction(szKey, CClientGame::StaticProcessServerControlBind, true, bState);
137139
}
138140
}

Client/sdk/core/CKeyBindsInterface.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ class CKeyBindWithState : public CKeyBind
9494
bool triggerState{false}; // true == "down", false == "up"
9595
};
9696

97+
enum class BindingContext
98+
{
99+
USER, // Created by user via /bind command
100+
RESOURCE, // Created by resource via bindKey
101+
SYSTEM // Created by system/default
102+
};
103+
97104
class CCommandBind : public CKeyBindWithState
98105
{
99106
public:
@@ -104,8 +111,10 @@ class CCommandBind : public CKeyBindWithState
104111
std::string arguments;
105112
std::string resource;
106113
std::string originalScriptKey; // Original key set by script
114+
std::string sourceResource; // Resource that created this binding
107115
bool wasCreatedByScript{false};
108-
bool isReplacingScriptKey{false}; // true if script set key is not being used
116+
bool isReplacingScriptKey{false}; // true if script set key is not being used
117+
BindingContext context{BindingContext::USER}; // Context of this binding
109118
};
110119

111120
class CKeyFunctionBind : public CKeyBindWithState
@@ -174,6 +183,12 @@ class CKeyBindsInterface
174183
virtual void UserRemoveCommandBoundKey(CCommandBind* pBind) = 0;
175184
virtual CCommandBind* FindMatchingUpBind(CCommandBind* pBind) = 0;
176185

186+
// Context-aware binding methods
187+
virtual bool CommandExistsInContext(const char* key, const char* command, BindingContext context, bool checkState = false, bool state = true, const char* arguments = NULL, const char* resource = NULL) = 0;
188+
virtual bool RemoveCommandFromContext(const char* key, const char* command, BindingContext context, bool checkState = false, bool state = true, const char* arguments = NULL, const char* resource = NULL) = 0;
189+
virtual bool HasAnyBindingForKey(const char* key, bool checkState = false, bool state = true) = 0;
190+
virtual bool HasBindingInContext(const char* key, BindingContext context, bool checkState = false, bool state = true) = 0;
191+
177192
// Control-bind funcs
178193
virtual bool AddGTAControl(const char* szKey, const char* szControl) = 0;
179194
virtual bool AddGTAControl(const SBindableKey* pKey, SBindableGTAControl* pControl) = 0;

Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9173,6 +9173,8 @@ bool CStaticFunctionDefinitions::UnbindKey(CPlayer* pPlayer, const char* szKey,
91739173
(pControl && (bSuccess = pKeyBinds->RemoveControlFunction(szKey, pLuaMain, bCheckHitState, bHitState, iLuaFunction)) &&
91749174
!pKeyBinds->ControlFunctionExists(szKey, NULL, bCheckHitState, bHitState)))
91759175
{
9176+
// Only send UNBIND_KEY RPC if there are no more function bindings for this key
9177+
// This allows user command bindings to persist
91769178
unsigned char ucKeyLength = static_cast<unsigned char>(strlen(szKey));
91779179

91789180
CBitStream bitStream;

0 commit comments

Comments
 (0)