Files
sbox-public/engine/Sandbox.Engine/Systems/Input/Bindings/BindCollection.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

267 lines
6.5 KiB
C#

namespace Sandbox.Engine;
/// <summary>
///
/// A collection of action binds.
///
/// BindCollection
/// - Action: attack1
/// - Slot0: mouse1
/// - Action: selectall
/// - Slot0: ctrl + a
///
/// The bind collection can be saved and loaded from disk via the BindSaveConfig class.
///
/// The bind collection can have a base collection which it will fall back to if it contains
/// the same binds. This allows us to have a "common" collection which can be shared between
/// all games, but can also let the games + users to override those binds if they choose.
///
/// </summary>
public class BindCollection
{
/// <summary>
/// The base collection. Game binds have this set to the common binds.
/// </summary>
public BindCollection Base { get; set; }
/// <summary>
/// Will be either "common" or the ident of the current game.
/// </summary>
public string CollectionName { get; set; }
/// <summary>
/// The location of the config file to load from in EngineFileSystem.Config
/// </summary>
public string ConfigPath { get; set; }
/// <summary>
/// The actual collection of binds.
/// </summary>
public CaseInsensitiveDictionary<ActionBind> Actions = new CaseInsensitiveDictionary<ActionBind>();
/// <summary>
/// Creates a collection and tries to load it from disk.
/// </summary>
public BindCollection( string name )
{
CollectionName = name;
ConfigPath = $"/input/{CollectionName}.json";
FillDefaultCommonInputs();
BindSaveConfig.Load( ConfigPath, this );
}
/// <summary>
/// If we're the common collection and have no binds (because we haven't
/// been able to load a customized config from disk) then we'll fill in
/// the defaults based on Input.CommonInputs.
/// </summary>
void FillDefaultCommonInputs()
{
if ( CollectionName != "common" ) return;
if ( Actions.Count != 0 ) return;
foreach ( var input in Input.CommonInputs )
{
if ( !string.IsNullOrWhiteSpace( input.KeyboardCode ) )
{
var bind = GetBind( input.Name );
bind.Set( 0, input.KeyboardCode );
bind.Default = input.KeyboardCode;
}
}
}
/// <summary>
/// Get the bind, create if it doesn't exist
/// </summary>
public ActionBind GetBind( string actionName, bool create = true )
{
if ( !Actions.TryGetValue( actionName, out var bind ) )
{
if ( create )
{
bind = new ActionBind() { Name = actionName };
Actions[actionName] = bind;
}
}
return bind;
}
/// <summary>
/// Set the bind value for this action. This will overwrite what's in this slot.
/// </summary>
public ActionBind Set( string actionName, int slot, string buttonName )
{
var bind = GetBind( actionName );
bind.Set( slot, buttonName );
return bind;
}
/// <summary>
/// Get the bind value at this slot
/// </summary>
public string Get( string actionName, int slot )
{
Actions.TryGetValue( actionName, out var bind );
if ( bind == null ) return null;
var entry = bind.Get( slot );
return entry.FullString;
}
/// <summary>
/// Save the collection to disk
/// </summary>
public void SaveToDisk()
{
BindSaveConfig.Save( ConfigPath, this );
}
/// <summary>
/// Reset the collection to the default values.
/// </summary>
internal void ResetToDefaults()
{
if ( CollectionName == "common" )
{
Actions.Clear();
FillDefaultCommonInputs();
}
foreach ( var a in Actions )
{
a.Value.Set( 0, a.Value.Default );
a.Value.Set( 1, null );
}
}
/// <summary>
/// Enumerate all actions that contain this button
/// </summary>
internal IEnumerable<ActionBind> EnumerateWithButton( string button )
{
//
// No actions set (older project) fall back to use common binds
//
if ( Actions.Count == 0 )
{
if ( Base == null )
yield break;
foreach ( var action in Base.EnumerateWithButton( button ) )
{
yield return action;
}
yield break;
}
foreach ( var action in Actions )
{
//
// If this is a common action then check for the action set in the common collection instead.
//
// I'm commenting this out for now since it's not letting people override the default binds
// - Carson
//if ( Base != null && action.Value.IsCommon && Base.Actions.TryGetValue( action.Key, out var common ) )
//{
// if ( common.HasButton( button ) )
// yield return common;
// continue;
//}
if ( !action.Value.HasButton( button ) ) continue;
yield return action.Value;
}
}
/// <summary>
/// The action list has changed, we just got the config from the server.
/// Here we'll clear the actions, add all of the defaults from the new config
/// and then load the user config if it exists.
/// </summary>
internal void UpdateActions( List<InputAction> inputActions )
{
Actions.Clear();
foreach ( var action in inputActions )
{
// if this doesn't have a default bind, skip it
if ( string.IsNullOrEmpty( action.KeyboardCode ) ) continue;
var bind = GetBind( action.Name );
bind.Set( 0, action.KeyboardCode );
bind.Default = action.KeyboardCode;
bind.IsCommon = Base?.Actions.ContainsKey( action.Name ) ?? false;
}
BindSaveConfig.Load( ConfigPath, this );
}
public class ActionBind
{
public string Name { get; set; }
public BindEntry[] Slots = new BindEntry[2];
/// <summary>
/// If this is set then we want to read the value from the base collection
/// </summary>
public bool IsCommon { get; set; }
public string Default { get; set; }
internal BindEntry Get( int slot )
{
return Slots[slot];
}
internal void Set( int slot, string buttonString )
{
Slots[slot].Set( buttonString );
IsCommon = false;
}
internal bool HasButton( string button )
{
return Slots[0].HasButton( button ) || Slots[1].HasButton( button );
}
internal bool Test( string button, HashSet<string> activeButtons )
{
return Slots[0].Test( button, activeButtons ) || Slots[1].Test( button, activeButtons );
}
}
public struct BindEntry
{
public string FullString { get; set; }
HashSet<string> ButtonList;
internal bool HasButton( string button )
{
return ButtonList?.Contains( button ) ?? false;
}
internal void Set( string buttonString )
{
buttonString = buttonString ?? "";
FullString = buttonString;
ButtonList = FullString.Split( new[] { '+', ' ' }, StringSplitOptions.RemoveEmptyEntries ).ToHashSet( StringComparer.OrdinalIgnoreCase );
}
internal bool Test( string button, HashSet<string> activeButtons )
{
if ( ButtonList == null ) return false;
if ( !HasButton( button ) ) return false;
return ButtonList.IsSubsetOf( activeButtons );
}
}
}