一款激战2自动pvp机器人脚本bot
### 主函数入口
from utils.FindImgs import*
from utils.Register import*
from utils.IO import*
from utils.InitSet import*
from utils.AutoSkill import*
import utils.config as uconfig
from utils.MoveControl import*
from utils.Points import*
import multiprocessing as mp
import threading
import random
import time

class MainControl():
    def __init__(self) -> None:
        self.keyboard = 0            # 键盘类
        self.mouse = 0               # 鼠标类
        self.memory = 0              # 内存类
        self.gw2_hwnd = 0            # 窗口hwnd
        self.pvp_nums = 0            # pvp场数
        self.autokey_flag = False    # 开启按键脚本
        self.autowalk_flag = False   # 开始自动寻路
        self.dead = 0                # 1 死亡 | 2 活着
        self.skill_plan = []         # 按键方案
        self.is_leader = 0           # 是否队长模式  1 队长 | 2 队员
        self.Qcase = mp.Queue(1)     # 图像检测模式
        self.Qresult = mp.Queue(1)   # 图像检测结果
        self.title_h = 0             # 标题栏高度
        self.is_attacked = False     # 受到攻击
    
    # 获取识图结果(多进程)
    def get_det_result(self, case_str):
        if self.Qresult.full():     # 满了先清空结果
            self.Qresult.get()
        while(1):
            if self.Qcase.empty():
                self.Qcase.put(case_str)    # 空了再加入检测组别
            while(1):
                time.sleep(0.01)    
                if not self.Qresult.empty():
                    return self.Qresult.get()

    # 控制自动寻路以及决策
    def control_walk(self):
        cur_locat = 'reset'  # 当前节点
        continue_walk = 0    # 继续自动寻路标志      0 关闭 | 1 开启
        end_locat_old = '0'  # 历史节点
        mc = MoveControl(self.memory, self.keyboard)
        while(1):
            try:
                if self.autowalk_flag == True:
                    if continue_walk == 0:      # 不继承寻路时，才开启新的节点
                        maping, mapend = self.memory.getMapId()
                        map_points, map_name = readpoints(mapend)     # 地图节点
                        x, y, _, _ = self.memory.getMoveAttribute()
                        if cur_locat == 'reset':   # 只有在家里才读取位置
                            if map_points['红色家'][0] < x < map_points['红色家'][1] and map_points['红色家'][2] < y <map_points['红色家'][3]:
                                cur_locat = '红色'  # 当前位置
                                home_color = '红' # 阵营颜色
                            elif map_points['蓝色家'][0] < x < map_points['蓝色家'][1] and map_points['蓝色家'][2] < y <map_points['蓝色家'][3]:
                                cur_locat = '蓝色'  # 当前位置
                                home_color = '蓝'  # 阵营颜色
                        if cur_locat == 'reset':    # 防止路失败
                            continue
                        # 随机计算终点,当检测失效或己方已占领所有点位等到20s              
                        while(1):   # 随机计算终点，并且防止起点终点重复, 与上一次终点重复
                            end_locat = map_points['顺序'][random.randint(0, len(map_points['顺序'])-1)]
                            if end_locat != cur_locat and end_locat != end_locat_old:
                                end_locat_old = cur_locat
                                break
                        walk_path = cur_locat + '到' + end_locat
                        walk_list = map_points[walk_path]
                        color_print('INFO', f'阵营：{home_color}色 | 自动寻路：{cur_locat} --> {end_locat}')

                    walk_list, x_old, y_old = mc.walk(walk_list, angleErr=30)  # 寻路, 如果暂停则返回剩下的节点
                    if walk_list != True:   # 没寻路完成
                        while(1):   # 
                            time.sleep(0.5)
                            hp, _ = self.memory.gethp()
                            maping, _ = self.memory.getMapId()
                            if hp == 0 or maping == 350:           # 死亡或者结束比赛，恢复默认在家状态
                                cur_locat = 'reset'
                                continue_walk = 0                  # 不再继续上次寻路
                                break
                            elif self.autowalk_flag == True:
                                Px, Py, Pz, Pa = self.memory.getMoveAttribute()
                                if abs(Px - x_old) < 20 and abs(Py - y_old) < 20:
                                    continue_walk = 1              # 复活 继续上次寻路
                                    break

                    elif walk_list == True:     # 寻路到了终点
                        continue_walk = 0       # 不再继续上次寻路
                        for i in range(20):     # 等待20秒站点
                            time.sleep(1)
                            if self.autowalk_flag == False:
                                break
                        cur_locat = end_locat   # 当前节点等于终点
                else:
                    cur_locat = 'reset'
                    time.sleep(0.5)
            except:
                color_print('ERRO', '自动寻路失败!')
                time.sleep(10)
                continue

    # 进图前开始匹配
    def before_game(self):
        self.pvp_nums = self.pvp_nums + 1
        color_print('INFO', f' <{self.gw2_hwnd}> 开始第 <{self.pvp_nums}> 场游戏')
        maping, mapend = self.memory.getMapId()
        while(maping == 350):           
            if self.is_leader == '1':   # 队长模式
                result = self.get_det_result('before_game_1')
                if result['likaipvp'] == []:  # 点击pvp图标的条件
                    self.mouse.check_click_mouse(result['pvp'], self.title_h, 1)
                    time.sleep(0.5)
                self.mouse.check_click_mouse(result['jingji'], self.title_h)    # 先点排位再点匹配
                time.sleep(1)
                self.mouse.check_click_mouse(result['pipei'], self.title_h)     # 匹配
                self.mouse.check_click_mouse(result['jixu'], self.title_h)      # 配置丢失
            elif self.is_leader == '2':  # 队员模式
                result = self.get_det_result('before_game_2')
                self.mouse.check_click_mouse(result['rudui'], self.title_h)  # 加入队伍
                self.mouse.check_click_mouse(result['zhunbei'], self.title_h)  # 确认准备
                self.mouse.check_click_mouse(result['jixu'], self.title_h)  # 放弃比赛点继续
                self.mouse.check_click_mouse(result['jixu'], self.title_h) # 配置不全，继续

            result = self.get_det_result('before_game_3')
            self.mouse.check_click_mouse(result['queren'], self.title_h, 1) # 确认准备
            ### 地图选择
            if result['dituxuanze'] != []:
                self.mouse.left_down(487, 404)
                self.mouse.left_up(487, 404)
                color_print('INFO', '准备进入比赛地图')
            maping, mapend = self.memory.getMapId()

    # 比赛中-进入地图开始比赛
    def middle_game(self):
        self.dead = 3     # 1 死亡 | 2 活着 | 3 开局至位
        maping, mapend = self.memory.getMapId()
        hp_max_old = 999999 # 初始化最大血量
        while(maping != 350):
            maping, mapend = self.memory.getMapId()
            if maping == mapend:    # 已经加载完成地图 执行
                ### 判定死亡状态
                hp, hp_max = self.memory.gethp()
                if hp_max > hp_max_old: # 血量突然变大，倒地
                    self.dead = 1
                    self.is_attacked = uconfig.is_attacked = False
                elif hp_max < hp_max_old and self.dead != 3: # 非游戏开始时,血量突然减少，复活
                    self.dead = 0
                hp_max_old = hp_max
                ### 检测比赛是否结束，并离开比赛
                result = self.get_det_result('middle_game')
                if result['defen'] != []:
                    self.autokey_flag = uconfig.autokey_flag =  False   # 出现得分，停止自动按键
                    self.autowalk_flag = uconfig.autowalk_flag =  False   # 出现得分，停止自动寻路
                    self.is_attacked = uconfig.is_attacked = False
                    while(result['defen'] != []):
                        result = self.get_det_result('middle_game')
                        self.mouse.check_click_mouse(result['likaibisai'], self.title_h)
                    break
                else:
                    if result['dengdai'] != []:   # 有等待了才点准备，防止误触加入队伍
                        # 进图后确认准备, 没有得分的界面再点，防止误触加入队伍
                        self.mouse.check_click_mouse(result['shi'], self.title_h)
                ### 小号出现特长则关闭 \ 关闭首领pvp配置\说明界面
                if result['techang'] or result['guanbi'] != []:
                    self.mouse.check_click_mouse(result['techang'], self.title_h, 1)
                    self.mouse.check_click_mouse(result['guanbi'], self.title_h, 1)
                    time.sleep(0.5)
                ### 游戏进入倒计时，准备自动按键与寻路
                # 游戏刚开始或者是复活了,排除得分遮挡死亡
                # 注意此处只检测了一个auto
                # if not self.autowalk_flag and result['defen'] == []:
                if (self.dead == 3 or self.dead == 0) and not self.autowalk_flag and result['defen'] == []:
                    color_print('INFO', '开始自动按键/寻路策略')
                    self.dead = 0           # 关闭至高位
                    time.sleep(4)     # 复活等待1.5s
                    self.autokey_flag = uconfig.autokey_flag = True
                    self.autowalk_flag = uconfig.autowalk_flag = True
                    self.is_attacked = uconfig.is_attacked = True
                ### 已经死亡，结束自杀按键
                if self.dead == 1 and self.autowalk_flag:
                    color_print('INFO', '结束自动按键/寻路策略')
                    self.autokey_flag = uconfig.autokey_flag = False
                    self.autowalk_flag = uconfig.autowalk_flag = False
                    self.is_attacked = uconfig.is_attacked = False

    # 比赛后-退出地图
    def end_game(self):
        while(1):   # 此while用于比赛后等待过图
            maping, mapend = self.memory.getMapId()
            time.sleep(0.5)
            if mapend == 350:
                color_print('INFO', '\t返回主城成功')
                break

    # 开始pvp
    def begin_pvp(self):
        while(1):
            self.before_game()  # 进图前开始匹配
            self.middle_game()  # 比赛中-进入地图开始比赛
            self.end_game()     # 比赛后-退出地图

    # main
    def script_main(self):
        # registration()                                                        # 软件注册,因开源-已取消注册
        color_print(
            'INFO', '本项目永久免费。开源地址: https://github.com/Jai-wei/Gw2AutoPvp/')
        color_print(
            'INFO', '如果你是在闲鱼购买的，请立即退款！')
        time.sleep(3)
        self.gw2_hwnd = get_gw2_window_hwnd()                                   # 选择游戏窗口句柄
        self.is_leader = leader_mode_select()                                   # 选择队长/队员模式
        self.title_h = set_win_locat(self.gw2_hwnd)                             # 设置窗口大小及位置
        self.keyboard, self.mouse, self.memory = init_class(self.gw2_hwnd)      # 类初始化
        self.skill_plan = read_suicide_method()                                 # 按键方案
        detectpic = mp.Process(target=beginshot, args=(self.Qcase, self.Qresult, self.gw2_hwnd))
        detectpic.start()                                                       # 多进程-图像检测
        autowalk = threading.Thread(target=self.control_walk)                   # 多线程-自动寻路
        autowalk.start()
        autokey = threading.Thread(target=autoskill, args=(self.keyboard, self.skill_plan))
        autokey.start()                                                         # 多线程-自动按键
        time.sleep(1.5)                                                         # 等待进程启动
        self.begin_pvp()                                                        # 开启pvp

