diff --git a/ClassicalSharp/2D/Screens/ChatScreen.cs b/ClassicalSharp/2D/Screens/ChatScreen.cs
index 186b2369b..1ae840b67 100644
--- a/ClassicalSharp/2D/Screens/ChatScreen.cs
+++ b/ClassicalSharp/2D/Screens/ChatScreen.cs
@@ -201,7 +201,7 @@ namespace ClassicalSharp {
return textInput.HandlesKeyPress( key );
}
- void OpenTextInputBar( string initialText ) {
+ public void OpenTextInputBar( string initialText ) {
if( !game.CursorVisible )
game.CursorVisible = true;
suppressNextPress = true;
diff --git a/ClassicalSharp/2D/Screens/NormalScreen.cs b/ClassicalSharp/2D/Screens/NormalScreen.cs
index 2faca2af9..8f95e4f1f 100644
--- a/ClassicalSharp/2D/Screens/NormalScreen.cs
+++ b/ClassicalSharp/2D/Screens/NormalScreen.cs
@@ -121,6 +121,10 @@ namespace ClassicalSharp {
return false;
}
+ public void OpenTextInputBar( string text ) {
+ chat.OpenTextInputBar( text );
+ }
+
public override bool HandlesMouseScroll( int delta ) {
return chat.HandlesMouseScroll( delta );
}
diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj
index 33a1fe1b1..8bdcdc851 100644
--- a/ClassicalSharp/ClassicalSharp.csproj
+++ b/ClassicalSharp/ClassicalSharp.csproj
@@ -138,6 +138,8 @@
+
+
@@ -234,6 +236,7 @@
+
diff --git a/ClassicalSharp/Entities/LocalPlayer.cs b/ClassicalSharp/Entities/LocalPlayer.cs
index a49e4ca8f..e213a5a28 100644
--- a/ClassicalSharp/Entities/LocalPlayer.cs
+++ b/ClassicalSharp/Entities/LocalPlayer.cs
@@ -264,7 +264,7 @@ namespace ClassicalSharp {
PitchDegrees = Utils.LerpAngle( lastPitch, nextPitch, t );
}
- internal void HandleKeyDown( Key key ) {
+ internal bool HandleKeyDown( Key key ) {
KeyMap keys = game.InputHandler.Keys;
if( key == keys[KeyMapping.Respawn] && canRespawn ) {
Vector3I p = Vector3I.Floor( SpawnPoint );
@@ -289,7 +289,10 @@ namespace ClassicalSharp {
flying = !flying;
} else if( key == keys[KeyMapping.NoClip] && canNoclip ) {
noClip = !noClip;
+ } else {
+ return false;
}
+ return true;
}
/// Calculates the jump velocity required such that when a client presses
diff --git a/ClassicalSharp/Game/InputHandler.cs b/ClassicalSharp/Game/InputHandler.cs
index cebae3100..e19f14eb3 100644
--- a/ClassicalSharp/Game/InputHandler.cs
+++ b/ClassicalSharp/Game/InputHandler.cs
@@ -1,11 +1,13 @@
using System;
using OpenTK;
using OpenTK.Input;
+using ClassicalSharp.Hotkeys;
namespace ClassicalSharp {
public sealed class InputHandler {
+ public HotkeyList Hotkeys = new HotkeyList();
Game game;
public InputHandler( Game game ) {
this.game = game;
@@ -268,12 +270,25 @@ namespace ClassicalSharp {
} else if( key == Keys[KeyMapping.Screenshot] ) {
game.screenshotRequested = true;
} else if( game.activeScreen == null || !game.activeScreen.HandlesKeyDown( key ) ) {
- if( !HandleBuiltinKey( key ) ) {
- game.LocalPlayer.HandleKeyDown( key );
+
+ if( !HandleBuiltinKey( key ) && !game.LocalPlayer.HandleKeyDown( key ) ) {
+ HandleHotkey( key );
}
}
}
+ void HandleHotkey( Key key ) {
+ string text;
+ bool more;
+
+ if( Hotkeys.IsHotkey( key, game.Keyboard, out text, out more ) ) {
+ if( !more )
+ game.Network.SendChat( text );
+ else if( game.activeScreen is NormalScreen )
+ ((NormalScreen)game.activeScreen).OpenTextInputBar( text );
+ }
+ }
+
MouseButtonEventArgs simArgs = new MouseButtonEventArgs();
bool SimulateMouse( Key key, bool pressed ) {
if( !(key == mapLeft || key == mapMiddle || key == mapRight ) )
diff --git a/ClassicalSharp/Hotkeys/HotkeyList.cs b/ClassicalSharp/Hotkeys/HotkeyList.cs
new file mode 100644
index 000000000..3758db829
--- /dev/null
+++ b/ClassicalSharp/Hotkeys/HotkeyList.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using OpenTK.Input;
+
+namespace ClassicalSharp.Hotkeys {
+
+ /// Maintains the list of hotkeys defined by the client and by SetTextHotkey packets.
+ public sealed class HotkeyList {
+
+ struct Hotkey {
+ public Key BaseKey;
+ public byte Flags; // ctrl 1, shift 2, alt 4
+ public string Text; // contents to copy directly into the input bar
+ public bool MoreInput; // whether the user is able to enter further input
+ }
+
+ List hotkeys = new List();
+
+ /// Creates or updates an existing hotkey with the given baseKey and modifier flags.
+ public void AddHotkey( Key baseKey, byte flags, string text, bool more ) {
+ if( !UpdateExistingHotkey( baseKey, flags, text, more ) )
+ AddNewHotkey( baseKey, flags, text, more );
+ }
+
+ /// Removes an existing hotkey with the given baseKey and modifier flags.
+ /// Whether a hotkey with the given baseKey and modifier flags was found
+ /// and subsequently removed.
+ public bool RemoveHotkey( Key baseKey, byte flags ) {
+ for( int i = 0; i < hotkeys.Count; i++ ) {
+ Hotkey hKey = hotkeys[i];
+ if( hKey.BaseKey == baseKey && hKey.Flags == flags ) {
+ hotkeys.RemoveAt( i );
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool UpdateExistingHotkey( Key baseKey, byte flags, string text, bool more ) {
+ for( int i = 0; i < hotkeys.Count; i++ ) {
+ Hotkey hKey = hotkeys[i];
+ if( hKey.BaseKey == baseKey && hKey.Flags == flags ) {
+ hKey.Text = text;
+ hKey.MoreInput = more;
+ hotkeys[i] = hKey;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void AddNewHotkey( Key baseKey, byte flags, string text, bool more ) {
+ Hotkey hotkey;
+ hotkey.BaseKey = baseKey;
+ hotkey.Flags = flags;
+ hotkey.Text = text;
+ hotkey.MoreInput = more;
+ hotkeys.Add( hotkey );
+ // sort so that hotkeys with largest modifiers are first
+ hotkeys.Sort( (a, b) => b.Flags.CompareTo( a.Flags ) );
+ }
+
+ /// Determines whether a hotkey is active based on the given key,
+ /// and the currently active control, alt, and shift modifiers
+ public bool IsHotkey( Key key, KeyboardDevice keyboard,
+ out string text, out bool moreInput ) {
+ byte flags = 0;
+ if( keyboard[Key.ControlLeft] || keyboard[Key.ControlRight] ) flags |= 1;
+ if( keyboard[Key.ShiftLeft] || keyboard[Key.ShiftRight] ) flags |= 2;
+ if( keyboard[Key.AltLeft] || keyboard[Key.AltRight] ) flags |= 4;
+
+ foreach( Hotkey hKey in hotkeys ) {
+ if( (hKey.Flags & flags) == hKey.Flags && hKey.BaseKey == key ) {
+ text = hKey.Text;
+ moreInput = hKey.MoreInput;
+ return true;
+ }
+ }
+
+ text = null;
+ moreInput = false;
+ return false;
+ }
+ }
+}
diff --git a/ClassicalSharp/Hotkeys/LwjglToKey.cs b/ClassicalSharp/Hotkeys/LwjglToKey.cs
new file mode 100644
index 000000000..b9a0f8711
--- /dev/null
+++ b/ClassicalSharp/Hotkeys/LwjglToKey.cs
@@ -0,0 +1,73 @@
+using System;
+using OpenTK.Input;
+
+namespace ClassicalSharp.Hotkeys {
+
+ /// Maps LWJGL keycodes to OpenTK keys.
+ public static class LwjglToKey {
+
+ public static Key[] Map = new Key[256];
+
+ static int curIndex = 0;
+ static void Add( Key key ) {
+ Map[curIndex++] = key;
+ }
+
+ static void Add( string value ) {
+ for( int i = 0; i < value.Length; i++ ) {
+ Add( (Key)(value[i] - 'A' + (int)Key.A) );
+ }
+ }
+
+ static void Skip( int amount ) {
+ curIndex += amount;
+ }
+
+ static LwjglToKey() {
+ Add( Key.Unknown ); Add( Key.Escape );
+ for( int i = 0; i < 9; i++ )
+ Add( (Key)(i + Key.Number1) );
+ Add( Key.Number0 ); Add( Key.Minus );
+ Add( Key.Plus ); Add( Key.BackSpace );
+ Add( Key.Tab ); Add( "QWERTYUIOP" );
+ Add( Key.BracketLeft ); Add( Key.BracketRight );
+ Add( Key.Enter ); Add( Key.ControlLeft );
+ Add( "ASDFGHJKL" ); Add( Key.Semicolon );
+ Add( Key.Quote ); Add( Key.Tilde );
+ Add( Key.ShiftLeft ); Add( Key.BackSlash );
+ Add( "ZXCVBNM" ); Add( Key.Comma );
+ Add( Key.Period ); Add( Key.Slash );
+ Add( Key.ShiftRight ); Skip( 1 ); // TODO: multiply
+ Add( Key.AltLeft ); Add( Key.Space );
+ Add( Key.CapsLock );
+ for( int i = 0; i < 10; i++ )
+ Add( (Key)(i + Key.F1) );
+ Add( Key.NumLock ); Add( Key.ScrollLock );
+ Add( Key.Number7 ); Add( Key.Number8 );
+ Add( Key.Number9 ); Add( Key.KeypadSubtract );
+ Add( Key.Number4 ); Add( Key.Number5 );
+ Add( Key.Number6 ); Add( Key.KeypadAdd );
+ Add( Key.Number1 ); Add( Key.Number2 );
+ Add( Key.Number3 ); Add( Key.Number0 );
+ Add( Key.KeypadDecimal ); Skip( 3 );
+ Add( Key.F11 ); Add( Key.F12 );
+ Skip( 11 );
+ for( int i = 0; i < 6; i++ )
+ Add( (Key)(i + Key.F13) );
+ Skip( 35 ); Add( Key.KeypadAdd );
+ Skip( 14 ); Add( Key.KeypadEnter );
+ Add( Key.ControlRight ); Skip( 23 );
+ Add( Key.KeypadDivide ); Skip( 2 );
+ Add( Key.AltRight ); Skip( 12 );
+ Add( Key.Pause ); Skip( 1 );
+ Add( Key.Home ); Add( Key.Up );
+ Add( Key.PageUp ); Skip( 1 );
+ Add( Key.Left ); Skip( 1 );
+ Add( Key.Right ); Skip( 1 );
+ Add( Key.End ); Add( Key.Down );
+ Add( Key.PageDown ); Add( Key.Insert );
+ Add( Key.Delete ); Skip( 7 );
+ Add( Key.WinLeft ); Add( Key.WinRight );
+ }
+ }
+}
\ No newline at end of file
diff --git a/ClassicalSharp/Network/NetworkProcessor.CPE.cs b/ClassicalSharp/Network/NetworkProcessor.CPE.cs
index fb6456249..398d95ae5 100644
--- a/ClassicalSharp/Network/NetworkProcessor.CPE.cs
+++ b/ClassicalSharp/Network/NetworkProcessor.CPE.cs
@@ -1,6 +1,7 @@
using System;
using ClassicalSharp.TexturePack;
using OpenTK.Input;
+using ClassicalSharp.Hotkeys;
namespace ClassicalSharp {
@@ -57,7 +58,7 @@ namespace ClassicalSharp {
"EmoteFix", "ClickDistance", "HeldBlock", "BlockPermissions",
"SelectionCuboid", "MessageTypes", "CustomBlocks", "EnvColors",
"HackControl", "EnvMapAppearance", "ExtPlayerList", "ChangeModel",
- "EnvWeatherType", "PlayerClick", // NOTE: There are no plans to support TextHotKey.
+ "EnvWeatherType", "PlayerClick", "TextHotKey",
};
void HandleCpeExtInfo() {
@@ -126,6 +127,27 @@ namespace ClassicalSharp {
game.Inventory.CanChangeHeldBlock = canChange;
}
+ void HandleCpeSetTextHotkey() {
+ string label = reader.ReadAsciiString();
+ string action = reader.ReadAsciiString();
+ int keyCode = reader.ReadInt32();
+ byte keyMods = reader.ReadUInt8();
+
+ if( keyCode < 0 || keyCode > 255 ) return;
+ Key key = LwjglToKey.Map[keyCode];
+ if( key == Key.Unknown ) return;
+
+ Console.WriteLine( "CPE Hotkey added: " + key + "," + keyMods + " : " + action );
+ if( action == "" ) {
+ game.InputHandler.Hotkeys.RemoveHotkey( key, keyMods );
+ } else if( action[action.Length - 1] == '\n' ) { // more input needed by user
+ action = action.Substring( 0, action.Length - 1 );
+ game.InputHandler.Hotkeys.AddHotkey( key, keyMods, action, true );
+ } else {
+ game.InputHandler.Hotkeys.AddHotkey( key, keyMods, action, false );
+ }
+ }
+
void HandleCpeExtAddPlayerName() {
short nameId = reader.ReadInt16();
string playerName = Utils.StripColours( reader.ReadAsciiString() );
diff --git a/ClassicalSharp/Network/NetworkProcessor.cs b/ClassicalSharp/Network/NetworkProcessor.cs
index a98c23196..09df91b0d 100644
--- a/ClassicalSharp/Network/NetworkProcessor.cs
+++ b/ClassicalSharp/Network/NetworkProcessor.cs
@@ -455,7 +455,7 @@ namespace ClassicalSharp {
HandleMessage, HandleKick, HandleSetPermission,
HandleCpeExtInfo, HandleCpeExtEntry, HandleCpeSetClickDistance,
- HandleCpeCustomBlockSupportLevel, HandleCpeHoldThis, null,
+ HandleCpeCustomBlockSupportLevel, HandleCpeHoldThis, HandleCpeSetTextHotkey,
HandleCpeExtAddPlayerName, HandleCpeExtAddEntity, HandleCpeExtRemovePlayerName,
HandleCpeEnvColours, HandleCpeMakeSelection, HandleCpeRemoveSelection,
HandleCpeSetBlockPermission, HandleCpeChangeModel, HandleCpeEnvSetMapApperance,