public FishingSpotData(Coordinates scan1, Coordinates scan2,
     ScreenshotColor bubbleColor, AbstractFishingRodAction.Tolerance tolerance)
 {
     this.Scan1 = scan1;
     this.Scan2 = scan2;
     this.BubbleColor = bubbleColor;
     this.Tolerance = tolerance;
 }
        protected override sealed async Task FinishThrowFishingRodAsync(IInteractionProvider provider)
        {
            // Simply throw the fishing rod straight, without checking for bubbles.
            Coordinates coords = new Coordinates(800, 1009);
            var pos = provider.GetCurrentWindowPosition();
            coords = pos.RelativeToAbsoluteCoordinates(pos.ScaleCoordinates(coords,
                MouseHelpers.ReferenceWindowSize));

            provider.MoveMouse(coords);
            await provider.WaitAsync(300);
            provider.ReleaseMouseButton();
        }
        public static async Task DoSimpleMouseClickAsync(IInteractionProvider provider,
            Coordinates coords, VerticalScaleAlignment valign = VerticalScaleAlignment.Center,
            int buttonDownTimeout = 200)
        {
            var pos = provider.GetCurrentWindowPosition();
            coords = pos.RelativeToAbsoluteCoordinates(pos.ScaleCoordinates(coords,
                ReferenceWindowSize, valign));

            provider.MoveMouse(coords);
            provider.PressMouseButton();
            await provider.WaitAsync(buttonDownTimeout);
            provider.ReleaseMouseButton();
        }
        public override sealed async Task RunAsync(IInteractionProvider provider)
        {
            // Click on the Speedchat Icon.
            Coordinates c = new Coordinates(122, 40);
            await MouseHelpers.DoSimpleMouseClickAsync(provider, c, VerticalScaleAlignment.Left, 100);

            int currentYNumber = 0;
            for (int i = 0; i < menuItems.Length; i++)
            {
                await provider.WaitAsync(300);

                currentYNumber += menuItems[i];
                c = new Coordinates(xWidths[i], (40 + currentYNumber * 38));
                await MouseHelpers.DoSimpleMouseClickAsync(provider, c, VerticalScaleAlignment.Left, 100);
            }
        }
        public override sealed async Task RunAsync(IInteractionProvider provider)
        {
            // Click on the "Plant Flower" button.
            await MouseHelpers.DoSimpleMouseClickAsync(provider, new Coordinates(76, 264),
                VerticalScaleAlignment.Left);
            await provider.WaitAsync(200);

            // Click on the jellybean fields.
            foreach (int jellybean in jellybeanCombination)
            {
                var c = new Coordinates((int)Math.Round(560 + jellybean * 60.5), 514);
                await MouseHelpers.DoSimpleMouseClickAsync(provider, c,
                    VerticalScaleAlignment.Center, 100);
                await provider.WaitAsync(100);
            }
            await provider.WaitAsync(100);

            // Click on the "Plant" button.
            await MouseHelpers.DoSimpleMouseClickAsync(provider, new Coordinates(975, 772));
        }
        /// <summary>
        /// Converts relative coordinates in the window to new absolute coordinates
        /// based on the specified reference size.
        /// Note: TT Rewritten scales its window contents accounting for the aspect ratio
        /// (at least if the width is greater than from the 4:3 resolution). Therefore
        /// we use the given VerticalScaleAlignment to correctly calculate the
        /// X coordinate (note that we require the aspect ratio to be ≥ 4:3).
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="coords">The coordinates to scale.</param>
        /// <param name="referenceSize">The size which was used to create the coordinates.
        /// This size is interpreted as being a scaled 4:3 window without retaining
        /// aspect ratio.</param>
        /// <param name="valign"></param>
        /// <returns></returns>
        public static Coordinates ScaleCoordinates(this WindowPosition pos, Coordinates coords, 
            Size referenceSize, VerticalScaleAlignment valign = VerticalScaleAlignment.Center)
        {
            double aspectWidth = pos.Size.Height / 3d * 4d;
            double widthDifference = pos.Size.Width - aspectWidth;

            int newX;
            if (valign == VerticalScaleAlignment.NoAspectRatio)
            {
                newX = (int)Math.Round((double)coords.X / referenceSize.Width * pos.Size.Width);
            }
            else
            {
                newX = (int)Math.Round((double)coords.X / referenceSize.Width * aspectWidth
                    + (valign == VerticalScaleAlignment.Left ? 0 : 
                    valign == VerticalScaleAlignment.Center ? widthDifference / 2 : widthDifference));
            }

            return new Coordinates()
            {
                X = newX,
                Y = (int)Math.Round((double)coords.Y / referenceSize.Height * pos.Size.Height)
            };
        }
 public void MoveMouse(Coordinates c) => MoveMouse(c.X, c.Y);
 public ScreenshotColor GetPixel(Coordinates coords) => GetPixel(coords.X, coords.Y);
        private Coordinates GetMouseCoordinatesFromScreenCoordinates(Coordinates screenCoords)
        {
            // Note: The mouse coordinates are relative to the primary monitor size and
            // location, not to the virtual screen size, so we use
            // SystemInformation.PrimaryMonitorSize.
            var primaryScreenSize = SystemInformation.PrimaryMonitorSize;
            
            double x = (double)0x10000 * screenCoords.X / primaryScreenSize.Width;
            double y = (double)0x10000 * screenCoords.Y / primaryScreenSize.Height;

            /* For correct conversion when converting the flointing point numbers
               to integers, we need round away from 0, e.g.
               if x = 0, res = 0
               if  0 < x ≤ 1, res =  1
               if -1 ≤ x < 0, res = -1

               E.g. if a second monitor is placed at the left hand side of the primary monitor
               and both monitors have a resolution of 1280x960, the x-coordinates of the second
               monitor would be in the range (-1280, -1) and the ones of the primary monitor
               in the range (0, 1279).
               If we would want to place the mouse cursor at the rightmost pixel of the second
               monitor, we would calculate -1 / 1280 * 65536 = -51.2 and round that down to
               -52 which results in the screen x-coordinate of -1 (whereas -51 would result in 0).
               Similarly, +52 results in +1 whereas +51 would result in 0.
               Also, to place the cursor on the leftmost pixel on the second monitor we would use
               -65536 as mouse coordinates resulting in a screen x-coordinate of -1280 (whereas
               -65535 would result in -1279).
            */
            int resX = (int)(x >= 0 ? Math.Ceiling(x) : Math.Floor(x));
            int resY = (int)(y >= 0 ? Math.Ceiling(y) : Math.Floor(y));

            return new Coordinates(resX, resY);
        }
        private void DoMouseInput(int x, int y, bool absoluteCoordinates, bool? mouseDown)
        {
            // TODO: Maybe we should instead send WM_MOUSEMOVE, WM_LBUTTONDOWN etc.
            // messages directly to the destination window so that we don't need to
            // position the mouse cursor which makes it harder e.g. to
            // click on the "Stop" button of the simulator.

            // Convert the screen coordinates into mouse coordinates.
            Coordinates cs = new Coordinates(x, y);
            cs = GetMouseCoordinatesFromScreenCoordinates(cs);

            var mi = new NativeMethods.MOUSEINPUT();
            mi.dx = cs.X;
            mi.dy = cs.Y;
            if (absoluteCoordinates)
                mi.dwFlags |= NativeMethods.MOUSEEVENTF.ABSOLUTE;
            if (!(!absoluteCoordinates && x == 0 && y == 0))
            {
                // A movement occured.
                mi.dwFlags |= NativeMethods.MOUSEEVENTF.MOVE;
            }

            if (mouseDown.HasValue)
            {
                mi.dwFlags |= mouseDown.Value ? NativeMethods.MOUSEEVENTF.LEFTDOWN 
                    : NativeMethods.MOUSEEVENTF.LEFTUP;
            }
            
            var input = new NativeMethods.INPUT();
            input.type = NativeMethods.INPUT_MOUSE;
            input.U.mi = mi;

            NativeMethods.INPUT[] inputs = { input };

            if (NativeMethods.SendInput(1, inputs, NativeMethods.INPUT.Size) == 0)
                throw new Win32Exception();
        }
        protected override sealed async Task FinishCastFishingRodAsync(IInteractionProvider provider)
        {
            // Try to find a bubble.
            const string actionInformationScanning = "Scanning for fish bubbles…";
            OnActionInformationUpdated(actionInformationScanning);

            const int scanStep = 15;

            Stopwatch sw = new Stopwatch();
            sw.Start();

            Coordinates? oldCoords = null;
            Coordinates? newCoords;
            int coordsMatchCounter = 0;
            while (true)
            {
                var screenshot = provider.GetCurrentWindowScreenshot();
                newCoords = null;

                // TODO: The fish bubble detection should be changed so that it does not scan
                // for a specific color, but instead checks that for a point if the color is
                // darker than the neighbor pixels (in some distance).
                for (int y = spotData.Scan1.Y; y <= spotData.Scan2.Y && !newCoords.HasValue; y += scanStep)
                {
                    for (int x = spotData.Scan1.X; x <= spotData.Scan2.X; x += scanStep)
                    {
                        var c = new Coordinates(x, y);
                        c = screenshot.WindowPosition.ScaleCoordinates(c,
                            MouseHelpers.ReferenceWindowSize);
                        if (CompareColor(spotData.BubbleColor, screenshot.GetPixel(c),
                            spotData.Tolerance))
                        {
                            newCoords = new Coordinates(x + 20, y + 20);
                            Coordinates scaledCoords = screenshot.WindowPosition.RelativeToAbsoluteCoordinates(
                                screenshot.WindowPosition.ScaleCoordinates(
                                newCoords.Value, MouseHelpers.ReferenceWindowSize));

                            OnActionInformationUpdated($"Found bubble at {scaledCoords.X}, {scaledCoords.Y}…");
                            break;
                        }
                    }
                }
                if (!newCoords.HasValue)
                    OnActionInformationUpdated(actionInformationScanning);


                if (newCoords.HasValue && oldCoords.HasValue
                    && Math.Abs(oldCoords.Value.X - newCoords.Value.X) <= scanStep
                    && Math.Abs(oldCoords.Value.Y - newCoords.Value.Y) <= scanStep)
                {
                    // The new coordinates are (nearly) the same as the previous ones.
                    coordsMatchCounter++;
                }
                else
                {
                    // Reset the counter and update the coordinates even if we currently didn't
                    // find them.
                    oldCoords = newCoords;
                    coordsMatchCounter = 0;
                }


                // Now position the mouse already so that we just need to release the button.
                if (!newCoords.HasValue)
                {
                    // If we couldn't find the bubble we use default destination x,y values.
                    newCoords = new Coordinates(800, 1009);
                }
                else
                {
                    // Calculate the destination coordinates.
                    newCoords = new Coordinates(
                        (int)Math.Round(800d + 120d / 429d * (800d - newCoords.Value.X) *
                        (0.75 + (820d - newCoords.Value.Y) / 820 * 0.38)),
                        (int)Math.Round(846d + 169d / 428d * (820d - newCoords.Value.Y))
                    );
                }

                // Note: Instead of using a center position for scaling the X coordinate,
                // TTR seems to interpret it as being scaled from an 4/3 ratio. Therefore
                // we need to specify "NoAspectRatio" here.
                // However it could be that they will change this in the future, then 
                // we would need to use "Center".
                // Note: We assume the point to click on is exactly centered. Otherwise
                // we would need to adjust the X coordinate accordingly.
                var coords = screenshot.WindowPosition.RelativeToAbsoluteCoordinates(
                    screenshot.WindowPosition.ScaleCoordinates(newCoords.Value,
                    MouseHelpers.ReferenceWindowSize, VerticalScaleAlignment.NoAspectRatio));
                provider.MoveMouse(coords);


                if (coordsMatchCounter == 2)
                {
                    // If we found the same coordinates two times, we assume
                    // the bubble is not moving at the moment.
                    break;
                }
                
                await provider.WaitAsync(500);

                // Ensure we don't wait longer than 36 seconds.
                if (sw.ElapsedMilliseconds >= 36000)
                    break;
            }


            // There is no need to wait here because the mouse has already been positioned and we
            // waited at least 2x 500 ms at the new position, so now just release the mouse button.
            provider.ReleaseMouseButton();
        }
 public override sealed async Task RunAsync(IInteractionProvider provider)
 {
     Coordinates c = new Coordinates(1503, 1086);
     await MouseHelpers.DoSimpleMouseClickAsync(provider, c);
 }
 public Coordinates Add(Coordinates c) => new Coordinates(X + c.X, Y + c.Y);
 public Coordinates RelativeToAbsoluteCoordinates(Coordinates c) => Coordinates.Add(c);
        /// <summary>
        /// Clicks on the fishing rod button.
        /// </summary>
        /// <param name="provider"></param>
        /// <returns></returns>
        protected async Task StartCastFishingRodAsync(IInteractionProvider provider)
        {
            Coordinates coords = new Coordinates(800, 846);
            var pos = provider.GetCurrentWindowPosition();
            coords = pos.RelativeToAbsoluteCoordinates(pos.ScaleCoordinates(coords,
                MouseHelpers.ReferenceWindowSize));

            // Move the mouse and press the button.
            provider.MoveMouse(coords);
            provider.PressMouseButton();

            await provider.WaitAsync(300);

            CheckForFishErrorDialog(provider);
        }
 public override async Task RunAsync(IInteractionProvider provider)
 {
     Coordinates c = new Coordinates(1397, 206 + (int)button * 49);
     await MouseHelpers.DoSimpleMouseClickAsync(provider, c, VerticalScaleAlignment.Right);
 }