]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views/player.py
Clean up the graph, make a new JSON view for accuracy data.
[xonotic/xonstat.git] / xonstat / views / player.py
1 import datetime\r
2 import json\r
3 import logging\r
4 import re\r
5 import sqlalchemy as sa\r
6 import sqlalchemy.sql.functions as func\r
7 import time\r
8 from pyramid.response import Response\r
9 from pyramid.url import current_route_url\r
10 from sqlalchemy import desc\r
11 from webhelpers.paginate import Page, PageURL\r
12 from xonstat.models import *\r
13 from xonstat.util import page_url\r
14 \r
15 log = logging.getLogger(__name__)\r
16 \r
17 \r
18 def player_index(request):\r
19     """\r
20     Provides a list of all the current players. \r
21     """\r
22     if 'page' in request.matchdict:\r
23         current_page = int(request.matchdict['page'])\r
24     else:\r
25         current_page = 1\r
26 \r
27     try:\r
28         player_q = DBSession.query(Player).\\r
29                 filter(Player.player_id > 2).\\r
30                 filter(Player.active_ind == True).\\r
31                 filter(sa.not_(Player.nick.like('Anonymous Player%'))).\\r
32                 order_by(Player.player_id.desc())\r
33 \r
34         players = Page(player_q, current_page, items_per_page=10, url=page_url)\r
35 \r
36         last_linked_page = current_page + 4\r
37         if last_linked_page > players.last_page:\r
38             last_linked_page = players.last_page\r
39 \r
40         pages_to_link = range(current_page+1, last_linked_page+1)\r
41 \r
42     except Exception as e:\r
43         players = None\r
44         raise e\r
45 \r
46     return {'players':players,\r
47             'pages_to_link':pages_to_link,\r
48             }\r
49 \r
50 \r
51 def get_games_played(player_id):\r
52     """\r
53     Provides a breakdown by gametype of the games played by player_id.\r
54 \r
55     Returns a tuple containing (total_games, games_breakdown), where\r
56     total_games is the absolute number of games played by player_id\r
57     and games_breakdown is an array containing (game_type_cd, # games)\r
58     """\r
59     games_played = DBSession.query(Game.game_type_cd, func.count()).\\r
60             filter(Game.game_id == PlayerGameStat.game_id).\\r
61             filter(PlayerGameStat.player_id == player_id).\\r
62             group_by(Game.game_type_cd).\\r
63             order_by(func.count().desc()).all()\r
64 \r
65     total = 0\r
66     for (game_type_cd, games) in games_played:\r
67         total += games\r
68 \r
69     return (total, games_played)\r
70 \r
71 \r
72 # TODO: should probably factor the above function into this one such that\r
73 # total_stats['ctf_games'] is the count of CTF games and so on...\r
74 def get_total_stats(player_id):\r
75     """\r
76     Provides aggregated stats by player_id.\r
77 \r
78     Returns a dict with the keys 'kills', 'deaths', 'alivetime'.\r
79 \r
80     kills = how many kills a player has over all games\r
81     deaths = how many deaths a player has over all games\r
82     alivetime = how long a player has played over all games\r
83 \r
84     If any of the above are None, they are set to 0.\r
85     """\r
86     total_stats = {}\r
87     (total_stats['kills'], total_stats['deaths'], total_stats['alivetime']) = DBSession.\\r
88             query("total_kills", "total_deaths", "total_alivetime").\\r
89             from_statement(\r
90                 "select sum(kills) total_kills, "\r
91                 "sum(deaths) total_deaths, "\r
92                 "sum(alivetime) total_alivetime "\r
93                 "from player_game_stats "\r
94                 "where player_id=:player_id"\r
95             ).params(player_id=player_id).one()\r
96 \r
97     (total_stats['wins'],) = DBSession.\\r
98             query("total_wins").\\r
99             from_statement(\r
100                 "select count(*) total_wins "\r
101                 "from games g, player_game_stats pgs "\r
102                 "where g.game_id = pgs.game_id "\r
103                 "and player_id=:player_id "\r
104                 "and (g.winner = pgs.team or pgs.rank = 1)"\r
105             ).params(player_id=player_id).one()\r
106 \r
107     for (key,value) in total_stats.items():\r
108         if value == None:\r
109             total_stats[key] = 0\r
110 \r
111     return total_stats\r
112 \r
113 \r
114 def get_accuracy_stats(player_id, weapon_cd, games):\r
115     """\r
116     Provides accuracy for weapon_cd by player_id for the past N games.\r
117     """\r
118     # Reaching back 90 days should give us an accurate enough average\r
119     # We then multiply this out for the number of data points (games) to\r
120     # create parameters for a flot graph\r
121     try:\r
122         raw_avg = DBSession.query(func.sum(PlayerWeaponStat.hit),\r
123                 func.sum(PlayerWeaponStat.fired)).\\r
124                 filter(PlayerWeaponStat.player_id == player_id).\\r
125                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\\r
126                 one()\r
127 \r
128         raw_avg = round(float(raw_avg[0])/raw_avg[1]*100, 2)\r
129 \r
130         avg = []\r
131         for i in range(games):\r
132             avg.append((i, raw_avg))\r
133 \r
134         # Determine the raw accuracy (hit, fired) numbers for $games games\r
135         # This is then enumerated to create parameters for a flot graph\r
136         raw_accs = DBSession.query(PlayerWeaponStat.game_id, \r
137             PlayerWeaponStat.hit, PlayerWeaponStat.fired).\\r
138                 filter(PlayerWeaponStat.player_id == player_id).\\r
139                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\\r
140                 order_by(PlayerWeaponStat.game_id.desc()).\\r
141                 limit(games).\\r
142                 all()\r
143 \r
144         # they come out in opposite order, so flip them in the right direction\r
145         raw_accs.reverse()\r
146 \r
147         games = []\r
148         accs = []\r
149         for i in range(len(raw_accs)):\r
150             games.append((i, raw_accs[i][0]))\r
151             accs.append((i, round(float(raw_accs[i][1])/raw_accs[i][2]*100, 2)))\r
152     except:\r
153         accs = 0\r
154         avg = 0\r
155 \r
156     return (games, avg, accs)\r
157 \r
158 \r
159 def player_info(request):\r
160     """\r
161     Provides detailed information on a specific player\r
162     """\r
163     player_id = int(request.matchdict['id'])\r
164     if player_id <= 2:\r
165         player_id = -1;\r
166 \r
167     try:\r
168         player = DBSession.query(Player).filter_by(player_id=player_id).\\r
169                 filter(Player.active_ind == True).one()\r
170 \r
171         # games played, alivetime, wins, kills, deaths\r
172         total_stats = get_total_stats(player.player_id)\r
173 \r
174         # games breakdown - N games played (X ctf, Y dm) etc\r
175         (total_games, games_breakdown) = get_games_played(player.player_id)\r
176 \r
177 \r
178         # friendly display of elo information and preliminary status\r
179         elos = DBSession.query(PlayerElo).filter_by(player_id=player_id).\\r
180                 filter(PlayerElo.game_type_cd.in_(['ctf','duel','dm'])).\\r
181                 order_by(PlayerElo.elo.desc()).all()\r
182 \r
183         elos_display = []\r
184         for elo in elos:\r
185             if elo.games > 32:\r
186                 str = "{0} ({1})"\r
187             else:\r
188                 str = "{0}* ({1})"\r
189 \r
190             elos_display.append(str.format(round(elo.elo, 3),\r
191                 elo.game_type_cd))\r
192 \r
193         # data for the accuracy graph, which is converted into a JSON array for\r
194         # usage by flot\r
195         (games, avg, accs) = get_accuracy_stats(player_id, 'nex', 20)\r
196 \r
197         avg = json.dumps(avg)\r
198         accs = json.dumps(accs)\r
199 \r
200 \r
201         # recent games table, all data\r
202         recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\\r
203                 filter(PlayerGameStat.player_id == player_id).\\r
204                 filter(PlayerGameStat.game_id == Game.game_id).\\r
205                 filter(Game.server_id == Server.server_id).\\r
206                 filter(Game.map_id == Map.map_id).\\r
207                 order_by(Game.game_id.desc())[0:10]\r
208 \r
209     except Exception as e:\r
210         player = None\r
211         elos_display = None\r
212         total_stats = None\r
213         recent_games = None\r
214         total_games = None\r
215         games_breakdown = None\r
216         avg = None\r
217         accs = None\r
218 \r
219     return {'player':player,\r
220             'elos_display':elos_display,\r
221             'recent_games':recent_games,\r
222             'total_stats':total_stats,\r
223             'total_games':total_games,\r
224             'games_breakdown':games_breakdown,\r
225             'avg':avg,\r
226             'accs':accs,\r
227             }\r
228 \r
229 \r
230 def player_game_index(request):\r
231     """\r
232     Provides an index of the games in which a particular\r
233     player was involved. This is ordered by game_id, with\r
234     the most recent game_ids first. Paginated.\r
235     """\r
236     player_id = request.matchdict['player_id']\r
237 \r
238     if 'page' in request.matchdict:\r
239         current_page = request.matchdict['page']\r
240     else:\r
241         current_page = 1\r
242 \r
243     try:\r
244         games_q = DBSession.query(Game, Server, Map).\\r
245             filter(PlayerGameStat.game_id == Game.game_id).\\r
246             filter(PlayerGameStat.player_id == player_id).\\r
247             filter(Game.server_id == Server.server_id).\\r
248             filter(Game.map_id == Map.map_id).\\r
249             order_by(Game.game_id.desc())\r
250 \r
251         games = Page(games_q, current_page, url=page_url)\r
252 \r
253         pgstats = {}\r
254         for (game, server, map) in games:\r
255             pgstats[game.game_id] = DBSession.query(PlayerGameStat).\\r
256                     filter(PlayerGameStat.game_id == game.game_id).\\r
257                     order_by(PlayerGameStat.rank).\\r
258                     order_by(PlayerGameStat.score).all()\r
259 \r
260     except Exception as e:\r
261         player = None\r
262         games = None\r
263 \r
264     return {'player_id':player_id,\r
265             'games':games,\r
266             'pgstats':pgstats}\r
267 \r
268 def player_accuracy(request):\r
269     """\r
270     Provides a JSON response representing the accuracy for the given weapon.\r
271 \r
272     Parameters:\r
273        weapon = which weapon to display accuracy for. Valid values are 'nex',\r
274                 'shotgun', 'uzi', and 'minstanex'.\r
275        games = over how many games to display accuracy. Can be up to 50.\r
276     """\r
277     player_id = request.matchdict['id']\r
278     allowed_weapons = ['nex', 'shotgun', 'uzi', 'minstanex']\r
279     weapon_cd = 'nex'\r
280     games = 20\r
281 \r
282     if request.params.has_key('weapon'):\r
283         if request.params['weapon'] in allowed_weapons:\r
284             weapon_cd = request.params['weapon']\r
285 \r
286     (games, avg, accs) = get_accuracy_stats(player_id, weapon_cd, games)\r
287 \r
288     return {'weapon':weapon_cd, 'games':games, 'avg':avg, 'accs':accs}\r
289 \r