commit 1c4975c25124bc4a3e54134ef581253fa87c3ee0 Author: Buds Date: Sat Nov 1 20:01:44 2025 +0100 first commit diff --git a/findbase.py b/findbase.py new file mode 100644 index 0000000..8133480 --- /dev/null +++ b/findbase.py @@ -0,0 +1,1959 @@ +#!/usr/bin/env python + +import cv2 +import numpy as np +import pygetwindow as gw +import tkinter as tk +from tkinter import messagebox, filedialog, simpledialog +from PIL import Image, ImageGrab, ImageTk +import threading +import time +import pickle +import os +from concurrent.futures import ThreadPoolExecutor +import json + +class TemplatePresetManager: + """Gestionnaire de presets de templates avec sauvegarde/chargement""" + + def __init__(self, presets_file="lastwar_presets.json"): + self.presets_file = presets_file + self.presets = {} + self.current_preset = None + self.load_presets() + + def load_presets(self): + try: + if os.path.exists(self.presets_file): + with open(self.presets_file, 'r', encoding='utf-8') as f: + preset_list = json.load(f) + + self.presets = {} + for preset_info in preset_list: + name = preset_info['name'] + pkl_file = preset_info['file'] + + if os.path.exists(pkl_file): + with open(pkl_file, 'rb') as pf: + templates = pickle.load(pf) + self.presets[name] = { + 'templates': templates, + 'file': pkl_file + } + + print(f"Presets charges: {len(self.presets)}") + return True + except Exception as e: + print(f"Erreur chargement presets: {e}") + return False + + def save_presets(self): + try: + preset_list = [] + for name, data in self.presets.items(): + preset_list.append({ + 'name': name, + 'file': data['file'] + }) + + with open(self.presets_file, 'w', encoding='utf-8') as f: + json.dump(preset_list, f, indent=2, ensure_ascii=False) + + return True + except Exception as e: + print(f"Erreur sauvegarde presets: {e}") + return False + + def add_preset(self, name, templates): + safe_name = "".join(c if c.isalnum() or c in (' ', '_', '-') else '_' for c in name) + pkl_file = f"preset_{safe_name}.pkl" + + try: + with open(pkl_file, 'wb') as f: + pickle.dump(templates, f) + + self.presets[name] = { + 'templates': templates, + 'file': pkl_file + } + + self.save_presets() + print(f"Preset '{name}' cree avec {len(templates)} templates") + return True + except Exception as e: + print(f"Erreur creation preset: {e}") + return False + + def delete_preset(self, name): + if name in self.presets: + try: + pkl_file = self.presets[name]['file'] + if os.path.exists(pkl_file): + os.remove(pkl_file) + + del self.presets[name] + self.save_presets() + print(f"Preset '{name}' supprime") + return True + except Exception as e: + print(f"Erreur suppression preset: {e}") + return False + + def rename_preset(self, old_name, new_name): + if old_name in self.presets and new_name not in self.presets: + self.presets[new_name] = self.presets.pop(old_name) + self.save_presets() + print(f"Preset renomme: '{old_name}' -> '{new_name}'") + return True + return False + + def get_preset_names(self): + return list(self.presets.keys()) + + def load_preset(self, name): + if name in self.presets: + self.current_preset = name + return self.presets[name]['templates'] + return None + + +class GameOverlay: + def __init__(self, window_title="Last War"): + self.window_title = window_title + self.target_window = None + self.running = False + self.overlay_root = None + self.canvas = None + self.overlay_created = False + + self.preset_manager = TemplatePresetManager() + + self.captured_templates = [] + self.mode = "setup" + self.settings_file = "lastwar_settings.pkl" + + self.current_threshold = 0.85 + self.threshold_step = 0.02 + self.min_threshold = 0.70 + self.max_threshold = 0.98 + + self.capture_window = None + self.capture_canvas = None + + self.control_window = None + self.main_menu = None + + self.template_mode = "normal" + self.debug_matches = [] + + self.last_positions = [] + self.frame_skip = 0 + self.max_frame_skip = 1 + + self.executor = ThreadPoolExecutor(max_workers=3) + + # ========== MENU PRINCIPAL ========== + + def create_main_menu(self): + """Menu principal simplifie""" + self.main_menu = tk.Tk() + self.main_menu.title("LastWar - Gestionnaire de Templates") + self.main_menu.geometry("500x600") + self.main_menu.resizable(False, False) + + # Titre + title = tk.Label(self.main_menu, + text="LastWar - Gestionnaire de Templates", + font=("Arial", 16, "bold"), + fg="darkblue") + title.pack(pady=15) + + # Section Presets + preset_frame = tk.LabelFrame(self.main_menu, + text="PRESETS DE TEMPLATES", + font=("Arial", 12, "bold"), + padx=10, pady=10) + preset_frame.pack(fill="both", expand=True, padx=20, pady=10) + + # Liste des presets + list_container = tk.Frame(preset_frame) + list_container.pack(fill="both", expand=True, pady=5) + + scrollbar = tk.Scrollbar(list_container) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + self.preset_listbox = tk.Listbox(list_container, + yscrollcommand=scrollbar.set, + font=("Arial", 11), + height=10) + self.preset_listbox.pack(side=tk.LEFT, fill="both", expand=True) + scrollbar.config(command=self.preset_listbox.yview) + + self.refresh_preset_list() + + # Info preset actuel + self.current_preset_label = tk.Label(preset_frame, + text=f"Preset actuel: {self.preset_manager.current_preset or 'Aucun'}", + font=("Arial", 10, "italic"), + fg="gray") + self.current_preset_label.pack(pady=5) + + # Boutons de gestion des presets + btn_frame = tk.Frame(preset_frame) + btn_frame.pack(pady=10) + + tk.Button(btn_frame, text="Creer Nouveau Preset", + command=self.create_new_preset, + bg="green", fg="white", font=("Arial", 10, "bold"), + width=20, height=2).grid(row=0, column=0, padx=5, pady=2) + + tk.Button(btn_frame, text="Charger & Demarrer", + command=self.load_and_start_preset, + bg="blue", fg="white", font=("Arial", 10, "bold"), + width=20, height=2).grid(row=0, column=1, padx=5, pady=2) + + tk.Button(btn_frame, text="Editer Preset", + command=self.edit_selected_preset, + bg="orange", fg="white", font=("Arial", 9), + width=20).grid(row=1, column=0, padx=5, pady=2) + + tk.Button(btn_frame, text="Supprimer Preset", + command=self.delete_selected_preset, + bg="red", fg="white", font=("Arial", 9), + width=20).grid(row=1, column=1, padx=5, pady=2) + + tk.Button(btn_frame, text="Renommer Preset", + command=self.rename_selected_preset, + bg="purple", fg="white", font=("Arial", 9), + width=20).grid(row=2, column=0, columnspan=2, padx=5, pady=2) + + # Bouton quitter + tk.Button(self.main_menu, text="Quitter", + command=self.quit_application, + bg="gray", fg="white", font=("Arial", 10), + width=20).pack(pady=10) + + self.main_menu.protocol("WM_DELETE_WINDOW", self.quit_application) + self.main_menu.mainloop() + + def refresh_preset_list(self): + """Rafraichit la liste des presets""" + self.preset_listbox.delete(0, tk.END) + for name in self.preset_manager.get_preset_names(): + templates_count = len(self.preset_manager.presets[name]['templates']) + self.preset_listbox.insert(tk.END, f"{name} ({templates_count} templates)") + + def create_new_preset(self): + """Cree un nouveau preset""" + name = simpledialog.askstring("Nouveau Preset", + "Nom du nouveau preset:", + parent=self.main_menu) + if not name: + return + + if name in self.preset_manager.presets: + messagebox.showerror("Erreur", f"Le preset '{name}' existe deja") + return + + # Cacher le menu principal + self.main_menu.withdraw() + + # Lancer la capture + self.captured_templates = [] + if self.create_capture_interface(): + if hasattr(self, 'capture_window') and self.capture_window: + self.capture_window.wait_window() + + # Verifier le resultat + if self.captured_templates and len(self.captured_templates) >= 2: + # Sauvegarder comme nouveau preset + if self.preset_manager.add_preset(name, self.captured_templates): + self.refresh_preset_list() + + # Reafficher le menu + self.main_menu.deiconify() + + def load_and_start_preset(self): + """Charge le preset selectionne et demarre la detection""" + selection = self.preset_listbox.curselection() + if not selection: + messagebox.showwarning("Aucune selection", "Selectionnez un preset") + return + + preset_name = self.preset_listbox.get(selection[0]).split(" (")[0] + templates = self.preset_manager.load_preset(preset_name) + + if templates: + self.captured_templates = templates + for template in self.captured_templates: + template['strict_threshold'] = self.current_threshold + + # Fermer le menu principal + self.main_menu.destroy() + self.main_menu = None + + # Demarrer la detection + self.mode = "detection" + self.start_overlay_detection() + + def edit_selected_preset(self): + """Edite le preset selectionne""" + selection = self.preset_listbox.curselection() + if not selection: + messagebox.showwarning("Aucune selection", "Selectionnez un preset") + return + + preset_name = self.preset_listbox.get(selection[0]).split(" (")[0] + templates = self.preset_manager.load_preset(preset_name) + + if templates: + self.captured_templates = templates + + # Cacher le menu + self.main_menu.withdraw() + + # Ouvrir l'editeur + self.open_template_editor(preset_name) + + def delete_selected_preset(self): + """Supprime le preset selectionne""" + selection = self.preset_listbox.curselection() + if not selection: + messagebox.showwarning("Aucune selection", "Selectionnez un preset") + return + + preset_name = self.preset_listbox.get(selection[0]).split(" (")[0] + + if messagebox.askyesno("Confirmer", f"Supprimer le preset '{preset_name}' ?"): + if self.preset_manager.delete_preset(preset_name): + self.refresh_preset_list() + + def rename_selected_preset(self): + """Renomme le preset selectionne""" + selection = self.preset_listbox.curselection() + if not selection: + messagebox.showwarning("Aucune selection", "Selectionnez un preset") + return + + old_name = self.preset_listbox.get(selection[0]).split(" (")[0] + new_name = simpledialog.askstring("Renommer", + f"Nouveau nom pour '{old_name}':", + parent=self.main_menu) + + if new_name and new_name != old_name: + if self.preset_manager.rename_preset(old_name, new_name): + self.refresh_preset_list() + + # ========== EDITEUR DE TEMPLATES ========== + + def open_template_editor(self, preset_name): + """Ouvre l'editeur de templates pour un preset""" + editor = tk.Toplevel() + editor.title(f"Edition: {preset_name}") + editor.geometry("400x600") + + # Titre + tk.Label(editor, text=f"Edition: {preset_name}", + font=("Arial", 14, "bold")).pack(pady=10) + + # Liste des templates + list_frame = tk.LabelFrame(editor, text="Templates", font=("Arial", 10, "bold")) + list_frame.pack(fill="both", expand=True, padx=10, pady=10) + + template_list = tk.Listbox(list_frame, font=("Arial", 10), height=15) + template_list.pack(fill="both", expand=True, padx=5, pady=5) + + def refresh_template_list(): + template_list.delete(0, tk.END) + for i, tmpl in enumerate(self.captured_templates): + size = tmpl.get('size', (0, 0)) + template_list.insert(tk.END, f"Template {i}: {size[0]}x{size[1]}") + + refresh_template_list() + + # Boutons d'action + btn_frame = tk.Frame(editor) + btn_frame.pack(pady=10) + + def add_template(): + editor.withdraw() + self.create_template_capture_window() + # Attendre la fermeture + editor.after(100, lambda: check_capture_done(editor)) + + def check_capture_done(ed): + # Verifier periodiquement si la capture est terminee + if hasattr(self, 'capture_window') and self.capture_window and self.capture_window.winfo_exists(): + ed.after(100, lambda: check_capture_done(ed)) + else: + refresh_template_list() + ed.deiconify() + + def delete_template(): + sel = template_list.curselection() + if sel: + idx = sel[0] + if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"): + self.captured_templates.pop(idx) + refresh_template_list() + + def save_changes(): + if self.preset_manager.add_preset(preset_name, self.captured_templates): + editor.destroy() + self.main_menu.deiconify() + self.refresh_preset_list() + + def cancel_edit(): + editor.destroy() + self.main_menu.deiconify() + + tk.Button(btn_frame, text="Ajouter Template", + command=add_template, + bg="green", fg="white", width=18).pack(pady=2) + + tk.Button(btn_frame, text="Supprimer Template", + command=delete_template, + bg="red", fg="white", width=18).pack(pady=2) + + tk.Button(btn_frame, text="Sauvegarder", + command=save_changes, + bg="blue", fg="white", width=18, + font=("Arial", 10, "bold")).pack(pady=10) + + tk.Button(btn_frame, text="Annuler", + command=cancel_edit, + bg="gray", fg="white", width=18).pack(pady=2) + + # ========== CAPTURE DE TEMPLATES ========== + + def create_capture_interface(self): + """Overlay transparent pour capturer des templates""" + try: + print("Mode capture - Cliquez sur le jeu pour selectionner") + print(" Entree = Valider | Echap = Annuler") + + self.capture_window = tk.Toplevel() + self.capture_window.title("CAPTURE TEMPLATES") + self.capture_window.attributes("-topmost", True) + self.capture_window.attributes("-alpha", 0.3) + + self.capture_window.geometry(f"{self.target_window.width}x{self.target_window.height}+{self.target_window.left}+{self.target_window.top}") + self.capture_window.overrideredirect(True) + + self.capture_canvas = tk.Canvas(self.capture_window, + width=self.target_window.width, + height=self.target_window.height, + highlightthickness=0, + bg='gray') + self.capture_canvas.pack() + + self.capture_canvas.create_text( + self.target_window.width // 2, 30, + text="CLIQUEZ SUR LES TEMPLATES (2 minimum)\nEntree = Valider | Echap = Annuler", + fill="yellow", + font=("Arial", 14, "bold"), + justify=tk.CENTER, + tags="instructions" + ) + + self.counter_text = self.capture_canvas.create_text( + self.target_window.width // 2, 70, + text="Templates: 0/2 minimum", + fill="white", + font=("Arial", 12, "bold"), + tags="counter" + ) + + def on_click(event): + if len(self.captured_templates) >= 8: + return + + image = self.capture_window_fast(self.target_window) + if image is None: + return + + x, y = event.x, event.y + + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + size = 24 + x1 = max(0, x - size//2) + y1 = max(0, y - size//2) + x2 = min(gray.shape[1], x + size//2) + y2 = min(gray.shape[0], y + size//2) + + template = gray[y1:y2, x1:x2] + + if template.size > 100: + self.captured_templates.append({ + 'template': template, + 'size': (x2-x1, y2-y1), + 'position': (x, y), + 'strict_threshold': self.current_threshold + }) + + self.capture_canvas.create_rectangle( + x - size//2, y - size//2, + x + size//2, y + size//2, + outline="green", + width=3, + tags="selection" + ) + self.capture_canvas.create_text( + x, y, + text=str(len(self.captured_templates)), + fill="green", + font=("Arial", 16, "bold"), + tags="selection" + ) + + count = len(self.captured_templates) + color = "lime" if count >= 2 else "white" + self.capture_canvas.itemconfig(self.counter_text, + text=f"Templates: {count}/2 minimum (max 8)", + fill=color) + + def validate(): + if len(self.captured_templates) >= 2: + self.capture_window.destroy() + else: + print("Minimum 2 templates requis") + + def cancel(): + self.captured_templates = [] + self.capture_window.destroy() + + self.capture_canvas.bind("", on_click) + self.capture_window.bind("", lambda e: validate()) + self.capture_window.bind("", lambda e: cancel()) + + self.capture_window.focus_set() + + return True + + except Exception as e: + print(f"Erreur creation interface: {e}") + return False + + def create_template_capture_window(self): + """Overlay pour ajouter UN template (mode edition) avec detection visible""" + try: + temp_overlay = tk.Toplevel() + temp_overlay.title("AJOUT TEMPLATE") + temp_overlay.attributes("-topmost", True) + temp_overlay.attributes("-alpha", 0.3) + + temp_overlay.geometry(f"{self.target_window.width}x{self.target_window.height}+{self.target_window.left}+{self.target_window.top}") + temp_overlay.overrideredirect(True) + + canvas = tk.Canvas(temp_overlay, + width=self.target_window.width, + height=self.target_window.height, + highlightthickness=0, + bg='gray') + canvas.pack() + + canvas.create_text( + self.target_window.width // 2, 30, + text="CLIQUEZ POUR AJOUTER UN TEMPLATE\nEchap pour annuler", + fill="yellow", + font=("Arial", 16, "bold"), + justify=tk.CENTER + ) + + # Variable pour suivre l'etat + is_active = [True] + + def update_detections(): + """Met a jour l'affichage des detections existantes""" + if not is_active[0]: + return + + try: + # Capturer l'image actuelle + image = self.capture_window_fast(self.target_window) + if image is not None: + # Detecter avec les templates existants + positions = self.find_matches_with_templates(image) + + # Effacer les anciennes detections + canvas.delete("detection") + + # Afficher les detections en rouge + for x, y, w, h in positions: + margin = 4 + x1 = max(0, x - margin) + y1 = max(0, y - margin) + x2 = x + w + margin + y2 = y + h + margin + + canvas.create_rectangle( + x1, y1, x2, y2, + outline="#FF0000", + width=2, + fill="", + tags="detection" + ) + + # Continuer a mettre a jour + if is_active[0]: + temp_overlay.after(100, update_detections) + + except: + pass + + def on_click(event): + image = self.capture_window_fast(self.target_window) + if image is None: + return + + x, y = event.x, event.y + + if self.add_template_at_position(image, x, y): + canvas.create_oval(x-30, y-30, x+30, y+30, + outline="green", width=4) + canvas.create_text(x, y, text="OK", + fill="green", font=("Arial", 32, "bold")) + is_active[0] = False + temp_overlay.after(1000, temp_overlay.destroy) + else: + canvas.create_text(x, y, text="X", + fill="red", font=("Arial", 32, "bold")) + + def on_close(): + is_active[0] = False + temp_overlay.destroy() + + canvas.bind("", on_click) + temp_overlay.bind("", lambda e: on_close()) + temp_overlay.focus_set() + + # Demarrer la mise a jour des detections + update_detections() + + except Exception as e: + print(f"Erreur overlay capture: {e}") + + def add_template_at_position(self, image, x, y): + """Ajoute un template a la position specifiee""" + try: + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + size = 24 + x1 = max(0, x - size//2) + y1 = max(0, y - size//2) + x2 = min(gray.shape[1], x + size//2) + y2 = min(gray.shape[0], y + size//2) + + template = gray[y1:y2, x1:x2] + + if template.size > 100: + self.captured_templates.append({ + 'template': template, + 'size': (x2-x1, y2-y1), + 'position': (x, y), + 'strict_threshold': self.current_threshold + }) + print(f"Template ajoute ({x2-x1}x{y2-y1})") + + # Sauvegarder et rafraîchir l'affichage + if self.preset_manager.current_preset: + self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) + + # Rafraîchir la liste dans la fenêtre de contrôle + if hasattr(self, 'refresh_templates_display'): + try: + self.refresh_templates_display() + except: + pass + + return True + return False + except Exception as e: + print(f"Erreur ajout template: {e}") + return False + + # ========== UTILITAIRES ========== + + def find_game_window(self): + try: + windows = gw.getWindowsWithTitle("Last War-Survival Game") + if windows: + return windows[0] + + all_windows = gw.getAllWindows() + for window in all_windows: + if "last war" in window.title.lower(): + return window + return None + except: + return None + + def capture_window_fast(self, window): + try: + left, top, width, height = window.left, window.top, window.width, window.height + if width <= 0 or height <= 0: + return None + current_bbox = (left, top, left + width, top + height) + screenshot = ImageGrab.grab(current_bbox, all_screens=False) + return np.array(screenshot) + except: + return None + + def save_settings(self): + try: + settings = {'threshold': self.current_threshold, 'version': '1.0'} + with open(self.settings_file, 'wb') as f: + pickle.dump(settings, f) + return True + except: + return False + + def load_settings(self): + try: + if os.path.exists(self.settings_file): + with open(self.settings_file, 'rb') as f: + settings = pickle.load(f) + self.current_threshold = settings.get('threshold', 0.85) + return True + return False + except: + return False + + def adjust_threshold_up(self): + if self.current_threshold < self.max_threshold: + self.current_threshold = min(self.max_threshold, self.current_threshold + self.threshold_step) + for template in self.captured_templates: + template['strict_threshold'] = self.current_threshold + print(f"Seuil: {self.current_threshold:.2f} (plus strict)") + self.update_threshold_display() + self.save_settings() + + def adjust_threshold_down(self): + if self.current_threshold > self.min_threshold: + self.current_threshold = max(self.min_threshold, self.current_threshold - self.threshold_step) + for template in self.captured_templates: + template['strict_threshold'] = self.current_threshold + print(f"Seuil: {self.current_threshold:.2f} (moins strict)") + self.update_threshold_display() + self.save_settings() + + def reset_threshold(self): + self.current_threshold = 0.85 + for template in self.captured_templates: + template['strict_threshold'] = self.current_threshold + print(f"Seuil: {self.current_threshold:.2f}") + self.update_threshold_display() + self.save_settings() + + # ========== DETECTION ========== + + def find_matches_single_template(self, gray_image, template_data, template_id): + try: + template = template_data['template'] + threshold = template_data.get('strict_threshold', self.current_threshold) + + if template.size == 0: + return [] + + result = cv2.matchTemplate(gray_image, template, cv2.TM_CCOEFF_NORMED) + locations = np.where(result >= threshold) + + matches = [] + for pt in zip(*locations[::-1]): + x, y = pt + h, w = template.shape + score = result[y, x] + + if score >= threshold: + matches.append({ + 'x': x, 'y': y, 'w': w, 'h': h, + 'score': score, 'template_id': template_id + }) + + return matches + except Exception as e: + return [] + + def find_matches_with_templates(self, image): + positions = [] + + if not self.captured_templates: + return positions + + try: + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + + futures = [] + for i, template_data in enumerate(self.captured_templates): + future = self.executor.submit( + self.find_matches_single_template, + gray, template_data, i + ) + futures.append(future) + + all_matches = [] + for future in futures: + try: + matches = future.result(timeout=0.1) + all_matches.extend(matches) + except: + continue + + all_matches.sort(key=lambda m: m['score'], reverse=True) + + template_counts = {} + debug_matches = [] + + for match in all_matches: + x, y, w, h = match['x'], match['y'], match['w'], match['h'] + score = match['score'] + template_id = match['template_id'] + + if template_counts.get(template_id, 0) >= 15: + continue + + is_duplicate = False + for existing_x, existing_y, _, _ in positions: + if abs(x - existing_x) < 15 and abs(y - existing_y) < 10: + is_duplicate = True + break + + if not is_duplicate and score >= self.current_threshold: + positions.append((x, y, w, h)) + debug_matches.append((x, y, w, h, template_id, score)) + template_counts[template_id] = template_counts.get(template_id, 0) + 1 + + if len(positions) >= 25: + break + + self.debug_matches = debug_matches + + except Exception as e: + print(f"Erreur matching: {e}") + + return positions + + def toggle_debug_mode(self): + if self.template_mode == "debug": + self.template_mode = "normal" + print("Mode debug desactive") + else: + self.template_mode = "debug" + print("Mode debug active") + self.update_control_interface() + + def update_overlay(self, positions): + if not self.canvas or not self.overlay_root or not self.running: + return + + try: + if not self.overlay_root.winfo_exists(): + return + + if positions != self.last_positions: + self.canvas.delete("detection") + + if self.template_mode == "debug" and hasattr(self, 'debug_matches'): + for x, y, w, h, template_id, score in self.debug_matches: + margin = 4 + x1 = max(0, x - margin) + y1 = max(0, y - margin) + x2 = x + w + margin + y2 = y + h + margin + + colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FFA500", "#800080"] + color = colors[template_id % len(colors)] + + self.canvas.create_rectangle( + x1, y1, x2, y2, + outline=color, + width=2, + tags="detection" + ) + + self.canvas.create_text( + x + w//2, y + h//2, + text=f"T{template_id}", + fill=color, + font=("Arial", 10, "bold"), + tags="detection" + ) + + self.canvas.create_text( + x + w//2, y + h + 15, + text=f"{score:.2f}", + fill=color, + font=("Arial", 8), + tags="detection" + ) + else: + for x, y, w, h in positions: + margin = 4 + x1 = max(0, x - margin) + y1 = max(0, y - margin) + x2 = x + w + margin + y2 = y + h + margin + + self.canvas.create_rectangle( + x1, y1, x2, y2, + outline="#FF0000", + width=2, + fill="", + tags="detection" + ) + + self.last_positions = positions.copy() + + except (tk.TclError, RuntimeError): + self.running = False + + def safe_update_overlay(self, positions): + if self.overlay_root and self.running: + try: + self.overlay_root.after(0, lambda: self.update_overlay(positions)) + except (tk.TclError, RuntimeError): + self.running = False + + def create_overlay(self, window): + try: + self.overlay_root = tk.Toplevel(self.control_window) + self.overlay_root.title("LastWar Detection Active") + + self.overlay_root.attributes("-alpha", 0.85) + self.overlay_root.attributes("-topmost", True) + self.overlay_root.overrideredirect(True) + + self.overlay_root.geometry(f"{window.width}x{window.height}+{window.left}+{window.top}") + + self.canvas = tk.Canvas( + self.overlay_root, + width=window.width, + height=window.height, + highlightthickness=0, + bg='black' + ) + self.canvas.pack() + + self.overlay_root.attributes("-transparentcolor", "black") + self.overlay_root.bind("", lambda e: self.stop()) + self.overlay_root.protocol("WM_DELETE_WINDOW", self.stop) + + self.overlay_created = True + print("Overlay detection actif") + + except Exception as e: + print(f"Erreur overlay: {e}") + self.running = False + + def create_control_window(self): + try: + self.control_window = tk.Toplevel() + self.control_window.title("Controles Detection") + self.control_window.geometry("700x700") + self.control_window.attributes("-topmost", True) + + # Section seuil + seuil_frame = tk.LabelFrame(self.control_window, text="AJUSTEMENT SEUIL", font=("Arial", 10, "bold")) + seuil_frame.pack(fill="x", padx=5, pady=5) + + self.threshold_label = tk.Label(seuil_frame, + text=f"Seuil: {self.current_threshold:.2f}", + font=("Arial", 12, "bold"), + fg="green") + self.threshold_label.pack(pady=5) + + btn_frame = tk.Frame(seuil_frame) + btn_frame.pack(pady=5) + + tk.Button(btn_frame, text="+ STRICT", + command=self.adjust_threshold_up, + bg="red", fg="white", font=("Arial", 9, "bold"), width=12).pack(side=tk.LEFT, padx=2) + + tk.Button(btn_frame, text="- STRICT", + command=self.adjust_threshold_down, + bg="orange", fg="white", font=("Arial", 9, "bold"), width=12).pack(side=tk.LEFT, padx=2) + + tk.Button(seuil_frame, text="RESET SEUIL", command=self.reset_threshold, + bg="blue", fg="white", font=("Arial", 8), width=20).pack(pady=2) + + tk.Label(seuil_frame, text="(Sauvegarde automatique)", + font=("Arial", 8, "italic"), fg="gray").pack() + + # Section templates avec liste directe + template_frame = tk.LabelFrame(self.control_window, text="GESTION TEMPLATES", font=("Arial", 10, "bold")) + template_frame.pack(fill="both", expand=True, padx=5, pady=5) + + # Bouton ajouter en haut + tk.Button(template_frame, text="+ AJOUTER TEMPLATE", + command=self.add_template_during_detection, + bg="green", fg="white", + font=("Arial", 10, "bold"), + width=30).pack(pady=5) + + # Zone scrollable pour la liste des templates + list_frame = tk.Frame(template_frame) + list_frame.pack(fill="both", expand=True, padx=5, pady=5) + + canvas_scroll = tk.Canvas(list_frame) + scrollbar = tk.Scrollbar(list_frame, orient="vertical", command=canvas_scroll.yview) + self.templates_list_frame = tk.Frame(canvas_scroll) + + self.templates_list_frame.bind( + "", + lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) + ) + + canvas_scroll.create_window((0, 0), window=self.templates_list_frame, anchor="nw") + canvas_scroll.configure(yscrollcommand=scrollbar.set) + + canvas_scroll.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + # Stocker les images pour tkinter + self.template_photos = [] + + # Afficher les templates + self.refresh_templates_display() + + # Section mode debug + mode_frame = tk.LabelFrame(self.control_window, text="MODE DEBUG", font=("Arial", 10, "bold")) + mode_frame.pack(fill="x", padx=5, pady=5) + + self.mode_label = tk.Label(mode_frame, + text="Mode Normal", + font=("Arial", 10, "bold"), + fg="blue") + self.mode_label.pack(pady=5) + + tk.Button(mode_frame, text="DEBUG ON/OFF", + command=self.toggle_debug_mode, + bg="orange", fg="white", + font=("Arial", 9, "bold"), + width=20).pack(pady=5) + + # Bouton retour menu + tk.Button(self.control_window, text="<< RETOUR MENU PRINCIPAL", + command=self.return_to_main_menu, + bg="purple", fg="white", + font=("Arial", 10, "bold"), + width=30).pack(pady=10) + + self.control_window.bind("", self.handle_keypress) + self.control_window.focus_set() + self.control_window.protocol("WM_DELETE_WINDOW", self.return_to_main_menu) + + except Exception as e: + print(f"Erreur fenetre controle: {e}") + + def handle_keypress(self, event): + key = event.keysym.lower() + + if key in ['up', 'plus', 'equal']: + self.adjust_threshold_up() + elif key in ['down', 'minus']: + self.adjust_threshold_down() + elif key == 'r': + self.reset_threshold() + elif key == 'a': + self.add_template_during_detection() + elif key == 'd': + self.toggle_debug_mode() + elif key == 'escape': + self.return_to_main_menu() + + def refresh_templates_display(self): + """Rafraichit l'affichage de la liste des templates""" + if not hasattr(self, 'templates_list_frame'): + return + + # Effacer + for widget in self.templates_list_frame.winfo_children(): + widget.destroy() + self.template_photos.clear() + + if not self.captured_templates: + tk.Label(self.templates_list_frame, + text="Aucun template\nUtilisez 'AJOUTER TEMPLATE'", + font=("Arial", 10), + fg="gray").pack(pady=20) + return + + for i, tmpl in enumerate(self.captured_templates): + # Frame pour chaque template + template_frame = tk.Frame(self.templates_list_frame, relief=tk.RIDGE, borderwidth=2, bg="white") + template_frame.pack(fill="x", padx=2, pady=3) + + # Image du template + try: + template_data = tmpl['template'] + scale = 3 + h, w = template_data.shape + template_resized = cv2.resize(template_data, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST) + + if len(template_resized.shape) == 2: + template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) + else: + template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB) + + pil_image = Image.fromarray(template_rgb) + photo = ImageTk.PhotoImage(pil_image) + self.template_photos.append(photo) + + img_label = tk.Label(template_frame, image=photo, bg="white") + img_label.pack(side=tk.LEFT, padx=5, pady=5) + except: + tk.Label(template_frame, text="[Img]", bg="white").pack(side=tk.LEFT, padx=5) + + # Infos + info_frame = tk.Frame(template_frame, bg="white") + info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=5) + + tk.Label(info_frame, text=f"T{i}", + font=("Arial", 10, "bold"), bg="white").pack(anchor="w") + + size = tmpl.get('size', (0, 0)) + tk.Label(info_frame, text=f"{size[0]}x{size[1]}px", + font=("Arial", 8), bg="white").pack(anchor="w") + + # Boutons + btn_frame = tk.Frame(template_frame, bg="white") + btn_frame.pack(side=tk.RIGHT, padx=5) + + tk.Button(btn_frame, text="Recadrer", + command=lambda idx=i: self.recrop_from_control(idx), + bg="orange", fg="white", font=("Arial", 8), width=9).pack(pady=1) + + tk.Button(btn_frame, text="Supprimer", + command=lambda idx=i: self.delete_template_from_control(idx), + bg="red", fg="white", font=("Arial", 8), width=9).pack(pady=1) + + def recrop_from_control(self, idx): + """Recadre depuis la fenetre de controle""" + self.recrop_template_inline(idx, self.refresh_templates_display) + + def delete_template_from_control(self, idx): + """Supprime depuis la fenetre de controle""" + if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"): + self.captured_templates.pop(idx) + self.refresh_templates_display() + if self.preset_manager.current_preset: + self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) + + def add_template_during_detection(self): + """Ajoute un template pendant la detection""" + print("Mode ajout template active...") + self.create_template_capture_window() + + def open_delete_menu(self): + """Gestionnaire visuel complet des templates""" + if not self.captured_templates: + messagebox.showinfo("Aucun template", "Aucun template disponible") + return + + manager_window = tk.Toplevel(self.control_window) + manager_window.title("Gestion des Templates") + manager_window.geometry("700x600") + manager_window.attributes("-topmost", True) + + tk.Label(manager_window, + text="Gestion des Templates", + font=("Arial", 12, "bold")).pack(pady=10) + + # Frame principal avec canvas scrollable + main_frame = tk.Frame(manager_window) + main_frame.pack(fill="both", expand=True, padx=10, pady=5) + + canvas_scroll = tk.Canvas(main_frame) + scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=canvas_scroll.yview) + scrollable_frame = tk.Frame(canvas_scroll) + + scrollable_frame.bind( + "", + lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) + ) + + canvas_scroll.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas_scroll.configure(yscrollcommand=scrollbar.set) + + canvas_scroll.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + # Images de reference pour tkinter + template_images = [] + + def refresh_templates(): + # Effacer + for widget in scrollable_frame.winfo_children(): + widget.destroy() + template_images.clear() + + for i, tmpl in enumerate(self.captured_templates): + # Frame pour chaque template + template_frame = tk.Frame(scrollable_frame, relief=tk.RIDGE, borderwidth=2, bg="white") + template_frame.pack(fill="x", padx=5, pady=5) + + # Image du template (agrandie) + try: + template_data = tmpl['template'] + # Agrandir x4 pour voir les details + scale = 4 + h, w = template_data.shape + new_size = (w * scale, h * scale) + + template_resized = cv2.resize(template_data, new_size, interpolation=cv2.INTER_NEAREST) + + # Convertir pour tkinter + if len(template_resized.shape) == 2: + template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) + else: + template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB) + + pil_image = Image.fromarray(template_rgb) + photo = ImageTk.PhotoImage(pil_image) + template_images.append(photo) + + img_label = tk.Label(template_frame, image=photo, bg="white") + img_label.pack(side=tk.LEFT, padx=10, pady=10) + except Exception as e: + tk.Label(template_frame, text="Erreur image", bg="white").pack(side=tk.LEFT, padx=10) + + # Infos + info_frame = tk.Frame(template_frame, bg="white") + info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=10) + + tk.Label(info_frame, text=f"Template {i}", + font=("Arial", 12, "bold"), bg="white").pack(anchor="w") + + size = tmpl.get('size', (0, 0)) + tk.Label(info_frame, text=f"Taille: {size[0]}x{size[1]} pixels", + font=("Arial", 9), bg="white").pack(anchor="w") + + threshold = tmpl.get('strict_threshold', self.current_threshold) + tk.Label(info_frame, text=f"Seuil: {threshold:.2f}", + font=("Arial", 9), bg="white").pack(anchor="w") + + # Boutons + btn_frame = tk.Frame(template_frame, bg="white") + btn_frame.pack(side=tk.RIGHT, padx=10) + + tk.Button(btn_frame, text="Recadrer", + command=lambda idx=i: recrop_and_refresh(idx), + bg="orange", fg="white", font=("Arial", 9), width=10).pack(pady=2) + + tk.Button(btn_frame, text="Supprimer", + command=lambda idx=i: delete_and_refresh(idx), + bg="red", fg="white", font=("Arial", 9), width=10).pack(pady=2) + + def recrop_and_refresh(idx): + """Recadre et rafraichit l'affichage""" + self.recrop_template_inline(idx, refresh_templates) + + def delete_and_refresh(idx): + if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"): + self.captured_templates.pop(idx) + refresh_templates() + self.update_control_interface() + if self.preset_manager.current_preset: + self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) + + refresh_templates() + + tk.Button(manager_window, text="Fermer", + command=manager_window.destroy, + font=("Arial", 10), width=20).pack(pady=10) + + def recrop_template_inline(self, template_idx, callback_refresh): + """Recadre le template et rafraichit automatiquement""" + if template_idx >= len(self.captured_templates): + return + + try: + current_template = self.captured_templates[template_idx] + template_image = current_template['template'] + + # Fenetre de recadrage + crop_window = tk.Toplevel() + crop_window.title(f"Recadrage Template {template_idx}") + crop_window.geometry("700x600") + crop_window.attributes("-topmost", True) + crop_window.grab_set() # Modale + + tk.Label(crop_window, + text=f"Recadrage Template {template_idx}", + font=("Arial", 14, "bold")).pack(pady=10) + + tk.Label(crop_window, + text="Cliquez et glissez pour tracer le rectangle de selection", + font=("Arial", 10), fg="blue").pack() + + # Agrandir l'image + scale = 8 + h, w = template_image.shape + template_resized = cv2.resize(template_image, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST) + template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) + + # Canvas + canvas_frame = tk.Frame(crop_window, relief=tk.SUNKEN, borderwidth=2) + canvas_frame.pack(pady=10, padx=20) + + canvas = tk.Canvas(canvas_frame, width=w*scale, height=h*scale, bg="white", cursor="cross") + canvas.pack(padx=5, pady=5) + + # Afficher l'image + pil_img = Image.fromarray(template_rgb) + photo = ImageTk.PhotoImage(pil_img) + canvas.create_image(0, 0, anchor=tk.NW, image=photo, tags="image") + canvas.image = photo + + # Variables pour le rectangle + start_x = None + start_y = None + rect_id = None + selection = [None, None, None, None] + + def on_mouse_down(event): + nonlocal start_x, start_y, rect_id + start_x = event.x + start_y = event.y + if rect_id: + canvas.delete(rect_id) + + def on_mouse_drag(event): + nonlocal rect_id + if start_x is not None and start_y is not None: + if rect_id: + canvas.delete(rect_id) + + x = max(0, min(event.x, w*scale)) + y = max(0, min(event.y, h*scale)) + + rect_id = canvas.create_rectangle( + start_x, start_y, x, y, + outline="red", width=2, tags="selection" + ) + + width_px = abs(x - start_x) // scale + height_px = abs(y - start_y) // scale + size_label.config(text=f"Selection: {width_px}x{height_px} pixels") + + def on_mouse_up(event): + nonlocal start_x, start_y + if start_x is not None and start_y is not None: + x = max(0, min(event.x, w*scale)) + y = max(0, min(event.y, h*scale)) + + x1 = min(start_x, x) // scale + y1 = min(start_y, y) // scale + x2 = max(start_x, x) // scale + y2 = max(start_y, y) // scale + + selection[0] = x1 + selection[1] = y1 + selection[2] = x2 + selection[3] = y2 + + width_px = x2 - x1 + height_px = y2 - y1 + size_label.config(text=f"Selection: {width_px}x{height_px} pixels") + + canvas.bind("", on_mouse_down) + canvas.bind("", on_mouse_drag) + canvas.bind("", on_mouse_up) + + # Info + info_frame = tk.Frame(crop_window) + info_frame.pack(pady=5) + + size_label = tk.Label(info_frame, + text="Tracez un rectangle sur l'image", + font=("Arial", 10)) + size_label.pack() + + # Boutons + btn_frame = tk.Frame(crop_window) + btn_frame.pack(pady=15) + + def apply_crop(): + if selection[0] is None: + messagebox.showwarning("Aucune selection", "Tracez d'abord un rectangle") + return + + x1, y1, x2, y2 = selection + + if x2 - x1 < 8 or y2 - y1 < 8: + messagebox.showerror("Selection trop petite", "Minimum 8x8 pixels") + return + + # Extraire le nouveau template + cropped = template_image[y1:y2, x1:x2] + + # Mettre a jour + self.captured_templates[template_idx] = { + 'template': cropped, + 'size': (x2-x1, y2-y1), + 'position': current_template.get('position', (0, 0)), + 'strict_threshold': self.current_threshold + } + + # Sauvegarder + if self.preset_manager.current_preset: + self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) + + # Fermer et rafraichir + crop_window.destroy() + callback_refresh() + + def reset_selection(): + nonlocal rect_id, start_x, start_y + if rect_id: + canvas.delete(rect_id) + rect_id = None + start_x = None + start_y = None + selection[0] = None + selection[1] = None + selection[2] = None + selection[3] = None + size_label.config(text="Tracez un rectangle sur l'image") + + tk.Button(btn_frame, text="Appliquer", + command=apply_crop, + bg="green", fg="white", + font=("Arial", 10, "bold"), + width=12).grid(row=0, column=0, padx=5) + + tk.Button(btn_frame, text="Reinitialiser", + command=reset_selection, + bg="orange", fg="white", + font=("Arial", 10), + width=12).grid(row=0, column=1, padx=5) + + tk.Button(btn_frame, text="Annuler", + command=crop_window.destroy, + bg="gray", fg="white", + font=("Arial", 10), + width=12).grid(row=0, column=2, padx=5) + + except Exception as e: + print(f"Erreur recadrage: {e}") + """Ouvre un menu pour visualiser, supprimer ou editer des templates""" + if not self.captured_templates: + messagebox.showinfo("Aucun template", "Aucun template a supprimer") + return + + manager_window = tk.Toplevel(self.control_window) + manager_window.title("Gestionnaire de Templates") + manager_window.geometry("600x500") + manager_window.attributes("-topmost", True) + + tk.Label(manager_window, + text="Gestion des Templates", + font=("Arial", 12, "bold")).pack(pady=10) + + # Frame principal avec canvas scrollable + main_frame = tk.Frame(manager_window) + main_frame.pack(fill="both", expand=True, padx=10, pady=5) + + canvas_scroll = tk.Canvas(main_frame) + scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=canvas_scroll.yview) + scrollable_frame = tk.Frame(canvas_scroll) + + scrollable_frame.bind( + "", + lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) + ) + + canvas_scroll.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas_scroll.configure(yscrollcommand=scrollbar.set) + + canvas_scroll.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + # Images de reference pour tkinter + template_images = [] + + def refresh_templates(): + # Effacer + for widget in scrollable_frame.winfo_children(): + widget.destroy() + template_images.clear() + + for i, tmpl in enumerate(self.captured_templates): + # Frame pour chaque template + template_frame = tk.Frame(scrollable_frame, relief=tk.RIDGE, borderwidth=2, bg="white") + template_frame.pack(fill="x", padx=5, pady=5) + + # Image du template (agrandie) + try: + template_data = tmpl['template'] + # Agrandir x4 pour voir les details + scale = 4 + h, w = template_data.shape + new_size = (w * scale, h * scale) + + template_resized = cv2.resize(template_data, new_size, interpolation=cv2.INTER_NEAREST) + + # Convertir pour tkinter + if len(template_resized.shape) == 2: + template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) + else: + template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB) + + pil_image = Image.fromarray(template_rgb) + photo = ImageTk.PhotoImage(pil_image) + template_images.append(photo) + + img_label = tk.Label(template_frame, image=photo, bg="white") + img_label.pack(side=tk.LEFT, padx=10, pady=10) + except Exception as e: + tk.Label(template_frame, text="Erreur image", bg="white").pack(side=tk.LEFT, padx=10) + + # Infos + info_frame = tk.Frame(template_frame, bg="white") + info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=10) + + tk.Label(info_frame, text=f"Template {i}", + font=("Arial", 12, "bold"), bg="white").pack(anchor="w") + + size = tmpl.get('size', (0, 0)) + tk.Label(info_frame, text=f"Taille: {size[0]}x{size[1]} pixels", + font=("Arial", 9), bg="white").pack(anchor="w") + + threshold = tmpl.get('strict_threshold', self.current_threshold) + tk.Label(info_frame, text=f"Seuil: {threshold:.2f}", + font=("Arial", 9), bg="white").pack(anchor="w") + + # Boutons + btn_frame = tk.Frame(template_frame, bg="white") + btn_frame.pack(side=tk.RIGHT, padx=10) + + tk.Button(btn_frame, text="Recadrer", + command=lambda idx=i: recrop_and_refresh(idx), + bg="orange", fg="white", font=("Arial", 9), width=10).pack(pady=2) + + tk.Button(btn_frame, text="Supprimer", + command=lambda idx=i: delete_and_refresh(idx), + bg="red", fg="white", font=("Arial", 9), width=10).pack(pady=2) + + def recrop_and_refresh(idx): + """Recadre et rafraichit l'affichage""" + self.recrop_template_inline(idx, refresh_templates) + + def delete_and_refresh(idx): + if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"): + self.captured_templates.pop(idx) + refresh_templates() + self.update_control_interface() + if self.preset_manager.current_preset: + self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) + + refresh_templates() + + tk.Button(manager_window, text="Fermer", + command=manager_window.destroy, + font=("Arial", 10), width=20).pack(pady=10) + + def recrop_template(self, template_idx, parent_window): + """Permet de recadrer le template en tracant un rectangle""" + if template_idx >= len(self.captured_templates): + return + + parent_window.withdraw() + + try: + current_template = self.captured_templates[template_idx] + template_image = current_template['template'] + + # Fenetre de recadrage + crop_window = tk.Toplevel() + crop_window.title(f"Recadrage Template {template_idx}") + crop_window.geometry("700x600") + crop_window.attributes("-topmost", True) + + tk.Label(crop_window, + text=f"Recadrage Template {template_idx}", + font=("Arial", 14, "bold")).pack(pady=10) + + tk.Label(crop_window, + text="Cliquez et glissez pour tracer le rectangle de selection", + font=("Arial", 10), fg="blue").pack() + + # Agrandir l'image pour faciliter le recadrage + scale = 8 + h, w = template_image.shape + template_resized = cv2.resize(template_image, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST) + template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB) + + # Frame avec canvas + canvas_frame = tk.Frame(crop_window, relief=tk.SUNKEN, borderwidth=2) + canvas_frame.pack(pady=10, padx=20) + + canvas = tk.Canvas(canvas_frame, width=w*scale, height=h*scale, bg="white", cursor="cross") + canvas.pack(padx=5, pady=5) + + # Afficher l'image + pil_img = Image.fromarray(template_rgb) + photo = ImageTk.PhotoImage(pil_img) + canvas.create_image(0, 0, anchor=tk.NW, image=photo, tags="image") + canvas.image = photo + + # Variables pour le rectangle de selection + start_x = None + start_y = None + rect_id = None + selection = [None, None, None, None] # x1, y1, x2, y2 + + def on_mouse_down(event): + nonlocal start_x, start_y, rect_id + start_x = event.x + start_y = event.y + if rect_id: + canvas.delete(rect_id) + + def on_mouse_drag(event): + nonlocal rect_id + if start_x is not None and start_y is not None: + if rect_id: + canvas.delete(rect_id) + + # Limiter aux bounds de l'image + x = max(0, min(event.x, w*scale)) + y = max(0, min(event.y, h*scale)) + + rect_id = canvas.create_rectangle( + start_x, start_y, x, y, + outline="red", width=2, tags="selection" + ) + + # Afficher les dimensions + width_px = abs(x - start_x) // scale + height_px = abs(y - start_y) // scale + size_label.config(text=f"Selection: {width_px}x{height_px} pixels") + + def on_mouse_up(event): + nonlocal start_x, start_y + if start_x is not None and start_y is not None: + # Limiter aux bounds + x = max(0, min(event.x, w*scale)) + y = max(0, min(event.y, h*scale)) + + # Enregistrer la selection (en pixels de l'image originale) + x1 = min(start_x, x) // scale + y1 = min(start_y, y) // scale + x2 = max(start_x, x) // scale + y2 = max(start_y, y) // scale + + selection[0] = x1 + selection[1] = y1 + selection[2] = x2 + selection[3] = y2 + + width_px = x2 - x1 + height_px = y2 - y1 + size_label.config(text=f"Selection: {width_px}x{height_px} pixels") + + canvas.bind("", on_mouse_down) + canvas.bind("", on_mouse_drag) + canvas.bind("", on_mouse_up) + + # Info + info_frame = tk.Frame(crop_window) + info_frame.pack(pady=5) + + size_label = tk.Label(info_frame, + text="Tracez un rectangle sur l'image", + font=("Arial", 10)) + size_label.pack() + + # Boutons + btn_frame = tk.Frame(crop_window) + btn_frame.pack(pady=15) + + def apply_crop(): + if selection[0] is None: + messagebox.showwarning("Aucune selection", "Tracez d'abord un rectangle") + return + + x1, y1, x2, y2 = selection + + # Verifier que la selection est valide + if x2 - x1 < 8 or y2 - y1 < 8: + messagebox.showerror("Selection trop petite", "La selection doit faire au moins 8x8 pixels") + return + + # Extraire le nouveau template + cropped = template_image[y1:y2, x1:x2] + + # Mettre a jour le template + self.captured_templates[template_idx] = { + 'template': cropped, + 'size': (x2-x1, y2-y1), + 'position': current_template.get('position', (0, 0)), + 'strict_threshold': self.current_threshold + } + + # Sauvegarder + if self.preset_manager.current_preset: + self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates) + + messagebox.showinfo("Succes", f"Template recadre: {x2-x1}x{y2-y1} pixels") + crop_window.destroy() + parent_window.deiconify() + + def reset_selection(): + nonlocal rect_id, start_x, start_y + if rect_id: + canvas.delete(rect_id) + rect_id = None + start_x = None + start_y = None + selection[0] = None + selection[1] = None + selection[2] = None + selection[3] = None + size_label.config(text="Tracez un rectangle sur l'image") + + def cancel(): + crop_window.destroy() + parent_window.deiconify() + + tk.Button(btn_frame, text="Appliquer", + command=apply_crop, + bg="green", fg="white", + font=("Arial", 10, "bold"), + width=12).grid(row=0, column=0, padx=5) + + tk.Button(btn_frame, text="Reinitialiser", + command=reset_selection, + bg="orange", fg="white", + font=("Arial", 10), + width=12).grid(row=0, column=1, padx=5) + + tk.Button(btn_frame, text="Annuler", + command=cancel, + bg="gray", fg="white", + font=("Arial", 10), + width=12).grid(row=0, column=2, padx=5) + + except Exception as e: + print(f"Erreur recadrage: {e}") + parent_window.deiconify() + + def return_to_main_menu(self): + """Retourne au menu principal""" + # Demander confirmation + if messagebox.askyesno("Retour menu", "Arreter la detection et retourner au menu principal ?"): + # Arreter la detection + self.running = False + + # Fermer l'overlay et le controle + if self.overlay_root: + try: + self.overlay_root.destroy() + except: + pass + + if self.control_window: + try: + self.control_window.destroy() + except: + pass + + # Recreer le menu principal + self.mode = "setup" + self.create_main_menu() + + def update_threshold_display(self): + if hasattr(self, 'control_window') and self.control_window and hasattr(self, 'threshold_label'): + try: + if self.control_window.winfo_exists(): + self.threshold_label.config(text=f"Seuil: {self.current_threshold:.2f}") + self.control_window.update_idletasks() + except: + pass + self.update_control_interface() + + def update_control_interface(self): + if not hasattr(self, 'control_window') or not self.control_window: + return + + try: + if hasattr(self, 'mode_label'): + mode_text = { + "normal": "Mode Normal", + "debug": "Mode Debug (IDs visibles)" + } + mode_color = { + "normal": "blue", + "debug": "orange" + } + self.mode_label.config( + text=mode_text.get(self.template_mode, "Mode inconnu"), + fg=mode_color.get(self.template_mode, "black") + ) + + # Rafraichir la liste des templates + self.refresh_templates_display() + + self.control_window.update_idletasks() + except: + pass + + def detection_loop(self): + frame_count = 0 + last_detection_time = time.time() + + print("Boucle detection demarree") + + while self.running and self.mode == "detection": + try: + if not self.target_window or not self.running: + break + + if self.overlay_root and not self.overlay_root.winfo_exists(): + self.running = False + break + + frame_count += 1 + current_time = time.time() + + if self.frame_skip > 0: + self.frame_skip -= 1 + time.sleep(0.016) + continue + + image = self.capture_window_fast(self.target_window) + if image is None: + time.sleep(0.02) + continue + + start_detection = time.time() + positions = self.find_matches_with_templates(image) + detection_time = time.time() - start_detection + + if detection_time > 0.05: + self.frame_skip = self.max_frame_skip + elif detection_time > 0.03: + self.frame_skip = 1 + + if self.overlay_created and self.overlay_root and self.running: + try: + self.safe_update_overlay(positions) + except: + self.running = False + break + + if current_time - last_detection_time > 2.0: + fps = frame_count / (current_time - last_detection_time) if frame_count > 0 else 0 + if self.running: + print(f"{len(positions)} bases | FPS: {fps:.1f} | {detection_time*1000:.1f}ms | Seuil: {self.current_threshold:.2f}") + last_detection_time = current_time + frame_count = 0 + + time.sleep(0.01) + + except: + self.running = False + break + + print("Boucle detection terminee") + + def start_overlay_detection(self): + if not self.target_window: + return + + try: + self.create_control_window() + self.create_overlay(self.target_window) + + detection_thread = threading.Thread(target=self.detection_loop, daemon=True) + detection_thread.start() + + print("Detection active") + + if self.control_window: + try: + self.control_window.mainloop() + except: + self.stop() + finally: + self.running = False + + except Exception as e: + print(f"Erreur demarrage: {e}") + self.stop() + + def start(self): + print("LastWar - Gestionnaire de Templates") + + self.load_settings() + self.target_window = self.find_game_window() + + if not self.target_window: + print("Fenetre de jeu non trouvee") + return + + self.running = True + self.create_main_menu() + + def stop(self): + self.running = False + + if hasattr(self, 'control_window') and self.control_window: + try: + self.control_window.quit() + self.control_window.destroy() + except: + pass + + if hasattr(self, 'overlay_root') and self.overlay_root: + try: + self.overlay_root.quit() + self.overlay_root.destroy() + except: + pass + + if hasattr(self, 'executor'): + try: + self.executor.shutdown(wait=False) + except: + pass + + print("Arrete proprement") + + import sys + sys.exit(0) + + def quit_application(self): + self.running = False + if self.main_menu: + try: + self.main_menu.quit() + self.main_menu.destroy() + except: + pass + import sys + sys.exit(0) + + +def main(): + overlay = GameOverlay() + + import signal + + def signal_handler(sig, frame): + print("\nArret demande (Ctrl+C)...") + overlay.stop() + + signal.signal(signal.SIGINT, signal_handler) + + try: + overlay.start() + except KeyboardInterrupt: + print("\nArret demande...") + overlay.stop() + except Exception as e: + print(f"Erreur: {e}") + overlay.stop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/findbase_templates/find28.pkl b/findbase_templates/find28.pkl new file mode 100644 index 0000000..83f3b4d Binary files /dev/null and b/findbase_templates/find28.pkl differ diff --git a/lastwar_presets.json b/lastwar_presets.json new file mode 100644 index 0000000..0f75f8e --- /dev/null +++ b/lastwar_presets.json @@ -0,0 +1,10 @@ +[ + { + "name": "base 26", + "file": "preset_base 26.pkl" + }, + { + "name": "base 28", + "file": "preset_base 28.pkl" + } +] \ No newline at end of file diff --git a/preset_base 26.pkl b/preset_base 26.pkl new file mode 100644 index 0000000..962625a Binary files /dev/null and b/preset_base 26.pkl differ diff --git a/preset_base 28.pkl b/preset_base 28.pkl new file mode 100644 index 0000000..386b078 Binary files /dev/null and b/preset_base 28.pkl differ