Python如何实现简单DNF脚本

尹何平
2023-12-01

效果

https://v.qq.com/x/page/m09169jorj0.html?vuid24=UQ4gZ9NuNjFsyWtJ%2FXiQNw%3D%3D&ptag=2_7.3.6.19976_copy

前言

在此声明,本博客仅作学习交流使用,不可用于任何商业途径,对产生的任何影响,本人概不负责。如有侵权,请联系删除。

本文参考自https://blog.csdn.net/chijiandi/article/details/85276661#_1

       玩了好几年dnf,虽然脱坑了,正好有个兴趣,就做了一个简陋的脚本。代码水平有限,正在努力学习中。

使用环境

语言:python

工具:pycharm

实现

键盘模拟

win10使用键盘模拟时,开启测试模式。

     bcdedit /set testsigning on

 

图像分析

1.定位怪物和门的坐标

首先需要定位人物和怪物,本文修改了npk文件,怪物大小设置25*25,颜色为纯色。通过找色块找到位置。

author = 'Yanxiang'

version = '1.0'

date = '10/06/2019'

import cv2

import numpy as np

import time


def detective(img_path,flag):
    kernel_4 = np.ones((4, 4), np.uint8)  # 4x4的卷积核
    def separate_color_yellow(hsv):
        # 怪物颜色提取
        lower_hsv = np.array([0, 0, 255])  # 提取颜色的低值..
        high_hsv = np.array([0, 0, 255])  # 提取颜色的高值
        mask = cv2.inRange(hsv, lowerb=lower_hsv, upperb=high_hsv)
        print("怪物坐标提取完成")
        return mask

    def separate_color_door(hsv):
        # 门颜色提取
        lower_hsv = np.array([120, 255, 255])  # 提取颜色的低值
        high_hsv = np.array([120, 255, 255])  # 提取颜色的高值
        mask = cv2.inRange(hsv, lowerb=lower_hsv, upperb=high_hsv)
        print("门的坐标提取完成")
        return mask

    def erocode_dilate(mask):
        erosion = cv2.erode(mask, kernel_4, iterations=1)
        erosion = cv2.erode(erosion, kernel_4, iterations=1)
        dilation = cv2.dilate(erosion, kernel_4, iterations=1)
        dilation = cv2.dilate(dilation, kernel_4, iterations=1)
        return dilation
    # flag = 0  表示找到怪物的平均坐标
    # flag = 1  表示找到人物坐标
    def contours(dilation, flag):
        # target是把原图中的非目标颜色区域去掉剩下的图像
        target = cv2.bitwise_and(Img, Img, mask=dilation)
        # 将滤波后的图像变成二值图像放在binary中
        ret, binary = cv2.threshold(dilation, 127, 255, cv2.THRESH_BINARY)
        # 在binary中发现轮廓,轮廓按照面积从小到大排列
        img_contours, contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        p = 0
        inumber = 1
        # 新建数组存放所有怪物 X,Y坐标的中心值
        monster_x = []
        monster_Y = []
        # pos_x =0
        # pos_y =0
        if(len(contours)!=0):
            for i in contours:  # 遍历所有的轮廓
                x, y, w, h = cv2.boundingRect(i)  # 将轮廓分解为识别对象的左上角坐标和宽、高
                if w * h > 100:
                    if flag == 0:
                        p += 1
                        print('第 %d 个怪物的底中的位置为' % (p), x + w / 2, y + h )
                        monster_x.append(x + w / 2)
                        monster_Y.append(y + h )
                        return monster_x,monster_Y
                    if flag == 2:
                        p += 1
                        print('第 %d 个的中心像素位置为' % (p), x + w / 2, y + h / 2)
                        pos_xx = x
                        pos_yy = y
                        return pos_xx,pos_yy
        else:
            if flag == 0:
                return monster_x,monster_Y
            if flag == 2:
                return 0,0

    Img = cv2.imread(img_path)#读入一幅图像
    if Img is not None:#判断图片是否读入
        HSV = cv2.cvtColor(Img, cv2.COLOR_BGR2HSV)#把BGR图像转换为HSV格式
        # 提取颜色
        if flag==0:
            mask_yellow = separate_color_yellow(HSV)
            dilation_yellow = erocode_dilate(mask_yellow)
            pos_x,pos_y = contours(dilation_yellow,flag)
            return pos_x,pos_y

        elif flag==2:
            mask_door = separate_color_door(HSV)
            dilation_red = erocode_dilate(mask_door)
            pos_x,pos_y = contours(dilation_red,flag)
            return pos_x,pos_y