if __name__ == '__main__':
    T = MainControl()
    T.script_main()
    input()

Gw2AutoPvp.py
Jai-wei/Gw2AutoPvp
### 自动按键

import time
import random
from utils.IO import*
import utils.config as uconfig


def read_suicide_method(path='./config/suicide.txt'):
    skill_plan = []
    with open(path, "r", encoding="utf-8") as file:
        for line in file:
            buf = line.rstrip()
            if "相隔" in buf:
                wait_time = buf.split("\"")[1]
                key_vk = buf.split("\"")[3]
                skill_plan.append([wait_time, key_vk])
    color_print('INFO', '******* 自动按键方案如下：*******')
    for i in skill_plan:
        color_print('INFO', f"***\t相隔{i[0]}秒，\t按下{i[1]}键\t***")
    color_print('WARN', '可修改<config>文件夹下的<suicide.txt>\n')
    return skill_plan


def autoskill(keyb, skill_plan):
    while(1):
        random_t = random.randint(1, 3)
        time.sleep(random_t)
        # time.sleep(1.5)
        if uconfig.autokey_flag:    # 自动按键
            if uconfig.is_attacked == True:
                for i in skill_plan:
                    time.sleep(float(i[0]))
                    keyb.key_down(i[1])
                    time.sleep(0.1)
                    keyb.key_up(i[1])
        else:
            random_t = random.randint(7, 17)
            time.sleep(random_t)
            # 自动跳跃
            # random_t = random.randint(0, 20)
            # if random_t < 2:        # 跳跃
            #     keyb.key_down('space')
            #     keyb.key_up('space')

