]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views/player.py
Merge branch 'badges' into approved
[xonotic/xonstat.git] / xonstat / views / player.py
1 import datetime
2 import json
3 import logging
4 import re
5 import sqlalchemy as sa
6 import sqlalchemy.sql.functions as func
7 import time
8 from pyramid.response import Response
9 from pyramid.url import current_route_url
10 from sqlalchemy import desc, distinct
11 from webhelpers.paginate import Page, PageURL
12 from xonstat.models import *
13 from xonstat.util import page_url
14
15 log = logging.getLogger(__name__)
16
17
18 def player_index_data(request):
19     if request.params.has_key('page'):
20         current_page = request.params['page']
21     else:
22         current_page = 1
23
24     try:
25         player_q = DBSession.query(Player).\
26                 filter(Player.player_id > 2).\
27                 filter(Player.active_ind == True).\
28                 filter(sa.not_(Player.nick.like('Anonymous Player%'))).\
29                 order_by(Player.player_id.desc())
30
31         players = Page(player_q, current_page, items_per_page=10, url=page_url)
32
33     except Exception as e:
34         players = None
35         raise e
36
37     return {'players':players
38            }
39
40
41 def player_index(request):
42     """
43     Provides a list of all the current players.
44     """
45     return player_index_data(request)
46
47
48 def player_index_json(request):
49     """
50     Provides a list of all the current players. JSON.
51     """
52     return [{'status':'not implemented'}]
53
54
55 def _get_games_played(player_id):
56     """
57     DEPRECATED: Now included in _get_total_stats()
58     
59     Provides a breakdown by gametype of the games played by player_id.
60
61     Returns a tuple containing (total_games, games_breakdown), where
62     total_games is the absolute number of games played by player_id
63     and games_breakdown is an array containing (game_type_cd, # games)
64     """
65     games_played = DBSession.query(Game.game_type_cd, func.count()).\
66             filter(Game.game_id == PlayerGameStat.game_id).\
67             filter(PlayerGameStat.player_id == player_id).\
68             group_by(Game.game_type_cd).\
69             order_by(func.count().desc()).all()
70
71     total = 0
72     for (game_type_cd, games) in games_played:
73         total += games
74
75     return (total, games_played)
76
77
78 def _get_total_stats(player_id):
79     """
80     Provides aggregated stats by player_id.
81
82     Returns a dict with the keys 'kills', 'deaths', 'alivetime'.
83
84     games = how many games a player has played
85     games_breakdown = how many games of given type a player has played (dictionary)
86     games_alivetime = how many time a player has spent in a give game type (dictionary)
87     kills = how many kills a player has over all games
88     deaths = how many deaths a player has over all games
89     suicides = how many suicides a player has over all games
90     alivetime = how long a player has played over all games
91     alivetime_week = how long a player has played over all games in the last week
92     alivetime_month = how long a player has played over all games in the last month
93     wins = how many games a player has won
94
95     If any of the above are None, they are set to 0.
96     """
97     # 7 and 30 day windows
98     one_week_ago  = datetime.datetime.utcnow() - datetime.timedelta(days=7)
99     one_month_ago = datetime.datetime.utcnow() - datetime.timedelta(days=30)
100
101     total_stats = {}
102
103     games_played = DBSession.query(
104             Game.game_type_cd, func.count(), func.sum(PlayerGameStat.alivetime)).\
105             filter(Game.game_id == PlayerGameStat.game_id).\
106             filter(PlayerGameStat.player_id == player_id).\
107             group_by(Game.game_type_cd).\
108             order_by(func.count().desc()).\
109             all()
110
111     total_stats['games'] = 0
112     total_stats['games_breakdown'] = {}  # this is a dictionary inside a dictionary .. dictception?
113     total_stats['games_alivetime'] = {}
114     for (game_type_cd, games, alivetime) in games_played:
115         total_stats['games'] += games
116         total_stats['games_breakdown'][game_type_cd] = games
117         total_stats['games_alivetime'][game_type_cd] = alivetime
118
119      # more fields can be added here, e.g. 'collects' for kh games
120     (total_stats['kills'], total_stats['deaths'], total_stats['suicides'],
121      total_stats['alivetime'],) = DBSession.query(
122             func.sum(PlayerGameStat.kills),
123             func.sum(PlayerGameStat.deaths),
124             func.sum(PlayerGameStat.suicides),
125             func.sum(PlayerGameStat.alivetime)).\
126             filter(PlayerGameStat.player_id == player_id).\
127             one()
128
129     (total_stats['alivetime_week'],) = DBSession.query(
130             func.sum(PlayerGameStat.alivetime)).\
131             filter(PlayerGameStat.player_id == player_id).\
132             filter(PlayerGameStat.create_dt > one_week_ago).\
133             one()
134
135     (total_stats['alivetime_month'],) = DBSession.query(
136             func.sum(PlayerGameStat.alivetime)).\
137             filter(PlayerGameStat.player_id == player_id).\
138             filter(PlayerGameStat.create_dt > one_month_ago).\
139             one()
140
141     (total_stats['wins'],) = DBSession.\
142             query("total_wins").\
143             from_statement(
144                 "select count(*) total_wins "
145                 "from games g, player_game_stats pgs "
146                 "where g.game_id = pgs.game_id "
147                 "and player_id=:player_id "
148                 "and (g.winner = pgs.team or pgs.rank = 1)"
149             ).params(player_id=player_id).one()
150
151 #    (total_stats['wins'],) = DBSession.query(
152 #            func.count("*")).\
153 #            filter(Game.game_id == PlayerGameStat.game_id).\
154 #            filter(PlayerGameStat.player_id == player_id).\
155 #            filter(Game.winner == PlayerGameStat.team or PlayerGameStat.rank == 1).\
156 #            one()
157
158     (total_stats['duel_wins'],) = DBSession.query(
159             func.count("*")).\
160             filter(Game.game_id == PlayerGameStat.game_id).\
161             filter(Game.game_type_cd == "duel").\
162             filter(PlayerGameStat.player_id == player_id).\
163             filter(PlayerGameStat.rank == 1).\
164             one()
165
166     (total_stats['duel_kills'], total_stats['duel_deaths'], total_stats['duel_suicides'],) = DBSession.query(
167             func.sum(PlayerGameStat.kills),
168             func.sum(PlayerGameStat.deaths),
169             func.sum(PlayerGameStat.suicides)).\
170             filter(Game.game_id == PlayerGameStat.game_id).\
171             filter(Game.game_type_cd == "duel").\
172             filter(PlayerGameStat.player_id == player_id).\
173             one()
174
175     (total_stats['dm_wins'],) = DBSession.query(
176             func.count("*")).\
177             filter(Game.game_id == PlayerGameStat.game_id).\
178             filter(Game.game_type_cd == "dm").\
179             filter(PlayerGameStat.player_id == player_id).\
180             filter(PlayerGameStat.rank == 1).\
181             one()
182
183     (total_stats['dm_kills'], total_stats['dm_deaths'], total_stats['dm_suicides'],) = DBSession.query(
184             func.sum(PlayerGameStat.kills),
185             func.sum(PlayerGameStat.deaths),
186             func.sum(PlayerGameStat.suicides)).\
187             filter(Game.game_id == PlayerGameStat.game_id).\
188             filter(Game.game_type_cd == "dm").\
189             filter(PlayerGameStat.player_id == player_id).\
190             one()
191
192     (total_stats['tdm_kills'], total_stats['tdm_deaths'], total_stats['tdm_suicides'],) = DBSession.query(
193             func.sum(PlayerGameStat.kills),
194             func.sum(PlayerGameStat.deaths),
195             func.sum(PlayerGameStat.suicides)).\
196             filter(Game.game_id == PlayerGameStat.game_id).\
197             filter(Game.game_type_cd == "tdm").\
198             filter(PlayerGameStat.player_id == player_id).\
199             one()
200
201     (total_stats['tdm_wins'],) = DBSession.query(
202             func.count("*")).\
203             filter(Game.game_id == PlayerGameStat.game_id).\
204             filter(Game.game_type_cd == "tdm").\
205             filter(PlayerGameStat.player_id == player_id).\
206             filter(PlayerGameStat.rank == 1).\
207             one()
208
209     (total_stats['ctf_wins'],) = DBSession.query(
210             func.count("*")).\
211             filter(Game.game_id == PlayerGameStat.game_id).\
212             filter(Game.game_type_cd == "ctf").\
213             filter(PlayerGameStat.player_id == player_id).\
214             filter(PlayerGameStat.rank == 1).\
215             one()
216
217     (total_stats['ctf_caps'], total_stats['ctf_pickups'], total_stats['ctf_drops'],
218      total_stats['ctf_returns'], total_stats['ctf_fckills'],) = DBSession.query(
219             func.sum(PlayerGameStat.captures),
220             func.sum(PlayerGameStat.pickups),
221             func.sum(PlayerGameStat.drops),
222             func.sum(PlayerGameStat.returns),
223             func.sum(PlayerGameStat.carrier_frags)).\
224             filter(Game.game_id == PlayerGameStat.game_id).\
225             filter(Game.game_type_cd == "ctf").\
226             filter(PlayerGameStat.player_id == player_id).\
227             one()
228
229     for (key,value) in total_stats.items():
230         if value == None:
231             total_stats[key] = 0
232
233     return total_stats
234
235
236 def _get_fav_map(player_id):
237     """
238     Get the player's favorite map. The favorite map is defined
239     as the map that he or she has played the most in the past 
240     90 days.
241
242     Returns a dictionary with keys for the map's name and id.
243     """
244     # 90 day window
245     back_then = datetime.datetime.utcnow() - datetime.timedelta(days=90)
246
247     raw_fav_map = DBSession.query(Map.name, Map.map_id).\
248             filter(Game.game_id == PlayerGameStat.game_id).\
249             filter(Game.map_id == Map.map_id).\
250             filter(PlayerGameStat.player_id == player_id).\
251             filter(PlayerGameStat.create_dt > back_then).\
252             group_by(Map.name, Map.map_id).\
253             order_by(func.count().desc()).\
254             limit(5).all()
255
256     fav_map = []
257     for map_e in raw_fav_map:
258         entry = {}
259         entry['name'] = map_e[0]
260         entry['id']   = map_e[1]
261         fav_map.append(entry)
262
263     return fav_map
264
265
266 def _get_fav_weapon(player_id):
267     """
268     Get the player's favorite weapon. The favorite weapon is defined
269     as the weapon that he or she has employed the most in the past 
270     90 days.
271
272     Returns a sequence of dictionaries with keys for the weapon's name and id.
273     The sequence holds the most-used weapons in decreasing order.
274     """
275     # 90 day window
276     back_then = datetime.datetime.utcnow() - datetime.timedelta(days=90)
277
278     raw_fav_weapon = DBSession.query(Weapon.descr, Weapon.weapon_cd).\
279             filter(Game.game_id == PlayerWeaponStat.game_id).\
280             filter(PlayerWeaponStat.player_id == player_id).\
281             filter(PlayerWeaponStat.weapon_cd == Weapon.weapon_cd).\
282             filter(PlayerWeaponStat.create_dt > back_then).\
283             group_by(Weapon.descr, Weapon.weapon_cd).\
284             order_by(func.count().desc()).\
285             limit(5).all()
286
287     fav_weapon = []
288     for wpn_e in raw_fav_weapon:
289         entry = {}
290         entry['name'] = wpn_e[0]
291         entry['id']   = wpn_e[1]
292         fav_weapon.append(entry)
293
294     return fav_weapon
295
296
297 def _get_fav_server(player_id):
298     """
299     Get the player's favorite server. The favorite server is defined
300     as the server that he or she has played on the most in the past 
301     90 days.
302
303     Returns a sequence of dictionaries with keys for the server's name and id.
304     The sequence holds the most-used servers in decreasing order.
305     """
306     # 90 day window
307     back_then = datetime.datetime.utcnow() - datetime.timedelta(days=90)
308
309     raw_fav_server = DBSession.query(Server.name, Server.server_id).\
310             filter(Game.game_id == PlayerGameStat.game_id).\
311             filter(Game.server_id == Server.server_id).\
312             filter(PlayerGameStat.player_id == player_id).\
313             filter(PlayerGameStat.create_dt > back_then).\
314             group_by(Server.name, Server.server_id).\
315             order_by(func.count().desc()).\
316             limit(5).all()
317
318     fav_server = []
319     for srv_e in raw_fav_server:
320         entry = {}
321         entry['name'] = srv_e[0]
322         entry['id']   = srv_e[1]
323         fav_server.append(entry)
324
325     return fav_server
326
327
328 def _get_rank(player_id):
329     """
330     Get the player's rank as well as the total number of ranks.
331     """
332     rank = DBSession.query("game_type_cd", "rank", "max_rank").\
333             from_statement(
334                 "select pr.game_type_cd, pr.rank, overall.max_rank "
335                 "from player_ranks pr,  "
336                    "(select game_type_cd, max(rank) max_rank "
337                     "from player_ranks  "
338                     "group by game_type_cd) overall "
339                 "where pr.game_type_cd = overall.game_type_cd  "
340                 "and player_id = :player_id "
341                 "order by rank").\
342             params(player_id=player_id).all()
343
344     return rank;
345
346
347 def get_accuracy_stats(player_id, weapon_cd, games):
348     """
349     Provides accuracy for weapon_cd by player_id for the past N games.
350     """
351     # Reaching back 90 days should give us an accurate enough average
352     # We then multiply this out for the number of data points (games) to
353     # create parameters for a flot graph
354     try:
355         raw_avg = DBSession.query(func.sum(PlayerWeaponStat.hit),
356                 func.sum(PlayerWeaponStat.fired)).\
357                 filter(PlayerWeaponStat.player_id == player_id).\
358                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
359                 one()
360
361         avg = round(float(raw_avg[0])/raw_avg[1]*100, 2)
362
363         # Determine the raw accuracy (hit, fired) numbers for $games games
364         # This is then enumerated to create parameters for a flot graph
365         raw_accs = DBSession.query(PlayerWeaponStat.game_id, 
366             PlayerWeaponStat.hit, PlayerWeaponStat.fired).\
367                 filter(PlayerWeaponStat.player_id == player_id).\
368                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
369                 order_by(PlayerWeaponStat.game_id.desc()).\
370                 limit(games).\
371                 all()
372
373         # they come out in opposite order, so flip them in the right direction
374         raw_accs.reverse()
375
376         accs = []
377         for i in range(len(raw_accs)):
378             accs.append((raw_accs[i][0], round(float(raw_accs[i][1])/raw_accs[i][2]*100, 2)))
379     except:
380         accs = []
381         avg = 0.0
382
383     return (avg, accs)
384
385
386 def get_damage_stats(player_id, weapon_cd, games):
387     """
388     Provides damage info for weapon_cd by player_id for the past N games.
389     """
390     try:
391         raw_avg = DBSession.query(func.sum(PlayerWeaponStat.actual),
392                 func.sum(PlayerWeaponStat.hit)).\
393                 filter(PlayerWeaponStat.player_id == player_id).\
394                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
395                 one()
396
397         avg = round(float(raw_avg[0])/raw_avg[1], 2)
398
399         # Determine the damage efficiency (hit, fired) numbers for $games games
400         # This is then enumerated to create parameters for a flot graph
401         raw_dmgs = DBSession.query(PlayerWeaponStat.game_id, 
402             PlayerWeaponStat.actual, PlayerWeaponStat.hit).\
403                 filter(PlayerWeaponStat.player_id == player_id).\
404                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
405                 order_by(PlayerWeaponStat.game_id.desc()).\
406                 limit(games).\
407                 all()
408
409         # they come out in opposite order, so flip them in the right direction
410         raw_dmgs.reverse()
411
412         dmgs = []
413         for i in range(len(raw_dmgs)):
414             # try to derive, unless we've hit nothing then set to 0!
415             try:
416                 dmg = round(float(raw_dmgs[i][1])/raw_dmgs[i][2], 2)
417             except:
418                 dmg = 0.0
419
420             dmgs.append((raw_dmgs[i][0], dmg))
421     except Exception as e:
422         dmgs = []
423         avg = 0.0
424
425     return (avg, dmgs)
426
427
428 def player_info_data(request):
429     player_id = int(request.matchdict['id'])
430     if player_id <= 2:
431         player_id = -1;
432
433     try:
434         player = DBSession.query(Player).filter_by(player_id=player_id).\
435                 filter(Player.active_ind == True).one()
436
437         # games played, alivetime, wins, kills, deaths
438         total_stats = _get_total_stats(player.player_id)
439
440         # games breakdown - N games played (X ctf, Y dm) etc
441         # DEPRECATED: included in total_stats, see above
442         # (total_games, games_breakdown) = _get_games_played(player.player_id)
443
444         # favorite map from the past 90 days
445         try:
446             fav_map = _get_fav_map(player.player_id)
447         except:
448             fav_map = None
449
450         # favorite weapon from the past 90 days
451         try:
452             fav_weapon = _get_fav_weapon(player.player_id)
453         except:
454             fav_weapon = None
455
456         # favorite server from the past 90 days
457         try:
458             fav_server = _get_fav_server(player.player_id)
459         except:
460             fav_server = None
461
462         # friendly display of elo information and preliminary status
463         elos = DBSession.query(PlayerElo).filter_by(player_id=player_id).\
464                 filter(PlayerElo.game_type_cd.in_(['ctf','duel','dm'])).\
465                 order_by(PlayerElo.elo.desc()).all()
466
467         elos_display = []
468         elos_dict    = {}
469         for elo in elos:
470             if elo.games > 32:
471                 str = "{0} ({1})"
472             else:
473                 str = "{0}* ({1})"
474
475             elos_display.append(str.format(round(elo.elo, 3),
476                 elo.game_type_cd))
477             elos_dict[elo.game_type_cd] = round(elo.elo, 3)
478         elos_display = ', '.join(elos_display)
479
480         # get current rank information
481         ranks = _get_rank(player_id)
482         
483         ranks_display = []
484         ranks_dict    = {}
485         for gtc,rank,max_rank in ranks:
486             ranks_display.append("{1} of {2} ({0})".format(gtc, rank, max_rank))
487             ranks_dict[gtc] = (rank, max_rank)
488         ranks_display = ', '.join(ranks_display)
489
490
491         # which weapons have been used in the past 90 days
492         # and also, used in 5 games or more?
493         back_then = datetime.datetime.utcnow() - datetime.timedelta(days=90)
494         recent_weapons = []
495         for weapon in DBSession.query(PlayerWeaponStat.weapon_cd, func.count()).\
496                 filter(PlayerWeaponStat.player_id == player_id).\
497                 filter(PlayerWeaponStat.create_dt > back_then).\
498                 group_by(PlayerWeaponStat.weapon_cd).\
499                 having(func.count() > 4).\
500                 all():
501                     recent_weapons.append(weapon[0])
502
503         # recent games table, all data
504         recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\
505                 filter(PlayerGameStat.player_id == player_id).\
506                 filter(PlayerGameStat.game_id == Game.game_id).\
507                 filter(Game.server_id == Server.server_id).\
508                 filter(Game.map_id == Map.map_id).\
509                 order_by(Game.game_id.desc())[0:10]
510
511     except Exception as e:
512         player = None
513         elos = None
514         elos_display = None
515         total_stats = None
516         recent_games = None
517         # DEPRECATED: included in total_stats, see above
518         #total_games = None
519         #games_breakdown = None
520         recent_weapons = []
521         fav_map = None
522         fav_weapon = None
523         fav_server = None
524         ranks = None
525         ranks_display = None;
526
527     return {'player':player,
528             'elos':elos_dict,
529             'elos_display':elos_display,
530             'recent_games':recent_games,
531             'total_stats':total_stats,
532             # DEPRECATED: included in total_stats, see above
533             #'total_games':total_games,
534             #'games_breakdown':games_breakdown,
535             'recent_weapons':recent_weapons,
536             'fav_map':fav_map,
537             'fav_weapon':fav_weapon,
538             'fav_server':fav_server,
539             'ranks':ranks_dict,
540             'ranks_display':ranks_display,
541             }
542
543
544 def player_info(request):
545     """
546     Provides detailed information on a specific player
547     """
548     return player_info_data(request)
549
550
551 def player_info_json(request):
552     """
553     Provides detailed information on a specific player. JSON.
554     """
555     return [{'status':'not implemented'}]
556
557
558 def player_game_index_data(request):
559     player_id = request.matchdict['player_id']
560
561     if request.params.has_key('page'):
562         current_page = request.params['page']
563     else:
564         current_page = 1
565
566     try:
567         games_q = DBSession.query(Game, Server, Map).\
568             filter(PlayerGameStat.game_id == Game.game_id).\
569             filter(PlayerGameStat.player_id == player_id).\
570             filter(Game.server_id == Server.server_id).\
571             filter(Game.map_id == Map.map_id).\
572             order_by(Game.game_id.desc())
573
574         games = Page(games_q, current_page, items_per_page=10, url=page_url)
575
576         pgstats = {}
577         for (game, server, map) in games:
578             pgstats[game.game_id] = DBSession.query(PlayerGameStat).\
579                     filter(PlayerGameStat.game_id == game.game_id).\
580                     order_by(PlayerGameStat.rank).\
581                     order_by(PlayerGameStat.score).all()
582
583     except Exception as e:
584         player = None
585         games = None
586
587     return {'player_id':player_id,
588             'games':games,
589             'pgstats':pgstats}
590
591
592 def player_game_index(request):
593     """
594     Provides an index of the games in which a particular
595     player was involved. This is ordered by game_id, with
596     the most recent game_ids first. Paginated.
597     """
598     return player_game_index_data(request)
599
600
601 def player_game_index_json(request):
602     """
603     Provides an index of the games in which a particular
604     player was involved. This is ordered by game_id, with
605     the most recent game_ids first. Paginated. JSON.
606     """
607     return [{'status':'not implemented'}]
608
609
610 def player_accuracy_data(request):
611     player_id = request.matchdict['id']
612     allowed_weapons = ['nex', 'rifle', 'shotgun', 'uzi', 'minstanex']
613     weapon_cd = 'nex'
614     games = 20
615
616     if request.params.has_key('weapon'):
617         if request.params['weapon'] in allowed_weapons:
618             weapon_cd = request.params['weapon']
619
620     if request.params.has_key('games'):
621         try:
622             games = request.params['games']
623
624             if games < 0:
625                 games = 20
626             if games > 50:
627                 games = 50
628         except:
629             games = 20
630
631     (avg, accs) = get_accuracy_stats(player_id, weapon_cd, games)
632
633     # if we don't have enough data for the given weapon
634     if len(accs) < games:
635         games = len(accs)
636
637     return {
638             'player_id':player_id, 
639             'player_url':request.route_url('player_info', id=player_id), 
640             'weapon':weapon_cd, 
641             'games':games, 
642             'avg':avg, 
643             'accs':accs
644             }
645
646
647 def player_accuracy(request):
648     """
649     Provides the accuracy for the given weapon. (JSON only)
650     """
651     return player_accuracy_data(request)
652
653
654 def player_accuracy_json(request):
655     """
656     Provides a JSON response representing the accuracy for the given weapon.
657
658     Parameters:
659        weapon = which weapon to display accuracy for. Valid values are 'nex',
660                 'shotgun', 'uzi', and 'minstanex'.
661        games = over how many games to display accuracy. Can be up to 50.
662     """
663     return player_accuracy_data(request)
664
665
666 def player_damage_data(request):
667     player_id = request.matchdict['id']
668     allowed_weapons = ['grenadelauncher', 'electro', 'crylink', 'hagar',
669             'rocketlauncher', 'laser']
670     weapon_cd = 'rocketlauncher'
671     games = 20
672
673     if request.params.has_key('weapon'):
674         if request.params['weapon'] in allowed_weapons:
675             weapon_cd = request.params['weapon']
676
677     if request.params.has_key('games'):
678         try:
679             games = request.params['games']
680
681             if games < 0:
682                 games = 20
683             if games > 50:
684                 games = 50
685         except:
686             games = 20
687
688     (avg, dmgs) = get_damage_stats(player_id, weapon_cd, games)
689
690     # if we don't have enough data for the given weapon
691     if len(dmgs) < games:
692         games = len(dmgs)
693
694     return {
695             'player_id':player_id, 
696             'player_url':request.route_url('player_info', id=player_id), 
697             'weapon':weapon_cd, 
698             'games':games, 
699             'avg':avg, 
700             'dmgs':dmgs
701             }
702
703
704 def player_damage_json(request):
705     """
706     Provides a JSON response representing the damage for the given weapon.
707
708     Parameters:
709        weapon = which weapon to display damage for. Valid values are
710          'grenadelauncher', 'electro', 'crylink', 'hagar', 'rocketlauncher',
711          'laser'.
712        games = over how many games to display damage. Can be up to 50.
713     """
714     return player_damage_data(request)