X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonstat.git;a=blobdiff_plain;f=xonstat%2Fviews%2Fsubmission.py;h=567d8a4777ba67eb3e6b3c48be0d32a8230e4b37;hp=b527167c4adc25ad95bb841e566dd837efdb6cc5;hb=HEAD;hpb=1e666a0127e25d276462b5e7f7acea9492a0e1d6 diff --git a/xonstat/views/submission.py b/xonstat/views/submission.py index b527167..567d8a4 100644 --- a/xonstat/views/submission.py +++ b/xonstat/views/submission.py @@ -6,10 +6,10 @@ import re import pyramid.httpexceptions from sqlalchemy import Sequence -from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound +from sqlalchemy.orm.exc import NoResultFound from xonstat.elo import EloProcessor from xonstat.models import DBSession, Server, Map, Game, PlayerGameStat, PlayerWeaponStat -from xonstat.models import PlayerRank, PlayerCaptime +from xonstat.models import PlayerRank, PlayerCaptime, PlayerGameFragMatrix from xonstat.models import TeamGameStat, PlayerGameAnticheat, Player, Hashkey, PlayerNick from xonstat.util import strip_colors, qfont_decode, verify_request, weapon_map @@ -79,6 +79,9 @@ class Submission(object): # bots who played in the match self.bots = [] + # player indexes for those who played + self.player_indexes = set() + # distinct weapons that we have seen fired self.weapons = set() @@ -91,6 +94,8 @@ class Submission(object): # does any human have a fastest cap? self.human_fastest = False + self.parse() + def next_item(self): """Returns the next key:value pair off the queue.""" try: @@ -127,7 +132,7 @@ class Submission(object): """Construct a player events listing from the submission.""" # all of the keys related to player records - player_keys = ['i', 'n', 't', 'e'] + player_keys = ['i', 'n', 't', 'r', 'e'] player = {key: pid} @@ -147,7 +152,7 @@ class Submission(object): if sub_key.endswith("cnt-fired"): player_fired_weapon = True self.add_weapon_fired(sub_key) - elif sub_key == 'scoreboard-score' and int(sub_value) != 0: + elif sub_key == 'scoreboard-score' and int(round(float(sub_value))) != 0: player_nonzero_score = True elif sub_key == 'scoreboard-fastest': player_fastest = True @@ -163,6 +168,9 @@ class Submission(object): played = self.played_in_game(player) human = self.is_human_player(player) + if played: + self.player_indexes.add(int(player["i"])) + if played and human: self.humans.append(player) @@ -338,6 +346,10 @@ def has_minimum_real_players(settings, submission): except: minimum_required_players = 2 + # Make an exception for CTS since it can be done by individuals and there is no Elo anyway + if submission.game_type_cd == "cts": + minimum_required_players = 1 + return len(submission.humans) >= minimum_required_players @@ -397,6 +409,13 @@ def should_do_weapon_stats(game_type_cd): return game_type_cd not in {'cts'} +def should_do_frag_matrix(game_type_cd): + """True if the game type should record frag matrix values. False otherwise.""" + return game_type_cd in { + 'as', 'ca', 'ctf', 'dm', 'dom', 'ft', 'freezetag', 'ka', 'kh', 'rune', 'tdm', + } + + def gametype_elo_eligible(game_type_cd): """True of the game type should process Elos. False otherwise.""" return game_type_cd in {'duel', 'dm', 'ca', 'ctf', 'tdm', 'ka', 'ft'} @@ -604,7 +623,7 @@ def create_game(session, game_type_cd, server_id, map_id, match_id, start_dt, du game.mod = mod[:64] # There is some drift between start_dt (provided by app) and create_dt - # (default in the database), so we'll make them the same until this is + # (default in the database), so we'll make them the same until this is # resolved. game.create_dt = start_dt @@ -695,10 +714,6 @@ def create_default_game_stat(session, game_type_cd): if game_type_cd in 'ca' 'dm' 'duel' 'rune' 'tdm': pgstat.kills = pgstat.deaths = pgstat.suicides = 0 - if game_type_cd == 'cq': - pgstat.kills = pgstat.deaths = pgstat.suicides = pgstat.captures = 0 - pgstat.drops = 0 - if game_type_cd == 'ctf': pgstat.kills = pgstat.captures = pgstat.pickups = pgstat.drops = 0 pgstat.returns = pgstat.carrier_frags = 0 @@ -723,16 +738,10 @@ def create_default_game_stat(session, game_type_cd): pgstat.captures = pgstat.drops = pgstat.pushes = pgstat.destroys = 0 pgstat.carrier_frags = 0 - if game_type_cd == 'lms': - pgstat.kills = pgstat.deaths = pgstat.suicides = pgstat.lives = 0 - if game_type_cd == 'nb': pgstat.kills = pgstat.deaths = pgstat.suicides = pgstat.captures = 0 pgstat.drops = 0 - if game_type_cd == 'rc': - pgstat.kills = pgstat.deaths = pgstat.suicides = pgstat.laps = 0 - return pgstat @@ -842,7 +851,7 @@ def create_default_team_stat(session, game_type_cd): # all team game modes have a score, so we'll zero that out always teamstat.score = 0 - if game_type_cd in 'ca' 'ft' 'lms' 'ka': + if game_type_cd in 'ca' 'ft' 'ka': teamstat.rounds = 0 if game_type_cd == 'ctf': @@ -1042,25 +1051,61 @@ def get_or_create_players(session, events_by_hashkey): return players_by_hashkey +def create_frag_matrix(session, player_indexes, pgstat, events): + """ + Construct a PlayerFragMatrix object from the events of a given player. + + :param session: The DBSession we're adding objects to. + :param player_indexes: The set of player indexes of those that actually played in the game. + :param pgstat: The PlayerGameStat object of the player whose frag matrix we want to create. + :param events: The raw player events of the above player. + :return: PlayerFragMatrix + """ + player_index = int(events.get("i", None)) + + # "kills-4" -> 4 + victim_index = lambda x: int(x.split("-")[1]) + + matrix = {victim_index(k): int(v) for (k, v) in events.items() + if k.startswith("kills-") and victim_index(k) in player_indexes} + + if len(matrix) > 0: + pfm = PlayerGameFragMatrix(pgstat.game_id, pgstat.player_game_stat_id, pgstat.player_id, + player_index, matrix) + + session.add(pfm) + return pfm + else: + return None + + def submit_stats(request): """ Entry handler for POST stats submissions. """ - try: - # placeholder for the actual session - session = None + # placeholder for the actual session + session = None + try: log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body + - "----- END REQUEST BODY -----\n\n") + "----- END REQUEST BODY -----\n\n") (idfp, status) = verify_request(request) - submission = Submission(request.body, request.headers) + try: + submission = Submission(request.body, request.headers) + except: + msg = "Invalid submission" + log.debug(msg) + raise pyramid.httpexceptions.HTTPUnprocessableEntity( + body=msg, + content_type="text/plain" + ) do_precondition_checks(request.registry.settings, submission) - #---------------------------------------------------------------------- + ####################################################################### # Actual setup (inserts/updates) below here - #---------------------------------------------------------------------- + ####################################################################### session = DBSession() # All game types create Game, Server, Map, and Player records @@ -1092,13 +1137,25 @@ def submit_stats(request): players_by_hashkey = get_or_create_players(session, events_by_hashkey) pgstats = [] + elo_pgstats = [] player_ids = [] hashkeys_by_player_id = {} for hashkey, player in players_by_hashkey.items(): events = events_by_hashkey[hashkey] + pgstat = create_game_stat(session, game, gmap, player, events) pgstats.append(pgstat) + if should_do_frag_matrix(submission.game_type_cd): + create_frag_matrix(session, submission.player_indexes, pgstat, events) + + # player ranking opt-out + if 'r' in events and events['r'] == '0': + log.debug("Excluding player {} from ranking calculations (opt-out)" + .format(pgstat.player_id)) + else: + elo_pgstats.append(pgstat) + if player.player_id > 1: create_anticheats(session, pgstat, game, player, events) @@ -1116,8 +1173,11 @@ def submit_stats(request): create_team_stat(session, game, events) if server.elo_ind and gametype_elo_eligible(submission.game_type_cd): - ep = EloProcessor(session, game, pgstats) + ep = EloProcessor(session, game, elo_pgstats) ep.save(session) + elos = ep.wip + else: + elos = {} session.commit() log.debug('Success! Stats recorded.') @@ -1135,7 +1195,7 @@ def submit_stats(request): "gmap": gmap, "player_ids": player_ids, "hashkeys": hashkeys_by_player_id, - "elos": ep.wip, + "elos": elos, "ranks": ranks, }