帝国时代4修改器

ui界面

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
🎮 帝国时代4修改器 v1.0 by简彦36827
Age of Empires IV Cheat Tool - Modern GUI Version

现代化GUI界面,包含:
- 左侧菜单导航
- 游戏注入设置
- 快捷键管理
- 全局热键控制
- 执行日志查看
- 版本自动检测
"""

import tkinter as tk
from tkinter import ttk, messagebox, filedialog, simpledialog
import json
import os
import time
import threading
import keyboard
import pyautogui
import shutil
import requests
import webbrowser
from pathlib import Path

class ModernAOECheatGUI:
    def __init__(self):
        """初始化现代化GUI界面"""
        # 版本信息
        self.version = "1.0"
        self.version_url = "https://www.8cdn.cn/cln_ver.js"
        self.download_url = "https://oss.8cdn.cn/"
      
        # 配置文件
        self.config_file = "aoe_cheat_config.json"
        self.config = self.load_config()
      
        # 运行状态
        self.is_running = False
        self.hotkey_listener = None
      
        # 设置pyautogui
        pyautogui.PAUSE = 0.05
        pyautogui.FAILSAFE = True
      
        # 创建主窗口
        self.root = tk.Tk()
        self.setup_window()
        self.create_widgets()
        self.setup_hotkeys()
      
        # 启动时检查版本 - 延迟执行确保UI组件已创建
        self.root.after(1000, lambda: threading.Thread(target=self.check_version, daemon=True).start())

    def setup_window(self):
        """设置主窗口"""
        self.root.title("🎮 帝国时代4修改器 v1.0 by简彦36827")
        self.root.geometry("1000x750")
        self.root.resizable(True, True)
      
        # 设置窗口图标(如果有的话)
        try:
            self.root.iconbitmap("icon.ico")
        except:
            pass
      
        # 设置现代化主题
        style = ttk.Style()
      
        # 尝试使用vista或clam主题
        available_themes = style.theme_names()
        if 'vista' in available_themes:
            style.theme_use('vista')
        elif 'clam' in available_themes:
            style.theme_use('clam')
        else:
            style.theme_use('default')
      
        # 重新设计的协调配色方案 - 浅色按钮配黑色文字
        self.colors = {
            'bg_primary': '#93c5fd',      # 浅蓝色(主色调)
            'bg_secondary': '#60a5fa',     # 中蓝色(辅助色)
            'bg_accent': '#dbeafe',        # 很浅蓝色(强调色)
            'bg_light': '#f0f9ff',         # 极浅蓝色(背景色)
            'text_primary': '#ffffff',     # 纯白(按钮文字)
            'text_secondary': '#1f2937',   # 深灰色(正文)
            'text_dark': '#111827',        # 更深的文字色(标题)
            'success': '#86efac',          # 浅绿色(成功)
            'warning': '#fde68a',          # 浅黄色(警告)
            'danger': '#fca5a5',           # 浅红色(危险)
            'sidebar_bg': '#f8fafc',       # 侧边栏背景(浅灰白)
            'content_bg': '#ffffff',       # 内容区背景(纯白)
            'border': '#e5e7eb',           # 边框(浅灰)
            'hover': '#7dd3fc'             # 悬停色(浅蓝)
        }
      
        # 配置ttk样式 - 统一的蓝色系主题
        style.configure('Sidebar.TFrame', background=self.colors['sidebar_bg'])
        style.configure('Content.TFrame', background=self.colors['content_bg'])
        style.configure('Title.TLabel', 
                       background=self.colors['sidebar_bg'], 
                       foreground=self.colors['bg_primary'],
                       font=('微软雅黑', 12, 'bold'))
        style.configure('Version.TLabel', 
                       background=self.colors['sidebar_bg'], 
                       foreground=self.colors['text_secondary'],
                       font=('微软雅黑', 8))
        style.configure('MenuButton.TButton',
                       background=self.colors['bg_light'],
                       foreground=self.colors['text_dark'],
                       padding=(8, 10),
                       font=('微软雅黑', 9),
                       borderwidth=1,
                       relief='solid')
      
        # 配置按钮样式 - 协调的蓝色系,使用黑色文字提高可读性
        style.configure('Accent.TButton',
                       background=self.colors['bg_primary'],
                       foreground='black',
                       padding=(12, 8),
                       font=('微软雅黑', 9, 'bold'),
                       borderwidth=0)
        style.map('Accent.TButton',
                 background=[('active', self.colors['hover'])])
      
        style.configure('Success.TButton',
                       background=self.colors['success'],
                       foreground='black',
                       padding=(12, 10),
                       font=('微软雅黑', 9, 'bold'),
                       borderwidth=0)
        style.map('Success.TButton',
                 background=[('active', '#65f3a0')])
      
        style.configure('Warning.TButton',
                       background=self.colors['warning'],
                       foreground='black',
                       padding=(10, 8),
                       font=('微软雅黑', 9, 'bold'),
                       borderwidth=0)
        style.map('Warning.TButton',
                 background=[('active', '#fcd34d')])
      
        style.configure('Danger.TButton',
                       background=self.colors['danger'],
                       foreground='black',
                       padding=(10, 8),
                       font=('微软雅黑', 9, 'bold'),
                       borderwidth=0)
        style.map('Danger.TButton',
                 background=[('active', '#f87171')])
      
        # 配置Treeview样式
        style.configure('Modern.Treeview',
                       background='white',
                       foreground=self.colors['text_dark'],
                       fieldbackground='white',
                       borderwidth=1,
                       relief='solid')
        style.configure('Modern.Treeview.Heading',
                       background=self.colors['bg_primary'],
                       foreground='black',
                       font=('微软雅黑', 9, 'bold'))
      
        # 配置Entry样式
        style.configure('Modern.TEntry',
                       borderwidth=1,
                       relief='solid',
                       fieldbackground='white',
                       bordercolor=self.colors['border'])
      
        # 配置LabelFrame样式
        style.configure('Modern.TLabelframe',
                       background=self.colors['content_bg'],
                       borderwidth=1,
                       relief='solid',
                       bordercolor=self.colors['border'])
        style.configure('Modern.TLabelframe.Label',
                       background=self.colors['content_bg'],
                       foreground=self.colors['bg_primary'],
                       font=('微软雅黑', 10, 'bold'))

    def create_widgets(self):
        """创建界面组件"""
        # 主容器
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
      
        # 左侧菜单区域
        self.create_menu_panel(main_frame)
      
        # 右侧内容区域
        self.create_content_panel(main_frame)
      
        # 默认显示注入菜单
        self.show_injection_panel()

    def create_menu_panel(self, parent):
        """创建左侧菜单面板"""
        # 菜单容器 - 使用自定义样式,增加宽度
        menu_frame = ttk.Frame(parent, width=220, style='Sidebar.TFrame')
        menu_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 1))
        menu_frame.pack_propagate(False)
      
        # 标题 - 使用自定义样式
        title_label = ttk.Label(menu_frame, text="🎮 帝国时代4修改器", 
                               style='Title.TLabel')
        title_label.pack(pady=(15, 25))
      
        # 菜单按钮
        self.menu_buttons = {}
      
        menus = [
            ("💉 注入菜单", self.show_injection_panel),
            ("🎯 快捷键菜单", self.show_hotkey_panel),
            ("🔥 全局热键", self.show_global_hotkey_panel),
            ("📝 日志菜单", self.show_log_panel),
            ("🎮 启动游戏", self.launch_game_directly)
        ]
      
        # 菜单按钮容器
        buttons_frame = ttk.Frame(menu_frame, style='Sidebar.TFrame')
        buttons_frame.pack(fill=tk.X, padx=10)
      
        for text, command in menus:
            btn = ttk.Button(buttons_frame, text=text, command=command, 
                           width=25, style='MenuButton.TButton')
            btn.pack(pady=3, fill=tk.X)
            self.menu_buttons[text] = btn
      
        # 底部版本信息区域
        version_frame = ttk.Frame(menu_frame, style='Sidebar.TFrame')
        version_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=(0, 15))
      
        # 分隔线
        separator = ttk.Separator(version_frame, orient='horizontal')
        separator.pack(fill=tk.X, pady=(0, 10))
      
        # 版本号标签 - 可点击
        self.version_label = ttk.Label(version_frame, 
                                      text=f"V{self.version} ", 
                                      style='Version.TLabel',
                                      cursor='hand2')
        self.version_label.pack(anchor=tk.CENTER)
      
        # 绑定点击事件
        self.version_label.bind('<Button-1>', self.on_version_click)
      
        # 版本状态标签
        self.version_status_var = tk.StringVar(value="点击检查更新")
        self.version_status_label = ttk.Label(version_frame, 
                                             textvariable=self.version_status_var,
                                             style='Version.TLabel',
                                             cursor='hand2')
        self.version_status_label.pack(anchor=tk.CENTER, pady=(2, 0))
      
        # 绑定点击事件到状态标签也可以触发检测
        self.version_status_label.bind('<Button-1>', self.on_version_click)

    def create_content_panel(self, parent):
        """创建右侧内容面板"""
        # 内容容器 - 使用自定义样式
        self.content_frame = ttk.Frame(parent, style='Content.TFrame')
        self.content_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
      
        # 创建各个面板(默认隐藏)
        self.create_injection_panel()
        self.create_hotkey_panel()
        self.create_global_hotkey_panel()
        self.create_log_panel()

    def create_injection_panel(self):
        """创建游戏注入设置面板"""
        self.injection_panel = ttk.LabelFrame(self.content_frame, text="💉 游戏注入设置", 
                                            padding=20, style='Modern.TLabelframe')
      
        # 游戏目录设置
        dir_frame = ttk.Frame(self.injection_panel, style='Content.TFrame')
        dir_frame.pack(fill=tk.X, pady=(0, 25))
      
        ttk.Label(dir_frame, text="🎮 游戏目录设置", 
                 font=('微软雅黑', 12, 'bold'),
                 background=self.colors['content_bg'],
                 foreground=self.colors['text_dark']).pack(anchor=tk.W, pady=(0, 10))
      
        # 路径输入区域
        path_frame = ttk.Frame(dir_frame, style='Content.TFrame')
        path_frame.pack(fill=tk.X, pady=(0, 12))
      
        self.game_path_var = tk.StringVar(value=self.config.get('game_path', ''))
        path_entry_frame = tk.Frame(path_frame, bg=self.colors['content_bg'], relief='solid', borderwidth=1)
        path_entry_frame.pack(fill=tk.X, padx=(0, 8), side=tk.LEFT, expand=True)
      
        self.game_path_entry = tk.Entry(path_entry_frame, textvariable=self.game_path_var, 
                                       state='readonly', font=('微软雅黑', 9),
                                       bg='white', fg=self.colors['text_secondary'],
                                       relief='flat', borderwidth=0)
        self.game_path_entry.pack(fill=tk.X, padx=8, pady=6)
      
        ttk.Button(path_frame, text="📁 浏览", command=self.browse_game_directory,
                  style='Accent.TButton', width=10).pack(side=tk.RIGHT)
      
        # 自动检测按钮
        detect_frame = ttk.Frame(dir_frame, style='Content.TFrame')
        detect_frame.pack(fill=tk.X, pady=(0, 15))
      
        ttk.Button(detect_frame, text="🔍 自动检测游戏目录", 
                  command=self.auto_detect_game_directory,
                  style='Warning.TButton', width=18).pack(side=tk.LEFT)
      
        # 注入状态
        status_frame = ttk.Frame(self.injection_panel, style='Content.TFrame')
        status_frame.pack(fill=tk.X, pady=(0, 20))
      
        ttk.Label(status_frame, text="注入状态:", 
                 font=('微软雅黑', 11, 'bold'),
                 background=self.colors['content_bg'],
                 foreground=self.colors['text_dark']).pack(anchor=tk.W)
      
        # 状态显示区域 - 使用背景框
        status_display_frame = ttk.Frame(status_frame, style='Content.TFrame')
        status_display_frame.pack(fill=tk.X, pady=(8, 0))
      
        # 创建状态显示背景
        status_bg_frame = tk.Frame(status_display_frame, 
                                  bg='#f8fafc', 
                                  relief='solid', 
                                  borderwidth=1,
                                  bd=1)
        status_bg_frame.pack(fill=tk.X, padx=2, pady=2)
      
        self.injection_status_var = tk.StringVar(value="🔄 检测中...")
        self.injection_status_label = tk.Label(status_bg_frame, 
                                              textvariable=self.injection_status_var,
                                              font=('微软雅黑', 11, 'bold'),
                                              bg='#f8fafc',
                                              fg=self.colors['text_secondary'],
                                              pady=8,
                                              padx=12)
        self.injection_status_label.pack(anchor=tk.W)
      
        # 操作按钮
        button_frame = ttk.Frame(self.injection_panel, style='Content.TFrame')
        button_frame.pack(fill=tk.X, pady=(0, 20))
      
        # 所有按钮在同一行
        ttk.Button(button_frame, text="💉 注入cheat.lua", 
                  command=self.inject_cheat_file, 
                  style='Success.TButton',
                  width=18).pack(side=tk.LEFT, padx=(0, 12))
      
        ttk.Button(button_frame, text="🗑️ 卸载注入", 
                  command=self.remove_injection,
                  style='Danger.TButton',
                  width=15).pack(side=tk.LEFT, padx=(0, 12))
      
        ttk.Button(button_frame, text="🔄 刷新状态", 
                  command=self.refresh_injection_status,
                  style='Accent.TButton',
                  width=15).pack(side=tk.LEFT)
      
        # 注入说明
        info_frame = ttk.LabelFrame(self.injection_panel, text="📖 使用说明", 
                                  padding=15, style='Modern.TLabelframe')
        info_frame.pack(fill=tk.BOTH, expand=True, pady=(20, 0))
      
        info_text = """1. 首先设置或自动检测游戏安装目录
