C#/C# 리니지m/

C#으로 만드는 리니지m 매크로 (3)함수반복과 베르설정

2019. 10. 7.

2019/09/27 - [프로그래밍/C# 리니지m] - C#으로 만드는 리니지m 매크로 (1)이미지서치

2019/10/02 - [프로그래밍/C# 리니지m] - C#으로 만드는 리니지m 매크로 (2-1)hp인식 - tesseract

2019/10/03 - [프로그래밍/C# 리니지m] - C#으로 만드는 리니지m 매크로 (2-2)hp인식 - 픽셀서치

 

이번 시간에는 그동안 만들었던 함수들을 반복시행하게 만들고

일정 HP이하에서 베르할수있도록 만들어보겠습니다.

 

우선

위와 같이 label 1

textbox 1

button 1개를

추가해줍니다.

 

목표는 textbox에 적은 수치보다 현재 hp 작아지면

특정좌표를 클릭(귀환 주문서) 하게 만드는것입니다.

 

우선 이번시간의 메인코드를 만들어봅시다.

private void function_ber(object sender, EventArgs e)
        {
            if (textBox1.Text != null || textBox1.Text != " ")
            {
                button1.PerformClick();
                button2.PerformClick();
                button3.PerformClick();
                button4.PerformClick();
            }
            // 프로그램명 입력이 되어있는 상태면 버튼1,2,3,4 를 눌러준다

            string sethp = textBox2.Text;
            // 입력한 값을 sethp에 저장

            float hp = Convert.ToSingle(sethp);
            // 크기비교를 위해 sethp값을 float형으로 변환

            float currenthp = Convert.ToSingle(label3.Text);
            // 크기비교를 위해 픽셀서치로 찾은 HP%를 float형으로 변환 획득            
                        

            if (hp >= currenthp && bercheck == false) // 설정한 hp 가 현재hp 보다 작아지면 & 베르를 한적이없으면
            {
                IntPtr xy = new IntPtr(1205 | (630 << 16));
                // 8번 슬롯의 좌표값 (1280 x 720 해상도기준)

                // 클릭이벤트 밑에서 설명

                bercheck = true;
                // 베르 하였으므로 체크변수 true
            }
        }
        

 

설명은 주석으로 달아놨습니다.

 

아직 설명드리지 못한 부분때문에(주석 밑에서 설명부분) 완성된 코드는아닙니다.

우선 처음 이미지서치할때 썼던 핸들러를 전역에서 사용하기위해

전역변수 선언, 최초 이미지서치 코드 일부분을 수정하여

다음과 같이 만들겠습니다

 

전역 변수 선언

IntPtr handel;

 

이미지서치부분 코드 수정

private void Button1_Click(object sender, EventArgs e)
        {
            string a = textBox1.Text;

            IntPtr b = FindWindow(null, a);

            IntPtr c = FindWindowEx(b, 0, "RenderWindow", "TheRender");
            handle = c; //<<< 이부분 추가
            // 전역 변수 handle에 핸들값 c를저장

            Graphics gdata = Graphics.FromHwnd(c);

            Rectangle rect = Rectangle.Round(gdata.VisibleClipBounds);

            Bitmap bmp = new Bitmap(rect.Width, rect.Height);

            using (Graphics g = Graphics.FromImage(bmp))
            {
                IntPtr hdc = g.GetHdc();
                PrintWindow(c, hdc, 0x2);
                g.ReleaseHdc(hdc);
            }

            screen = new Bitmap(bmp, new Size(1280, 720));

            pictureBox1.Image = screen;
        }
        

그리고 획득한 핸들러값을 통해 해당 프로세스에

명령호출(키보드&마우스입력)을 해주는 함수를 사용하기위해

추가적인 DllImport를 해주겠습니다.

[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll")]
public static extern bool SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

 

명령호출 함수는 위에서 보는바와 같이 두종류가 있습니다.

PostmessageSendmessage.

간단히 설명드리자면

Postmessage는 명령 스택에 차곡차곡 쌓아서 순서대로 호출

(다른함수와 순서가 섞일수도있음)

Sendmessage는 즉각적으로 처리

(반응성이 빠르나 컴퓨터사양에 따라 실행안될수도있음)

이라고 볼수있습니다.

저는 개인적으로는 매크로 프로그램에서는 Sendmessage가 좋다고 생각합니다.

두 함수가 사용법은 동일하니 상황에 따라서 바꿔주시면 될듯합니다.

 

그럼 아까 완성하지 못했던 코드를 완성해 봅시다.

private void function_ber(object sender, EventArgs e)
        {
            if (textBox1.Text != null || textBox1.Text != " ")
            {
                button1.PerformClick();
                button2.PerformClick();
                button3.PerformClick();
                button4.PerformClick();
            }
            // 프로그램명 입력이 되어있는 상태면 버튼1,2,3,4 를 눌러준다

            string sethp = textBox2.Text;
            // 입력한 값을 sethp에 저장

            float hp = Convert.ToSingle(sethp);
            // 크기비교를 위해 sethp값을 float형으로 변환

            float currenthp = Convert.ToSingle(label3.Text);
            // 크기비교를 위해 픽셀서치로 찾은 HP%를 float형으로 변환 획득            
                        

            if (hp >= currenthp && bercheck == false) // 설정한 hp 가 현재hp 보다 작아지면 & 베르를 한적이없으면
            {
                IntPtr xy = new IntPtr(1205 | (630 << 16));
                // 8번 슬롯의 좌표값 (1280 x 720 해상도기준)

                SendMessage(handle, 0x0201, IntPtr.Zero, xy);
                SendMessage(handle, 0x0202, IntPtr.Zero, xy);
                // 지정된 핸들에서 xy좌표에 대해 마우스좌측 누르기, 때기
                // SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam)
                // uint값에 넣어준 0x0201은 마우스좌버튼누르기, 0x0202는 마우스좌버튼때기

                bercheck = true;
                // 베르 하였으므로 체크변수 true
            }
        }
        

 

 

큰 알고리즘을 적어보자면

1. 프로그램명을 통해 핸들러획득

2. 획득한 핸들러를통해 이미지획득(서치)

3. 획득한 이미지에서 필요한 부분(HP부분)만 캡처

4. tesseract or 픽셀서치를통해 HP% 계산

5. 입력한 수치보다 HP가 적어지면 지정좌표클릭

이라고 볼수있겠네요.

 

그럼 이제 이 알고리즘을 일정간격으로

반복 실행하여서 자동으로 베르하게 만들어봅시다.

 

이번시간에 추가한 버튼을 클릭시 실행되되게 할예정이기에

추가한 버튼 클릭 이벤트를 생성해줍니다.

 

timer 를 이용해보도록 하겠습니다.

timer 선언

System.Wiondows.Forms.Timer timer;

 

timer 생성(Form1 안에서 해주시면됩니다.)

public Form1()
        {
            InitializeComponent();

            timer = new Timer();
            // 타이머 선언
            timer.Interval = 500;
            // 타이머 실행간격 500ms(0.5초)
            timer.Tick += new EventHandler(function_ber);
            // 타이머 실행시 호출될 함수설정
        }
        

 

버튼클릭시 timer 실행

		private void Button5_Click(object sender, EventArgs e)
        {
            if(timer.Enabled == false)
            {
                timer.Start();
                // 버튼 클릭시 타이머 실행중이 아니면 타이머 실행
            }
            else if(timer.Enabled == true)
            {
                timer.Stop();
                // 버튼 클릭시 타이머 실행중이라면 타이머 스탑
            }
        }
        

 

테스트를 해봅시다.

 

 

전체코드

더보기
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Tesseract;

namespace imagesearch
{
    public partial class Form1 : Form
    {
        [DllImport("User32", EntryPoint = "FindWindow")]
        private static extern IntPtr FindWindow(string IpClassName, string IpWindowName);

        [DllImport("user32")]
        private static extern IntPtr FindWindowEx(IntPtr hWnd1, int hWnd2, string lp1, string lp2);

        [DllImport("user32.dll")]
        internal static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcblt, int nFlags);

        [DllImport("user32.dll")]
        public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern bool SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        Bitmap screen;

        IntPtr handle;

        Timer timer;

        bool bercheck = false;

        public Form1()
        {
            InitializeComponent();

            timer = new Timer();
            // 타이머 선언
            timer.Interval = 500;
            // 타이머 실행간격 500ms(0.5초)
            timer.Tick += new EventHandler(function_ber);
            // 타이머 실행시 호출될 함수설정
        }
        
        private void Button1_Click(object sender, EventArgs e)
        {
            string a = textBox1.Text;

            IntPtr b = FindWindow(null, a);

            IntPtr c = FindWindowEx(b, 0, "RenderWindow", "TheRender");
            handle = c;
            // 전역 변수 handle에 핸들값 c를저장

            Graphics gdata = Graphics.FromHwnd(c);

            Rectangle rect = Rectangle.Round(gdata.VisibleClipBounds);

            Bitmap bmp = new Bitmap(rect.Width, rect.Height);

            using (Graphics g = Graphics.FromImage(bmp))
            {
                IntPtr hdc = g.GetHdc();
                PrintWindow(c, hdc, 0x2);
                g.ReleaseHdc(hdc);
            }

            screen = new Bitmap(bmp, new Size(1280, 720));

            pictureBox1.Image = screen;
        }

        private void Button2_Click(object sender, EventArgs e)
        {
            Rectangle rect = new Rectangle(130, 13, 85, 20);
            // x,y 시작좌표 x,y범위 생성

            Bitmap cap = screen.Clone(rect, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            // 캡쳐뜬 이미지에서 위의 Rect 만큼 자르기

            cap = new Bitmap(cap, new Size(170, 40));
            // 인식률을 높이기위해 두배크기로 리사이즈

            for(int i = 0; i < cap.Width; i++)
            {
                for(int j = 0; j < cap.Height; j++)
                {
                    Color c = cap.GetPixel(i, j);
                    int binary = (c.R + c.G + c.B) / 3;

                    if (binary > 190)
                        cap.SetPixel(i, j, Color.White);
                    else
                        cap.SetPixel(i, j, Color.Black);
                }
            }
            // 간단한 for을 통한 Binary(이진화)

            screen = cap;

            pictureBox2.Image = screen;
            // picturebox2 에 자르고 키우고 이진화한 이미지 띄우기
        }

        private void Button3_Click(object sender, EventArgs e)
        {
            Pix pix = PixConverter.ToPix(screen);
            
            var engine = new TesseractEngine(@"./tessdata", "eng", EngineMode.TesseractOnly);
            // tesseractengine 생성
            string whitelist = "0123456789/";
            engine.SetVariable("tessedit_char_whitelist", whitelist);
            // 인식률을 높이기위한 숫자와 '/' 만 화이트리스트 적용

            var result = engine.Process(pix);

            string HP = result.GetText();

            HP = HP.Replace(" ", "");
            // 공백제거

            label1.Text = HP;

            string[] split = HP.Split('/');
            // 현재와 맥스수치를 나눠주기 구분점 '/'
            label2.Text = "HP : " + (Convert.ToSingle(split[0]) * 100 / Convert.ToSingle(split[1])).ToString("##.##") + "%";
        }

        private void Button4_Click(object sender, EventArgs e)
        {
            Rectangle rect = new Rectangle(75, 30, 199, 4);
            // x,y 시작좌표 x,y범위 생성 hp바의 맨밑줄만 떠오는것

            Bitmap cap = new Bitmap(pictureBox1.Image);
            // 버튼2 클릭 이벤트에서 screen값을 바꾸었기때문에 다시 picturebox1이미지 불러오기

            cap = cap.Clone(rect, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            // 캡쳐뜬 이미지에서 위의 Rect 만큼 자르기

            cap = new Bitmap(cap, new Size(174, 10));
            // 보기좋게 picturebox3과 같은 크기로 리사이즈

            pictureBox3.Image = cap;
            // picturebox3에 이미지 띄우기

            int point = 0;

            for(int i = cap.Width-1; i > 0; i--)
            {
                Color c = cap.GetPixel(i, cap.Height-1);
                if (c.R > 200)
                {
                    point = i;
                    break;
                }
            }
            // for 반복문을 통해 뒷 픽셀부터 확인하여 red값이 200이넘는 픽셀위치찾기

            label3.Text = (100 * point / cap.Width).ToString("##.##");
            // 찾은 픽셀 위치를 전체 범위에서 몇퍼센트인지 label3에 출력
        }

        private void Button5_Click(object sender, EventArgs e)
        {
            if(timer.Enabled == false)
            {
                timer.Start();
            }
            else if(timer.Enabled == true)
            {
                timer.Stop();
            }
        }

        private void function_ber(object sender, EventArgs e)
        {
            if (textBox1.Text != null || textBox1.Text != " ")
            {
                button1.PerformClick();
                button2.PerformClick();
                button3.PerformClick();
                button4.PerformClick();
            }
            // 프로그램명 입력이 되어있는 상태면 버튼1,2,3,4 를 눌러준다

            string sethp = textBox2.Text;
            // 입력한 값을 sethp에 저장

            float hp = Convert.ToSingle(sethp);
            // 크기비교를 위해 sethp값을 float형으로 변환

            float currenthp = Convert.ToSingle(label3.Text);
            // 크기비교를 위해 픽셀서치로 찾은 HP%를 float형으로 변환 획득            
                        

            if (hp >= currenthp && bercheck == false) // 설정한 hp 가 현재hp 보다 작아지면 & 베르를 한적이없으면
            {
                IntPtr xy = new IntPtr(1205 | (630 << 16));
                // 8번 슬롯의 좌표값 (1280 x 720 해상도기준)

                SendMessage(handle, 0x0201, IntPtr.Zero, xy);
                SendMessage(handle, 0x0202, IntPtr.Zero, xy);
                // 지정된 핸들에서 xy좌표에 대해 마우스좌측 누르기, 때기
                // SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam)
                // uint값에 넣어준 0x0201은 마우스좌버튼누르기, 0x0202는 마우스좌버튼때기

                bercheck = true;
                // 베르 하였으므로 체크변수 true
            }
        }
    }
}