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