2.定位人的坐标

和上面差不多,不贴了。还有另外一种方法定位的人物。

3.窗口定位与模拟键盘

首先将屏幕置于最前窗口。

class setf():
    def __init__(self):
        self.gamename = '地下城与勇士'
        self.shell = win32com.client.Dispatch("WScript.Shell")
        self.dll = CDLL("user32.dll")

    def setfocus(self):
        # pid = self.get_pid_for_pname(self.gamename)
        # if pid:
        #     print(222)
        #     for hwnd in self.get_hwnds_for_pid(pid):
        # 获得窗口句柄
        hwnd = win32gui.FindWindow(None, "地下城与勇士")
        # 将dnf窗口保持焦点
        self.shell.SendKeys('%')
        self.dll.LockSetForegroundWindow(2)
        if self.dll.IsIconic(hwnd):
            win32gui.SendMessage(hwnd, win32con.WM_SYSCOMMAND, win32con.SC_RESTORE, 0)
        self.dll.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0,
                              win32con.SWP_NOSIZE | win32con.SWP_NOMOVE)
        self.dll.SetForegroundWindow(hwnd)
        self.dll.SetActiveWindow(hwnd)

移动 参考模拟按键

完整的按键模拟方法包引用自网络。

import rabird.winio
import time
import atexit

# KeyBoard Commands
# Command port
KBC_KEY_CMD = 0x64
# Data port
KBC_KEY_DATA = 0x60

__winio = None

def __get_winio():
    global __winio

    if __winio is None:
            __winio = rabird.winio.WinIO()
            def __clear_winio():
                    global __winio
                    __winio = None
            atexit.register(__clear_winio)

    return __winio

def wait_for_buffer_empty():
    '''
    Wait keyboard buffer empty
    '''

    winio = __get_winio()

    dwRegVal = 0x02
    while (dwRegVal & 0x02):
            dwRegVal = winio.get_port_byte(KBC_KEY_CMD)

def key_down(scancode):
    winio = __get_winio()
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_CMD, 0xd2);
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_DATA, scancode)

def SPkey_down(scancode):
    winio = __get_winio()

    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_CMD, 0xd2);
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_DATA, 0xe0)
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_CMD, 0xd2);
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_DATA, scancode)

def key_up(scancode):
    winio = __get_winio()
    wait_for_buffer_empty();
    winio.set_port_byte( KBC_KEY_CMD, 0xd2);
    wait_for_buffer_empty();
    winio.set_port_byte( KBC_KEY_DATA, scancode | 0x80);

def SPkey_up(scancode):
    winio = __get_winio()
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_CMD, 0xd2);
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_DATA, 0xe0)
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_CMD, 0xd2);
    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_DATA, scancode | 0x80)

def mouse_down():
    winio = __get_winio()

    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_CMD, 0xd3);
    wait_for_buffer_empty();
    winio.set_port_dword(KBC_KEY_DATA, 0x09)

def mouse_up():
    winio = __get_winio()

    wait_for_buffer_empty();
    winio.set_port_byte(KBC_KEY_CMD, 0xd3);
    wait_for_buffer_empty();
    winio.set_port_dword(KBC_KEY_DATA, 0x08)

def key_press(scancode, press_time = 0.05):
    key_down( scancode )
    time.sleep( press_time )
    key_up( scancode )
    time.sleep(0.05)

def SPkey_press(scancode, press_time = 0.05):
    SPkey_down( scancode )
    time.sleep( press_time )
    SPkey_up( scancode )
    time.sleep(press_time)

def mouse_clicked(clicked_time = 0.05):
    mouse_down()
    time.sleep( clicked_time )
    mouse_up()
    time.sleep( clicked_time )

https://blog.csdn.net/qq_37232329/article/details/79926440

上面的链接为扫描码

4.人物移动

从上面得知人物、怪物、门的坐标,可以移动。在移动之前,先计算移动速度。

主要代码如下:

