1959 lines
79 KiB
Python
1959 lines
79 KiB
Python
#!/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("<Button-1>", on_click)
|
|
self.capture_window.bind("<Return>", lambda e: validate())
|
|
self.capture_window.bind("<Escape>", 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("<Button-1>", on_click)
|
|
temp_overlay.bind("<Escape>", 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("<Escape>", 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(
|
|
"<Configure>",
|
|
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("<KeyPress>", 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(
|
|
"<Configure>",
|
|
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("<ButtonPress-1>", on_mouse_down)
|
|
canvas.bind("<B1-Motion>", on_mouse_drag)
|
|
canvas.bind("<ButtonRelease-1>", 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(
|
|
"<Configure>",
|
|
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("<ButtonPress-1>", on_mouse_down)
|
|
canvas.bind("<B1-Motion>", on_mouse_drag)
|
|
canvas.bind("<ButtonRelease-1>", 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() |