except:
minimum_required_players = 2
- return len(submission.human_players) >= minimum_required_players
+ return len(submission.humans) >= minimum_required_players
def do_precondition_checks(settings, submission):
return gmap
-def create_game(session, start_dt, game_type_cd, server_id, map_id,
- match_id, duration, mod, winner=None):
+def create_game(session, game_type_cd, server_id, map_id, match_id, start_dt, duration, mod,
+ winner=None):
"""
Creates a game. Parameters:
session - SQLAlchemy database session factory
- start_dt - when the game started (datetime object)
game_type_cd - the game type of the game being played
+ mod - mods in use during the game
server_id - server identifier of the server hosting the game
map_id - map on which the game was played
- winner - the team id of the team that won
+ match_id - a unique match ID given by the server
+ start_dt - when the game started (datetime object)
duration - how long the game lasted
- mod - mods in use during the game
+ winner - the team id of the team that won
"""
seq = Sequence('games_game_id_seq')
game_id = session.execute(seq)
- game = Game(game_id=game_id, start_dt=start_dt, game_type_cd=game_type_cd,
- server_id=server_id, map_id=map_id, winner=winner)
+ game = Game(game_id=game_id, start_dt=start_dt, game_type_cd=game_type_cd, server_id=server_id,
+ map_id=map_id, winner=winner)
game.match_id = match_id
game.mod = mod[:64]
# resolved.
game.create_dt = start_dt
- try:
- game.duration = datetime.timedelta(seconds=int(round(float(duration))))
- except:
- pass
+ game.duration = duration
try:
- session.query(Game).filter(Game.server_id==server_id).\
- filter(Game.match_id==match_id).one()
+ session.query(Game).filter(Game.server_id == server_id)\
+ .filter(Game.match_id == match_id).one()
log.debug("Error: game with same server and match_id found! Ignoring.")
- # if a game under the same server and match_id found,
- # this is a duplicate game and can be ignored
- raise pyramid.httpexceptions.HTTPOk('OK')
- except NoResultFound, e:
+ # if a game under the same server_id and match_id exists, this is a duplicate
+ msg = "Duplicate game (pre-existing match_id)"
+ log.debug(msg)
+ raise pyramid.httpexceptions.HTTPOk(body=msg, content_type="text/plain")
+
+ except NoResultFound:
# server_id/match_id combination not found. game is ok to insert
session.add(game)
session.flush()
- log.debug("Created game id {0} on server {1}, map {2} at \
- {3}".format(game.game_id,
- server_id, map_id, start_dt))
+ log.debug("Created game id {} on server {}, map {} at {}"
+ .format(game.game_id, server_id, map_id, start_dt))
return game
pgstat.rank = int(events.get('rank', None))
pgstat.scoreboardpos = int(events.get('scoreboardpos', pgstat.rank))
- if pgstat.nick != player.nick \
- and player.player_id > 2 \
- and pgstat.nick != 'Anonymous Player':
- register_new_nick(session, player, pgstat.nick)
-
wins = False
# gametype-specific stuff is handled here. if passed to us, we store it
return ranks
+def update_player(session, player, events):
+ """
+ Updates a player record using the latest information.
+ :param session: SQLAlchemy session
+ :param player: Player model representing what is in the database right now (before updates)
+ :param events: Dict of player events from the submission
+ :return: player
+ """
+ nick = events.get('n', 'Anonymous Player')[:128]
+ if nick != player.nick and not nick.startswith("Anonymous Player"):
+ register_new_nick(session, player, nick)
+
+ return player
+
+
+def create_player(session, events):
+ """
+ Creates a new player from the list of events.
+ :param session: SQLAlchemy session
+ :param events: Dict of player events from the submission
+ :return: Player
+ """
+ player = Player()
+ session.add(player)
+ session.flush()
+
+ nick = events.get('n', None)
+ if nick:
+ player.nick = nick[:128]
+ player.stripped_nick = strip_colors(qfont_decode(player.nick))
+ else:
+ player.nick = "Anonymous Player #{0}".format(player.player_id)
+ player.stripped_nick = player.nick
+
+ hk = Hashkey(player_id=player.player_id, hashkey=events.get('P', None))
+ session.add(hk)
+
+ return player
+
+
+def get_or_create_players(session, events_by_hashkey):
+ hashkeys = set(events_by_hashkey.keys())
+ players_by_hashkey = {}
+
+ bot = session.query(Player).filter(Player.player_id == 1).one()
+ anon = session.query(Player).filter(Player.player_id == 2).one()
+
+ # fill in the bots and anonymous players
+ for hashkey in events_by_hashkey.keys():
+ if hashkey.startswith("bot#"):
+ players_by_hashkey[hashkey] = bot
+ hashkeys.remove(hashkey)
+ elif hashkey.startswith("player#"):
+ players_by_hashkey[hashkey] = anon
+ hashkeys.remove(hashkey)
+
+ # We are left with the "real" players and can now fetch them by their collective hashkeys.
+ # Those that are returned here are pre-existing players who need to be updated.
+ for p, hk in session.query(Player, Hashkey)\
+ .filter(Player.player_id == Hashkey.player_id)\
+ .filter(Hashkey.hashkey.in_(hashkeys))\
+ .all():
+ log.debug("Found existing player {} with hashkey {}"
+ .format(p.player_id, hk.hashkey))
+
+ player = update_player(session, p, events_by_hashkey[hk.hashkey])
+ players_by_hashkey[hk.hashkey] = player
+ hashkeys.remove(hk.hashkey)
+
+ # The remainder are the players we haven't seen before, so we need to create them.
+ for hashkey in hashkeys:
+ player = create_player(session, events_by_hashkey[hashkey])
+
+ log.debug("Created player {0} ({2}) with hashkey {1}"
+ .format(player.player_id, hashkey, player.nick.encode('utf-8')))
+
+ players_by_hashkey[hashkey] = player
+
+ return players_by_hashkey
+
+
def submit_stats(request):
"""
Entry handler for POST stats submissions.
game = create_game(
session=session,
- start_dt=datetime.datetime.utcnow(),
- server_id=server.server_id,
game_type_cd=submission.game_type_cd,
+ mod=submission.mod,
+ server_id=server.server_id,
map_id=gmap.map_id,
match_id=submission.match_id,
- duration=submission.duration,
- mod=submission.mod
+ start_dt=datetime.datetime.utcnow(),
+ duration=submission.duration
)
- # keep track of the players we've seen
- player_ids = []
+ events_by_hashkey = {elem["P"]: elem for elem in submission.humans + submission.bots}
+ players_by_hashkey = get_or_create_players(session, events_by_hashkey)
+
pgstats = []
- hashkeys = {}
- for events in submission.humans + submission.bots:
- player = get_or_create_player(session, events['P'], events.get('n', None))
+ 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 player.player_id > 2:
player_ids.append(player.player_id)
- hashkeys[player.player_id] = events['P']
+ hashkeys_by_player_id[player.player_id] = hashkey
if should_do_weapon_stats(submission.game_type_cd) and player.player_id > 1:
create_weapon_stats(session, submission.version, game, player, pgstat, events)
- # store them on games for easy access
+ # player_ids for human players get stored directly on games for fast indexing
game.players = player_ids
for events in submission.teams:
"game": game,
"gmap": gmap,
"player_ids": player_ids,
- "hashkeys": hashkeys,
+ "hashkeys": hashkeys_by_player_id,
"elos": ep.wip,
"ranks": ranks,
}