from sqlalchemy import Sequence
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from xonstat.d0_blind_id import d0_blind_id_verify
+from xonstat.elo import process_elos
from xonstat.models import *
from xonstat.util import strip_colors, qfont_decode
for events in players:
if is_real_player(events):
for (key,value) in events.items():
- if key == 'scoreboard-score' and value != '0':
+ if key == 'scoreboard-score' and value != 0:
flg_nonzero_score = True
if r.search(key):
flg_acc_events = True
real_players = num_real_players(player_events)
- #TODO: put this into a config setting in the ini file?
if real_players < minimum_required_players:
flg_has_min_real_players = False
return flg_has_min_real_players
+def verify_requests(settings):
+ """
+ Determines whether or not to verify requests using the blind_id algorithm
+ """
+ try:
+ val_verify_requests = settings['xonstat.verify_requests']
+ if val_verify_requests == "true":
+ flg_verify_requests = True
+ else:
+ flg_verify_requests = False
+ except:
+ flg_verify_requests = True
+
+ return flg_verify_requests
+
+
def has_required_metadata(metadata):
"""
Determines if a give set of metadata has enough data to create a game,
new_nick - the new nickname
"""
# see if that nick already exists
- stripped_nick = strip_colors(player.nick)
+ stripped_nick = strip_colors(qfont_decode(player.nick))
try:
player_nick = session.query(PlayerNick).filter_by(
player_id=player.player_id, stripped_nick=stripped_nick).one()
if not re.search('^Anonymous Player #\d+$', player.nick):
player_nick = PlayerNick()
player_nick.player_id = player.player_id
- player_nick.stripped_nick = player.stripped_nick
+ player_nick.stripped_nick = stripped_nick
player_nick.nick = player.nick
session.add(player_nick)
# We change to the new nick regardless
player.nick = new_nick
- player.stripped_nick = strip_colors(new_nick)
+ player.stripped_nick = strip_colors(qfont_decode(new_nick))
session.add(player)
+def update_fastest_cap(session, player_id, game_id, map_id, captime):
+ """
+ Check the fastest cap time for the player and map. If there isn't
+ one, insert one. If there is, check if the passed time is faster.
+ If so, update!
+ """
+ # we don't record fastest cap times for bots or anonymous players
+ if player_id <= 2:
+ return
+
+ # see if a cap entry exists already
+ # then check to see if the new captime is faster
+ try:
+ cur_fastest_cap = session.query(PlayerCaptime).filter_by(
+ player_id=player_id, map_id=map_id).one()
+
+ # current captime is faster, so update
+ if captime < cur_fastest_cap.fastest_cap:
+ cur_fastest_cap.fastest_cap = captime
+ cur_fastest_cap.game_id = game_id
+ cur_fastest_cap.create_dt = datetime.datetime.utcnow()
+ session.add(cur_fastest_cap)
+
+ except NoResultFound, e:
+ # none exists, so insert
+ cur_fastest_cap = PlayerCaptime(player_id, game_id, map_id, captime)
+ session.add(cur_fastest_cap)
+ session.flush()
+
+
def get_or_create_server(session=None, name=None, hashkey=None, ip_addr=None,
revision=None):
"""
def create_game(session=None, start_dt=None, game_type_cd=None,
- server_id=None, map_id=None, winner=None, match_id=None):
+ server_id=None, map_id=None, winner=None, match_id=None,
+ duration=None):
"""
Creates a game. Parameters:
server_id=server_id, map_id=map_id, winner=winner)
game.match_id = match_id
+ try:
+ game.duration = datetime.timedelta(seconds=int(round(float(duration))))
+ except:
+ pass
+
try:
session.query(Game).filter(Game.server_id==server_id).\
filter(Game.match_id==match_id).one()
except NoResultFound, e:
# 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))
# with a suffix added for uniqueness.
if nick:
player.nick = nick[:128]
- player.stripped_nick = strip_colors(nick[:128])
+ player.stripped_nick = strip_colors(qfont_decode(nick[:128]))
else:
player.nick = "Anonymous Player #{0}".format(player.player_id)
player.stripped_nick = player.nick
#set game id from game record
pgstat.game_id = game.game_id
- # all games have a score
+ # all games have a score and every player has an alivetime
pgstat.score = 0
+ pgstat.alivetime = datetime.timedelta(seconds=0)
if game.game_type_cd == 'dm' or game.game_type_cd == 'tdm' or game.game_type_cd == 'duel':
pgstat.kills = 0
pgstat.carrier_frags = 0
for (key,value) in player_events.items():
- if key == 'n': pgstat.nick = value[:128]
- if key == 't': pgstat.team = value
- if key == 'rank': pgstat.rank = value
+ if key == 'n':
+ pgstat.nick = value[:128]
+ pgstat.stripped_nick = strip_colors(qfont_decode(pgstat.nick))
+ if key == 't': pgstat.team = int(value)
+ if key == 'rank': pgstat.rank = int(value)
if key == 'alivetime':
pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
- if key == 'scoreboard-drops': pgstat.drops = value
- if key == 'scoreboard-returns': pgstat.returns = value
- if key == 'scoreboard-fckills': pgstat.carrier_frags = value
- if key == 'scoreboard-pickups': pgstat.pickups = value
- if key == 'scoreboard-caps': pgstat.captures = value
- if key == 'scoreboard-score': pgstat.score = value
- if key == 'scoreboard-deaths': pgstat.deaths = value
- if key == 'scoreboard-kills': pgstat.kills = value
- if key == 'scoreboard-suicides': pgstat.suicides = value
+ if key == 'scoreboard-drops': pgstat.drops = int(value)
+ if key == 'scoreboard-returns': pgstat.returns = int(value)
+ if key == 'scoreboard-fckills': pgstat.carrier_frags = int(value)
+ if key == 'scoreboard-pickups': pgstat.pickups = int(value)
+ if key == 'scoreboard-caps': pgstat.captures = int(value)
+ if key == 'scoreboard-score': pgstat.score = int(value)
+ if key == 'scoreboard-deaths': pgstat.deaths = int(value)
+ if key == 'scoreboard-kills': pgstat.kills = int(value)
+ if key == 'scoreboard-suicides': pgstat.suicides = int(value)
+ if key == 'scoreboard-captime':
+ pgstat.fastest_cap = datetime.timedelta(seconds=float(value)/100)
+ if key == 'avglatency': pgstat.avg_latency = float(value)
# check to see if we had a name, and if
# not use an anonymous handle
# if the player is ranked #1 and it is a team game, set the game's winner
# to be the team of that player
# FIXME: this is a hack, should be using the 'W' field (not present)
- if pgstat.rank == '1' and pgstat.team:
+ if pgstat.rank == 1 and pgstat.team:
game.winner = pgstat.team
session.add(game)
def create_player_weapon_stats(session=None, player=None,
- game=None, pgstat=None, player_events=None):
+ game=None, pgstat=None, player_events=None, game_meta=None):
"""
Creates accuracy records for each weapon used by a given player in a
given game. Parameters:
pgstat - Corresponding PlayerGameStat record for these weapon stats
player_events - dictionary containing the raw weapon values that need to be
transformed
+ game_meta - dictionary of game metadata (only used for stats version info)
"""
pwstats = []
+ # Version 1 of stats submissions doubled the data sent.
+ # To counteract this we divide the data by 2 only for
+ # POSTs coming from version 1.
+ try:
+ version = int(game_meta['V'])
+ if version == 1:
+ is_doubled = True
+ log.debug('NOTICE: found a version 1 request, halving the weapon stats...')
+ else:
+ is_doubled = False
+ except:
+ is_doubled = False
+
for (key,value) in player_events.items():
matched = re.search("acc-(.*?)-cnt-fired", key)
if matched:
pwstat.frags = int(round(float(
player_events['acc-' + weapon_cd + '-frags'])))
+ if is_doubled:
+ pwstat.fired = pwstat.fired/2
+ pwstat.max = pwstat.max/2
+ pwstat.hit = pwstat.hit/2
+ pwstat.actual = pwstat.actual/2
+ pwstat.frags = pwstat.frags/2
+
session.add(pwstat)
pwstats.append(pwstat)
if key in 'S' 'n':
value = unicode(value, 'utf-8')
- if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W' 'I':
+ if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W' 'I' 'D':
game_meta[key] = value
if key == 'P':
def create_player_stats(session=None, player=None, game=None,
- player_events=None):
+ player_events=None, game_meta=None):
"""
Creates player game and weapon stats according to what type of player
"""
pgstat = create_player_game_stat(session=session,
player=player, game=game, player_events=player_events)
- #TODO: put this into a config setting in the ini file?
+ # fastest cap "upsert"
+ if game.game_type_cd == 'ctf' and pgstat.fastest_cap is not None:
+ update_fastest_cap(session, pgstat.player_id, game.game_id,
+ game.map_id, pgstat.fastest_cap)
+
+ # bots don't get weapon stats. sorry, bots!
if not re.search('^bot#\d+$', player_events['P']):
create_player_weapon_stats(session=session,
player=player, game=game, pgstat=pgstat,
- player_events=player_events)
+ player_events=player_events, game_meta=game_meta)
def stats_submit(request):
Entry handler for POST stats submissions.
"""
try:
- session = DBSession()
+ # placeholder for the actual session
+ session = None
log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +
"----- END REQUEST BODY -----\n\n")
(idfp, status) = verify_request(request)
- if not idfp:
- log.debug("ERROR: Unverified request")
- raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")
+ if verify_requests(request.registry.settings):
+ if not idfp:
+ log.debug("ERROR: Unverified request")
+ raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")
- (game_meta, players) = parse_body(request)
+ (game_meta, players) = parse_body(request)
if not has_required_metadata(game_meta):
log.debug("ERROR: Required game meta missing")
except:
revision = "unknown"
+ #----------------------------------------------------------------------
+ # This ends the "precondition" section of sanity checks. All
+ # functions not requiring a database connection go ABOVE HERE.
+ #----------------------------------------------------------------------
+ session = DBSession()
+
server = get_or_create_server(session=session, hashkey=idfp,
name=game_meta['S'], revision=revision,
ip_addr=get_remote_addr(request))
gmap = get_or_create_map(session=session, name=game_meta['M'])
- # FIXME: use the gmtime instead of utcnow() when the timezone bug is
- # fixed
+ # duration is optional
+ if 'D' in game_meta:
+ duration = game_meta['D']
+ else:
+ duration = None
+
game = create_game(session=session,
start_dt=datetime.datetime.utcnow(),
#start_dt=datetime.datetime(
#*time.gmtime(float(game_meta['T']))[:6]),
server_id=server.server_id, game_type_cd=game_meta['G'],
- map_id=gmap.map_id, match_id=game_meta['I'])
+ map_id=gmap.map_id, match_id=game_meta['I'],
+ duration=duration)
# find or create a record for each player
# and add stats for each if they were present at the end
hashkey=player_events['P'], nick=nick)
log.debug('Creating stats for %s' % player_events['P'])
create_player_stats(session=session, player=player, game=game,
- player_events=player_events)
+ player_events=player_events, game_meta=game_meta)
# update elos
try:
- game.process_elos(session)
+ process_elos(game, session)
except Exception as e:
log.debug('Error (non-fatal): elo processing failed.')
log.debug('Success! Stats recorded.')
return Response('200 OK')
except Exception as e:
- session.rollback()
+ if session:
+ session.rollback()
return e