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