Skip to content

Commit ebb7401

Browse files
authored
feature: Allow providing default game settings along with your game (#2761)
* Allow providing default game settings along with your game * Determine default keybinds from default_settings.json. Fallback on hardcoded values. * Cleanup. Working and ready to merge. * Implementing Aru recommendations
1 parent 354bb23 commit ebb7401

File tree

3 files changed

+119
-52
lines changed

3 files changed

+119
-52
lines changed

Intersect.Client.Framework/Database/JsonDatabase.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ public JsonDatabase()
1919
_ = TryOpenOrCreate(_instancePath, out _instance);
2020
}
2121

22+
public JsonDatabase(string path)
23+
{
24+
_instancePath = path;
25+
_ = TryOpenOrCreate(_instancePath, out _instance);
26+
}
27+
2228
private static string GetInstancePath(ClientConfiguration instance)
2329
{
2430
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ".intersect",
@@ -38,10 +44,19 @@ private bool TryOpenOrCreate(string instancePath, [NotNullWhen(true)] out JObjec
3844
fileInfo.Directory.Create();
3945
}
4046

41-
using var streamWriter = fileInfo.CreateText();
42-
streamWriter.WriteLine("{}");
43-
instance = new JObject();
44-
return true;
47+
//Allow game devs to provide default settings
48+
var defaultSettingsJson = Path.Combine("resources", "default_settings.json");
49+
if (File.Exists(defaultSettingsJson))
50+
{
51+
File.Copy(defaultSettingsJson, instancePath);
52+
}
53+
else
54+
{
55+
using var streamWriter = fileInfo.CreateText();
56+
streamWriter.WriteLine("{}");
57+
instance = new JObject();
58+
return true;
59+
}
4560
}
4661

4762
using var streamReader = fileInfo.OpenText();
Lines changed: 91 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,13 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using Intersect.Client.Framework.Database;
23
using Intersect.Client.Framework.GenericClasses;
4+
using Newtonsoft.Json;
35

46
namespace Intersect.Client.Framework.Input;
57

