Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 166 additions & 2 deletions Client/core/CKeyBinds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,18 @@ void CKeyBinds::RemoveDeletedBinds()
void CKeyBinds::ClearCommandsAndControls()
{
const auto predicate = [](const KeyBindPtr& bind) {
return !bind->isBeingDeleted && bind->type != KeyBindType::FUNCTION && bind->type != KeyBindType::CONTROL_FUNCTION;
if (bind->isBeingDeleted)
return false;

if (bind->type == KeyBindType::COMMAND)
{
auto commandBind = static_cast<const CCommandBind*>(bind.get());
// Only remove resource bindings, preserve user bindings
return commandBind->context == BindingContext::RESOURCE;
}

// Remove all control bindings (GTA_CONTROL)
return bind->type == KeyBindType::GTA_CONTROL;
};
RemoveBinds(m_binds, !m_bProcessingKeyStroke, predicate);
}
Expand Down Expand Up @@ -612,9 +623,11 @@ bool CKeyBinds::AddCommand(const char* szKey, const char* szCommand, const char*
CCommandBind* pUserAddedBind = FindCommandMatch(NULL, szCommand, szArguments, szResource, szKey, true, bState, true, false);
if (pUserAddedBind)
{
// Upgrade
// Upgrade user binding to resource binding
pUserAddedBind->wasCreatedByScript = true;
pUserAddedBind->isReplacingScriptKey = true;
pUserAddedBind->context = BindingContext::RESOURCE;
pUserAddedBind->sourceResource = szResource;
assert(pUserAddedBind->originalScriptKey == szKey);
return true;
}
Expand All @@ -631,13 +644,20 @@ bool CKeyBinds::AddCommand(const char* szKey, const char* szCommand, const char*
if (szResource)
{
bind->resource = szResource;
bind->sourceResource = szResource;
bind->wasCreatedByScript = bScriptCreated;
bind->context = BindingContext::RESOURCE;

if (bScriptCreated)
bind->originalScriptKey = szKey;
else if (szOriginalScriptKey)
bind->originalScriptKey = szOriginalScriptKey; // Will wait for script to addcommand before doing replace
}
else
{
// User-created binding (via /bind command)
bind->context = BindingContext::USER;
}

m_binds.emplace_back(bind.release());
return true;
Expand Down Expand Up @@ -2632,3 +2652,147 @@ bool CKeyBinds::TriggerKeyStrokeHandler(const SString& strKey, bool bState, bool
}
return true;
}

bool CKeyBinds::CommandExistsInContext(const char* key, const char* command, BindingContext context, bool checkState, bool state, const char* arguments, const char* resource)
{
if (!key || !command)
return false;

for (const KeyBindPtr& bind : m_binds)
{
if (bind->isBeingDeleted || bind->type != KeyBindType::COMMAND)
continue;

auto commandBind = static_cast<const CCommandBind*>(bind.get());

if (commandBind->context != context)
continue;

if (stricmp(commandBind->boundKey->szKey, key) != 0)
continue;

if (stricmp(commandBind->command.c_str(), command) != 0)
continue;

if (checkState && commandBind->triggerState != state)
continue;

if (arguments && commandBind->arguments != arguments)
continue;

if (resource && commandBind->resource != resource)
continue;

return true;
}

return false;
}

bool CKeyBinds::RemoveCommandFromContext(const char* key, const char* command, BindingContext context, bool checkState, bool state, const char* arguments, const char* resource)
{
if (!key || !command)
return false;

const auto predicate = [&](const KeyBindPtr& bind) {
if (bind->isBeingDeleted || bind->type != KeyBindType::COMMAND)
return false;

auto commandBind = static_cast<const CCommandBind*>(bind.get());

if (commandBind->context != context)
return false;

if (stricmp(commandBind->boundKey->szKey, key) != 0)
return false;

if (stricmp(commandBind->command.c_str(), command) != 0)
return false;

if (checkState && commandBind->triggerState != state)
return false;

if (arguments && commandBind->arguments != arguments)
return false;

if (resource && commandBind->resource != resource)
return false;

return true;
};

return RemoveBinds(m_binds, !m_bProcessingKeyStroke, predicate);
}

bool CKeyBinds::HasAnyBindingForKey(const char* key, bool checkState, bool state)
{
if (!key)
return false;

for (const KeyBindPtr& bind : m_binds)
{
if (bind->isBeingDeleted)
continue;

if (bind->type == KeyBindType::COMMAND)
{
auto commandBind = static_cast<const CCommandBind*>(bind.get());
if (stricmp(commandBind->boundKey->szKey, key) == 0)
{
if (!checkState || commandBind->triggerState == state)
return true;
}
}
else if (bind->type == KeyBindType::FUNCTION)
{
auto functionBind = static_cast<const CKeyFunctionBind*>(bind.get());
if (stricmp(functionBind->boundKey->szKey, key) == 0)
{
if (!checkState || functionBind->triggerState == state)
return true;
}
}
else if (bind->type == KeyBindType::CONTROL_FUNCTION)
{
auto controlBind = static_cast<const CControlFunctionBind*>(bind.get());
if (stricmp(controlBind->boundKey->szKey, key) == 0)
{
if (!checkState || controlBind->triggerState == state)
return true;
}
}
else if (bind->type == KeyBindType::GTA_CONTROL)
{
auto gtaBind = static_cast<const CGTAControlBind*>(bind.get());
if (stricmp(gtaBind->boundKey->szKey, key) == 0)
return true;
}
}

return false;
}

bool CKeyBinds::HasBindingInContext(const char* key, BindingContext context, bool checkState, bool state)
{
if (!key)
return false;

for (const KeyBindPtr& bind : m_binds)
{
if (bind->isBeingDeleted || bind->type != KeyBindType::COMMAND)
continue;

auto commandBind = static_cast<const CCommandBind*>(bind.get());

if (commandBind->context != context)
continue;

if (stricmp(commandBind->boundKey->szKey, key) != 0)
continue;

if (!checkState || commandBind->triggerState == state)
return true;
}

return false;
}
6 changes: 6 additions & 0 deletions Client/core/CKeyBinds.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ class CKeyBinds final : public CKeyBindsInterface
bool bCheckState, bool bState, bool bCheckScriptCreated, bool bScriptCreated);
void SortCommandBinds();

