Merge branch 'master' into zykure/wip
authorJan Behrens <zykure@web.de>
Sat, 17 Aug 2013 10:40:08 +0000 (12:40 +0200)
committerJan Behrens <zykure@web.de>
Sat, 17 Aug 2013 10:40:08 +0000 (12:40 +0200)
Conflicts:
xonstat/static/css/app.css
xonstat/static/css/app.min.css
xonstat/templates/game_info.mako
xonstat/templates/scoreboard.mako
xonstat/views/submission.py

34 files changed:
XonStat.egg-info/PKG-INFO
XonStat.egg-info/SOURCES.txt
XonStat.egg-info/requires.txt
setup.py
xonstat/__init__.py
xonstat/batch/badges/skin.py
xonstat/models.py
xonstat/static/css/app.css
xonstat/static/css/app.min.css
xonstat/static/images/icons/24x24/cake.png [new file with mode: 0644]
xonstat/templates/404.mako [new file with mode: 0644]
xonstat/templates/game_index.mako
xonstat/templates/game_info.mako
xonstat/templates/main_index.mako
xonstat/templates/map_captimes.mako
xonstat/templates/map_index.mako
xonstat/templates/map_info.mako
xonstat/templates/player_captimes.mako
xonstat/templates/player_game_index.mako
xonstat/templates/player_hashkey_info_text.mako
xonstat/templates/player_info.mako
xonstat/templates/rank_index.mako
xonstat/templates/scoreboard.mako
xonstat/templates/search.mako
xonstat/templates/server_index.mako
xonstat/templates/server_info.mako
xonstat/util.py
xonstat/views/__init__.py
xonstat/views/exceptions.py [new file with mode: 0644]
xonstat/views/game.py
xonstat/views/helpers.py
xonstat/views/map.py
xonstat/views/player.py
xonstat/views/submission.py

index d5879af..c00fe2b 100644 (file)
@@ -1,4 +1,4 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: XonStat
 Version: 0.0
 Summary: XonStat
@@ -6,37 +6,45 @@ Home-page: UNKNOWN
 Author: UNKNOWN
 Author-email: UNKNOWN
 License: UNKNOWN
-Description: This is XonStat, the application in front of xonstatdb. XonStat handles the submission of statistical information from the open source first person shooter Xonotic. 
+Description: This is **XonStat**, the application in front of [xonstatdb][xonstatdb].  
+        [XonStat][xonstat] handles the submission of statistical information from the open source first person shooter [Xonotic][xonotic].
+        
+        ----
         
         To start, first run the following from the root directory to set up dependencies:
         
             python setup.py develop
         
-        Next you'll want to set up xonstatdb. This is maintained as a separate project here:
+        Next you'll want to set up [xonstatdb][xonstatdb]. This is maintained as a separate project here:
         
             https://github.com/antzucaro/xonstatdb
         
-        Next you'll want to open up development.ini and change a few things for added security. Chief among these is the "sqlalchemy.url" setting, which contains your username and password for the database. Change that match the new password you gave xonstat during the installation of xonstatdb. The other setting to change is "security.secret," which is used to keep your web session (cookies and such) secure. 
+        Next you'll want to open up development.ini and change a few things for added security.
+        Chief among these is the "sqlalchemy.url" setting, which contains your username and password for the database.
+        Change that match the new password you gave xonstat during the installation of xonstatdb.
+        The other setting to change is "session.secret," which is used to keep your web session (cookies and such) secure.
         
         To start the server run the following from the root directory. I recommend running this within a GNU screen session:
         
             paster serve development.ini #(or production.ini if you've configured that settings file instead)
         
-        To get a Xonotic server configured to use this server, change the CVAR "g_playerstats_uri" to point to the correct host, port, and URL path. By default this is:
+        To get a Xonotic server configured to use this server, change the CVAR `g_playerstats_uri` to point to the correct host, port, and URL path. By default this is:
         
             http://localhost:6543/stats/submit
         
-        ...so in the command line of the server (or in your config) you can put:
+        ...so in the server console (or in your config) you can put:
         
             set g_playerstats_uri http://localhost:6543/stats/submit
         
-        If you have any questions or issues please open up a bug report here, or - better yet ! - fork it and send me a pull request. 
+        If you have any questions or issues please open up a bug report here, or - better yet ! - fork it and send me a pull request.
         
-        TODO:
+        [xonstatdb]: https://github.com/antzucaro/xonstatdb
+        [xonstat]: http://stats.xonotic.org/
+        [xonotic]: http://www.xonotic.org/
         
