294 lines
13 KiB
Python
294 lines
13 KiB
Python
"""
|
|
Event Processor - Handles kill and bomb events
|
|
|
|
Responsibilities:
|
|
- Process leetify show_event data (kills with score impacts)
|
|
- Process classic all_kill and c4_event data
|
|
- Generate unique event_ids
|
|
- Store twin probability changes (leetify only)
|
|
- Handle bomb plant/defuse events
|
|
"""
|
|
|
|
import sqlite3
|
|
import json
|
|
import logging
|
|
import uuid
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class EventProcessor:
|
|
@staticmethod
|
|
def process_leetify_events(match_data, conn: sqlite3.Connection) -> bool:
|
|
"""
|
|
Process leetify event data
|
|
|
|
Args:
|
|
match_data: MatchData object with leetify_data parsed
|
|
conn: L2 database connection
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
"""
|
|
try:
|
|
if not hasattr(match_data, 'data_leetify') or not match_data.data_leetify:
|
|
return True
|
|
|
|
leetify_data = match_data.data_leetify.get('leetify_data', {})
|
|
round_stats = leetify_data.get('round_stat', [])
|
|
|
|
if not round_stats:
|
|
return True
|
|
|
|
cursor = conn.cursor()
|
|
event_count = 0
|
|
|
|
for r in round_stats:
|
|
round_num = r.get('round', 0)
|
|
show_events = r.get('show_event', [])
|
|
|
|
for evt in show_events:
|
|
event_type_code = evt.get('event_type', 0)
|
|
|
|
# event_type: 3=kill, others for bomb/etc
|
|
if event_type_code == 3 and evt.get('kill_event'):
|
|
# Process kill event
|
|
k = evt['kill_event']
|
|
|
|
event_id = str(uuid.uuid4())
|
|
event_time = evt.get('ts', 0)
|
|
|
|
attacker_steam_id = str(k.get('Killer', ''))
|
|
victim_steam_id = str(k.get('Victim', ''))
|
|
weapon = k.get('WeaponName', '')
|
|
|
|
is_headshot = bool(k.get('Headshot', False))
|
|
is_wallbang = bool(k.get('Penetrated', False))
|
|
is_blind = bool(k.get('AttackerBlind', False))
|
|
is_through_smoke = bool(k.get('ThroughSmoke', False))
|
|
is_noscope = bool(k.get('NoScope', False))
|
|
|
|
# Extract assist info
|
|
assister_steam_id = None
|
|
flash_assist_steam_id = None
|
|
trade_killer_steam_id = None
|
|
|
|
if evt.get('assist_killer_score_change'):
|
|
assister_steam_id = str(list(evt['assist_killer_score_change'].keys())[0])
|
|
|
|
if evt.get('flash_assist_killer_score_change'):
|
|
flash_assist_steam_id = str(list(evt['flash_assist_killer_score_change'].keys())[0])
|
|
|
|
if evt.get('trade_score_change'):
|
|
trade_killer_steam_id = str(list(evt['trade_score_change'].keys())[0])
|
|
|
|
# Extract score changes
|
|
score_change_attacker = 0.0
|
|
score_change_victim = 0.0
|
|
|
|
if evt.get('killer_score_change'):
|
|
vals = list(evt['killer_score_change'].values())
|
|
if vals and isinstance(vals[0], dict):
|
|
score_change_attacker = float(vals[0].get('score', 0))
|
|
|
|
if evt.get('victim_score_change'):
|
|
vals = list(evt['victim_score_change'].values())
|
|
if vals and isinstance(vals[0], dict):
|
|
score_change_victim = float(vals[0].get('score', 0))
|
|
|
|
# Extract twin (team win probability) changes
|
|
twin = evt.get('twin', 0.0)
|
|
c_twin = evt.get('c_twin', 0.0)
|
|
twin_change = evt.get('twin_change', 0.0)
|
|
c_twin_change = evt.get('c_twin_change', 0.0)
|
|
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO fact_round_events (
|
|
event_id, match_id, round_num, event_type, event_time,
|
|
attacker_steam_id, victim_steam_id, assister_steam_id,
|
|
flash_assist_steam_id, trade_killer_steam_id, weapon,
|
|
is_headshot, is_wallbang, is_blind, is_through_smoke,
|
|
is_noscope, score_change_attacker, score_change_victim,
|
|
twin, c_twin, twin_change, c_twin_change, data_source_type
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
event_id, match_data.match_id, round_num, 'kill', event_time,
|
|
attacker_steam_id, victim_steam_id, assister_steam_id,
|
|
flash_assist_steam_id, trade_killer_steam_id, weapon,
|
|
is_headshot, is_wallbang, is_blind, is_through_smoke,
|
|
is_noscope, score_change_attacker, score_change_victim,
|
|
twin, c_twin, twin_change, c_twin_change, 'leetify'
|
|
))
|
|
|
|
event_count += 1
|
|
|
|
logger.debug(f"Processed {event_count} leetify events for match {match_data.match_id}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing leetify events for match {match_data.match_id}: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
@staticmethod
|
|
def process_classic_events(match_data, conn: sqlite3.Connection) -> bool:
|
|
"""
|
|
Process classic event data (all_kill, c4_event)
|
|
|
|
Args:
|
|
match_data: MatchData object with round_list parsed
|
|
conn: L2 database connection
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
"""
|
|
try:
|
|
if not hasattr(match_data, 'data_round_list') or not match_data.data_round_list:
|
|
return True
|
|
|
|
round_list = match_data.data_round_list.get('round_list', [])
|
|
|
|
if not round_list:
|
|
return True
|
|
|
|
cursor = conn.cursor()
|
|
event_count = 0
|
|
|
|
for idx, rd in enumerate(round_list, start=1):
|
|
round_num = idx
|
|
|
|
# Extract round basic info for fact_rounds
|
|
current_score = rd.get('current_score', {})
|
|
ct_score = current_score.get('ct', 0)
|
|
t_score = current_score.get('t', 0)
|
|
win_type = current_score.get('type', 0)
|
|
pasttime = current_score.get('pasttime', 0)
|
|
final_round_time = current_score.get('final_round_time', 0)
|
|
|
|
# Determine winner_side from win_type
|
|
winner_side = _map_win_type_to_side(win_type)
|
|
|
|
# Insert/update fact_rounds
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO fact_rounds (
|
|
match_id, round_num, winner_side, win_reason, win_reason_desc,
|
|
duration, ct_score, t_score, end_time_stamp, final_round_time,
|
|
pasttime, data_source_type
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
match_data.match_id, round_num, winner_side, win_type,
|
|
_map_win_type_desc(win_type), float(pasttime), ct_score, t_score,
|
|
'', final_round_time, pasttime, 'classic'
|
|
))
|
|
|
|
# Process kill events
|
|
all_kill = rd.get('all_kill', [])
|
|
for kill in all_kill:
|
|
event_id = str(uuid.uuid4())
|
|
event_time = kill.get('pasttime', 0)
|
|
|
|
attacker = kill.get('attacker', {})
|
|
victim = kill.get('victim', {})
|
|
|
|
attacker_steam_id = str(attacker.get('steamid_64', ''))
|
|
victim_steam_id = str(victim.get('steamid_64', ''))
|
|
weapon = kill.get('weapon', '')
|
|
|
|
is_headshot = bool(kill.get('headshot', False))
|
|
is_wallbang = bool(kill.get('penetrated', False))
|
|
is_blind = bool(kill.get('attackerblind', False))
|
|
is_through_smoke = bool(kill.get('throughsmoke', False))
|
|
is_noscope = bool(kill.get('noscope', False))
|
|
|
|
# Classic has spatial data - will be filled by spatial_processor
|
|
# But we still need to insert the event
|
|
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO fact_round_events (
|
|
event_id, match_id, round_num, event_type, event_time,
|
|
attacker_steam_id, victim_steam_id, weapon, is_headshot,
|
|
is_wallbang, is_blind, is_through_smoke, is_noscope,
|
|
data_source_type
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
event_id, match_data.match_id, round_num, 'kill', event_time,
|
|
attacker_steam_id, victim_steam_id, weapon, is_headshot,
|
|
is_wallbang, is_blind, is_through_smoke, is_noscope, 'classic'
|
|
))
|
|
|
|
event_count += 1
|
|
|
|
# Process bomb events
|
|
c4_events = rd.get('c4_event', [])
|
|
for c4 in c4_events:
|
|
event_id = str(uuid.uuid4())
|
|
event_name = c4.get('event_name', '')
|
|
event_time = c4.get('pasttime', 0)
|
|
steam_id = str(c4.get('steamid_64', ''))
|
|
|
|
# Map event_name to event_type
|
|
if 'plant' in event_name.lower():
|
|
event_type = 'bomb_plant'
|
|
attacker_steam_id = steam_id
|
|
victim_steam_id = None
|
|
elif 'defuse' in event_name.lower():
|
|
event_type = 'bomb_defuse'
|
|
attacker_steam_id = steam_id
|
|
victim_steam_id = None
|
|
else:
|
|
event_type = 'unknown'
|
|
attacker_steam_id = steam_id
|
|
victim_steam_id = None
|
|
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO fact_round_events (
|
|
event_id, match_id, round_num, event_type, event_time,
|
|
attacker_steam_id, victim_steam_id, data_source_type
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
event_id, match_data.match_id, round_num, event_type,
|
|
event_time, attacker_steam_id, victim_steam_id, 'classic'
|
|
))
|
|
|
|
event_count += 1
|
|
|
|
logger.debug(f"Processed {event_count} classic events for match {match_data.match_id}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing classic events for match {match_data.match_id}: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
|
|
def _map_win_type_to_side(win_type):
|
|
"""Map win_type to winner_side for classic data"""
|
|
# Based on CS:GO win types
|
|
t_win_types = {1, 8, 12, 17}
|
|
ct_win_types = {2, 7, 9, 11}
|
|
|
|
if win_type in t_win_types:
|
|
return 'T'
|
|
elif win_type in ct_win_types:
|
|
return 'CT'
|
|
else:
|
|
return 'None'
|
|
|
|
|
|
def _map_win_type_desc(win_type):
|
|
"""Map win_type to description"""
|
|
type_map = {
|
|
0: 'None',
|
|
1: 'TargetBombed',
|
|
2: 'BombDefused',
|
|
7: 'CTsWin',
|
|
8: 'TerroristsWin',
|
|
9: 'TargetSaved',
|
|
11: 'CTSurrender',
|
|
12: 'TSurrender',
|
|
17: 'TerroristsPlanted'
|
|
}
|
|
return type_map.get(win_type, f'Unknown_{win_type}')
|