// Context-aware binding methods
bool CommandExistsInContext(const char* key, const char* command, BindingContext context, bool checkState = false, bool state = true, const char* arguments = NULL, const char* resource = NULL);
bool RemoveCommandFromContext(const char* key, const char* command, BindingContext context, bool checkState = false, bool state = true, const char* arguments = NULL, const char* resource = NULL);
bool HasAnyBindingForKey(const char* key, bool checkState = false, bool state = true);
bool HasBindingInContext(const char* key, BindingContext context, bool checkState = false, bool state = true);

// Control-bind funcs
bool AddGTAControl(const char* szKey, const char* szControl);
bool AddGTAControl(const SBindableKey* pKey, SBindableGTAControl* pControl);
Expand Down
18 changes: 17 additions & 1 deletion Client/mods/deathmatch/logic/CResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,23 @@ CResource::~CResource()

// Remove all keybinds on this VM
g_pClientGame->GetScriptKeyBinds()->RemoveAllKeys(m_pLuaVM);
g_pCore->GetKeyBinds()->SetAllCommandsActive(m_strResourceName, false);

// Remove all resource-specific command bindings while preserving user bindings
CKeyBindsInterface* pKeyBinds = g_pCore->GetKeyBinds();
pKeyBinds->SetAllCommandsActive(m_strResourceName, false);

// Additional cleanup: remove any remaining resource bindings that weren't caught by SetAllCommandsActive
for (auto& bind : *pKeyBinds)
{
if (bind->type == KeyBindType::COMMAND)
{
auto commandBind = static_cast<CCommandBind*>(bind.get());
if (commandBind->context == BindingContext::RESOURCE && commandBind->resource == m_strResourceName)
{
pKeyBinds->Remove(commandBind);
}
}
}

// Destroy the txd root so all dff elements are deleted except those moved out
g_pClientGame->GetElementDeleter()->DeleteRecursive(m_pResourceTXDRoot);
Expand Down
17 changes: 3 additions & 14 deletions Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7219,27 +7219,16 @@ bool CStaticFunctionDefinitions::UnbindKey(const char* szKey, const char* szHitS
}
}

pBind = g_pCore->GetKeyBinds()->GetBindFromCommand(szCommandName, NULL, false, szKey, bCheckHitState, bHitState);