-        - "e matches" and "e joins" seem to be mutually exclusive. Add a check for either (instead of just joins" before adding a player_game_stats record).
+        ----
         
-        - map names are being recorded multiple times in the maps table. They should be found when being played subsequent times. 
+        Project is licensed GPLv2+.
         
         
         0.0
index 84716af..6ae8789 100644 (file)
@@ -1,11 +1,11 @@
 CHANGES.txt
 COPYING.txt
 MANIFEST.in
-README.txt
 development.ini
 production.ini
 setup.cfg
 setup.py
+test.txt
 XonStat.egg-info/PKG-INFO
 XonStat.egg-info/SOURCES.txt
 XonStat.egg-info/dependency_links.txt
@@ -15,13 +15,281 @@ XonStat.egg-info/paster_plugins.txt
 XonStat.egg-info/requires.txt
 XonStat.egg-info/top_level.txt
 xonstat/__init__.py
+xonstat/d0_blind_id.py
+xonstat/elo.py
 xonstat/models.py
 xonstat/tests.py
 xonstat/util.py
+xonstat/batch/badges/img/asfalt.png
+xonstat/batch/badges/img/background_archer-v1.png
+xonstat/batch/badges/img/black_linen_v2.png
+xonstat/batch/badges/img/broken_noise.png
+xonstat/batch/badges/img/burried.png
+xonstat/batch/badges/img/dark_leather.png
+xonstat/batch/badges/img/dark_wall.png
+xonstat/batch/badges/img/overlay_classic.png
+xonstat/batch/badges/img/overlay_minimal.png
+xonstat/batch/badges/img/txture.png
+xonstat/batch/badges/output/10180.png
+xonstat/batch/badges/output/10427.png
+xonstat/batch/badges/output/10543.png
+xonstat/batch/badges/output/10551.png
+xonstat/batch/badges/output/1060.png
+xonstat/batch/badges/output/10627.png
+xonstat/batch/badges/output/10710.png
+xonstat/batch/badges/output/10738.png
+xonstat/batch/badges/output/10780.png
+xonstat/batch/badges/output/10814.png
+xonstat/batch/badges/output/10876.png
+xonstat/batch/badges/output/10939.png
+xonstat/batch/badges/output/10978.png
+xonstat/batch/badges/output/1101.png
+xonstat/batch/badges/output/1229.png
+xonstat/batch/badges/output/1543.png
+xonstat/batch/badges/output/1726.png
+xonstat/batch/badges/output/1789.png
+xonstat/batch/badges/output/1820.png
+xonstat/batch/badges/output/2024.png
+xonstat/batch/badges/output/2026.png
+xonstat/batch/badges/output/2148.png
+xonstat/batch/badges/output/2206.png
+xonstat/batch/badges/output/2247.png
+xonstat/batch/badges/output/2265.png
+xonstat/batch/badges/output/2311.png
+xonstat/batch/badges/output/2409.png
+xonstat/batch/badges/output/2485.png
+xonstat/batch/badges/output/251.png
+xonstat/batch/badges/output/26.png
+xonstat/batch/badges/output/2627.png
+xonstat/batch/badges/output/264.png
+xonstat/batch/badges/output/2640.png
+xonstat/batch/badges/output/2755.png
+xonstat/batch/badges/output/2769.png
+xonstat/batch/badges/output/2848.png
+xonstat/batch/badges/output/2961.png
+xonstat/batch/badges/output/3071.png
+xonstat/batch/badges/output/3322.png
+xonstat/batch/badges/output/3367.png
+xonstat/batch/badges/output/3370.png
+xonstat/batch/badges/output/359.png
+xonstat/batch/badges/output/3652.png
+xonstat/batch/badges/output/3666.png
+xonstat/batch/badges/output/3715.png
+xonstat/batch/badges/output/3781.png
+xonstat/batch/badges/output/3823.png
+xonstat/batch/badges/output/3903.png
+xonstat/batch/badges/output/3911.png
+xonstat/batch/badges/output/4057.png
+xonstat/batch/badges/output/4059.png
+xonstat/batch/badges/output/4083.png
+xonstat/batch/badges/output/4295.png
+xonstat/batch/badges/output/4321.png
+xonstat/batch/badges/output/4323.png
+xonstat/batch/badges/output/4349.png
+xonstat/batch/badges/output/4387.png
+xonstat/batch/badges/output/4388.png
+xonstat/batch/badges/output/4466.png
+xonstat/batch/badges/output/4718.png
+xonstat/batch/badges/output/4722.png
+xonstat/batch/badges/output/4875.png
+xonstat/batch/badges/output/4888.png
+xonstat/batch/badges/output/496.png
+xonstat/batch/badges/output/4996.png
+xonstat/batch/badges/output/5028.png
+xonstat/batch/badges/output/5116.png
+xonstat/batch/badges/output/5161.png
+xonstat/batch/badges/output/5411.png
+xonstat/batch/badges/output/5729.png
+xonstat/batch/badges/output/6312.png
+xonstat/batch/badges/output/6755.png
+xonstat/batch/badges/output/6810.png
+xonstat/batch/badges/output/6873.png
+xonstat/batch/badges/output/6967.png
+xonstat/batch/badges/output/7003.png
+xonstat/batch/badges/output/7080.png
+xonstat/batch/badges/output/7247.png
+xonstat/batch/badges/output/7416.png
+xonstat/batch/badges/output/7494.png
+xonstat/batch/badges/output/7519.png
+xonstat/batch/badges/output/7678.png
+xonstat/batch/badges/output/7942.png
+xonstat/batch/badges/output/7981.png
+xonstat/batch/badges/output/8087.png
+xonstat/batch/badges/output/8198.png
+xonstat/batch/badges/output/8519.png
+xonstat/batch/badges/output/8533.png
+xonstat/batch/badges/output/8761.png
+xonstat/batch/badges/output/8985.png
+xonstat/batch/badges/output/9203.png
+xonstat/batch/badges/output/9223.png
+xonstat/batch/badges/output/9230.png
+xonstat/batch/badges/output/9243.png
+xonstat/batch/badges/output/9344.png
+xonstat/batch/badges/output/9394.png
+xonstat/batch/badges/output/9545.png
+xonstat/batch/badges/output/9684.png
+xonstat/batch/badges/output/9803.png
+xonstat/batch/badges/output/9970.png
+xonstat/batch/badges/output/minimal/10180.png
+xonstat/batch/badges/output/minimal/10427.png
+xonstat/batch/badges/output/minimal/10543.png
+xonstat/batch/badges/output/minimal/10551.png
+xonstat/batch/badges/output/minimal/1060.png
+xonstat/batch/badges/output/minimal/10627.png
+xonstat/batch/badges/output/minimal/10710.png
+xonstat/batch/badges/output/minimal/10738.png
+xonstat/batch/badges/output/minimal/10780.png
+xonstat/batch/badges/output/minimal/10814.png
+xonstat/batch/badges/output/minimal/10876.png
+xonstat/batch/badges/output/minimal/10939.png
+xonstat/batch/badges/output/minimal/10978.png
+xonstat/batch/badges/output/minimal/1101.png
+xonstat/batch/badges/output/minimal/1229.png
+xonstat/batch/badges/output/minimal/1543.png
+xonstat/batch/badges/output/minimal/1726.png
+xonstat/batch/badges/output/minimal/1789.png
+xonstat/batch/badges/output/minimal/1820.png
+xonstat/batch/badges/output/minimal/2024.png
+xonstat/batch/badges/output/minimal/2026.png
+xonstat/batch/badges/output/minimal/2148.png
+xonstat/batch/badges/output/minimal/2206.png
+xonstat/batch/badges/output/minimal/2247.png
+xonstat/batch/badges/output/minimal/2265.png
+xonstat/batch/badges/output/minimal/2311.png
+xonstat/batch/badges/output/minimal/2409.png
+xonstat/batch/badges/output/minimal/2485.png
+xonstat/batch/badges/output/minimal/251.png
+xonstat/batch/badges/output/minimal/26.png
+xonstat/batch/badges/output/minimal/2627.png
+xonstat/batch/badges/output/minimal/264.png
+xonstat/batch/badges/output/minimal/2640.png
+xonstat/batch/badges/output/minimal/2755.png
+xonstat/batch/badges/output/minimal/2769.png
+xonstat/batch/badges/output/minimal/2848.png
+xonstat/batch/badges/output/minimal/2961.png
+xonstat/batch/badges/output/minimal/3071.png
+xonstat/batch/badges/output/minimal/3322.png
+xonstat/batch/badges/output/minimal/3367.png
+xonstat/batch/badges/output/minimal/3370.png
+xonstat/batch/badges/output/minimal/359.png
+xonstat/batch/badges/output/minimal/3652.png
+xonstat/batch/badges/output/minimal/3666.png
+xonstat/batch/badges/output/minimal/3715.png
+xonstat/batch/badges/output/minimal/3781.png
+xonstat/batch/badges/output/minimal/3823.png
+xonstat/batch/badges/output/minimal/3903.png
+xonstat/batch/badges/output/minimal/3911.png
+xonstat/batch/badges/output/minimal/4057.png
+xonstat/batch/badges/output/minimal/4059.png
+xonstat/batch/badges/output/minimal/4083.png
+xonstat/batch/badges/output/minimal/4295.png
+xonstat/batch/badges/output/minimal/4321.png
+xonstat/batch/badges/output/minimal/4323.png
+xonstat/batch/badges/output/minimal/4349.png
+xonstat/batch/badges/output/minimal/4387.png
+xonstat/batch/badges/output/minimal/4388.png
+xonstat/batch/badges/output/minimal/4466.png
+xonstat/batch/badges/output/minimal/4718.png
+xonstat/batch/badges/output/minimal/4722.png
+xonstat/batch/badges/output/minimal/4875.png
+xonstat/batch/badges/output/minimal/4888.png
+xonstat/batch/badges/output/minimal/496.png
+xonstat/batch/badges/output/minimal/4996.png
+xonstat/batch/badges/output/minimal/5028.png
+xonstat/batch/badges/output/minimal/5116.png
+xonstat/batch/badges/output/minimal/5161.png
+xonstat/batch/badges/output/minimal/5411.png
+xonstat/batch/badges/output/minimal/5729.png
+xonstat/batch/badges/output/minimal/6312.png
+xonstat/batch/badges/output/minimal/6755.png
+xonstat/batch/badges/output/minimal/6810.png
+xonstat/batch/badges/output/minimal/6873.png
+xonstat/batch/badges/output/minimal/6967.png
+xonstat/batch/badges/output/minimal/7003.png
+xonstat/batch/badges/output/minimal/7080.png
+xonstat/batch/badges/output/minimal/7247.png
+xonstat/batch/badges/output/minimal/7416.png
+xonstat/batch/badges/output/minimal/7494.png
+xonstat/batch/badges/output/minimal/7519.png
+xonstat/batch/badges/output/minimal/7678.png
+xonstat/batch/badges/output/minimal/7942.png
+xonstat/batch/badges/output/minimal/7981.png
+xonstat/batch/badges/output/minimal/8087.png
+xonstat/batch/badges/output/minimal/8198.png
+xonstat/batch/badges/output/minimal/8519.png
+xonstat/batch/badges/output/minimal/8533.png
+xonstat/batch/badges/output/minimal/8761.png
+xonstat/batch/badges/output/minimal/8985.png
+xonstat/batch/badges/output/minimal/9203.png
+xonstat/batch/badges/output/minimal/9223.png
+xonstat/batch/badges/output/minimal/9230.png
+xonstat/batch/badges/output/minimal/9243.png
+xonstat/batch/badges/output/minimal/9344.png
+xonstat/batch/badges/output/minimal/9394.png
+xonstat/batch/badges/output/minimal/9545.png
+xonstat/batch/badges/output/minimal/9684.png
+xonstat/batch/badges/output/minimal/9803.png
+xonstat/batch/badges/output/minimal/9970.png
 xonstat/static/favicon.ico
 xonstat/static/css/colorbox.css
-xonstat/static/css/normalize.css
+xonstat/static/css/reset.css
 xonstat/static/css/style.css
+xonstat/static/css/style.min.css
+xonstat/static/css/tables.css
+xonstat/static/css/img/Xonotic_icon.png
+xonstat/static/css/img/button_sprite.png
+xonstat/static/css/img/glyphicons-halflings-white.png
+xonstat/static/css/img/glyphicons-halflings.png
+xonstat/static/css/img/inputbox_sprite.png
+xonstat/static/css/img/web_background.png
+xonstat/static/css/img/web_background_2.jpg
+xonstat/static/css/img/web_background_3.jpg
+xonstat/static/css/img/web_background_l2.png
+xonstat/static/css/img/web_border.png
+xonstat/static/css/img/web_checkbox_c0.png
+xonstat/static/css/img/web_checkbox_c1.png
+xonstat/static/css/img/web_checkbox_d0.png
+xonstat/static/css/img/web_checkbox_d1.png
+xonstat/static/css/img/web_checkbox_f0.png
+xonstat/static/css/img/web_checkbox_f1.png
+xonstat/static/css/img/web_checkbox_n0.png
+xonstat/static/css/img/web_checkbox_n1.png
+xonstat/static/css/img/web_checkmark.png
+xonstat/static/css/img/web_closebutton_c.png
+xonstat/static/css/img/web_closebutton_f.png
+xonstat/static/css/img/web_closebutton_n.png
+xonstat/static/css/img/web_colorpicker.png
+xonstat/static/css/img/web_colorpicker_selected.png
+xonstat/static/css/img/web_cursor.png
+xonstat/static/css/img/web_cursor_move.png
+xonstat/static/css/img/web_cursor_resize.png
+xonstat/static/css/img/web_cursor_resize2.png
+xonstat/static/css/img/web_icon_aeslevel1.png
+xonstat/static/css/img/web_icon_aeslevel2.png
+xonstat/static/css/img/web_icon_aeslevel3.png
+xonstat/static/css/img/web_icon_aeslevel4.png
+xonstat/static/css/img/web_icon_aeslevel5.png
+xonstat/static/css/img/web_icon_ipv4.png
+xonstat/static/css/img/web_icon_ipv6.png
+xonstat/static/css/img/web_icon_pure1.png
+xonstat/static/css/img/web_radiobutton_c0.png
+xonstat/static/css/img/web_radiobutton_c1.png
+xonstat/static/css/img/web_radiobutton_d0.png
+xonstat/static/css/img/web_radiobutton_d1.png
+xonstat/static/css/img/web_radiobutton_f0.png
+xonstat/static/css/img/web_radiobutton_f1.png
+xonstat/static/css/img/web_radiobutton_n0.png
+xonstat/static/css/img/web_radiobutton_n1.png
+xonstat/static/css/img/web_scrollbar_c.png
+xonstat/static/css/img/web_scrollbar_f.png
+xonstat/static/css/img/web_scrollbar_n.png
+xonstat/static/css/img/web_scrollbar_s.png
+xonstat/static/css/img/web_slider_c.png
+xonstat/static/css/img/web_slider_d.png
+xonstat/static/css/img/web_slider_f.png
+xonstat/static/css/img/web_slider_n.png
+xonstat/static/css/img/web_slider_s.png
+xonstat/static/css/img/web_tooltip.png
 xonstat/static/images/border.png
 xonstat/static/images/controls.png
 xonstat/static/images/crylink.png
@@ -38,15 +306,75 @@ xonstat/static/images/minelayer.png
 xonstat/static/images/minstanex.png
 xonstat/static/images/nex.png
 xonstat/static/images/porto.png
+xonstat/static/images/rifle.png
 xonstat/static/images/rocketlauncher.png
 xonstat/static/images/seeker.png
 xonstat/static/images/shotgun.png
-xonstat/static/images/sniperrifle.png
 xonstat/static/images/tuba.png
 xonstat/static/images/uzi.png
+xonstat/static/images/icons/24x24/arena.png
+xonstat/static/images/icons/24x24/as.png
+xonstat/static/images/icons/24x24/assault.png
+xonstat/static/images/icons/24x24/ca.png
+xonstat/static/images/icons/24x24/ctf.png
+xonstat/static/images/icons/24x24/cts.png
+xonstat/static/images/icons/24x24/dm.png
+xonstat/static/images/icons/24x24/dom.png
+xonstat/static/images/icons/24x24/duel.png
+xonstat/static/images/icons/24x24/freezetag.png
+xonstat/static/images/icons/24x24/ft.png
+xonstat/static/images/icons/24x24/ka.png
+xonstat/static/images/icons/24x24/kh.png
+xonstat/static/images/icons/24x24/lms.png
+xonstat/static/images/icons/24x24/nb.png
+xonstat/static/images/icons/24x24/nexball.png
+xonstat/static/images/icons/24x24/ons.png
+xonstat/static/images/icons/24x24/overall.png
+xonstat/static/images/icons/24x24/rc.png
+xonstat/static/images/icons/24x24/rune.png
+xonstat/static/images/icons/24x24/tdm.png
+xonstat/static/images/icons/48x48/arena.png
+xonstat/static/images/icons/48x48/as.png
+xonstat/static/images/icons/48x48/assault.png
+xonstat/static/images/icons/48x48/ca.png
+xonstat/static/images/icons/48x48/ctf.png
+xonstat/static/images/icons/48x48/cts.png
+xonstat/static/images/icons/48x48/dm.png
+xonstat/static/images/icons/48x48/dom.png
+xonstat/static/images/icons/48x48/duel.png
+xonstat/static/images/icons/48x48/freezetag.png
+xonstat/static/images/icons/48x48/ft.png
+xonstat/static/images/icons/48x48/ka.png
+xonstat/static/images/icons/48x48/kh.png
+xonstat/static/images/icons/48x48/lms.png
+xonstat/static/images/icons/48x48/nb.png
+xonstat/static/images/icons/48x48/nexball.png
+xonstat/static/images/icons/48x48/ons.png
+xonstat/static/images/icons/48x48/overall.png
+xonstat/static/images/icons/48x48/rc.png
+xonstat/static/images/icons/48x48/rune.png
+xonstat/static/images/icons/48x48/tdm.png
+xonstat/static/js/bootstrap-alert.js
+xonstat/static/js/bootstrap-button.js
+xonstat/static/js/bootstrap-carousel.js
+xonstat/static/js/bootstrap-collapse.js
+xonstat/static/js/bootstrap-collapse.min.js
+xonstat/static/js/bootstrap-dropdown.js
+xonstat/static/js/bootstrap-modal.js
+xonstat/static/js/bootstrap-popover.js
+xonstat/static/js/bootstrap-scrollspy.js
+xonstat/static/js/bootstrap-tab.js
+xonstat/static/js/bootstrap-tabs.js
+xonstat/static/js/bootstrap-tooltip.js
+xonstat/static/js/bootstrap-transition.js
+xonstat/static/js/bootstrap-typeahead.js
+xonstat/static/js/default.js
+xonstat/static/js/jquery-1.6.1.min.js
+xonstat/static/js/jquery-1.7.1.min.js
 xonstat/static/js/jquery.colorbox-min.js
 xonstat/static/js/jquery.dataTables.min.js
-xonstat/static/js/jquery.js
+xonstat/static/js/jquery.dataTables.numHtml.js
+xonstat/static/js/jquery.flot.min.js
 xonstat/templates/accuracy.mako
 xonstat/templates/base.mako
 xonstat/templates/game_index.mako
@@ -55,10 +383,14 @@ xonstat/templates/main_index.mako
 xonstat/templates/map_index.mako
 xonstat/templates/map_info.mako
 xonstat/templates/mytemplate.pt
+xonstat/templates/nav.mako
+xonstat/templates/navlinks.mako
 xonstat/templates/player_game_index.mako
 xonstat/templates/player_index.mako
 xonstat/templates/player_info.mako
+xonstat/templates/rank_index.mako
 xonstat/templates/scoreboard.mako
+xonstat/templates/search.mako
 xonstat/templates/server_game_index.mako
 xonstat/templates/server_index.mako
 xonstat/templates/server_info.mako
@@ -67,5 +399,6 @@ xonstat/views/game.py
 xonstat/views/main.py
 xonstat/views/map.py
 xonstat/views/player.py
+xonstat/views/search.py
 xonstat/views/server.py
 xonstat/views/submission.py
\ No newline at end of file
index fdef497..8b1506d 100644 (file)
@@ -1,4 +1,4 @@
-pyramid>=1.1
+pyramid
 SQLAlchemy
 transaction
 repoze.tm2>=1.0b1
@@ -7,4 +7,3 @@ WebError
 sqlahelper
 webhelpers
 psycopg2
-PasteScript
index 38572a2..9394f20 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,6 @@ requires = [
     'repoze.tm2>=1.0b1', # default_commit_veto
     'zope.sqlalchemy',
     'WebError',
-    'pyramid-jinja2',
     'sqlahelper',
     'webhelpers',
     'psycopg2',
index bbd08bf..449d3b9 100644 (file)
@@ -1,6 +1,7 @@
 import sqlahelper
 from pyramid_beaker import set_cache_regions_from_settings
 from pyramid.config import Configurator
+from pyramid.httpexceptions import HTTPNotFound
 from pyramid.renderers import JSONP
 from sqlalchemy import engine_from_config
 from xonstat.models import initialize_db
@@ -21,72 +22,70 @@ def main(global_config, **settings):
 
     config = Configurator(settings=settings)
 
+    config.add_renderer('jsonp', JSONP(param_name='callback'))
+
+    # for static assets
     config.add_static_view('static', 'xonstat:static')
 
-    config.add_renderer('jsonp', JSONP(param_name='callback'))
+    # for 404s
+    config.add_view(notfound, context=HTTPNotFound, renderer="404.mako")
 
     # ROOT ROUTE
     config.add_route("main_index", "/")
     config.add_view(main_index, route_name="main_index", renderer="main_index.mako")
 
     # MAIN SUBMISSION ROUTE
-    #config.add_route("stats_submit", "stats/submit")
-    #config.add_view(stats_submit, route_name="stats_submit")
     config.add_route("submit_stats", "stats/submit")
     config.add_view(submit_stats, route_name="submit_stats")
 
     # PLAYER ROUTES
     config.add_route("player_game_index",      "/player/{player_id:\d+}/games")
-    config.add_route("player_game_index_json", "/player/{player_id:\d+}/games.json")
     config.add_view(player_game_index,      route_name="player_game_index",      renderer="player_game_index.mako")
+
+    config.add_route("player_game_index_json", "/player/{player_id:\d+}/games.json")
     config.add_view(player_game_index_json, route_name="player_game_index_json", renderer="jsonp")
 
+    config.add_route("player_hashkey_info_text", "/player/me")
+    config.add_view(player_hashkey_info_text, route_name="player_hashkey_info_text", renderer="player_hashkey_info_text.mako")
+
     config.add_route("player_info",      "/player/{id:\d+}")
-    config.add_route("player_info_json", "/player/{id:\d+}.json")
     config.add_view(player_info,      route_name="player_info",      renderer="player_info.mako")
-    config.add_view(player_info_json, route_name="player_info_json", renderer="jsonp")
-
-    config.add_route("player_hashkey_info_text", "/hashkey/{hashkey}")
-    config.add_view(player_hashkey_info_text, route_name="player_hashkey_info_text", renderer="player_hashkey_info_text.mako")
 
-    #config.add_route("player_hashkey_info_json", "/hashkey/{hashkey}.json")
-    #config.add_view(player_hashkey_info_json, route_name="player_hashkey_info_json", renderer="jsonp")
+    config.add_route("player_info_json", "/player/{id:\d+}.json")
+    config.add_view(player_info_json, route_name="player_info_json", renderer="jsonp")
 
     config.add_route("player_elo_info_json", "/elo/{hashkey}")
     config.add_view(player_elo_info_json, route_name="player_elo_info_json", renderer="jsonp")
 
     config.add_route("player_accuracy",      "/player/{id:\d+}/accuracy")
-    #config.add_route("player_accuracy_json", "/player/{id:\d+}/accuracy.json")
     config.add_view(player_accuracy_json, route_name="player_accuracy",      renderer="jsonp")
-    #config.add_view(player_accuracy_json, route_name="player_accuracy_json", renderer="jsonp")
 
     config.add_route("player_index",      "/players")
-    config.add_route("player_index_json", "/players.json")
     config.add_view(player_index,      route_name="player_index",      renderer="player_index.mako")
+
+    config.add_route("player_index_json", "/players.json")
     config.add_view(player_index_json, route_name="player_index_json", renderer="jsonp")
 
     config.add_route("player_damage", "/player/{id:\d+}/damage")
     config.add_view(player_damage_json, route_name="player_damage", renderer="jsonp")
 
     config.add_route("player_captimes",      "/player/{id:\d+}/captimes")
-    config.add_route("player_captimes_json", "/player/{id:\d+}/captimes.json")
     config.add_view(player_captimes,      route_name="player_captimes",      renderer="player_captimes.mako")
+
+    config.add_route("player_captimes_json", "/player/{id:\d+}/captimes.json")
     config.add_view(player_captimes_json, route_name="player_captimes_json", renderer="jsonp")
 
     # GAME ROUTES
-    # config.add_route("game_index",      "/games")
-    # config.add_route("game_index_json", "/games.json")
-    # config.add_view(game_index,      route_name="game_index",      renderer="game_index.mako")
-    # config.add_view(game_index_json, route_name="game_index_json", renderer="jsonp")
-
     config.add_route("game_info",      "/game/{id:\d+}")
-    config.add_route("game_info_json", "/game/{id:\d+}.json")
     config.add_view(game_info,      route_name="game_info",      renderer="game_info.mako")
+
+    config.add_route("game_info_json", "/game/{id:\d+}.json")
     config.add_view(game_info_json, route_name="game_info_json", renderer="jsonp")
 
-    config.add_route("rank_index",      "/ranks/{game_type_cd:ctf|dm|tdm|duel}")
-    config.add_route("rank_index_json", "/ranks/{game_type_cd:ctf|dm|tdm|duel}.json")
+    config.add_route("rank_index",      "/ranks/{game_type_cd:ctf|dm|tdm|duel|ca|ft}")
     config.add_view(rank_index,      route_name="rank_index",      renderer="rank_index.mako")
+
+    config.add_route("rank_index_json", "/ranks/{game_type_cd:ctf|dm|tdm|duel|ca|ft}.json")
     config.add_view(rank_index_json, route_name="rank_index_json", renderer="jsonp")
 
     config.add_route("game_index", "/games")
@@ -94,40 +93,47 @@ def main(global_config, **settings):
 
     # SERVER ROUTES
     config.add_route("server_index",      "/servers")
-    config.add_route("server_index_json", "/servers.json")
     config.add_view(server_index,      route_name="server_index",      renderer="server_index.mako")
+
+    config.add_route("server_index_json", "/servers.json")
     config.add_view(server_index_json, route_name="server_index_json", renderer="jsonp")
 
     config.add_route("server_game_index",      "/server/{server_id:\d+}/games/page/{page:\d+}")
-    config.add_route("server_game_index_json", "/server/{server_id:\d+}/games.json")
     config.add_view(server_game_index,      route_name="server_game_index",      renderer="server_game_index.mako")
+
+    config.add_route("server_game_index_json", "/server/{server_id:\d+}/games.json")
     config.add_view(server_game_index_json, route_name="server_game_index_json", renderer="jsonp")
 
     config.add_route("server_info",      "/server/{id:\d+}")
-    config.add_route("server_info_json", "/server/{id:\d+}.json")
     config.add_view(server_info,      route_name="server_info",      renderer="server_info.mako")
+
+    config.add_route("server_info_json", "/server/{id:\d+}.json")
     config.add_view(server_info_json, route_name="server_info_json", renderer="jsonp")
 
     # MAP ROUTES
     config.add_route("map_index",      "/maps")
-    config.add_route("map_index_json", "/maps.json")
     config.add_view(map_index,      route_name="map_index",      renderer="map_index.mako")
+
+    config.add_route("map_index_json", "/maps.json")
     config.add_view(map_index_json, route_name="map_index_json", renderer="jsonp")
 
     config.add_route("map_info",      "/map/{id:\d+}")
-    config.add_route("map_info_json", "/map/{id:\d+}.json")
     config.add_view(map_info,      route_name="map_info",      renderer="map_info.mako")
+
+    config.add_route("map_info_json", "/map/{id:\d+}.json")
     config.add_view(map_info_json, route_name="map_info_json", renderer="jsonp")
 
     config.add_route("map_captimes",      "/map/{id:\d+}/captimes")
-    config.add_route("map_captimes_json", "/map/{id:\d+}/captimes.json")
     config.add_view(map_captimes,      route_name="map_captimes",      renderer="map_captimes.mako")
+
+    config.add_route("map_captimes_json", "/map/{id:\d+}/captimes.json")
     config.add_view(map_captimes_json, route_name="map_captimes_json", renderer="jsonp")
 
     # SEARCH ROUTES
     config.add_route("search",      "search")
-    config.add_route("search_json", "search.json")
     config.add_view(search,      route_name="search",      renderer="search.mako")
+
+    config.add_route("search_json", "search.json")
     config.add_view(search_json, route_name="search_json", renderer="jsonp")
 
     return config.make_wsgi_app()
index 982512e..f18370f 100644 (file)
@@ -413,16 +413,16 @@ class Skin:
         try:
             txt = "%.2f%%" % round(win_pct, 2)
         except:
-            win_pct = 0
+            win_pct = 0.
 
         if self.winp_pos:
-            if win_pct >= 0.5:
-                nr = 2*(win_pct-0.5)
+            if win_pct >= 50.0:
+                nr = 2*(win_pct/100-0.5)
                 r = nr*self.winp_colortop[0] + (1-nr)*self.winp_colormid[0]
                 g = nr*self.winp_colortop[1] + (1-nr)*self.winp_colormid[1]
                 b = nr*self.winp_colortop[2] + (1-nr)*self.winp_colormid[2]
             else:
-                nr = 2*win_pct
+                nr = 2*(win_pct/100)
                 r = nr*self.winp_colormid[0] + (1-nr)*self.winp_colorbot[0]
                 g = nr*self.winp_colormid[1] + (1-nr)*self.winp_colorbot[1]
                 b = nr*self.winp_colormid[2] + (1-nr)*self.winp_colorbot[2]
index bda038b..a407184 100644 (file)
@@ -260,11 +260,42 @@ class SummaryStat(object):
         return "<SummaryStat(total_players=%s, total_games=%s, total_servers=%s)>" % (self.total_players, self.total_games, self.total_servers)
 
 
+class TeamGameStat(object):
+    def __init__(self, team_game_stat_id=None, create_dt=None):
+        self.team_game_stat_id = team_game_stat_id
+        self.create_dt = create_dt
+
+    def __repr__(self):
+        return "<TeamGameStat(%s, %s, %s)>" % (self.team_game_stat_id, self.game_id, self.team)
+
+    def to_dict(self):
+        return {
+            'team_game_stat_id':self.team_game_stat_id,
+            'game_id':self.game_id,
+            'team':self.team,
+            'score':self.score,
+            'rounds':self.rounds,
+            'caps':self.caps,
+            'create_dt':self.create_dt.strftime('%Y-%m-%dT%H:%M:%SZ'),
+        }
+
+    def team_html_color(self):
+        if self.team == 5:
+            return "red"
+        if self.team == 14:
+            return "blue"
+        if self.team == 13:
+            return "yellow"
+        if self.team == 10:
+            return "pink"
+
+
 def initialize_db(engine=None):
     DBSession.configure(bind=engine)
     Base.metadata.bind = engine
     Base.metadata.create_all(engine)
-    MetaData = sqlalchemy.MetaData(bind=engine, reflect=True)
+    MetaData = sqlalchemy.MetaData(bind=engine)
+    MetaData.reflect()
 
     # assign all those tables to an object
     achievements_table = MetaData.tables['achievements']
@@ -284,6 +315,7 @@ def initialize_db(engine=None):
     player_ranks_table = MetaData.tables['player_ranks']
     player_captimes_table = MetaData.tables['player_map_captimes']
     summary_stats_table = MetaData.tables['summary_stats']
+    team_game_stats_table = MetaData.tables['team_game_stats']
 
     # now map the tables and the objects together
     mapper(PlayerAchievement, achievements_table)
@@ -302,3 +334,4 @@ def initialize_db(engine=None):
     mapper(PlayerRank, player_ranks_table)
     mapper(PlayerCaptime, player_captimes_table)
     mapper(SummaryStat, summary_stats_table)
+    mapper(TeamGameStat, team_game_stats_table)
index eafe336..ce2b71d 100644 (file)
@@ -150,7 +150,6 @@ table td {
 /* Game scoreboard */
 .game {
   float: left;
-  margin-bottom: 30px;
   min-width: 700px;
   padding: 10px 7px;
 }
@@ -180,24 +179,32 @@ table td {
     background-color: #222;
 }
 
-.teamscores td {
-    background-color: #000;
-    text-align: center;
-    padding: 4px;
-    font-size: 18px;
+.teamscore {
+  text-align: right;
+  text-shadow: -1px -1px 0 #222;
+  font-size: 20px;
+  font-weight: bold;
+  padding: 10px 7px;
+  margin-right: 5px;
+  margin-top: 12px;
 }
-.teamscores td.red {
-  background-color: #4d0000;
+.teamscore .teamname {
+  font-size: 12px;
 }
-.teamscores td.blue {
-  background-color: #00004d;
+.teamscore .red {
+  color: #ad0000;
 }
-.teamscores td.yellow {
-  background-color: #4d4d00;
+.teamscore .blue {
+  color: #0000ad;
 }
-.teamscores td.pink {
-  background-color: #4d004d;
+.teamscore .yellow {
+  color: #adad00;
+}
+.teamscore .pink {
+  color: #ad00ad;
 }
+.player-score { color: #FEFF3A; }
+.ping { width: 50px; }
 
 /* accuracy and weapon graphs */
 .weapon-nav {
@@ -234,6 +241,7 @@ table td {
 .tabbable p { font-size: 14px; }
 .tabs-below .nav-tabs > li > a { border-radius: 4px 4px 4px 4px; }
 .nav-tabs > .active > a, .nav-tabs > .active > a:hover { background-color: #111; color: #aaa; border-color: #222; }
+.nav-tabs > .active > a, .nav-tabs > .active > a:focus { background-color: #111; color: #aaa; border-color: #222; }
 .nav-tabs > li > a { border-radius: 4px 4px 4px 4px; text-align: center; }
 .nav-tabs > li > a:hover { background-color: #111; border-color: #333; }
 .nav-tabs { border-bottom: 0px solid #000; }
@@ -258,18 +266,69 @@ table td {
   float: left;
 }
 
-/* elo colors */
-.eloup { color: green; }
-.elodown { color: rgb(190,0,0); }
-.eloneutral { color: gray; }
 
-/* limit player nick lengths */
+/* Gametype filters */
+.btn-toolbar .nav > li a {
+    width: 80px;
+}
+
+.btn.dropdown-toggle, .btn.dropdown-toggle:active {
+    background: none;
+    border: 1px solid transparent;
+    border-radius:4px 4px 4px 4px;
+    line-height:20px;
+    color: #428bca;
+    padding: 10px 0px 10px 0px;
+    font-size: 14px;
+    outline: 0;
+}
+.btn.dropdown-toggle:hover, .btn.dropdown-toggle:focus {
+    background-color:#111;
+    color:#2a6496;
+    border-color:#333;
+}
+
+.btn.dropdown-toggle > .caret {
+  height: 21px;
+  border-top-color: #428bca;
+  border-top-width: 8px;
+  border-left-width: 8px;
+  border-right-width: 8px;
+}
+
+.dropdown-menu {
+  width: 100px;
+}
+
+.dropdown-menu.nav-tabs {
+    padding:4px;
+    background-color: #111;
+    border: 1px solid #333;
+    width: 256px; }
+.dropdown-menu.nav-tabs > li > a {
+    width:80px;
+    color:inherit;
+}
+.dropdown-menu.nav-tabs > li > a:hover {
+    color:#222;
+}
+
+/* Player nicknames */
 .nostretch {
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
 
+.player-nick {
+    width: 25%;
+}
+
+/* elo colors */
+.eloup { color: green; }
+.elodown { color: rgb(190,0,0); }
+.eloneutral { color: gray; }
+
 /* Navigation links */
 .pagination > li > a, .pagination > li > span {
     background-color: #111111;
index 6938bc2..d0a01e8 100644 (file)
@@ -1,5 +1 @@
-@font-face{font-family:'XoloniumNormal';src:url('fonts/xolonium-webfont.eot');src:url('fonts/xolonium-webfont.eot?#iefix') format('embedded-opentype'),url('fonts/xolonium-webfont.woff') format('woff'),url('fonts/xolonium-webfont.ttf') format('truetype'),url('fonts/xolonium-webfont.svg#XoloniumNormal') format('svg');font-weight:normal;font-style:normal}body{background:url("img/web_background_4.jpg") no-repeat fixed center center / cover black;background-color:black;color:#d0d0d0;font-family:"XoloniumNormal","Helvetica Neue",Helvetica,Arial,sans-serif}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999}h1{font-size:30px;line-height:36px}h1 small{font-size:18px}h2{font-size:24px;line-height:36px}h2 small{font-size:18px}h3{line-height:27px;font-size:18px}h3 small{font-size:14px}h4,h5,h6{line-height:18px}h4{font-size:14px}h4 small{font-size:12px}h5{font-size:12px}h6{font-size:11px;color:#999;text-transform:uppercase}table{background:#000;background:none repeat scroll 0 0 rgba(0,0,0,0.7);border:1px solid #436688}table th{border:1px solid #436688;background-color:#001021}table td{border:1px solid #436688;font-size:10px}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#222}.table th,.table td{border:1px solid #436688}.table td{vertical-align:middle}.table .tdcenter{text-align:center}.accordion-group{border:1px solid #272525}.accordion-inner{border:0}#statline{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;position:relative;top:-25px}#xonborder{background:#000;background:none repeat scroll 0 0 rgba(0,0,0,0.5);border-radius:15px 15px 15px 15px;margin-bottom:30px;margin-left:0;padding:20px}#title{color:#08c;font-size:30px;margin-bottom:15px;position:relative;text-align:center;text-shadow:2px 2px 3px #333}.indexform{margin:20px 0 20px 0}.indexbox{width:250px}.navbar-brand{margin-left:0;padding-bottom:0;padding-top:10px;text-align:left}.navbar-inverse{background:none repeat scroll 0 0 rgba(0,0,0,0.6)}.navbar-inverse .nav>.active>a,.navbar-inverse .nav>.active>a:hover,.navbar-inverse .nav>.active>a:focus{background:none repeat scroll 0 0 rgba(49,49,49,0.6)}.navbar-inverse .nav>li>a,.navbar-brand{font-family:XoloniumNormal}.search,input[type="search"]{background-color:#606060;border:1px solid #202020;color:#aaa;width:100px}.game{float:left;margin-bottom:30px;min-width:700px;padding:10px 7px}.game a{color:#CCC}.game a:hover{color:#d95f00;text-decoration:none}.game tr{background-color:#000}.game tr.red{background-color:#4d0000}.game tr.blue{background-color:#00004d}.game tr.yellow{background-color:#4d4d00}.game tr.pink{background-color:#4d004d}.game tr:hover{background-color:#222}.teamscores td{background-color:#000;text-align:center;padding:4px;font-size:18px}.teamscores td.red{background-color:#4d0000}.teamscores td.blue{background-color:#00004d}.teamscores td.yellow{background-color:#4d4d00}.teamscores td.pink{background-color:#4d004d}.weapon-nav{height:70px;margin-bottom:20px}.weapon-nav ul{display:block;list-style:none outside none}.weapon-nav li{cursor:pointer;float:left;margin-right:10px}.weapon-nav li:hover{border-bottom:2px solid #001021}.weapon-nav .weapon-active{border-bottom:2px solid #436688}.weapon-nav p{text-align:center}.flot table,.flot td{background-color:black;border:0}#gbtabcontainer{margin-top:10px}#gbtab{font-size:12px}.tabbable p{font-size:14px}.tabs-below .nav-tabs>li>a{border-radius:4px 4px 4px 4px}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{background-color:#111;color:#aaa;border-color:#222}.nav-tabs>li>a{border-radius:4px 4px 4px 4px;text-align:center}.nav-tabs>li>a:hover{background-color:#111;border-color:#333}.nav-tabs{border-bottom:0 solid #000}.table .tdcenter{text-align:center}.game-detail img{float:left;margin-right:10px;margin-bottom:5px}.game img{float:left;margin-right:5px;margin-bottom:5px}.game-detail p,.game h4{float:left}.eloup{color:green}.elodown{color:#be0000}.eloneutral{color:gray}.nostretch{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination>li>a,.pagination>li>span{background-color:#111;border-color:#313131;color:#797979}.pagination>li>a:hover,.pagination>li>a:focus,.pagination>.active>a,.pagination>.active>span{background-color:#2b2222}@media(min-width:768px){.navbar-form{float:right}}
-
-.scoreboard-entry { width: 120px; max-width: 120px; overflow: hidden; }
-
-td.teamscore { font-size: 16px; text-align: center; }
+@font-face{font-family:'XoloniumNormal';src:url('fonts/xolonium-webfont.eot');src:url('fonts/xolonium-webfont.eot?#iefix') format('embedded-opentype'),url('fonts/xolonium-webfont.woff') format('woff'),url('fonts/xolonium-webfont.ttf') format('truetype'),url('fonts/xolonium-webfont.svg#XoloniumNormal') format('svg');font-weight:normal;font-style:normal}body{background:url("img/web_background_4.jpg") no-repeat fixed center center / cover black;background-color:black;color:#d0d0d0;font-family:"XoloniumNormal","Helvetica Neue",Helvetica,Arial,sans-serif}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999}h1{font-size:30px;line-height:36px}h1 small{font-size:18px}h2{font-size:24px;line-height:36px}h2 small{font-size:18px}h3{line-height:27px;font-size:18px}h3 small{font-size:14px}h4,h5,h6{line-height:18px}h4{font-size:14px}h4 small{font-size:12px}h5{font-size:12px}h6{font-size:11px;color:#999;text-transform:uppercase}table{background:#000;background:none repeat scroll 0 0 rgba(0,0,0,0.7);border:1px solid #436688}table th{border:1px solid #436688;background-color:#001021}table td{border:1px solid #436688;font-size:10px}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#222}.table th,.table td{border:1px solid #436688}.table td{vertical-align:middle}.table .tdcenter{text-align:center}.accordion-group{border:1px solid #272525}.accordion-inner{border:0}#statline{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;position:relative;top:-25px}#xonborder{background:#000;background:none repeat scroll 0 0 rgba(0,0,0,0.5);border-radius:15px 15px 15px 15px;margin-bottom:30px;margin-left:0;padding:20px}#title{color:#08c;font-size:30px;margin-bottom:15px;position:relative;text-align:center;text-shadow:2px 2px 3px #333}.indexform{margin:20px 0 20px 0}.indexbox{width:250px}.navbar-brand{margin-left:0;padding-bottom:0;padding-top:10px;text-align:left}.navbar-inverse{background:none repeat scroll 0 0 rgba(0,0,0,0.6)}.navbar-inverse .nav>.active>a,.navbar-inverse .nav>.active>a:hover,.navbar-inverse .nav>.active>a:focus{background:none repeat scroll 0 0 rgba(49,49,49,0.6)}.navbar-inverse .nav>li>a,.navbar-brand{font-family:XoloniumNormal}.search,input[type="search"]{background-color:#606060;border:1px solid #202020;color:#aaa;width:100px}.game{float:left;min-width:700px;padding:10px 7px}.game a{color:#CCC}.game a:hover{color:#d95f00;text-decoration:none}.game tr{background-color:#000}.game tr.red{background-color:#4d0000}.game tr.blue{background-color:#00004d}.game tr.yellow{background-color:#4d4d00}.game tr.pink{background-color:#4d004d}.game tr:hover{background-color:#222}.teamscore{text-align:right;text-shadow:-1px -1px 0 #222;font-size:20px;font-weight:bold;padding:10px 7px;margin-right:5px;margin-top:12px}.teamscore .teamname{font-size:12px}.teamscore .red{color:#ad0000}.teamscore .blue{color:#0000ad}.teamscore .yellow{color:#adad00}.teamscore .pink{color:#ad00ad}.player-score{color:#feff3a}.ping{width:50px}.weapon-nav{height:70px;margin-bottom:20px}.weapon-nav ul{display:block;list-style:none outside none}.weapon-nav li{cursor:pointer;float:left;margin-right:10px}.weapon-nav li:hover{border-bottom:2px solid #001021}.weapon-nav .weapon-active{border-bottom:2px solid #436688}.weapon-nav p{text-align:center}.flot table,.flot td{background-color:black;border:0}#gbtabcontainer{margin-top:10px}#gbtab{font-size:12px}.tabbable p{font-size:14px}.tabs-below .nav-tabs>li>a{border-radius:4px 4px 4px 4px}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{background-color:#111;color:#aaa;border-color:#222}.nav-tabs>.active>a,.nav-tabs>.active>a:focus{background-color:#111;color:#aaa;border-color:#222}.nav-tabs>li>a{border-radius:4px 4px 4px 4px;text-align:center}.nav-tabs>li>a:hover{background-color:#111;border-color:#333}.nav-tabs{border-bottom:0 solid #000}.table .tdcenter{text-align:center}.game-detail img{float:left;margin-right:10px;margin-bottom:5px}.game img{float:left;margin-right:5px;margin-bottom:5px}.game-detail p,.game h4{float:left}.btn-toolbar .nav>li a{width:80px}.btn.dropdown-toggle,.btn.dropdown-toggle:active{background:0;border:1px solid transparent;border-radius:4px 4px 4px 4px;line-height:20px;color:#428bca;padding:10px 0 10px 0;font-size:14px;outline:0}.btn.dropdown-toggle:hover,.btn.dropdown-toggle:focus{background-color:#111;color:#2a6496;border-color:#333}.btn.dropdown-toggle>.caret{height:21px;border-top-color:#428bca;border-top-width:8px;border-left-width:8px;border-right-width:8px}.dropdown-menu{width:100px}.dropdown-menu.nav-tabs{padding:4px;background-color:#111;border:1px solid #333;width:256px}.dropdown-menu.nav-tabs>li>a{width:80px;color:inherit}.dropdown-menu.nav-tabs>li>a:hover{color:#222}.nostretch{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.player-nick{width:25%}.eloup{color:green}.elodown{color:#be0000}.eloneutral{color:gray}.pagination>li>a,.pagination>li>span{background-color:#111;border-color:#313131;color:#797979}.pagination>li>a:hover,.pagination>li>a:focus,.pagination>.active>a,.pagination>.active>span{background-color:#2b2222}@media(min-width:768px){.navbar-form{float:right}}
diff --git a/xonstat/static/images/icons/24x24/cake.png b/xonstat/static/images/icons/24x24/cake.png
new file mode 100644 (file)
index 0000000..6e490a5
Binary files /dev/null and b/xonstat/static/images/icons/24x24/cake.png differ
diff --git a/xonstat/templates/404.mako b/xonstat/templates/404.mako
new file mode 100644 (file)
index 0000000..9e1b150
--- /dev/null
@@ -0,0 +1,48 @@
+<%inherit file="base.mako"/>
+<%namespace name="nav" file="nav.mako" />
+
+<%block name="navigation">
+${nav.nav('games')}
+</%block>
+
+<h1>HTTP 404</h1>
+
+% if 0 <= rand < 30:
+<h2>Well this is <i>awkward</i>...I couldn't find what you were looking for!</h2>
+<p>(better get back to playing then, <b>hm</b>?)</p>
+% endif
+
+% if 30 <= rand < 40:
+<h2>Whoa there! Looks like you've taken a wrong turn.</h2>
+<p>(no one <i>tries</i> to get on highway 404, <b>no one</b>.)</p>
+% endif
+
+% if 40 <= rand < 50:
+<h2>There's nothing to see here. *waves hand*</h2>
+<p>(did Mirio put you up to this?)</p>
+% endif
+
+% if 50 <= rand < 60:
+<h2>Hey, you! Watch out - kojn's behind you!</h2>
+<p>(he killed this page, apparently)</p>
+% endif
+
+% if 60 <= rand < 70:
+<h2>Samual must have destroyed this page.</h2>
+<p>(it wasn't pulling its own weight anyway)</p>
+% endif
+
+% if 70 <= rand < 80:
+<h2>divVerent has encrypted this page so hard you'll never decipher it.</h2>
+<p>(either that or you've hit the wrong page or something)</p>
+% endif
+
+% if 80 <= rand < 90:
+<h2>merlijn was unhappy with this page, so he removed it from the server.</h2>
+<p>(after yelling at me about it, of course)</p>
+% endif
+
+% if 90 <= rand <= 100:
+<h2>Morphed is modeling this page. It's gonna be awesome.</h2>
+<p>(until then, you'll probably want to get in a game or two)</p>
+% endif
index 957947f..dd477d0 100644 (file)
@@ -34,10 +34,11 @@ Game Index
   </div>
 </div>
 <div class="row">
-  <div class="span12 tabbable">
+  <div class="span12 btn-toolbar">
     <ul class="nav nav-tabs">
-      % for gt in ('overall','duel','ctf','dm','tdm','ca','kh','ft','lms','as','dom','nb','cts','rc'):
       ##% for gt in ('overall','duel','ctf','dm','tdm','ca','kh','ft','lms','as','dom','nb','cts','rc'):
+      ##% for gt in ('overall','duel','ctf','dm','tdm','ca','kh','ft','lms','as','dom','nb','cts','rc'):
+      % for gt in ('overall','duel','ctf','dm','tdm'):
       <li>
       % if gt == 'overall':
       <a href="${request.route_url("game_index")}" alt="${gt}" title="" data-toggle="none">
@@ -49,6 +50,24 @@ Game Index
       </a>
       </li>
       % endfor
+      <li>
+        <div class="btn-group nav">
+          <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
+            <span class="caret"> </span><br/>
+            more...
+          </a>
+          <ul class="dropdown-menu nav-tabs">
+            % for gt in ('ca','kh','ft','lms','as','dom','nb','cts','rc'):
+            <li>
+            <a href="${request.route_url("game_index", game_type_cd=gt)}" alt="${gt}" title="" data-toggle="none">
+              <span class="sprite sprite-${gt}"> </span><br/>
+              ${gt}
+            </a>
+            </li>
+            % endfor
+          </ul>
+        </div>
+      </li>
     </ul>
   </div>
   <div class="span12 offset1 tab-content">
index 0bcd541..b9ab876 100644 (file)
@@ -32,9 +32,9 @@ Game Information
 
 % else:
 <div class="row">
-  <div class="span10 game-detail">
-    <h2>Game Detail</h2>
-    <img width="48" height="48" src="/static/images/icons/48x48/${game.game_type_cd}.png" alt="${game.game_type_cd}"/>
+  <h2>Game Detail</h2>
+  <div class="span8 game-detail">
+    <img width="64" height="64" src="/static/images/icons/48x48/${game.game_type_cd}.png" alt="${game.game_type_cd}"/>
     <p>
     Played: <span class="abstime" data-epoch="${game.epoch()}" title="${game.start_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${game.fuzzy_date()}</span><br />
     Game Type: ${gametype.descr} (${game.game_type_cd})<br />
@@ -46,14 +46,56 @@ Game Information
     </p>
     <span class="clear"></span>
   </div>
+  % if teamscores:
+  <div class="span3 teamscores">
+    <table class="table table-condensed">
+    <thead>
+      <th>Team</th>
+      <th>Score</th>
+    </thead>
+    <tbody>
+    % for ts in teamscores:
+      <tr class="${ts.team}"><td>${ts.team.capitalize()}</td><td>${ts.score}</td></tr>
+    % endfor
+    </tbody>
+    </table>
+  </div>
+  % endif
 </div>
 
+% if len(tgstats) == len(stats_by_team):
+## if we have teamscores in the db
+% for tgstat in tgstats:
+<div class="row">
+  <div class="span1 teamscore">
+  <div class="teamname ${tgstat.team_html_color()}">
+  ${tgstat.team_html_color().capitalize()}
+  </div>
+  <div class="${tgstat.team_html_color()}">
+  % if game.game_type_cd == 'ctf':
+  ${tgstat.caps}
+  % elif game.game_type_cd == 'ca':
+  ${tgstat.rounds}
+## dom -> ticks, rc -> laps, nb -> goals, as -> objectives
+  % else:
+  ${tgstat.score}
+  % endif
+  </div>
+  </div>
+  <div class="span10 game">
+  ${scoreboard(game.game_type_cd, stats_by_team[tgstat.team], show_elo, show_latency)}
+  </div>
+</div>
+% endfor
+% else:
+% for team in stats_by_team.keys():
 <div class="row">
   <div class="span12 game">
-    <h3>Scoreboard</h3>
-    ${scoreboard(game.game_type_cd, pgstats, teams, show_elo, show_latency)}
+  ${scoreboard(game.game_type_cd, stats_by_team[team], show_elo, show_latency)}
   </div>
 </div>
+% endfor
+% endif
 
 % if len(captimes) > 0:
 <div class="row">
index 1814b2f..983f2ee 100644 (file)
@@ -46,9 +46,9 @@ Leaderboard
       <table class="table table-hover table-condensed">
         <thead>
           <tr>
-            <th>#</th>
-            <th>Nick</th>
-            <th>Elo</th>
+            <th style="width:40px;">#</th>
+            <th style="width:150px;">Nick</th>
+            <th style="width:60px;">Elo</th>
           </tr>
         </thead>
         <tbody>
@@ -56,7 +56,7 @@ Leaderboard
         % for r in rs:
         <tr>
           <td>${i}</td>
-          <td class="player-nick" style="max-width: 16em;"><a href="${request.route_url('player_info', id=r.player_id)}" title="Go to the player info page for this player">${r.nick_html_colors()|n}</a></td>
+          <td class="nostretch" style="max-width:150px;"><a href="${request.route_url('player_info', id=r.player_id)}" title="Go to the player info page for this player">${r.nick_html_colors()|n}</a></td>
           <td>${int(round(r.elo))}</td>
         </tr>
         <% i = i+1 %>
@@ -79,9 +79,9 @@ Leaderboard
     <table class="table table-hover table-condensed">
       <thead>
         <tr>
-          <th>#</th>
-          <th>Nick</th>
-          <th class="play-time">Play Time</th>
+          <th style="width:40px;">#</th>
+          <th style="width:150px;">Nick</th>
+          <th class="play-time" style="width:90px;">Play Time</th>
         </tr>
       </thead>
       <tbody>
@@ -90,9 +90,9 @@ Leaderboard
         <tr>
           <td>${i}</td>
           % if player_id != '-':
-          <td class="player-nick"><a href="${request.route_url('player_info', id=player_id)}" title="Go to the player info page for this player">${nick|n}</a></td>
+          <td class="nostretch" style="max-width:150px;"><a href="${request.route_url('player_info', id=player_id)}" title="Go to the player info page for this player">${nick|n}</a></td>
           % else:
-          <td>${nick|n}</td>
+          <td class="nostretch" style="max-width:150px;">${nick|n}</td>
           % endif
           <td class="play-time">${alivetime}</td>
         </tr>
@@ -100,7 +100,6 @@ Leaderboard
       % endfor
       </tbody>
     </table>
-    <p class="note">*Most active stats are from the past 7 days</p>
   </div> <!-- /span4 -->
 
 
@@ -110,9 +109,9 @@ Leaderboard
     <table class="table table-hover table-condensed">
       <thead>
         <tr>
-          <th>#</th>
-          <th>Server</th>
-          <th>Games</th>
+          <th style="width:40px;">#</th>
+          <th style="width:180px;">Server</th>
+          <th style="width:60px;">Games</th>
         </tr>
       </thead>
       <tbody>
@@ -121,9 +120,9 @@ Leaderboard
         <tr>
           <td>${i}</td>
           % if server_id != '-':
-          <td><a href="${request.route_url('server_info', id=server_id)}" title="Go to the server info page for ${name}">${name}</a></td>
+          <td class="nostretch" style="max-width:180px;"><a href="${request.route_url('server_info', id=server_id)}" title="Go to the server info page for ${name}">${name}</a></td>
           % else:
-          <td>${name}</td>
+          <td class="nostretch" style="max-width:180px;">${name}</td>
           % endif
           <td>${count}</td>
         </tr>
@@ -140,9 +139,9 @@ Leaderboard
     <table class="table table-hover table-condensed">
       <thead>
         <tr>
-          <th>#</th>
-          <th>Map</th>
-          <th>Games</th>
+          <th style="width:40px;">#</th>
+          <th style="width:180px;">Map</th>
+          <th style="width:60px;">Games</th>
         </tr>
       </thead>
       <tbody>
@@ -151,9 +150,9 @@ Leaderboard
         <tr>
           <td>${i}</td>
           % if map_id != '-':
-          <td><a href="${request.route_url('map_info', id=map_id)}" title="Go to the map info page for ${name}">${name}</a></td>
+          <td class="nostretch" style="max-width:180px;"><a href="${request.route_url('map_info', id=map_id)}" title="Go to the map info page for ${name}">${name}</a></td>
           % else:
-          <td>${name}</td>
+          <td class="nostretch" style="max-width:180px;">${name}</td>
           % endif
           <td>${count}</td>
         </tr>
@@ -163,6 +162,9 @@ Leaderboard
     </table>
   </div> <!-- /span4 -->
 </div> <!-- /row -->
+<row class="span12">
+    <p class="note">*Most active stats are from the past 7 days</p>
+</div>
 
 
 ##### RECENT GAMES #####
@@ -189,7 +191,7 @@ Leaderboard
           <td><a href="${request.route_url('server_info', id=rg.server_id)}" title="Go to the detail page for this server">${rg.server_name}</a></td>
           <td><a href="${request.route_url('map_info', id=rg.map_id)}" title="Go to the map detail page for this map">${rg.map_name}</a></td>
           <td><span class="abstime" data-epoch="${rg.epoch}" title="${rg.start_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${rg.fuzzy_date}</span></td>
-          <td class="player-nick">
+          <td class="nostretch">
             % if rg.player_id > 2:
             <a href="${request.route_url('player_info', id=rg.player_id)}" title="Go to the player info page for this player">${rg.nick_html_colors|n}</a></td>
             % else:
index 55ebc1e..4fc0ec5 100644 (file)
@@ -12,7 +12,7 @@ Map captimes
     <h2>${map.name}</h2>
     <p><a href="${map_url}">Back to map info page</a></p>
 
-    <h3>Fastest flag capture times:</h3>
+    <h3>Fastest Flag Captures:</h3>
 
     <table class="table table-bordered table-condensed">
       <thead>
@@ -30,8 +30,8 @@ Map captimes
           <td class="tdcenter"><a class="btn btn-primary btn-small" href="${request.route_url('game_info', id=ct.game_id)}" title="View detailed information about this game">view</a></td>
           <td>${ct.fastest_cap.total_seconds()} seconds</td>
           <td class="player-nick">
-            % if rg.player_id > 2:
-            <a href="${request.route_url('player_info', id=rg.player_id)}" title="Go to the player info page for this player">${rg.nick_html_colors|n}</a>
+            % if ct.player_id > 2:
+            <a href="${request.route_url('player_info', id=ct.player_id)}" title="Go to the player info page for this player">${ct.player_nick_html|n}</a>
             % else:
             ${rg.nick_html_colors|n}
             % endif
@@ -44,3 +44,4 @@ Map captimes
 
   </div>
 </div>
+
index 62dac7e..7c75016 100644 (file)
@@ -34,7 +34,7 @@ Map Index
         <td><a href="${request.route_url("map_info", id=map.map_id)}" title="Go to this map's info page">${map.name}</a></th>
         <td><span class="abstime" data-epoch="${map.epoch()}" title="${map.create_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${map.fuzzy_date()}</span></td>
          <td class="tdcenter">
-          <a href="${request.route_url("game_finder", _query={'map_id':map.map_id})}" title="View recent games on this map">
+          <a href="${request.route_url("game_index", _query={'map_id':map.map_id})}" title="View recent games on this map">
             <i class="glyphicon glyphicon-list"></i>
           </a>
         </td>
index 9e872b5..5fd152d 100644 (file)
@@ -33,9 +33,9 @@ ${parent.title()}
     <table class="table table-hover table-condensed">
       <thead>
         <tr>
-          <th>#</th>
-          <th>Nick</th>
-          <th>Score</th>
+          <th style="width:40px;">#</th>
+          <th style="width:150px;">Nick</th>
+          <th style="width:90px;">Score</th>
         </tr>
       </thead>
       <tbody>
@@ -44,9 +44,9 @@ ${parent.title()}
         <tr>
           <td>${i}</td>
           % if score_player_id != '-':
-          <td><a href="${request.route_url('player_info', id=score_player_id)}" title="Go to the player info page for this player">${score_nick|n}</a></td>
+          <td class="nostretch" style="max-width:150px;"><a href="${request.route_url('player_info', id=score_player_id)}" title="Go to the player info page for this player">${score_nick|n}</a></td>
           % else:
-          <td>${score_nick}</td>
+          <td class="nostretch" style="max-width:150px;">${score_nick}</td>
           % endif
           <td>${score_value}</td>
         </tr>
@@ -54,7 +54,6 @@ ${parent.title()}
         % endfor
         </tbody>
     </table>
-    <p class="note">*Most active stats are from the past 7 days</p>
   </div>
 
 
@@ -63,9 +62,9 @@ ${parent.title()}
   <table class="table table-hover table-condensed">
     <thead>
       <tr>
-        <th>#</th>
-        <th>Nick</th>
-        <th>Playing Time</th>
+        <th style="width:40px;">#</th>
+        <th style="width:150px;">Nick</th>
+        <th style="width:90px;">Playing Time</th>
       </tr>
     </thead>
     <tbody>
@@ -74,9 +73,9 @@ ${parent.title()}
       <tr>
         <td>${i}</td>
         % if player_id != '-':
-        <td><a href="${request.route_url('player_info', id=player_id)}" title="Go to the player info page for this player">${nick|n}</a></td>
+        <td class="nostretch" style="max-width:150px;"><a href="${request.route_url('player_info', id=player_id)}" title="Go to the player info page for this player">${nick|n}</a></td>
         % else:
-        <td>${nick}</td>
+        <td class="nostretch" style="max-width:150px;">${nick}</td>
         % endif
         <td>${alivetime}</td>
       </tr>
@@ -92,9 +91,9 @@ ${parent.title()}
   <table class="table table-hover table-condensed">
     <thead>
       <tr>
-        <th>#</th>
-        <th>Name</th>
-        <th>Times Played</th>
+        <th style="width:40px;">#</th>
+        <th style="width:150px;">Name</th>
+        <th style="width:90px;"># Games</th>
       </tr>
     </thead>
     <tbody>
@@ -102,7 +101,7 @@ ${parent.title()}
     % for (server_id, name, times_played) in top_servers:
       <tr>
         <td>${i}</td>
-        <td><a href="${request.route_url('server_info', id=server_id)}" title="Go to the server info page for this server">${name}</a></td>
+        <td class="nostretch" style="max-width:150px;"><a href="${request.route_url('server_info', id=server_id)}" title="Go to the server info page for this server">${name}</a></td>
         <td>${times_played}</td>
       </tr>
       <% i = i+1 %>
@@ -112,6 +111,12 @@ ${parent.title()}
 </div>
 </div> <!-- /row -->
 
+<div class="row">
+  <div class="span12">
+    <p class="note">*Most active stats are from the past 7 days</p>
+  </div>
+</div>
+
 % if len(captimes) > 0:
 <div class="row">
   <div class="span6">
@@ -172,7 +177,7 @@ ${parent.title()}
           <td class="tdcenter"><span class="sprite sprite-${rg.game_type_cd}" alt="${rg.game_type_cd}" title="${rg.game_type_descr}"></span></td>
           <td><a href="${request.route_url('server_info', id=rg.server_id)}" title="Go to the detail page for this server">${rg.server_name}</a></td>
           <td><span class="abstime" data-epoch="${rg.epoch}" title="${rg.start_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${rg.fuzzy_date}</span></td>
-          <td>
+          <td class="nostretch">
             % if rg.player_id > 2:
             <a href="${request.route_url('player_info', id=rg.player_id)}" title="Go to the player info page for this player">${rg.nick_html_colors|n}</a>
             % else:
@@ -183,7 +188,7 @@ ${parent.title()}
         % endfor
       </tbody>
     </table>
-    <p><a href="${request.route_url('game_finder', _query={'map_id':gmap.map_id})}">More...</a></p>
+    <p><a href="${request.route_url('game_index', _query={'map_id':gmap.map_id})}">More...</a></p>
   </div>
 </div>
 % endif
index fdb9462..a9872e4 100644 (file)
@@ -6,13 +6,17 @@ Player captimes
 </%block>
 
 % if len(captimes) == 0:
-  <h2>Sorry, no caps yet. Get playing!</h2>
+<h2>Sorry, no caps yet. Get playing!</h2>
+<p><a href="${player_url}">Back to player info page</a></p>
 % else:
 
 <div class="row">
   <div class="span12">
-    <h3>Fastest Flag Captures by ${player.nick_html_colors()|n}</h3>
-    <p><a href="${player_url}">Back to player info page</a></p>
+    <h3>Fastest Flag Captures by
+      <a href="${request.route_url('player_info', id=player.player_id)}">
+        ${player.nick_html_colors()|n}
+      </a>
+    </h3>
  
     <table class="table table-hover table-condensed">
       <thead>
index 68c1a5e..18eeeab 100644 (file)
@@ -44,12 +44,12 @@ Recent Games
   <div class="span12 tabbable">
     <ul class="nav nav-tabs">
       % for game in games_played:
+      % if not game.game_type_cd in ['cq']:
       <li 
       % if game.game_type_cd == game_type_cd or (game.game_type_cd == 'overall' and game_type_cd is None):
       class="active"
       % endif
       >
-
       % if game.game_type_cd == 'overall':
       <a href="${request.route_url("player_game_index", player_id=player.player_id)}" alt="${game.game_type_cd}" title="" data-toggle="none">
       % else:
@@ -59,6 +59,7 @@ Recent Games
         ${game.game_type_cd} <br />
       </a>
       </li>
+      % endif
       % endfor
     </ul>
   </div>
index bacdd03..cea9643 100644 (file)
@@ -21,7 +21,7 @@ e wins ${games_played[0].wins}
 e favorite-map ${fav_maps['overall'].map_name} ${fav_maps['overall'].times_played} ${fav_maps['overall'].game_type_cd}
 % for game_type_cd in overall_stats.keys():
 % if game_type_cd != 'overall':
-G ${game_type_cd}
+G ${game_type_cd}
 % if game_type_cd in elos.keys():
 e elo ${elos[game_type_cd].elo}
 % endif
@@ -45,6 +45,5 @@ e wins ${gp.wins}
 % endif
 % endfor
 e favorite-map ${fav_maps[game_type_cd].map_name} ${fav_maps[game_type_cd].times_played}
-}
 % endif
 % endfor
index 70fd74e..677c058 100644 (file)
@@ -193,8 +193,15 @@ Player Information
 % else:
 <div class="row">
   <div class="span12">
-    <h2>${player.nick_html_colors()|n}</h2>
-    <h4><i><span class="abstime" data-epoch="${player.epoch()}" title="${player.create_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">Joined ${player.joined_pretty_date()}</span> (player #${player.player_id})</i></h4>
+    <h2>
+      ${player.nick_html_colors()|n}
+    </h2>
+    <h4>
+      <i><span class="abstime" data-epoch="${player.epoch()}" title="${player.create_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">Joined ${player.joined_pretty_date()}</span> (player #${player.player_id})</i>
+      % if cake_day:
+      <img src="/static/images/icons/24x24/cake.png" title="Happy cake day!" />
+      % endif
+    </h4>
   </div>
 </div>
 
@@ -202,6 +209,7 @@ Player Information
   <div id="gbtabcontainer" class="tabbable tabs-below">
     <div class="tab-content">
       % for g in games_played:
+      % if not g.game_type_cd in ['cq']:
       <div class="tab-pane fade in 
         % if g.game_type_cd == 'overall':
         active
@@ -211,19 +219,31 @@ Player Information
           <p>
           % if g.game_type_cd in overall_stats:
           Last Played: <small><span class="abstime" data-epoch="${overall_stats[g.game_type_cd].last_played_epoch}" title="${overall_stats[g.game_type_cd].last_played.strftime('%a, %d %b %Y %H:%M:%S UTC')}"> ${overall_stats[g.game_type_cd].last_played_fuzzy} </span> <br /></small>
+          % else:
+          <small><br /></small>
           % endif
 
-          Games Played: <small>${g.games} <br /></small>
+          Games Played: 
+          % if g.game_type_cd == 'overall':
+          <small><a href="${request.route_url("player_game_index", player_id=player.player_id)}" title="View recent games">
+          % else:
+          <small><a href="${request.route_url("player_game_index", player_id=player.player_id, _query={'type':g.game_type_cd})}" title="View recent ${overall_stats[g.game_type_cd].game_type_descr} games">
+          % endif
+          ${g.games}</a> <br /></small>
 
           Playing Time: <small>${overall_stats[g.game_type_cd].total_playing_time} <br /></small>
 
           % if g.game_type_cd in fav_maps:
-          Favorite Map: <small>${fav_maps[g.game_type_cd].map_name} <br /></small>
+          Favorite Map: <small><a href="${request.route_url("map_info", id=fav_maps[g.game_type_cd].map_id)}" title="Go to the detail page for this map">${fav_maps[g.game_type_cd].map_name}</a> <br /></small>
+          % else:
+          <small><br /></small>
           % endif
           
           % if g.game_type_cd == 'ctf':
           % if overall_stats[g.game_type_cd].total_captures is not None:
-          <small><a href="${request.route_url("player_captimes", id=player.player_id)}">Fastest flag captures...</a></small>
+          <small><a href="${request.route_url("player_captimes", id=player.player_id)}">Fastest flag captures...</a> <br /></small>
+          % else:
+          <small><br /></small>
           % endif
           % else:
           <small><br /></small>
@@ -239,6 +259,8 @@ Player Information
           % if overall_stats[g.game_type_cd].k_d_ratio is not None:
           Kill Ratio: <small>${round(overall_stats[g.game_type_cd].k_d_ratio,2)} (${overall_stats[g.game_type_cd].total_kills} kills, ${overall_stats[g.game_type_cd].total_deaths} deaths) <br /></small>
           % endif
+          % else:
+          <small><br /></small>
           % endif
 
           % if g.game_type_cd in elos:
@@ -247,20 +269,39 @@ Player Information
           % else:
           Elo: <small>${round(elos[g.game_type_cd].elo,2)} (${elos[g.game_type_cd].games} games) <br /></small>
           % endif
+          % else:
+          <small><br /></small>
           % endif
 
           % if g.game_type_cd in ranks:
           % if g.game_type_cd == 'overall':
-          Best Rank: <small>${ranks[g.game_type_cd].rank} of ${ranks[g.game_type_cd].max_rank} (${ranks[g.game_type_cd].game_type_cd}, percentile: ${round(ranks[g.game_type_cd].percentile,2)})<br /></small>
-
+          Best Rank: <small>${ranks[g.game_type_cd].rank} of ${ranks[g.game_type_cd].max_rank} (${ranks[g.game_type_cd].game_type_cd}, percentile: ${round(ranks[g.game_type_cd].percentile,2)}) <br /></small>
           % else:
-          Rank: <small>${ranks[g.game_type_cd].rank} of ${ranks[g.game_type_cd].max_rank} (percentile: ${round(ranks[g.game_type_cd].percentile,2)})<br /></small>
+          Rank: 
+          <small>
+            <a href="
+              % if ranks[g.game_type_cd].rank % 20 == 0:
+                ${request.route_url('rank_index', game_type_cd=g.game_type_cd, _query={'page':ranks[g.game_type_cd].rank/20})}
+
+              % else:
+                ${request.route_url('rank_index', game_type_cd=g.game_type_cd, _query={'page':ranks[g.game_type_cd].rank/20+1})}
+
+              % endif
+            " title="Player rank page for this player">
+            ${ranks[g.game_type_cd].rank} of ${ranks[g.game_type_cd].max_rank}</a>
+            (percentile: ${round(ranks[g.game_type_cd].percentile,2)})
+            <br />
+          </small>
           % endif
+          % else:
+          <small><br /></small>
           % endif
 
           % if g.game_type_cd == 'ctf':
-          % if  overall_stats[g.game_type_cd].cap_ratio is not None:
+          % if overall_stats[g.game_type_cd].cap_ratio is not None:
           Cap Ratio: <small>${round(overall_stats[g.game_type_cd].cap_ratio,2)} (${overall_stats[g.game_type_cd].total_captures} captures, ${overall_stats[g.game_type_cd].total_pickups} pickups) <br /></small>
+          % else:
+          <small><br /></small>
           % endif
           % else:
           <small><br /></small>
@@ -268,6 +309,7 @@ Player Information
           </p>
         </div>
       </div>
+      % endif
       % endfor
     </div>
   </div>
@@ -277,7 +319,7 @@ Player Information
     <ul id="gbtab" class="nav nav-tabs">
       % for g in games_played:
       <li>
-      <a href="#tab-${g.game_type_cd}" data-toggle="tab" alt="${g.game_type_cd}" title="">
+      <a href="#tab-${g.game_type_cd}" data-toggle="tab" alt="${g.game_type_cd}" title="${overall_stats[g.game_type_cd].game_type_descr}">
         <span class="sprite sprite-${g.game_type_cd}"> </span><br />
         ${g.game_type_cd} <br />
         <small>(${g.games})</small>
@@ -492,7 +534,7 @@ Player Information
       </tbody>
     </table>
     % if total_games > 10:
-    <p><a href="${request.route_url("player_game_index", player_id=player.player_id, page=1)}" title="Game index for ${player.nick}">More...</a></p>
+    <p><a href="${request.route_url("player_game_index", player_id=player.player_id, page=1)}" title="Game index for ${player.stripped_nick}">More...</a></p>
     % endif
   </div>
 </div>
index fbfdd8a..809a164 100644 (file)
@@ -21,15 +21,15 @@ Capture The Flag Rank Index
     % else:
     <table id="rank-index-table" class="table table-hover table-condensed" border="1">
       <tr>
-        <th>Rank</th>
-        <th>Nick</th>
-        <th>Elo</th>
+        <th style="width:40px;">Rank</th>
+        <th style="width:420px;">Nick</th>
+        <th style="width:90px;">Elo</th>
       </tr>
       <% i = 1 %>
       % for rank in ranks:
       <tr>
         <td>${rank.rank}</td>
-        <td><a href="${request.route_url("player_info", id=rank.player_id)}" title="Go to this player's info page">${rank.nick_html_colors()|n}</a></th>
+        <td class="nostretch" style="max-width:420px;"><a href="${request.route_url("player_info", id=rank.player_id)}" title="Go to this player's info page">${rank.nick_html_colors()|n}</a></th>
         <td>${int(round(rank.elo))}</th>
       </tr>
       <% i += 1 %>
index cfcdfc3..8c94d43 100644 (file)
@@ -1,54 +1,42 @@
-<%def name="scoreboard(game_type_cd, pgstats, teams=None, show_elo=False, show_latency=False)">
-##teams: { scoreboardpos : ( teamname, teamscore, playercount ) }
-% if teamscores:
-<table class="table table-condensed">
-<tbody><tr class="teamscores">
-% for team,score in sorted(teamscores.items(), key=lambda x:x[1], reverse=True):
-    <td class="${team} teamscore" width="${100/len(teamscores)}%">${team.capitalize()} Team: ${score}</td>
-% endfor
-  </tr></tbody>
-</table>
-% endif
-
-<table class="table table-hover table-condensed">
+<%def name="scoreboard(game_type_cd, pgstats, show_elo=False, show_latency=False)">
+<table  class="table table-hover table-condensed">
   ${scoreboard_header(game_type_cd, pgstats[0])}
   <tbody>
   % for pgstat in pgstats:
-  <tr class="${pgstat.team_html_color()}">
-    <td class="nostretch">
-      % if pgstat.player_id > 2:
-      <a href="${request.route_url("player_info", id=pgstat.player_id)}"
-        title="Go to the info page for this player">
-        <span class="nick">${pgstat.nick_html_colors()|n}</span>
-      </a>
-      % else:
-      <span class="nick">${pgstat.nick_html_colors()|n}</span>
+    <tr class="${pgstat.team_html_color()}">
+      % if show_latency and pgstat.avg_latency is not None:
+        <td class="tdcenter">
+          ${int(round(pgstat.avg_latency))}
+        </td>
+      % elif show_latency:
+        <td class="tdcenter">-</td>
       % endif
-    </td>
-    % if show_latency and pgstat.avg_latency is not None:
-    <td class="scoreboard-entry">
-      ${int(round(pgstat.avg_latency))}
-    </td>
-    % elif show_latency:
-    <td class="scoreboard-entry"></td>
-    % endif
-    ${scoreboard_row(game_type_cd, pgstat)}
-    % if game_type_cd != 'cts':
-    <td class="scoreboard-entry">${pgstat.score}</td>
-    % endif
-    % if show_elo:
-    % if pgstat.elo_delta is not None:
-    <td class="scoreboard-entry">${round(pgstat.elo_delta,2)}</td>
-    % else:
-    <td class="scoreboard-entry">-</td>
-    % endif
-    % endif
-    ##% if teams:
-    ##% if teams.has_key(pgstat.scoreboardpos):
-    ##<td class="scoreboard-entry teamscore" rowspan="${teams[pgstat.scoreboardpos].playercount}">${teams[pgstat.scoreboardpos].teamscore}</td>
-    ##% endif
-    ##% endif
-  </tr>
+
+      <td class="player-nick">
+        % if pgstat.player_id > 2:
+          <a href="${request.route_url("player_info", id=pgstat.player_id)}"
+            title="Go to the info page for this player">
+            <span class="nick">${pgstat.nick_html_colors()|n}</span>
+          </a>
+        % else:
+          <span class="nick">${pgstat.nick_html_colors()|n}</span>
+        % endif
+      </td>
+
+      ${scoreboard_row(game_type_cd, pgstat)}
+
+      % if game_type_cd != 'cts':
+        <td class="player-score">${pgstat.score}</td>
+      % endif
+
+      % if show_elo:
+        % if pgstat.elo_delta is not None:
+          <td>${round(pgstat.elo_delta,2)}</td>
+        % else:
+          <td>-</td>
+        % endif
+      % endif
+    </tr>
   % endfor
   </tbody>
 </table>
 % if game_type_cd == 'as':
 <thead>
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="kills">Kills</th>
     <th class="deaths">Deaths</th>
     <th class="suicides">Suicides</th>
     <th class="objectives">Objectives</th>
     <th class="score">Score</th>
-    ##% if teams:
-    ##<th class="teamscore">Teamscore</th>
-    ##% endif
     % if show_elo:
     <th>Elo Change</th>
     % endif
 % if game_type_cd in 'ca' 'dm' 'duel' 'rune' 'tdm':
 <thead>
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="kills">Kills</th>
     <th class="deaths">Deaths</th>
     <th class="suicides">Suicides</th>
     <th class="score">Score</th>
-    ##% if teams:
-    ##<th class="teamscore">Teamscore</th>
-    ##% endif
     % if show_elo:
     <th>Elo Change</th>
     % endif
 % if game_type_cd == 'cq':
 <thead>
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="kills">Kills</th>
     <th class="deaths">Deaths</th>
     <th class="captured">Captured</th>
     <th class="released">Released</th>
     <th class="score">Score</th>
-    ##% if show_elo:
-    ##<th>Elo Change</th>
-    ##% endif
+    % if show_elo:
+    <th>Elo Change</th>
+    % endif
   </tr>
 </thead>
 % endif
 % if game_type_cd == 'cts':
 <thead>
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="fastest">Fastest Time</th>
     <th class="deaths">Deaths</th>
   </tr>
 % if game_type_cd == 'ctf':
 <thead class="ctf ${pgstat.team_html_color()}">
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="kills">Kills</th>
     <th class="captures">Captures</th>
     <th class="pickups">Pickups</th>
     <th class="fck" title="Flag Carrier Kill">FCK</th>
     <th class="returns">Returns</th>
     <th class="score">Score</th>
-    ##% if teams:
-    ##<th class="teamscore">Teamscore</th>
-    ##% endif
     % if show_elo:
     <th>Elo Change</th>
     % endif
 % if game_type_cd == 'dom':
 <thead class="dom ${pgstat.team_html_color()}">
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="kills">Kills</th>
     <th class="deaths">Deaths</th>
     <th class="takes">Takes</th>
     <th class="ticks">Ticks</th>
     <th class="score">Score</th>
-    ##% if teams:
-    ##<th class="teamscore">Teamscore</th>
-    ##% endif
     % if show_elo:
     <th>Elo Change</th>
     % endif
 % if game_type_cd in 'ft' 'freezetag':
 <thead class="freezetag ${pgstat.team_html_color()}">
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="kills">Kills</th>
     <th class="deaths">Deaths</th>
     <th class="revivals">Revivals</th>
     <th class="score">Score</th>
-    ##% if teams:
-    ##<th class="teamscore">Teamscore</th>
-    ##% endif
     % if show_elo:
     <th>Elo Change</th>
     % endif
 % if game_type_cd in 'ka' 'keepaway':
 <thead>
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="kills">Kills</th>
     <th class="deaths">Deaths</th>
     <th class="pickups">Pickups</th>
     <th class="bctime">BC Time</th>
     <th class="bckills">BC Kills</th>
-    ##% if teams:
-    ##<th class="teamscore">Teamscore</th>
-    ##% endif
+    <th class="score">Score</th>
     % if show_elo:
     <th>Elo Change</th>
     % endif
 % if game_type_cd == 'kh':
 <thead class="kh ${pgstat.team_html_color()}">
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="kills">Kills</th>
     <th class="deaths">Deaths</th>
     <th class="pickups">Pickups</th>
     <th class="destroys">Destroys</th>
     <th class="kckills">KC Kills</th>
     <th class="score">Score</th>
-    ##% if teams:
-    ##<th class="teamscore">Teamscore</th>
-    ##% endif
     % if show_elo:
     <th>Elo Change</th>
     % endif
 % if game_type_cd in 'nb' 'nexball':
 <thead class="nb ${pgstat.team_html_color()}">
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="goals">Goals</th>
     <th class="faults">Faults</th>
     <th class="score">Score</th>
-    ##% if teams:
-    ##<th class="teamscore">Teamscore</th>
-    ##% endif
     % if show_elo:
     <th>Elo Change</th>
     % endif
 % if game_type_cd == 'rc':
 <thead>
   <tr>
-    <th class="nick">Nick</th>
     % if show_latency:
     <th class="ping">Ping</th>
     % endif
+    <th class="nick">Nick</th>
     <th class="laps">Laps</th>
     <th class="fastest">Fastest Lap</th>
     <th class="time">Time</th>
 ##### SCOREBOARD ROWS #####
 <%def name="scoreboard_row(game_type_cd, pgstat)">
 % if game_type_cd == 'as':
-<td class="scoreboard-entry">${pgstat.kills}</td>
-<td class="scoreboard-entry">${pgstat.deaths}</td>
-<td class="scoreboard-entry">${pgstat.suicides}</td>
-<td class="scoreboard-entry">${pgstat.collects}</td>
+  <td>${pgstat.kills}</td>
+  <td>${pgstat.deaths}</td>
+  <td>${pgstat.suicides}</td>
+  <td>${pgstat.collects}</td>
 % endif
 
 % if game_type_cd in 'ca' 'dm' 'duel' 'rune' 'tdm':
-<td class="scoreboard-entry">${pgstat.kills}</td>
-<td class="scoreboard-entry">${pgstat.deaths}</td>
-<td class="scoreboard-entry">${pgstat.suicides}</td>
+  <td>${pgstat.kills}</td>
+  <td>${pgstat.deaths}</td>
+  <td>${pgstat.suicides}</td>
 % endif
 
 % if game_type_cd == 'cq':
-<td class="scoreboard-entry">${pgstat.kills}</td>
-<td class="scoreboard-entry">${pgstat.deaths}</td>
-<td class="scoreboard-entry">${pgstat.captures}</td>
-<td class="scoreboard-entry">${pgstat.drops}</td>
+  <td>${pgstat.kills}</td>
+  <td>${pgstat.deaths}</td>
+  <td>${pgstat.captures}</td>
+  <td>${pgstat.drops}</td>
 % endif
 
 % if game_type_cd == 'cts':
-% if pgstat.fastest is not None:
-<td class="scoreboard-entry">${round(float(pgstat.fastest.seconds) + (pgstat.fastest.microseconds/1000000.0), 2)}</td>
-% else:
-<td class="scoreboard-entry">-</td>
-% endif
-<td class="scoreboard-entry">${pgstat.deaths}</td>
+  % if pgstat.fastest is not None:
+    <td>${round(float(pgstat.fastest.seconds) + (pgstat.fastest.microseconds/1000000.0), 2)}</td>
+  % else:
+    <td>-</td>
+  % endif
+
+  <td>${pgstat.deaths}</td>
 % endif
 
 % if game_type_cd == 'ctf':
-<td class="scoreboard-entry">${pgstat.kills}</td>
-<td class="scoreboard-entry">${pgstat.captures}</td>
-<td class="scoreboard-entry">${pgstat.pickups}</td>
-<td class="scoreboard-entry">${pgstat.carrier_frags}</td>
-<td class="scoreboard-entry">${pgstat.returns}</td>
+  <td>${pgstat.kills}</td>
+  <td>${pgstat.captures}</td>
+  <td>${pgstat.pickups}</td>
+  <td>${pgstat.carrier_frags}</td>
+  <td>${pgstat.returns}</td>
 % endif
 
 % if game_type_cd == 'dom':
-<td class="scoreboard-entry">${pgstat.kills}</td>
-<td class="scoreboard-entry">${pgstat.deaths}</td>
-<td class="scoreboard-entry">${pgstat.pickups}</td>
-<td class="scoreboard-entry">${pgstat.drops}</td>
+  <td>${pgstat.kills}</td>
+  <td>${pgstat.deaths}</td>
+  <td>${pgstat.pickups}</td>
+  <td>${pgstat.drops}</td>
 % endif
 
 % if game_type_cd in 'ft' 'freezetag':
-<td class="scoreboard-entry">${pgstat.kills}</td>
-<td class="scoreboard-entry">${pgstat.deaths}</td>
-<td class="scoreboard-entry">${pgstat.revivals}</td>
+  <td>${pgstat.kills}</td>
+  <td>${pgstat.deaths}</td>
+  <td>${pgstat.revivals}</td>
 % endif
 
 % if game_type_cd in 'ka' 'keepaway':
-<td class="scoreboard-entry">${pgstat.kills}</td>
-<td class="scoreboard-entry">${pgstat.deaths}</td>
-<td class="scoreboard-entry">${pgstat.pickups}</td>
-
-% if pgstat.time is not None:
-<td class="scoreboard-entry">${round(float(pgstat.time.seconds) + (pgstat.time.microseconds/1000000.0), 2)}</td>
-% else:
-<td class="scoreboard-entry">-</td>
-% endif
+  <td>${pgstat.kills}</td>
+  <td>${pgstat.deaths}</td>
+  <td>${pgstat.pickups}</td>
+
+  % if pgstat.time is not None:
+    <td>${round(float(pgstat.time.seconds) + (pgstat.time.microseconds/1000000.0), 2)}</td>
+  % else:
+    <td>-</td>
+  % endif
 
-<td class="scoreboard-entry">${pgstat.fckills}</td>
+  <td>${pgstat.carrier_frags}</td>
 % endif
 
 % if game_type_cd == 'kh':
-<td class="scoreboard-entry">${pgstat.kills}</td>
-<td class="scoreboard-entry">${pgstat.deaths}</td>
-<td class="scoreboard-entry">${pgstat.pickups}</td>
-<td class="scoreboard-entry">${pgstat.captures}</td>
-<td class="scoreboard-entry">${pgstat.drops}</td>
-<td class="scoreboard-entry">${pgstat.pushes}</td>
-<td class="scoreboard-entry">${pgstat.destroys}</td>
-<td class="scoreboard-entry">${pgstat.carrier_frags}</td>
+  <td>${pgstat.kills}</td>
+  <td>${pgstat.deaths}</td>
+  <td>${pgstat.pickups}</td>
+  <td>${pgstat.captures}</td>
+  <td>${pgstat.drops}</td>
+  <td>${pgstat.pushes}</td>
+  <td>${pgstat.destroys}</td>
+  <td>${pgstat.carrier_frags}</td>
 % endif
 
 % if game_type_cd in 'nb' 'nexball':
-<td class="scoreboard-entry">${pgstat.captures}</td>
-<td class="scoreboard-entry">${pgstat.drops}</td>
+  <td>${pgstat.captures}</td>
+  <td>${pgstat.drops}</td>
 % endif
 
 % if game_type_cd == 'rc':
-<td class="scoreboard-entry">${pgstat.laps}</td>
+  <td>${pgstat.laps}</td>
 
-% if pgstat.fastest is not None:
-<td class="scoreboard-entry">${round(float(pgstat.fastest.seconds) + (pgstat.fastest.microseconds/1000000.0), 2)}</td>
-% else:
-<td class="scoreboard-entry">-</td>
-% endif
+  % if pgstat.fastest is not None:
+    <td>${round(float(pgstat.fastest.seconds) + (pgstat.fastest.microseconds/1000000.0), 2)}</td>
+  % else:
+    <td>-</td>
+  % endif
 
-% if pgstat.time is not None:
-<td class="scoreboard-entry">${round(float(pgstat.time.seconds) + (pgstat.time.microseconds/1000000.0), 2)}</td>
-% else:
-<td class="scoreboard-entry">-</td>
-% endif
+  % if pgstat.time is not None:
+    <td>${round(float(pgstat.time.seconds) + (pgstat.time.microseconds/1000000.0), 2)}</td>
+  % else:
+    <td>-</td>
+  % endif
 % endif
 
 </%def>
index f105436..7637a3a 100644 (file)
@@ -139,7 +139,7 @@ Advanced Search
         <td><a href="${request.route_url("server_info", id=server.server_id)}" title="Go to this server's info page">${server.name}</a></th>
         <td><span class="abstime" data-epoch="${server.epoch()}" title="${server.create_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${server.fuzzy_date()}</span></td>
         <td class="tdcenter">
-          <a href="${request.route_url("game_finder", _query={'server_id':server.server_id})}" title="View recent games on this server">
+          <a href="${request.route_url("game_index", _query={'server_id':server.server_id})}" title="View recent games on this server">
             <i class="glyphicon glyphicon-list"></i>
           </a>
         </td>
@@ -165,7 +165,7 @@ Advanced Search
           <td><a href="${request.route_url("map_info", id=map.map_id)}" title="Go to this map's info page">${map.name}</a></th>
           <td><span class="abstime" data-epoch="${map.epoch()}" title="${map.create_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${map.fuzzy_date()}</span></td>
            <td class="tdcenter">
-            <a href="${request.route_url("game_finder", _query={'map_id':map.map_id})}" title="View recent games on this map">
+            <a href="${request.route_url("game_index", _query={'map_id':map.map_id})}" title="View recent games on this map">
               <i class="glyphicon glyphicon-list"></i>
             </a>
           </td>
index d266ba3..7c2e210 100644 (file)
@@ -34,7 +34,7 @@ Server Index
         <td><a href="${request.route_url("server_info", id=server.server_id)}" title="Go to this server's info page">${server.name}</a></th>
         <td><span class="abstime" data-epoch="${server.epoch()}" title="${server.create_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${server.fuzzy_date()}</span></td>
         <td class="tdcenter">
-          <a href="${request.route_url("game_finder", _query={'server_id':server.server_id})}" title="View recent games on this server">
+          <a href="${request.route_url("game_index", _query={'server_id':server.server_id})}" title="View recent games on this server">
             <i class="glyphicon glyphicon-list"></i>
           </a>
         </td>
index 5c2cf73..f103045 100644 (file)
@@ -24,7 +24,13 @@ Server Information
   <div class="span12">
     <h2>${server.name}</h2>
     <p>
-      IP Address: ${server.ip_addr} <br />
+      IP Address: 
+      % if server.port is not None:
+      ${server.ip_addr}:${server.port}
+      % else:
+      ${server.ip_addr}
+      % endif
+      <br />
       Revision: ${server.revision} <br />
       Added <span class="abstime" data-epoch="${server.epoch()}" title="${server.create_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${server.fuzzy_date()}</span> <br />
     </p>
@@ -38,9 +44,9 @@ Server Information
       <table class="table table-hover table-condensed">
         <thead>
           <tr>
-            <th>#</th>
-            <th>Nick</th>
-            <th>Score</th>
+            <th style="width:40px;">#</th>
+            <th style="width:150px;">Nick</th>
+            <th style="width:90px;">Score</th>
           </tr>
         </thead>
         <tbody>
@@ -49,9 +55,9 @@ Server Information
           <tr>
             <td>${i}</td>
             % if score_player_id != '-':
-            <td><a href="${request.route_url('player_info', id=score_player_id)}" title="Go to the player info page for this player">${score_nick|n}</a></td>
+            <td class="nostretch" style="max-width:150px;"><a href="${request.route_url('player_info', id=score_player_id)}" title="Go to the player info page for this player">${score_nick|n}</a></td>
             % else:
-            <td>${score_nick}</td>
+            <td class="nostretch" style="max-width:150px;">${score_nick}</td>
             % endif
             <td>${score_value}</td>
           </tr>
@@ -59,7 +65,6 @@ Server Information
         % endfor
         </tbody>
       </table>
-      <p class="note">*Most active stats are from the past 7 days</p>
   </div> <!-- /span4 -->
 
 
@@ -68,9 +73,9 @@ Server Information
     <table class="table table-hover table-condensed">
       <thead>
         <tr>
-          <th>#</th>
-          <th>Nick</th>
-          <th>Playing Time</th>
+          <th style="width:40px;">#</th>
+          <th style="width:150px;">Nick</th>
+          <th style="width:90px;">Playing Time</th>
         </tr>
       </thead>
       <tbody>
@@ -79,9 +84,9 @@ Server Information
         <tr>
           <td>${i}</td>
           % if player_id != '-':
-          <td><a href="${request.route_url('player_info', id=player_id)}" title="Go to the player info page for this player">${nick|n}</a></td>
+          <td class="nostretch" style="max-width:150px;"><a href="${request.route_url('player_info', id=player_id)}" title="Go to the player info page for this player">${nick|n}</a></td>
           % else:
-          <td>${nick}</td>
+          <td class="nostretch" style="max-width:150px;">${nick}</td>
           % endif
           <td>${alivetime}</td>
         </tr>
@@ -97,9 +102,9 @@ Server Information
     <table class="table table-hover table-condensed">
       <thead>
         <tr>
-          <th>#</th>
-          <th>Map</th>
-          <th># Games</th>
+          <th style="width:40px;">#</th>
+          <th style="width:150px;">Map</th>
+          <th style="width:120px;"># Games</th>
         </tr>
       </thead>
       <tbody>
@@ -108,9 +113,9 @@ Server Information
         <tr>
           <td>${i}</td>
           % if map_id != '-':
-          <td><a href="${request.route_url('map_info', id=map_id)}" title="Go to the map info page for ${name}">${name}</a></td>
+          <td class="nostretch" style="max-width:150px;"><a href="${request.route_url('map_info', id=map_id)}" title="Go to the map info page for ${name}">${name}</a></td>
           % else:
-          <td>${name}</td>
+          <td class="nostretch" style="max-width:150px;">${name}</td>
           % endif
           <td>${count}</td>
         </tr>
@@ -121,7 +126,11 @@ Server Information
   </div> <!-- /span4 -->
 
 </div> <!-- /row -->
-
+<div class="row">
+  <div class="span12">
+    <p class="note">*Most active stats are from the past 7 days</p>
+  </div>
+</div>
 
 
 % if len(recent_games) > 0:
@@ -145,7 +154,7 @@ Server Information
           <td class="tdcenter"><span class="sprite sprite-${rg.game_type_cd}" alt="${rg.game_type_cd}" title="${rg.game_type_descr}"></span></td>
           <td><a href="${request.route_url('map_info', id=rg.map_id)}" title="Go to the map detail page for this map">${rg.map_name}</a></td>
           <td><span class="abstime" data-epoch="${rg.epoch}" title="${rg.start_dt.strftime('%a, %d %b %Y %H:%M:%S UTC')}">${rg.fuzzy_date}</span></td>
-          <td>
+          <td class="nostretch">
             % if rg.player_id > 2:
             <a href="${request.route_url('player_info', id=rg.player_id)}" title="Go to the player info page for this player">${rg.nick_html_colors|n}</a>
             % else:
@@ -156,7 +165,7 @@ Server Information
         % endfor
       </tbody>
     </table>
-    <p><a href="${request.route_url('game_finder', _query={'server_id':server.server_id})}">More...</a></p>
+    <p><a href="${request.route_url('game_index', _query={'server_id':server.server_id})}">More...</a></p>
   </div>
 </div>
 % endif
index b400d2a..d859930 100644 (file)
@@ -1,9 +1,16 @@
+import logging
+import pyramid.httpexceptions
 import re
 from colorsys import rgb_to_hls, hls_to_rgb
 from cgi import escape as html_escape
 from datetime import datetime, timedelta
 from decimal import Decimal
 from collections import namedtuple
+from xonstat.d0_blind_id import d0_blind_id_verify
+
+
+log = logging.getLogger(__name__)
+
 
 # Map of special chars to ascii from Darkplace's console.c.
 _qfont_table = [
@@ -203,3 +210,65 @@ def to_json(data):
             result[key] = to_json(value.to_dict())
     return result
 
+
+def is_leap_year(today_dt=None):
+    if today_dt is None:
+        today_dt = datetime.utcnow()
+
+    if today_dt.year % 400 == 0:
+       leap_year = True
+    elif today_dt.year % 100 == 0:
+       leap_year = False
+    elif today_dt.year % 4 == 0:
+       leap_year = True
+    else:
+       leap_year = False
+
+    return leap_year
+
+
+def is_cake_day(create_dt, today_dt=None):
+    cake_day = False
+
+    if today_dt is None:
+        today_dt = datetime.utcnow()
+
+    # cakes are given on the first anniversary, not the actual create date!
+    if datetime.date(today_dt) != datetime.date(create_dt):
+        if today_dt.day == create_dt.day and today_dt.month == create_dt.month:
+            cake_day = True
+
+        # leap year people get their cakes on March 1
+        if not is_leap_year(today_dt) and create_dt.month == 2 and create_dt.day == 29:
+            if today_dt.month == 3 and today_dt.day == 1:
+                cake_day = True
+
+    return cake_day
+
+
+def verify_request(request):
+    """Verify requests using the d0_blind_id library"""
+
+    # first determine if we should be verifying or not
+    val_verify_requests = request.registry.settings.get('xonstat.verify_requests', 'true')
+    if val_verify_requests == "true":
+        flg_verify_requests = True
+    else:
+        flg_verify_requests = False
+
+    try:
+        (idfp, status) = d0_blind_id_verify(
+                sig=request.headers['X-D0-Blind-Id-Detached-Signature'],
+                querystring='',
+                postdata=request.body)
+
+        log.debug('\nidfp: {0}\nstatus: {1}'.format(idfp, status))
+    except:
+        idfp = None
+        status = None
+
+    if flg_verify_requests and not idfp:
+        log.debug("ERROR: Unverified request")
+        raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")
+
+    return (idfp, status)
index f238d7a..d88a139 100644 (file)
@@ -22,4 +22,6 @@ from xonstat.views.server import server_index_json
 from xonstat.views.search import search_q, search
 from xonstat.views.search import search_json
 
+from xonstat.views.exceptions   import notfound
+
 from xonstat.views.main   import main_index
diff --git a/xonstat/views/exceptions.py b/xonstat/views/exceptions.py
new file mode 100644 (file)
index 0000000..0cc9148
--- /dev/null
@@ -0,0 +1,5 @@
+import logging
+import random
+
+def notfound(request):
+    return {'rand': int(random.random() * 100)}
index 0198446..58d48f8 100644 (file)
@@ -2,6 +2,7 @@ import datetime
 import logging
 import re
 import time
+from collections import OrderedDict
 from pyramid.response import Response
 from sqlalchemy import desc, func, over
 from collections import namedtuple
@@ -88,10 +89,7 @@ def _game_info_data(request):
     else:
         show_elo = False
 
-    if request.params.has_key('show_latency'):
-        show_latency = True
-    else:
-        show_latency = False
+    show_latency = False
 
     try:
         notfound = False
@@ -108,12 +106,34 @@ def _game_info_data(request):
                 order_by(PlayerGameStat.score).\
                 all()
 
+        # if at least one player has a valid latency, we'll show the column
+        for pgstat in pgstats:
+            if pgstat.avg_latency is not None:
+                show_latency = True
+
+        q = DBSession.query(TeamGameStat).\
+                filter(TeamGameStat.game_id == game_id)
+        if game.game_type_cd == 'ctf':
+            q = q.order_by(TeamGameStat.caps.desc())
+        elif game.game_type_cd == 'ca':
+            q = q.order_by(TeamGameStat.rounds.desc())
+        # dom -> ticks, rc -> laps, nb -> goals, as -> objectives
+
+        q = q.order_by(TeamGameStat.score.desc())
+
+        tgstats = q.all()
+
+        stats_by_team = OrderedDict()
+        for pgstat in pgstats:
+            if pgstat.team not in stats_by_team.keys():
+                stats_by_team[pgstat.team] = []
+            stats_by_team[pgstat.team].append(pgstat)
+
         captimes = []
         if game.game_type_cd == 'ctf':
             for pgstat in pgstats:
                 if pgstat.fastest is not None:
                     captimes.append(pgstat)
-
             captimes = sorted(captimes, key=lambda x:x.fastest)
 
         teamscores = {}
@@ -183,11 +203,13 @@ def _game_info_data(request):
         map = None
         gametype = None
         pgstats = None
+        tgstats = None
         pwstats = None
         captimes = None
         teams = None
         show_elo = False
         show_latency = False
+        stats_by_team = None
         raise inst
 
     return {'game':game,
@@ -195,12 +217,14 @@ def _game_info_data(request):
             'map':map,
             'gametype':gametype,
             'pgstats':pgstats,
+            'tgstats':tgstats,
             'pwstats':pwstats,
             'captimes':captimes,
             'teams':teams,
             'teamscores':teamscores,
             'show_elo':show_elo,
             'show_latency':show_latency,
+            'stats_by_team':stats_by_team,
             }
 
 
index a3b3f53..8a10a48 100644 (file)
@@ -119,12 +119,12 @@ def recent_games_q(server_id=None, map_id=None, player_id=None,
                 filter(PlayerGameStat.player_id==player_id)
         else:
             recent_games_q = recent_games_q.\
-                filter(PlayerGameStat.rank==1).\
+                filter(PlayerGameStat.scoreboardpos==1).\
                 filter(Game.game_id==pgstat_alias.game_id).\
                 filter(pgstat_alias.player_id==player_id)
     else:
         recent_games_q = recent_games_q.\
-            filter(PlayerGameStat.rank==1)
+            filter(PlayerGameStat.scoreboardpos==1)
 
     if game_type_cd is not None:
         recent_games_q = recent_games_q.\
index cd6f80f..004f22a 100644 (file)
@@ -166,7 +166,7 @@ def map_info_json(request):
 
 def map_captimes_data(request):
     map_id = int(request.matchdict['id'])
-
+        
     MapCaptimes = namedtuple('PlayerCaptimes', ['fastest_cap', 'create_dt', 'create_dt_epoch', 'create_dt_fuzzy',
         'player_id', 'player_nick', 'player_nick_stripped', 'player_nick_html',
         'game_id', 'server_id', 'server_name'])
@@ -193,6 +193,7 @@ def map_captimes_data(request):
                   "AND  pgs.player_id = ct.player_id "
                   "AND  pgs.game_id = ct.game_id "
                 "ORDER  BY ct.fastest_cap "
+                "LIMIT  25"
             ).params(map_id=map_id).all()
 
     mmap = DBSession.query(Map).filter_by(map_id=map_id).one()
index 197a4d2..fa8cde4 100644 (file)
@@ -8,6 +8,7 @@ from collections import namedtuple
 from webhelpers.paginate import Page
 from xonstat.models import *
 from xonstat.util import page_url, to_json, pretty_date, datetime_seconds
+from xonstat.util import is_cake_day, verify_request
 from xonstat.views.helpers import RecentGame, recent_games_q
 
 log = logging.getLogger(__name__)
@@ -136,6 +137,7 @@ def get_overall_stats(player_id):
         - cap_ratio (ctf only)
         - total_carrier_frags (ctf only)
         - game_type_cd
+        - game_type_descr
 
     The key to the dictionary is the game type code. There is also an
     "overall" game_type_cd which sums the totals and computes the total ratios.
@@ -143,13 +145,14 @@ def get_overall_stats(player_id):
     OverallStats = namedtuple('OverallStats', ['total_kills', 'total_deaths',
         'k_d_ratio', 'last_played', 'last_played_epoch', 'last_played_fuzzy',
         'total_playing_time', 'total_playing_time_secs', 'total_pickups', 'total_captures', 'cap_ratio',
-        'total_carrier_frags', 'game_type_cd'])
+        'total_carrier_frags', 'game_type_cd', 'game_type_descr'])
 
-    raw_stats = DBSession.query('game_type_cd', 'total_kills',
-            'total_deaths', 'last_played', 'total_playing_time',
+    raw_stats = DBSession.query('game_type_cd', 'game_type_descr',
+            'total_kills', 'total_deaths', 'last_played', 'total_playing_time',
             'total_pickups', 'total_captures', 'total_carrier_frags').\
             from_statement(
                 "SELECT g.game_type_cd, "
+                       "gt.descr game_type_descr, "
                        "Sum(pgs.kills)         total_kills, "
                        "Sum(pgs.deaths)        total_deaths, "
                        "Max(pgs.create_dt)     last_played, "
@@ -158,12 +161,15 @@ def get_overall_stats(player_id):
                        "Sum(pgs.captures)      total_captures, "
                        "Sum(pgs.carrier_frags) total_carrier_frags "
                 "FROM   games g, "
+                       "cd_game_type gt, "
                        "player_game_stats pgs "
                 "WHERE  g.game_id = pgs.game_id "
+                  "AND  g.game_type_cd = gt.game_type_cd "
                   "AND  pgs.player_id = :player_id "
-                "GROUP  BY g.game_type_cd "
+                "GROUP  BY g.game_type_cd, game_type_descr "
                 "UNION "
-                "SELECT 'overall' game_type_cd, "
+                "SELECT 'overall'              game_type_cd, "
+                       "'Overall'              game_type_descr, "
                        "Sum(pgs.kills)         total_kills, "
                        "Sum(pgs.deaths)        total_deaths, "
                        "Max(pgs.create_dt)     last_played, "
@@ -171,10 +177,8 @@ def get_overall_stats(player_id):
                        "Sum(pgs.pickups)       total_pickups, "
                        "Sum(pgs.captures)      total_captures, "
                        "Sum(pgs.carrier_frags) total_carrier_frags "
-                "FROM   games g, "
-                       "player_game_stats pgs "
-                "WHERE  g.game_id = pgs.game_id "
-                  "AND  pgs.player_id = :player_id "
+                "FROM   player_game_stats pgs "
+                "WHERE  pgs.player_id = :player_id "
             ).params(player_id=player_id).all()
 
     # to be indexed by game_type_cd
@@ -205,7 +209,8 @@ def get_overall_stats(player_id):
                 total_captures=row.total_captures,
                 cap_ratio=cap_ratio,
                 total_carrier_frags=row.total_carrier_frags,
-                game_type_cd=row.game_type_cd)
+                game_type_cd=row.game_type_cd,
+                game_type_descr=row.game_type_descr)
 
         overall_stats[row.game_type_cd] = os
 
@@ -236,7 +241,8 @@ def get_overall_stats(player_id):
                 total_captures          = os.total_captures,
                 cap_ratio               = os.cap_ratio,
                 total_carrier_frags     = os.total_carrier_frags,
-                game_type_cd            = os.game_type_cd)
+                game_type_cd            = os.game_type_cd,
+                game_type_descr         = os.game_type_descr)
 
     return overall_stats
 
@@ -394,7 +400,7 @@ def get_recent_games(player_id):
     Provides a list of recent games for a player. Uses the recent_games_q helper.
     """
     # recent games played in descending order
-    rgs = recent_games_q(player_id=player_id).limit(10).all()
+    rgs = recent_games_q(player_id=player_id, force_player_id=True).limit(10).all()
     recent_games = [RecentGame(row) for row in rgs]
 
     return recent_games
@@ -515,6 +521,7 @@ def player_info_data(request):
         ranks          = get_ranks(player_id)
         recent_games   = get_recent_games(player_id)
         recent_weapons = get_recent_weapons(player_id)
+        cake_day       = is_cake_day(player.create_dt)
 
     except Exception as e:
         player         = None
@@ -525,6 +532,9 @@ def player_info_data(request):
         ranks          = None
         recent_games   = None
         recent_weapons = []
+        cake_day       = False
+        ## do not raise exceptions here (only for debugging)
+        # raise e
 
     return {'player':player,
             'games_played':games_played,
@@ -533,7 +543,8 @@ def player_info_data(request):
             'elos':elos,
             'ranks':ranks,
             'recent_games':recent_games,
-            'recent_weapons':recent_weapons
+            'recent_weapons':recent_weapons,
+            'cake_day':cake_day,
             }
 
 
@@ -627,7 +638,7 @@ def player_game_index_data(request):
         rgs_q = recent_games_q(player_id=player.player_id,
             force_player_id=True, game_type_cd=game_type_cd)
 
-        games = Page(rgs_q, current_page, items_per_page=10, url=page_url)
+        games = Page(rgs_q, current_page, items_per_page=20, url=page_url)
 
         # replace the items in the canned pagination class with more rich ones
         games.items = [RecentGame(row) for row in games.items]
@@ -777,12 +788,15 @@ def player_damage_json(request):
 
 
 def player_hashkey_info_data(request):
-    hashkey = request.matchdict['hashkey']
+    (idfp, status) = verify_request(request)
+
+    # if config is to *not* verify requests and we get nothing back, this
+    # query will return nothing and we'll 404.
     try:
         player = DBSession.query(Player).\
                 filter(Player.player_id == Hashkey.player_id).\
                 filter(Player.active_ind == True).\
-                filter(Hashkey.hashkey == hashkey).one()
+                filter(Hashkey.hashkey == idfp).one()
 
         games_played   = get_games_played(player.player_id)
         overall_stats  = get_overall_stats(player.player_id)
@@ -925,11 +939,6 @@ def player_captimes_data(request):
     if player_id <= 2:
         player_id = -1;
 
-    #player_captimes = DBSession.query(PlayerCaptime).\
-    #        filter(PlayerCaptime.player_id==player_id).\
-    #        order_by(PlayerCaptime.fastest_cap).\
-    #        all()
-
     PlayerCaptimes = namedtuple('PlayerCaptimes', ['fastest_cap', 'create_dt', 'create_dt_epoch', 'create_dt_fuzzy',
         'player_id', 'game_id', 'map_id', 'map_name', 'server_id', 'server_name'])
 
index 5134bd9..7442c80 100644 (file)
@@ -8,11 +8,9 @@ import sqlalchemy.sql.expression as expr
 from pyramid.response import Response\r
 from sqlalchemy import Sequence\r
 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound\r
-from collections import namedtuple\r
-from xonstat.d0_blind_id import d0_blind_id_verify\r
 from xonstat.elo import process_elos\r
 from xonstat.models import *\r
-from xonstat.util import strip_colors, qfont_decode\r
+from xonstat.util import strip_colors, qfont_decode, verify_request\r
 \r
 \r
 log = logging.getLogger(__name__)\r
@@ -28,7 +26,9 @@ def parse_stats_submission(body):
     players = []\r
     teams = []\r
 \r
-    last_key = None\r
+    # we're not in either stanza to start\r
+    in_P = in_Q = False\r
+\r
     for line in body.split('\n'):\r
         try:\r
             (key, value) = line.strip().split(' ', 1)\r
@@ -36,24 +36,36 @@ def parse_stats_submission(body):
             # Server (S) and Nick (n) fields can have international characters.\r
             if key in 'S' 'n':\r
                 value = unicode(value, 'utf-8')\r
-                last_key = key\r
 \r
-            if key not in 'Q' 'P' 'n' 'e' 't' 'i':\r
+            if key not in 'P' 'Q' 'n' 'e' 't' 'i':\r
                 game_meta[key] = value\r
-                last_key = key\r
-\r
-            if key in 'P' 'Q':\r
-                # if we were working on a player or team record already,\r
-                # append it and work on a new one (only set team info)\r
-                if len(events) > 0:\r
-                    if last_key == 'P':\r
-                        players.append(events)\r
-                    elif last_key == 'Q':\r
-                        teams.append(events)\r
+\r
+            if key == 'Q' or key == 'P':\r
+                #log.debug('Found a {0}'.format(key))\r
+                #log.debug('in_Q: {0}'.format(in_Q))\r
+                #log.debug('in_P: {0}'.format(in_P))\r
+                #log.debug('events: {0}'.format(events))\r
+\r
+                # check where we were before and append events accordingly\r
+                if in_Q and len(events) > 0:\r
+                    #log.debug('creating a team (Q) entry')\r
+                    teams.append(events)\r
                     events = {}\r
+                elif in_P and len(events) > 0:\r
+                    #log.debug('creating a player (P) entry')\r
+                    players.append(events)\r
+                    events = {}\r
+\r
+                if key == 'P':\r
+                    #log.debug('key == P')\r
+                    in_P = True\r
+                    in_Q = False\r
+                elif key == 'Q':\r
+                    #log.debug('key == Q')\r
+                    in_P = False\r
+                    in_Q = True\r
 \r
                 events[key] = value\r
-                last_key = key\r
 \r
             if key == 'e':\r
                 (subkey, subvalue) = value.split(' ', 1)\r
@@ -66,12 +78,11 @@ def parse_stats_submission(body):
             # no key/value pair - move on to the next line\r
             pass\r
 \r
-    # add the last player we were working on\r
-    if len(events) > 0:\r
-        if last_key == 'P':\r
-            players.append(events)\r
-        elif last_key == 'Q':\r
-            teams.append(events)\r
+    # add the last entity we were working on\r
+    if in_P and len(events) > 0:\r
+        players.append(events)\r
+    elif in_Q and len(events) > 0:\r
+        teams.append(events)\r
 \r
     return (game_meta, players, teams)\r
 \r
@@ -155,34 +166,6 @@ def is_supported_gametype(gametype, version):
     return is_supported\r
 \r
 \r
-def verify_request(request):\r
-    """Verify requests using the d0_blind_id library"""\r
-\r
-    # first determine if we should be verifying or not\r
-    val_verify_requests = request.registry.settings.get('xonstat.verify_requests', 'true')\r
-    if val_verify_requests == "true":\r
-        flg_verify_requests = True\r
-    else:\r
-        flg_verify_requests = False\r
-\r
-    try:\r
-        (idfp, status) = d0_blind_id_verify(\r
-                sig=request.headers['X-D0-Blind-Id-Detached-Signature'],\r
-                querystring='',\r
-                postdata=request.body)\r
-\r
-        log.debug('\nidfp: {0}\nstatus: {1}'.format(idfp, status))\r
-    except:\r
-        idfp = None\r
-        status = None\r
-\r
-    if flg_verify_requests and not idfp:\r
-        log.debug("ERROR: Unverified request")\r
-        raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")\r
-\r
-    return (idfp, status)\r
-\r
-\r
 def do_precondition_checks(request, game_meta, raw_players):\r
     """Precondition checks for ALL gametypes.\r
        These do not require a database connection."""\r
@@ -213,7 +196,7 @@ def is_real_player(events):
     """\r
     Determines if a given set of events correspond with a non-bot\r
     """\r
-    if not events['P'].startswith('bot#'):\r
+    if not events['P'].startswith('bot'):\r
         return True\r
     else:\r
         return False\r
@@ -609,7 +592,7 @@ def create_default_game_stat(session, game_type_cd):
     return pgstat\r
 \r
 \r
-def create_game_stat(session, game_meta, game, server, gmap, player, teams, events):\r
+def create_game_stat(session, game_meta, game, server, gmap, player, events):\r
     """Game stats handler for all game types"""\r
 \r
     game_type_cd = game.game_type_cd\r
@@ -674,8 +657,6 @@ def create_game_stat(session, game_meta, game, server, gmap, player, teams, even
                 update_fastest_cap(session, player.player_id, game.game_id,\r
                         gmap.map_id, pgstat.fastest)\r
 \r
-    pgstat.teamscore = teams[pgstat.team].score\r
-\r
     # there is no "winning team" field, so we have to derive it\r
     if wins and pgstat.team is not None and game.winner is None:\r
         game.winner = pgstat.team\r
@@ -686,6 +667,53 @@ def create_game_stat(session, game_meta, game, server, gmap, player, teams, even
     return pgstat\r
 \r
 \r
+def create_default_team_stat(session, game_type_cd):\r
+    """Creates a blanked-out teamstat record for the given game type"""\r
+\r
+    # this is what we have to do to get partitioned records in - grab the\r
+    # sequence value first, then insert using the explicit ID (vs autogenerate)\r
+    seq = Sequence('team_game_stats_team_game_stat_id_seq')\r
+    teamstat_id = session.execute(seq)\r
+    teamstat = TeamGameStat(team_game_stat_id=teamstat_id,\r
+            create_dt=datetime.datetime.utcnow())\r
+\r
+    # all team game modes have a score, so we'll zero that out always\r
+    teamstat.score = 0\r
+\r
+    if game_type_cd in 'ca' 'ft' 'lms' 'ka':\r
+        teamstat.rounds = 0\r
+\r
+    if game_type_cd == 'ctf':\r
+        teamstat.caps = 0\r
+\r
+    return teamstat\r
+\r
+\r
+def create_team_stat(session, game, events):\r
+    """Team stats handler for all game types"""\r
+\r
+    try:\r
+        teamstat = create_default_team_stat(session, game.game_type_cd)\r
+        teamstat.game_id = game.game_id\r
+\r
+        # we should have a team ID if we have a 'Q' event\r
+        if re.match(r'^team#\d+$', events.get('Q', '')):\r
+            team = int(events.get('Q').replace('team#', ''))\r
+            teamstat.team = team\r
+\r
+        # gametype-specific stuff is handled here. if passed to us, we store it\r
+        for (key,value) in events.items():\r
+            if key == 'scoreboard-score': teamstat.score = int(round(float(value)))\r
+            if key == 'scoreboard-caps': teamstat.caps = int(value)\r
+            if key == 'scoreboard-rounds': teamstat.rounds = int(value)\r
+\r
+        session.add(teamstat)\r
+    except Exception as e:\r
+        raise e\r
+\r
+    return teamstat\r
+\r
+\r
 def create_weapon_stats(session, game_meta, game, player, pgstat, events):\r
     """Weapon stats handler for all game types"""\r
     pwstats = []\r
@@ -816,16 +844,6 @@ def submit_stats(request):
                 duration     = duration,\r
                 mod          = game_meta.get('O', None))\r
 \r
-        TeamInfo = namedtuple("TeamInfo", ['team','score'])\r
-        teams = {}\r
-        for events in raw_teams:\r
-            team = events['Q']\r
-            if team.startswith("team#"):\r
-                t = int(team[5:])\r
-                for (key,value) in events.items():\r
-                    if key == 'scoreboard-teamscore':\r
-                        teams[t] = TeamInfo(team=t, score=int(value))\r
-\r
         for events in raw_players:\r
             player = get_or_create_player(\r
                 session = session,\r
@@ -833,12 +851,18 @@ def submit_stats(request):
                 nick    = events.get('n', None))\r
 \r
             pgstat = create_game_stat(session, game_meta, game, server,\r
-                    gmap, player, teams, events)\r
+                    gmap, player, events)\r
 \r
             if should_do_weapon_stats(game_type_cd) and player.player_id > 1:\r
                 pwstats = create_weapon_stats(session, game_meta, game, player,\r
                         pgstat, events)\r
 \r
+        for events in raw_teams:\r
+            try:\r
+                teamstat = create_team_stat(session, game, events)\r
+            except Exception as e:\r
+                raise e\r
+\r
         if should_do_elos(game_type_cd):\r
             create_elos(session, game)\r
 \r
@@ -848,4 +872,4 @@ def submit_stats(request):
     except Exception as e:\r
         if session:\r
             session.rollback()\r
-        return e\r
+        raise e\r