2. 点击"注入cheat.lua"将作弊文件复制到游戏目录
3. 启动游戏后使用快捷键菜单中的功能
4. 游戏更新后可能需要重新注入
5. 不需要时可以点击"卸载注入"移除文件

注意:确保游戏目录路径正确,通常包含RelicCardinal.exe文件"""
      
        info_label = ttk.Label(info_frame, text=info_text, justify=tk.LEFT, 
                              wraplength=500, font=('微软雅黑', 9),
                              background=self.colors['content_bg'],
                              foreground=self.colors['text_secondary'])
        info_label.pack(anchor=tk.W)

    def create_hotkey_panel(self):
        """创建快捷键管理面板"""
        self.hotkey_panel = ttk.Frame(self.content_frame, style='Content.TFrame')
      
        # 控制台设置区域
        console_frame = ttk.LabelFrame(self.hotkey_panel, text="🎮 控制台设置", 
                                     padding=15, style='Modern.TLabelframe')
        console_frame.pack(fill=tk.X, pady=(0, 15))
      
        # 间隔时间设置
        interval_frame = ttk.Frame(console_frame, style='Content.TFrame')
        interval_frame.pack(fill=tk.X, pady=(0, 10))
      
        ttk.Label(interval_frame, text="执行间隔(秒):", 
                 font=('微软雅黑', 10),
                 background=self.colors['content_bg'],
                 foreground=self.colors['text_dark']).pack(side=tk.LEFT)
      
        self.interval_var = tk.DoubleVar(value=self.config.get('interval', 0.1))
        interval_spin = ttk.Spinbox(interval_frame, from_=0.05, to=5.0, increment=0.05, 
                                   textvariable=self.interval_var, width=10,
                                   font=('微软雅黑', 9))
        interval_spin.pack(side=tk.LEFT, padx=(10, 0))
      
        # 快捷键管理区域
        hotkey_frame = ttk.LabelFrame(self.hotkey_panel, text="⌨️ 快捷键管理", 
                                    padding=15, style='Modern.TLabelframe')
        hotkey_frame.pack(fill=tk.BOTH, expand=True)
      
        # 工具栏
        toolbar = ttk.Frame(hotkey_frame, style='Content.TFrame')
        toolbar.pack(fill=tk.X, pady=(0, 10))
      
        # 居中显示的按钮容器
        center_frame = ttk.Frame(toolbar, style='Content.TFrame')
        center_frame.pack(expand=True)
      
        ttk.Button(center_frame, text="➕ 添加", command=self.add_hotkey,
                  style='Accent.TButton').pack(side=tk.LEFT, padx=5)
        ttk.Button(center_frame, text="✏️ 编辑", command=self.edit_hotkey,
                  style='Accent.TButton').pack(side=tk.LEFT, padx=5)
        ttk.Button(center_frame, text="🗑️ 删除", command=self.delete_hotkey,
                  style='Danger.TButton').pack(side=tk.LEFT, padx=5)
        ttk.Button(center_frame, text="💾 保存配置", command=self.save_config,
                  style='Success.TButton').pack(side=tk.LEFT, padx=5)
      
        # 快捷键列表
        list_frame = ttk.Frame(hotkey_frame, style='Content.TFrame')
        list_frame.pack(fill=tk.BOTH, expand=True)
      
        columns = ('name', 'hotkey', 'enabled')
        self.hotkey_tree = ttk.Treeview(list_frame, columns=columns, show='headings', 
                                       height=12, style='Modern.Treeview')
      
        # 设置列标题和属性
        self.hotkey_tree.heading('name', text='名称')
        self.hotkey_tree.heading('hotkey', text='快捷键')
        self.hotkey_tree.heading('enabled', text='启用')
      
        self.hotkey_tree.column('name', width=200, anchor=tk.CENTER)
        self.hotkey_tree.column('hotkey', width=150, anchor=tk.CENTER)
        self.hotkey_tree.column('enabled', width=100, anchor=tk.CENTER)
      
        # 滚动条
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.hotkey_tree.yview)
        self.hotkey_tree.configure(yscrollcommand=scrollbar.set)
      
        self.hotkey_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
      
        # 加载快捷键
        self.load_hotkeys()

    def create_global_hotkey_panel(self):
        """创建全局热键面板"""
        self.global_hotkey_panel = ttk.LabelFrame(self.content_frame, text="🔥 全局热键控制", 
                                                padding=25, style='Modern.TLabelframe')
      
        # F1热键状态
        status_frame = ttk.Frame(self.global_hotkey_panel, style='Content.TFrame')
        status_frame.pack(fill=tk.X, pady=(0, 25))
      
        ttk.Label(status_frame, text="F1热键状态:", 
                 font=('微软雅黑', 13, 'bold'),
                 background=self.colors['content_bg'],
                 foreground=self.colors['text_dark']).pack(anchor=tk.W)
      
        self.f1_status_var = tk.StringVar(value="🟢 已启用")
        self.f1_status_label = ttk.Label(status_frame, textvariable=self.f1_status_var, 
                                        font=('微软雅黑', 11),
                                        background=self.colors['content_bg'],
                                        foreground=self.colors['success'])
        self.f1_status_label.pack(anchor=tk.W, pady=(8, 0))
      
        # 使用说明
        info_frame = ttk.LabelFrame(self.global_hotkey_panel, text="📖 使用说明", 
                                  padding=20, style='Modern.TLabelframe')
        info_frame.pack(fill=tk.BOTH, expand=True, pady=(25, 0))
      
        info_text = """🔥 全局热键功能说明:

📌 F1 - 执行完整作弊序列
   • 自动打开游戏控制台 (CTRL+SHIFT+`)
   • 输入作弊命令 dofile("cheat/cheat.lua")
   • 关闭控制台
   • 执行额外快捷键(如果有设置)

⚠️ 重要提醒:
   • 确保游戏窗口处于激活状态
   • 在游戏开始后按F1执行
   • 确保已正确注入cheat.lua文件
   • 可在日志菜单查看执行详情

🎯 执行顺序:
   1. 检测注入状态
   2. 打开控制台
   3. 输入作弊命令
   4. 等待命令执行
   5. 关闭控制台
   6. 执行自定义快捷键"""
      
        info_label = ttk.Label(info_frame, text=info_text, justify=tk.LEFT, 
                              wraplength=600, font=('微软雅黑', 9),
                              background=self.colors['content_bg'],
                              foreground=self.colors['text_secondary'])
        info_label.pack(anchor=tk.W)

    def create_log_panel(self):
        """创建日志面板"""
        self.log_panel = ttk.LabelFrame(self.content_frame, text="📝 执行日志", 
                                      padding=15, style='Modern.TLabelframe')
      
        # 工具栏
        toolbar = ttk.Frame(self.log_panel, style='Content.TFrame')
        toolbar.pack(fill=tk.X, pady=(0, 10))
      
        ttk.Button(toolbar, text="🗑️ 清空日志", command=self.clear_log,
                  style='Danger.TButton').pack(side=tk.LEFT, padx=(0, 10))
        ttk.Button(toolbar, text="💾 保存日志", command=self.save_log,
                  style='Accent.TButton').pack(side=tk.LEFT)
      
        # 日志显示区域
        log_frame = ttk.Frame(self.log_panel, style='Content.TFrame')
        log_frame.pack(fill=tk.BOTH, expand=True)
      
        # 使用协调的蓝色系日志配色
        self.log_text = tk.Text(log_frame, wrap=tk.WORD, font=('Consolas', 9), 
                               bg='#1e3a8a', fg='#dbeafe', insertbackground='#60a5fa',
                               selectbackground='#3b82f6', selectforeground='white',
                               relief='flat', borderwidth=0)
      
        log_scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=log_scrollbar.set)
      
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
      
        # 初始日志
        self.add_log("🎮 帝国时代4修改器启动")
        self.add_log("📋 F1热键已激活,可随时使用")

    def show_injection_panel(self):
        """显示注入设置面板"""
        self.hide_all_panels()
        self.injection_panel.pack(fill=tk.BOTH, expand=True)
        self.refresh_injection_status()

    def show_hotkey_panel(self):
        """显示快捷键设置面板"""
        self.hide_all_panels()
        self.hotkey_panel.pack(fill=tk.BOTH, expand=True)

    def show_global_hotkey_panel(self):
        """显示全局热键面板"""
        self.hide_all_panels()
        self.global_hotkey_panel.pack(fill=tk.BOTH, expand=True)

    def show_log_panel(self):
        """显示日志面板"""
        self.hide_all_panels()
        self.log_panel.pack(fill=tk.BOTH, expand=True)

    def hide_all_panels(self):
        """隐藏所有面板"""
        for panel in [self.injection_panel, self.hotkey_panel, 
                     self.global_hotkey_panel, self.log_panel]:
            panel.pack_forget()

    def load_config(self):
        """加载配置文件"""
        default_config = {
            'game_path': '',
            'interval': 0.1,
            'hotkeys': [
                {'name': '打开控制台', 'hotkey': 'ctrl+shift+`', 'enabled': True},
                {'name': '启用/停用战争迷雾', 'hotkey': 'ctrl+f', 'enabled': True},
                {'name': '所有资源加99999', 'hotkey': 'ctrl+m', 'enabled': True},
                {'name': '启用/停用人口超控', 'hotkey': 'ctrl+p', 'enabled': True},
                {'name': '启用/停用快速采集', 'hotkey': 'ctrl+g', 'enabled': True},
                {'name': '启用/停用快速建造', 'hotkey': 'ctrl+w', 'enabled': True}
            ]
        }
      
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    # 合并默认配置
                    for key, value in default_config.items():
                        if key not in config:
                            config[key] = value
                    return config
        except Exception as e:
            print(f"加载配置失败: {e}")
      
        return default_config

    def save_config(self, show_message=True):
        """保存配置文件"""
        try:
            self.config['game_path'] = self.game_path_var.get()
            self.config['interval'] = self.interval_var.get()
          
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, ensure_ascii=False, indent=2)
          
            self.add_log("💾 配置已保存")
            if show_message:
                messagebox.showinfo("成功", "配置已保存")
        except Exception as e:
            self.add_log(f"❌ 保存配置失败: {e}")
            if show_message:
                messagebox.showerror("错误", f"保存配置失败: {e}")

    def load_hotkeys(self):
        """加载快捷键到表格"""
        # 清空现有项目
        for item in self.hotkey_tree.get_children():
            self.hotkey_tree.delete(item)
      
        # 添加快捷键
        for hotkey in self.config.get('hotkeys', []):
            enabled_text = "✅" if hotkey.get('enabled', True) else "❌"
            self.hotkey_tree.insert('', tk.END, values=(
                hotkey.get('name', ''),
                hotkey.get('hotkey', ''),
                enabled_text
            ))

    def add_hotkey(self):
        """添加新快捷键"""
        dialog = HotkeyDialog(self.root, "添加快捷键")
        if dialog.result:
            self.config['hotkeys'].append(dialog.result)
            self.load_hotkeys()
            self.add_log(f"➕ 添加快捷键: {dialog.result['name']}")

    def edit_hotkey(self):
        """编辑选中的快捷键"""
        selection = self.hotkey_tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请先选择要编辑的快捷键")
            return
      
        index = self.hotkey_tree.index(selection[0])
        hotkey_data = self.config['hotkeys'][index]
      
        dialog = HotkeyDialog(self.root, "编辑快捷键", hotkey_data)
        if dialog.result:
            self.config['hotkeys'][index] = dialog.result
            self.load_hotkeys()
            self.add_log(f"✏️ 编辑快捷键: {dialog.result['name']}")

    def delete_hotkey(self):
        """删除选中的快捷键"""
        selection = self.hotkey_tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请先选择要删除的快捷键")
            return
      
        index = self.hotkey_tree.index(selection[0])
        hotkey_name = self.config['hotkeys'][index]['name']
      
        if messagebox.askyesno("确认", f"确定要删除快捷键 '{hotkey_name}' 吗?"):
            del self.config['hotkeys'][index]
            self.load_hotkeys()
            self.add_log(f"🗑️ 删除快捷键: {hotkey_name}")

    def browse_game_directory(self):
        """浏览选择游戏目录"""
        directory = filedialog.askdirectory(title="选择帝国时代4游戏目录")
        if directory:
            self.game_path_var.set(directory)
            self.add_log(f"📁 设置游戏目录: {directory}")
          
            # 自动保存配置并刷新注入状态
            self.save_config(show_message=False)
            self.refresh_injection_status()

    def auto_detect_game_directory(self):
        """自动检测游戏目录"""
        self.add_log("🔍 正在自动检测游戏目录...")
      
        # 常见Steam安装路径
        steam_paths = [
            "C:/Program Files (x86)/Steam/steamapps/common/Age of Empires IV",
            "D:/Steam/steamapps/common/Age of Empires IV",
            "E:/Steam/steamapps/common/Age of Empires IV",
            "F:/Steam/steamapps/common/Age of Empires IV",
            # Xbox Game Pass路径
            "C:/Program Files/WindowsApps/Microsoft.Phoenix_*",
            # Epic Games路径
            "C:/Program Files/Epic Games/AgeOfEmpiresIV"
        ]
      
        for path in steam_paths:
            if "*" in path:
                # 处理通配符路径
                import glob
                matches = glob.glob(path)
                for match in matches:
                    if os.path.exists(os.path.join(match, "RelicCardinal.exe")):
                        self.game_path_var.set(match)
                        self.add_log(f"✅ 自动检测成功: {match}")
                      
                        # 自动保存配置并刷新注入状态
                        self.save_config(show_message=False)
                        self.refresh_injection_status()
                        return
            else:
                if os.path.exists(os.path.join(path, "RelicCardinal.exe")):
                    self.game_path_var.set(path)
                    self.add_log(f"✅ 自动检测成功: {path}")
                  
                    # 自动保存配置并刷新注入状态
                    self.save_config(show_message=False)
                    self.refresh_injection_status()
                    return
      
        self.add_log("❌ 未找到游戏目录,请手动选择")
        messagebox.showwarning("未找到", "未能自动检测到游戏目录,请手动选择")

    def refresh_injection_status(self):
        """刷新注入状态"""
        game_path = self.game_path_var.get()
        if not game_path:
            self.injection_status_var.set("🚫 未设置游戏目录")
            # 更新状态标签颜色为警告色
            self.injection_status_label.configure(foreground='#f59e0b')
            return
      
        cheat_dir = os.path.join(game_path, "cheat")
        cheat_file = os.path.join(cheat_dir, "cheat.lua")
      
        if os.path.exists(cheat_file):
            self.injection_status_var.set("✅ 已注入 - 注入成功")
            # 更新状态标签颜色为成功色
            self.injection_status_label.configure(foreground='#10b981')
        else:
            self.injection_status_var.set("❌ 未注入 - 需要先注入cheat.lua文件")
            # 更新状态标签颜色为错误色
            self.injection_status_label.configure(foreground='#ef4444')

    def inject_cheat_file(self):
        """注入cheat.lua文件"""
        game_path = self.game_path_var.get()
        if not game_path:
            messagebox.showerror("错误", "请先设置游戏目录")
            return
      
        if not os.path.exists("cheat.lua"):
            messagebox.showerror("错误", "找不到cheat.lua文件")
            return
      
        try:
            cheat_dir = os.path.join(game_path, "cheat")
            os.makedirs(cheat_dir, exist_ok=True)
          
            cheat_file = os.path.join(cheat_dir, "cheat.lua")
            shutil.copy2("cheat.lua", cheat_file)
          
            self.add_log(f"✅ 注入成功: {cheat_file}")
            self.refresh_injection_status()
          
            # 自动保存配置以记录注入状态
            self.save_config(show_message=False)
          
            messagebox.showinfo("成功", "cheat.lua文件注入成功!")
          
        except Exception as e:
            self.add_log(f"❌ 注入失败: {e}")
            messagebox.showerror("错误", f"注入失败: {e}")

    def remove_injection(self):
        """移除注入文件"""
        game_path = self.game_path_var.get()
        if not game_path:
            messagebox.showerror("错误", "请先设置游戏目录")
            return
      
        cheat_dir = os.path.join(game_path, "cheat")
        cheat_file = os.path.join(cheat_dir, "cheat.lua")
      
        if not os.path.exists(cheat_file):
            messagebox.showinfo("提示", "没有找到已注入的文件")
            return
      
        if messagebox.askyesno("确认", "确定要卸载注入的cheat.lua文件吗?"):
            try:
                os.remove(cheat_file)
                # 如果目录为空,也删除目录
                if os.path.exists(cheat_dir) and not os.listdir(cheat_dir):
                    os.rmdir(cheat_dir)
              
                self.add_log("❌ 注入已卸载")
                self.refresh_injection_status()
              
                # 自动保存配置以记录卸载状态
                self.save_config(show_message=False)
              
                messagebox.showinfo("成功", "注入文件已成功卸载")
              
            except Exception as e:
                self.add_log(f"❌ 卸载失败: {e}")
                messagebox.showerror("错误", f"卸载失败: {e}")

    def setup_hotkeys(self):
        """设置全局热键"""
        try:
            # 设置F1热键
            keyboard.add_hotkey('f1', self.on_f1_pressed)
            self.add_log("🔥 F1全局热键已激活")
        except Exception as e:
            self.add_log(f"❌ 热键设置失败: {e}")

    def on_f1_pressed(self):
        """F1热键处理"""
        if not self.is_running:
            self.add_log("🎯 F1按下,开始执行作弊序列...")
            self.is_running = True
            threading.Thread(target=self.execute_cheat_sequence, daemon=True).start()
        else:
            self.add_log("⚠️ 序列正在执行中,请稍等...")

    def execute_cheat_sequence(self):
        """执行完整作弊序列"""
        try:
            # 检查注入状态
            game_path = self.game_path_var.get()
            if game_path:
                cheat_file = os.path.join(game_path, "cheat", "cheat.lua")
                if not os.path.exists(cheat_file):
                    self.add_log("⚠️ 警告:未检测到cheat.lua注入文件")
          
            interval = self.interval_var.get()
          
            # 1. 打开控制台
            self.add_log("📝 步骤1: 打开控制台...")
            keyboard.send('ctrl+shift+`')
            time.sleep(interval)
          
            if not self.is_running:
                return
          
            # 2. 输入作弊命令
            self.add_log("📝 步骤2: 输入作弊命令...")
            pyautogui.hotkey('ctrl', 'a')  # 清空
            time.sleep(0.1)
            pyautogui.write('dofile("cheat/cheat.lua")')
            time.sleep(interval)
            pyautogui.press('enter')
            time.sleep(interval * 20)  # 等待命令执行
          
            if not self.is_running:
                return
          
            # 3. 关闭控制台
            self.add_log("📝 步骤3: 关闭控制台...")
            keyboard.send('ctrl+shift+`')
            time.sleep(interval)
          
            # 4. 执行额外快捷键
            enabled_hotkeys = [hk for hk in self.config.get('hotkeys', []) 
                             if hk.get('enabled', True) and hk.get('name') != '打开控制台']
          
            if enabled_hotkeys:
                self.add_log(f"📝 步骤4: 执行{len(enabled_hotkeys)}个额外快捷键...")
                for hotkey in enabled_hotkeys:
                    if not self.is_running:
                        return
                  
                    name = hotkey.get('name', '')
                    key = hotkey.get('hotkey', '')
                    self.add_log(f"   • 执行: {name} ({key})")
                    keyboard.send(key)
                    time.sleep(interval)
          
            self.add_log("🎉 作弊序列执行完成!")
          
        except Exception as e:
            self.add_log(f"❌ 执行错误: {e}")
        finally:
            self.is_running = False

    def check_version(self):
        """检查版本更新"""
        self.add_log("🔍 正在检查版本更新...")
      
        # 更新状态为检查中
        self.root.after(0, lambda: self.version_status_var.set("检查中..."))
      
        try:
            self.add_log(f"📡 请求URL: {self.version_url}")
            response = requests.get(self.version_url, timeout=10)
            self.add_log(f"📡 响应状态: {response.status_code}")
          
            if response.status_code == 200:
                content = response.text.strip()
                self.add_log(f"📄 服务器响应: {content}")
              
                # 解析版本号 - 支持多种格式
                import re
                # 尝试多种版本号格式
                patterns = [
                    r'"(\d+\.\d+)"',  # "1.1"
                    r"'(\d+\.\d+)'",  # '1.1'
                    r'(\d+\.\d+)',    # 1.1 (纯数字)
                    r'version\s*=\s*["\']?(\d+\.\d+)["\']?',  # version = "1.1"
                    r'var\s+\w+\s*=\s*["\']?(\d+\.\d+)["\']?'  # var version = "1.1"
                ]
              
                latest_version = None
                for pattern in patterns:
                    version_match = re.search(pattern, content)
                    if version_match:
                        latest_version = version_match.group(1)
                        self.add_log(f"🔍 使用模式 '{pattern}' 解析成功")
                        break
              
                if latest_version:
                    self.add_log(f"🔢 服务器版本: v{latest_version}")
                    self.add_log(f"🔢 当前版本: v{self.version}")
                  
                    # 版本比较 - 转换为浮点数比较
                    try:
                        server_ver = float(latest_version)
                        current_ver = float(self.version)
                      
                        if server_ver > current_ver:
                            self.add_log("🆕 发现新版本!")
                            self.root.after(0, lambda: self.version_status_var.set(f"发现新版本 v{latest_version}"))
                            self.show_update_dialog(latest_version)
                        else:
                            self.add_log("✅ 当前已是最新版本")
                            self.root.after(0, lambda: self.version_status_var.set("已是最新版本"))
                    except ValueError:
                        self.add_log("❌ 版本号格式错误,无法比较")
                        self.root.after(0, lambda: self.version_status_var.set("版本格式错误"))
                else:
                    self.add_log("❌ 无法解析服务器版本号")
                    self.add_log(f"❌ 尝试的模式: {patterns}")
                    self.root.after(0, lambda: self.version_status_var.set("解析失败"))
            else:
                self.add_log(f"❌ 服务器响应错误: {response.status_code}")
                self.root.after(0, lambda: self.version_status_var.set("服务器错误"))
              
        except requests.exceptions.Timeout:
            self.add_log("⏰ 版本检查超时(10秒)")
            self.root.after(0, lambda: self.version_status_var.set("连接超时"))
        except requests.exceptions.ConnectionError:
            self.add_log("🌐 网络连接失败,无法检查版本")
            self.root.after(0, lambda: self.version_status_var.set("网络连接失败"))
        except requests.exceptions.RequestException as e:
            self.add_log(f"📡 网络请求失败: {e}")
            self.root.after(0, lambda: self.version_status_var.set("网络请求失败"))
        except Exception as e:
            self.add_log(f"❌ 版本检查异常: {e}")
            self.root.after(0, lambda: self.version_status_var.set("检查异常"))
      
        self.add_log("🔍 版本检查完成")

    def show_update_dialog(self, latest_version):
        """显示更新对话框"""
        def open_download():
            webbrowser.open(self.download_url)
            self.root.quit()
      
        result = messagebox.askyesno(
            "发现新版本",
            f"发现新版本 v{latest_version}(当前版本 v{self.version})\n\n是否前往下载页面?",
            icon='question'
        )
      
        if result:
            open_download()

    def add_log(self, message):
        """添加日志"""
        timestamp = time.strftime("%H:%M:%S")
        log_entry = f"[{timestamp}] {message}\n"
      
        # 在UI线程中更新
        def update_log():
            self.log_text.insert(tk.END, log_entry)
            self.log_text.see(tk.END)
      
        if threading.current_thread() == threading.main_thread():
            update_log()
        else:
            self.root.after(0, update_log)

    def clear_log(self):
        """清空日志"""
        self.log_text.delete(1.0, tk.END)
        self.add_log("🗑️ 日志已清空")

    def save_log(self):
        """保存日志"""
        try:
            log_content = self.log_text.get(1.0, tk.END)
            filename = filedialog.asksaveasfilename(
                title="保存日志",
                defaultextension=".txt",
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
            )
            if filename:
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write(log_content)
                self.add_log(f"💾 日志已保存到: {filename}")
        except Exception as e:
            messagebox.showerror("错误", f"保存日志失败: {e}")

    def run(self):
        """运行程序"""
        try:
            self.root.mainloop()
        except KeyboardInterrupt:
            pass
        finally:
            self.cleanup()

    def cleanup(self):
        """清理资源"""
        self.is_running = False
        try:
            keyboard.unhook_all_hotkeys()
        except:
            pass

    def on_version_click(self, event):
        """处理版本标签点击事件"""
        self.version_status_var.set("检查中...")
        self.add_log("🖱️ 手动触发版本检查")
        threading.Thread(target=self.check_version, daemon=True).start()

    def launch_game_directly(self):
        """直接启动游戏"""
        game_path = self.game_path_var.get()
        if not game_path:
            messagebox.showerror("错误", "请先在注入菜单中设置游戏目录")
            self.add_log("❌ 启动失败:未设置游戏目录")
            # 自动跳转到注入菜单
            self.show_injection_panel()
            return
      
        # 检查游戏文件
        game_exe = os.path.join(game_path, "RelicCardinal.exe")
        if not os.path.exists(game_exe):
            # 尝试查找AoE4.exe作为备选
            game_exe_alt = os.path.join(game_path, "AoE4.exe")
            if os.path.exists(game_exe_alt):
                game_exe = game_exe_alt
            else:
                # 如果找不到exe文件,直接尝试Steam启动
                self.add_log("❌ 找不到游戏exe文件,尝试Steam协议启动")
                self.launch_with_steam()
                return
      
        # 询问用户启动方式
        choice = messagebox.askyesnocancel("选择启动方式", 
                                          "选择游戏启动方式:\n\n"
                                          "• 是(Y) - 直接启动exe文件\n"
                                          "• 否(N) - 使用Steam协议启动 (推荐)\n"
                                          "• 取消 - 不启动游戏")
      
        if choice is None:  # 取消
            self.add_log("🚫 用户取消启动游戏")
            return
        elif choice:  # 是 - 直接启动
            try:
                self.add_log("🎮 正在直接启动游戏exe...")
                import subprocess
                subprocess.Popen(game_exe, cwd=game_path)
                self.add_log(f"✅ 游戏启动成功:{os.path.basename(game_exe)}")
                messagebox.showinfo("成功", "游戏启动成功!\n请等待游戏加载完成后使用F1热键")
            except Exception as e:
                self.add_log(f"❌ 直接启动失败: {e}")
                # 直接启动失败,询问是否尝试Steam启动
                if messagebox.askyesno("启动失败", f"直接启动失败: {e}\n\n是否尝试使用Steam协议启动?"):
                    self.launch_with_steam()
        else:  # 否 - Steam启动
            self.launch_with_steam()

    def launch_with_steam(self):
        """使用Steam协议启动游戏"""
        try:
            self.add_log("🎮 正在使用Steam命令行启动游戏...")
          
            # 完整的启动参数 - 包含默认参数和开发者参数
            launch_options = [
                "-dev",  
                "-nomovies"
            ]
          
            # 尝试找到Steam安装路径
            steam_paths = [
                "C:/Program Files (x86)/Steam/steam.exe",
                "C:/Program Files/Steam/steam.exe",
                "D:/Steam/steam.exe",
                "E:/Steam/steam.exe",
                "F:/Steam/steam.exe"
            ]
          
            steam_exe = None
            for path in steam_paths:
                if os.path.exists(path):
                    steam_exe = path
                    break
          
            if steam_exe:
                # 使用Steam命令行启动游戏
                self.add_log(f"🔧 找到Steam: {steam_exe}")
                self.add_log(f"🔧 启动参数: {' '.join(launch_options)}")
              
                import subprocess
              
                # 构建Steam命令 - 使用-applaunch参数
                steam_cmd = [steam_exe, "-applaunch", "1466860"] + launch_options
              
                self.add_log(f"🔧 执行命令: {' '.join(steam_cmd)}")
              
                # 启动游戏
                subprocess.Popen(steam_cmd, shell=False)
              
                self.add_log("✅ Steam命令行启动成功")
              
                # 显示成功信息
                messagebox.showinfo("Steam启动", 
                                  "✅ 已通过Steam命令行启动游戏!\n\n"
                                  f"🔧 使用的启动参数:\n{' '.join(launch_options)}\n\n"
                                  "🎮 请等待游戏加载完成后使用F1热键\n\n"
                                  "💡 这些参数将自动生效,无需手动设置")
              
            else:
                # 如果找不到Steam,回退到协议方式
                self.add_log("⚠️ 未找到Steam安装路径,使用协议方式启动")
              
                # Steam协议URL
                steam_url = "steam://rungameid/1466860"
                launch_options_str = " ".join(launch_options)
              
                import webbrowser
              
                # 记录启动参数
                self.add_log(f"🔧 推荐启动参数: {launch_options_str}")
              
                # 使用webbrowser打开Steam协议
                webbrowser.open(steam_url)
              
                self.add_log("✅ Steam协议调用成功")
                self.add_log("💡 提示:启动参数需要在Steam游戏属性中手动设置")
              
                # 显示设置说明
                messagebox.showinfo("Steam启动", 
                                  "✅ 已通过Steam协议启动游戏!\n\n"
                                  "📋 推荐在Steam中手动设置启动参数:\n"
                                  "1. 在Steam库中右键游戏\n"
                                  "2. 选择'属性'\n"
                                  f"3. 在启动选项中添加:\n{launch_options_str}\n\n"
                                  "🎮 请等待游戏加载完成后使用F1热键")
          
        except Exception as e:
            self.add_log(f"❌ Steam启动失败: {e}")
            messagebox.showerror("错误", f"Steam启动失败: {e}\n\n请确保:\n1. 已安装Steam客户端\n2. Steam正在运行\n3. 拥有帝国时代4游戏")

class HotkeyDialog:
    """快捷键编辑对话框"""
    def __init__(self, parent, title, hotkey_data=None):
        self.result = None
      
        # 创建对话框
        self.dialog = tk.Toplevel(parent)
        self.dialog.title(title)
        self.dialog.geometry("400x250")
        self.dialog.resizable(False, False)
        self.dialog.transient(parent)
        self.dialog.grab_set()
      
        # 居中显示
        self.dialog.update_idletasks()
        x = (self.dialog.winfo_screenwidth() // 2) - (400 // 2)
        y = (self.dialog.winfo_screenheight() // 2) - (250 // 2)
        self.dialog.geometry(f"400x250+{x}+{y}")
      
        # 创建界面
        self.create_widgets(hotkey_data)
      
        # 等待对话框关闭
        self.dialog.wait_window()

    def create_widgets(self, hotkey_data):
        """创建对话框界面"""
        main_frame = ttk.Frame(self.dialog, padding=20)
        main_frame.pack(fill=tk.BOTH, expand=True)
      
        # 名称输入
        ttk.Label(main_frame, text="名称:", font=('微软雅黑', 10)).pack(anchor=tk.W)
        self.name_var = tk.StringVar(value=hotkey_data.get('name', '') if hotkey_data else '')
        name_entry = ttk.Entry(main_frame, textvariable=self.name_var, width=40)
        name_entry.pack(fill=tk.X, pady=(5, 15))
      
        # 快捷键输入
        ttk.Label(main_frame, text="快捷键:", font=('微软雅黑', 10)).pack(anchor=tk.W)
        self.hotkey_var = tk.StringVar(value=hotkey_data.get('hotkey', '') if hotkey_data else '')
        hotkey_entry = ttk.Entry(main_frame, textvariable=self.hotkey_var, width=40)
        hotkey_entry.pack(fill=tk.X, pady=(5, 10))
      
        # 提示信息
        tip_label = ttk.Label(main_frame, text="示例: ctrl+f, alt+tab, shift+a", 
                             font=('微软雅黑', 8), foreground='gray')
        tip_label.pack(anchor=tk.W, pady=(0, 15))
      
        # 启用状态
        self.enabled_var = tk.BooleanVar(value=hotkey_data.get('enabled', True) if hotkey_data else True)
        enabled_check = ttk.Checkbutton(main_frame, text="启用此快捷键", variable=self.enabled_var)
        enabled_check.pack(anchor=tk.W, pady=(0, 20))
      
        # 按钮区域
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X)
      
        ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.RIGHT, padx=(10, 0))
        ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.RIGHT)

    def on_ok(self):
        """确定按钮处理"""
        name = self.name_var.get().strip()
        hotkey = self.hotkey_var.get().strip()
      
        if not name:
            messagebox.showerror("错误", "请输入名称")
            return
      
        if not hotkey:
            messagebox.showerror("错误", "请输入快捷键")
            return
      
        self.result = {
            'name': name,
            'hotkey': hotkey,
            'enabled': self.enabled_var.get()
        }
      
        self.dialog.destroy()

    def on_cancel(self):
        """取消按钮处理"""
        self.dialog.destroy()

def main():
    """主函数"""
    try:
        app = ModernAOECheatGUI()
        app.run()
    except Exception as e:
        print(f"程序启动失败: {e}")
        messagebox.showerror("错误", f"程序启动失败: {e}")

if __name__ == "__main__":
    main() 

编译代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
帝国时代4修改器 - 打包为EXE脚本
Build script for AOE4 Cheat Tool
"""

import subprocess
import sys
import os
import shutil

def check_pyinstaller():
    """检查PyInstaller是否已安装"""
    try:
        import PyInstaller
        print("✅ PyInstaller已安装")
        return True
    except ImportError:
        print("❌ PyInstaller未安装")
        return False

def install_pyinstaller():
    """安装PyInstaller"""
    print("🔧 正在安装PyInstaller...")
    try:
        subprocess.run([sys.executable, "-m", "pip", "install", "pyinstaller"], check=True)
        print("✅ PyInstaller安装成功")
        return True
    except subprocess.CalledProcessError:
        print("❌ PyInstaller安装失败")
        return False

def build_exe():
    """打包为EXE文件"""
    print("🚀 开始打包...")
  
    # 准备打包命令
    cmd = [
        "pyinstaller",
        "--onefile",                    # 打包为单文件
        "--windowed",                   # 不显示控制台窗口
        "--name=帝国时代4修改器",          # 设置程序名称
        "--icon=icon.ico",              # 设置图标(如果存在)
        "--add-data=cheat.lua;.",       # 添加cheat.lua文件
        "--hidden-import=PIL",          # 确保PIL模块被包含
        "--hidden-import=PIL._tkinter_finder",
        "--collect-all=tkinter",        # 包含tkinter所有组件
        "aoe_modern_gui.py"             # 主程序文件
    ]
  
    # 如果没有图标文件,移除图标参数
    if not os.path.exists("icon.ico"):
        cmd = [c for c in cmd if not c.startswith("--icon")]
        print("⚠️ 未找到icon.ico文件,将使用默认图标")
  
    try:
        print("📦 执行打包命令...")
        result = subprocess.run(cmd, check=True, capture_output=True, text=True)
        print("✅ 打包成功!")
      
        # 检查输出文件
        exe_path = os.path.join("dist", "帝国时代4修改器.exe")
        if os.path.exists(exe_path):
            file_size = os.path.getsize(exe_path) / 1024 / 1024  # MB
            print(f"📄 生成的EXE文件: {exe_path}")
            print(f"📏 文件大小: {file_size:.1f} MB")
        else:
            print("⚠️ 未找到生成的EXE文件")
      
        return True
      
    except subprocess.CalledProcessError as e:
        print(f"❌ 打包失败: {e}")
        if e.stdout:
            print("标准输出:", e.stdout)
        if e.stderr:
            print("错误输出:", e.stderr)
        return False

def create_release_package():
    """创建发布包"""
    print("📦 创建发布包...")
  
    release_dir = "release"
    if os.path.exists(release_dir):
        shutil.rmtree(release_dir)
  
    os.makedirs(release_dir)
  
    # 复制必要文件
    files_to_copy = [
        ("dist/帝国时代4修改器.exe", "帝国时代4修改器.exe"),
        ("cheat.lua", "cheat.lua"),
        ("README.md", "使用说明.txt")
    ]
  
    for src, dst in files_to_copy:
        if os.path.exists(src):
            dst_path = os.path.join(release_dir, dst)
            if src.endswith('.md'):
                # 如果是markdown文件,复制为txt
                shutil.copy2(src, dst_path)
            else:
                shutil.copy2(src, dst_path)
            print(f"✅ 复制: {src} -> {dst_path}")
        else:
            print(f"⚠️ 文件不存在: {src}")
  
    print(f"🎉 发布包已创建到: {release_dir}/")
    return True

def cleanup():
    """清理临时文件"""
    print("🧹 清理临时文件...")
  
    dirs_to_remove = ["build", "__pycache__"]
    files_to_remove = ["*.spec"]
  
    for dir_name in dirs_to_remove:
        if os.path.exists(dir_name):
            shutil.rmtree(dir_name)
            print(f"✅ 删除目录: {dir_name}")
  
    import glob
    for pattern in files_to_remove:
        for file_path in glob.glob(pattern):
            os.remove(file_path)
            print(f"✅ 删除文件: {file_path}")

def main():
    """主函数"""
    print("🎮 帝国时代4修改器 - EXE打包工具")
    print("=" * 50)
  
    # 检查必要文件
    required_files = ["aoe_modern_gui.py", "cheat.lua"]
    missing_files = [f for f in required_files if not os.path.exists(f)]
  
    if missing_files:
        print(f"❌ 缺少必要文件: {', '.join(missing_files)}")
        return False
  
    # 检查并安装PyInstaller
    if not check_pyinstaller():
        if not install_pyinstaller():
            return False
  
    try:
        # 打包EXE
        if not build_exe():
            return False
      
        # 创建发布包
        create_release_package()
      
        # 清理临时文件
        cleanup()
      
        print("\n🎉 打包完成!")
        print("📁 发布文件位于 release/ 目录")
        print("\n📋 发布包包含:")
        print("  - 帝国时代4修改器.exe (主程序)")
        print("  - cheat.lua (作弊脚本)")
        print("  - 使用说明.txt (使用指南)")
        print("\n✨ 现在可以将release目录的文件分发给用户了!")
      
        return True
      
    except Exception as e:
        print(f"❌ 打包过程中出现错误: {e}")
        return False

if __name__ == "__main__":
    success = main()
    input(f"\n{'成功' if success else '失败'}!按回车键退出...") 

脚本代码


Scar_DoString([[
    g_cheat_sgroup=SGroup_CreateIfNotFound("g_cheat_sgroup")
    g_cheat_egroup=EGroup_CreateIfNotFound("g_cheat_egroup")
    ResourceContainer_CreateCache("g_cheat_cache", 256)
    print("container initialized")
]])

Scar_DoString([[
    function Cheat_ToggleFOW()
        if g_cheat_fow_disabled then
            FOW_PlayerUnRevealAll(Game_GetLocalPlayer())
        else
            FOW_PlayerRevealAll(Game_GetLocalPlayer())
        end
        g_cheat_fow_disabled=not g_cheat_fow_disabled
        print("g_cheat_fow_disabled="..tostring(g_cheat_fow_disabled))
    end
]])

Scar_DoString([[
    g_cheat_bonus_upgrade_list={
        "story_mode_health_bonus",

        "upgrade_reliable_supplies_fre",
        "upgrade_shipworthy_timber_fre",
        "upgrade_surplus_iron_fre",
    }
    print("g_cheat_bonus_upgrade_list initialized")

    function Cheat_ToggleBonus()
        local all_upgraded=true
        for _,upgrade in ipairs(g_cheat_bonus_upgrade_list) do
            if not Player_HasUpgrade(Game_GetLocalPlayer(),BP_GetUpgradeBlueprint(upgrade)) then
                all_upgraded=false
                Player_CompleteUpgrade(Game_GetLocalPlayer(),BP_GetUpgradeBlueprint(upgrade))
                print(upgrade.."=true")
            end
        end
        if all_upgraded then
            for _,upgrade in ipairs(g_cheat_bonus_upgrade_list) do
                Player_RemoveUpgrade(Game_GetLocalPlayer(),BP_GetUpgradeBlueprint(upgrade))
                print(upgrade.."=false")
            end
        end
    end
]])

Scar_DoString([[
    g_cheat_bonus_ability_list={
        "tower_outpost_alert_aura_nrm",
        "tower_outpost_alert_aura_eng",
        "network_of_citadels_eng",
    }
    print("g_cheat_bonus_ability_list initialized")

    function Cheat_AddBonusAbility()
        Misc_GetSelectedEntities(g_cheat_egroup,true)
        for _,ability in ipairs(g_cheat_bonus_ability_list) do
            EGroup_AddAbility(g_cheat_egroup,BP_GetAbilityBlueprint(ability))
            print(ability.."=true")
        end
    end
]])

Scar_DoString([[
    function Cheat_AddResources()
        Player_AddResource(Game_GetLocalPlayer(),RT_Command,99999)
        Player_AddResource(Game_GetLocalPlayer(),RT_Food,99999)
        Player_AddResource(Game_GetLocalPlayer(),RT_Wood,99999)
        Player_AddResource(Game_GetLocalPlayer(),RT_Gold,99999)
        Player_AddResource(Game_GetLocalPlayer(),RT_Stone,99999)
        if RT_Merc_Byz then
            Player_AddResource(Game_GetLocalPlayer(),RT_Merc_Byz,99999)
        end
        local experience = Player_GetStateModelFloat(Game_GetLocalPlayer(), "jeanne_d_arc_total_experience") + 99999
        Player_SetStateModelFloat(Game_GetLocalPlayer(), "jeanne_d_arc_total_experience", experience)
        print("resources+=99999")
    end
]])

Scar_DoString([[
    function Cheat_TogglePopCap()
        if Player_GetCurrentPopulationCap(Game_GetLocalPlayer(),CT_Personnel)==99999 then
            Player_ClearPopCapOverride(Game_GetLocalPlayer())
        else
            Player_SetPopCapOverride(Game_GetLocalPlayer(),99999)
        end
        print("pop_cap="..Player_GetCurrentPopulationCap(Game_GetLocalPlayer(),CT_Personnel))
    end
]])

Scar_DoString([[
    g_cheat_spawn_villager_list={
        "unit_villager_1_eng",
        "unit_villager_1_nrm",
        "unit_villager_1_chi",
        "unit_villager_1_fre",
        "unit_villager_1_hre",
        "unit_villager_1_rus",
        "unit_villager_1_abb",
        "unit_villager_1_sul",
        "unit_villager_1_mon",
    }
    print("g_cheat_spawn_villager_list initialized")

    function Cheat_SpawnVillager()
        local pos=Camera_GetPivot()
        if Misc_GetScreenCenterPosition then
            pos=Misc_GetScreenCenterPosition()
        end
        for _,villager in ipairs(g_cheat_spawn_villager_list) do
            Squad_CreateAndSpawnTowardCheat(BP_GetSquadBlueprint(villager), Game_GetLocalPlayer(), 0, pos, pos)
            print(villager.." spawned")
        end
    end
]])

Scar_DoString([[
    g_cheat_spawn_villager_xp3_list={
        "unit_villager_1_mal",
        "unit_villager_1_ott",
        "unit_villager_1_jpn",
        "unit_villager_1_byz",
        "unit_villager_1_abb_ha_01",
        "unit_villager_1_chi_ha_01",
        "unit_villager_1_fre_ha_01",
        "unit_villager_1_hre_ha_01",
    }
    print("g_cheat_spawn_villager_xp3_list initialized")

    function Cheat_SpawnVillagerXP3()
        local pos=Camera_GetPivot()
        if Misc_GetScreenCenterPosition then
            pos=Misc_GetScreenCenterPosition()
        end
        for _,villager in ipairs(g_cheat_spawn_villager_xp3_list) do
            Squad_CreateAndSpawnTowardCheat(BP_GetSquadBlueprint(villager), Game_GetLocalPlayer(), 0, pos, pos)
            print(villager.." spawned")
        end
    end
]])

Scar_DoString([[
    if not g_cheat_fast_gather_modifier_list then
        g_cheat_fast_gather_modifier_list={
            Modifier_Create(MAT_EntityTypeAndDescendants, "resource_entity_harvest_rate_food", MUT_Multiplication, false, 100, "unit_villager_1"),
            Modifier_Create(MAT_EntityTypeAndDescendants, "resource_entity_harvest_rate_wood", MUT_Multiplication, false, 100, "unit_villager_1"),
            Modifier_Create(MAT_EntityTypeAndDescendants, "resource_entity_harvest_rate_gold", MUT_Multiplication, false, 100, "unit_villager_1"),
            Modifier_Create(MAT_EntityTypeAndDescendants, "resource_entity_harvest_rate_stone", MUT_Multiplication, false, 100, "unit_villager_1"),
            applied_modid={}
        }
    end
    print("g_cheat_fast_gather_modifier_list initialized")

    function Cheat_ToggleFastGather()
        if #g_cheat_fast_gather_modifier_list.applied_modid==0 then
            for i,modifier in ipairs(g_cheat_fast_gather_modifier_list) do
                g_cheat_fast_gather_modifier_list.applied_modid[i]=Modifier_ApplyToPlayer(modifier, Game_GetLocalPlayer(), 0)
            end
        else
            Modifier_Remove(g_cheat_fast_gather_modifier_list.applied_modid)
        end
        print("g_cheat_fast_gather="..tostring(#g_cheat_fast_gather_modifier_list.applied_modid~=0))
    end
]])

Scar_DoString([[
    if not g_cheat_fast_production_modifier_list then
        g_cheat_fast_production_modifier_list={
            Modifier_Create(MAT_Player, "production_speed_player_modifier", MUT_Multiplication, false, 100, nil),
            applied_modid={}
        }
    end
    print("g_cheat_fast_production_modifier_list initialized")

    function Cheat_ToggleFastProduction()
        if #g_cheat_fast_production_modifier_list.applied_modid==0 then
            for i,modifier in ipairs(g_cheat_fast_production_modifier_list) do
                g_cheat_fast_production_modifier_list.applied_modid[i]=Modifier_ApplyToPlayer(modifier, Game_GetLocalPlayer(), 0)
            end
        else
            Modifier_Remove(g_cheat_fast_production_modifier_list.applied_modid)
        end
        print("g_cheat_fast_production="..tostring(#g_cheat_fast_production_modifier_list.applied_modid~=0))
    end
]])

Scar_DoString([[
    if not g_cheat_fast_construction_and_repair_modifier_list then
        g_cheat_fast_construction_and_repair_modifier_list={
            applied_modid={}
        }
        for _,ebp in ipairs(BP_GetEntityBlueprintsWithType("human")) do
            table.insert(g_cheat_fast_construction_and_repair_modifier_list,Modifier_Create(MAT_EntityType, "construction_rate", MUT_Multiplication, false, 100, ebp))
            table.insert(g_cheat_fast_construction_and_repair_modifier_list,Modifier_Create(MAT_EntityType, "repair_rate_modifier", MUT_Multiplication, false, 100, ebp))
        end
    end
    print("g_cheat_fast_construction_and_repair_modifier_list initialized")

    function Cheat_ToggleFastConstructionAndRepair()
        if #g_cheat_fast_construction_and_repair_modifier_list.applied_modid==0 then
            for i,modifier in ipairs(g_cheat_fast_construction_and_repair_modifier_list) do
                g_cheat_fast_construction_and_repair_modifier_list.applied_modid[i]=Modifier_ApplyToPlayer(modifier, Game_GetLocalPlayer(), 0)
            end
        else
            Modifier_Remove(g_cheat_fast_construction_and_repair_modifier_list.applied_modid)
        end
        print("g_cheat_fast_construction_and_repair="..tostring(#g_cheat_fast_construction_and_repair_modifier_list.applied_modid~=0))
    end
]])

Scar_DoString([[
    if not g_cheat_fast_cd_modifier_list then
        g_cheat_fast_cd_modifier_list={
            applied_modid={}
        }
        for i=0,BP_GetPropertyBagGroupCount(PBG_Ability)-1 do
            g_cheat_fast_cd_modifier_list[i+1]=Modifier_Create(MAT_Ability, "ability_recharge_time_modifier", MUT_Multiplication, false, 0.01, BP_GetPropertyBagGroupPathName(PBG_Ability,i))
        end
    end
    print("g_cheat_fast_cd_modifier_list initialized")

    if not g_cheat_jeanne_d_arc_charges_cd_list then
        g_cheat_jeanne_d_arc_charges_cd_list={
            Player_GetStateModelFloat(Game_GetLocalPlayer(), "jeanne_d_arc_wrath_charges_cooldown"),
            Player_GetStateModelFloat(Game_GetLocalPlayer(), "jeanne_d_arc_consecrate_charges_cooldown"),
        }
    end
    print("g_cheat_jeanne_d_arc_charges_cd_list initialized")

    function Cheat_ToggleFastCD()
        if #g_cheat_fast_cd_modifier_list.applied_modid==0 then
            for i,modifier in ipairs(g_cheat_fast_cd_modifier_list) do
                g_cheat_fast_cd_modifier_list.applied_modid[i]=Modifier_ApplyToPlayer(modifier, Game_GetLocalPlayer(), 0)
            end
            Player_SetStateModelFloat(Game_GetLocalPlayer(), "jeanne_d_arc_wrath_charges_cooldown", g_cheat_jeanne_d_arc_charges_cd_list[1]/100)
            Player_SetStateModelFloat(Game_GetLocalPlayer(), "jeanne_d_arc_consecrate_charges_cooldown", g_cheat_jeanne_d_arc_charges_cd_list[2]/100)
        else
            Modifier_Remove(g_cheat_fast_cd_modifier_list.applied_modid)
            Player_SetStateModelFloat(Game_GetLocalPlayer(), "jeanne_d_arc_wrath_charges_cooldown", g_cheat_jeanne_d_arc_charges_cd_list[1])
            Player_SetStateModelFloat(Game_GetLocalPlayer(), "jeanne_d_arc_consecrate_charges_cooldown", g_cheat_jeanne_d_arc_charges_cd_list[2])
        end
        print("g_cheat_fast_cd="..tostring(#g_cheat_fast_cd_modifier_list.applied_modid~=0))
    end
]])

Scar_DoString([[
    function Cheat_AgeUp()
        Player_SetCurrentAge(Game_GetLocalPlayer(),Player_GetCurrentAge(Game_GetLocalPlayer())+1)
        print("age="..Player_GetCurrentAge(Game_GetLocalPlayer()))
    end
]])

Scar_DoString([[
    function Cheat_AgeDown()
        Player_SetCurrentAge(Game_GetLocalPlayer(),Player_GetCurrentAge(Game_GetLocalPlayer())-1)
        EGroup_Duplicate(Player_GetAllEntities(Game_GetLocalPlayer()),g_cheat_egroup)
        EGroup_Filter(g_cheat_egroup,"wonder",FILTER_REMOVE)
        EGroup_ForEach(g_cheat_egroup,
            function(_,_,entity)
                Entity_ConvertBlueprint(entity, Entity_GetBlueprint(entity))
            end
        )
        print("age="..Player_GetCurrentAge(Game_GetLocalPlayer()))
    end
]])

Scar_DoString([[
    if not g_cheat_lock_current_tier_visuals_eid_list then
        g_cheat_lock_current_tier_visuals_eid_list={}
    end
    print("g_cheat_lock_current_tier_visuals_eid_list initialized")

    function Cheat_ToggleLockCurrentTierVisuals()
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup,true)
        local entity
        SGroup_ForEach(g_cheat_sgroup,
            function(_,_,squad)
                entity=Squad_GetFirstEntity(squad)
                g_cheat_lock_current_tier_visuals_eid_list[entity.EntityID]=not g_cheat_lock_current_tier_visuals_eid_list[entity.EntityID]
                Entity_SetLockCurrentTierVisuals(entity,g_cheat_lock_current_tier_visuals_eid_list[entity.EntityID])
                print("lock_current_tier_visuals="..tostring(g_cheat_lock_current_tier_visuals_eid_list[entity.EntityID]))
            end
        )
        EGroup_ForEach(g_cheat_egroup,
            function(_,_,entity)
                g_cheat_lock_current_tier_visuals_eid_list[entity.EntityID]=not g_cheat_lock_current_tier_visuals_eid_list[entity.EntityID]
                Entity_SetLockCurrentTierVisuals(entity,g_cheat_lock_current_tier_visuals_eid_list[entity.EntityID])
                print("lock_current_tier_visuals="..tostring(g_cheat_lock_current_tier_visuals_eid_list[entity.EntityID]))
            end
        )
    end
]])

Scar_DoString([[
    g_cheat_building_ebp_list=BP_GetEntityBlueprintsWithType("building")
    print("g_cheat_building_ebp_list initialized")
    g_cheat_town_center_ebp_list=BP_GetEntityBlueprintsWithType_Internal({"hq","town_center_non_capital"},ALL)
    print("g_cheat_town_center_ebp_list initialized")

    function Cheat_ToggleBuildingProductionAvailability()
        if g_cheat_building_production_unlocked then
            for _,ebp in ipairs(g_cheat_building_ebp_list) do
                Player_SetEntityProductionAvailability(Game_GetLocalPlayer(),ebp,ITEM_DEFAULT)
            end
        else
            for _,ebp in ipairs(g_cheat_building_ebp_list) do
                Player_SetEntityProductionAvailability(Game_GetLocalPlayer(),ebp,ITEM_UNLOCKED)
            end
            for _,ebp in ipairs(g_cheat_town_center_ebp_list) do
                Player_SetEntityProductionAvailability(Game_GetLocalPlayer(),ebp,ITEM_REMOVED)
            end
        end
        g_cheat_building_production_unlocked=not g_cheat_building_production_unlocked
        print("g_cheat_building_production_unlocked="..tostring(g_cheat_building_production_unlocked))
    end
]])

Scar_DoString([[
    function Cheat_UnlockAvailability()
        g_cheat_building_production_unlocked=false
        Player_ClearAvailabilities(Game_GetLocalPlayer())
        Player_SetUpgradeAvailability(Game_GetLocalPlayer(),BP_GetUpgradeBlueprint("upgrade_outpost_handcannonslits_chi"),ITEM_UNLOCKED)
        Player_SetUpgradeAvailability(Game_GetLocalPlayer(),BP_GetUpgradeBlueprint("upgrade_outpost_arrowslits"),ITEM_UNLOCKED)
        Player_SetUpgradeAvailability(Game_GetLocalPlayer(),BP_GetUpgradeBlueprint("upgrade_outpost_springald"),ITEM_UNLOCKED)
        Player_SetUpgradeAvailability(Game_GetLocalPlayer(),BP_GetUpgradeBlueprint("upgrade_outpost_cannon"),ITEM_UNLOCKED)
        print("availabilities unlocked")
    end
]])

Scar_DoString([[
    if not g_cheat_clipboard then
        g_cheat_clipboard={}
    end
    print("g_cheat_clipboard initialized")

    function Cheat_Copy()
        g_cheat_clipboard={}
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup,true)
        local group_center,item
        if not SGroup_IsEmpty(g_cheat_sgroup) then
            group_center=SGroup_GetPosition(g_cheat_sgroup)
            SGroup_ForEach(g_cheat_sgroup,
                function(_,_,squad)
                    item={}
                    item.bp=Squad_GetBlueprint(squad)
                    item.owner=Util_GetPlayerOwner(squad)
                    item.offset=Squad_GetPosition(squad)
                    item.offset.x=item.offset.x-group_center.x
                    item.offset.y=item.offset.y-group_center.y
                    item.offset.z=item.offset.z-group_center.z
                    table.insert(g_cheat_clipboard,item)
                end
            )
        elseif not EGroup_IsEmpty(g_cheat_egroup) then
            group_center=EGroup_GetPosition(g_cheat_egroup)
            EGroup_ForEach(g_cheat_egroup,
                function(_,_,entity)
                    item={}
                    item.bp=Entity_GetBlueprint(entity)
                    item.owner=Util_GetPlayerOwner(entity)
                    item.offset=Entity_GetPosition(entity)
                    item.offset.x=item.offset.x-group_center.x
                    item.offset.y=item.offset.y-group_center.y
                    item.offset.z=item.offset.z-group_center.z
                    table.insert(g_cheat_clipboard,item)
                end
            )
        end
        print("---------")
        print("clipboard")
        print("---------")
        print("bp_name".."\t".."player_id"..": ".."player_name".." ".."player_relation")
        print("---------")
        local id,name,relation
        for _,item in ipairs(g_cheat_clipboard) do
            if item.owner then
                id=World_GetPlayerIndex(item.owner)
                name=Player_GetDisplayName(item.owner).LocString
                relation=Player_ObserveRelationship(Game_GetLocalPlayer(),item.owner)
                if relation==R_NEUTRAL then relation="R_NEUTRAL"
                elseif relation==R_ALLY then relation="R_ALLY"
                elseif relation==R_ENEMY then relation="R_ENEMY"
                else relation="R_UNDEFINED" end
            else
                id=0
                name="gaia"
                relation="world"
            end
            print(BP_GetName(item.bp).."\t"..id..": "..name.." "..relation)
        end
        print("---------")
    end

    function Cheat_Cut()
        Cheat_Copy()
        Cheat_Delete()
    end

    function Cheat_Paste()
        local pos=World_Pos(0,0,0)
        local screen_center=Camera_GetPivot()
        if Misc_GetScreenCenterPosition then
            screen_center=Misc_GetScreenCenterPosition()
        end
        local entity
        for _,item in ipairs(g_cheat_clipboard) do
            pos.x=screen_center.x+item.offset.x
            pos.y=screen_center.y+item.offset.y
            pos.z=screen_center.z+item.offset.z
            if BP_GetType(item.bp)==PBGTYPE_Squad then
                if item.owner then
                    Squad_CreateAndSpawnTowardCheat(item.bp, item.owner, 0, pos, pos)
                else
                    Squad_CreateAndSpawnTowardENVCheat(item.bp, 0, pos, pos)
                end
            else
                Entity_Precache(item.bp,0,Game_GetLocalPlayer(),"g_cheat_cache","","")
                if item.owner then
                    entity=Entity_Create(item.bp, item.owner, pos, true)
                else
                    entity=Entity_CreateENV(item.bp, pos, true)
                end
                Entity_ForceConstruct(entity)
                Entity_Spawn(entity)
            end
        end
        print("clipboard pasted")
    end
]])

Scar_DoString([[
    function Cheat_CopyOwner()
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup, true)
        if SGroup_IsEmpty(g_cheat_sgroup) then
            g_cheat_owner_clipboard=Util_GetPlayerOwner(g_cheat_egroup)
        else
            g_cheat_owner_clipboard=Util_GetPlayerOwner(g_cheat_sgroup)
        end
        local id,name,relation
        if g_cheat_owner_clipboard then
            id=World_GetPlayerIndex(g_cheat_owner_clipboard)
            name=Player_GetDisplayName(g_cheat_owner_clipboard).LocString
            relation=Player_ObserveRelationship(Game_GetLocalPlayer(),g_cheat_owner_clipboard)
            if relation==R_NEUTRAL then relation="R_NEUTRAL"
            elseif relation==R_ALLY then relation="R_ALLY"
            elseif relation==R_ENEMY then relation="R_ENEMY"
            else relation="R_UNDEFINED" end
        else
            id=0
            name="gaia"
            relation="world"
        end
        print("---------")
        print("owner_clipboard")
        print("---------")
        print("player_id"..": ".."player_name".." ".."player_relation")
        print("---------")
        print(id..": "..name.." "..relation)
        print("---------")
    end

    function Cheat_PasteOwner()
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup,true)
        if g_cheat_owner_clipboard then
            SGroup_SetPlayerOwner(g_cheat_sgroup,g_cheat_owner_clipboard)
            EGroup_SetPlayerOwner(g_cheat_egroup,g_cheat_owner_clipboard)
        else
            SGroup_SetWorldOwned(g_cheat_sgroup)
            EGroup_SetWorldOwned(g_cheat_egroup)
        end
        Sound_Play2D("sfx_ability_monk_conversion_cast")
        print("owner pasted")
    end
]])

Scar_DoString([[
    function Cheat_Heal()
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup, true)
        SGroup_CallSquadFunction(g_cheat_sgroup,Squad_SetHealth,1)
        EGroup_CallEntityFunction(g_cheat_egroup,Entity_SetHealth,1)
        print("selection healed")
    end
]])

Scar_DoString([[
    function Cheat_ToggleInvulnerable()
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup, true)
        EGroup_CallEntityFunction(g_cheat_egroup,Entity_SetStayBurningWhileInvulnerable,true)
        if not SGroup_IsEmpty(g_cheat_sgroup) then
            local invulnerable=not SGroup_GetInvulnerable(g_cheat_sgroup,ALL)
            SGroup_SetInvulnerable(g_cheat_sgroup,invulnerable)
            print("invulnerable="..tostring(invulnerable))
        elseif not EGroup_IsEmpty(g_cheat_egroup) then
            local invulnerable=not EGroup_GetInvulnerable(g_cheat_egroup,ALL)
            EGroup_SetInvulnerable(g_cheat_egroup,invulnerable)
            print("invulnerable="..tostring(invulnerable))
        end
    end
]])

Scar_DoString([[
    if not g_cheat_super_speed_modifier_list then
        g_cheat_super_speed_modifier_list={
            Modifier_Create(MAT_Entity, "speed_maximum_modifier", MUT_Multiplication, false, 100, nil),
            applied_modid={}
        }
    end
    print("g_cheat_super_speed_modifier_list initialized")

    function Cheat_ToggleSuperSpeed()
        local all_accelerated=true
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        SGroup_ForEach(g_cheat_sgroup,
            function(_,_,squad)
                if not g_cheat_super_speed_modifier_list.applied_modid[squad.SquadID] then
                    all_accelerated=false
                    g_cheat_super_speed_modifier_list.applied_modid[squad.SquadID]=Modifier_ApplyToSquad(g_cheat_super_speed_modifier_list[1],squad,0)
                    print("super_speed=true")
                end
            end
        )
        if all_accelerated then
            SGroup_ForEach(g_cheat_sgroup,
                function(_,_,squad)
                    Modifier_Remove(g_cheat_super_speed_modifier_list.applied_modid[squad.SquadID])
                    g_cheat_super_speed_modifier_list.applied_modid[squad.SquadID]=nil
                    print("super_speed=false")
                end
            )
        end
    end
]])

Scar_DoString([[
    function Cheat_Teleport()
        local pos=Camera_GetPivot()
        if Misc_GetScreenCenterPosition then
            pos=Misc_GetScreenCenterPosition()
        end
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup, true)
        SGroup_WarpToPos(g_cheat_sgroup,pos)
        EGroup_WarpToPos(g_cheat_egroup,pos)
        print("selection teleported")
    end
]])

Scar_DoString([[
    function Cheat_Rotate()
        local pos=Camera_GetPivot()
        if Misc_GetScreenCenterPosition then
            pos=Misc_GetScreenCenterPosition()
        end
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup, true)
        SGroup_SnapFacePosition(g_cheat_sgroup,pos)
        EGroup_ForEach(g_cheat_egroup,
            function(_,_,entity)
                Entity_SetHeading(entity,Util_GetDirectionFromAtoB(g_cheat_egroup,pos),true)
            end
        )
        print("selection rotated")
    end
]])

Scar_DoString([[
    function Cheat_SetOnFire()
        Misc_GetSelectedEntities(g_cheat_egroup, true)
        EGroup_CallEntityFunction(g_cheat_egroup,Entity_SetHealth,0.25)
        print("selection ignited")
    end
]])

Scar_DoString([[
    function Cheat_Kill()
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup, true)
        SGroup_Kill(g_cheat_sgroup)
        EGroup_Kill(g_cheat_egroup)
        print("selection killed")
    end
]])

Scar_DoString([[
    function Cheat_Delete()
        Misc_GetSelectedSquads(g_cheat_sgroup, true)
        Misc_GetSelectedEntities(g_cheat_egroup, true)
        SGroup_DestroyAllSquads(g_cheat_sgroup)
        EGroup_DestroyAllEntities(g_cheat_egroup)
        print("selection cleared")
    end
]])

-- needs fix
Scar_DoString([[
    function Cheat_ToggleTimerRate()
        if g_TimerRate==0 then
            g_TimerRate=1
            SetTimerRate(1)
        else
            g_TimerRate=0
            SetTimerRate(0)
        end
        print("g_TimerRate="..g_TimerRate)
    end
]])

Scar_DoString([[
    if not g_cheat_player_colour_id then
        g_cheat_player_colour_id=1
    end
    function Cheat_TogglePlayerColour()
        g_cheat_player_colour_id=g_cheat_player_colour_id%9+1
        Game_SetPlayerColour(Game_GetLocalPlayer(),g_cheat_player_colour_id)
        print("g_cheat_player_colour_id="..g_cheat_player_colour_id)
    end
]])

Scar_DoString([[
    function Cheat_SaveWorldObjects()
        g_cheat_world_objects={}
        World_GetAllNeutralSquads(g_cheat_sgroup)
        World_GetAllNeutralEntities(g_cheat_egroup)
        EGroup_FilterSquads(g_cheat_egroup,FILTER_REMOVE)
        SGroup_ForEach(g_cheat_sgroup,
            function(_,_,squad)
                table.insert(g_cheat_world_objects,
                    {
                        bp=Squad_GetBlueprint(squad),
                        pos=Squad_GetPosition(squad),
                        heading=Squad_GetHeading(squad),
                    }
                )
            end
        )
        EGroup_ForEach(g_cheat_egroup,
            function(_,_,entity)
                table.insert(g_cheat_world_objects,
                    {
                        bp=Entity_GetBlueprint(entity),
                        pos=Entity_GetPosition(entity),
                        heading=Entity_GetHeading(entity),
                    }
                )
            end
        )
        print("world objects saved")
    end

    if not g_cheat_world_objects then
        Cheat_SaveWorldObjects()
    end

    function Cheat_LoadWorldObjects()
        World_GetAllNeutralEntities(g_cheat_egroup)
        EGroup_DestroyAllEntities(g_cheat_egroup)
        local entity
        for _,item in ipairs(g_cheat_world_objects) do
            if BP_GetType(item.bp)==PBGTYPE_Squad then
                Squad_CreateAndSpawnTowardENV(item.bp, 0, item.pos, Util_GetFacingFromDirection(item.pos,item.heading))
            else
                entity=Entity_CreateENVFacing(item.bp, item.pos, Util_GetFacingFromDirection(item.pos,item.heading), true)
                Entity_ForceConstruct(entity)
                Entity_Spawn(entity)
            end
        end
        print("world objects loaded")
    end
]])

Scar_DoString([[
    function Cheat_Win() World_EndSP(true,0) end
    function Cheat_Lose() World_EndSP(false,0) end
]])

Scar_DoString([[
    function Cheat_Deactivate()
        if g_cheat_fow_disabled then Cheat_ToggleFOW() end
        if Player_GetCurrentPopulationCap(Game_GetLocalPlayer(),CT_Personnel)==99999 then Cheat_TogglePopCap() end
        if #g_cheat_fast_gather_modifier_list.applied_modid~=0 then Cheat_ToggleFastGather() end
        if #g_cheat_fast_production_modifier_list.applied_modid~=0 then Cheat_ToggleFastProduction() end
        if #g_cheat_fast_construction_and_repair_modifier_list.applied_modid~=0 then Cheat_ToggleFastConstructionAndRepair() end
        if #g_cheat_fast_cd_modifier_list.applied_modid~=0 then Cheat_ToggleFastCD() end
        for eid,_ in pairs(g_cheat_lock_current_tier_visuals_eid_list) do
            Entity_SetLockCurrentTierVisuals(Entity_FromID(eid),false)
            g_cheat_lock_current_tier_visuals_eid_list[eid]=nil
        end
        if g_cheat_building_production_unlocked then Cheat_ToggleBuildingProductionAvailability() end
        EGroup_SetInvulnerable(Player_GetAllEntities(Game_GetLocalPlayer()),false)
        for sid,modid in pairs(g_cheat_super_speed_modifier_list.applied_modid) do
            g_cheat_super_speed_modifier_list.applied_modid[sid]=nil
            Modifier_Remove(modid)
        end
        if g_TimerRate==0 then Cheat_ToggleTimerRate() end
    end
]])

Scar_DoString([[
    function Cheat_ToggleUpgrade(name)
        local ubp=BP_GetUpgradeBlueprint(name)
        if Player_HasUpgrade(Game_GetLocalPlayer(),ubp) then
            Player_RemoveUpgrade(Game_GetLocalPlayer(),ubp)
            print(name.."=false")
        else
            Player_CompleteUpgrade(Game_GetLocalPlayer(),ubp)
            print(name.."=true")
        end
    end
]])

Scar_DoString([[
    function Cheat_Spawn(name,id)
        local pos=Camera_GetPivot()
        if Misc_GetScreenCenterPosition then
            pos=Misc_GetScreenCenterPosition()
        end
        if not id then
            id=World_GetPlayerIndex(Game_GetLocalPlayer())
        end
        if SBP_Exists(name) then
            local sbp=BP_GetSquadBlueprint(name)
            if id==0 then
                Squad_CreateAndSpawnTowardENVCheat(sbp, 0, pos, pos)
            else
                Squad_CreateAndSpawnTowardCheat(sbp, World_GetPlayerAt(id), 0, pos, pos)
            end
        else
            local ebp=BP_GetEntityBlueprint(name)
            local entity
            Entity_Precache(ebp,0,Game_GetLocalPlayer(),"g_cheat_cache","","")
            if id==0 then
                entity=Entity_CreateENV(ebp, pos, true)
            else
                entity=Entity_Create(ebp, World_GetPlayerAt(id), pos, true)
            end
            Entity_ForceConstruct(entity)
            Entity_Spawn(entity)
        end
        print(name.." spawned")
    end
]])

function reload()
    dofile("cheat/cheat.lua")
end
function deactivate()
    Scar_DoString("if Cheat_Deactivate then Cheat_Deactivate() end")
end
function tooktheredpill()
    Scar_DoString("if Cheat_ToggleFOW then Cheat_ToggleFOW() end")
end
function iamironman()
    Scar_DoString("if Cheat_ToggleBonus then Cheat_ToggleBonus() end")
end
function oi()
    Scar_DoString("if Cheat_AddBonusAbility then Cheat_AddBonusAbility() end")
end
function showmethemoney()
    Scar_DoString("if Cheat_AddResources then Cheat_AddResources() end")
end
function pointbreak()
    Scar_DoString("if Cheat_TogglePopCap then Cheat_TogglePopCap() end")
end
function theblip(xp3)
    cursor_setposition(0.5,0.5)
    if xp3 then Scar_DoString("if Cheat_SpawnVillagerXP3 then Cheat_SpawnVillagerXP3() end")
    else Scar_DoString("if Cheat_SpawnVillager then Cheat_SpawnVillager() end") end
end
function moredotsmoredots()
    Scar_DoString("if Cheat_ToggleFastGather then Cheat_ToggleFastGather() end")
end
function catfoodforprawnguns()
    Scar_DoString("if Cheat_ToggleFastProduction then Cheat_ToggleFastProduction() end")
end
function warpten()
    Scar_DoString("if Cheat_ToggleFastConstructionAndRepair then Cheat_ToggleFastConstructionAndRepair() end")
end
function hanshotfirst()
    Scar_DoString("if Cheat_ToggleFastCD then Cheat_ToggleFastCD() end")
end
function waraintwhatitusedtobe()
    Scar_DoString("if Cheat_AgeUp then Cheat_AgeUp() end")
end
function medievalman()
    Scar_DoString("if Cheat_AgeDown then Cheat_AgeDown() end")
end
function capsical()
    Scar_DoString("if Cheat_ToggleLockCurrentTierVisuals then Cheat_ToggleLockCurrentTierVisuals() end")
end
function modifythephasevariance()
    Scar_DoString("if Cheat_ToggleBuildingProductionAvailability then Cheat_ToggleBuildingProductionAvailability() end")
end
function sosayweall()
    Scar_DoString("if Cheat_UnlockAvailability then Cheat_UnlockAvailability() end")
end
function copy()
    Scar_DoString("if Cheat_Copy then Cheat_Copy() end")
end
function cut()
    Scar_DoString("if Cheat_Cut then Cheat_Cut() end")
end
function paste()
    cursor_setposition(0.5,0.5)
    Scar_DoString("if Cheat_Paste then Cheat_Paste() end")
end
function copyowner()
    Scar_DoString("if Cheat_CopyOwner then Cheat_CopyOwner() end")
end
function pasteowner()
    Scar_DoString("if Cheat_PasteOwner then Cheat_PasteOwner() end")
end
function imadoctornotaroachjim()
    Scar_DoString("if Cheat_Heal then Cheat_Heal() end")
end
function whosyourdaddy()
    Scar_DoString("if Cheat_ToggleInvulnerable then Cheat_ToggleInvulnerable() end")
end
function runbarryrun()
    Scar_DoString("if Cheat_ToggleSuperSpeed then Cheat_ToggleSuperSpeed() end")
end
function zeleport()
    cursor_setposition(0.5,0.5)
    Scar_DoString("if Cheat_Teleport then Cheat_Teleport() end")
end
function russianroulette()
    cursor_setposition(0.5,0.5)
    Scar_DoString("if Cheat_Rotate then Cheat_Rotate() end")
end
function shesawitch()
    Scar_DoString("if Cheat_SetOnFire then Cheat_SetOnFire() end")
end
function youshallnotpass()
    Scar_DoString("if Cheat_Kill then Cheat_Kill() end")
end
function thesnap()
    Scar_DoString("if Cheat_Delete then Cheat_Delete() end")
end
function seethatcomming()
    Scar_DoString("if Cheat_ToggleTimerRate then Cheat_ToggleTimerRate() end")
end
function CHANGE()
    Scar_DoString("if Cheat_TogglePlayerColour then Cheat_TogglePlayerColour() end")
end
function xelnagascycle()
    Scar_DoString("if Cheat_LoadWorldObjects then Cheat_LoadWorldObjects() end")
end
function whatisbestinlife()
    Scar_DoString("if Cheat_Win then Cheat_Win() end")
end
function letsjustbugoutandcalliteven()
    Scar_DoString("if Cheat_Lose then Cheat_Lose() end")
end
function upgrade(name)
    Scar_DoString("if Cheat_ToggleUpgrade then Cheat_ToggleUpgrade('"..name.."') end")
end
function spawn(name,id)
    cursor_setposition(0.5,0.5)
    Scar_DoString("if Cheat_Spawn then Cheat_Spawn('"..name.."',"..tostring(id)..") end")
end
Scar_DoString "print('lua function initialized')"

bind("CONTROL+HOME","reload")
bind("CONTROL+END","deactivate")
bind("CONTROL+F","tooktheredpill")
bind("CONTROL+B","iamironman")
bind("CONTROL+O","oi")
bind("CONTROL+M","showmethemoney")
bind("CONTROL+P","pointbreak")
bind("CONTROL+N","theblip")
bind("CONTROL+SHIFT+N","theblip(true)")
bind("CONTROL+G","moredotsmoredots")
bind("CONTROL+E","catfoodforprawnguns")
bind("CONTROL+W","warpten")
bind("CONTROL+Q","hanshotfirst")
bind("CONTROL+UP","waraintwhatitusedtobe")
bind("CONTROL+DOWN","medievalman")
bind("CONTROL+L","capsical")
bind("CONTROL+U","modifythephasevariance")
bind("CONTROL+SHIFT+U","sosayweall")
bind("CONTROL+C","copy")
bind("CONTROL+X","cut")
bind("CONTROL+V","paste")
bind("CONTROL+SHIFT+C","copyowner")
bind("CONTROL+SHIFT+V","pasteowner")
bind("CONTROL+H","imadoctornotaroachjim")
bind("CONTROL+I","whosyourdaddy")
bind("CONTROL+J","runbarryrun")
bind("CONTROL+T","zeleport")
bind("CONTROL+R","russianroulette")
bind("ALT+DELETE","shesawitch")
bind("CONTROL+DELETE","youshallnotpass")
bind("CONTROL+SHIFT+DELETE","thesnap")
bind("CONTROL+SPACE","seethatcomming")
bind("CONTROL+TAB","CHANGE")
bind("CONTROL+Z","xelnagascycle")
bind("CONTROL+PAGEUP","whatisbestinlife")
bind("CONTROL+PAGEDOWN","letsjustbugoutandcalliteven")
Scar_DoString "print('binding initialized')"

Scar_DoString "print('cheat activated')"