SAutoScript 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ SAutoScript - 游戏自动脚本系统
5
+ 基于图像识别技术的动作游戏自动脚本系统,支持Windows平台的鼠标键盘仿真输入。
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+ __author__ = "SAutoScript Team"
10
+ __license__ = "MIT"
11
+
12
+ from sautoscript.core.base_game_script import BaseGameScript
13
+ from sautoscript.core.input_controller import InputController
14
+ from sautoscript.core.screen_capture import ScreenCapture
15
+ from sautoscript.core.image_recognition import ImageRecognition
16
+ from sautoscript.core.game_operations import GameOperations
17
+ from sautoscript.core.window_locator import WindowLocator
18
+ from sautoscript.core.error_logger import log_exception
19
+
20
+ __all__ = [
21
+ "BaseGameScript",
22
+ "InputController",
23
+ "ScreenCapture",
24
+ "ImageRecognition",
25
+ "GameOperations",
26
+ "WindowLocator",
27
+ "log_exception"
28
+ ]
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ SAutoScript 核心模块
5
+ """
6
+
7
+ from sautoscript.core.base_game_script import BaseGameScript
8
+ from sautoscript.core.input_controller import InputController
9
+ from sautoscript.core.screen_capture import ScreenCapture
10
+ from sautoscript.core.image_recognition import ImageRecognition
11
+ from sautoscript.core.game_operations import GameOperations
12
+ from sautoscript.core.window_locator import WindowLocator
13
+ from sautoscript.core.error_logger import log_exception
14
+
15
+ __all__ = [
16
+ "BaseGameScript",
17
+ "InputController",
18
+ "ScreenCapture",
19
+ "ImageRecognition",
20
+ "GameOperations",
21
+ "WindowLocator",
22
+ "log_exception"
23
+ ]
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import os
5
+ import sys
6
+ import time
7
+ import random
8
+ import yaml
9
+ from abc import ABC, abstractmethod
10
+ from loguru import logger
11
+ import gc
12
+ import psutil # 用于内存监控
13
+
14
+ # 绝对导入
15
+ try:
16
+ from sautoscript.core.image_recognition import ImageRecognition
17
+ from sautoscript.core.input_controller import InputController
18
+ from sautoscript.core.screen_capture import ScreenCapture
19
+ from sautoscript.core.game_operations import GameOperations, GameStuckError, GameTooManyClickError
20
+ from sautoscript.core.window_locator import WindowLocator
21
+ from sautoscript.core.error_logger import log_exception
22
+ except ImportError:
23
+ # 处理独立运行时的导入
24
+ from core.image_recognition import ImageRecognition
25
+ from core.input_controller import InputController
26
+ from core.screen_capture import ScreenCapture
27
+ from core.game_operations import (
28
+ GameOperations,
29
+ GameStuckError,
30
+ GameTooManyClickError,
31
+ )
32
+ from core.window_locator import WindowLocator
33
+ from core.error_logger import log_exception
34
+
35
+
36
+ class BaseGameScript(ABC):
37
+ """
38
+ 游戏脚本基类
39
+
40
+ 所有游戏脚本都应继承此类,实现通用的初始化、配置加载和主循环功能。
41
+ 子类需要实现game_logic方法来定义具体的游戏逻辑。
42
+ """
43
+
44
+ def __init__(self, config_path=None):
45
+ """
46
+ 初始化游戏脚本
47
+
48
+ Args:
49
+ config_path: 配置文件路径,默认为../config/settings.yaml
50
+ """
51
+ # 加载配置
52
+ self.config = self._load_config(config_path)
53
+
54
+ # 初始化组件
55
+ self._init_components()
56
+
57
+ # 运行状态
58
+ self.running = False
59
+
60
+ # 成功执行次数
61
+ self.success_num = 0
62
+
63
+ # 循环控制
64
+ # 循环控制
65
+ loop_config = self.config.get("loop_control", {})
66
+ self.max_loops = loop_config.get("max_iterations", 100) # 默认最多执行100次循环
67
+ self.loop_delay = loop_config.get("iteration_delay", 1) # 默认循环间隔1秒
68
+
69
+ # 内存优化配置
70
+ memory_config = self.config.get("memory_optimization", {})
71
+ self.gc_frequency = memory_config.get("gc_frequency", 50) # 垃圾回收频率
72
+ self.memory_threshold_mb = memory_config.get(
73
+ "memory_threshold_mb", 400
74
+ ) # 内存阈值(MB)
75
+ self.enable_smart_gc = memory_config.get("enable_smart_gc", True) # 启用智能GC
76
+ self.enable_memory_monitoring = memory_config.get(
77
+ "enable_memory_monitoring", True
78
+ ) # 启用内存监控
79
+ self.memory_check_frequency = memory_config.get(
80
+ "memory_check_frequency", 20
81
+ ) # 内存检查频率
82
+
83
+ logger.info(f"{self.__class__.__name__}初始化完成")
84
+
85
+ def _load_config(self, config_path=None):
86
+ """
87
+ 加载配置文件
88
+
89
+ Args:
90
+ config_path: 配置文件路径
91
+
92
+ Returns:
93
+ dict: 配置字典
94
+ """
95
+ if config_path is None:
96
+ # 默认配置文件路径
97
+ config_path = os.path.join(
98
+ os.path.dirname(__file__), "..", "..", "config", "settings.yaml"
99
+ )
100
+
101
+ try:
102
+ with open(config_path, "r", encoding="utf-8") as f:
103
+ return yaml.safe_load(f)
104
+ except Exception as e:
105
+ logger.error(f"加载配置文件失败: {e}")
106
+ # 记录错误到以时间命名的日志文件
107
+ script_name = self.__class__.__name__
108
+ log_exception(
109
+ f"在{script_name}的_load_config方法中加载配置文件{config_path}时发生错误",
110
+ script_name,
111
+ )
112
+ return {}
113
+
114
+ def _init_components(self):
115
+ """
116
+ 初始化组件
117
+ """
118
+ try:
119
+ # 初始化屏幕捕获组件
120
+ screen_capture_config = self.config.get("screen_capture", {})
121
+ self.screen_capture = ScreenCapture(screen_capture_config)
122
+
123
+ # 初始化输入控制组件
124
+ input_controller_config = self.config.get("input_control", {})
125
+ self.input_controller = InputController(input_controller_config)
126
+
127
+ # 初始化图像识别组件
128
+ self.image_recognition = ImageRecognition(
129
+ self.config.get("image_recognition", {})
130
+ )
131
+
132
+ # 初始化窗口定位组件
133
+ self.window_locator = WindowLocator()
134
+
135
+ # 初始化游戏操作组件
136
+ self.game_ops = GameOperations(
137
+ self.image_recognition,
138
+ self.input_controller,
139
+ self.screen_capture,
140
+ self.config,
141
+ )
142
+ except Exception as e:
143
+ logger.error(f"组件初始化失败: {e}")
144
+ # 记录错误到以时间命名的日志文件
145
+ script_name = self.__class__.__name__
146
+ log_exception(
147
+ f"在{script_name}的_init_components方法中初始化组件时发生错误",
148
+ script_name,
149
+ )
150
+ raise
151
+
152
+ def start(self, max_loops=None, loop_delay=None):
153
+ """
154
+ 启动脚本
155
+ """
156
+ logger.info(f"启动{self.__class__.__name__}")
157
+
158
+ # 设置循环次数和延迟
159
+ if max_loops:
160
+ self.max_loops = max_loops # 最多执行 x 次循环
161
+ if loop_delay:
162
+ self.loop_delay = loop_delay # 循环间隔 x 秒
163
+
164
+ self.running = True
165
+
166
+ try:
167
+ # 调用启动前钩子
168
+ self.on_start()
169
+
170
+ # 主循环
171
+ self._main_loop()
172
+ except KeyboardInterrupt:
173
+ logger.info("接收到中断信号,正在停止...")
174
+ if self.success_num:
175
+ logger.info(f"======================================")
176
+ logger.info(f"中断前执行成功次数: {self.success_num}")
177
+ logger.info(f"======================================")
178
+
179
+ except Exception as e:
180
+ logger.error(f"运行时错误: {e}")
181
+ # 记录错误到以时间命名的日志文件
182
+ script_name = self.__class__.__name__
183
+ log_exception(f"在{script_name}的start方法中发生错误", script_name)
184
+ if self.success_num:
185
+ logger.info(f"======================================")
186
+ logger.info(f"异常终止前执行成功次数: {self.success_num}")
187
+ logger.info(f"======================================")
188
+
189
+ finally:
190
+ self.stop()
191
+
192
+ def stop(self):
193
+ """
194
+ 停止脚本
195
+ """
196
+ logger.info(f"停止{self.__class__.__name__}")
197
+ self.running = False
198
+
199
+ # 调用停止后钩子
200
+ self.on_stop()
201
+
202
+ def _main_loop(self):
203
+ """
204
+ 主循环 - 集成智能垃圾回收
205
+ """
206
+ loop_count = 0
207
+
208
+ while self.running and loop_count < self.max_loops:
209
+ try:
210
+ # 调用游戏逻辑
211
+ self.game_logic()
212
+ loop_count += 1
213
+ logger.info(f"======================================")
214
+ logger.info(f"循环执行次数: {loop_count}/{self.max_loops}")
215
+ if self.success_num:
216
+ logger.info(f"当前执行成功次数: {self.success_num}/{loop_count}")
217
+ logger.info(f"======================================")
218
+
219
+ # 智能垃圾回收逻辑
220
+ if self.enable_smart_gc:
221
+ # 定期垃圾回收
222
+ if loop_count % self.gc_frequency == 0:
223
+ logger.debug(f"执行定期垃圾回收 (第{loop_count}次循环)")
224
+ gc.collect()
225
+
226
+ # 内存监控
227
+ if (
228
+ self.enable_memory_monitoring
229
+ and loop_count % self.memory_check_frequency == 0
230
+ ):
231
+ memory_mb = self._get_memory_usage()
232
+ if memory_mb > self.memory_threshold_mb:
233
+ logger.warning(
234
+ f"内存使用过高: {memory_mb:.1f}MB > {self.memory_threshold_mb}MB,执行垃圾回收"
235
+ )
236
+ gc.collect()
237
+ # 再次检查内存使用
238
+ memory_after_gc = self._get_memory_usage()
239
+ logger.info(f"垃圾回收后内存使用: {memory_after_gc:.1f}MB")
240
+
241
+ # 每次循环后添加延迟
242
+ time.sleep(self.loop_delay)
243
+ except Exception as e:
244
+ logger.error(f"主循环错误: {e},脚本将退出")
245
+ # 记录错误到以时间命名的日志文件
246
+ script_name = self.__class__.__name__
247
+ log_exception(
248
+ f"在{script_name}的主循环第{loop_count+1}次迭代中发生错误",
249
+ script_name,
250
+ )
251
+ break
252
+
253
+ # 循环结束时的最终垃圾回收
254
+ if self.enable_smart_gc:
255
+ logger.debug("主循环结束,执行最终垃圾回收")
256
+ gc.collect()
257
+
258
+ logger.info(f"已完成{loop_count}次循环,脚本将退出")
259
+
260
+ def _get_memory_usage(self):
261
+ """
262
+ 获取当前进程的内存使用量(MB)
263
+
264
+ Returns:
265
+ float: 内存使用量(MB)
266
+ """
267
+ try:
268
+ process = psutil.Process()
269
+ memory_info = process.memory_info()
270
+ memory_mb = memory_info.rss / 1024 / 1024 # 转换为MB
271
+ return memory_mb
272
+ except Exception as e:
273
+ logger.warning(f"获取内存使用量失败: {e}")
274
+ return 0.0
275
+
276
+ @abstractmethod
277
+ def game_logic(self):
278
+ """
279
+ 游戏逻辑,子类必须实现此方法
280
+ """
281
+ pass
282
+
283
+ def on_start(self):
284
+ """
285
+ 启动前钩子,子类可以重写此方法来执行启动前的操作
286
+ """
287
+ pass
288
+
289
+ def on_stop(self):
290
+ """
291
+ 停止后钩子,子类可以重写此方法来执行停止后的操作
292
+ """
293
+ pass
294
+
295
+ def create_template_from_screenshot(self, region, filename):
296
+ """
297
+ 从截图区域创建模板
298
+
299
+ Args:
300
+ region: 截图区域 (x, y, width, height)
301
+ filename: 模板文件名
302
+
303
+ Returns:
304
+ bool: 是否成功创建模板
305
+ """
306
+ # 捕获屏幕
307
+ screenshot = self.screen_capture.capture()
308
+
309
+ if screenshot is not None:
310
+ # 保存指定区域为模板
311
+ success = self.image_recognition.save_screenshot_region(
312
+ screenshot, region, filename
313
+ )
314
+
315
+ if success:
316
+ logger.info(f"模板已创建: {filename}")
317
+ return True
318
+
319
+ logger.error("创建模板失败")
320
+ return False
321
+
322
+ def random_event(self):
323
+ """
324
+ 随机事件,子类可以重写此方法来实现随机事件
325
+ """
326
+
327
+ # 例
328
+ random_type = random.choice(["jump", "left_right", "squat_down"])
329
+
330
+ if random_type == "jump":
331
+ # 跳跃
332
+ self.input_controller.key_press('space')
333
+ time.sleep(9)
334
+ self.input_controller.key_press('space')
335
+ elif random_type == "left_right":
336
+ # 左右移动
337
+ self.input_controller.key_press('a')
338
+ time.sleep(9)
339
+ self.input_controller.key_press('d')
340
+ elif random_type == "squat_down":
341
+ # 蹲下
342
+ self.input_controller.key_down('ctrl')
343
+ time.sleep(9)
344
+ self.input_controller.key_up('ctrl')
345
+
346
+ pass
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import os
5
+ import sys
6
+ import traceback
7
+ import datetime
8
+ from loguru import logger
9
+
10
+
11
+ class ErrorLogger:
12
+ """
13
+ 错误日志记录器
14
+
15
+ 用于在发生错误时将完整的错误信息保存到以时间命名的日志文件中
16
+ """
17
+
18
+ def __init__(self, log_dir="logs/errors"):
19
+ """
20
+ 初始化错误日志记录器
21
+
22
+ Args:
23
+ log_dir: 错误日志目录
24
+ """
25
+ self.log_dir = log_dir
26
+
27
+ # 确保错误日志目录存在
28
+ os.makedirs(self.log_dir, exist_ok=True)
29
+
30
+ def log_error(self, error, context="", script_name=""):
31
+ """
32
+ 记录错误到以时间命名的日志文件
33
+
34
+ Args:
35
+ error: 错误对象或错误信息
36
+ context: 错误上下文信息
37
+ script_name: 脚本名称
38
+ """
39
+ # 生成时间戳作为文件名
40
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
41
+
42
+ # 添加脚本名称到文件名(如果有)
43
+ if script_name:
44
+ filename = f"{script_name}_{timestamp}.log"
45
+ else:
46
+ filename = f"error_{timestamp}.log"
47
+
48
+ # 完整文件路径
49
+ filepath = os.path.join(self.log_dir, filename)
50
+
51
+ # 构建错误信息
52
+ error_message = f"===== 错误日志 =====\n"
53
+ error_message += f"时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
54
+ error_message += f"错误消息: {str(error)}\n"
55
+
56
+ if script_name:
57
+ error_message += f"脚本名称: {script_name}\n"
58
+
59
+ if context:
60
+ error_message += f"上下文信息:\n{context}\n"
61
+
62
+ # 获取堆栈跟踪
63
+ error_message += "\n堆栈跟踪:\n"
64
+ error_message += traceback.format_exc()
65
+ error_message += "\n===================="
66
+
67
+ # 写入错误日志文件
68
+ try:
69
+ with open(filepath, "w", encoding="utf-8") as f:
70
+ f.write(error_message)
71
+
72
+ logger.info(f"错误日志已保存到: {filepath}")
73
+ except Exception as e:
74
+ logger.error(f"保存错误日志失败: {e}")
75
+
76
+
77
+ # 全局错误日志记录器实例
78
+ error_logger = ErrorLogger()
79
+
80
+
81
+ def log_error(error, script_name="", context={}):
82
+ """
83
+ 记录普通错误信息
84
+
85
+ Args:
86
+ error: 错误信息
87
+ script_name: 脚本名称
88
+ context: 上下文信息(字典)
89
+ """
90
+ # 格式化上下文信息
91
+ context_str = ""
92
+ if context:
93
+ for key, value in context.items():
94
+ context_str += f" {key}: {value}\n"
95
+
96
+ error_logger.log_error(str(error), context_str, script_name)
97
+
98
+
99
+ def log_exception(error, script_name="", context={}):
100
+ """
101
+ 记录异常信息
102
+
103
+ Args:
104
+ error: 异常对象或错误信息
105
+ script_name: 脚本名称
106
+ context: 上下文信息(字典)
107
+ """
108
+ # 格式化上下文信息
109
+ context_str = ""
110
+ if context:
111
+ for key, value in context.items():
112
+ context_str += f" {key}: {value}\n"
113
+
114
+ error_logger.log_error(str(error), context_str, script_name)