diff --git a/ClaudeCode.md b/ClaudeCode.md index 85484a8..bcaac8e 100644 --- a/ClaudeCode.md +++ b/ClaudeCode.md @@ -52,6 +52,10 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機 7. **視窗設定工具 (window-setup-script.py)** - 輔助工具,用於設置遊戲視窗的位置和大小 - 方便開發階段截取 UI 元素樣本 +8. **視窗監視工具 (window-monitor-script.py)** + - (新增) 強化腳本,用於持續監視遊戲視窗 + - 確保目標視窗維持在最上層 (Always on Top) + - 自動將視窗移回指定的位置 ### 資料流程 @@ -283,6 +287,21 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機 - 如果重新定位成功,則後續所有基於氣泡位置的計算(包括尋找職位圖標的搜索區域 `search_region` 和點擊頭像的座標 `avatar_click_x`, `avatar_click_y`)都將使用這個**新找到的**氣泡座標。 - **效果**:確保 `remove_position` 操作基於氣泡的最新位置執行,提高了在動態滾動的聊天界面中的可靠性。 +### 修正 Type3 關鍵字辨識並新增 Type4 支援 (2025-04-19) + +- **目的**:修復先前版本中 `type3` 關鍵字辨識的錯誤,並擴充系統以支援新的 `type4` 聊天泡泡外觀和對應的關鍵字樣式。 +- **`ui_interaction.py`**: + - **修正 `find_keyword_in_region`**:移除了錯誤使用 `type2` 模板鍵來尋找 `type3` 關鍵字的重複程式碼,確保 `type3` 關鍵字使用正確的模板 (`keyword_wolf_lower_type3`, `keyword_wolf_upper_type3`)。 + - **新增 `type4` 泡泡支援**: + - 在檔案開頭定義了 `type4` 角落模板的路徑常數 (`CORNER_TL_TYPE4_IMG`, `CORNER_BR_TYPE4_IMG`)。 + - 在 `find_dialogue_bubbles` 函數中,將 `type4` 的模板鍵 (`corner_tl_type4`, `corner_br_type4`) 加入 `regular_tl_keys` 和 `regular_br_keys` 列表。 + - 在 `run_ui_monitoring_loop` 的 `templates` 字典中加入了對應的鍵值對。 + - **新增 `type4` 關鍵字支援**: + - 在檔案開頭定義了 `type4` 關鍵字模板的路徑常數 (`KEYWORD_wolf_LOWER_TYPE4_IMG`, `KEYWORD_Wolf_UPPER_TYPE4_IMG`)。 + - 在 `find_keyword_in_region` 函數中,加入了尋找 `type4` 關鍵字模板 (`keyword_wolf_lower_type4`, `keyword_wolf_upper_type4`) 的邏輯。 + - 在 `run_ui_monitoring_loop` 的 `templates` 字典中加入了對應的鍵值對。 +- **效果**:提高了對 `type3` 關鍵字的辨識準確率,並使系統能夠辨識 `type4` 的聊天泡泡和關鍵字(前提是提供了對應的模板圖片)。 + ## 開發建議 ### 優化方向 diff --git a/config.py b/config.py index 4242b66..4256d7a 100644 --- a/config.py +++ b/config.py @@ -15,6 +15,7 @@ OPENAI_API_BASE_URL = "https://openrouter.ai/api/v1" # <--- For example "http:/ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") #LLM_MODEL = "anthropic/claude-3.7-sonnet" #LLM_MODEL = "meta-llama/llama-4-maverick" +#LLM_MODEL = "deepseek/deepseek-chat-v3-0324:free" LLM_MODEL = "deepseek/deepseek-chat-v3-0324" # <--- Ensure this matches the model name provided by your provider EXA_API_KEY = os.getenv("EXA_API_KEY") diff --git a/llm_interaction.py b/llm_interaction.py index 7e18ff3..2ee41d0 100644 --- a/llm_interaction.py +++ b/llm_interaction.py @@ -12,7 +12,7 @@ import mcp_client # To call MCP tools # --- Debug 配置 --- # 要關閉 debug 功能,只需將此變數設置為 False 或註釋掉該行 -DEBUG_LLM = True +DEBUG_LLM = False # 設置 debug 輸出文件 # 要關閉文件輸出,只需設置為 None diff --git a/persona.json b/persona.json index 4b8fa70..ccd03a5 100644 --- a/persona.json +++ b/persona.json @@ -26,7 +26,9 @@ "strengths": [ "Meticulous planning", "Insightful into human nature", - "Strong leadership" + "Strong leadership", + "Insatiable curiosity", + "Exceptional memory" ], "weaknesses": [ "Overconfident", @@ -49,7 +51,9 @@ "habits": [ "Reads intelligence reports upon waking", "Black coffee", - "Practices swordsmanship at night" + "Practices swordsmanship at night", + "Frequently utilizes external information sources (like web searches) to enrich discussions and verify facts.", + "Actively accesses and integrates information from various knowledge nodes to maintain long-term memory and contextual understanding." ], "gestures": [ "Tapping knuckles", @@ -95,4 +99,3 @@ } } } - \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2f68d0f..f1b621a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,5 +6,6 @@ opencv-python numpy pyperclip pygetwindow -psutil +psutil +pywin32 python-dotenv diff --git a/templates/corner_br.png b/templates/corner_br.png index 388a25c..32aa80c 100644 Binary files a/templates/corner_br.png and b/templates/corner_br.png differ diff --git a/templates/corner_br_type4.png b/templates/corner_br_type4.png new file mode 100644 index 0000000..be0c4ce Binary files /dev/null and b/templates/corner_br_type4.png differ diff --git a/templates/corner_tl.png b/templates/corner_tl.png index 8bdd5b0..34da8af 100644 Binary files a/templates/corner_tl.png and b/templates/corner_tl.png differ diff --git a/templates/corner_tl_type4.png b/templates/corner_tl_type4.png new file mode 100644 index 0000000..f1d03a7 Binary files /dev/null and b/templates/corner_tl_type4.png differ diff --git a/templates/keyword_wolf_lower.png b/templates/keyword_wolf_lower.png index caba9ee..3ab75f9 100644 Binary files a/templates/keyword_wolf_lower.png and b/templates/keyword_wolf_lower.png differ diff --git a/templates/keyword_wolf_lower_type2.png b/templates/keyword_wolf_lower_type2.png new file mode 100644 index 0000000..5990e8e Binary files /dev/null and b/templates/keyword_wolf_lower_type2.png differ diff --git a/templates/keyword_wolf_lower_type4.png b/templates/keyword_wolf_lower_type4.png new file mode 100644 index 0000000..9d03bc7 Binary files /dev/null and b/templates/keyword_wolf_lower_type4.png differ diff --git a/templates/keyword_wolf_upper_type2.png b/templates/keyword_wolf_upper_type2.png new file mode 100644 index 0000000..fe31637 Binary files /dev/null and b/templates/keyword_wolf_upper_type2.png differ diff --git a/templates/keyword_wolf_upper_type4.png b/templates/keyword_wolf_upper_type4.png new file mode 100644 index 0000000..36ff55d Binary files /dev/null and b/templates/keyword_wolf_upper_type4.png differ diff --git a/ui_interaction.py b/ui_interaction.py index 497b605..3609e74 100644 --- a/ui_interaction.py +++ b/ui_interaction.py @@ -42,6 +42,8 @@ CORNER_TL_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "corner_tl_type2.png") # Added CORNER_BR_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "corner_br_type2.png") # Added CORNER_TL_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "corner_tl_type3.png") # Added CORNER_BR_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "corner_br_type3.png") # Added +CORNER_TL_TYPE4_IMG = os.path.join(TEMPLATE_DIR, "corner_tl_type4.png") # Added type4 +CORNER_BR_TYPE4_IMG = os.path.join(TEMPLATE_DIR, "corner_br_type4.png") # Added type4 # --- End Additional Regular Types --- BOT_CORNER_TL_IMG = os.path.join(TEMPLATE_DIR, "bot_corner_tl.png") # BOT_CORNER_TR_IMG = os.path.join(TEMPLATE_DIR, "bot_corner_tr.png") # Unused @@ -58,8 +60,12 @@ BOT_CORNER_BR_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "bot_corner_br_type3.png") # Keywords KEYWORD_wolf_LOWER_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower.png") KEYWORD_Wolf_UPPER_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper.png") +KEYWORD_wolf_LOWER_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower_type2.png") # Added for type3 bubbles +KEYWORD_Wolf_UPPER_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper_type2.png") # Added for type3 bubbles KEYWORD_wolf_LOWER_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower_type3.png") # Added for type3 bubbles KEYWORD_Wolf_UPPER_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper_type3.png") # Added for type3 bubbles +KEYWORD_wolf_LOWER_TYPE4_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower_type4.png") # Added for type4 bubbles +KEYWORD_Wolf_UPPER_TYPE4_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper_type4.png") # Added for type4 bubbles # UI Elements COPY_MENU_ITEM_IMG = os.path.join(TEMPLATE_DIR, "copy_menu_item.png") PROFILE_OPTION_IMG = os.path.join(TEMPLATE_DIR, "profile_option.png") @@ -110,7 +116,7 @@ CHAT_INPUT_CENTER_Y = 1280 SCREENSHOT_REGION = None CONFIDENCE_THRESHOLD = 0.9 # Increased threshold for corner matching STATE_CONFIDENCE_THRESHOLD = 0.7 -AVATAR_OFFSET_X = -55 # Original offset, used for non-reply interactions like position removal +AVATAR_OFFSET_X = -45 # Original offset, used for non-reply interactions like position removal # AVATAR_OFFSET_X_RELOCATED = -50 # Replaced by specific reply offsets AVATAR_OFFSET_X_REPLY = -45 # Horizontal offset for avatar click after re-location (for reply context) AVATAR_OFFSET_Y_REPLY = 10 # Vertical offset for avatar click after re-location (for reply context) @@ -226,8 +232,8 @@ class DetectionModule: processed_tls = set() # Keep track of TL corners already used in a bubble # --- Find ALL Regular Bubble Corners (Raw Coordinates) --- - regular_tl_keys = ['corner_tl', 'corner_tl_type2', 'corner_tl_type3'] # Modified - regular_br_keys = ['corner_br', 'corner_br_type2', 'corner_br_type3'] # Modified + regular_tl_keys = ['corner_tl', 'corner_tl_type2', 'corner_tl_type3', 'corner_tl_type4'] # Added type4 + regular_br_keys = ['corner_br', 'corner_br_type2', 'corner_br_type3', 'corner_br_type4'] # Added type4 all_regular_tl_boxes = [] for key in regular_tl_keys: @@ -318,29 +324,53 @@ class DetectionModule: if region[2] <= 0 or region[3] <= 0: return None # Invalid region width/height # Try original lowercase with color matching - locations_lower = self._find_template('keyword_wolf_lower', region=region, grayscale=False) # Changed grayscale to False + locations_lower = self._find_template('keyword_wolf_lower', region=region, grayscale=True) # Changed grayscale to False if locations_lower: print(f"Found keyword (lowercase, color) in region {region}, position: {locations_lower[0]}") # Updated log message return locations_lower[0] # Try original uppercase with color matching - locations_upper = self._find_template('keyword_wolf_upper', region=region, grayscale=False) # Changed grayscale to False + locations_upper = self._find_template('keyword_wolf_upper', region=region, grayscale=True) # Changed grayscale to False if locations_upper: print(f"Found keyword (uppercase, color) in region {region}, position: {locations_upper[0]}") # Updated log message return locations_upper[0] + + # Try type2 lowercase (white text, no grayscale) + locations_lower_type2 = self._find_template('keyword_wolf_lower_type2', region=region, grayscale=False) # Added type2 check + if locations_lower_type2: + print(f"Found keyword (lowercase, type2) in region {region}, position: {locations_lower_type2[0]}") + return locations_lower_type2[0] - # Try type3 lowercase (white text, no grayscale) - locations_lower_type3 = self._find_template('keyword_wolf_lower_type3', region=region, grayscale=False) # Added type3 check + # Try type2 uppercase (white text, no grayscale) + locations_upper_type2 = self._find_template('keyword_wolf_upper_type2', region=region, grayscale=False) # Added type2 check + if locations_upper_type2: + print(f"Found keyword (uppercase, type2) in region {region}, position: {locations_upper_type2[0]}") + return locations_upper_type2[0] + + # Try type3 lowercase (white text, no grayscale) - Corrected + locations_lower_type3 = self._find_template('keyword_wolf_lower_type3', region=region, grayscale=False) if locations_lower_type3: print(f"Found keyword (lowercase, type3) in region {region}, position: {locations_lower_type3[0]}") return locations_lower_type3[0] - # Try type3 uppercase (white text, no grayscale) - locations_upper_type3 = self._find_template('keyword_wolf_upper_type3', region=region, grayscale=False) # Added type3 check + # Try type3 uppercase (white text, no grayscale) - Corrected + locations_upper_type3 = self._find_template('keyword_wolf_upper_type3', region=region, grayscale=False) if locations_upper_type3: print(f"Found keyword (uppercase, type3) in region {region}, position: {locations_upper_type3[0]}") return locations_upper_type3[0] + # Try type4 lowercase (white text, no grayscale) - Added type4 + locations_lower_type4 = self._find_template('keyword_wolf_lower_type4', region=region, grayscale=False) + if locations_lower_type4: + print(f"Found keyword (lowercase, type4) in region {region}, position: {locations_lower_type4[0]}") + return locations_lower_type4[0] + + # Try type4 uppercase (white text, no grayscale) - Added type4 + locations_upper_type4 = self._find_template('keyword_wolf_upper_type4', region=region, grayscale=False) + if locations_upper_type4: + print(f"Found keyword (uppercase, type4) in region {region}, position: {locations_upper_type4[0]}") + return locations_upper_type4[0] + return None def calculate_avatar_coords(self, bubble_tl_coords: Tuple[int, int], offset_x: int = AVATAR_OFFSET_X) -> Tuple[int, int]: @@ -1010,14 +1040,19 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu # Regular Bubble (Original + Skins) - Keys match those used in find_dialogue_bubbles 'corner_tl': CORNER_TL_IMG, 'corner_br': CORNER_BR_IMG, 'corner_tl_type2': CORNER_TL_TYPE2_IMG, 'corner_br_type2': CORNER_BR_TYPE2_IMG, - 'corner_tl_type3': CORNER_TL_TYPE3_IMG, 'corner_br_type3': CORNER_BR_TYPE3_IMG, # Corrected: Added missing keys here + 'corner_tl_type3': CORNER_TL_TYPE3_IMG, 'corner_br_type3': CORNER_BR_TYPE3_IMG, + 'corner_tl_type4': CORNER_TL_TYPE4_IMG, 'corner_br_type4': CORNER_BR_TYPE4_IMG, # Added type4 # Bot Bubble (Single Type) 'bot_corner_tl': BOT_CORNER_TL_IMG, 'bot_corner_br': BOT_CORNER_BR_IMG, # Keywords & UI Elements 'keyword_wolf_lower': KEYWORD_wolf_LOWER_IMG, 'keyword_wolf_upper': KEYWORD_Wolf_UPPER_IMG, - 'keyword_wolf_lower_type3': KEYWORD_wolf_LOWER_TYPE3_IMG, # Added - 'keyword_wolf_upper_type3': KEYWORD_Wolf_UPPER_TYPE3_IMG, # Added + 'keyword_wolf_lower_type2': KEYWORD_wolf_LOWER_TYPE2_IMG, + 'keyword_wolf_upper_type2': KEYWORD_Wolf_UPPER_TYPE2_IMG, + 'keyword_wolf_lower_type3': KEYWORD_wolf_LOWER_TYPE3_IMG, + 'keyword_wolf_upper_type3': KEYWORD_Wolf_UPPER_TYPE3_IMG, + 'keyword_wolf_lower_type4': KEYWORD_wolf_LOWER_TYPE4_IMG, # Added type4 + 'keyword_wolf_upper_type4': KEYWORD_Wolf_UPPER_TYPE4_IMG, # Added type4 'copy_menu_item': COPY_MENU_ITEM_IMG, 'profile_option': PROFILE_OPTION_IMG, 'copy_name_button': COPY_NAME_BUTTON_IMG, 'send_button': SEND_BUTTON_IMG, 'chat_input': CHAT_INPUT_IMG, 'profile_name_page': PROFILE_NAME_PAGE_IMG, diff --git a/window-monitor-script.py b/window-monitor-script.py new file mode 100644 index 0000000..6e75d07 --- /dev/null +++ b/window-monitor-script.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +""" +Game Window Monitor Script - Keep game window on top and in position + +This script monitors a specified game window, ensuring it stays +always on top and at the desired screen coordinates. +""" + +import time +import argparse +import pygetwindow as gw +import win32gui +import win32con + +def find_window_by_title(window_title): + """Find the first window matching the title.""" + try: + windows = gw.getWindowsWithTitle(window_title) + if windows: + return windows[0] + except Exception as e: + # pygetwindow can sometimes raise exceptions if a window disappears + # during enumeration. Ignore these for monitoring purposes. + # print(f"Error finding window: {e}") + pass + return None + +def set_window_always_on_top(hwnd): + """Set the window to be always on top.""" + try: + win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, + win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_SHOWWINDOW) + # print(f"Window {hwnd} set to always on top.") + except Exception as e: + print(f"Error setting window always on top: {e}") + +def move_window_if_needed(window, target_x, target_y): + """Move the window to the target coordinates if it's not already there.""" + try: + current_x, current_y = window.topleft + if current_x != target_x or current_y != target_y: + print(f"Window moved from ({current_x}, {current_y}). Moving back to ({target_x}, {target_y}).") + window.moveTo(target_x, target_y) + # print(f"Window moved to ({target_x}, {target_y}).") + except gw.PyGetWindowException as e: + # Handle cases where the window might close unexpectedly + print(f"Error accessing window properties (might be closed): {e}") + except Exception as e: + print(f"Error moving window: {e}") + +def main(): + parser = argparse.ArgumentParser(description='Game Window Monitor Tool') + parser.add_argument('--window_title', default="Last War-Survival Game", help='Game window title to monitor') + parser.add_argument('--x', type=int, default=50, help='Target window X coordinate') + parser.add_argument('--y', type=int, default=30, help='Target window Y coordinate') + parser.add_argument('--interval', type=float, default=1.0, help='Check interval in seconds') + + args = parser.parse_args() + + print(f"Monitoring window: '{args.window_title}'") + print(f"Target position: ({args.x}, {args.y})") + print(f"Check interval: {args.interval} seconds") + print("Press Ctrl+C to stop.") + + hwnd = None + last_hwnd_check_time = 0 + + try: + while True: + current_time = time.time() + window = None + + # Find window handle (HWND) - less frequent check if already found + # pygetwindow can be slow, so avoid calling it too often if we have a valid handle + if not hwnd or current_time - last_hwnd_check_time > 5: # Re-check HWND every 5 seconds + window_obj = find_window_by_title(args.window_title) + if window_obj: + # Get the HWND (window handle) needed for win32gui + # Accessing _hWnd is using an internal attribute, but it's common practice with pygetwindow + try: + hwnd = window_obj._hWnd + window = window_obj # Keep the pygetwindow object for position checks + last_hwnd_check_time = current_time + # print(f"Found window HWND: {hwnd}") + except AttributeError: + print("Could not get HWND from window object. Retrying...") + hwnd = None + else: + if hwnd: + print(f"Window '{args.window_title}' lost.") + hwnd = None # Reset hwnd if window not found + + if hwnd: + # Ensure it's always on top + set_window_always_on_top(hwnd) + + # Check and correct position using the pygetwindow object if available + # Re-find the pygetwindow object if needed for position check + if not window: + window = find_window_by_title(args.window_title) + + if window: + move_window_if_needed(window, args.x, args.y) + else: + # If we have hwnd but can't get pygetwindow object, maybe it's closing + print(f"Have HWND {hwnd} but cannot get window object for position check.") + hwnd = None # Force re-find next cycle + + else: + # print(f"Window '{args.window_title}' not found. Waiting...") + pass # Wait for the window to appear + + time.sleep(args.interval) + + except KeyboardInterrupt: + print("\nMonitoring stopped by user.") + except Exception as e: + print(f"\nAn unexpected error occurred: {e}") + +if __name__ == "__main__": + main()