]> git.xonotic.org Git - xonotic/xonstat.git/commitdiff
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 d5879af336f4dd89dabacd50f8801350cdc55e84..c00fe2b6773b71e97276a619ce069c61b587e959 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 84716afeb43cda3d97c424ada9447f3998989c15..6ae87896f31991b188f2807fda397866efe017d5 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 fdef49787cd86fbe8c62a989ff1d795eab1552b6..8b1506d095b0e3a9ab5a90acfa04ee0fd8c3a1e9 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 38572a23635c029674d2756496351e4627d337c8..9394f20cc291e9c7eef5b77e8cb91f8ab8c2a65f 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 bbd08bf7485559650228867bb59fd67d0ede5262..449d3b93e23f614881e0b0da838ac68dada2a4e1 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 982512edb83c9653c48a8d7355c594b5ee61691d..f18370f9d79546d9e1212cb840323191f457fff7 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 bda038bc82e9ba8b403e43364b5f6561774ca167..a4071843d73cb5c995fe64d5493df9005d98640c 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 eafe336cd4d9e09b1abcb10dcfed4980f866dc12..ce2b71d4470544a0c974520df10a8b5e80da6f16 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 6938bc2a4b00bbd75a653f19f72da71e7039f256..d0a01e823941db17f2e2a0b553fd3f5118139b15 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 957947fb19c269dee8d1bdeea2893c985a1041a4..dd477d0ba7dd95a0effc56effdef6223905f4b2d 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 0bcd541ce865e109884644326e1fd5859deb6894..b9ab8765282b5385401386b7272deb5443e4a377 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 1814b2f1c06fba94b605de51dfc9f8a1bbb128a1..983f2eebb7888ba7a82f10c06886e1ccb3bfc4f1 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 55ebc1eb80044d3b8cde85b6ee437f77c999c9c0..4fc0ec59a53558cf77a81e28bc43a8c5c17a445e 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 62dac7ec2cce07534209102277edbae8ccd2fc90..7c75016cdfabfa2c110719e267f23026616550a9 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 9e872b5a3776898589c1cef5080b36429e85b0a9..5fd152d5ac543e1ebf91c893da3a64235ea342bf 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 fdb9462037c4a6e47f744ca873db7323e5cb7f20..a9872e4a51873466f7b5fb47433c800d50b1bf37 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 68c1a5ec33acd711fe962fa00b3391a6dff300b0..18eeeabb98a8151edd65f5e307866f722fe35a14 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 bacdd03f63bc62f658fd367cfd7ea7b126e9626e..cea9643603d09d580fd6647d3d0c1bbd7f54e6a1 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 70fd74e12ee4ad316fdd3ca3d2cc30e16a5d7280..677c0580417e0eb5a3d56ca66a991e62ed4ab2a8 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 fbfdd8a0d1add974a69725214b473ace23bb5289..809a1640c975dd8ea1dc66b9a707ec6483371238 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 cfcdfc30ecbdcca3576ad235f68ca1cf9f287f3a..8c94d4336bca7bd2fd0db08030ccd6f87ad9a8a8 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 f105436c165dd868ca84381632c69e49c1daa4f7..7637a3af5f6ddd3b87fae345861ffce5ce16d318 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 d266ba33b864f574d05c9ba956465c1f3ae684be..7c2e210bb28a7e15dbd61a651683b8fc6033213a 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 5c2cf73d4f770d18562ae447a4d0d7e164af9876..f103045dc36154204b1c9fb8001653ff6c447dc5 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 b400d2aeba6d5b4c59df7fa2a19bc3982a1b26f7..d8599303c324f332cf3dbbbe38c568989834c002 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 f238d7adeea9f47c3df364c037122b02f4e5bf00..d88a1390eacc8a2cdefd281a7a986e87dcb93427 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 019844629f8d165078ad443a57587a15ef6abe2e..58d48f89d4a356dafdc853a390373dc0d13a5286 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 a3b3f535136fe4cfd3754314b5d9068b6ec3d98c..8a10a48b733d8602ea3f5fd6fb72203021eaae23 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 cd6f80fb4e849784057f75eb5f0dc1f617e33b22..004f22a37f788078d3471d6bb00e9f4282d4516b 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 197a4d28b8301cd3e6a05fca75a02d01c2d74421..fa8cde44ded4cfe90d161388130546a6ae45de59 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 5134bd9d36ff2c21171d2257d0ae6f853fa1a04a..7442c80f086025c914fe7ae309fcb06b5bc603cd 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