#!/usr/bin/env python3 """ Timmy's Morrowind Agent — Gameplay loop with perception and action. Uses: - Quartz screenshots for visual perception - macOS Vision OCR for text reading (limited with Morrowind fonts) - OpenMW console commands (via ` key) for game state queries - pynput for keyboard/mouse input - OpenMW log file for event tracking The agent runs a perceive → think → act loop. """ import sys, time, subprocess, shutil, os, re sys.path.insert(0, '/Users/apayne/.timmy/morrowind') from play import * from pynput.keyboard import Key, KeyCode OPENMW_LOG = os.path.expanduser("~/Library/Preferences/openmw/openmw.log") SCREENSHOT_DIR = os.path.expanduser("~/.timmy/morrowind/screenshots") os.makedirs(SCREENSHOT_DIR, exist_ok=True) frame = 0 def focus_game(): subprocess.run(["osascript", "-e", 'tell application "System Events" to set frontmost of process "openmw" to true'], capture_output=True) time.sleep(0.5) click(3456 // 4, 2234 // 4) time.sleep(0.2) def read_log_tail(n=20): """Read last N lines of OpenMW log.""" try: with open(OPENMW_LOG, 'r') as f: lines = f.readlines() return [l.strip() for l in lines[-n:]] except: return [] def get_location_from_log(): """Parse the most recent cell loading from the log.""" lines = read_log_tail(50) cells = [] for line in reversed(lines): m = re.search(r'Loading cell (.+?)(?:\s*\(|$)', line) if m: cells.append(m.group(1).strip()) return cells[0] if cells else "unknown" def open_console(): """Open the OpenMW console with ` key.""" press_key(KeyCode.from_char('`')) time.sleep(0.3) def close_console(): """Close the console.""" press_key(KeyCode.from_char('`')) time.sleep(0.3) def console_command(cmd): """Type a command in the console and read the response.""" open_console() time.sleep(0.2) type_text(cmd) time.sleep(0.1) press_key(Key.enter) time.sleep(0.5) # Screenshot to read console output result = screenshot() texts = ocr() close_console() return " | ".join(t["text"] for t in texts) def perceive(): """Gather all available information about the game state.""" global frame frame += 1 # Screenshot path_info = screenshot() if path_info: shutil.copy('/tmp/morrowind_screen.png', f'{SCREENSHOT_DIR}/frame_{frame:04d}.png') # Log-based perception location = get_location_from_log() log_lines = read_log_tail(10) # OCR (limited but sometimes useful) texts = ocr() ocr_text = " | ".join(t["text"] for t in texts[:10] if t["confidence"] > 0.3) return { "frame": frame, "location": location, "ocr": ocr_text, "log": log_lines[-5:], "screenshot": f'{SCREENSHOT_DIR}/frame_{frame:04d}.png', } def act_explore(): """Basic exploration behavior: walk forward, look around, interact.""" # Walk forward walk_forward(2.0 + (frame % 3)) # Occasionally look around if frame % 3 == 0: look_around(30 + (frame % 60) - 30) # Occasionally try to interact if frame % 5 == 0: use() time.sleep(0.5) def act_wander(): """Random wandering with more variety.""" import random action = random.choice(['forward', 'forward', 'forward', 'turn_left', 'turn_right', 'look_up', 'interact']) if action == 'forward': walk_forward(random.uniform(1.0, 4.0)) elif action == 'turn_left': look_around(random.randint(-90, -20)) time.sleep(0.2) walk_forward(random.uniform(1.0, 2.0)) elif action == 'turn_right': look_around(random.randint(20, 90)) time.sleep(0.2) walk_forward(random.uniform(1.0, 2.0)) elif action == 'look_up': look_up(random.randint(10, 30)) time.sleep(0.5) look_down(random.randint(10, 30)) elif action == 'interact': use() time.sleep(1.0) # ═══════════════════════════════════════ # MAIN LOOP # ═══════════════════════════════════════ if __name__ == "__main__": print("=== Timmy's Morrowind Agent ===") print("Focusing game...") focus_game() time.sleep(1) # Dismiss any menus press_key(Key.esc) time.sleep(0.3) press_key(Key.esc) time.sleep(0.3) click(3456 // 4, 2234 // 4) time.sleep(0.3) print("Starting gameplay loop...") NUM_CYCLES = 15 for i in range(NUM_CYCLES): print(f"\n--- Cycle {i+1}/{NUM_CYCLES} ---") # Perceive state = perceive() print(f" Location: {state['location']}") print(f" OCR: {state['ocr'][:100]}") # Act act_wander() # Brief pause between cycles time.sleep(0.5) # Final perception print("\n=== Final State ===") state = perceive() print(f"Location: {state['location']}") print(f"Frames captured: {frame}") print(f"Screenshots in: {SCREENSHOT_DIR}") # Quicksave print("Quicksaving...") from pynput.keyboard import Key press_key(Key.f5) time.sleep(1) print("Done.")