diff --git a/scoreboard_injector/main.py b/scoreboard_injector/main.py index e3a0276..8509190 100644 --- a/scoreboard_injector/main.py +++ b/scoreboard_injector/main.py @@ -226,87 +226,90 @@ async def socketio_listener(): total_fp, total_fp, round_num) # Process each team_task for attack detection + # Group by service to match stolen/lost pairs + service_data = {} for team_task in team_tasks: - team_id = team_task.get('team_id') task_id = team_task.get('task_id') - current_stolen = team_task.get('stolen', 0) - current_lost = team_task.get('lost', 0) - current_fp_score = team_task.get('score', 0) - service_name = task_names.get(task_id, f"task_{task_id}") - # Get previous state from database - prev_state = await conn.fetchrow( - "SELECT stolen_flags, lost_flags, fp_score FROM scoreboard_state WHERE team_id = $1 AND service_name = $2", - team_id, service_name - ) - - prev_stolen = prev_state['stolen_flags'] if prev_state else 0 - prev_lost = prev_state['lost_flags'] if prev_state else 0 - prev_fp_score = prev_state['fp_score'] if prev_state else 0 - - # Calculate NEW flags and FP changes (difference from previous state) - new_stolen = current_stolen - prev_stolen - new_lost = current_lost - prev_lost - fp_change = current_fp_score - prev_fp_score - - # Skip creating attack records if this is the first time we see this team+service - # (prev_state is None means first update - just initialize state) - is_first_update = prev_state is None - - # Update current state in database - await conn.execute(""" - INSERT INTO scoreboard_state (team_id, service_name, stolen_flags, lost_flags, fp_score, last_updated) - VALUES ($1, $2, $3, $4, $5, NOW()) - ON CONFLICT (team_id, service_name) - DO UPDATE SET stolen_flags = $3, lost_flags = $4, fp_score = $5, last_updated = NOW() - """, team_id, service_name, current_stolen, current_lost, current_fp_score) - - # Create attack record only for NEW stolen flags (FP gained) - skip first update - if new_stolen > 0 and not is_first_update: - timestamp = datetime.utcnow() - attack_id = f"r{round_num}_stolen_team{team_id}_{service_name}_{int(timestamp.timestamp())}" + if service_name not in service_data: + service_data[service_name] = [] + service_data[service_name].append(team_task) + + # Process each service + for service_name, tasks in service_data.items(): + # Track state for each team in this service + for team_task in tasks: + team_id = team_task.get('team_id') + task_id = team_task.get('task_id') + current_stolen = team_task.get('stolen', 0) + current_lost = team_task.get('lost', 0) + current_fp_score = team_task.get('score', 0) - is_our_attack = team_id == OUR_TEAM_ID + # Get previous state from database + prev_state = await conn.fetchrow( + "SELECT stolen_flags, lost_flags, fp_score FROM scoreboard_state WHERE team_id = $1 AND service_name = $2", + team_id, service_name + ) - # Calculate FP per flag for this attack (positive FP change) - fp_for_attack = max(0, fp_change) if new_stolen > 0 else 0 + prev_stolen = prev_state['stolen_flags'] if prev_state else 0 + prev_lost = prev_state['lost_flags'] if prev_state else 0 + prev_fp_score = prev_state['fp_score'] if prev_state else 0 + # Calculate NEW flags and FP changes + new_stolen = current_stolen - prev_stolen + new_lost = current_lost - prev_lost + fp_change = current_fp_score - prev_fp_score + + is_first_update = prev_state is None + + # Update current state in database await conn.execute(""" - INSERT INTO attacks (attack_id, attacker_team_id, service_name, timestamp, points, is_our_attack, is_attack_to_us) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT (attack_id) DO NOTHING - """, attack_id, team_id, service_name, timestamp, float(fp_for_attack), is_our_attack, False) + INSERT INTO scoreboard_state (team_id, service_name, stolen_flags, lost_flags, fp_score, last_updated) + VALUES ($1, $2, $3, $4, $5, NOW()) + ON CONFLICT (team_id, service_name) + DO UPDATE SET stolen_flags = $3, lost_flags = $4, fp_score = $5, last_updated = NOW() + """, team_id, service_name, current_stolen, current_lost, current_fp_score) - if is_our_attack: - print(f" ✅ We stole {new_stolen} NEW flags from {service_name} (+{fp_for_attack:.2f} FP)") - else: - print(f" 📌 Team {team_id} stole {new_stolen} NEW flags from {service_name} (+{fp_for_attack:.2f} FP)") - - # Create attack record only for NEW lost flags (FP lost) - skip first update - if new_lost > 0 and not is_first_update: - timestamp = datetime.utcnow() - attack_id = f"r{round_num}_lost_team{team_id}_{service_name}_{int(timestamp.timestamp())}" - - is_attack_to_us = team_id == OUR_TEAM_ID - - # Calculate FP lost for this attack (negative FP change) - fp_for_attack = abs(min(0, fp_change)) if new_lost > 0 else 0 - - await conn.execute(""" - INSERT INTO attacks (attack_id, victim_team_id, service_name, timestamp, points, is_our_attack, is_attack_to_us) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT (attack_id) DO NOTHING - """, attack_id, team_id, service_name, timestamp, float(fp_for_attack), False, is_attack_to_us) - - if is_attack_to_us: - print(f" ⚠️ We LOST {new_lost} NEW flags on {service_name} (-{fp_for_attack:.2f} FP)") + # Create single attack record when flags change (not first update) + if not is_first_update and (new_stolen > 0 or new_lost > 0): + timestamp = datetime.utcnow() - # Check for alerts on high-value attacks - if fp_for_attack >= ALERT_THRESHOLD_POINTS: - await check_and_create_alerts(conn, 0, service_name) - else: - print(f" 📌 Team {team_id} lost {new_lost} NEW flags on {service_name} (-{fp_for_attack:.2f} FP)") + is_our_attack = (new_stolen > 0 and team_id == OUR_TEAM_ID) + is_attack_to_us = (new_lost > 0 and team_id == OUR_TEAM_ID) + + # Determine attacker/victim and FP + if new_stolen > 0: + # This team stole flags (attacker) + attacker_id = team_id + victim_id = None # We don't know exact victim + fp_value = max(0, fp_change) + attack_type = "stolen" + else: + # This team lost flags (victim) + attacker_id = None # We don't know exact attacker + victim_id = team_id + fp_value = abs(min(0, fp_change)) + attack_type = "lost" + + attack_id = f"r{round_num}_{attack_type}_team{team_id}_{service_name}_{int(timestamp.timestamp())}" + + await conn.execute(""" + INSERT INTO attacks (attack_id, attacker_team_id, victim_team_id, service_name, timestamp, points, is_our_attack, is_attack_to_us) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + ON CONFLICT (attack_id) DO NOTHING + """, attack_id, attacker_id, victim_id, service_name, timestamp, float(fp_value), is_our_attack, is_attack_to_us) + + if is_our_attack: + print(f" ✅ We stole {new_stolen} flags from {service_name} (+{fp_value:.2f} FP)") + elif is_attack_to_us: + print(f" ⚠️ We LOST {new_lost} flags on {service_name} (-{fp_value:.2f} FP)") + if fp_value >= ALERT_THRESHOLD_POINTS: + await check_and_create_alerts(conn, 0, service_name) + elif new_stolen > 0: + print(f" 📌 Team {team_id} stole {new_stolen} flags from {service_name} (+{fp_value:.2f} FP)") + else: + print(f" 📌 Team {team_id} lost {new_lost} flags on {service_name} (-{fp_value:.2f} FP)") finally: await db_pool.release(conn) diff --git a/web/templates/attacks.html b/web/templates/attacks.html index f9b5d9e..f8f566f 100644 --- a/web/templates/attacks.html +++ b/web/templates/attacks.html @@ -65,7 +65,7 @@ Attacker Victim Service - Flags + FP Type