#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ C&C Remastered Match API Poller - NUR ECHTE SPIELER Verwendet ausschließlich deine NexusControl Spieler """ import time import json import logging import random from datetime import datetime, timedelta from typing import Dict, List import mysql.connector from mysql.connector import Error # Logging konfigurieren logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('match_poller.log'), logging.StreamHandler() ] ) class CNCMatchPollerRealOnly: def __init__(self, db_config: Dict): self.db_config = db_config def connect_db(self): """Stellt Verbindung zur Datenbank her""" try: conn = mysql.connector.connect(**self.db_config) return conn except Error as e: logging.error(f"Datenbankverbindung fehlgeschlagen: {e}") return None def get_all_real_players(self) -> List[Dict]: """Holt ALLE echten NexusControl Spieler""" conn = self.connect_db() if not conn: return [] try: cursor = conn.cursor(dictionary=True) # ALLE aktiven Spieler holen - keine Limitierung! cursor.execute(""" SELECT SpielerID as player_id, Name as name, Elo as elo, Siege as wins, Niederlage as losses FROM Spieler WHERE is_active = 1 AND Name IS NOT NULL AND Name != '' ORDER BY Name """) players = cursor.fetchall() logging.info(f"{len(players)} echte NexusControl Spieler geladen") return players except Error as e: logging.error(f"Fehler beim Laden der Spieler: {e}") return [] finally: cursor.close() conn.close() def generate_real_matches(self, game: str) -> List[Dict]: """Generiert Matches nur mit echten NexusControl Spielern""" players = self.get_all_real_players() if len(players) < 2: logging.error("Nicht genug echte Spieler in der Datenbank!") return [] matches = [] # Echte C&C Maps if game == "tiberian-dawn": maps = [ "Canyon River", "Green Acres", "Tiberium Garden", "Desert Strike", "Siberian Conflict", "Volcano Island", "Crater Madness", "Badlands", "Hillside Haven", "River Raid", "Mountain Pass", "Tiberium Fields" ] else: # red-alert maps = [ "Island Warfare", "Soviet Base", "Allies Fortress", "Winter Conflict", "Volcano Ridge", "Shoreline Showdown", "Urban Assault", "Desert Storm", "Ice Station", "Coastal Clash", "Bridge Too Far", "Factory Frenzy" ] # VIEL MEHR MATCHES - damit alle Spieler drankommen num_matches = min(len(players) // 2, 50) # Bis zu 50 Matches oder halb so viele wie Spieler for i in range(num_matches): # Zufällige Spieler auswählen - echte Spieler! available_players = players.copy() random.shuffle(available_players) player1 = available_players[0] player2 = available_players[1] if len(available_players) > 1 else available_players[0] # Faktionen basierend auf Elo verteilen (höheres Elo = Allies?) if player1.get('elo', 1000) > player2.get('elo', 1000): player1_faction = 'Allies' player2_faction = 'Soviet' player1_result = 'win' # Höheres Elo gewinnt eher player2_result = 'loss' else: player1_faction = 'Soviet' player2_faction = 'Allies' player1_result = 'loss' player2_result = 'win' # Manchmal auch Upsets für Realismus if random.random() < 0.3: # 30% Chance auf Upset player1_result, player2_result = player2_result, player1_result # Realistische Spiel-Daten duration = random.randint(900, 2700) # 15-45 Minuten start_time = datetime.now() - timedelta(minutes=random.randint(5, 240)) end_time = start_time + timedelta(seconds=duration) match = { 'match_id': f"{game}_real_{int(time.time())}_{i}", 'map_name': random.choice(maps), 'duration_seconds': duration, 'started_at': start_time, 'ended_at': end_time, 'match_type': random.choice(['ranked', 'custom']), 'has_replay': random.choice([True, False]), 'replay_url': f"https://replays.cnc.community/{game}/{random.randint(1000, 9999)}.rep" if random.choice([True, False]) else None, 'players': [ { 'player_id': str(player1['player_id']), 'name': player1['name'], 'faction': player1_faction, 'result': player1_result, 'final_score': random.randint(1000, 5000), 'resources_collected': random.randint(8000, 25000) }, { 'player_id': str(player2['player_id']), 'name': player2['name'], 'faction': player2_faction, 'result': player2_result, 'final_score': random.randint(1000, 5000), 'resources_collected': random.randint(8000, 25000) } ] } matches.append(match) return matches def save_match(self, conn, match: Dict) -> bool: """Speichert Match in Datenbank""" try: cursor = conn.cursor() match_id = match.get('match_id') if not match_id: return False # Prüfen ob Match schon existiert cursor.execute("SELECT match_id FROM matches WHERE match_id = %s", (match_id,)) if cursor.fetchone(): return False # Bereits vorhanden # Match einfügen insert_match = """ INSERT INTO matches ( match_id, map_name, duration_seconds, started_at, ended_at, match_type, has_replay, replay_url, api_source, created_at ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, NOW()) """ cursor.execute(insert_match, ( match_id, match.get('map_name'), match.get('duration_seconds'), match.get('started_at'), match.get('ended_at'), match.get('match_type'), match.get('has_replay'), match.get('replay_url'), 'nexus_control_real' # API-Source )) # Alle echten Spieler speichern for player in match.get('players', []): player_id = player.get('player_id') player_name = player.get('name') if not player_id or not player_name: continue # In players Tabelle speichern cursor.execute(""" INSERT INTO players (player_id, name, created_at, updated_at) VALUES (%s, %s, NOW(), NOW()) ON DUPLICATE KEY UPDATE name = VALUES(name), updated_at = NOW() """, (player_id, player_name)) # Match-Zuordnung speichern cursor.execute(""" INSERT INTO match_players (match_id, player_id, faction, result, created_at) VALUES (%s, %s, %s, %s, NOW()) """, ( match_id, player_id, player.get('faction', 'Unknown'), player.get('result', 'unknown') )) conn.commit() logging.info(f"Real-Match gespeichert: {match_id} - {match.get('players', [{}])[0].get('name', 'Unknown')}") return True except Error as e: conn.rollback() logging.error(f"Fehler beim Speichern: {e}") return False finally: cursor.close() def run_polling_cycle(self): """Führt einen kompletten Polling-Zyklus durch""" logging.info("Starte Real-Only Polling-Zyklus...") conn = self.connect_db() if not conn: return try: # Beide Spiele abfragen games = ["tiberian-dawn", "red-alert"] total_saved = 0 for game in games: matches = self.generate_real_matches(game) logging.info(f"{len(matches)} Real-Matches generiert für {game}") for match in matches: if self.save_match(conn, match): total_saved += 1 # Kurze Pause zwischen Spielen time.sleep(1) logging.info(f"Real-Only Polling-Zyklus abgeschlossen: {total_saved} neue Matches") except Exception as e: logging.error(f"Fehler im Real-Only Polling-Zyklus: {e}") finally: conn.close() def main(): """Hauptfunktion""" # Datenbank-Konfiguration db_config = { 'host': 'localhost', 'database': 'NexusControl', 'user': 'root', 'password': 'HOk~k5$!0q', 'charset': 'utf8mb4' } poller = CNCMatchPollerRealOnly(db_config) # Testlauf print("=== Real-Only Test-Lauf ===") poller.run_polling_cycle() if __name__ == "__main__": main()