Welcome! Share code as fast as possible.
- Use the language picker to change the language manually.
- The previous link will get modified. You can then share this new link with others.
- Visit
https://code-dump.vercel.app/<extension>
- For example, to create a link which for a JavaScript code you will visit
https://code-dump.vercel.app/js
Any link generated does not have an expiry.
GitHub
import sys
import json
import requests
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QWidget
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QRect, QPoint, QRectF
from PyQt6.QtGui import QColor, QPainter, QPen, QScreen, QFont, QFontDatabase, QPainterPath
import win32gui
import win32api
import win32con
import urllib3
import time
from enum import Enum, auto
from typing import Dict, List, Optional
from dataclasses import dataclass
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class LogLevel(Enum):
ERROR = auto()
WARNING = auto()
INFO = auto()
DEBUG = auto()
class SpellIcon(QLabel):
def __init__(self, spell_name, parent=None, debug_mode=False):
super().__init__(parent)
self.spell_name = spell_name
self.timer_value = 0
self.timer_active = False
self.summoner_name = ""
self.debug_mode = debug_mode
self.parent_widget = parent
self.overlay = parent.parent() # Reference to DebugOverlay for logging
# Load custom font
font_id = QFontDatabase.addApplicationFont("Beaufort-Bold.ttf")
# Get icon size from parent overlay
self.icon_size = self.overlay.icon_size
self.setFixedSize(self.icon_size, self.icon_size)
# Scale font size relative to icon size
font_size = int(self.icon_size * 0.32) # 32% of icon size
if font_id != -1:
self.timer_font = QFont("Beaufort", font_size, QFont.Weight.Bold)
else:
self.overlay.log("Failed to load Beaufort font, using fallback", LogLevel.WARNING)
self.timer_font = QFont("Arial", font_size, QFont.Weight.Bold)
self.update_style()
def update_style(self):
if self.debug_mode:
self.setStyleSheet("""
background-color: rgba(255, 0, 0, 180);
border: 1px solid yellow;
color: white;
font-size: 16px;
font-weight: bold;
""")
self.setText(self.get_spell_abbreviation(self.spell_name))
else:
self.setStyleSheet("""
background-color: transparent;
border: none;
color: white;
font-size: 16px;
font-weight: bold;
""")
self.setText("") # Clear the spell abbreviation
def get_spell_abbreviation(self, spell_name):
abbreviations = {
'SummonerFlash': 'F',
'SummonerDot': 'I', # Ignite
'SummonerExhaust': 'E',
'SummonerBarrier': 'B',
'SummonerHeal': 'H',
'SummonerGhost': 'G',
'SummonerBoost': 'C', # Cleanse
'SummonerTeleport': 'T',
'S12_SummonerTeleportUpgrade': 'T', # Unleashed Teleport
'SummonerSmite': 'S',
'SummonerMana': 'M', # Clarity
'SummonerSnowball': 'M' # Mark (ARAM)
}
return abbreviations.get(spell_name, '?')
def get_summoner_spell_cooldown(self, spell_name, game_time=0, haste=0):
"""Get cooldown from Data Dragon, with special handling for Teleport upgrade"""
try:
cooldown = self.parent().data_dragon.summoner_spells.get(spell_name, {}).get('cooldown', [300])[0]
if spell_name == 'SummonerTeleport' and game_time >= 600:
cooldown = 330 # Unleashed Teleport cooldown (5.5 minutes)
if haste > 0:
cooldown = cooldown * (100 / (100 + haste))
return cooldown
except Exception as e:
self.overlay.log(f"Error getting cooldown for {spell_name}: {e}", LogLevel.ERROR)
return 300 # Fallback cooldown
def start_timer(self, duration):
self.overlay.log(
f"Starting timer for {self.spell_name} ({self.summoner_name}) with duration {duration}s",
LogLevel.INFO
)
self.timer_value = float(duration)
self.timer_active = True
self.update()
# Ensure timer is running when we start a new countdown
if not self.overlay.spell_timer.isActive():
self.overlay.spell_timer.start(16)
def reset_timer(self):
self.overlay.log(
f"Manually resetting timer for {self.spell_name} ({self.summoner_name})",
LogLevel.INFO
)
self.timer_value = 0
self.timer_active = False
if self.debug_mode:
self.setText(self.get_spell_abbreviation(self.spell_name))
else:
self.setText("")
self.update()
def start_timer(self, duration):
self.overlay.log(
f"Starting timer for {self.spell_name} ({self.summoner_name}) with duration {duration}s",
LogLevel.INFO
)
self.timer_value = float(duration)
self.timer_active = True
self.update()
# Ensure timer is running when we start a new countdown
if not self.overlay.spell_timer.isActive():
self.overlay.spell_timer.start(16)
def update_timer(self):
if self.timer_active and self.timer_value > 0:
self.timer_value -= 0.016 # 16ms worth of time
if self.timer_value <= 0:
self.timer_value = 0
self.timer_active = False
if self.debug_mode:
self.setText(self.get_spell_abbreviation(self.spell_name))
else:
self.setText("")
self.overlay.log(
f"Timer completed for {self.spell_name} ({self.summoner_name})",
LogLevel.INFO
)
elif int(self.timer_value) % 30 == 0: # Log every 30 seconds
self.overlay.log(
f"Timer {self.spell_name} for {self.summoner_name}: {int(self.timer_value)}s remaining",
LogLevel.DEBUG
)
self.update()
def paintEvent(self, event):
super().paintEvent(event)
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
if self.timer_active:
# Scale margins based on icon size
margin = int(self.icon_size * 0.05) # 5% margin
rect = self.rect().adjusted(margin, margin, -margin, -margin)
# Scale shadow offsets based on icon size
shadow_offset = max(1, int(self.icon_size * 0.05)) # At least 1px, up to 5% of icon size
shadow_offsets = [
(-shadow_offset, -shadow_offset),
(shadow_offset, -shadow_offset),
(-shadow_offset, shadow_offset),
(shadow_offset, shadow_offset)
]
# Calculate the progress (1.0 to 0.0)
base_cd = self.overlay.data_dragon.get_spell_cooldown(self.spell_name) # Fixed data_dragon access
progress = self.timer_value / base_cd
# Draw square progress indicator
rect = self.rect().adjusted(2, 2, -2, -2) # Create margin
# Draw progress overlay (black with 33% opacity)
if progress > 0:
painter.setBrush(QColor(40, 50, 70, 200)) # Dark blue-grey with 50% opacity
span_angle = int(progress * 360 * 16) # Removed negative sign to reverse direction
# Create a larger rect for the arc (2x the size)
size_multiplier = 2.0
center = rect.center()
larger_size = rect.width() * size_multiplier
larger_rect = QRectF(
center.x() - larger_size/2,
center.y() - larger_size/2,
larger_size,
larger_size
)
# Create path with larger arc
path = QPainterPath()
path.moveTo(float(center.x()), float(center.y()))
path.arcTo(larger_rect, 90, span_angle/16) # Start at top (90 degrees)
path.lineTo(float(center.x()), float(center.y()))
# Clip to original square shape
painter.setClipRect(rect)
painter.drawPath(path)
painter.setClipRect(self.rect()) # Reset clip
# Draw timer text with shadow
painter.setFont(self.timer_font)
minutes = int(self.timer_value) // 60
seconds = int(self.timer_value) % 60
time_text = f"{minutes}:{seconds:02d}"
# Draw black shadow
painter.setPen(QPen(Qt.GlobalColor.black, 2))
for offset_x, offset_y in shadow_offsets:
painter.drawText(self.rect().adjusted(offset_x, offset_y, offset_x, offset_y),
Qt.AlignmentFlag.AlignCenter, time_text)
# Draw white text
painter.setPen(QPen(Qt.GlobalColor.white, 1))
painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, time_text)
@dataclass
class SpellUsage:
spell_name: str
cast_count: int = 0
total_time: float = 0.0
@property
def average_time(self) -> float:
return self.total_time / self.cast_count if self.cast_count > 0 else 0.0
@dataclass
class Enemy:
name: str
team: str
spell1: SpellIcon
spell2: SpellIcon
haste: int = 0
class DebugOverlay(QMainWindow):
def __init__(self):
super().__init__()
self.debug_mode = False
self.log_level = LogLevel.ERROR
self.data_dragon = DataDragon()
# Initialize log history
self.log_history = []
# Keep existing data structures
self.spell_icons = {}
self.summoner_haste = {}
self.spell_usage: Dict[str, SpellUsage] = {} # Add spell usage tracking
# New data structure
self.enemies: Dict[str, Enemy] = {}
self.game_active = False
self.game_time = 0
# Window flags for overlay behavior
self.setWindowFlags(
Qt.WindowType.FramelessWindowHint | # No window frame
Qt.WindowType.WindowStaysOnTopHint | # Always on top
Qt.WindowType.Tool | # Task bar icon hidden
Qt.WindowType.WindowTransparentForInput # Add this back to prevent screen flashing
)
# Window attributes for proper overlay behavior
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) # Transparent background
self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating) # Don't steal focus
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) # Keep game cursor
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
screen = QApplication.primaryScreen()
self.screen_geometry = screen.geometry()
self.screen_width = self.screen_geometry.width()
self.screen_height = self.screen_geometry.height()
# Use relative positions and sizes
self.left_column_x = int(self.screen_width * 0.4) # 40% from the left
self.right_column_x = int(self.screen_width * 0.7) # 60% from the left
self.first_row_y = int(self.screen_height * 0.25) # Centered vertically
self.player_height = int(self.screen_height * 0.1) # 10% of screen height
self.spell_spacing = int(self.player_height * 0.5) # 50% of player height
self.spell_icons = {}
self.game_active = False
self.last_game_check = 0
self.game_time = 0
self.spell_usage: Dict[str, SpellUsage] = {} # Track spell usage statistics
self.setup_ui()
# Single timer for all game updates (every 10 seconds)
self.game_update_timer = QTimer()
self.game_update_timer.timeout.connect(self.update_game_state_and_data)
self.game_update_timer.start(10000) # Check every 10 seconds
# Spell timer runs at 60 FPS (16.67ms) when timers are active
self.spell_timer = QTimer()
self.spell_timer.timeout.connect(self.update_spell_timers)
self.spell_timer.start(16) # ~60 FPS
# Keep tab and mouse timers at high frequency for responsiveness
self.tab_timer = QTimer()
self.tab_timer.timeout.connect(self.check_tab)
self.tab_timer.start(16)
self.mouse_timer = QTimer()
self.mouse_timer.timeout.connect(self.check_mouse_click)
self.mouse_timer.start(16)
# Track last click states
self.last_left_click = False
self.last_right_click = False
def setup_ui(self):
# Get League window size instead of screen size
self.screen_width, self.screen_height = self.get_league_window_size()
self.resize(self.screen_width, self.screen_height)
# Base calculations on game window height for consistent scaling
scoreboard_center_x = self.screen_width // 2
# Icon size - 3.5% of window height
self.icon_size = int(self.screen_height * 0.024)
# Spacing calculations
self.horizontal_spacing = int(self.icon_size * .35) # 120% of icon size
self.spell_spacing = int(self.icon_size * 1.06) # 110% of icon size
self.player_height = int(self.icon_size * 2.98) # 240% of icon size
# Column positions - adjust these offsets to move columns left/right
self.left_column_x = scoreboard_center_x - self.horizontal_spacing -35 # Subtract to move left column more left
self.right_column_x = scoreboard_center_x + self.horizontal_spacing # Add to move right column more right
# Vertical positioning
scoreboard_top = int(self.screen_height * 0.298) # 40% from top
self.first_row_y = scoreboard_top
def check_mouse_click(self):
"""Handle mouse clicks without affecting the game window"""
if not self.game_active or not self.isVisible():
return
left_click = win32api.GetAsyncKeyState(win32con.VK_LBUTTON) & 0x8000
right_click = win32api.GetAsyncKeyState(win32con.VK_RBUTTON) & 0x8000
cursor_pos = QPoint(win32api.GetCursorPos()[0], win32api.GetCursorPos()[1])
if (left_click and not self.last_left_click) or (right_click and not self.last_right_click):
for summoner_name, summoner_spells in self.spell_icons.items():
enemy = self.enemies.get(summoner_name) # Get Enemy object if it exists
for spell_icon in summoner_spells:
icon_pos = spell_icon.mapToGlobal(QPoint(0, 0))
icon_rect = QRect(icon_pos.x(), icon_pos.y(),
spell_icon.width(), spell_icon.height())
if icon_rect.contains(cursor_pos):
if right_click and not self.last_right_click:
spell_icon.reset_timer()
self.log(f"Reset timer for {spell_icon.spell_name} for {summoner_name}", LogLevel.INFO)
elif left_click and not self.last_left_click and not spell_icon.timer_active:
# Get base cooldown and apply haste
base_cd = self.data_dragon.get_spell_cooldown(spell_icon.spell_name)
haste = enemy.haste if enemy else 0 # Use Enemy dataclass for haste
if haste > 0:
cd = base_cd * (100 / (100 + haste))
cd = round(cd)
self.log(f"Applying {haste} haste to {base_cd}s cooldown = {cd}s", LogLevel.INFO)
else:
cd = base_cd
spell_icon.start_timer(cd)
self.track_spell_cast(spell_icon.spell_name, cd) # Track the spell cast
self.log(f"Started {spell_icon.spell_name} timer for {cd}s for {summoner_name}", LogLevel.INFO)
self.last_left_click = left_click
self.last_right_click = right_click
def check_game_state(self):
"""Check if a game is in progress and initialize if needed"""
try:
response = requests.get('https://127.0.0.1:2999/liveclientdata/gamestats',
verify=False,
timeout=1.0)
if response.status_code == 200:
if not self.game_active:
self.start_new_game()
else:
self.end_game("Game API returned non-200 status")
except requests.exceptions.RequestException as e:
if self.game_active:
if isinstance(e, requests.exceptions.ConnectTimeout):
self.end_game("Game ended - client disconnected")
else:
self.end_game(f"Connection error: {str(e)}")
def start_new_game(self):
"""Initialize a new game"""
try:
self.game_active = True
self.game_time = 0
# Initialize game data and start timers
self.log("Initializing game data...", LogLevel.INFO)
self.initialize_game_data()
enemy_count = len(self.enemies)
timer_count = sum(1 for spells in self.spell_icons.values() for _ in spells)
self.log(f"Successfully created {timer_count} timers for {enemy_count} enemies", LogLevel.INFO)
self.game_data_timer.start(10000) # Update every 10 seconds
self.log("Game monitoring started", LogLevel.INFO)
if self.debug_mode:
self.log("\nTimer Status:", LogLevel.DEBUG)
self.log(f"- Game data timer: {self.game_data_timer.isActive()}", LogLevel.DEBUG)
self.log(f"- Spell timer: {self.spell_timer.isActive()}", LogLevel.DEBUG)
except Exception as e:
self.log(f"Error starting new game: {e}", LogLevel.ERROR)
def end_game(self, reason="Game ended"):
"""Clean up when game ends"""
if self.game_active:
self.log(f"Game End Detected: {reason}", LogLevel.INFO)
self.game_active = False
# Stop all timers
timer_count = 0
for summoner_name, spells in self.spell_icons.items():
for spell in spells:
if spell.timer_active:
timer_count += 1
spell.hide()
spell.deleteLater()
self.log(f"Cleaned up {timer_count} active timers", LogLevel.INFO)
# Stop timers
if self.spell_timer.isActive():
self.spell_timer.stop()
if self.game_data_timer.isActive():
self.game_data_timer.stop()
# Clear all game state
enemy_count = len(self.enemies)
self.enemies.clear()
self.spell_icons.clear()
self.summoner_haste.clear()
self.game_time = 0
self.log(f"Cleared data for {enemy_count} enemies", LogLevel.INFO)
self.log("Waiting for new game...", LogLevel.INFO)
# Export final game stats if in debug mode
if self.debug_mode:
self.log_game_stats()
def initialize_game_data(self):
"""Initialize game data when a new game is detected"""
try:
response = requests.get('https://127.0.0.1:2999/liveclientdata/allgamedata', verify=False)
if response.status_code == 200:
data = response.json()
# Get active player's team - fix team detection
active_player_name = data.get('activePlayer', {}).get('summonerName', '')
if not active_player_name:
self.log("Could not get active player name!", LogLevel.ERROR)
return
# Find active player in allPlayers to get team
for player in data['allPlayers']:
if player['summonerName'] == active_player_name:
our_team = player['team']
enemy_team = 'CHAOS' if our_team == 'ORDER' else 'ORDER'
x_position = self.right_column_x if our_team == 'ORDER' else self.left_column_x
self.log(f"Creating icons for enemy team: {enemy_team} at x={x_position}", LogLevel.INFO)
enemy_players = [p for p in data['allPlayers'] if p['team'] == enemy_team]
for i, player in enumerate(enemy_players):
self.create_spell_icons(player, i, x_position)
self.log(f"Initialization complete - tracking {len(self.enemies)} enemies", LogLevel.INFO)
self.log_game_state() # Log initial state in debug mode
return
self.log("Could not find active player in player list!", LogLevel.ERROR)
except Exception as e:
self.log(f"Error initializing game data: {e}", LogLevel.ERROR)
def create_spell_icons(self, player, index, x_position):
"""Helper function to create spell buttons for a player"""
try:
summoner_name = player['summonerName']
spell1_raw = player['summonerSpells']['summonerSpellOne']['rawDisplayName']
spell2_raw = player['summonerSpells']['summonerSpellTwo']['rawDisplayName']
spell1_id = spell1_raw.replace('GeneratedTip_SummonerSpell_', '').replace('_DisplayName', '')
spell2_id = spell2_raw.replace('GeneratedTip_SummonerSpell_', '').replace('_DisplayName', '')
base_y = self.first_row_y + (index * self.player_height)
spell1 = SpellIcon(spell1_id, self.central_widget, self.debug_mode)
spell2 = SpellIcon(spell2_id, self.central_widget, self.debug_mode)
spell1.summoner_name = summoner_name
spell2.summoner_name = summoner_name
spell1.move(x_position, base_y)
spell2.move(x_position, base_y + self.spell_spacing)
# Create Enemy object with the new data structure
enemy = Enemy(
name=summoner_name,
team=player['team'],
spell1=spell1,
spell2=spell2,
haste=self.calculate_haste(player)
)
self.enemies[summoner_name] = enemy
# Keep old data structure working during transition
self.spell_icons[summoner_name] = [spell1, spell2]
self.summoner_haste[summoner_name] = enemy.haste
self.log(f"Created spell buttons for {summoner_name}", LogLevel.INFO)
except Exception as e:
self.log(f"Error creating spell buttons for player: {e}", LogLevel.ERROR)
def debug_haste_calculation(self, player) -> None:
"""Debug helper for haste calculations"""
if not self.debug_mode:
return
summoner_name = player['summonerName']
cosmic_insight = self.check_for_cosmic_insight(player)
ionian_boots = self.check_for_ionian_boots(player)
total_haste = (18 if cosmic_insight else 0) + (10 if ionian_boots else 0)
self.log(f"Haste calculation for {summoner_name}:", LogLevel.DEBUG)
self.log(f" Cosmic Insight: {cosmic_insight} (+18)", LogLevel.DEBUG)
self.log(f" Ionian Boots: {ionian_boots} (+10)", LogLevel.DEBUG)
self.log(f" Total Haste: {total_haste}", LogLevel.DEBUG)
def calculate_haste(self, player) -> int:
"""Calculate total summoner spell haste for a player"""
self.debug_haste_calculation(player) # Add debug info
has_cosmic_insight = self.check_for_cosmic_insight(player)
has_ionian_boots = self.check_for_ionian_boots(player)
return (18 if has_cosmic_insight else 0) + (10 if has_ionian_boots else 0)
def track_spell_cast(self, spell_name: str, cooldown: float):
"""Track spell usage statistics"""
if spell_name not in self.spell_usage:
self.spell_usage[spell_name] = SpellUsage(spell_name)
usage = self.spell_usage[spell_name]
usage.cast_count += 1
usage.total_time += cooldown
if self.debug_mode:
avg_time = usage.average_time
self.log(f"Spell usage - {spell_name}: {usage.cast_count} casts, avg CD: {avg_time:.1f}s", LogLevel.DEBUG)
def update_game_data(self):
"""Update game data periodically"""
if not self.game_active:
return
try:
# Update game time
time_response = requests.get('https://127.0.0.1:2999/liveclientdata/gamestats', verify=False)
if time_response.status_code == 200:
stats = time_response.json()
old_game_time = self.game_time
self.game_time = stats.get('gameTime', 0)
# Check for teleport upgrade only when crossing the 10-minute mark
if old_game_time < 600 and self.game_time >= 600:
print("10 minute mark reached - upgrading teleports")
for spells in self.spell_icons.values():
for spell in spells:
if spell.spell_name == 'SummonerTeleport':
spell.spell_name = 'S12_SummonerTeleportUpgrade'
spell.setText(spell.get_spell_abbreviation('S12_SummonerTeleportUpgrade'))
except Exception as e:
print(f"Error updating game data: {e}")
def update_spell_timers(self):
"""Only update if there are active timers"""
has_active_timers = False
for summoner_name, spells in self.spell_icons.items():
for spell in spells:
if spell.timer_active:
has_active_timers = True
spell.update_timer()
# Stop the timer if nothing is active
if not has_active_timers:
self.spell_timer.stop()
def check_tab(self):
if not self.game_active:
return
foreground_window = win32gui.GetForegroundWindow()
window_title = win32gui.GetWindowText(foreground_window)
is_league_focused = "League of Legends" in window_title
tab_pressed = win32api.GetAsyncKeyState(0x09) & 0x8000 # Tab key
alt_pressed = win32api.GetAsyncKeyState(0x12) & 0x8000 # Alt key
if is_league_focused and tab_pressed and not alt_pressed: # Only show if Alt is not pressed
self.show()
else:
self.hide()
# Commented out window focus logging to reduce spam
# if self.debug_mode and tab_pressed:
# self.log(f"Active window: {window_title}", LogLevel.DEBUG)
# self.log(f"League focused: {is_league_focused}", LogLevel.DEBUG)
# Commented out window focus logging to reduce spam
# if self.debug_mode and tab_pressed:
# self.log(f"Active window: {window_title}", LogLevel.DEBUG)
# self.log(f"League focused: {is_league_focused}", LogLevel.DEBUG)
def log(self, message: str, level: LogLevel):
"""Log a message with the specified level"""
timestamp = time.strftime('%H:%M:%S')
# Format based on log level
if level == LogLevel.ERROR:
prefix = "ERROR"
elif level == LogLevel.WARNING:
prefix = "WARN"
elif level == LogLevel.INFO:
prefix = "INFO"
elif level == LogLevel.DEBUG:
prefix = "DEBUG"
# Format the log message
log_message = f"[{timestamp}] {prefix}: {message}"
# Always print INFO and ERROR to terminal
if level in [LogLevel.INFO, LogLevel.ERROR]:
print(log_message)
# Print DEBUG and WARNING only in debug mode
elif self.debug_mode:
print(log_message)
# Keep last 1000 messages in history
self.log_history.append(log_message)
if len(self.log_history) > 1000:
self.log_history.pop(0)
def print_debug(self, message):
"""Temporary method until all references are updated"""
self.log(message, LogLevel.DEBUG)
def toggle_debug_mode(self):
"""Toggle debug mode on/off"""
self.debug_mode = not self.debug_mode
# Update all existing spell icons
for spells in self.spell_icons.values():
for spell in spells:
spell.debug_mode = self.debug_mode
spell.update_style()
self.print_debug(f"Debug mode {'enabled' if self.debug_mode else 'disabled'}")
def update_game_state_and_data(self):
"""Combined method for all game updates every 10 seconds"""
try:
response = requests.get('https://127.0.0.1:2999/liveclientdata/allgamedata', verify=False)
was_active = self.game_active
self.game_active = response.status_code == 200
if not self.game_active:
if was_active:
self.log("Game ended - clearing overlay", LogLevel.INFO)
self.enemies.clear()
self.spell_icons.clear()
self.summoner_haste.clear()
return
elif not was_active:
self.log("Game detected - initializing overlay", LogLevel.INFO)
self.initialize_game_data()
return
if response.status_code == 200:
data = response.json()
# Update game time and check teleport upgrade
old_game_time = self.game_time
self.game_time = data.get('gameStats', {}).get('gameTime', 0)
if old_game_time < 600 and self.game_time >= 600:
self.log("10 minute mark reached - upgrading teleports", LogLevel.INFO)
self.upgrade_teleports()
# Update haste values using Enemy dataclass
for player in data['allPlayers']:
summoner_name = player['summonerName']
if summoner_name not in self.enemies:
continue
new_haste = self.calculate_haste(player)
enemy = self.enemies[summoner_name]
old_haste = enemy.haste
if new_haste != old_haste:
self.log(f"Haste changed for {summoner_name}: {old_haste} -> {new_haste}", LogLevel.INFO)
# Update both data structures
enemy.haste = new_haste
self.summoner_haste[summoner_name] = new_haste
self.update_active_timers(summoner_name, old_haste, new_haste)
except requests.exceptions.ConnectionError:
# Only log if we're transitioning from active to inactive
if self.game_active:
self.log("Waiting for game to start...", LogLevel.INFO)
self.game_active = False
self.enemies.clear()
self.spell_icons.clear()
self.summoner_haste.clear()
except Exception as e:
self.log(f"Unexpected error: {e}", LogLevel.ERROR)
if not isinstance(e, requests.exceptions.ConnectionError):
self.log(f"Unexpected error type: {type(e)}", LogLevel.ERROR)
def check_for_cosmic_insight(self, player):
"""Check if a player has the Cosmic Insight rune"""
runes = player.get('perks', {}).get('styles', [])
for rune_style in runes:
selections = rune_style.get('selections', [])
for selection in selections:
if selection.get('perk') == 8347: # Cosmic Insight ID
return True
return False
def check_for_ionian_boots(self, player):
"""Check if a player has Ionian Boots of Lucidity"""
for item in player.get('items', []):
if item.get('itemID') == 3158: # Ionian Boots item ID
return True
return False
def update_active_timers(self, summoner_name, old_haste, new_haste):
"""Update any active timers when haste changes"""
if summoner_name not in self.enemies:
return
enemy = self.enemies[summoner_name]
for spell in [enemy.spell1, enemy.spell2]:
if spell.timer_active:
# Get base cooldown
base_cd = self.data_dragon.get_spell_cooldown(spell.spell_name)
# Calculate old and new cooldowns
old_cd = base_cd * (100 / (100 + old_haste))
new_cd = base_cd * (100 / (100 + new_haste))
# Adjust current timer proportionally
remaining_pct = spell.timer_value / old_cd
spell.timer_value = round(new_cd * remaining_pct)
self.log(f"Updated {spell.spell_name} timer for {enemy.name} with new haste {new_haste}", LogLevel.INFO)
def upgrade_teleports(self):
"""Helper method to upgrade teleport spells at 10 minutes"""
for enemy in self.enemies.values():
for spell in [enemy.spell1, enemy.spell2]:
if spell.spell_name == 'SummonerTeleport':
spell.spell_name = 'S12_SummonerTeleportUpgrade'
if spell.debug_mode:
spell.setText(spell.get_spell_abbreviation('S12_SummonerTeleportUpgrade'))
self.log(f"Upgraded teleport for {enemy.name}", LogLevel.INFO)
def get_enemy(self, summoner_name: str) -> Optional[Enemy]:
"""Safely get an enemy by summoner name"""
enemy = self.enemies.get(summoner_name)
if enemy is None:
self.log(f"Warning: No enemy found for {summoner_name}", LogLevel.WARNING)
return enemy
def get_active_spells(self) -> List[SpellIcon]:
"""Get all currently active spell timers"""
active_spells = []
for enemy in self.enemies.values():
for spell in [enemy.spell1, enemy.spell2]:
if spell.timer_active:
active_spells.append(spell)
return active_spells
def log_game_state(self):
"""Debug helper to log current game state"""
if not self.debug_mode:
return
self.log("Current Game State:", LogLevel.DEBUG)
self.log(f"Game Active: {self.game_active}", LogLevel.DEBUG)
self.log(f"Game Time: {self.game_time}", LogLevel.DEBUG)
for enemy in self.enemies.values():
self.log(f"Enemy: {enemy.name} (Haste: {enemy.haste})", LogLevel.DEBUG)
for spell in [enemy.spell1, enemy.spell2]:
if spell.timer_active:
self.log(f" {spell.spell_name}: {spell.timer_value}s remaining", LogLevel.DEBUG)
def export_debug_info(self) -> dict:
"""Export current state for debugging"""
debug_info = {
'game_state': {
'active': self.game_active,
'game_time': self.game_time
},
'enemies': [
{
'name': enemy.name,
'team': enemy.team,
'haste': enemy.haste,
'spells': [
{
'name': spell.spell_name,
'active': spell.timer_active,
'remaining': spell.timer_value if spell.timer_active else 0
}
for spell in [enemy.spell1, enemy.spell2]
]
}
for enemy in self.enemies.values()
],
'spell_usage': [
{
'spell': name,
'casts': usage.cast_count,
'avg_cooldown': usage.average_time
}
for name, usage in self.spell_usage.items()
]
}
if self.debug_mode:
self.log("Debug info exported", LogLevel.DEBUG)
self.log(f"Active enemies: {len(self.enemies)}", LogLevel.DEBUG)
self.log(f"Tracked spells: {len(self.spell_usage)}", LogLevel.DEBUG)
return debug_info
def get_game_stats(self) -> dict:
"""Get current game statistics"""
stats = {
'active_timers': 0,
'total_spells_tracked': 0,
'spells_by_type': {},
'haste_stats': {
'min': float('inf'),
'max': 0,
'avg': 0
}
}
total_haste = 0
for enemy in self.enemies.values():
# Track haste stats
stats['haste_stats']['min'] = min(stats['haste_stats']['min'], enemy.haste)
stats['haste_stats']['max'] = max(stats['haste_stats']['max'], enemy.haste)
total_haste += enemy.haste
# Track spell stats
for spell in [enemy.spell1, enemy.spell2]:
stats['total_spells_tracked'] += 1
if spell.timer_active:
stats['active_timers'] += 1
# Track spell types
if spell.spell_name not in stats['spells_by_type']:
stats['spells_by_type'][spell.spell_name] = 0
stats['spells_by_type'][spell.spell_name] += 1
if self.enemies:
stats['haste_stats']['avg'] = total_haste / len(self.enemies)
if stats['haste_stats']['min'] == float('inf'):
stats['haste_stats']['min'] = 0
return stats
def log_game_stats(self):
"""Log current game statistics"""
if not self.debug_mode:
return
stats = self.get_game_stats()
self.log("Current Game Statistics:", LogLevel.DEBUG)
self.log(f"Active Timers: {stats['active_timers']}", LogLevel.DEBUG)
self.log(f"Total Spells Tracked: {stats['total_spells_tracked']}", LogLevel.DEBUG)
self.log("\nSpells by Type:", LogLevel.DEBUG)
for spell_name, count in stats['spells_by_type'].items():
self.log(f" {spell_name}: {count}", LogLevel.DEBUG)
self.log("\nHaste Statistics:", LogLevel.DEBUG)
self.log(f" Min: {stats['haste_stats']['min']}", LogLevel.DEBUG)
self.log(f" Max: {stats['haste_stats']['max']}", LogLevel.DEBUG)
self.log(f" Avg: {stats['haste_stats']['avg']:.1f}", LogLevel.DEBUG)
def check_game_state(self) -> bool:
"""Check if a game is in progress and handle state changes"""
try:
response = requests.get('https://127.0.0.1:2999/liveclientdata/gamestats',
verify=False,
timeout=1.0)
# Game ended or not active
if response.status_code != 200:
if self.game_active:
self.end_game("Game client closed - cleaning up timers")
return False
# New game detected
if not self.game_active:
self.log("League game detected - initializing overlay", LogLevel.INFO)
self.start_new_game()
return True
except requests.exceptions.RequestException as e:
if self.game_active:
if isinstance(e, requests.exceptions.ConnectTimeout):
self.end_game("Game ended - waiting for new game")
else:
self.end_game(f"Connection error: {str(e)}")
return False
def get_league_window_size(self):
"""Get the size of the League of Legends game window"""
try:
# Find League of Legends game window specifically
hwnd = win32gui.FindWindow(None, "League of Legends (TM) Client")
if hwnd != 0:
# Get window rect
rect = win32gui.GetWindowRect(hwnd)
width = rect[2] - rect[0]
height = rect[3] - rect[1]
self.log(f"Found League game window: {width}x{height}", LogLevel.INFO)
return width, height
else:
self.log("League game window not found, using screen resolution", LogLevel.WARNING)
screen = QApplication.primaryScreen()
size = screen.size()
return size.width(), size.height()
except Exception as e:
self.log(f"Error getting League window size: {e}", LogLevel.ERROR)
# Fallback to screen size
screen = QApplication.primaryScreen()
size = screen.size()
return size.width(), size.height()
class DataDragon:
def __init__(self):
self.version = None
self.summoner_spells = {}
self.initialize()
def initialize(self):
"""Get the current version and summoner spell data"""
try:
# Get current version
version_url = 'https://ddragon.leagueoflegends.com/realms/na.json'
version_response = requests.get(version_url)
if version_response.status_code == 200:
version_data = version_response.json()
self.version = version_data['n']['summoner']
print(f"Retrieved Data Dragon version: {self.version}")
# Get summoner spell data
spells_url = f'https://ddragon.leagueoflegends.com/cdn/{self.version}/data/en_US/summoner.json'
spells_response = requests.get(spells_url)
if spells_response.status_code == 200:
spell_data = spells_response.json()
# Process each summoner spell
for spell_id, spell_info in spell_data['data'].items():
if 'CLASSIC' in spell_info.get('modes', []):
self.summoner_spells[spell_id] = {
'name': spell_info['name'],
'cooldown': spell_info['cooldown'][0],
'description': spell_info['description']
}
print(f"Loaded {len(self.summoner_spells)} summoner spells")
except Exception as e:
print(f"Error initializing Data Dragon: {e}")
def get_spell_cooldown(self, spell_id):
"""Get the base cooldown for a summoner spell"""
if spell_id in self.summoner_spells:
return self.summoner_spells[spell_id]['cooldown']
return 300 # Default fallback cooldown
def main():
app = QApplication(sys.argv)
if hasattr(Qt.ApplicationAttribute, 'AA_EnableHighDpiScaling'):
QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling, True)
if hasattr(Qt.ApplicationAttribute, 'AA_UseHighDpiPixmaps'):
QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps, True)
overlay = DebugOverlay()
# Check command line arguments for debug mode
if len(sys.argv) > 1 and sys.argv[1] == '--debug':
overlay.toggle_debug_mode()
sys.exit(app.exec())
if __name__ == "__main__":
main()