utils/AutoSkill.py
Jai-wei/Gw2AutoPvp
### 图像检测

from AiBot import WinBotMain
import random
from utils.IO import *
import os

map_name_id = {
    875: '静风神殿',
    1305: '巨灵领域',
    549: '凯洛城之战',
    795: '炼狱的遗产',
    1163: '摩羯号的复仇',
    554: '尼菲尔森林',
    900: '天空巨锤',
    1171: '永恒大剧场',
    894: '众灵守望',
    1011: '末日英雄之战',
}



class ScreenshotScript(WinBotMain):
    wait_timeout = 0.1
    interval_timeout = 0.1
    log_level = "INFO"
    raise_err = False
    gw2_hwnd = 0
    Qcase = ''
    Qresult = ''

    def script_main(self):  # begin
        while(1):
            dresult = {}            # 结果
            a = self.Qcase.get()
            if a == 'before_game_1':    # 队长模式
                dresult['info'] = 'before_game_1'
                dresult['likaipvp'] = self.find_images(self.gw2_hwnd, './src/likaipvp.png', mode=True, similarity=0.7)
                dresult['pvp'] = self.find_images(self.gw2_hwnd, './src/pvp.png', mode=True, similarity=0.7)
                dresult['jingji'] = self.find_images(self.gw2_hwnd, './src/jingji.png', mode=True, similarity=0.7)
                dresult['pipei'] = self.find_images(self.gw2_hwnd, './src/pipei.png', mode=True, similarity=0.7)
                dresult['jixu'] = self.find_images(self.gw2_hwnd, './src/jixu.png', mode=True, similarity=0.7, region=(0, 0, 535, 660))
                self.Qresult.put(dresult)
            elif a == 'before_game_2':  # 队员模式
                dresult['info'] = 'before_game_2'
                dresult['rudui'] = self.find_images(self.gw2_hwnd, './src/rudui.png', mode=True, similarity=0.7)
                dresult['zhunbei'] = self.find_images(self.gw2_hwnd, './src/zhunbei.png', mode=True, similarity=0.7)
                dresult['jixu'] = self.find_images(self.gw2_hwnd, './src/jixu.png', mode=True, similarity=0.7)
                self.Qresult.put(dresult)
            elif a == 'before_game_3':  # 此while用于进入比赛地图前
                dresult['info'] = 'before_game_3'
                dresult['queren'] = self.find_images(self.gw2_hwnd, './src/queren.png', mode=True, similarity=0.7)
                dresult['dituxuanze'] = self.find_images(self.gw2_hwnd, './src/dituxuanze.png', mode=True, similarity=0.7)
                self.Qresult.put(dresult)
            elif a == 'middle_game':
                dresult['info'] = 'middle_game'
                dresult['guanbi'] = self.find_images(self.gw2_hwnd, './src/guanbi.png', mode=True, similarity=0.9, region=(624,0,1217,817))
                dresult['defen'] = self.find_images(self.gw2_hwnd, './src/defen.png', mode=True, similarity=0.7)
                dresult['likaibisai'] = self.find_images(self.gw2_hwnd, './src/likaibisai.png', mode=True, similarity=0.7)
                dresult['dengdai'] = self.find_images(self.gw2_hwnd, './src/dengdai.png', mode=True, similarity=0.6)
                dresult['shi'] = self.find_images(self.gw2_hwnd, './src/shi.png', mode=True, similarity=0.6)
                dresult['techang'] = self.find_images(self.gw2_hwnd, './src/techang.png', mode=True, similarity=0.7)
                dresult['daojishi'] = self.find_images(self.gw2_hwnd, './src/daojishi.png', mode=True, similarity=0.7)
                self.Qresult.put(dresult)
            elif a in ['875', '1305', '549', '795', '1163', '554', '900', '1171', '894']:
                dresult['info'] = a
                path = 'src/map_icon/' + map_name_id[int(a)] + '/'
                pics = os.listdir(path)
                for i in pics:
                    name = i.split('.')[0]
                    ppath = path + i
                    dresult[name] = self.find_images(self.gw2_hwnd, ppath, mode=True, similarity=0.87)
                color_print("DBUG", f"{dresult}")
                self.Qresult.put(dresult)


def beginshot(Qcase, Qresult, hwnd):
    Sss = ScreenshotScript
    Sss.gw2_hwnd = hwnd
    Sss.Qcase = Qcase
    Sss.Qresult = Qresult
    random_port = random.randint(100, 65000)
    color_print('INFO', f"随机端口号{random_port}")
    Sss.execute(random_port, local=True)


if __name__ == '__main__':
    beginshot(68310, './Temp/1.jpg')

utils/FindImgs.py
Jai-wei/Gw2AutoPvp
### 格式化输出

import time


def color_print(infor, str, debug = 1):
    ''' 输出
    infor: INFO | WARN | DBUG | ERRO
    str  : To show
    debug: For debug
    '''
    if debug ==1 :
        print(f"[{infor}]~[{time.strftime('%H:%M:%S', time.localtime(time.time()))}] {str}")


def color_input():
    ''' 输入
    '''
    print(
        f"[IN<<]~[{time.strftime('%H:%M:%S', time.localtime(time.time()))}] ", end='')
    inp = input()
    return inp

utils/IO.py
Jai-wei/Gw2AutoPvp
### 程序初始化

import win32gui
import ctypes
from utils.IO import *
from utils.Keyboard import*
from utils.Mouse import*
from utils.Memory import*
import win32process

def enum_windows_callback(hwnd, windows_list):
    window_text = win32gui.GetWindowText(hwnd)
    if window_text:
        windows_list.append((hwnd, window_text))

# 选择进程
def get_gw2_window_hwnd():
    windows = []
    gw2_hwnds = []
    win32gui.EnumWindows(enum_windows_callback, windows)
    for hwnd, window_text in windows:   # 选择激战2窗口
        if window_text == "激战2":
            gw2_hwnds.append(hwnd)
    del windows

    if len(gw2_hwnds) == 0:
        color_print('ERRO', '请先打开游戏后，再运行此脚本!!!')
        return 0
    elif len(gw2_hwnds) == 1:
        gw2_hwnd = gw2_hwnds[0]
        color_print('INFO', f"单开游戏，游戏句柄为{gw2_hwnds[0]}")
        return gw2_hwnd
    else:
        color_print('INFO', '请选择需要窗口句柄，输入前面的编号并按回车键结束....')
        for i in range(len(gw2_hwnds)):
            print(f"\t{i+1}. {gw2_hwnds[i]}")
        select_id = int(color_input())
        while(select_id > len(gw2_hwnds)):
            color_print('WARN', '输入有误，请重新选择窗口句柄id...')
            select_id = int(color_input())
        gw2_hwnd = gw2_hwnds[select_id - 1]
        return gw2_hwnd


# 选择模式
def leader_mode_select():
    color_print('INFO', '请选择 队长/队员 模式，单排请选择 队长模式...')
    color_print('INFO', '输入数字 1 或 2 ，按回车键确定')
    color_print('INFO', '\t 1.队长模式')
    color_print('INFO', '\t 2.队员模式')
    while(1):
        is_leader = color_input()
        if is_leader not in ['1', '2']:
            color_print('WARN', '输入有误！请重新输入！')
        else:
            if is_leader == '1':
                color_print('INFO', '\t已选择队长模式！')
                time.sleep(0.5)
            else:
                color_print('INFO', '\t已选择队员模式！')
                time.sleep(0.5)
            return is_leader


# 设置窗口大小
def set_win_locat(hwnd):
    client_rect = win32gui.GetClientRect(hwnd)
    rect_fake = win32gui.GetWindowRect(hwnd)        # 假的rect
    f = ctypes.windll.dwmapi.DwmGetWindowAttribute
    rect = ctypes.wintypes.RECT()                   # 真的rect,不受缩放影响
    DWMWA_EXTENDED_FRAME_BOUNDS = 9
    f(ctypes.wintypes.HWND(hwnd), ctypes.wintypes.DWORD(DWMWA_EXTENDED_FRAME_BOUNDS), ctypes.byref(rect), ctypes.sizeof(rect))
    win32gui.MoveWindow(hwnd, rect_fake[0], rect_fake[1], 1260, 850, True)       # 设定窗口位置
    return rect.bottom - rect.top - client_rect[3]  # 返回标题栏高度

# 初始化类
def init_class(hwnd):
    keyboard = Keyboard(hwnd)
    mouse = Mouse(hwnd)
    memory = Memory(win32process.GetWindowThreadProcessId(hwnd)[1])
    return keyboard, mouse, memory

utils/InitSet.py
Jai-wei/Gw2AutoPvp
### 后台键盘类

from ctypes import windll
import string
import time


class Keyboard():
    def __init__(self, hwnd) -> None:
        import sys
        if not windll.shell32.IsUserAnAdmin():
            # 需要和目标窗口同一权限，游戏窗口通常是管理员权限
            # 不是管理员就提权
            windll.shell32.ShellExecuteW(
                None, "runas", sys.executable, __file__, None, 1)
        self.handle = hwnd
        # print(self.handle)
        self.PostMessageW = windll.user32.PostMessageW
        self.MapVirtualKeyW = windll.user32.MapVirtualKeyW
        self.VkKeyScanA = windll.user32.VkKeyScanA

        self.WM_KEYDOWN = 0x100
        self.WM_KEYUP = 0x101

        # https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
        self.VkCode = {
            "back":  0x08,
            "tab":  0x09,
            "return":  0x0D,
            "shift":  0x10,
            "control":  0x11,
            "menu":  0x12,
            "pause":  0x13,
            "capital":  0x14,
            "esc":  0x1B,
            "space":  0x20,
            "end":  0x23,
            "home":  0x24,
            "left":  0x25,
            "up":  0x26,
            "right":  0x27,
            "down":  0x28,
            "print":  0x2A,
            "snapshot":  0x2C,
            "insert":  0x2D,
            "delete":  0x2E,
            "lwin":  0x5B,
            "rwin":  0x5C,
            "numpad0":  0x60,
            "numpad1":  0x61,
            "numpad2":  0x62,
            "numpad3":  0x63,
            "numpad4":  0x64,
            "numpad5":  0x65,
            "numpad6":  0x66,
            "numpad7":  0x67,
            "numpad8":  0x68,
            "numpad9":  0x69,
            "multiply":  0x6A,
            "add":  0x6B,
            "separator":  0x6C,
            "subtract":  0x6D,
            "decimal":  0x6E,
            "divide":  0x6F,
            "f1":  0x70,
            "f2":  0x71,
            "f3":  0x72,
            "f4":  0x73,
            "f5":  0x74,
            "f6":  0x75,
            "f7":  0x76,
            "f8":  0x77,
            "f9":  0x78,
            "f10":  0x79,
            "f11":  0x7A,
            "f12":  0x7B,
            "numlock":  0x90,
            "scroll":  0x91,
            "lshift":  0xA0,
            "rshift":  0xA1,
            "lcontrol":  0xA2,
            "rcontrol":  0xA3,
            "lmenu":  0xA4,
            "rmenu":  0XA5
        }

    def get_virtual_keycode(self, key: str):
        """根据按键名获取虚拟按键码

        Args:
            key (str): 按键名

        Returns:
            int: 虚拟按键码
        """
        if len(key) == 1 and key in string.printable:
            # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscana
            return self.VkKeyScanA(ord(key)) & 0xff
        else:
            return self.VkCode[key]

    def key_down(self, key: str):
        """按下指定按键

        Args:
            handle (HWND): 窗口句柄
            key (str): 按键名
        """
        vk_code = self.get_virtual_keycode(key)
        scan_code = self.MapVirtualKeyW(vk_code, 0)
        # https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown
        wparam = vk_code
        lparam = (scan_code << 16) | 1
        self.PostMessageW(self.handle, self.WM_KEYDOWN, wparam, lparam)

    def key_up(self, key: str):
        """放开指定按键

        Args:
            handle (HWND): 窗口句柄
            key (str): 按键名
        """
        vk_code = self.get_virtual_keycode(key)
        scan_code = self.MapVirtualKeyW(vk_code, 0)
        # https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keyup
        wparam = vk_code
        lparam = (scan_code << 16) | 0XC0000001
        self.PostMessageW(self.handle, self.WM_KEYUP, wparam, lparam)

    def key_push(self, key: str, t=0.1):
        """按下并弹起按键

        Args:
            handle (HWND): 窗口句柄
            key (str): 按键名
            t (float): 按下时长
        """
        self.key_down(key)
        time.sleep(t)
        self.key_up(key)
utils/Keyboard.py
Jai-wei/Gw2AutoPvp
### 内存类

from pymem import Pymem
from pymem.ptypes import RemotePointer
import math


class Memory():
    def __init__(self, exename):
        self.process_name = Pymem(exename)
        self.process_lp = self.process_name.base_address
        self.Maping_base_adr = 0x2A2172C            # 加载中地图id基址
        self.Mapend_base_adr = 0x25540C0            # 加载完成地图id基址
        self.hp_base_adr = 0x29C3678                # 血量基址
        self.coordinate_base_adr = 0x2A6E600        # 人物坐标基址
        self.face_x_base_adr = 0x2A21798            # 人物x面向基址
        self.face_y_base_adr = 0x2A2179C            # 人物y面向基址

    def getPointerAddress(self, base, offsets):
        '''读取内存

        Args:
            process_name: Gw2-64.exe
            base: Gw2_base_lp + 偏移
            offsets: 多级偏移
            return: 地址
        '''
        remote_pointer = RemotePointer(self.process_name.process_handle, base)
        for offset in offsets:
            if offset != offsets[-1]:
                remote_pointer = RemotePointer(
                    self.process_name.process_handle, remote_pointer.value + offset)
            else:
                return remote_pointer.value + offset
        if len(offsets) == 0:
            return RemotePointer(self.process_name.process_handle, remote_pointer.value)

    def calAngle(self, x1, y1, x2, y2):
        '''计算人物朝向角度

        Args:
            x1: x1
            y1: y1
            x2: x2
            y2: y2
            return: angle (North-0°, 0~360°)
        '''
        if x1 == x2 and y2 >= y1:
            return 0
        if x1 == x2 and y2 < y1:
            return 180
        if y1 == y2 and x1 > x2:
            return 90
        if y1 == y2 and x1 < x2:
            return 270
        k = -(y2-y1)/(x2-x1)
        result = math.atan(k)*57.29577
        if x1 > x2:
            result += 270
        else:
            result += 90
        return result

    def getMapId(self):
        '''获取当前地图id
            return: 加载中，加载完毕
        '''
        try:
            Maping = self.process_name.read_int(
                self.process_lp + self.Maping_base_adr)    # 加载中
            Mapend = self.process_name.read_int(
                self.process_lp + self.Mapend_base_adr)    # 加载完成
            return Maping, Mapend
        except:
            pass

    def gethp(self):
        '''获取当前血量
            return: 当前血量，最大血量
        '''
        try:
            hp = int(self.process_name.read_float(
                self.getPointerAddress(self.process_lp + self.hp_base_adr, [0xB4])))
            hp_max = int(self.process_name.read_float(
                self.getPointerAddress(self.process_lp + self.hp_base_adr, [0xB8])))
            return hp, hp_max
        except:
            pass

    def getMoveAttribute(self):
        '''获取坐标及面向

        Args:
            process_name: ...
            process_lp: Gw2.base_address
            return: x y z angle
        '''
        # 先找两个+30的，然后加第一个最慢+50
        # X : 3, 1  //  Fy: Search-Direct
        P_x = round(self.process_name.read_float(self.getPointerAddress(
            self.process_lp + self.coordinate_base_adr, [0x50, 0x30])), 3)
        P_y = round(self.process_name.read_float(self.getPointerAddress(
            self.process_lp + self.coordinate_base_adr, [0x50, 0x34])), 3)
        P_z = round(self.process_name.read_float(self.getPointerAddress(
            self.process_lp + self.coordinate_base_adr, [0x50, 0x38])), 3)
        Face_x = round(self.process_name.read_float(
            self.process_lp + self.face_x_base_adr), 3)        # 第一个是x
        Face_y = round(self.process_name.read_float(
            self.process_lp + self.face_y_base_adr), 3)        # 第二个是y
        P_a = self.calAngle(0, 0, Face_x, Face_y)
        return P_x, P_y, P_z, P_a


