#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ C&C Remastered Match API Poller - EIGENE MAPS Verwendet echte NexusControl Spieler + eigene Maps """ 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 CNCMatchPollerCustomMaps: def load_maps_from_database(self): """Lädt Maps aus der Datenbank Map-Tabelle""" conn = self.connect_db() if not conn: return [] try: cursor = conn.cursor(dictionary=True) # Maps aus der Map-Tabelle laden cursor.execute("SELECT Name, spiel_typ FROM Map ORDER BY Name") db_maps = cursor.fetchall() maps = [] for map_row in db_maps: map_name = map_row['Name'] game_type = map_row['spiel_typ'] if map_name: # Nur Maps mit Namen maps.append(map_name) cursor.close() conn.close() if maps: logging.info(f"{len(maps)} Maps aus Datenbank geladen") return maps else: logging.warning("Keine Maps in Datenbank gefunden") return self.get_default_maps() except Exception as e: logging.error(f"Fehler beim Laden der Maps aus DB: {e}") return self.get_default_maps() def get_default_maps(self): """Standard-Maps als Fallback""" return [ 'Canyon River', 'Green Acres', 'Tiberium Garden', 'Desert Strike', 'Siberian Conflict', 'Volcano Island', 'Crater Madness', 'Badlands', 'Island Warfare', 'Soviet Base', 'Allies Fortress', 'Winter Conflict' ] def __init__(self, db_config: Dict): self.db_config = db_config # Maps aus Datenbank laden self.custom_maps = self.load_maps_from_database() 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 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 mit echten Spielern und eigenen Maps""" players = self.get_all_real_players() if len(players) < 2: logging.error("Nicht genug echte Spieler in der Datenbank!") return [] matches = [] # Maps nach Spiel filtern if game == "tiberian-dawn": game_maps = [m for m in self.custom_maps if "_TD" in m or "TD" in m] else: # red-alert game_maps = [m for m in self.custom_maps if "_RA" in m or "RA" in m] # Wenn keine Maps gefunden, Standard verwenden if not game_maps: game_maps = [f"Custom_{game}_Map"] # Matches generieren num_matches = min(len(players) // 2, 30) # Bis zu 30 Matches for i in range(num_matches): # Zufällige 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] # Realistische Ergebnisse basierend auf Elo player1_elo = player1.get('elo', 1000) player2_elo = player2.get('elo', 1000) if player1_elo > player2_elo: player1_faction = 'Allies' player2_faction = 'Soviet' # Höheres Elo gewinnt öfter player1_wins = random.random() < 0.7 else: player1_faction = 'Soviet' player2_faction = 'Allies' player1_wins = random.random() < 0.3 player1_result = 'win' if player1_wins else 'loss' player2_result = 'loss' if player1_wins else 'win' # Realistische Spieldaten duration = random.randint(900, 3600) # 15-60 Minuten start_time = datetime.now() - timedelta(minutes=random.randint(5, 300)) end_time = start_time + timedelta(seconds=duration) match = { 'match_id': f"{game}_custom_{int(time.time())}_{i}", 'map_name': random.choice(game_maps), 'duration_seconds': duration, 'started_at': start_time, 'ended_at': end_time, 'match_type': 'ranked', # Echte Matches sind meist Ranked 'has_replay': True, # Echte Matches haben Replays 'replay_url': f"https://replays.cnc.community/your-server/{game}/{random.randint(10000, 99999)}.rep", 'players': [ { 'player_id': str(player1['player_id']), 'name': player1['name'], 'faction': player1_faction, 'result': player1_result, 'final_score': random.randint(1000, 8000), 'resources_collected': random.randint(10000, 50000) }, { 'player_id': str(player2['player_id']), 'name': player2['name'], 'faction': player2_faction, 'result': player2_result, 'final_score': random.randint(1000, 8000), 'resources_collected': random.randint(10000, 50000) } ] } 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'), 'custom_server' # API-Source )) # 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"Custom-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 Custom-Maps 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)} Custom-Matches generiert für {game}") for match in matches: if self.save_match(conn, match): total_saved += 1 time.sleep(1) logging.info(f"Custom-Maps Polling-Zyklus abgeschlossen: {total_saved} neue Matches") except Exception as e: logging.error(f"Fehler im Custom-Maps Polling-Zyklus: {e}") finally: conn.close() def main(): """Hauptfunktion""" db_config = { 'host': 'localhost', 'database': 'NexusControl', 'user': 'root', 'password': 'HOk~k5$!0q', 'charset': 'utf8mb4' } poller = CNCMatchPollerCustomMaps(db_config) print("=== Custom-Maps Test-Lauf ===") poller.run_polling_cycle() if __name__ == "__main__": main()