def  moveTo(monsters_x,monsters_y,person_x,person_y,flag = 0):
    ''' flag = 1 时,为 人物 向 门口 移动
        flag = 0 时,为 人物 向 怪物 移动 , 距离怪物 80 个像素时 , 释放技能

    '''

    if(flag==0):
        # 释放技能的位置,在怪物前方60像素
        skill_pos = 60
    else:
        skill_pos = 0
    # 1 怪物 在 人物 的 右上
    if((person_x - monsters_x)<0 and (person_y - monsters_y) >0):
        if(monsters_x - person_x <skill_pos):
            print('距离右边怪物挺近,直接往上移动')
            key_press(0x34)
            key_press(0x25, abs(person_y - monsters_y) / y_speed)
        else:
            move_time = abs(monsters_x - person_x - skill_pos) / x_speed - abs(person_y - monsters_y) / y_speed
            if (move_time > 0):
                print('跑啊跑,往右上移动')
                key_press(0x34)
                key_down(0x34)
                time.sleep(0.1)
                key_press(0x25, abs(person_y - monsters_y) / y_speed)
                time.sleep(move_time)
                key_up(0xb4)
            else:
                print('不值得跑,先往右,后往上')
                key_press(0x34)
                key_press(0x34, abs(monsters_x -skill_pos- person_x) / x_speed)
                key_press(0x25, abs(person_y - monsters_y) / y_speed)

5.释放技能

剑魂的技冷却时间,释放技能时间。此方法计算的是可用的技能。可以继续优化,如:加入技能释放范围;或者按照一定顺序计算冷却时间最短的技能。

list_to_time = [0,0,0,0,0,0,0,0,0,0,0]
list_skill =         ['q', 'e', 'w', 'd','t','y','a','s','f','g','h']
list_skill_cooling = [  6.8, 9.6, 32,  9,   37, 103, 22,  0,  17.5,36, 120]
skill_release_time = [  0.5, 0.5, 3,   0.5, 0.5, 7, 5,    0.3, 1,  1.5, 2]
list_key           = [0x10,0x12,0x11,0x20,0x14,0x15,0x1e,0x1f,0x21,0x22,0x23]
def rel_skill(skill,ini_time):
    for i in list_skill:
        while(skill==i):
            list_to_time[list_skill.index(i)] = ini_time + list_skill_cooling[list_skill.index(i)] + skill_release_time[list_skill.index(i)]
            f = open('data.txt','w')
            for j in range(len(list_skill)):
                f.write(str(list_to_time[j])+'\n')
            f.close()
            key_press(list_key[list_skill.index(i)])
            time.sleep(skill_release_time[list_skill.index(i)]+0.8)
            break
    return

 

屏幕快照

截图

import win32gui,win32ui,win32con,win32api
from key import *
def window_capture(filename,flag = 0):
    hwnd = win32gui.FindWindow(None, "地下城与勇士")
    # 根据窗口句柄获取窗口的设备上下文DC(Divice Context)
    hwndDC = win32gui.GetWindowDC(hwnd)
    win32gui.SetForegroundWindow(hwnd)
    # 根据窗口的DC获取mfcDC
    mfcDC = win32ui.CreateDCFromHandle(hwndDC)
    # mfcDC创建可兼容的DCW
    saveDC = mfcDC.CreateCompatibleDC()
    # 创建bigmap准备保存图片
    saveBitMap = win32ui.CreateBitmap()
    # 获取监控器信息
    left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    width = right - left
    height = bottom - top
    saveBitMap.CreateCompatibleBitmap(mfcDC, width, height)
    # 高度saveDC,将截图保存到saveBitmap中
    saveDC.SelectObject(saveBitMap)
    # 截取从左上角(0,0)长宽为(w,h)的图片
    if flag==0:
        saveDC.BitBlt((0, 0), (width, height), mfcDC, (0, 0), win32con.SRCCOPY)
        saveBitMap.SaveBitmapFile(saveDC, filename)
    else:
        saveDC.BitBlt((720, 795), (75, 60), mfcDC, (0, 0), win32con.SRCCOPY)
        saveBitMap.SaveBitmapFile(saveDC, './pos.png')


    return width, height

结语

水平有限,花了5天时间修改npk文件,10几天写代码。初学python,我所会的就只是一些基础以及一丁点制作思路,没有后续的成品,谢谢。

 类似资料: