崩坏：星穹铁道自动化 | 崩坏：星穹铁道自动|疾跑锄大地|清体力| 基于模拟按键
from utils.config import get_config,read_json_info
from utils.map import Map
from utils.dungeon import Dungeon
from utils.log import log
import time
import os
import pyuac
if not pyuac.isUserAdmin():
    pyuac.runAsAdmin()
# 实例化
auto_map = Map()
auto_dungeon = Dungeon()
# 读取配置
map_list_data = get_config("map_list_data")
auto_map_list_data = get_config("auto_map_list_data")
gamepath = get_config("gamepath")
dungeon_config = read_json_info("dungeon.json","配置1",prepath="dungeon")
# 配置启用
auto_map.calculated.img_log_value = get_config("img_log")
auto_map.team_change = get_config("team_change")
auto_map.teamid = get_config("team_id")
auto_map.id = get_config("character_id")
auto_map.commission = get_config("commission")
auto_map.close_game = get_config("close_game")
auto_map.nums = get_config("auto_map_nums")
auto_dungeon.team_change = get_config("team_change")
auto_dungeon.teamid = get_config("dungeon_team_id")
auto_dungeon.id = get_config("dungeon_character_id")
# 启动游戏
os.startfile(gamepath)
time.sleep(10)
# 游戏执行
auto_map.calculated.get_hwnd()
auto_dungeon.calculated.get_hwnd()
if auto_map.calculated.hwnd == 0:
    log.warning("未检测到游戏运行,请启动游戏")
else:
    auto_map.calculated.set_windowsize()
    # 登录
    auto_map.calculated.login()
    # 清体力
    auto_dungeon.start(dungeon_config)
    # 锄大地
    auto_map.start(map_list_data,auto_map_list_data)
time.sleep(5)

FastStart.py
Souloco/StarRail-FastRun
import threading
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk
from utils.config import read_map,read_maplist_name,set_config,get_config,save_dungeon_info,read_json_info
from utils.map import Map
from utils.dungeon import Dungeon
import os
import logging
from utils.log import log
from pynput import keyboard
import pyuac
import requests
import time
import win32console
import win32gui
# 全局属性
# 标题
TITLE_NAME = 'StarRail-FastRun'
# 版本号
VER = get_config("version")
# 版本更新提示
ver_update = False
# 线程变量
t = threading.Thread()
# 用于日志文本框
class TextboxHandler(logging.Handler):
    def __init__(self, textbox:tk.Text):
        logging.Handler.__init__(self)
        self.textbox = textbox

    def emit(self, record):
        msg = self.format(record)
        self.textbox.insert("end", msg + "\n")
        self.textbox.see("end")
# 事件
# 进入锄大地页面
def Enter_hoeframe():
    if not t.is_alive():
        logframe.pack_forget()
        mainframe.pack_forget()
        hoe_frame.pack()
        root.update()
    else:
        log.warning("功能线程还在运行！")
# 进入主页面
def Enter_mainframe():
    logframe.pack_forget()
    announce_frame.pack_forget()
    allframe.pack_forget()
    hoe_frame.pack_forget()
    dungeonframe.pack_forget()
    mainframe.pack()
    root.update()

# 打包地图列表
def get_map_list(map_value_list):
    map_use_list = []
    for i in range(len(map_value_list)):
        value = map_value_list[i].get()
        if value == 1:
            map_use_list.append(map_list[i])
    return map_use_list
# 保存
def save_config():
    map_use_list = get_map_list(map_value_list)
    auto_map_use_list = get_map_list(auto_map_value_list)
    set_config("map_list_data",map_use_list)
    set_config("auto_map_list_data",auto_map_use_list)
    set_config("team_id",teamid_sets.get())
    set_config("character_id",id_sets.get())
    set_config("team_change",team_change_var.get())
    set_config("img_log",img_log_Var.get())
    set_config("close_game",close_game_var.get())
    set_config("auto_map_nums",auto_map_nums.get())
    set_config("commission",commission_var.get())
# 进入日志页面
def Enter_logframe(logmode:int = 1):
    hoe_frame.pack_forget()
    dungeonframe.pack_forget()
    allframe.pack_forget()
    logframe.pack()
    if logmode == 1:
        logstart.configure(command=Enter_map)
        logreturn.configure(command=Enter_hoeframe)
    elif logmode == 2:
        logstart.configure(command=enter_dungeon_all)
        logreturn.configure(command=Enter_dungeonframe)
    elif logmode == 3:
        logstart.configure(command=enter_function_all)
        logreturn.configure(command=Enter_allframe)
    root.update()
# 进入清体力页面
def Enter_dungeonframe():
    if not t.is_alive():
        logframe.pack_forget()
        mainframe.pack_forget()
        dungeonframe.pack()
        root.update()
    else:
        log.warning("功能线程还在运行！")
# 进入多功能合一执行页面
def Enter_allframe():
    if not t.is_alive():
        logframe.pack_forget()
        mainframe.pack_forget()
        allframe.pack()
        root.update()
    else:
        log.warning("功能线程还在运行！")
# 锄地线程
def Enter_map():
    auto_map.calculated.get_hwnd()
    if auto_map.calculated.hwnd == 0:
        log.warning("未检测到游戏运行,请启动游戏")
    else:
        # 配置启用
        map_use_list = get_map_list(map_value_list)
        auto_map_use_list = get_map_list(auto_map_value_list)
        auto_map.calculated.img_log_value = img_log_Var.get()
        auto_map.team_change = team_change_var.get()
        auto_map.teamid = teamid_sets.get()
        auto_map.id = id_sets.get()
        auto_map.commission = commission_var.get()
        auto_map.close_game = close_game_var.get()
        auto_map.nums = auto_map_nums.get()
        # 激活窗口
        auto_map.calculated.active_window()
        # 线程启动
        global t
        if not t.is_alive():
            t = threading.Thread(name='chudi',target=auto_map.start,args=(map_use_list,auto_map_use_list,))
            t.daemon = True
            t.start()
        else:
            log.warning("线程已存在")
# 副本线程
def enter_dungeon_all():
    auto_dungeon.calculated.get_hwnd()
    if auto_dungeon.calculated.hwnd == 0:
        log.warning("未检测到游戏运行,请启动游戏")
    else:
        # 配置启用
        auto_dungeon.team_change = team_change_var.get()
        auto_dungeon.teamid = dungeon_teamid_sets.get()
        auto_dungeon.id = dungeon_id_sets.get()
        # 激活窗口
        auto_map.calculated.active_window()
        # 线程启动
        id = dungeon_notebook.index("current")
        global t
        if not t.is_alive():
            t = threading.Thread(name='dungeon',target=auto_dungeon.start,args=(dungeon_config_list[id],))
            t.daemon = True
            t.start()
        else:
            log.warning("线程已存在")

# 多功能执行
def enter_function():
    # 锄大地配置启用
    map_use_list = get_map_list(map_value_list)
    auto_map_use_list = get_map_list(auto_map_value_list)
    auto_map.calculated.img_log_value = img_log_Var.get()
    auto_map.team_change = team_change_var.get()
    auto_map.teamid = teamid_sets.get()
    auto_map.id = id_sets.get()
    auto_map.commission = commission_var.get()
    auto_map.close_game = close_game_var.get()
    auto_map.nums = auto_map_nums.get()
    # 清体力配置启用
    auto_dungeon.team_change = team_change_var.get()
    auto_dungeon.teamid = dungeon_teamid_sets.get()
    auto_dungeon.id = dungeon_id_sets.get()
    # 清体力执行
    id = dungeon_notebook.index("current")
    auto_dungeon.start(dungeon_config_list[id])
    # 锄地执行
    if auto_map.stop:
        return True
    auto_map.start(map_use_list,auto_map_use_list)
# 多功能执行线程
def enter_function_all():
    auto_map.calculated.get_hwnd()
    auto_dungeon.calculated.get_hwnd()
    if auto_map.calculated.hwnd == 0:
        log.warning("未检测到游戏运行,请启动游戏")
    else:
        # 激活窗口
        auto_map.calculated.active_window()
        global t
        if not t.is_alive():
            t = threading.Thread(name='allfunction',target=enter_function)
            t.daemon = True
            t.start()
        else:
            log.warning("线程已存在")
# 添加副本配置项
def add_dungeon_config():
    id = dungeon_notebook.index("current")
    dungeon_config_list[id].append({dungeon_choose.get():dungeon_nums.get()})
    dungeon_config_lable_list[id].append(ttk.Label(dungeon_tab_list[id],text=f"{dungeon_config_list[id][-1]}",font=('',12)))
    dungeon_config_lable_list[id][-1].pack(anchor='center')
    root.update()
# 删除副本配置项
def delete_dungeon_config():
    id = dungeon_notebook.index("current")
    dungeon_config_list[id].pop()
    dungeon_config_lable_list[id][-1].pack_forget()
    dungeon_config_lable_list[id][-1].destroy()
    dungeon_config_lable_list[id].pop()
    root.update()
# 保存副本配置项
def save_dungeon_config():
    id = dungeon_notebook.index("current")
    save_dungeon_info("dungeon.json",dungeon_title[id],dungeon_config_list[id])
    set_config("dungeon_team_id",dungeon_teamid_sets.get())
    set_config("dungeon_character_id",dungeon_id_sets.get())
    set_config("team_change",team_change_var.get())
# 保存所有配置项
def save_all_config():
    save_dungeon_config()
    save_config()
    set_config("gamepath",game_path.get())
# 清理图片log
def clear_imglog():
    logpath = "./logs/image"
    imglog_list = os.listdir(logpath)
    for img in imglog_list:
        os.remove(os.path.join(logpath,img))
    log.info("图片log清理完毕")

# 关闭程序
def close_window():
    root.destroy()
    auto_map.calculated.release_mouse_keyboard()

# 按键关闭程序
def btn_close_window():
    def on_press(key):
        if key == keyboard.Key.f8:
            close_window()
        if key == keyboard.Key.f10:
            log.info("暂停")
            auto_map.stop = True
            auto_dungeon.stop = True
    with keyboard.Listener(on_press=on_press) as listener:  # 创建按键监听线程
        listener.join()  # 等待按键监听线程结束
# map_value_list的值初始化
def set_map_value_list(map_value_list,value):
    for map_value in map_value_list:
        map_value.set(value)
# 清体力下拉框
def index_dungeon_change(event):
    dungeon_list = read_json_info("dungeon.json",index_dungeon_choose.get(),prepath="dungeon")
    dungeon_box.config(values=dungeon_list)
    dungeon_choose.set(dungeon_list[0])
    root.update()
# 显隐cmd
CMD = win32console.GetConsoleWindow()
def hide_cmd():
    if win32gui.IsWindowVisible(CMD):
        win32gui.ShowWindow(CMD, 0)  # 隐藏命令行窗口
    else:
        win32gui.ShowWindow(CMD, 1)  # 显示命令行窗口
if __name__ == '__main__':
    if not pyuac.isUserAdmin():
        pyuac.runAsAdmin()
        # messagebox.showerror("运行错误", "请以管理员权限运行")
        # raise Exception("请以管理员身份运行")
    # 锄大地实例
    auto_map = Map()
    # 清体力实例
    auto_dungeon = Dungeon()
    # gui页面
    root = tk.Tk()
    root.tk.call("source", "./tkinter/azure.tcl")
    root.tk.call("set_theme", "light")
    root.iconbitmap('./favicon.ico')
    root.geometry("+{}+{}".format(int(root.winfo_screenwidth()/4),int(root.winfo_screenheight()/8)))
    root.title(TITLE_NAME)
    root.resizable(True,True)
    # 主页面
    mainframe = ttk.Frame(root)
    ttk.Label(mainframe,text=TITLE_NAME,font=('Arial Black', 24)).grid()
    ttk.Label(mainframe,text=VER,font=('Arial Black', 16)).grid()
    ttk.Button(mainframe,text='锄大地',width=10,command=Enter_hoeframe).grid(pady=5,ipady=10)
    ttk.Button(mainframe,text='清体力',width=10,command=Enter_dungeonframe).grid(pady=5,ipady=10)
    ttk.Button(mainframe,text='多功能执行',width=10,command=Enter_allframe).grid(pady=5,ipady=10)
    ttk.Button(mainframe,text='显隐cmd',width=10,command=hide_cmd).grid(pady=5,ipady=10)
    # ttk.Button(mainframe,text='编辑配置',width=10).grid(pady=5,ipady=10)

    # 公告页面
    announce_frame = ttk.Frame(root)
    ttk.Label(announce_frame,text=TITLE_NAME,font=('Arial Black', 24)).grid(columnspan=2)
    ttk.Label(announce_frame,text=VER,font=('Arial Black', 16)).grid(columnspan=2)
    ttk.Label(announce_frame,text='项目地址:',font=('Arial Black', 16)).grid(column=0,row=2)
    repo_url = "https://github.com/Souloco/StarRail-FastRun"
    repo_url_text = ttk.Entry(announce_frame,width=40)
    repo_url_text.insert(0,repo_url)
    repo_url_text.grid(column=1,row=2)
    ttk.Label(announce_frame,text='公告',font=('Arial Black', 16)).grid(columnspan=2)
    s = ttk.Scrollbar(announce_frame)
    announce_text = tk.Text(announce_frame,font=('',16),undo=True, autoseparators=False,wrap='none', yscrollcommand=s.set)
    # 获取信息
    repos_url = 'https://api.github.com/repos/Souloco/StarRail-FastRun/releases/latest'
    # 请求版本最新信息
    try:
        response = requests.get(repos_url)
        if response.status_code == 200:
            data = response.json()
            # 获取版本信息
            version = data['tag_name']
            lowversion = str(data["body"]).split("lowversion:")[1]
            announce_text.insert('end',data["body"])
            if lowversion > VER:
                ver_update = True
                messagebox.showerror("版本更新","当前版本过低，请更新")
        else:
            announce_text.insert('end',"网络获取失败")
    except Exception:
        announce_text.insert('end',"疑似网络超时")
    announce_text.configure(state='disabled')
    announce_text.grid(columnspan=2,padx=5,pady=5)
    ttk.Button(announce_frame,text='确认',width=10,command=Enter_mainframe).grid(columnspan=2)
    announce_frame.pack()

    # 锄大地页面
    hoe_frame = ttk.Frame(root)
    ttk.Label(hoe_frame,text=TITLE_NAME,font=('Arial Black', 24)).grid(columnspan=4)
    ttk.Label(hoe_frame,text=VER,font=('Arial Black', 16)).grid(columnspan=4)
    ttk.Label(hoe_frame,text='必跑路线',font=('Arial Black', 16)).grid(columnspan=4)
    # notebook地图选项
    map_list = read_map()
    map_title = [('空间站「黑塔」',1),('雅利洛-VI',2),('仙舟「罗浮」',3),('匹诺康尼',4)]    # 星球选项
    map_notebook = ttk.Notebook(hoe_frame)
    map_tab_list = []
    map_value_list = []
    map_checkbutton_list = []
    map_list_data = get_config("map_list_data")
    map_allname_list1 = read_maplist_name()
    for i in range(len(map_title)):
        map_tab_list.append(ttk.Frame(map_notebook))
        map_notebook.add(map_tab_list[i],text=map_title[i][0])

    for i in range(len(map_list)):
        planet_id = map_list[i][map_list[i].index('_') + 1:map_list[i].index('-')]
        map_id = map_list[i][map_list[i].index('-') + 1:map_list[i].index('_',5)]
        index_id = map_list[i][map_list[i].index('_',5) + 1:map_list[i].index('.')]
        map_value_list.append(tk.IntVar(value=0))
        if map_list[i] in map_list_data:
            map_value_list[i].set(1)
        map_checkbutton_list.append(ttk.Checkbutton(map_tab_list[int(planet_id)-1],text=map_allname_list1[i],variable=map_value_list[i],onvalue=1, offvalue=0,width=10))
        map_checkbutton_list[i].grid(row=int(map_id),column=int(index_id))
    map_notebook.grid(columnspan=4)
    # 按钮
    ttk.Button(hoe_frame,text='全选',width=10,command=lambda:set_map_value_list(map_value_list,1)).grid(row=4,column=0,columnspan=2)
    ttk.Button(hoe_frame,text='清空',width=10,command=lambda:set_map_value_list(map_value_list,0)).grid(row=4,column=2,columnspan=2)
    # auto_notebook地图选项
    ttk.Label(hoe_frame,text='重跑路线',font=('Arial Black', 16)).grid(columnspan=4)
    auto_map_notebook = ttk.Notebook(hoe_frame)
    auto_map_tab_list = []
    auto_map_value_list = []
    auto_map_checkbutton_list = []
    auto_map_list_data = get_config("auto_map_list_data")
    for i in range(len(map_title)):
        auto_map_tab_list.append(ttk.Frame(auto_map_notebook))
        auto_map_notebook.add(auto_map_tab_list[i],text=map_title[i][0])
    for i in range(len(map_list)):
        planet_id = map_list[i][map_list[i].index('_') + 1:map_list[i].index('-')]
        map_id = map_list[i][map_list[i].index('-') + 1:map_list[i].index('_',5)]
        index_id = map_list[i][map_list[i].index('_',5) + 1:map_list[i].index('.')]
        auto_map_value_list.append(tk.IntVar(value=0))
        if map_list[i] in auto_map_list_data:
            auto_map_value_list[i].set(1)
        auto_map_checkbutton_list.append(ttk.Checkbutton(auto_map_tab_list[int(planet_id)-1],text=map_allname_list1[i],variable=auto_map_value_list[i],onvalue=1, offvalue=0,width=10))
        auto_map_checkbutton_list[i].grid(row=int(map_id),column=int(index_id))
    auto_map_notebook.grid(columnspan=4)
    # 按钮
    ttk.Button(hoe_frame,text='全选',width=10,command=lambda:set_map_value_list(auto_map_value_list,1)).grid(row=7,column=0)
    ttk.Button(hoe_frame,text='清空',width=10,command=lambda:set_map_value_list(auto_map_value_list,0)).grid(row=7,column=1)
    # 锄大地配置
    # 重跑次数
    ttk.Label(hoe_frame,text='重跑次数:',font=('', 12)).grid(row=7,column=2)
    auto_map_nums = tk.IntVar()
    auto_map_nums.set(get_config("auto_map_nums"))
    auto_map_spinbox = ttk.Spinbox(hoe_frame,from_=0, to=10, increment=1,textvariable=auto_map_nums)
    auto_map_spinbox.grid(row=7,column=3)
    # 切换队伍
    teamid_sets = tk.IntVar()
    teamid_option_list = [1,2,3,4,5,6]
    id_sets = tk.IntVar()
    id_option_list = [1,2,3,4]
    ttk.Label(hoe_frame,text='队伍/人物编号:').grid(row=8,column=0,pady=5)
    ttk.OptionMenu(hoe_frame,teamid_sets,get_config("team_id"),*teamid_option_list).grid(row=9,column=0,pady=5)
    ttk.OptionMenu(hoe_frame,id_sets,get_config("character_id"),*id_option_list).grid(row=10,column=0,pady=5)
    team_change_var = tk.BooleanVar()
    team_change_var.set(get_config("team_change"))
    img_log_Var = tk.BooleanVar()
    img_log_Var.set(get_config("img_log"))
    close_game_var = tk.BooleanVar()
    close_game_var.set(get_config("close_game"))
    commission_var = tk.BooleanVar()
    commission_var.set(get_config("commission"))
    # 配置开关
    ttk.Checkbutton(hoe_frame,text="切换队伍",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=team_change_var).grid(row=8,column=1,pady=5)
    ttk.Checkbutton(hoe_frame,text="委托开关",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=commission_var).grid(row=8,column=2,pady=5)
    ttk.Checkbutton(hoe_frame,text="截图记录",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=img_log_Var).grid(row=9,column=1,pady=5)
    ttk.Checkbutton(hoe_frame,text="自动关机",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=close_game_var).grid(row=9,column=2,pady=5)
    # 按钮
    ttk.Button(hoe_frame,text='确定',width=10,command=lambda:Enter_logframe(1)).grid(row=8,column=3)
    ttk.Button(hoe_frame,text='保存',width=10,command=save_config).grid(row=9,column=3)
    ttk.Button(hoe_frame,text='返回',width=10,command=Enter_mainframe).grid(row=10,column=3)

    # 日志页面
    logframe = ttk.Frame(root)
    ttk.Label(logframe, text='实时日志', font=('Arial Black', 16)).pack(anchor='nw')   # justify控制对其方向，anchor控制位置 共同使文本靠左
    logstart = ttk.Button(logframe, text='开始',width=5,command=Enter_map)
    logstart.place(relx=0.52,rely=0)
    ttk.Button(logframe, text='清理',width=5,command=clear_imglog).place(relx=0.64,rely=0)
    ttk.Button(logframe, text='结束',width=5,command=close_window).place(relx=0.76,rely=0)
    logreturn = ttk.Button(logframe, text='返回',width=5,command=Enter_mainframe)
    logreturn.place(relx=0.88,rely=0)
    s2 = ttk.Scrollbar(logframe)      # 设置垂直滚动条
    b2 = ttk.Scrollbar(logframe, orient='horizontal')    # 水平滚动条
    s2.pack(side='right', fill='y')     # 靠右，充满Y轴
    b2.pack(side='bottom', fill='x')    # 靠下，充满x轴
    logtext = tk.Text(logframe,font=('Consolas', 9),undo=True, autoseparators=False,wrap='none', xscrollcommand=b2.set, yscrollcommand=s2.set)  # , state=DISABLED, wrap='none'表示不自动换行
    logtext.pack(fill='both', expand='yes')
    s2.config(command=logtext.yview)  # Text随着滚动条移动被控制移动
    b2.config(command=logtext.xview)
    handler = TextboxHandler(logtext)
    log.add(handler,format="{time:HH:mm:ss} " + "|{level}| " + "{module}.{function}:{line} - {message}")

    # 清体力页面
    dungeonframe = ttk.Frame(root)
    ttk.Label(dungeonframe,text=TITLE_NAME,font=('Arial Black', 24)).grid(columnspan=4)
    ttk.Label(dungeonframe,text=VER,font=('Arial Black', 16)).grid(columnspan=4)
    ttk.Label(dungeonframe,text='副本类型:',font=('helvetica', 12)).grid(row=2,column=0)
    index_dungeon_list = read_json_info("dungeon.json","indexname",prepath="dungeon")
    index_dungeon_choose = tk.StringVar()
    index_dungeon_choose.set(index_dungeon_list[0])
    index_dungeon_box = ttk.Combobox(dungeonframe,textvariable=index_dungeon_choose,values=index_dungeon_list,width=25)
    index_dungeon_box.bind("<<ComboboxSelected>>",index_dungeon_change)
    index_dungeon_box.grid(row=2,column=1)
    ttk.Label(dungeonframe,text='具体副本:',font=('helvetica', 12)).grid(row=2,column=2)
    dungeon_list = read_json_info("dungeon.json",index_dungeon_choose.get(),prepath="dungeon")
    dungeon_choose = tk.StringVar()
    dungeon_choose.set(dungeon_list[0])
    dungeon_box = ttk.Combobox(dungeonframe,textvariable=dungeon_choose,values=dungeon_list,width=25)
    dungeon_box.grid(row=2,column=3)
    ttk.Label(dungeonframe,text='执行次数:',font=('helvetica', 12)).grid(row=3,column=0)
    dungeon_nums = tk.IntVar()
    dungeon_nums.set(1)
    dungeon_spinbox = ttk.Spinbox(dungeonframe,from_=1, to=100, increment=1,textvariable=dungeon_nums)
    dungeon_spinbox.grid(row=3,column=1)
    ttk.Button(dungeonframe,text='添加',command=add_dungeon_config).grid(row=3,column=2)
    ttk.Button(dungeonframe,text='删除',command=delete_dungeon_config).grid(row=3,column=3)
    # 切换队伍
    dungeon_teamid_sets = tk.IntVar()
    dungeon_id_sets = tk.IntVar()
    ttk.Label(dungeonframe,text='队伍/人物编号:').grid(row=4,column=0,pady=5)
    ttk.OptionMenu(dungeonframe,dungeon_teamid_sets,get_config("dungeon_team_id"),*teamid_option_list).grid(row=4,column=1,pady=5)
    ttk.OptionMenu(dungeonframe,dungeon_id_sets,get_config("dungeon_character_id"),*id_option_list).grid(row=4,column=2,pady=5)
    ttk.Checkbutton(dungeonframe,text="切换队伍",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=team_change_var).grid(row=4,column=3,pady=5)
    # notebook副本配置
    dungeon_notebook = ttk.Notebook(dungeonframe)
    dungeon_title = read_json_info("dungeon.json","configname",prepath="dungeon")
    dungeon_tab_list = []
    dungeon_config_list = []
    dungeon_config_lable_list = []
    for i in range(len(dungeon_title)):
        dungeon_tab_list.append(ttk.Frame(dungeon_notebook))
        dungeon_notebook.add(dungeon_tab_list[i],text=dungeon_title[i])
        dungeon_config_list.append(read_json_info("dungeon.json",dungeon_title[i],prepath="dungeon"))
        dungeon_config_lable = []
        for j in range(len(dungeon_config_list[i])):
            dungeon_config_lable.append(ttk.Label(dungeon_tab_list[i],text=f"{dungeon_config_list[i][j]}",font=('', 12)))
            dungeon_config_lable[j].pack(anchor='center')
        dungeon_config_lable_list.append(dungeon_config_lable)
    dungeon_notebook.grid(columnspan=4)
    ttk.Button(dungeonframe,text='确定',width=10,command=lambda:Enter_logframe(2)).grid(columnspan=4,pady=5)
    ttk.Button(dungeonframe,text='保存',width=10,command=save_dungeon_config).grid(columnspan=4,pady=5)
    ttk.Button(dungeonframe,text='返回',width=10,command=Enter_mainframe).grid(columnspan=4,pady=5)

    # 多功能合一执行页面
    allframe = ttk.Frame(root)
    ttk.Label(allframe,text=TITLE_NAME,font=('Arial Black', 24)).grid(columnspan=4)
    ttk.Label(allframe,text=VER,font=('Arial Black', 16)).grid(columnspan=4)
    ttk.Label(allframe,text='清体力队伍/人物编号:').grid(row=2,column=0,pady=5)
    ttk.OptionMenu(allframe,dungeon_teamid_sets,get_config("dungeon_team_id"),*teamid_option_list).grid(row=2,column=1,pady=5)
    ttk.OptionMenu(allframe,dungeon_id_sets,get_config("dungeon_character_id"),*id_option_list).grid(row=2,column=2,pady=5)
    ttk.Label(allframe,text='锄大地队伍/人物编号:').grid(row=3,column=0,pady=5)
    ttk.OptionMenu(allframe,teamid_sets,get_config("team_id"),*teamid_option_list).grid(row=3,column=1,pady=5)
    ttk.OptionMenu(allframe,id_sets,get_config("character_id"),*id_option_list).grid(row=3,column=2,pady=5)
    ttk.Checkbutton(allframe,text="切换队伍",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=team_change_var).grid(row=4,column=0,pady=5)
    ttk.Checkbutton(allframe,text="委托开关",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=commission_var).grid(row=4,column=1,pady=5)
    ttk.Checkbutton(allframe,text="截图记录",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=img_log_Var).grid(row=4,column=2,pady=5)
    ttk.Checkbutton(allframe,text="自动关机",style="Switch.TCheckbutton",onvalue=True,offvalue=False,variable=close_game_var).grid(row=4,column=3,pady=5)
    ttk.Label(allframe,text='(.\Game\StarRail.exe)游戏路径:').grid(row=5,column=0,pady=5)
    game_path = tk.StringVar()
    game_path.set(get_config("gamepath"))
    game_text = ttk.Entry(allframe,width=40,textvariable=game_path)
    game_text.grid(row=5,column=1,columnspan=3,pady=5)
    ttk.Button(allframe,text='确定',width=10,command=lambda:Enter_logframe(3)).grid(columnspan=4,pady=5)
    ttk.Button(allframe,text='保存',width=10,command=save_all_config).grid(columnspan=4,pady=5)
    ttk.Button(allframe,text='返回',width=10,command=Enter_mainframe).grid(columnspan=4,pady=5)
    # 按键监听线程
    t1 = threading.Thread(name='btn_close',target=btn_close_window)
    t1.daemon = True
    t1.start()
    # 版本更新
    if ver_update:
        time.sleep(5)
        close_window()
    win32gui.ShowWindow(CMD, 0)  # 隐藏命令行窗口
    root.mainloop()

gui.py
Souloco/StarRail-FastRun
from win32 import win32api,win32gui
import time
import cv2 as cv
import numpy as np
from .config import read_picture,model_dir
from .log import log
from cnocr import CnOcr
import os
import ctypes
from pynput import mouse, keyboard
from pynput.keyboard import Key
from .screenhoot import Screenhoot
class Calculated:
    def __init__(self):
        # 获取窗口句柄
        self.hwnd = win32gui.FindWindow("UnityWndClass","崩坏：星穹铁道")
        # 边框
        self.left_border = int(8)
        self.up_border = int(31)
        # 缩放比例
        self.scale = ctypes.windll.user32.GetDpiForSystem() / 96.0
        self.borderless = False
        # OCR识别
        self.reader = CnOcr(det_model_name="ch_PP-OCRv3_det", rec_model_name="densenet_lite_136-fc",det_root=os.path.join(model_dir, "cnocr"), rec_root=os.path.join(model_dir, "cnstr"))
        # 键鼠控制
        self.Mouse = mouse.Controller()
        self.Keyboard = keyboard.Controller()
        # 截图控制
        self.screenshot = Screenhoot()
        # 列表比较
        self.compare_lists = lambda a, b: all(x <= y for x, y in zip(a, b))
        # 图片logs是否记录
        self.img_log_value = False

    def get_hwnd(self):
        self.hwnd = win32gui.FindWindow("UnityWndClass","崩坏：星穹铁道")

    def active_window(self):
        win32gui.SetForegroundWindow(self.hwnd)

    def set_windowsize(self):
        """
        说明:
            设置窗口化或者全屏幕参数让截图区域正确
        """
        rect = win32gui.GetWindowRect(self.hwnd)
        w = rect[2]-rect[0]
        h = rect[3]-rect[1]
        self.borderless = True if w == 1920 and h == 1080 else False
        self.left_border = int((w-1920)/2)
        self.up_border = (h-1080)-self.left_border
        log.info(f"游戏分辨率:{w}x{h}x{self.scale}---{self.borderless}".replace("True","全屏幕").replace("False","窗口"))

    def get_WindowRect(self,hwnd):
        """
        说明:
            返回准确的窗口信息
        参数:
            hwnd: 窗口句柄
        """
        left, top, right, bottom = win32gui.GetWindowRect(hwnd)
        if not self.borderless:
            left, top, right, bottom = left+self.left_border,top+self.up_border,right-self.left_border,bottom-self.left_border
        return (left, top, right, bottom)

    def take_screenshot(self,points=(0,0,0,0)):
        """
        说明:
            返回BGR图像
        参数:
            points: 图像截取范围
        """
        left, top, right, bottom = self.get_WindowRect(self.hwnd)
        screenshot = self.screenshot.grab(left,top)
        if points != (0,0,0,0):
            return screenshot[points[1]:points[3],points[0]:points[2]]
        return screenshot

    def save_screenshot(self,name:str):
        """
        说明:
            保存截图名字为name至log/image
        参数"
            name: 文件名字
        """
        if self.img_log_value:
            left, top, right, bottom = self.get_WindowRect(self.hwnd)
            screenshot = self.screenshot.grab(left,top)
            cv.imwrite(f"./logs/image/{name}.jpg",screenshot,[cv.IMWRITE_JPEG_QUALITY, 25])

    def img_match(self,img,templeimg):
        """
        说明:
            返回最佳匹配系数与最佳匹配位置
        参数:
            img:原始图像
            templeimg:模板图像
        """
        res = cv.matchTemplate(img,templeimg,cv.TM_CCOEFF_NORMED)
        min_val,max_val,min_loc,max_loc = cv.minMaxLoc(res)
        return max_val,max_loc

    def img_click(self,templepath,points=(0,0,0,0),overtime=5.0,rates=0.90):
        """
        说明:
            识别到图片并点击,否则不点击
        参数:
            templepath:模板图像路径
            points:截图区域
            overtime:识别时间
            rates:对比度要求
        """
        templeimg = read_picture(templepath)
        img = self.take_screenshot(points)
        val,loc = self.img_match(img,templeimg)
        start_time = time.time()
        while val < rates and time.time() - start_time < overtime:
            img = self.take_screenshot(points)
            val,loc = self.img_match(img,templeimg)
            time.sleep(0.05)
        log.info(f"图片点击-{val}")
        if val >= rates:
            w = templeimg.shape[1]
            h = templeimg.shape[0]
            left, top, right, bottom = self.get_WindowRect(self.hwnd)
            x = left + points[0] + loc[0] + int(w/2)
            y = top + points[1]+loc[1] + int(h/2)
            self.Mouse.position = (x,y)
            time.sleep(0.1)
            self.Mouse.press(mouse.Button.left)
            time.sleep(0.5)
            self.Mouse.release(mouse.Button.left)
            return True
        else:
            log.info("识别超时")
            return False

    def img_check(self,templepath,points=(0,0,0,0),overtime=5.0,rates=0.90):
        """
        说明:
            识别图片返回布尔值
        参数:
            templepath:模板图像路径
            points:截图区域
            overtime:识别时间
            rates:对比度要求
        """
        templeimg = read_picture(templepath)
        img = self.take_screenshot(points)
        val,loc = self.img_match(img,templeimg)
        start_time = time.time()
        while val < rates and time.time() - start_time < overtime:
            img = self.take_screenshot(points)
            val,loc = self.img_match(img,templeimg)
            time.sleep(0.05)
        # log.info(f"图片检测-{val}")
        if val >= rates:
            return True
        else:
            return False

    def dungeon_img_click(self,templepath,points=(0,0,0,0),overtime=5.0,rates=0.90):
        """
        说明:
            识别图片返回布尔值
        参数:
            templepath:模板图像路径
            points:截图区域
            overtime:识别时间
            rates:对比度要求
        """
        templeimg = read_picture(templepath)
        img = self.take_screenshot(points)
        val,loc = self.img_match(img,templeimg)
        start_time = time.time()
        while val < rates and time.time() - start_time < overtime:
            img = self.take_screenshot(points)
            val,loc = self.img_match(img,templeimg)
            time.sleep(0.05)
        log.info(f"图片点击-{val}")
        if val >= rates:
            h = templeimg.shape[0]
            print((0,loc[1],1920,loc[1]+h))
            self.img_click("dungeon_transfer.jpg",points=(0,loc[1],1920,loc[1]+h))
        else:
            log.info("识别超时")

    def ocr_match(self,img,text:str,mode=1):
        """
        说明:
            返回匹配位置与匹配系数
        参数:
            img:图像
            text:匹配文本
            mode:匹配模式
        """
        result = self.reader.ocr(img)
        nums = len(text)
        ocr_num = 0
        # print(result)
        for res in result:
            loc = res['position']
            content = str(res['text']).replace(" ", "")
            rate = res['score']
            # 完全匹配
            if mode == 1:
                if text == content:
                    x = int((loc[0][0]+loc[1][0]+loc[2][0]+loc[3][0])/4)
                    y = int((loc[0][1]+loc[1][1]+loc[2][1]+loc[3][1])/4)
                    return (x,y),rate
            # 部分匹配/误差字数为1
            if mode == 2:
                for t in text:
                    if t in content[ocr_num:]:
                        ocr_num = ocr_num + 1
                if ocr_num >= nums - 1:
                    x = int((loc[0][0]+loc[1][0]+loc[2][0]+loc[3][0])/4)
                    y = int((loc[0][1]+loc[1][1]+loc[2][1]+loc[3][1])/4)
                    return (x,y),rate
        return (0,0),0

    def ocr_click(self,text:str,points=(0,0,0,0),overtime=5.0,rates=0.1,mode=1):
        """
        说明:
            识别到文本点击，否则不点击
        参数:
            text:需要检测的文本内容
            points:截图区域
            overtime:识别时间
            rates:对比度要求
            mode:匹配模式
        """
        img = self.take_screenshot(points)
        pos,rate = self.ocr_match(img,text,mode)
        start_time = time.time()
        while rate < rates and time.time() - start_time < overtime:
            img = self.take_screenshot(points)
            pos,rate = self.ocr_match(img,text,mode)
            time.sleep(0.05)
        if rate >= rates:
            log.info(f"OCR点击-{rate}")
            left, top, right, bottom = self.get_WindowRect(self.hwnd)
            x = left + points[0] + pos[0]
            y = top + points[1] + pos[1]
            self.Mouse.position = (x,y)
            time.sleep(0.1)
            self.Mouse.press(mouse.Button.left)
            time.sleep(0.5)
            self.Mouse.release(mouse.Button.left)
            return True
        else:
            log.warning(f"OCR点击失败-{rate}")
            return False

    def ocr_check(self,text:str,points=(0,0,0,0),overtime=5.0,rates=0.1,mode=1):
        """
        说明:
            识别文本返回布尔值
        参数:
            text:需要检测的文本内容
            points:截图区域
            overtime:识别时间
            rates:对比度要求
            mode:匹配模式
        """
        img = self.take_screenshot(points)
        pos,rate = self.ocr_match(img,text,mode)
        start_time = time.time()
        while rate < rates and time.time() - start_time < overtime:
            img = self.take_screenshot(points)
            pos,rate = self.ocr_match(img,text,mode)
            time.sleep(0.05)
        if rate >= rates:
            log.info(f"OCR检测-{rate}")
            return True
        else:
            log.warning(f"OCR检测-{rate}")
            return False

    def mouse_pos(self,points):
        """
        说明:
            返回实际鼠标坐标
        参数:
            points:相对游戏坐标
        """
        left, top, right, bottom = self.get_WindowRect(self.hwnd)
        x = left + points[0]
        y = top + points[1]
        return (x,y)

    def move(self,key:str = ["w","a","s","d"],times=1.0):
        """
        说明:
            移动
        参数:
            key:方位按键
            times:执行时间
        """
        log.info(f"执行{key}---{times}")
        self.Keyboard.press(Key.shift_l)
        self.Keyboard.press(key)
        start_time = time.perf_counter()
        while time.perf_counter() - start_time < times:
            pass
        self.Keyboard.release(key)
        self.Keyboard.release(Key.shift_l)

    def mouse_move(self,value:int):
        win32api.mouse_event(1,int(value*self.scale),0)
        time.sleep(0.5)

    def has_red(self, points=(0,0,0,0)):
        """
        说明:
            判断游戏指定位置是否有红色
        参数:
            points: 图像截取范围
        """
        img = self.take_screenshot(points)
        hsv_img = cv.cvtColor(img, cv.COLOR_BGR2HSV)

        lower_red = np.array([0, 100, 100])
        upper_red = np.array([10, 255, 255])
        lower_red2 = np.array([170, 100, 100])
        upper_red2 = np.array([180, 255, 255])

        mask1 = cv.inRange(hsv_img, lower_red, upper_red)
        mask2 = cv.inRange(hsv_img, lower_red2, upper_red2)
        mask = cv.bitwise_or(mask1, mask2)
        # 统计掩膜中的像素数目
        red_pixel_count = cv.countNonZero(mask)
        print(red_pixel_count)
        return red_pixel_count > 50

    def get_whiteimg(self,img):
        """
        说明:
            对白色做提取处理,返回RGB图像
        参数:
            img:提供的原始图片
        """
        hsv_img = cv.cvtColor(img, cv.COLOR_BGR2HSV)
        # 设置白色提取范围
        lower_white = np.array([0,0,70])
        upper_white = np.array([180,43,255])

        mask = cv.inRange(hsv_img,lower_white,upper_white)
        # 维度扩充并归一化
        mask = cv.merge([mask,mask,mask])
        return mask

    def get_pix_hsv(self,points=(0,0)):
        """
        说明:
            返回hsv像素值
        参数:
            points:相对游戏坐标
        """
        img = self.take_screenshot()
        hsv = cv.cvtColor(img, cv.COLOR_RGB2HSV)
        x = points[0]
        y = points[1]
        h = hsv[y,x,0]
        s = hsv[y,x,1]
        v = hsv[y,x,2]
        return [h,s,v]

    def fighting(self,mode=1):
        if mode == 1:   # 打怪
            log.info("打怪")
            self.Mouse.click(mouse.Button.left)
            time.sleep(1)
            if self.img_check("finish_fighting.jpg",(1735,1025,1920,1080),2):
                if self.has_red((50,68,230,245)):
                    self.wait_fight_end()
                    return True
            else:
                self.wait_fight_end()
                return True
        else:   # 打障碍物
            self.Mouse.click(mouse.Button.left)
            time.sleep(0.7)
            return True

    def wait_main_interface(self):
        start_time = time.time()    # 开始计算等待时间
        while True:
            if self.img_click("fighting_lost.jpg",(700,140,1200,400),2):
                log.info("战斗失败")
                break
            if self.img_check("finish_fighting.jpg",(1735,1025,1920,1080),2):
                break
            time.sleep(3)
            if time.time() - start_time > 600:
                return False
        time.sleep(2)   # 等待人物模型出现
        return True

    def check_main_interface(self):
        log.info("强制在主界面")
        if self.img_check("finish_fighting.jpg",(1735,1025,1920,1080),10):
            time.sleep(2)   # 等待人物模型出现
        else:
            while not self.img_check("finish_fighting.jpg",(1735,1025,1920,1080),1):
                self.Keyboard.press(Key.esc)
                time.sleep(0.05)
                self.Keyboard.release(Key.esc)
                time.sleep(1)
        return True

    def wait_fight_end(self):
        """
        说明:
            等待战斗结束
        """
        log.info("等待战斗结束")
        time.sleep(7)   # 缓冲
        self.wait_main_interface()

    def interaction(self,mode:str = ["w","a","s","d"]):
        """
        说明:
            等待传送结束
        """
        log.info("执行交互---传送")
        time.sleep(1)   # 截图识别延时性修复
        start_time = time.time()    # 开始计算等待时间
        while not self.img_check("interaction.jpg",(1000,580,1100,660),1):
            self.Keyboard.press(mode)
            time.sleep(0.1)
            self.Keyboard.release(mode)
            # 超时中断
            if time.time() - start_time > 30:
                return False
        time.sleep(0.5)
        self.Keyboard.press("f")
        time.sleep(0.05)
        self.Keyboard.release("f")
        time.sleep(2)   # 缓冲
        self.wait_main_interface()

    def open_map(self):
        """
        说明:
            打开地图
        """
        log.info("进入地图")
        while not self.img_check("map_navigation.jpg",(40,40,100,100),2):
            self.Keyboard.press("m")
            time.sleep(0.05)
            self.Keyboard.release("m")

    def release_mouse_keyboard(self):
        """
        说明:
            释放键鼠操作
        """
        self.Mouse.release(mouse.Button.left)
        for key in ["w","a","s","d","f",Key.esc,Key.alt_l]:
            self.Keyboard.release(key)
        return True

    def close_game(self):
        """
        说明:
            自动关机
        """
        # 关闭游戏
        self.Keyboard.press(Key.esc)
        time.sleep(0.05)
        self.Keyboard.release(Key.esc)
        self.img_click("exit1.png")
        self.img_click("sure.jpg")
        self.img_click("exit2.png",overtime=60)
        self.img_click("sure2.png",overtime=10)
        # 关机
        time.sleep(5)
        os.system('shutdown /s /t 60')
        # 关闭程序
        self.Keyboard.press(Key.f8)
        time.sleep(0.05)
        self.Keyboard.release(Key.f8)
        return True

    def change_team(self,teamid:int,id:int):
        """
        说明:
            切换队伍
        """
        log.info("切换队伍")
        self.Keyboard.press('t')
        time.sleep(0.5)
        self.Keyboard.release('t')
        time.sleep(1)   # 缓冲
        teamid = '0' + str(teamid)
        self.ocr_click(text=teamid,points=(550,0,1300,130))
        self.img_click("team_sure.png",points=(1500,950,1920,1080),overtime=2)
        time.sleep(2)   # 缓冲
        self.Keyboard.press(Key.esc)
        time.sleep(0.05)
        self.Keyboard.release(Key.esc)
        self.check_main_interface()
        if id >= 1 and id <= 4:
            id = str(id)
            self.Keyboard.press(id)
            time.sleep(0.05)
            self.Keyboard.release(id)

    def commission(self):
        """
        说明:
            委托功能
        """
        log.info("清委托")
        self.Keyboard.press(Key.esc)
        time.sleep(0.05)
        self.Keyboard.release(Key.esc)
        self.ocr_click(text='委托',points=(1700,400,1755,425))
        while self.img_click('red_notice.jpg',overtime=5,rates=0.80):
            if self.ocr_click(text='领取',points=(1460,880,1520,920),overtime=2):
                self.ocr_click(text='再次派遣',points=(1170,930,1300,960),overtime=2,mode=2)
        while not self.img_check("finish_fighting.jpg",(1735,1025,1920,1080),1):
            self.Keyboard.press(Key.esc)
            time.sleep(0.05)
            self.Keyboard.release(Key.esc)

    def login(self):
        """
        说明:
            登录功能
        """
        log.info("登录界面")
        while not self.img_check("finish_fighting.jpg",(1735,1025,1920,1080),1):
            self.Mouse.position = self.mouse_pos((950,900))
            self.Mouse.click(mouse.Button.left)
            time.sleep(1)

utils/calculated.py
Souloco/StarRail-FastRun
from .calculated import Calculated
from .log import log
import win32clipboard
from pynput.keyboard import Key
import time
class CDK:
    def __init__(self):
        self.calculated = Calculated()

    def writeToClipBoard(self,text):
        """
        说明：
            写入剪切板
        参数：
            text：写入的文本
        """
        win32clipboard.OpenClipboard()
        win32clipboard.EmptyClipboard()
        win32clipboard.SetClipboardText(text)
        win32clipboard.CloseClipboard()

    def Enter_cdk(self):
        """
        说明：
            进入兑换码页面
        """
        self.calculated.Keyboard.press(Key.esc)
        time.sleep(0.05)
        self.calculated.Keyboard.release(Key.esc)
        self.calculated.img_click("cdk_1.jpg",(1710,70,1800,120))
        self.calculated.img_click("cdk_2.jpg",(1410,180,1800,260))

    def cdk(self,text,times):
        if times <= 3:
            log.info(f"兑换中---{text}---第{times}次")
            self.writeToClipBoard(text)
            self.calculated.img_click("cdk_clear.jpg",(400,320,1525,755),1)
            self.calculated.img_click("cdk_copy.jpg",(400,320,1525,755))
            self.calculated.img_click("sure.jpg",(400,320,1525,755))
            if self.calculated.img_check("cdk_yes.jpg",(400,320,1525,755),2):
                self.calculated.img_click("sure.jpg",(400,320,1525,755))
                log.info(f"兑换中---{text}---兑换成功")
            else:
                log.info(f"兑换中---{text}---兑换失败")
                time.sleep(5)
                self.cdk(text,times+1)

    def cdk_all(self,cdk_list):
        log.info("兑换开始")
        self.Enter_cdk()
        for cdk in cdk_list:
            self.cdk(cdk,1)
        log.info("兑换结束")
        return True

utils/cdk.py
Souloco/StarRail-FastRun
import json
import os
import cv2 as cv
import sys
# 路径
if getattr(sys, 'frozen', False):
    root_dir = os.path.dirname(sys.executable)
elif __file__:
    root_dir = os.path.dirname((os.path.dirname(os.path.abspath(__file__))))
# map路径
map_dir = os.path.join(root_dir,"map")
# picture路径
picture_dir = os.path.join(root_dir,"picture")
# model路径
model_dir = os.path.join(root_dir,"model")
# dungeon路径
dungeon_dir = os.path.join(root_dir,"dungeon")
# 配置文件名字
CONFIG_FILE_NAME = "config.json"
def set_dir(root_path):
    """
    说明：
        刷新路径
    参数：
        root_path：替换后的根路径
    """
    global root_dir
    global picture_dir
    global map_dir
    global model_dir
    root_dir = root_path
    map_dir = os.path.join(root_path,"map")
    picture_dir = os.path.join(root_path,"picture")
    model_dir = os.path.join(root_path,"model")
def read_map():
    """
    说明：
        读取地图文件
    """
    map_list = os.listdir(map_dir)
    return map_list
def read_map_name():
    """
    说明：
        读取地图文件名字，返回列表
    """
    map_list = read_map()
    map_name_list = []
    map_planet_list = []
    for map in map_list:
        planet_id = int(map[map.index('_') + 1:map.index('-')])
        with open(os.path.join(map_dir,map),encoding='utf-8') as f:
            data = json.load(f)
            map_name = data["name"]
            if len(map_name_list) < planet_id:
                map_name_list.append([])
                map_planet_list.append([])
            map_name_list[planet_id-1].append(map_name)
            map_planet_list[planet_id-1].append(map)
    return map_name_list,map_planet_list
def read_json_info(path,info,prepath=""):
    """
    说明：
        读取单个json的info字段信息
    参数：
        path：json名字
        info:信息字段
        prepath:修补目录
    """
    with open(os.path.join(root_dir,prepath,path),encoding='utf-8') as f:
        data = json.load(f)
        info_list = data[info]
    return info_list

def save_dungeon_info(dungeon,info,value):
    """
    说明：
        保存单个dungeon的info字段信息
    参数：
        dungeon：地图json
        info:信息字段
    """
    with open(os.path.join(dungeon_dir,dungeon),"r",encoding='utf-8') as f:
        data = json.load(f)
        data[info] = value
    with open(os.path.join(dungeon_dir,dungeon), "w", encoding="utf-8") as f:
        json.dump(data,f,indent=2,ensure_ascii=False)

def read_maplist_name():
    """
    说明：
        读取map列表的info字段信息，返回列表
    参数：
        map：地图json名字
        info:信息字段
    """
    map_list = read_map()
    map_name_list = []
    for map in map_list:
        with open(os.path.join(map_dir,map),encoding='utf-8') as f:
            data = json.load(f)
            map_name = data["name"]
        map_name_list.append(map_name)
    return map_name_list
def read_picture(imgpath):
    """
    说明：
        读取图片
    参数：
        imgpath：图片名
    """
    img = cv.imread(os.path.join(picture_dir,imgpath))
    return img

def set_config(info:str,value):
    with open(os.path.join(root_dir,CONFIG_FILE_NAME),"r",encoding='utf-8') as f:
        data = json.load(f)
        data[info] = value
    with open(os.path.join(root_dir,CONFIG_FILE_NAME), "w", encoding="utf-8") as f:
        json.dump(data,f,indent=2,ensure_ascii=False)

def get_config(info:str):
    with open(os.path.join(root_dir,CONFIG_FILE_NAME),"r",encoding='utf-8') as f:
        data = json.load(f)
        info_list = data[info]
        return info_list
utils/config.py
Souloco/StarRail-FastRun
import time
from .calculated import Calculated
from .config import read_json_info
from .log import log
from pynput import mouse
from pyautogui import drag
from pynput.keyboard import Key
class Dungeon:
    def __init__(self):
        self.calculated = Calculated()
        self.team_change = False
        self.teamid = 1
        self.id = 1
        self.stop = False

    def open_dungeon(self):
        self.calculated.check_main_interface()
        self.calculated.Keyboard.press(Key.f4)
        time.sleep(0.05)
        self.calculated.Keyboard.release(Key.f4)
        self.calculated.img_click("dungeon_main.png",overtime=2)

    def enter_dungeon(self,dungeonpath:str,nums=1):
        self.open_dungeon()
        dungeon_id = dungeonpath[dungeonpath.index("_")+1:dungeonpath.index("-")]
        dungeon_name_dir = dungeonpath[0:dungeonpath.index("-")] + ".jpg"
        # 向下滚动寻找
        if not self.calculated.img_check(dungeon_name_dir,(0,0,0,0),1):
            self.calculated.Mouse.position = self.calculated.mouse_pos((450,400))
            drag(0,-150, 1,button='left')
        self.calculated.img_click(dungeon_name_dir)
        # 向下滚动寻找
        for i in range(5):
            if not self.calculated.img_check(dungeonpath,(0,0,0,0),1):
                self.calculated.Mouse.position = self.calculated.mouse_pos((1200,900))
                drag(0,-300, 1,button='left')
            else:
                break
        self.calculated.dungeon_img_click(dungeonpath)
        time.sleep(2)
        while not self.calculated.img_check("dungeon_fight1.jpg",overtime=1.0):
            time.sleep(1)
        if self.calculated.has_red((1250,950,1300,1000)):
            log.info("退出副本")
            self.calculated.img_click("exit.jpg")
            time.sleep(0.5)
            return True
        log.info("进入副本")
        self.calculated.img_click("dungeon_fight1.jpg")
        if self.calculated.img_check("dungeon_fight2.jpg",overtime=2):
            self.calculated.img_click("dungeon_fight2.jpg")
        else:
            self.calculated.img_click("exit3.jpg")
            self.calculated.img_click("exit.jpg")
            time.sleep(0.5)
            return True
        # 凝滞虚影主动打怪
        if dungeon_id == '3':
            self.calculated.wait_main_interface()
            self.calculated.fighting(2)
        while nums > 1:
            while not self.calculated.img_check("dungeon_again.jpg",overtime=2):
                if self.stop:
                    return True
                time.sleep(5)
            if self.calculated.has_red((1085,930,1120,960)):
                self.calculated.img_click("dungeon_exit.jpg")
                time.sleep(2)
                return True
            self.calculated.img_click("dungeon_again.jpg")
            time.sleep(2)
            nums = nums - 1
        while not self.calculated.img_check("dungeon_again.jpg",overtime=2):
            if self.stop:
                return True
            time.sleep(5)
        self.calculated.img_click("dungeon_exit.jpg")
        self.calculated.wait_fight_end()

    def start(self,dungeonlist):
        log.info("游戏初始化设置")
        self.stop = False
        self.calculated.set_windowsize()
        if self.team_change:
            log.info("清体力---切换队伍")
            self.calculated.change_team(self.teamid,self.id)
        log.info("清体力---开始")
        for dungeon in dungeonlist:
            if self.stop:
                log.info("清体力---暂停成功")
                return True
            for key,value in dungeon.items():
                log.info(f"清体力---执行副本{key}---{value}")
                dungeonpath = read_json_info("dungeon.json",key,prepath="dungeon")
                self.enter_dungeon(dungeonpath,value)
        log.info("清体力---执行完毕")
        self.stop = False

utils/dungeon.py
Souloco/StarRail-FastRun
from loguru import logger
import os
# 根路径
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# log路径
log_dir = os.path.join(root_dir,"logs")
log_path = os.path.join(log_dir,'日志文件.log')

log = logger.bind(file=log_path)
log.add(log_path,format="{time:HH:mm:ss} " + "|{level}| " + "{module}.{function}:{line} - {message}",
        rotation="1 days",enqueue=True,serialize=False,encoding="utf-8",retention="14 days")

utils/log.py
Souloco/StarRail-FastRun
import win32gui
import time
from .config import read_json_info
from .calculated import Calculated
from .log import log
from pynput import mouse
from pyautogui import drag
class Map:
    def __init__(self):
        self.calculated = Calculated()
        self.team_change = False
        self.teamid = 1
        self.id = 1
        self.commission = False
        self.close_game = False
        self.nums = 0
        self.stop = False

    def Enter_map_start(self,mapjson):
        """
        说明:
            执行mapjson的start列表
        参数:
            mapjson:路线json名
        """
        start_list = read_json_info(mapjson,"start",prepath="map")
        map_name_dir = mapjson[0:mapjson.index("_",5)] + ".jpg"
        map_name = read_json_info(mapjson,"name",prepath="map").split("-")[0]
        planet_id = int(mapjson[mapjson.index('_') + 1:mapjson.index('-')])
        for start in start_list:
            if self.stop:
                return True
            for key,value in start.items():
                if key == "map":
                    # 激活窗口
                    win32gui.SetForegroundWindow(self.calculated.hwnd)
                    self.calculated.wait_main_interface()
                    if not self.calculated.ocr_check(map_name,(0,0,200,40),1,mode=2):
                        # 进入地图
                        self.calculated.open_map()
                        # 进入星球
                        log.info("进入星球")
                        for i in range(3):
                            if self.calculated.img_check("planet_navigation.jpg",(40,40,100,100),2):
                                break
                            else:
                                self.calculated.img_click("Enter_planet.jpg",(1440,115,1870,160))
                        planet_img = "orientation_{planet_id}.png".format(planet_id=planet_id)
                        for i in range(3):
                            if self.calculated.img_check("map_navigation.jpg",(40,40,100,100),2):
                                break
                            else:
                                self.calculated.img_click(planet_img)
                        # 进入地名
                        log.info("进入地名")
                        # 滚动寻找
                        # 向下滚动寻找
                        for i in range(5):
                            if not self.calculated.img_check(map_name_dir,(1420,180,1890,1020),1):
                                self.calculated.Mouse.position = self.calculated.mouse_pos((1750,250))
                                self.calculated.Mouse.scroll(0,-200)
                            else:
                                break
                        # 向上滚动寻找
                        for i in range(5):
                            if not self.calculated.img_check(map_name_dir,(1420,180,1890,1020),1):
                                self.calculated.Mouse.position = self.calculated.mouse_pos((1750,250))
                                self.calculated.Mouse.scroll(0,200)
                            else:
                                break
                        self.calculated.img_click(map_name_dir,(1420,180,1890,1020),2)
                    else:
                        # 进入地图
                        self.calculated.open_map()
                    if value != "":
                        # 进入层数
                        log.info(f"进入{value}层")
                        self.calculated.ocr_click(value+"层",(0,700,125,1010),1)
                elif "point" in key:
                    log.info("寻找传送点")
                    # 向下滚动寻找
                    for i in range(5):
                        if not self.calculated.img_check(key,(0,0,0,0),1):
                            self.calculated.Mouse.position = self.calculated.mouse_pos((1330,440))
                            drag(0,-400, 1,button='left')
                        else:
                            break
                    # 向上滚动寻找
                    for i in range(5):
                        if not self.calculated.img_check(key,(0,0,0,0),1):
                            self.calculated.Mouse.position = self.calculated.mouse_pos((1330,640))
                            drag(0,400, 1,button='left')
                        else:
                            break
                    self.calculated.img_click(key,(0,0,0,0),overtime=value)
                elif key == "transfer":
                    log.info("进行传送")
                    self.calculated.img_click("transfer.jpg",(1470,945,1840,1000),overtime=value)
                    self.calculated.check_main_interface()
                else:
                    self.calculated.img_click(key,(0,0,0,0),overtime=value)

    def Enter_map_fighting(self,mapjson):
        """
        说明:
            执行mapjson的map列表
        参数:
            mapjson:路线json名
        """
        map_name = mapjson[0:mapjson.index(".")]
        operate_list = read_json_info(mapjson,"map",prepath="map")
        for operate in operate_list:
            if self.stop:
                return True
            for key,value in operate.items():
                if key in ["w","s","a","d"]:
                    self.calculated.move(key,value)
                elif key == "fighting":
                    self.calculated.fighting(value)
                elif key == "mouse_move":
                    self.calculated.mouse_move(value)
                elif key == "f":
                    self.calculated.interaction(value)
                elif key == "delay":
                    time.sleep(value)
            logtime = time.strftime("%m-%d-%H-%M-%S",time.localtime())
            self.calculated.save_screenshot(f"{map_name}-{logtime}")

    def Enter_map_onejson(self,mapjson):
        """
        说明:
            执行单个mapjson
        参数:
            mapjson:路线json名
        """
        map_name = read_json_info(mapjson,"name",prepath="map")
        log.info(f"当前执行路线:{map_name}")
        self.Enter_map_start(mapjson)
        self.Enter_map_fighting(mapjson)

    def Enter_map_jsonlist(self,jsonlist):
        """
        说明:
            执行json列表
        参数:
            mapjson:路线json列表
        """
        for mapjson in jsonlist:
            self.Enter_map_onejson(mapjson)
            if self.stop:
                log.info("锄大地---暂停成功")
                return True

    def map_init(self):
        log.info("地图初始化")
        self.calculated.Keyboard.press("m")
        self.calculated.Keyboard.release("m")
        self.calculated.img_check("map_navigation.jpg",(40,40,100,100),2)
        self.calculated.img_click("map_init_2.jpg",(600,970,660,1000))
        while not self.calculated.img_check("map_init_1.jpg",(660,970,695,1000),2):
            self.calculated.Mouse.press(mouse.Button.left)
            time.sleep(2)
            self.calculated.Mouse.release(mouse.Button.left)
        self.calculated.Keyboard.press("m")
        self.calculated.Keyboard.release("m")
        time.sleep(1)

    def start(self,map_list,auto_map_list):
        log.info("游戏初始化设置")
        self.stop = False
        self.calculated.set_windowsize()
        self.calculated.check_main_interface()
        if self.commission:
            log.info("清委托")
            self.calculated.commission()
        if self.team_change:
            log.info("锄大地---切换队伍")
            self.calculated.change_team(self.teamid,self.id)
        log.info("锄大地---地图初始化")
        self.map_init()
        log.info("锄大地---必跑路线")
        self.Enter_map_jsonlist(map_list)
        log.info("锄大地---重跑路线")
        for i in range(self.nums):
            self.Enter_map_jsonlist(auto_map_list)
        if self.close_game:
            log.info("锄大地---自动关机")
            self.calculated.close_game()
        log.info("锄大地---执行完毕")
        self.stop = False

    def check_map(self):
        log.info("锄大地---打开背包")
        self.calculated.Keyboard.press('b')
        self.calculated.Keyboard.release("b")
        self.calculated.img_click("check1.jpg",(0,0,1300,950),overtime=2)

utils/map.py
Souloco/StarRail-FastRun
import numpy as np
import win32gui
import win32ui
import win32con
import cv2
class Screenhoot():
    def __init__(self):
        # 根据窗口句柄获取窗口的设备上下文DC（Divice Context）
        self.hwndDC = win32gui.GetWindowDC(0)
        # 根据窗口的DC获取mfcDC
        self.mfcDC = win32ui.CreateDCFromHandle(self.hwndDC)
        # mfcDC创建可兼容的DC
        self.saveDC = self.mfcDC.CreateCompatibleDC()
        # 创建bigmap准备保存图片
        self.saveBitMap = win32ui.CreateBitmap()
        # 截取窗口大小
        self.width = 1920
        self.height = 1080
        # 为bitmap开辟空间
        self.saveBitMap.CreateCompatibleBitmap(self.mfcDC,self.width, self.height)
        # 将截图保存到saveBitmap中
        self.saveDC.SelectObject(self.saveBitMap)

    def grab(self,x,y):
        self.saveDC.BitBlt((0, 0), (self.width, self.height), self.mfcDC, (x,y), win32con.SRCCOPY)
        # self.saveBitMap.SaveBitmapFile(self.saveDC,"")
        img_buf = self.saveBitMap.GetBitmapBits(True)
        return np.frombuffer(img_buf, dtype="uint8").reshape(self.height,self.width,4)[:,:,:3]

    def __del__(self):
        # 释放内存
        self.mfcDC.DeleteDC()
        win32gui.DeleteObject(self.saveBitMap.GetHandle())
utils/screenhoot.py
Souloco/StarRail-FastRun