if __name__ == '__main__':
    import win32gui
    import win32process
    import time

    def enum_windows_callback(hwnd, windows_list):
        window_text = win32gui.GetWindowText(hwnd)
        if window_text:
            windows_list.append((hwnd, window_text))

    def get_gw2_window_hwnd():
        windows = []
        gw2_hwnds = []
        win32gui.EnumWindows(enum_windows_callback, windows)
        for hwnd, window_text in windows:   # 选择激战2窗口
            if window_text == "激战2":
                gw2_hwnds.append(hwnd)
        return gw2_hwnds[0]

    hwnd = get_gw2_window_hwnd()
    memory = Memory(win32process.GetWindowThreadProcessId(hwnd)[1])
    while(1):
        time.sleep(0.1)
        print(memory.getMoveAttribute())
        # print(memory.getMapId())
        # print(memory.gethp())

utils/Memory.py
Jai-wei/Gw2AutoPvp
### 鼠标后台类
'''
所有的点击都是不包含标题栏的宽度！
'''
from ctypes import windll, byref
from ctypes.wintypes import POINT
import time


class Mouse():
    def __init__(self, hwnd) -> None:
        import sys
        if not windll.shell32.IsUserAnAdmin():
            # 需要和目标窗口同一权限，游戏窗口通常是管理员权限
            # 不是管理员就提权
            windll.shell32.ShellExecuteW(
                None, "runas", sys.executable, __file__, None, 1)
        self.handle = hwnd
        self.PostMessageW = windll.user32.PostMessageW
        self.ClientToScreen = windll.user32.ClientToScreen
        self.WM_MOUSEMOVE = 0x0200
        self.WM_LBUTTONDOWN = 0x0201
        self.WM_LBUTTONUP = 0x202
        self.WM_RBUTTONDOWN = 0x0204
        self.WM_RBUTTONUP = 0x0205
        self.WM_MOUSEWHEEL = 0x020A
        self.WHEEL_DELTA = 120

    def move_to(self, x: int, y: int):
        """移动鼠标到坐标（x, y)

        Args:
            x (int): 横坐标
            y (int): 纵坐标
        """
        # https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousemove
        wparam = 0
        lparam = y << 16 | x
        self.PostMessageW(self.handle, self.WM_MOUSEMOVE, wparam, lparam)

    def left_down(self, x: int, y: int):
        """在坐标(x, y)按下鼠标左键

        Args:
            x (int): 横坐标
            y (int): 纵坐标
        """
        # https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondown
        wparam = 0
        lparam = y << 16 | x
        self.PostMessageW(self.handle, self.WM_LBUTTONDOWN, wparam, lparam)

    def left_up(self, x: int, y: int):
        """在坐标(x, y)放开鼠标左键

        Args:
            x (int): 横坐标
            y (int): 纵坐标
        """
        # https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttonup
        wparam = 0
        lparam = y << 16 | x
        self.PostMessageW(self.handle, self.WM_LBUTTONUP, wparam, lparam)

    def right_down(self, x: int, y: int):
        """在坐标(x, y)按下鼠标右键

        Args:
            x (int): 横坐标
            y (int): 纵坐标
        """
        # https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondown
        wparam = 0
        lparam = y << 16 | x
        self.PostMessageW(self.handle, self.WM_RBUTTONDOWN, wparam, lparam)

    def right_up(self, x: int, y: int):
        """在坐标(x, y)放开鼠标右键

        Args:
            x (int): 横坐标
            y (int): 纵坐标
        """
        # https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttonup
        wparam = 0
        lparam = y << 16 | x
        self.PostMessageW(self.handle, self.WM_RBUTTONUP, wparam, lparam)

    def scroll(self, delta: int, x: int, y: int):
        """在坐标(x, y)滚动鼠标滚轮

        Args:
            delta (int): 为正向上滚动，为负向下滚动
            x (int): 横坐标
            y (int): 纵坐标
        """
        self.move_to(x, y)
        # https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel
        wparam = delta << 16
        p = POINT(x, y)
        self.ClientToScreen(self.handle, byref(p))
        lparam = p.y << 16 | p.x
        self.PostMessageW(self.handle, self.WM_MOUSEWHEEL, wparam, lparam)

    def scroll_up(self, x: int, y: int):
        """在坐标(x, y)向上滚动鼠标滚轮

        Args:
            x (int): 横坐标
            y (int): 纵坐标
        """
        self.scroll(self.WHEEL_DELTA, x, y)

    def scroll_down(self, x: int, y: int):
        """在坐标(x, y)向下滚动鼠标滚轮

        Args:
            x (int): 横坐标
            y (int): 纵坐标
        """
        self.scroll(-self.WHEEL_DELTA, x, y)

    def check_click_mouse(self, poi, title_h, click_num = 2):
        """涵盖检查数据有效性的点击

        Args:
            x (int): 横坐标
            y (int): 纵坐标
            title_h: 标题栏高度
        """
        if poi == []:
                return False
        else:
            x = int(poi[0].x)
            y = int(poi[0].y - title_h)
            if click_num == 1:    # pvp图标只按一次
                self.left_down(x, y)
                self.left_up(x, y)
            elif click_num == 2:
                self.left_down(x, y)
                self.left_up(x, y)
                self.left_down(x, y)
                self.left_up(x, y)