// Use context-aware removal to only remove resource bindings
if ((!stricmp(szHitState, "down") || !stricmp(szHitState, "both")) &&
pKeyBinds->SetCommandActive(szKey, szCommandName, bHitState, NULL, szResource, false, true, true))
pKeyBinds->RemoveCommandFromContext(szKey, szCommandName, BindingContext::RESOURCE, bCheckHitState, bHitState, NULL, szResource))
{
pKeyBinds->SetAllCommandsActive(szResource, false, szCommandName, bHitState, NULL, true, szKey);

if (pBind)
pKeyBinds->Remove(pBind);

bSuccess = true;
}
bHitState = false;
if ((!stricmp(szHitState, "up") || !stricmp(szHitState, "both")) &&
pKeyBinds->SetCommandActive(szKey, szCommandName, bHitState, NULL, szResource, false, true, true))
pKeyBinds->RemoveCommandFromContext(szKey, szCommandName, BindingContext::RESOURCE, bCheckHitState, bHitState, NULL, szResource))
{
pKeyBinds->SetAllCommandsActive(szResource, false, szCommandName, bHitState, NULL, true, szKey);

if (pBind)
pKeyBinds->Remove(pBind);

bSuccess = true;
}
}
Expand Down
2 changes: 2 additions & 0 deletions Client/mods/deathmatch/logic/rpc/CInputRPCs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,15 @@ void CInputRPCs::UnbindKey(NetBitStreamInterface& bitStream)
const SBindableKey* pKey = pKeyBinds->GetBindableFromKey(szKey);
if (pKey)
{
// Only remove server-side function bindings, preserve user command bindings
pKeyBinds->RemoveFunction(szKey, CClientGame::StaticProcessServerKeyBind, true, bState);
}
else
{
SBindableGTAControl* pControl = pKeyBinds->GetBindableFromControl(szKey);
if (pControl)
{
// Only remove server-side control function bindings, preserve user command bindings
pKeyBinds->RemoveControlFunction(szKey, CClientGame::StaticProcessServerControlBind, true, bState);
}
}
Expand Down
17 changes: 16 additions & 1 deletion Client/sdk/core/CKeyBindsInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ class CKeyBindWithState : public CKeyBind
bool triggerState{false}; // true == "down", false == "up"
};

enum class BindingContext
{
USER, // Created by user via /bind command
RESOURCE, // Created by resource via bindKey
SYSTEM // Created by system/default
};

class CCommandBind : public CKeyBindWithState
{
public:
Expand All @@ -104,8 +111,10 @@ class CCommandBind : public CKeyBindWithState
std::string arguments;
std::string resource;
std::string originalScriptKey; // Original key set by script
std::string sourceResource; // Resource that created this binding
bool wasCreatedByScript{false};
bool isReplacingScriptKey{false}; // true if script set key is not being used
bool isReplacingScriptKey{false}; // true if script set key is not being used
BindingContext context{BindingContext::USER}; // Context of this binding
};

class CKeyFunctionBind : public CKeyBindWithState
Expand Down Expand Up @@ -174,6 +183,12 @@ class CKeyBindsInterface
virtual void UserRemoveCommandBoundKey(CCommandBind* pBind) = 0;
virtual CCommandBind* FindMatchingUpBind(CCommandBind* pBind) = 0;

// Context-aware binding methods
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;
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;
virtual bool HasAnyBindingForKey(const char* key, bool checkState = false, bool state = true) = 0;
virtual bool HasBindingInContext(const char* key, BindingContext context, bool checkState = false, bool state = true) = 0;

// Control-bind funcs
virtual bool AddGTAControl(const char* szKey, const char* szControl) = 0;
virtual bool AddGTAControl(const SBindableKey* pKey, SBindableGTAControl* pControl) = 0;
Expand Down
2 changes: 2 additions & 0 deletions Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9110,6 +9110,8 @@ bool CStaticFunctionDefinitions::UnbindKey(CPlayer* pPlayer, const char* szKey,
(pControl && (bSuccess = pKeyBinds->RemoveControlFunction(szKey, pLuaMain, bCheckHitState, bHitState, iLuaFunction)) &&
!pKeyBinds->ControlFunctionExists(szKey, NULL, bCheckHitState, bHitState)))
{
// Only send UNBIND_KEY RPC if there are no more function bindings for this key
// This allows user command bindings to persist
unsigned char ucKeyLength = static_cast<unsigned char>(strlen(szKey));

CBitStream bitStream;
Expand Down