From a9406906cb0fbb5005baeaa1aacbbe47aa9058ba Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Mon, 26 Oct 2015 10:46:48 +1100 Subject: [PATCH] Add hotkey gui, workaround MCDzienny servers adding a + for classicube.net accounts. --- ClassicalSharp/2D/Screens/ClickableScreen.cs | 48 +++ ClassicalSharp/2D/Screens/FilesScreen.cs | 28 +- ClassicalSharp/2D/Screens/HotkeyScreen.cs | 310 ++++++++++++++++++ .../2D/Screens/Menu/EnvSettingsScreen.cs | 2 +- .../2D/Screens/Menu/KeyBindingsScreen.cs | 18 +- .../2D/Screens/Menu/LoadLevelScreen.cs | 6 +- .../2D/Screens/Menu/MenuInputScreen.cs | 22 +- ClassicalSharp/2D/Screens/Menu/MenuScreen.cs | 32 +- .../2D/Screens/Menu/OptionsScreen.cs | 2 +- ClassicalSharp/2D/Screens/Menu/PauseScreen.cs | 37 +-- .../2D/Screens/Menu/SaveLevelScreen.cs | 2 +- .../2D/Screens/Menu/TexturePackScreen.cs | 6 +- ClassicalSharp/2D/Widgets/ButtonWidget.cs | 10 +- .../2D/Widgets/Menu/MenuInputValidator.cs | 17 + ClassicalSharp/2D/Widgets/Widget.cs | 6 + ClassicalSharp/ClassicalSharp.csproj | 2 + ClassicalSharp/Commands/CommandManager.cs | 1 + ClassicalSharp/Commands/DefaultCommands.cs | 41 +++ ClassicalSharp/Hotkeys/HotkeyList.cs | 39 +-- .../Network/NetworkProcessor.CPE.cs | 11 +- ClassicalSharp/Network/NetworkProcessor.cs | 5 +- ClassicalSharp/Utils/TextureRectangle.cs | 12 +- ClassicalSharp/Utils/Utils.cs | 10 + Launcher2/Gui/Screens/ResourcesScreen.cs | 2 +- 24 files changed, 526 insertions(+), 143 deletions(-) create mode 100644 ClassicalSharp/2D/Screens/ClickableScreen.cs create mode 100644 ClassicalSharp/2D/Screens/HotkeyScreen.cs diff --git a/ClassicalSharp/2D/Screens/ClickableScreen.cs b/ClassicalSharp/2D/Screens/ClickableScreen.cs new file mode 100644 index 000000000..8ff9252a7 --- /dev/null +++ b/ClassicalSharp/2D/Screens/ClickableScreen.cs @@ -0,0 +1,48 @@ +using System; +using System.Drawing; +using OpenTK.Input; + +namespace ClassicalSharp { + + public abstract class ClickableScreen : Screen { + + public ClickableScreen( Game game ) : base( game ) { + } + + protected bool HandleMouseClick( Widget[] widgets, int mouseX, int mouseY, MouseButton button ) { + if( button != MouseButton.Left ) return false; + + // iterate backwards (because last elements rendered are shown over others) + for( int i = widgets.Length - 1; i >= 0; i-- ) { + Widget widget = widgets[i]; + if( widget != null && widget.Bounds.Contains( mouseX, mouseY ) ) { + if( widget.OnClick != null ) + widget.OnClick( game, widget ); + return true; + } + } + return false; + } + + protected bool HandleMouseMove( Widget[] widgets, int mouseX, int mouseY ) { + for( int i = 0; i < widgets.Length; i++ ) { + if( widgets[i] == null ) continue; + widgets[i].Active = false; + } + + for( int i = widgets.Length - 1; i >= 0; i-- ) { + Widget widget = widgets[i]; + if( widget != null && widget.Bounds.Contains( mouseX, mouseY ) ) { + widget.Active = true; + WidgetSelected( widget ); + return true; + } + } + WidgetSelected( null ); + return false; + } + + protected virtual void WidgetSelected( Widget widget ) { + } + } +} \ No newline at end of file diff --git a/ClassicalSharp/2D/Screens/FilesScreen.cs b/ClassicalSharp/2D/Screens/FilesScreen.cs index 7b95a45f2..1e96bfcfd 100644 --- a/ClassicalSharp/2D/Screens/FilesScreen.cs +++ b/ClassicalSharp/2D/Screens/FilesScreen.cs @@ -4,7 +4,7 @@ using OpenTK.Input; namespace ClassicalSharp { - public abstract class FilesScreen : Screen { + public abstract class FilesScreen : ClickableScreen { public FilesScreen( Game game ) : base( game ) { } @@ -55,12 +55,12 @@ namespace ClassicalSharp { Anchor.Centre, Anchor.Centre, textFont, TextButtonClick ); } - ButtonWidget Make( int x, int y, string text, Action onClick ) { + ButtonWidget Make( int x, int y, string text, Action onClick ) { return ButtonWidget.Create( game, x, y, 40, 40, text, Anchor.Centre, Anchor.Centre, arrowFont, onClick ); } - protected abstract void TextButtonClick( Game game, ButtonWidget widget ); + protected abstract void TextButtonClick( Game game, Widget widget ); protected void PageClick( bool forward ) { currentIndex += forward ? 5 : -5; @@ -88,29 +88,11 @@ namespace ClassicalSharp { } public override bool HandlesMouseMove( int mouseX, int mouseY ) { - for( int i = 0; i < buttons.Length; i++ ) - buttons[i].Active = false; - - for( int i = 0; i < buttons.Length; i++ ) { - ButtonWidget widget = buttons[i]; - if( widget.Bounds.Contains( mouseX, mouseY ) ) { - widget.Active = true; - return true; - } - } - return false; + return HandleMouseMove( buttons, mouseX, mouseY ); } public override bool HandlesMouseClick( int mouseX, int mouseY, MouseButton button ) { - if( button != MouseButton.Left ) return false; - for( int i = 0; i < buttons.Length; i++ ) { - ButtonWidget widget = buttons[i]; - if( widget.Bounds.Contains( mouseX, mouseY ) ) { - widget.OnClick( game, widget ); - return true; - } - } - return false; + return HandleMouseClick( buttons, mouseX, mouseY, button ); } public override bool HandlesAllInput { diff --git a/ClassicalSharp/2D/Screens/HotkeyScreen.cs b/ClassicalSharp/2D/Screens/HotkeyScreen.cs new file mode 100644 index 000000000..c77d9a6d9 --- /dev/null +++ b/ClassicalSharp/2D/Screens/HotkeyScreen.cs @@ -0,0 +1,310 @@ +using System; +using System.Drawing; +using ClassicalSharp.Hotkeys; +using OpenTK.Input; + +namespace ClassicalSharp { + + // TODO: Hotkey added event for CPE + // TODO: save hotkeys + public sealed class HotkeyScreen : MenuScreen { + + HotkeyList hotkeys; + public HotkeyScreen( Game game ) : base( game ) { + hotkeys = game.InputHandler.Hotkeys; + } + + Font hintFont, arrowFont, textFont; + public override void Render( double delta ) { + RenderMenuBounds(); + graphicsApi.Texturing = true; + RenderMenuButtons( delta ); + + if( currentAction != null ) { + currentAction.Render( delta ); + currentMoreInputLabel.Render( delta ); + } + graphicsApi.Texturing = false; + } + + public override bool HandlesMouseMove( int mouseX, int mouseY ) { + return HandleMouseMove( buttons, mouseX, mouseY ); + } + + public override bool HandlesMouseClick( int mouseX, int mouseY, MouseButton button ) { + return HandleMouseClick( buttons, mouseX, mouseY, button ); + } + + bool supressNextPress; + public override bool HandlesKeyPress( char key ) { + if( supressNextPress ) { + supressNextPress = false; + return true; + } + + return currentAction == null ? false : + currentAction.HandlesKeyPress( key ); + } + + public override bool HandlesKeyDown( Key key ) { + if( key == Key.Escape ) { + game.SetNewScreen( new NormalScreen( game ) ); + return true; + } else if( focusWidget != null ) { + FocusKeyDown( key ); + return true; + } + + if( key == Key.Left ) { + PageClick( false ); + return true; + } else if( key == Key.Right ) { + PageClick( true ); + return true; + } + return currentAction == null ? false : + currentAction.HandlesKeyDown( key ); + } + + public override bool HandlesKeyUp( Key key ) { + return currentAction == null ? false : + currentAction.HandlesKeyUp( key ); + } + + public override void Init() { + game.Keyboard.KeyRepeat = true; + titleFont = new Font( "Arial", 16, FontStyle.Bold ); + regularFont = new Font( "Arial", 16, FontStyle.Regular ); + hintFont = new Font( "Arial", 14, FontStyle.Italic ); + arrowFont = new Font( "Arial", 18, FontStyle.Bold ); + textFont = new Font( "Arial", 14, FontStyle.Bold ); + + buttons = new [] { + MakeHotkey( 0, -160, 0 ), + MakeHotkey( 0, -120, 1 ), + MakeHotkey( 0, -80, 2 ), + MakeHotkey( 0, -40, 3 ), + MakeAddNew( 0, 0 ), + Make( -160, -80, "<", 40, 40, arrowFont, (g, w) => PageClick( false ) ), + Make( 160, -80, ">", 40, 40, arrowFont, (g, w) => PageClick( true ) ), + + ButtonWidget.Create( game, 0, 5, 240, 35, "Back to menu", Anchor.Centre, Anchor.BottomOrRight, + titleFont, (g, w) => g.SetNewScreen( new PauseScreen( g ) ) ), + null, // current key + null, // current modifiers + null, // leave open current + null, // save current + null, // remove current + null, + }; + } + + public override void OnResize( int oldWidth, int oldHeight, int width, int height ) { + if( currentAction != null ) { + currentAction.OnResize( oldWidth, oldHeight, width, height ); + currentMoreInputLabel.OnResize( oldWidth, oldHeight, width, height ); + } + base.OnResize( oldWidth, oldHeight, width, height ); + } + + public override void Dispose() { + game.Keyboard.KeyRepeat = false; + DisposeEditingWidgets(); + + hintFont.Dispose(); + arrowFont.Dispose(); + textFont.Dispose(); + base.Dispose(); + } + + ButtonWidget Make( int x, int y, string text, int width, int height, + Font font, Action onClick ) { + return ButtonWidget.Create( game, x, y, width, height, text, + Anchor.Centre, Anchor.Centre, font, onClick ); + } + + ButtonWidget MakeHotkey( int x, int y, int index ) { + string text = Get( index ); + ButtonWidget button = ButtonWidget.Create( + game, x, y, 240, 30, text, Anchor.Centre, Anchor.Centre, + textFont, TextButtonClick ); + + if( text != "-----" ) + button.Metadata = hotkeys.Hotkeys[index]; + return button; + } + + ButtonWidget MakeAddNew( int x, int y ) { + ButtonWidget button = ButtonWidget.Create( + game, x, y, 240, 30, "Add new", Anchor.Centre, Anchor.Centre, + textFont, TextButtonClick ); + + button.Metadata = default( Hotkey ); + return button; + } + + string Get( int index ) { + if( index >= hotkeys.Hotkeys.Count ) return "-----"; + + Hotkey hKey = hotkeys.Hotkeys[index]; + return hKey.BaseKey + " | " + MakeFlagsString( hKey.Flags ); + } + + void Set( int index ) { + string text = Get( index ); + ButtonWidget button = buttons[index]; + button.SetText( text); + button.Metadata = null; + if( text != "-----" ) + button.Metadata = hotkeys.Hotkeys[index]; + } + + string MakeFlagsString( byte flags ) { + if( flags == 0 ) return "None"; + + return ((flags & 1) == 0 ? " " : "Ctrl ") + + ((flags & 2) == 0 ? " " : "Shift ") + + ((flags & 4) == 0 ? " " : "Alt "); + } + + int currentIndex; + void PageClick( bool forward ) { + currentIndex += forward ? 4 : -4; + if( currentIndex >= hotkeys.Hotkeys.Count ) currentIndex -= 4; + if( currentIndex < 0 ) currentIndex = 0; + + LostFocus(); + for( int i = 0; i < 4; i++ ) + Set( currentIndex + i ); + } + + void TextButtonClick( Game game, Widget widget ) { + LostFocus(); + ButtonWidget button = (ButtonWidget)widget; + + if( button.Metadata != null ) { + curHotkey = (Hotkey)button.Metadata; + origHotkey = curHotkey; + // do stuff here + CreateEditingWidgets(); + } + } + + #region Modifying hotkeys + Hotkey curHotkey, origHotkey; + MenuInputWidget currentAction; + TextWidget currentMoreInputLabel; + ButtonWidget focusWidget; + + void CreateEditingWidgets() { + DisposeEditingWidgets(); + + buttons[8] = Make( -140, 60, "Key: " + curHotkey.BaseKey, + 250, 30, textFont, BaseKeyClick ); + buttons[9] = Make( 140, 60, "Modifiers: " + MakeFlagsString( curHotkey.Flags ), + 250, 30, textFont, ModifiersClick ); + buttons[10] = Make( -10, 120, curHotkey.MoreInput ? "yes" : "no", + 50, 25, textFont, LeaveOpenClick ); + buttons[11] = Make( -100, 160, "Save changes", + 160, 30, textFont, SaveChangesClick ); + buttons[12] = Make( 100, 160, "Remove hotkey", + 160, 30, textFont, RemoveHotkeyClick ); + + currentAction = MenuInputWidget.Create( + game, 0, 90, 600, 25, "", Anchor.Centre, Anchor.Centre, + regularFont, titleFont, hintFont, new StringValidator( 64 ) ); + currentMoreInputLabel = TextWidget.Create( + game, -170, 120, "Leave open for further input:", + Anchor.Centre, Anchor.Centre, textFont ); + + if( curHotkey.Text == null ) curHotkey.Text = ""; + currentAction.SetText( curHotkey.Text ); + } + + void DisposeEditingWidgets() { + if( currentAction != null ) { + currentAction.Dispose(); + currentAction = null; + } + + for( int i = 8; i < buttons.Length - 1; i++ ) { + if( buttons[i] != null ) { + buttons[i].Dispose(); + buttons[i] = null; + } + } + focusWidget = null; + } + + void LeaveOpenClick( Game game, Widget widget ) { + LostFocus(); + curHotkey.MoreInput = !curHotkey.MoreInput; + buttons[10].SetText( curHotkey.MoreInput ? "yes" : "no" ); + } + + void SaveChangesClick( Game game, Widget widget ) { + if( origHotkey.BaseKey != Key.Unknown ) + hotkeys.RemoveHotkey( origHotkey.BaseKey, origHotkey.Flags ); + + if( curHotkey.BaseKey != Key.Unknown ) + hotkeys.AddHotkey( curHotkey.BaseKey, curHotkey.Flags, + currentAction.GetText(), curHotkey.MoreInput ); + + for( int i = 0; i < 4; i++ ) + Set( currentIndex + i ); + DisposeEditingWidgets(); + } + + void RemoveHotkeyClick( Game game, Widget widget ) { + if( origHotkey.BaseKey != Key.Unknown ) + hotkeys.RemoveHotkey( origHotkey.BaseKey, origHotkey.Flags ); + + for( int i = 0; i < 4; i++ ) + Set( currentIndex + i ); + DisposeEditingWidgets(); + } + + void BaseKeyClick( Game game, Widget widget ) { + focusWidget = buttons[8]; + focusWidget.SetText( "Key: press a key.." ); + supressNextPress = true; + } + + void ModifiersClick( Game game, Widget widget ) { + focusWidget = buttons[9]; + focusWidget.SetText( "Modifiers: press a key.." ); + supressNextPress = true; + } + + void FocusKeyDown( Key key ) { + if( focusWidget == buttons[8] ) { + curHotkey.BaseKey = key; + buttons[8].SetText( "Key: " + curHotkey.BaseKey ); + supressNextPress = true; + } else if( focusWidget == buttons[9] ) { + if( key == Key.ControlLeft || key == Key.ControlRight ) curHotkey.Flags |= 1; + else if( key == Key.ShiftLeft || key == Key.ShiftRight ) curHotkey.Flags |= 2; + else if( key == Key.AltLeft || key == Key.AltRight ) curHotkey.Flags |= 4; + else curHotkey.Flags = 0; + + buttons[9].SetText( "Modifiers: " + MakeFlagsString( curHotkey.Flags ) ); + supressNextPress = true; + } + focusWidget = null; + } + + void LostFocus() { + if( focusWidget == null ) return; + + if( focusWidget == buttons[8] ) { + buttons[8].SetText( "Key: " + curHotkey.BaseKey ); + } else if( focusWidget == buttons[9] ) { + buttons[9].SetText( "Modifiers: " + MakeFlagsString( curHotkey.Flags ) ); + } + focusWidget = null; + supressNextPress = false; + } + + #endregion + } +} \ No newline at end of file diff --git a/ClassicalSharp/2D/Screens/Menu/EnvSettingsScreen.cs b/ClassicalSharp/2D/Screens/Menu/EnvSettingsScreen.cs index fd702b9e9..3162190a7 100644 --- a/ClassicalSharp/2D/Screens/Menu/EnvSettingsScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/EnvSettingsScreen.cs @@ -71,7 +71,7 @@ namespace ClassicalSharp { okayIndex = buttons.Length - 1; } - ButtonWidget Make( int x, int y, string text, Anchor vDocking, Action onClick, + ButtonWidget Make( int x, int y, string text, Anchor vDocking, Action onClick, Func getter, Action setter ) { ButtonWidget widget = ButtonWidget.Create( game, x, y, 240, 35, text, Anchor.Centre, vDocking, titleFont, onClick ); widget.GetValue = getter; diff --git a/ClassicalSharp/2D/Screens/Menu/KeyBindingsScreen.cs b/ClassicalSharp/2D/Screens/Menu/KeyBindingsScreen.cs index 3a94dba46..ef42fd9f2 100644 --- a/ClassicalSharp/2D/Screens/Menu/KeyBindingsScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/KeyBindingsScreen.cs @@ -53,10 +53,10 @@ namespace ClassicalSharp { } } - ButtonWidget widget; - void OnWidgetClick( Game game, ButtonWidget widget ) { - this.widget = widget; - int index = Array.IndexOf( buttons, widget ); + ButtonWidget curWidget; + void OnWidgetClick( Game game, Widget realWidget ) { + this.curWidget = (ButtonWidget)realWidget; + int index = Array.IndexOf( buttons, curWidget ); statusWidget.Dispose(); string text = "Press new key binding for " + descriptions[index] + ":"; @@ -66,8 +66,8 @@ namespace ClassicalSharp { public override bool HandlesKeyDown( Key key ) { if( key == Key.Escape ) { game.SetNewScreen( new NormalScreen( game ) ); - } else if( widget != null ) { - int index = Array.IndexOf( buttons, widget ); + } else if( curWidget != null ) { + int index = Array.IndexOf( buttons, curWidget ); KeyBinding mapping = (KeyBinding)index; KeyMap map = game.InputHandler.Keys; Key oldKey = map[mapping]; @@ -81,15 +81,15 @@ namespace ClassicalSharp { statusWidget.SetText( String.Format( format, descriptions[index], oldKey, key ) ); string text = descriptions[index] + " : " + keyNames[(int)key]; - widget.SetText( text ); + curWidget.SetText( text ); map[mapping] = key; } - widget = null; + curWidget = null; } return true; } - ButtonWidget Make( int x, int y, string text, Anchor vDocking, Action onClick ) { + ButtonWidget Make( int x, int y, string text, Anchor vDocking, Action onClick ) { return ButtonWidget.Create( game, x, y, 240, 35, text, Anchor.Centre, vDocking, keyFont, onClick ); } diff --git a/ClassicalSharp/2D/Screens/Menu/LoadLevelScreen.cs b/ClassicalSharp/2D/Screens/Menu/LoadLevelScreen.cs index 46049e729..de34b65bc 100644 --- a/ClassicalSharp/2D/Screens/Menu/LoadLevelScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/LoadLevelScreen.cs @@ -27,13 +27,13 @@ namespace ClassicalSharp { Make( 0, 5, "Back to menu", (g, w) => g.SetNewScreen( new PauseScreen( g ) ) ); } - ButtonWidget Make( int x, int y, string text, Action onClick ) { + ButtonWidget Make( int x, int y, string text, Action onClick ) { return ButtonWidget.Create( game, x, y, 240, 35, text, Anchor.Centre, Anchor.BottomOrRight, titleFont, onClick ); } - protected override void TextButtonClick( Game game, ButtonWidget widget ) { - string path = widget.Text; + protected override void TextButtonClick( Game game, Widget widget ) { + string path = ((ButtonWidget)widget).Text; if( File.Exists( path ) ) LoadMap( path ); } diff --git a/ClassicalSharp/2D/Screens/Menu/MenuInputScreen.cs b/ClassicalSharp/2D/Screens/Menu/MenuInputScreen.cs index 98142d8a2..93aee2adf 100644 --- a/ClassicalSharp/2D/Screens/Menu/MenuInputScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/MenuInputScreen.cs @@ -75,11 +75,12 @@ namespace ClassicalSharp { } ButtonWidget selectedWidget, targetWidget; - protected override void WidgetSelected( ButtonWidget widget ) { - if( selectedWidget == widget || widget == null || - widget == buttons[buttons.Length - 2] ) return; + protected override void WidgetSelected( Widget widget ) { + ButtonWidget button = (ButtonWidget)widget; + if( selectedWidget == button || button == null || + button == buttons[buttons.Length - 2] ) return; - selectedWidget = widget; + selectedWidget = button; if( targetWidget != null ) return; UpdateDescription( selectedWidget ); } @@ -93,18 +94,19 @@ namespace ClassicalSharp { descWidget = TextWidget.Create( game, 0, 100, text, Anchor.Centre, Anchor.Centre, regularFont ); } - protected void OnWidgetClick( Game game, ButtonWidget widget ) { + protected void OnWidgetClick( Game game, Widget widget ) { if( widget == buttons[okayIndex] ) { ChangeSetting(); return; } + ButtonWidget button = (ButtonWidget)widget; - int index = Array.IndexOf( buttons, widget ); + int index = Array.IndexOf( buttons, button ); MenuInputValidator validator = validators[index]; if( validator is BooleanValidator ) { - string value = widget.GetValue( game ); - widget.SetValue( game, value == "yes" ? "no" : "yes" ); - UpdateDescription( widget ); + string value = button.GetValue( game ); + button.SetValue( game, value == "yes" ? "no" : "yes" ); + UpdateDescription( button ); return; } @@ -112,7 +114,7 @@ namespace ClassicalSharp { inputWidget.Dispose(); targetWidget = selectedWidget; - inputWidget = MenuInputWidget.Create( game, 0, 150, 400, 25, widget.GetValue( game ), + inputWidget = MenuInputWidget.Create( game, 0, 150, 400, 25, button.GetValue( game ), Anchor.Centre, Anchor.Centre, regularFont, titleFont, hintFont, validator ); buttons[okayIndex] = ButtonWidget.Create( game, 240, 150, 30, 30, "OK", diff --git a/ClassicalSharp/2D/Screens/Menu/MenuScreen.cs b/ClassicalSharp/2D/Screens/Menu/MenuScreen.cs index 81002eabd..80e89eaac 100644 --- a/ClassicalSharp/2D/Screens/Menu/MenuScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/MenuScreen.cs @@ -4,7 +4,7 @@ using OpenTK.Input; namespace ClassicalSharp { - public abstract class MenuScreen : Screen { + public abstract class MenuScreen : ClickableScreen { public MenuScreen( Game game ) : base( game ) { } @@ -44,34 +44,11 @@ namespace ClassicalSharp { } public override bool HandlesMouseClick( int mouseX, int mouseY, MouseButton button ) { - if( button != MouseButton.Left ) return false; - for( int i = 0; i < buttons.Length; i++ ) { - ButtonWidget widget = buttons[i]; - if( widget != null && widget.Bounds.Contains( mouseX, mouseY ) ) { - if( widget.OnClick != null ) - widget.OnClick( game, widget ); - return true; - } - } - return false; + return HandleMouseClick( buttons, mouseX, mouseY, button ); } public override bool HandlesMouseMove( int mouseX, int mouseY ) { - for( int i = 0; i < buttons.Length; i++ ) { - if( buttons[i] == null ) continue; - buttons[i].Active = false; - } - - for( int i = 0; i < buttons.Length; i++ ) { - ButtonWidget widget = buttons[i]; - if( widget != null && widget.Bounds.Contains( mouseX, mouseY ) ) { - widget.Active = true; - WidgetSelected( widget ); - return true; - } - } - WidgetSelected( null ); - return false; + return HandleMouseMove( buttons, mouseX, mouseY ); } public override bool HandlesKeyPress( char key ) { @@ -85,8 +62,5 @@ namespace ClassicalSharp { public override bool HandlesKeyUp( Key key ) { return true; } - - protected virtual void WidgetSelected( ButtonWidget widget ) { - } } } \ No newline at end of file diff --git a/ClassicalSharp/2D/Screens/Menu/OptionsScreen.cs b/ClassicalSharp/2D/Screens/Menu/OptionsScreen.cs index 6a0050395..a04b9d1e6 100644 --- a/ClassicalSharp/2D/Screens/Menu/OptionsScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/OptionsScreen.cs @@ -79,7 +79,7 @@ namespace ClassicalSharp { okayIndex = buttons.Length - 1; } - ButtonWidget Make( int x, int y, string text, Anchor vDocking, Action onClick, + ButtonWidget Make( int x, int y, string text, Anchor vDocking, Action onClick, Func getter, Action setter ) { ButtonWidget widget = ButtonWidget.Create( game, x, y, 240, 35, text, Anchor.Centre, vDocking, titleFont, onClick ); widget.GetValue = getter; diff --git a/ClassicalSharp/2D/Screens/Menu/PauseScreen.cs b/ClassicalSharp/2D/Screens/Menu/PauseScreen.cs index 0713711a8..a37a58e77 100644 --- a/ClassicalSharp/2D/Screens/Menu/PauseScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/PauseScreen.cs @@ -18,30 +18,23 @@ namespace ClassicalSharp { public override void Init() { titleFont = new Font( "Arial", 16, FontStyle.Bold ); - if( game.Network.IsSinglePlayer ) { - buttons = new ButtonWidget[] { - Make( -140, -50, "Options", Anchor.Centre, (g, w) => g.SetNewScreen( new OptionsScreen( g ) ) ), - Make( -140, 0, "Environment settings", Anchor.Centre, (g, w) => g.SetNewScreen( new EnvSettingsScreen( g ) ) ), - Make( -140, 50, "Select texture pack", Anchor.Centre, (g, w) => g.SetNewScreen( new TexturePackScreen( g ) ) ), - Make( 140, -50, "Save level", Anchor.Centre, (g, w) => g.SetNewScreen( new SaveLevelScreen( g ) ) ), - Make( 140, 0, "Load level", Anchor.Centre, (g, w) => g.SetNewScreen( new LoadLevelScreen( g ) ) ), - // TODO: singleplayer Make( 0, 50, "Load/Save/Gen level", Docking.Centre, (g, w) => g.SetNewScreen( new SaveLevelScreen( g ) ) ), - Make( 0, 55, "Back to game", Anchor.BottomOrRight, (g, w) => g.SetNewScreen( new NormalScreen( g ) ) ), - Make( 0, 5, "Quit game", Anchor.BottomOrRight, (g, w) => g.Exit() ), - }; - } else { - buttons = new ButtonWidget[] { - Make( 0, -100, "Options", Anchor.Centre, (g, w) => g.SetNewScreen( new OptionsScreen( g ) ) ), - Make( 0, -50, "Environment settings", Anchor.Centre, (g, w) => g.SetNewScreen( new EnvSettingsScreen( g ) ) ), - Make( 0, 0, "Select texture pack", Anchor.Centre, (g, w) => g.SetNewScreen( new TexturePackScreen( g ) ) ), - Make( 0, 50, "Save level", Anchor.Centre, (g, w) => g.SetNewScreen( new SaveLevelScreen( g ) ) ), - Make( 0, 55, "Back to game", Anchor.BottomOrRight, (g, w) => g.SetNewScreen( new NormalScreen( g ) ) ), - Make( 0, 5, "Quit game", Anchor.BottomOrRight, (g, w) => g.Exit() ), - }; - } + buttons = new ButtonWidget[] { + Make( -140, -100, "Options", Anchor.Centre, (g, w) => g.SetNewScreen( new OptionsScreen( g ) ) ), + Make( -140, -50, "Environment settings", Anchor.Centre, (g, w) => g.SetNewScreen( new EnvSettingsScreen( g ) ) ), + Make( -140, 0, "Select texture pack", Anchor.Centre, (g, w) => g.SetNewScreen( new TexturePackScreen( g ) ) ), + Make( -140, 50, "Hotkeys", Anchor.Centre, (g, w) => g.SetNewScreen( new HotkeyScreen( g ) ) ), + + Make( 140, -100, "Save level", Anchor.Centre, (g, w) => g.SetNewScreen( new SaveLevelScreen( g ) ) ), + !game.Network.IsSinglePlayer ? null : + Make( 140, -50, "Load level", Anchor.Centre, (g, w) => g.SetNewScreen( new LoadLevelScreen( g ) ) ), + // TODO: singleplayer Generate level screen + + Make( 0, 55, "Back to game", Anchor.BottomOrRight, (g, w) => g.SetNewScreen( new NormalScreen( g ) ) ), + Make( 0, 5, "Quit game", Anchor.BottomOrRight, (g, w) => g.Exit() ), + }; } - ButtonWidget Make( int x, int y, string text, Anchor vDocking, Action onClick ) { + ButtonWidget Make( int x, int y, string text, Anchor vDocking, Action onClick ) { return ButtonWidget.Create( game, x, y, 240, 35, text, Anchor.Centre, vDocking, titleFont, onClick ); } diff --git a/ClassicalSharp/2D/Screens/Menu/SaveLevelScreen.cs b/ClassicalSharp/2D/Screens/Menu/SaveLevelScreen.cs index 9e63ecf63..954f340d2 100644 --- a/ClassicalSharp/2D/Screens/Menu/SaveLevelScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/SaveLevelScreen.cs @@ -77,7 +77,7 @@ namespace ClassicalSharp { base.Dispose(); } - void OkButtonClick( Game game, ButtonWidget widget ) { + void OkButtonClick( Game game, Widget widget ) { string text = inputWidget.GetText(); if( text.Length == 0 ) { MakeDescWidget( "Please enter a filename" ); diff --git a/ClassicalSharp/2D/Screens/Menu/TexturePackScreen.cs b/ClassicalSharp/2D/Screens/Menu/TexturePackScreen.cs index 4219b7d03..f97aab0d0 100644 --- a/ClassicalSharp/2D/Screens/Menu/TexturePackScreen.cs +++ b/ClassicalSharp/2D/Screens/Menu/TexturePackScreen.cs @@ -25,13 +25,13 @@ namespace ClassicalSharp { Make( 0, 5, "Back to menu", (g, w) => g.SetNewScreen( new PauseScreen( g ) ) ); } - ButtonWidget Make( int x, int y, string text, Action onClick ) { + ButtonWidget Make( int x, int y, string text, Action onClick ) { return ButtonWidget.Create( game, x, y, 240, 35, text, Anchor.Centre, Anchor.BottomOrRight, titleFont, onClick ); } - protected override void TextButtonClick( Game game, ButtonWidget widget ) { - string path = widget.Text; + protected override void TextButtonClick( Game game, Widget widget ) { + string path = ((ButtonWidget)widget).Text; if( File.Exists( path ) ) { TexturePackExtractor extractor = new TexturePackExtractor(); extractor.Extract( path, game ); diff --git a/ClassicalSharp/2D/Widgets/ButtonWidget.cs b/ClassicalSharp/2D/Widgets/ButtonWidget.cs index e24f5175e..b5c597317 100644 --- a/ClassicalSharp/2D/Widgets/ButtonWidget.cs +++ b/ClassicalSharp/2D/Widgets/ButtonWidget.cs @@ -11,15 +11,13 @@ namespace ClassicalSharp { } public static ButtonWidget Create( Game game, int x, int y, int width, int height, string text, Anchor horizontal, - Anchor vertical, Font font, Action onClick ) { + Anchor vertical, Font font, Action onClick ) { ButtonWidget widget = new ButtonWidget( game, font ); widget.Init(); widget.HorizontalAnchor = horizontal; widget.VerticalAnchor = vertical; - widget.XOffset = x; - widget.YOffset = y; - widget.DesiredMaxWidth = width; - widget.DesiredMaxHeight = height; + widget.XOffset = x; widget.YOffset = y; + widget.DesiredMaxWidth = width; widget.DesiredMaxHeight = height; widget.SetText( text ); widget.OnClick = onClick; return widget; @@ -75,10 +73,8 @@ namespace ClassicalSharp { Y = newY; } - public Action OnClick; public Func GetValue; public Action SetValue; - public bool Active; void MakeTexture( string text ) { DrawTextArgs args = new DrawTextArgs( text, font, true ); diff --git a/ClassicalSharp/2D/Widgets/Menu/MenuInputValidator.cs b/ClassicalSharp/2D/Widgets/Menu/MenuInputValidator.cs index cf099f76d..70040d02b 100644 --- a/ClassicalSharp/2D/Widgets/Menu/MenuInputValidator.cs +++ b/ClassicalSharp/2D/Widgets/Menu/MenuInputValidator.cs @@ -126,4 +126,21 @@ namespace ClassicalSharp { return s == "yes" || s == "no"; } } + + public sealed class StringValidator : MenuInputValidator { + + int maxLen; + public StringValidator( int len ) { + Range = "&7(Enter text)"; + maxLen = len; + } + + public override bool IsValidChar( char c ) { + return !(c < ' ' || c == '&' || c > '~'); + } + + public override bool IsValidString( string s ) { + return s.Length <= maxLen; + } + } } diff --git a/ClassicalSharp/2D/Widgets/Widget.cs b/ClassicalSharp/2D/Widgets/Widget.cs index 497145dd5..4acc969a0 100644 --- a/ClassicalSharp/2D/Widgets/Widget.cs +++ b/ClassicalSharp/2D/Widgets/Widget.cs @@ -11,6 +11,12 @@ namespace ClassicalSharp { VerticalAnchor = Anchor.LeftOrTop; } + /// Whether this widget is currently being moused over. + public bool Active; + + /// Invoked when this widget is clicked on. Can be left null. + public Action OnClick; + /// Horizontal coordinate of top left corner in window space. public int X; diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index 6ba4fbaa1..db35149fa 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -82,9 +82,11 @@ + + diff --git a/ClassicalSharp/Commands/CommandManager.cs b/ClassicalSharp/Commands/CommandManager.cs index e410ee36e..67cec70bd 100644 --- a/ClassicalSharp/Commands/CommandManager.cs +++ b/ClassicalSharp/Commands/CommandManager.cs @@ -17,6 +17,7 @@ namespace ClassicalSharp.Commands { RegisterCommand( new HelpCommand() ); RegisterCommand( new InfoCommand() ); RegisterCommand( new RenderTypeCommand() ); + RegisterCommand( new HotkeyCommand() ); } public void RegisterCommand( Command command ) { diff --git a/ClassicalSharp/Commands/DefaultCommands.cs b/ClassicalSharp/Commands/DefaultCommands.cs index 376540962..360ab75a5 100644 --- a/ClassicalSharp/Commands/DefaultCommands.cs +++ b/ClassicalSharp/Commands/DefaultCommands.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using ClassicalSharp.Renderers; +using OpenTK.Input; namespace ClassicalSharp.Commands { @@ -100,6 +101,46 @@ namespace ClassicalSharp.Commands { } } + public sealed class HotkeyCommand : Command { + + public HotkeyCommand() { + Name = "Hotkey"; + Help = new [] { + "&a/client hotkey [key] [modifiers] [more] [action]", + }; + } + + public override void Execute( CommandReader reader ) { + Key key; + byte modifiers; + bool more; + + if( !reader.NextOf( out key, ParseKey ) || + !reader.NextOf( out modifiers, Byte.TryParse ) || + !reader.NextOf( out more, Boolean.TryParse ) ) { + game.Chat.Add( "NOPE!" ); + return; + } + + string action = reader.NextAll(); + if( action != null ) { + game.InputHandler.Hotkeys + .AddHotkey( key, modifiers, action, more ); + game.Chat.Add( "Added" ); + } + } + + bool ParseKey( string value, out Key key ) { + try { + key = (Key)Enum.Parse( typeof( Key ), value, true ); + return true; + } catch { + key = Key.Unknown; + return false; + } + } + } + public sealed class RenderTypeCommand : Command { public RenderTypeCommand() { diff --git a/ClassicalSharp/Hotkeys/HotkeyList.cs b/ClassicalSharp/Hotkeys/HotkeyList.cs index 3758db829..448558e7e 100644 --- a/ClassicalSharp/Hotkeys/HotkeyList.cs +++ b/ClassicalSharp/Hotkeys/HotkeyList.cs @@ -7,14 +7,7 @@ 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(); + public 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 ) { @@ -26,10 +19,10 @@ namespace ClassicalSharp.Hotkeys { /// 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]; + for( int i = 0; i < Hotkeys.Count; i++ ) { + Hotkey hKey = Hotkeys[i]; if( hKey.BaseKey == baseKey && hKey.Flags == flags ) { - hotkeys.RemoveAt( i ); + Hotkeys.RemoveAt( i ); return true; } } @@ -37,12 +30,12 @@ namespace ClassicalSharp.Hotkeys { } bool UpdateExistingHotkey( Key baseKey, byte flags, string text, bool more ) { - for( int i = 0; i < hotkeys.Count; i++ ) { - Hotkey hKey = hotkeys[i]; + 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; + Hotkeys[i] = hKey; return true; } } @@ -50,17 +43,18 @@ namespace ClassicalSharp.Hotkeys { } void AddNewHotkey( Key baseKey, byte flags, string text, bool more ) { - Hotkey hotkey; + Hotkey hotkey; hotkey.BaseKey = baseKey; hotkey.Flags = flags; hotkey.Text = text; hotkey.MoreInput = more; - hotkeys.Add( hotkey ); + + Hotkeys.Add( hotkey ); // sort so that hotkeys with largest modifiers are first - hotkeys.Sort( (a, b) => b.Flags.CompareTo( a.Flags ) ); + Hotkeys.Sort( (a, b) => b.Flags.CompareTo( a.Flags ) ); } - /// Determines whether a hotkey is active based on the given key, + /// 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 ) { @@ -69,7 +63,7 @@ namespace ClassicalSharp.Hotkeys { if( keyboard[Key.ShiftLeft] || keyboard[Key.ShiftRight] ) flags |= 2; if( keyboard[Key.AltLeft] || keyboard[Key.AltRight] ) flags |= 4; - foreach( Hotkey hKey in hotkeys ) { + foreach( Hotkey hKey in Hotkeys ) { if( (hKey.Flags & flags) == hKey.Flags && hKey.BaseKey == key ) { text = hKey.Text; moreInput = hKey.MoreInput; @@ -81,5 +75,12 @@ namespace ClassicalSharp.Hotkeys { moreInput = false; return false; } + } + + public 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 } } diff --git a/ClassicalSharp/Network/NetworkProcessor.CPE.cs b/ClassicalSharp/Network/NetworkProcessor.CPE.cs index 398d95ae5..2e79a2506 100644 --- a/ClassicalSharp/Network/NetworkProcessor.CPE.cs +++ b/ClassicalSharp/Network/NetworkProcessor.CPE.cs @@ -140,18 +140,20 @@ namespace ClassicalSharp { 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 + } else if( action[action.Length - 1] == '\n' ) { action = action.Substring( 0, action.Length - 1 ); - game.InputHandler.Hotkeys.AddHotkey( key, keyMods, action, true ); - } else { game.InputHandler.Hotkeys.AddHotkey( key, keyMods, action, false ); + } else { // more input needed by user + game.InputHandler.Hotkeys.AddHotkey( key, keyMods, action, true ); } } void HandleCpeExtAddPlayerName() { short nameId = reader.ReadInt16(); string playerName = Utils.StripColours( reader.ReadAsciiString() ); + playerName = Utils.RemoveEndPlus( playerName ); string listName = reader.ReadAsciiString(); + listName = Utils.RemoveEndPlus( listName ); string groupName = reader.ReadAsciiString(); byte groupRank = reader.ReadUInt8(); @@ -159,7 +161,6 @@ namespace ClassicalSharp { CpeListInfo oldInfo = game.CpePlayersList[nameId]; CpeListInfo info = new CpeListInfo( (byte)nameId, playerName, listName, groupName, groupRank ); game.CpePlayersList[nameId] = info; - //Console.WriteLine( nameId + ": " + groupRank + " , " + groupName + " : " + listName ); if( oldInfo != null ) { game.Events.RaiseCpeListInfoChanged( (byte)nameId ); @@ -172,7 +173,9 @@ namespace ClassicalSharp { void HandleCpeExtAddEntity() { byte entityId = reader.ReadUInt8(); string displayName = reader.ReadAsciiString(); + displayName = Utils.RemoveEndPlus( displayName ); string skinName = reader.ReadAsciiString(); + skinName = Utils.RemoveEndPlus( skinName ); AddEntity( entityId, displayName, skinName, false ); } diff --git a/ClassicalSharp/Network/NetworkProcessor.cs b/ClassicalSharp/Network/NetworkProcessor.cs index 97afe9b8c..0d0a7e797 100644 --- a/ClassicalSharp/Network/NetworkProcessor.cs +++ b/ClassicalSharp/Network/NetworkProcessor.cs @@ -99,7 +99,7 @@ namespace ClassicalSharp { reader.ReadPendingData(); } catch( IOException ex ) { ErrorHandler.LogError( "reading packets", ex ); - game.Disconnect( "&eLost connection to the server", "IO error when reading packets" ); + game.Disconnect( "&eLost connection to the server", "I/O error when reading packets" ); Dispose(); return; } @@ -205,7 +205,7 @@ namespace ClassicalSharp { stream.Write( outBuffer, 0, packetLength ); } catch( IOException ex ) { ErrorHandler.LogError( "wrting packets", ex ); - game.Disconnect( "&eLost connection to the server", "IO Error while writing packets" ); + game.Disconnect( "&eLost connection to the server", "I/O Error while writing packets" ); Dispose(); } } @@ -334,6 +334,7 @@ namespace ClassicalSharp { void HandleAddEntity() { byte entityId = reader.ReadUInt8(); string name = reader.ReadAsciiString(); + name = Utils.RemoveEndPlus( name ); AddEntity( entityId, name, name, true ); } diff --git a/ClassicalSharp/Utils/TextureRectangle.cs b/ClassicalSharp/Utils/TextureRectangle.cs index 5c82b15ef..29f70c76b 100644 --- a/ClassicalSharp/Utils/TextureRectangle.cs +++ b/ClassicalSharp/Utils/TextureRectangle.cs @@ -7,18 +7,14 @@ namespace ClassicalSharp { public float U1, V1, U2, V2; public TextureRec( float u, float v, float uWidth, float vHeight ) { - U1 = u; - V1 = v; - U2 = u + uWidth; - V2 = v + vHeight; + U1 = u; V1 = v; + U2 = u + uWidth; V2 = v + vHeight; } public static TextureRec FromPoints( float u1, float u2, float v1, float v2 ) { TextureRec rec; - rec.U1 = u1; - rec.U2 = u2; - rec.V1 = v1; - rec.V2 = v2; + rec.U1 = u1;rec.U2 = u2; + rec.V1 = v1; rec.V2 = v2; return rec; } diff --git a/ClassicalSharp/Utils/Utils.cs b/ClassicalSharp/Utils/Utils.cs index f74f4d8c8..41a7cb0c3 100644 --- a/ClassicalSharp/Utils/Utils.cs +++ b/ClassicalSharp/Utils/Utils.cs @@ -65,6 +65,16 @@ namespace ClassicalSharp { return new String( output, 0, usedChars ); } + /// Returns a string with a + removed if it is the last character in the string. + public static string RemoveEndPlus( string value ) { + // Some servers (e.g. MCDzienny) use a '+' at the end to distinguish classicube.net accounts + // from minecraft.net accounts. Unfortunately they also send this ending + to the client. + if( String.IsNullOrEmpty( value ) ) return value; + + return value[value.Length - 1] == '+' ? + value.Substring( 0, value.Length - 1 ) : value; + } + /// Returns whether a equals b, ignoring any case differences. public static bool CaselessEquals( string a, string b ) { return a.Equals( b, StringComparison.OrdinalIgnoreCase ); diff --git a/Launcher2/Gui/Screens/ResourcesScreen.cs b/Launcher2/Gui/Screens/ResourcesScreen.cs index 15289ac1c..715020f5b 100644 --- a/Launcher2/Gui/Screens/ResourcesScreen.cs +++ b/Launcher2/Gui/Screens/ResourcesScreen.cs @@ -104,7 +104,7 @@ namespace Launcher2 { MakeButtonAt( "No", 60, 30, textFont, Anchor.Centre, 50, 40, (x, y) => game.SetScreen( new MainScreen( game ) ) ); } else { - MakeButtonAt( "Dismiss", 120, 30, textFont, Anchor.Centre, + MakeButtonAt( "Cancel", 120, 30, textFont, Anchor.Centre, 0, 40, (x, y) => game.SetScreen( new MainScreen( game ) ) ); } }