utils/Mouse.py
Jai-wei/Gw2AutoPvp
### 根据坐标自动寻路类

'''
'静风神殿': 875
'巨灵领域': 1305
'凯洛城之战': 549
'炼狱的遗产': 795
'摩羯号的复仇': 1163
'尼菲尔森林': 554
'天空巨锤': 900
'永恒大剧场': 1171
'众灵守望': 894
'末日英雄之战': 1011
'''

import time
from utils.IO import *
import utils.config as uconfig

class MoveControl():
    def __init__(self, OMemory, OKeyboard):
        '''
        Args:
            OMemory: Object Memory
            OKeyboard: Object Keyboard
        '''
        self.Me = OMemory
        self.K = OKeyboard
        self.t_left_key = 'j'
        self.t_rig_key = 'k'
        self.go_ah_key = 'w'

    def turnView(self, angle, direct):
        '''旋转视角
        Args:

            angle: 旋转角度
            direct: 旋转方向, left or right
        '''
        c_time = angle/360*3    # 时间
        if direct == 'left':
            self.K.key_down(self.t_left_key)
            time.sleep(c_time)
            self.K.key_up(self.t_left_key)
            time.sleep(0.03)
        elif direct == 'right':
            self.K.key_down(self.t_rig_key)
            time.sleep(c_time)
            self.K.key_up(self.t_rig_key)
            time.sleep(0.03)

    def walk(self, PoiList, angleErr=50, disErr=3):
        '''自动寻路

        Args:
            PoiList: 节点列表
            angleErr: 允许的角度误差, >0
            disErr: 允许目的点与人物之间的误差
        '''
        for i in range(len(PoiList)):
            TarX = PoiList[i][0]    # 目标点 x
            TarY = PoiList[i][1]    # 目标点 y
            # color_print('DBUG', "Next Point: Id:{z} ({x}, {y})".format(z=i+1, x=TarX, y=TarY), 1)
            while(1):       # 到达点之前一直循环
                if uconfig.autowalk_flag == False:  # 控制中途退出
                    # color_print('INFO', '已结束自动寻路')
                    self.K.key_up(self.go_ah_key) 
                    self.K.key_up(self.t_left_key)  
                    self.K.key_up(self.t_rig_key)
                    Px, Py, Pz, Pa = self.Me.getMoveAttribute()
                    return PoiList[i:], Px, Py

                # while(uconfig.is_attacked):     # 收到攻击时暂停寻路
                #     self.K.key_up(self.go_ah_key)
                #     self.K.key_up(self.t_left_key)
                #     self.K.key_up(self.t_rig_key)
                #     time.sleep(1)

                Px, Py, Pz, Pa = self.Me.getMoveAttribute()
                if ((abs(TarX - Px) <= disErr) and (abs(TarY - Py) <= disErr)):  # 到达点位
                    if i == len(PoiList) - 1:
                        self.K.key_up(self.go_ah_key)  # 弹起 w 前进
                        # color_print('INFO', "已到达寻路终点")
                    break
                TarAngle = self.Me.calAngle(Px, Py, TarX, TarY)     # 与目标点的角度
                abs_ang_ofs = abs(TarAngle - Pa)                    # 绝对值差值
                if abs_ang_ofs <= 180:  # 平角范围内
                    if abs_ang_ofs > angleErr:                      # 角度偏差过大停止前进
                        self.K.key_up(self.go_ah_key)             
                    if TarAngle - Pa < 0:
                        self.turnView(abs_ang_ofs, 'left')
                    else:
                        self.turnView(abs_ang_ofs, 'right')
                else:                   # 平角范围外
                    if TarAngle > Pa:
                        cash = 360-TarAngle+Pa
                        if cash > angleErr:                  # 角度偏差过大停止前进
                            self.K.key_up(self.go_ah_key)
                        self.turnView(cash, 'left')
                    else:
                        cash = 360+TarAngle-Pa
                        if cash > angleErr:                  # 角度偏差过大停止前进
                            self.K.key_up(self.go_ah_key)
                        self.turnView(cash, 'right')
                self.K.key_down(self.go_ah_key)  # 弹起 w 前进
        return True, 0, 0     # 返回已经完成寻路

utils/MoveControl.py
Jai-wei/Gw2AutoPvp
### 路径录制保存与解密解析

from pynput import keyboard
from utils.IO import *
from cryptography.fernet import Fernet
import json

import time

