From f1a415ebe08c01ccf3170139041ed8c7078e61a2 Mon Sep 17 00:00:00 2001 From: Maikel Date: Wed, 3 Feb 2021 22:32:03 +0100 Subject: [PATCH] Add right click scroll coasting to RA1 Add settings to turn scroll coasting on/off, modify scroll coast rate (uses edge scrolling rate if no custom rate is set), implement logic to scroll quicker if the mouse moves further away from point of right click Make less sensitive & fix map edge scrolling Fix scroll coasting over top tabs area of the screen and sidebar & radar map parts, prevent rubberband when right click scroll coasting is active and vice versa Add right-click scroll coasting like in Red Alert 2 --- redalert/display.cpp | 160 +++++++++++++++++++++++++++++++++---------- redalert/display.h | 23 ++++++- redalert/options.cpp | 6 ++ redalert/options.h | 2 + redalert/radar.cpp | 4 ++ redalert/scroll.cpp | 2 +- redalert/sidebar.cpp | 17 ++--- 7 files changed, 167 insertions(+), 47 deletions(-) diff --git a/redalert/display.cpp b/redalert/display.cpp index bb09d444..d492ae4a 100644 --- a/redalert/display.cpp +++ b/redalert/display.cpp @@ -184,6 +184,10 @@ DisplayClass::DisplayClass(void) , BandY(0) , NewX(0) , NewY(0) + , ScrollCoastPressPointX(0) + , ScrollCoastPressPointY(0) + , IsScrollCoasting(false) + , IsTentativeScrollCoast(false) { ShadowShapes = 0; TransIconset = 0; @@ -590,10 +594,11 @@ void DisplayClass::Set_View_Dimensions(int x, int y, int width, int height) IsToRedraw = true; Flag_To_Redraw(false); - TacButton.X = TacPixelX; - TacButton.Y = TacPixelY; - TacButton.Width = Lepton_To_Pixel(TacLeptonWidth); - TacButton.Height = Lepton_To_Pixel(TacLeptonHeight); + /* set TacButton to whole screen so the mouse actions can be optionally applied to whole screen */ + TacButton.X = 0; + TacButton.Y = 0; + TacButton.Width = SeenBuff.Get_Width(); + TacButton.Height = SeenBuff.Get_Height(); } /*********************************************************************************************** @@ -3046,6 +3051,15 @@ void DisplayClass::Refresh_Band(void) } } +bool DisplayClass::Is_Mouse_At_Tactical_View_Edge(int x, int y) +{ + int SidebarWidth = 160; // SIDE_WIDTH (80) * RESFACTOR (2) for win95 + if (y == 0 || x == 0 || x == SeenBuff.Get_Width() - SidebarWidth - 1 || y == SeenBuff.Get_Height() - 1) { + return true; + } + return false; +} + /*********************************************************************************************** * DisplayClass::TacticalClass::Action -- Processes input for the tactical map. * * * @@ -3084,7 +3098,14 @@ int DisplayClass::TacticalClass::Action(unsigned flags, KeyNumType& key) x = Get_Mouse_X(); y = Get_Mouse_Y(); } - bool edge = (y == 0 || x == 0 || x == SeenBuff.Get_Width() - 1 || y == SeenBuff.Get_Height() - 1); + // For right click scroll coasting the whole screen x and Y are passed to make sure + // scroll coasting works on outside of tactical display (e.g. the upper black part of the + // screen with the options and credits tabs + int displayx = x; + int displayy = y; + + int SidebarWidth = 160; + bool edge = Map.Is_Mouse_At_Tactical_View_Edge(x, y); COORDINATE coord = Map.Pixel_To_Coord(x, y); CELL cell = Coord_Cell(coord); if (coord) { @@ -3224,12 +3245,6 @@ int DisplayClass::TacticalClass::Action(unsigned flags, KeyNumType& key) Map.Set_Cursor_Pos(cell); } - /* - ** A right mouse button press cancels the current action or selection. - */ - if (flags & RIGHTPRESS) { - Map.Mouse_Right_Press(); - } /* ** Make sure that if the mouse button has been released and the map doesn't know about it, @@ -3279,6 +3294,29 @@ int DisplayClass::TacticalClass::Action(unsigned flags, KeyNumType& key) } } + /* + ** A right mouse button press cancels the current action or selection. + */ + if (flags & RIGHTPRESS) { + Map.Mouse_Right_Press(); + } + + /* + ** When the mouse buttons aren't pressed, only the mouse cursor shape is processed. + ** The shape changes depending on what object the mouse is currently over and what + ** object is currently selected. + */ + if (flags & RIGHTHELD) { + Map.Mouse_Right_Held(displayx, displayy); + } + + /* + ** Normal actions occur when the mouse button is released. + */ + if (flags & RIGHTRELEASE) { + Map.Mouse_Right_Release(cell, displayx, displayy, object, action); + } + return (GadgetClass::Action(0, key)); } @@ -3309,10 +3347,8 @@ int DisplayClass::TacticalClass::Selection_At_Mouse(unsigned flags, KeyNumType& } else { x = Get_Mouse_X(); y = Get_Mouse_Y(); - - if (x == 0 || y == 199 || x == 319) - edge = true; } + edge = Map.Is_Mouse_At_Tactical_View_Edge(x, y); COORDINATE coord = Map.Pixel_To_Coord(x, y); CELL cell = Coord_Cell(coord); @@ -3383,10 +3419,9 @@ int DisplayClass::TacticalClass::Command_Object(unsigned flags, KeyNumType& key) } else { x = Get_Mouse_X(); y = Get_Mouse_Y(); - - if (x == 0 || y == 199 || x == 319) - edge = true; } + edge = Map.Is_Mouse_At_Tactical_View_Edge(x, y); + COORDINATE coord = Map.Pixel_To_Coord(x, y); CELL cell = Coord_Cell(coord); @@ -3447,30 +3482,81 @@ int DisplayClass::TacticalClass::Command_Object(unsigned flags, KeyNumType& key) *=============================================================================================*/ void DisplayClass::Mouse_Right_Press(void) { - if (PendingObjectPtr && PendingObjectPtr->Is_Techno()) { - // PendingObjectPtr->Transmit_Message(RADIO_OVER_OUT); - PendingObjectPtr = 0; - PendingObject = 0; - PendingHouse = HOUSE_NONE; - Set_Cursor_Shape(0); - } else { - if (IsRepairMode) { - IsRepairMode = false; + if (IsRubberBand || IsTentative) { + return; + } + IsTentativeScrollCoast = true; + + ScrollCoastPressPointX = Get_Mouse_X(); + ScrollCoastPressPointY = Get_Mouse_Y(); +} + +void DisplayClass::Mouse_Right_Held(int x, int y) +{ + if (IsTentativeScrollCoast && Options.UseRightClickScrollCoast) { + if (abs(ScrollCoastPressPointX - x) > 8 || abs(ScrollCoastPressPointY - y) > 8) { + IsTentativeScrollCoast = false; + IsScrollCoasting = true; + } + } + + if (IsScrollCoasting && !IsRubberBand) { + COORDINATE ScrollCoastPressCoord = XY_Coord(ScrollCoastPressPointX, ScrollCoastPressPointY); + COORDINATE MouseCoord = XY_Coord(x, y); + + int base_scroll_rate = 1 * Options.ScrollCoastRate; + int ScrollLeptons = base_scroll_rate * abs(Distance(ScrollCoastPressCoord, MouseCoord)); + + // scroll rate sensitivity modifier + if (ScrollLeptons > 0) { + ScrollLeptons /= 6; + } + + int maxLeptons = 8 * CELL_LEPTON_W; + + // Clamp to maxLeptons max + ScrollLeptons = min(maxLeptons, ScrollLeptons); + + DirType dir = Direction(ScrollCoastPressCoord, MouseCoord); + int control = Dir_Facing(dir); + + Override_Mouse_Shape((MouseType)(MOUSE_N + control), false); + + Scroll_Map(dir, ScrollLeptons, true); + } +} + +void DisplayClass::Mouse_Right_Release(CELL cell, int x, int y, ObjectClass* object, ActionType action, bool wsmall) +{ + if (!IsScrollCoasting) { + if (PendingObjectPtr && PendingObjectPtr->Is_Techno()) { + // PendingObjectPtr->Transmit_Message(RADIO_OVER_OUT); + PendingObjectPtr = 0; + PendingObject = 0; + PendingHouse = HOUSE_NONE; + Set_Cursor_Shape(0); } else { - if (IsSellMode) { - IsSellMode = false; + if (IsRepairMode) { + IsRepairMode = false; } else { - if (IsTargettingMode != SPC_NONE) { - IsTargettingMode = SPC_NONE; + if (IsSellMode) { + IsSellMode = false; } else { - Unselect_All(); + if (IsTargettingMode != SPC_NONE) { + IsTargettingMode = SPC_NONE; + } else { + Unselect_All(); + } } } } + + // If it breaks... call 228. + Set_Default_Mouse(MOUSE_NORMAL, Map.IsSmall); } - // If it breaks... call 228. - Set_Default_Mouse(MOUSE_NORMAL, Map.IsSmall); + IsTentativeScrollCoast = false; + IsScrollCoasting = false; } /*********************************************************************************************** @@ -3499,6 +3585,10 @@ void DisplayClass::Mouse_Left_Up(CELL cell, bool shadow, ObjectClass* object, Ac { IsTentative = false; + if (IsScrollCoasting || IsTentativeScrollCoast) { + return; + } + TARGET target = TARGET_NONE; if (object != NULL) { target = object->As_Target(); @@ -4102,7 +4192,7 @@ void DisplayClass::Mouse_Left_Release(CELL cell, int x, int y, ObjectClass* obje *=============================================================================================*/ void DisplayClass::Mouse_Left_Press(int x, int y) { - if (!IsRepairMode && !IsSellMode && IsTargettingMode == SPC_NONE && !PendingObject) { + if (!IsRepairMode && !IsSellMode && IsTargettingMode == SPC_NONE && !PendingObject && !IsScrollCoasting) { IsTentative = true; BandX = x; BandY = y; @@ -4128,7 +4218,7 @@ void DisplayClass::Mouse_Left_Press(int x, int y) *=============================================================================================*/ void DisplayClass::Mouse_Left_Held(int x, int y) { - if (IsRubberBand) { + if (IsRubberBand && !IsScrollCoasting) { if (x != NewX || y != NewY) { x = Bound(x, 0, Lepton_To_Pixel(TacLeptonWidth) - 1); y = Bound(y, 0, Lepton_To_Pixel(TacLeptonHeight) - 1); diff --git a/redalert/display.h b/redalert/display.h index e4219adc..df18fd43 100644 --- a/redalert/display.h +++ b/redalert/display.h @@ -180,6 +180,7 @@ class DisplayClass : public MapClass virtual void Set_Tactical_Position(COORDINATE coord); void Refresh_Band(void); void Select_These(COORDINATE coord1, COORDINATE coord2, bool additive = false); + bool Is_Mouse_At_Tactical_View_Edge(int x, int y); COORDINATE Pixel_To_Coord(int x, int y) const; bool Coord_To_Pixel(COORDINATE coord, int& x, int& y) const; bool Push_Onto_TacMap(COORDINATE& source, COORDINATE& dest); @@ -222,11 +223,14 @@ class DisplayClass : public MapClass protected: virtual void Mouse_Right_Press(void); + virtual void Mouse_Right_Release(CELL cell, int x, int y, ObjectClass* object, ActionType action, bool wsmall = false); + virtual void Mouse_Right_Held(int x, int y); + + virtual void Mouse_Left_Press(int x, int y); virtual void Mouse_Left_Up(CELL cell, bool shadow, ObjectClass* object, ActionType action, bool wsmall = false); virtual void Mouse_Left_Held(int x, int y); - virtual void - Mouse_Left_Release(CELL cell, int x, int y, ObjectClass* object, ActionType action, bool wsmall = false); + virtual void Mouse_Left_Release(CELL cell, int x, int y, ObjectClass* object, ActionType action, bool wsmall = false); public: /* @@ -282,6 +286,14 @@ class DisplayClass : public MapClass */ unsigned IsTentative : 1; + // Right click scroll coasting like in Red Alert 2 & Yuri's revenge. When holding + // the right mouse button and moving the mouse around the map gets scrolled + unsigned IsScrollCoasting; + + // When the right mouse button is held down this flag gets set, if held and the mouse is + // dragged a sufficient distance, then IsScrollCoasting will be set to true + unsigned IsTentativeScrollCoast; + /* ** This gadget class is used for capturing input to the tactical map. All mouse input ** will be routed through this gadget. @@ -290,7 +302,7 @@ class DisplayClass : public MapClass { public: TacticalClass(void) - : GadgetClass(0, 0, 0, 0, LEFTPRESS | LEFTRELEASE | LEFTHELD | LEFTUP | RIGHTPRESS, true){}; + : GadgetClass(0, 0, 0, 0, LEFTPRESS | LEFTRELEASE | LEFTHELD | LEFTUP | RIGHTPRESS | RIGHTRELEASE | RIGHTHELD, true){}; int Selection_At_Mouse(unsigned flags, KeyNumType& key); int Command_Object(unsigned flags, KeyNumType& key); @@ -323,6 +335,11 @@ class DisplayClass : public MapClass int BandX, BandY; int NewX, NewY; + // When right clickign record the position of the mouse at time of the click + // this is to record the origin point of a scroll coast, the scroll coasting + // direction is calculated from this origin. + int ScrollCoastPressPointX, ScrollCoastPressPointY; + static void const* ShadowShapes; static unsigned char ShadowTrans[(SHADOW_COL_COUNT + 1) * 256]; diff --git a/redalert/options.cpp b/redalert/options.cpp index 52a064a3..259bbb83 100644 --- a/redalert/options.cpp +++ b/redalert/options.cpp @@ -86,6 +86,8 @@ char const* const OptionsClass::HotkeyName = "WinHotkeys"; OptionsClass::OptionsClass(void) : GameSpeed(3) , ScrollRate(3) + , UseRightClickScrollCoast(true) + , ScrollCoastRate(3) , Volume(".40") , // was .295 ScoreVolume(".25") @@ -566,6 +568,8 @@ void OptionsClass::Load_Settings(void) static char const* const OPTIONS = "Options"; GameSpeed = ini.Get_Int(OPTIONS, "GameSpeed", GameSpeed); ScrollRate = ini.Get_Int(OPTIONS, "ScrollRate", ScrollRate); + UseRightClickScrollCoast = ini.Get_Bool(OPTIONS, "UseRightClickScrollCoast", true); + ScrollCoastRate = ini.Get_Int(OPTIONS, "ScrollCoastRate", ScrollRate); Set_Brightness(ini.Get_Fixed(OPTIONS, "Brightness", Brightness)); Set_Sound_Volume(ini.Get_Fixed(OPTIONS, "Volume", Volume), false); Set_Score_Volume(ini.Get_Fixed(OPTIONS, "ScoreVolume", ScoreVolume), false); @@ -723,6 +727,8 @@ void OptionsClass::Save_Settings(void) static char const* const OPTIONS = "Options"; ini.Put_Int(OPTIONS, "GameSpeed", GameSpeed); ini.Put_Int(OPTIONS, "ScrollRate", ScrollRate); + ini.Put_Bool(OPTIONS, "UseRightClickScrollCoast", UseRightClickScrollCoast); + ini.Put_Int(OPTIONS, "ScrollCoastRate", ScrollCoastRate); ini.Put_Fixed(OPTIONS, "Brightness", Brightness); ini.Put_Fixed(OPTIONS, "Volume", Volume); #ifdef FIXIT_VERSION_3 diff --git a/redalert/options.h b/redalert/options.h index ad1c1d5e..8e6ee98d 100644 --- a/redalert/options.h +++ b/redalert/options.h @@ -80,6 +80,8 @@ class OptionsClass unsigned int GameSpeed; int ScrollRate; // Distance to scroll. + bool UseRightClickScrollCoast; // Enable scrolling map with right mouse button held + int ScrollCoastRate; // Rate for Scroll coasting fixed Volume; // Volume for sound effects. fixed ScoreVolume; // Volume for scores. #ifdef FIXIT_VERSION_3 diff --git a/redalert/radar.cpp b/redalert/radar.cpp index c01b4195..caddb0a4 100644 --- a/redalert/radar.cpp +++ b/redalert/radar.cpp @@ -1683,6 +1683,10 @@ int RadarClass::RTacticalClass::Action(unsigned flags, KeyNumType& key) return (false); } + if (Map.IsScrollCoasting) { + return (false); + } + /* ** Disable processing if the player names are up */ diff --git a/redalert/scroll.cpp b/redalert/scroll.cpp index 3be4d6d5..08eb979c 100644 --- a/redalert/scroll.cpp +++ b/redalert/scroll.cpp @@ -91,7 +91,7 @@ void ScrollClass::AI(KeyNumType& input, int x, int y) /* ** If rubber band mode is in progress, then don't allow scrolling of the tactical map. */ - if (!IsRubberBand /*&& !IsTentative*/) { + if (!IsRubberBand && !IsScrollCoasting /*&& !IsTentative*/) { /* ** Special check to not scroll within the special no-scroll regions. diff --git a/redalert/sidebar.cpp b/redalert/sidebar.cpp index d6b27978..d1091647 100644 --- a/redalert/sidebar.cpp +++ b/redalert/sidebar.cpp @@ -2025,8 +2025,9 @@ int SidebarClass::StripClass::SelectClass::Action(unsigned flags, KeyNumType& ke */ FactoryClass* factory = PlayerPtr->Fetch_Factory(otype); - Map.Override_Mouse_Shape(MOUSE_NORMAL); - + if (!Map.IsScrollCoasting) { + Map.Override_Mouse_Shape(MOUSE_NORMAL); + } if (index < Strip->BuildableCount) { if (otype != RTTI_SPECIAL) { choice = Fetch_Techno_Type(otype, oid); @@ -2047,7 +2048,7 @@ int SidebarClass::StripClass::SelectClass::Action(unsigned flags, KeyNumType& ke /* ** Display the help text if the mouse is over the button. */ - if (flags & LEFTUP) { + if ((flags & LEFTUP) && !Map.IsScrollCoasting) { Map.Help_Text(SpecialWeaponHelp[spc], X, Y, scheme->Color, true); flags &= ~LEFTUP; } @@ -2056,14 +2057,14 @@ int SidebarClass::StripClass::SelectClass::Action(unsigned flags, KeyNumType& ke ** A right mouse button signals "cancel". If we are in targeting ** mode then we don't want to be any more. */ - if (flags & RIGHTPRESS) { + if ((flags & RIGHTPRESS) && !Map.IsScrollCoasting) { Map.IsTargettingMode = SPC_NONE; } /* ** A left mouse press signal "activate". If our weapon type is ** available then we should activate it. */ - if (flags & LEFTPRESS) { + if ((flags & LEFTPRESS) && !Map.IsScrollCoasting) { if ((unsigned)spc < SPC_COUNT) { if (PlayerPtr->SuperWeapon[spc].Is_Ready()) { @@ -2087,7 +2088,7 @@ int SidebarClass::StripClass::SelectClass::Action(unsigned flags, KeyNumType& ke /* ** Display the help text if the mouse is over the button. */ - if (flags & LEFTUP) { + if ((flags & LEFTUP) && !Map.IsScrollCoasting) { Map.Help_Text(choice->Full_Name(), X, Y, scheme->Color, true); Map.Set_Cost(choice->Cost_Of() * PlayerPtr->CostBias); flags &= ~LEFTUP; @@ -2096,7 +2097,7 @@ int SidebarClass::StripClass::SelectClass::Action(unsigned flags, KeyNumType& ke /* ** A right mouse button signals "cancel". */ - if (flags & RIGHTPRESS) { + if ((flags & RIGHTPRESS) && !Map.IsScrollCoasting) { /* ** If production is in progress, put it on hold. If production is already @@ -2129,7 +2130,7 @@ int SidebarClass::StripClass::SelectClass::Action(unsigned flags, KeyNumType& ke } } - if (flags & LEFTPRESS) { + if ((flags & LEFTPRESS) && !Map.IsScrollCoasting) { /* ** If there is already a factory attached to this strip but the player didn't click