Files
wow/game_state.py
2026-03-18 09:04:37 +08:00

167 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import os
import sys
import time
import ctypes
import pyautogui
import pygetwindow as gw
from PIL import Image
# 解决 Windows 高 DPI 缩放问题
try:
ctypes.windll.shcore.SetProcessDpiAwareness(1)
except Exception:
pass
# 默认布局参数
_DEFAULTS = {
"pixel_size": 17,
"block_start_x": 30,
"scan_region_width": 155,
"scan_region_height": 15,
"offset_left": 20,
"offset_top": 45,
}
SCREENSHOT_DIR = 'screenshot'
CONFIG_FILE = 'game_state_config.json'
def _config_base():
if getattr(sys, 'frozen', False):
return os.path.dirname(sys.executable)
return os.path.dirname(os.path.abspath(__file__))
def _get_config_path():
base = _config_base()
p = os.path.join(base, CONFIG_FILE)
if os.path.exists(p):
return p
if getattr(sys, 'frozen', False) and getattr(sys, '_MEIPASS', ''):
p2 = os.path.join(sys._MEIPASS, CONFIG_FILE)
if os.path.exists(p2):
return p2
return p
def load_layout_config():
"""加载 game_state_config.json返回布局参数字典"""
path = _get_config_path()
cfg = dict(_DEFAULTS)
if os.path.exists(path):
try:
with open(path, 'r', encoding='utf-8') as f:
loaded = json.load(f)
for k in _DEFAULTS:
if k in loaded:
cfg[k] = loaded[k]
except Exception:
pass
cfg['center_offset'] = cfg['pixel_size'] // 2
return cfg
def save_layout_config(cfg):
"""保存布局配置到 game_state_config.json"""
path = _get_config_path()
out = {k: cfg[k] for k in _DEFAULTS if k in cfg}
with open(path, 'w', encoding='utf-8') as f:
json.dump(out, f, indent=2, ensure_ascii=False)
return path
def get_wow_info():
windows = gw.getWindowsWithTitle('魔兽世界')
if not windows:
return None
wow = windows[0]
cfg = load_layout_config()
return {
"left": wow.left + cfg['offset_left'],
"top": wow.top + cfg['offset_top'],
"layout": cfg,
}
def parse_game_state():
pos = get_wow_info()
if not pos:
return None
cfg = pos['layout']
w = cfg['scan_region_width']
h = cfg['scan_region_height']
pixel_size = cfg['pixel_size']
block_start_x = cfg['block_start_x']
center_offset = cfg['center_offset']
# 截图并保存到 screenshot 文件夹
screenshot = pyautogui.screenshot(region=(pos['left'], pos['top'], w, h))
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
screenshot.save(os.path.join(SCREENSHOT_DIR, 'game_state.png'))
# 按截图像素分段:每 pixel_size 一格 → hp, mp, combat, target, logistics_death, x, y
CHANNEL_NAMES = ('hp', 'mp', 'combat', 'target', 'logistics_death', 'x', 'y')
for idx, name in enumerate(CHANNEL_NAMES):
left = block_start_x + idx * pixel_size
box = (left, 0, left + pixel_size, pixel_size)
crop = screenshot.crop(box)
crop.save(os.path.join(SCREENSHOT_DIR, f'game_state_{name}.png'))
# 每格取中心像素,避免裁到边缘混合色
def get_val(idx):
sample_x = block_start_x + (idx * pixel_size) + center_offset
sample_y = center_offset
return screenshot.getpixel((sample_x, sample_y))
state = {}
# idx 0=血(红), 1=法(蓝), 2=战斗(绿), 3=目标(红)
hp_px = get_val(0)
state['hp'] = round(hp_px[0] / 255 * 100)
state['target_hp'] = round(hp_px[1] / 255 * 100)
mp_px = get_val(1)
state['mp'] = round(mp_px[2] / 255 * 100)
cb_px = get_val(2)
state['combat'] = cb_px[1] > 150 # 绿色通道
tg_px = get_val(3)
state['target'] = tg_px[0] > 150 # 红色通道
# 第 5 像素:后勤与死亡 (R=空格数, G=耐久比例, B=死亡状态)
p5 = get_val(4)
state['free_slots'] = p5[0]
state['durability'] = round(p5[1] / 255.0, 3)
state['death_state'] = 0 if p5[2] < 50 else (1 if p5[2] < 200 else 2) # 0存活 1尸体 2灵魂
# 第 6 像素x 坐标
p6 = get_val(5)
state['x'] = round((p6[0] + p6[1]/100), 2)
raw_deg = (p6[2] / 255) * 360.0
state['facing'] = (360.0 - raw_deg) % 360.0
# 第 7 像素y 坐标
p7 = get_val(6)
state['y'] = round((p7[0] + p7[1]/100), 2)
return state
if __name__ == "__main__":
print("开始监控魔兽状态... (Ctrl+C 退出)")
try:
while True:
state = parse_game_state()
if state:
death_txt = ('存活', '尸体', '灵魂')[state.get('death_state', 0)]
print(
f"\r[状态] 血:{state['hp']}% 法:{state['mp']}% 目标血:{state.get('target_hp', 0)}% | "
f"战斗:{'Y' if state['combat'] else 'N'} 目标:{'Y' if state['target'] else 'N'} | "
f"空格:{state.get('free_slots', 0)} 耐久:{state.get('durability', 0):.0%} {death_txt} | "
f"x:{state['x']} y:{state['y']} 朝向:{state['facing']:.1f}°",
end=""
)
time.sleep(0.5)
except KeyboardInterrupt:
print("\n已停止。")