map_name_id = {
    875: '静风神殿',
    1305: '巨灵领域',
    549: '凯洛城之战',
    795: '炼狱的遗产',
    1163: '摩羯号的复仇',
    554: '尼菲尔森林',
    900: '天空巨锤',
    1171: '永恒大剧场',
    894: '众灵守望',
    1011: '末日英雄之战',
}


def savePois(M, path):
    '''保存节点
    M:        class Memory
    path:     保存到文件
    '''
    pointlist = []
    t1 = time.time()
    xx, yy, zz, Pa = M.getMoveAttribute()
    while(1):
        with keyboard.Events() as event:
            for i in event:
                key_event = i
                break
            key_event = event.get()
            try:
                x, y, z, Pa = M.getMoveAttribute()
                if xx != x and yy != y:
                    if time.time() - t1 > 0.35:
                        color_print('INFO', str([x, y, z]))
                        pointlist.append([x, y, z])
                        t1 = time.time()
                        xx = x
                        yy = y
                        zz = z

                if key_event.key.char == 'q':
                    file = open(path, "w")
                    for i in pointlist:
                        aa = str(i[0]) + ',' + str(i[1])
                        file.write(aa+'\n')
                    file.close()
                    return pointlist
            except:
                pass


def readpoints(mapid):
    '''读取节点
    mapid:     id
    '''
    try:
        fpath = './src/data/' + map_name_id[mapid] + '.json'
        fernet = Fernet(b'KjdPx1gkDdR2J2CKMgfhVfZhhW4NPj9aCGT2ZNZzFmg=')
        with open(fpath, "rb") as encrypted_file:
            encrypted_data = encrypted_file.read()
        decrypted_data = fernet.decrypt(encrypted_data).decode('utf-8')
        decrypted_dict = json.loads(decrypted_data)
        return decrypted_dict, map_name_id[mapid]
    except:
        color_print('ERRO', '地图解析失败')
        return False

utils/Points.py
Jai-wei/Gw2AutoPvp
### 激活码,因开源已失效


import pyDes
import wmi
import datetime
from utils.IO import *

def registration():
    c = wmi.WMI()
    for board_id in c.Win32_BaseBoard():
        a = board_id.SerialNumber              
    for cpu in c.Win32_Processor():
        b = cpu.ProcessorId.strip()             
    ids = a + b
    need_input = False
    try:    # 读取预留钥密
        with open('./config/key.txt', "r", encoding="utf-8") as file:
            for line in file:
                input_text = line.rstrip()
    except:
        input_text = ""

    print(f"/*****您的机器码如下，复制后请联系作者获取激活码：*****/\n{ids}\n/*****请输入激活码，按回车键结束*****/")
    des_key = pyDes.des(b"q4v5n7g1", pyDes.CBC, b"q4v5n7g1",pad=None, padmode=pyDes.PAD_PKCS5)
    while(1):
        if need_input == False and input_text != "":
            need_input = True
        else:
            input_text = color_input()
        try:
            decrypted_text = des_key.decrypt(bytes.fromhex(input_text))
        except:
            color_print('WARN', '注册码错误，请重新输入')
            continue
        try:
            if decrypted_text.decode() == ids:
                color_print('INFO', '注册成功！欢迎使用')
                with open('./config/key.txt', 'w') as file:
                    file.write(input_text)
                return True
        except:
            color_print('WARN', '注册码错误，请重新输入')
            continue

def registertime():
    current_time = datetime.datetime.now()
    user_datetime = datetime.datetime(2023, 9, 3, 12, 00)
    if current_time < user_datetime:
        color_print('INFO', f'到期时间：{user_datetime}')
        return True
    else:
        color_print('WARN', f'已过期，请联系开发者')
        return False


utils/Register.py
Jai-wei/Gw2AutoPvp
# 用于多线程之间的数据同步, 仅适用于多线程

autokey_flag = False        # 开启按键脚本
autowalk_flag = False       # 开始自动寻路
is_attacked = False         # 受到攻击

utils/config.py
Jai-wei/Gw2AutoPvp
### 因开源，已无效

import wmi
import pyDes


def get_disk_sn():
    """
        获取硬盘序列号
        :return: 硬盘序列号列表
        """
    c = wmi.WMI()

    disk_sn_list = []
    for physical_disk in c.Win32_DiskDrive():
        disk_sn_list.append(physical_disk.SerialNumber.replace(" ", ""))
    return disk_sn_list


def get_baseboard_sn():
    """
        获取主板序列号
        :return: 主板序列号
        """
    c = wmi.WMI()
    for board_id in c.Win32_BaseBoard():
        # print(board_id.SerialNumber)
        return board_id.SerialNumber

def get_cpu_sn():
    """
    获取CPU序列号
    :return: CPU序列号
    """
    c = wmi.WMI()
    for cpu in c.Win32_Processor():
        # print(cpu.ProcessorId.strip())
        return cpu.ProcessorId.strip()


# a = get_baseboard_sn() + get_cpu_sn()
print("输入机器号：")
a = input()
k = pyDes.des('q4v5n7g1', pyDes.CBC, 'q4v5n7g1', pad=None, padmode=pyDes.PAD_PKCS5)
data = k.encrypt(a, padmode=pyDes.PAD_PKCS5).hex()
print(data)

des_key = pyDes.des(b"q4v5n7g1", pyDes.CBC, b"q4v5n7g1", pad=None, padmode=pyDes.PAD_PKCS5)
decrypted_text = des_key.decrypt(bytes.fromhex(data))


if str(decrypted_text.decode()) == a:
    print('验证成功')


utils/激活码.py
Jai-wei/Gw2AutoPvp