68
internal sealed class BuiltinControlsProvider : IControlsProvider
79
{
8-
private readonly Dictionary<Control, ControlMapping> _defaultMappings = new()
9-
{
10-
{ Control.MoveUp, new ControlMapping(new ControlBinding(Keys.None, Keys.Up), new ControlBinding(Keys.None, Keys.W)) },
11-
{ Control.MoveDown, new ControlMapping(new ControlBinding(Keys.None, Keys.Down), new ControlBinding(Keys.None, Keys.S)) },
12-
{ Control.MoveLeft, new ControlMapping(new ControlBinding(Keys.None, Keys.Left), new ControlBinding(Keys.None, Keys.A)) },
13-
{ Control.MoveRight, new ControlMapping(new ControlBinding(Keys.None, Keys.Right), new ControlBinding(Keys.None, Keys.D)) },
14-
{ Control.AttackInteract, new ControlMapping(new ControlBinding(Keys.None, Keys.E), new ControlBinding(Keys.None, Keys.LButton)) },
15-
{ Control.Block, new ControlMapping(new ControlBinding(Keys.None, Keys.Q), new ControlBinding(Keys.None, Keys.RButton)) },
16-
{ Control.AutoTarget, new ControlMapping(new ControlBinding(Keys.None, Keys.Tab), ControlBinding.Default) },
17-
{ Control.HoldToSoftRetargetOnSelfCast, new ControlMapping(new ControlBinding(Keys.None, Keys.LMenu), ControlBinding.Default) },
18-
{ Control.ToggleAutoSoftRetargetOnSelfCast, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
19-
{ Control.PickUp, new ControlMapping(new ControlBinding(Keys.None, Keys.Space), ControlBinding.Default) },
20-
{ Control.Enter, new ControlMapping(new ControlBinding(Keys.None, Keys.Enter), ControlBinding.Default) },
21-
{ Control.Screenshot, new ControlMapping(new ControlBinding(Keys.None, Keys.F12), ControlBinding.Default) },
22-
{ Control.OpenMenu, new ControlMapping(new ControlBinding(Keys.None, Keys.Escape), ControlBinding.Default) },
23-
{ Control.OpenInventory, new ControlMapping(new ControlBinding(Keys.None, Keys.I), ControlBinding.Default) },
24-
{ Control.OpenQuests, new ControlMapping(new ControlBinding(Keys.None, Keys.L), ControlBinding.Default) },
25-
{ Control.OpenCharacterInfo, new ControlMapping(new ControlBinding(Keys.None, Keys.C), ControlBinding.Default) },
26-
{ Control.OpenParties, new ControlMapping(new ControlBinding(Keys.None, Keys.P), ControlBinding.Default) },
27-
{ Control.OpenSpells, new ControlMapping(new ControlBinding(Keys.None, Keys.K), ControlBinding.Default) },
28-
{ Control.OpenFriends, new ControlMapping(new ControlBinding(Keys.None, Keys.F), ControlBinding.Default) },
29-
{ Control.OpenGuild, new ControlMapping(new ControlBinding(Keys.None, Keys.G), ControlBinding.Default) },
30-
{ Control.OpenSettings, new ControlMapping(new ControlBinding(Keys.None, Keys.O), ControlBinding.Default) },
31-
{ Control.OpenDebugger, new ControlMapping(new ControlBinding(Keys.None, Keys.F2), ControlBinding.Default) },
32-
{ Control.OpenAdminPanel, new ControlMapping(new ControlBinding(Keys.None, Keys.Insert), ControlBinding.Default) },
33-
{ Control.ToggleGui, new ControlMapping(new ControlBinding(Keys.None, Keys.F11), ControlBinding.Default) },
34-
{ Control.TurnAround, new ControlMapping(new ControlBinding(Keys.None, Keys.Control), ControlBinding.Default) },
35-
{ Control.ToggleZoomIn, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
36-
{ Control.ToggleZoomOut, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
37-
{ Control.HoldToZoomIn, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
38-
{ Control.HoldToZoomOut, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
39-
{ Control.ToggleFullscreen, new ControlMapping(new ControlBinding(Keys.Alt, Keys.Enter), ControlBinding.Default) },
40-
41-
// Hotkeys should be at the end of the list
42-
{ Control.Hotkey1, new ControlMapping(new ControlBinding(Keys.None, Keys.D1), ControlBinding.Default) },
43-
{ Control.Hotkey2, new ControlMapping(new ControlBinding(Keys.None, Keys.D2), ControlBinding.Default) },
44-
{ Control.Hotkey3, new ControlMapping(new ControlBinding(Keys.None, Keys.D3), ControlBinding.Default) },
45-
{ Control.Hotkey4, new ControlMapping(new ControlBinding(Keys.None, Keys.D4), ControlBinding.Default) },
46-
{ Control.Hotkey5, new ControlMapping(new ControlBinding(Keys.None, Keys.D5), ControlBinding.Default) },
47-
{ Control.Hotkey6, new ControlMapping(new ControlBinding(Keys.None, Keys.D6), ControlBinding.Default) },
48-
{ Control.Hotkey7, new ControlMapping(new ControlBinding(Keys.None, Keys.D7), ControlBinding.Default) },
49-
{ Control.Hotkey8, new ControlMapping(new ControlBinding(Keys.None, Keys.D8), ControlBinding.Default) },
50-
{ Control.Hotkey9, new ControlMapping(new ControlBinding(Keys.None, Keys.D9), ControlBinding.Default) },
51-
{ Control.Hotkey10, new ControlMapping(new ControlBinding(Keys.None, Keys.D0), ControlBinding.Default) },
52-
};
10+
private readonly Dictionary<Control, ControlMapping> _defaultMappings = LoadDefaultMappings();
5311

5412
public Control[] Controls { get; } = Enum.GetValues<Control>().Where(control => control.IsValid()).ToArray();
5513

@@ -67,5 +25,93 @@ public bool TryGetDefaultMapping(Control control, [NotNullWhen(true)] out Contro
6725

6826
public void ReloadFromOptions(Options? options)
6927
{
28+
// Builtin controls don't change based on options
29+
// Defaults are loaded once from default_settings.json at initialization
30+
}
31+
32+
/// <summary>
33+
/// Loads default mappings by starting with hardcoded defaults and then merging overrides from default_settings.json if present.
34+
/// </summary>
35+
public static Dictionary<Control, ControlMapping> LoadDefaultMappings()
36+
{
37+
// Start with all hardcoded defaults
38+
var defaults = GetHardcodedDefaults();
39+
40+
// Load default controls from default_settings.json
41+
var defaultSettingsPath = Path.Combine("resources", "default_settings.json");
42+
if (File.Exists(defaultSettingsPath))
43+
{
44+
var db = new JsonDatabase(defaultSettingsPath);
45+
46+
foreach (var control in defaults.Keys)
47+
{
48+
var controlId = control.GetControlId();
49+
var bindings = new List<ControlBinding>();
50+
51+
if (db.HasPreference($"{controlId}_binding0") && db.HasPreference($"{controlId}_binding1"))
52+
{
53+
var binding1 = JsonConvert.DeserializeObject<ControlBinding>(db.LoadPreference($"{controlId}_binding0"));
54+
var binding2 = JsonConvert.DeserializeObject<ControlBinding>(db.LoadPreference($"{controlId}_binding1"));
55+
if (binding1 != null && binding2 != null)
56+
{
57+
defaults[control] = new ControlMapping(binding1, binding2);
58+
}
59+
}
60+
}
61+
}
62+
63+
return defaults;
64+
}
65+
66+
/// <summary>
67+
/// Returns the hardcoded default control mappings.
68+
/// </summary>
69+
private static Dictionary<Control, ControlMapping> GetHardcodedDefaults()
70+
{
71+
return new Dictionary<Control, ControlMapping>
72+
{
73+
{ Control.MoveUp, new ControlMapping(new ControlBinding(Keys.None, Keys.Up), new ControlBinding(Keys.None, Keys.W)) },
74+
{ Control.MoveDown, new ControlMapping(new ControlBinding(Keys.None, Keys.Down), new ControlBinding(Keys.None, Keys.S)) },
75+
{ Control.MoveLeft, new ControlMapping(new ControlBinding(Keys.None, Keys.Left), new ControlBinding(Keys.None, Keys.A)) },
76+
{ Control.MoveRight, new ControlMapping(new ControlBinding(Keys.None, Keys.Right), new ControlBinding(Keys.None, Keys.D)) },
77+
{ Control.AttackInteract, new ControlMapping(new ControlBinding(Keys.None, Keys.E), new ControlBinding(Keys.None, Keys.LButton)) },
78+
{ Control.Block, new ControlMapping(new ControlBinding(Keys.None, Keys.Q), new ControlBinding(Keys.None, Keys.RButton)) },
79+
{ Control.AutoTarget, new ControlMapping(new ControlBinding(Keys.None, Keys.Tab), ControlBinding.Default) },
80+
{ Control.HoldToSoftRetargetOnSelfCast, new ControlMapping(new ControlBinding(Keys.None, Keys.LMenu), ControlBinding.Default) },
81+
{ Control.ToggleAutoSoftRetargetOnSelfCast, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
82+
{ Control.PickUp, new ControlMapping(new ControlBinding(Keys.None, Keys.Space), ControlBinding.Default) },
83+
{ Control.Enter, new ControlMapping(new ControlBinding(Keys.None, Keys.Enter), ControlBinding.Default) },
84+
{ Control.Screenshot, new ControlMapping(new ControlBinding(Keys.None, Keys.F12), ControlBinding.Default) },
85+
{ Control.OpenMenu, new ControlMapping(new ControlBinding(Keys.None, Keys.Escape), ControlBinding.Default) },
86+
{ Control.OpenInventory, new ControlMapping(new ControlBinding(Keys.None, Keys.I), ControlBinding.Default) },
87+
{ Control.OpenQuests, new ControlMapping(new ControlBinding(Keys.None, Keys.L), ControlBinding.Default) },
88+
{ Control.OpenCharacterInfo, new ControlMapping(new ControlBinding(Keys.None, Keys.C), ControlBinding.Default) },
89+
{ Control.OpenParties, new ControlMapping(new ControlBinding(Keys.None, Keys.P), ControlBinding.Default) },
90+
{ Control.OpenSpells, new ControlMapping(new ControlBinding(Keys.None, Keys.K), ControlBinding.Default) },
91+
{ Control.OpenFriends, new ControlMapping(new ControlBinding(Keys.None, Keys.F), ControlBinding.Default) },
92+
{ Control.OpenGuild, new ControlMapping(new ControlBinding(Keys.None, Keys.G), ControlBinding.Default) },
93+
{ Control.OpenSettings, new ControlMapping(new ControlBinding(Keys.None, Keys.O), ControlBinding.Default) },
94+
{ Control.OpenDebugger, new ControlMapping(new ControlBinding(Keys.None, Keys.F2), ControlBinding.Default) },
95+
{ Control.OpenAdminPanel, new ControlMapping(new ControlBinding(Keys.None, Keys.Insert), ControlBinding.Default) },
96+
{ Control.ToggleGui, new ControlMapping(new ControlBinding(Keys.None, Keys.F11), ControlBinding.Default) },
97+
{ Control.TurnAround, new ControlMapping(new ControlBinding(Keys.None, Keys.Control), ControlBinding.Default) },
98+
{ Control.ToggleZoomIn, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
99+
{ Control.ToggleZoomOut, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
100+
{ Control.HoldToZoomIn, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
101+
{ Control.HoldToZoomOut, new ControlMapping(ControlBinding.Default, ControlBinding.Default) },
102+
{ Control.ToggleFullscreen, new ControlMapping(new ControlBinding(Keys.Alt, Keys.Enter), ControlBinding.Default) },
103+
104+
// Hotkeys should be at the end of the list
105+
{ Control.Hotkey1, new ControlMapping(new ControlBinding(Keys.None, Keys.D1), ControlBinding.Default) },
106+
{ Control.Hotkey2, new ControlMapping(new ControlBinding(Keys.None, Keys.D2), ControlBinding.Default) },
107+
{ Control.Hotkey3, new ControlMapping(new ControlBinding(Keys.None, Keys.D3), ControlBinding.Default) },
108+
{ Control.Hotkey4, new ControlMapping(new ControlBinding(Keys.None, Keys.D4), ControlBinding.Default) },
109+
{ Control.Hotkey5, new ControlMapping(new ControlBinding(Keys.None, Keys.D5), ControlBinding.Default) },
110+
{ Control.Hotkey6, new ControlMapping(new ControlBinding(Keys.None, Keys.D6), ControlBinding.Default) },
111+
{ Control.Hotkey7, new ControlMapping(new ControlBinding(Keys.None, Keys.D7), ControlBinding.Default) },
112+
{ Control.Hotkey8, new ControlMapping(new ControlBinding(Keys.None, Keys.D8), ControlBinding.Default) },
113+
{ Control.Hotkey9, new ControlMapping(new ControlBinding(Keys.None, Keys.D9), ControlBinding.Default) },
114+
{ Control.Hotkey10, new ControlMapping(new ControlBinding(Keys.None, Keys.D0), ControlBinding.Default) },
115+
};
70116
}
71117
}

Intersect.Client.Framework/Input/HotkeyControlsProvider.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ namespace Intersect.Client.Framework.Input;
55

66
internal sealed class HotkeyControlsProvider : IControlsProvider
77
{
8+
private Dictionary<Control, ControlMapping>? _defaultMappings = null;
9+
810
public HotkeyControlsProvider() => Controls = ControlsFromOptions(Options.Instance);
911

1012
public Control[] Controls { get; private set; }
1113

1214
public bool TryGetDefaultMapping(Control control, [NotNullWhen(true)] out ControlMapping? defaultMapping)
1315
{
14-
defaultMapping = null;
15-
return false;
16+
_defaultMappings ??= BuiltinControlsProvider.LoadDefaultMappings();
17+
return _defaultMappings.TryGetValue(control, out defaultMapping);
1618
}
1719

1820
private static Control[] ControlsFromOptions(Options? options) =>
@@ -21,5 +23,9 @@ private static Control[] ControlsFromOptions(Options? options) =>
2123
.Select(hotkeyValue => Control.HotkeyOffset + hotkeyValue)
2224
.ToArray();
2325

24-
public void ReloadFromOptions(Options? options) => Controls = ControlsFromOptions(options);
26+
public void ReloadFromOptions(Options? options)
27+
{
28+
Controls = ControlsFromOptions(options);
29+
_defaultMappings = null; // Force reload with new hotbar slot count
30+
}
2531
}

0 commit comments

Comments
 (0)