Merge branch 'master' into zykure/approved
authorJan Behrens <zykure@web.de>
Fri, 19 Jul 2013 07:34:24 +0000 (09:34 +0200)
committerJan Behrens <zykure@web.de>
Fri, 19 Jul 2013 07:34:24 +0000 (09:34 +0200)
Conflicts:
xonstat/static/css/app.min.css

16 files changed:
XonStat.egg-info/PKG-INFO
XonStat.egg-info/SOURCES.txt
XonStat.egg-info/requires.txt
setup.py
xonstat/__init__.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/game_info.mako
xonstat/templates/player_info.mako
xonstat/util.py
xonstat/views/game.py
xonstat/views/helpers.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..728426d36ee1aa88b75ec3af864717d5ec8786db 100644 (file)
@@ -84,8 +84,8 @@ def main(global_config, **settings):
     config.add_view(game_info,      route_name="game_info",      renderer="game_info.mako")
     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_route("rank_index_json", "/ranks/{game_type_cd:ctf|dm|tdm|duel|ca|ft}.json")
     config.add_view(rank_index,      route_name="rank_index",      renderer="rank_index.mako")
     config.add_view(rank_index_json, route_name="rank_index_json", renderer="jsonp")
 
index bda038bc82e9ba8b403e43364b5f6561774ca167..c6d57909cd435ab94635a9dcee2b7711f212a46d 100644 (file)
@@ -260,6 +260,36 @@ 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
@@ -284,6 +314,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 +333,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 30797d0c0ea45a3d62e7486be759f7cf128c925a..6a273b50968116c01bb735629a4c94e0003b4c74 100644 (file)
@@ -150,7 +150,6 @@ table td {
 /* Game scoreboard */
 .game {
   float: left;
-  margin-bottom: 30px;
   min-width: 700px;
   padding: 10px 7px;
 }
@@ -318,6 +317,10 @@ table td {
   white-space: nowrap;
 }
 
+.player-nick {
+    width: 25%;
+}
+
 /* elo colors */
 .eloup { color: green; }
 .elodown { color: rgb(190,0,0); }
index c3a1157b34bc82be633a780c8713677fa0c6c815..73067460d42a738154606fb368195376dabd4b4e 100644 (file)
@@ -1 +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}.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}.teamscores tr{background-color:#000}.teamscores td{padding:3px;text-align:center;font-size:12px;font-weight:bold}.teamscores tr.red{background-color:#4d0000}.teamscores tr.blue{background-color:#00004d}.teamscores tr.yellow{background-color:#4d4d00}.teamscores tr.pink{background-color:#4d004d}.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}.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}}
+@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}.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}}
\ No newline at end of file
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
index 2d9bbfb47c6de8d0fc64e4d3844122f0035549cf..e4981426faf492624f7722590840da45b666c65e 100644 (file)
@@ -63,12 +63,13 @@ Game Information
   % endif
 </div>
 
+% for team in stats_by_team.keys():
 <div class="row">
   <div class="span12 game">
-    <h3>Scoreboard</h3>
-    ${scoreboard(game.game_type_cd, pgstats, show_elo, show_latency)}
+  ${scoreboard(game.game_type_cd, stats_by_team[team], show_elo, show_latency)}
   </div>
 </div>
+% endfor
 
 % if len(captimes) > 0:
 <div class="row">
index 3a838f70b480d78889de063408cf76fa8f0bc124..570eb0c791acf7fe2a26f764f0729a1290f51110 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>
 
index b400d2aeba6d5b4c59df7fa2a19bc3982a1b26f7..8af24efc1a16ec7c75d8da08c1476c9185dfba9c 100644 (file)
@@ -203,3 +203,37 @@ 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
index f1ba46132d09f330529edf9aafa8f3f994605c2e..6c43352eaf647bad50c19b8e1675f17e9c14a6ad 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 webhelpers.paginate import Page, PageURL
@@ -105,6 +106,14 @@ def _game_info_data(request):
                 order_by(PlayerGameStat.score).\
                 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)
+
+        log.debug(stats_by_team)
+
         captimes = []
         if game.game_type_cd == 'ctf':
             for pgstat in pgstats:
@@ -154,6 +163,7 @@ def _game_info_data(request):
             'captimes':captimes,
             '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 96d007e19439b5849e51f8ba528c43403fe5acb8..d299d88ce1e87188fe91686086949367089abac8 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
 from xonstat.views.helpers import RecentGame, recent_games_q
 
 log = logging.getLogger(__name__)
@@ -240,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
 
@@ -519,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
@@ -529,8 +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
+        # raise e
 
     return {'player':player,
             'games_played':games_played,
@@ -539,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,
             }
 
 
index fd70934d0d53d8744059b0067b81df7d7b87c764..6c035fef30e2db76a095a066694968c98d02a5df 100644 (file)
@@ -25,6 +25,10 @@ def parse_stats_submission(body):
     game_meta = {}\r
     events = {}\r
     players = []\r
+    teams = []\r
+\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
@@ -34,16 +38,34 @@ def parse_stats_submission(body):
             if key in 'S' 'n':\r
                 value = unicode(value, 'utf-8')\r
 \r
-            if key not in 'P' 'n' 'e' 't' 'i':\r
+            if key not in 'P' 'Q' 'n' 'e' 't' 'i':\r
                 game_meta[key] = value\r
 \r
-            if key == 'P':\r
-                # if we were working on a player record already, append\r
-                # it and work on a new one (only set team info)\r
-                if len(events) > 0:\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
 \r
             if key == 'e':\r
@@ -57,11 +79,13 @@ 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
+    # 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)\r
+    return (game_meta, players, teams)\r
 \r
 \r
 def is_blank_game(gametype, players):\r
@@ -672,6 +696,53 @@ def create_game_stat(session, game_meta, game, server, gmap, player, events):
     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
@@ -756,7 +827,7 @@ def submit_stats(request):
                 "----- END REQUEST BODY -----\n\n")\r
 \r
         (idfp, status) = verify_request(request)\r
-        (game_meta, raw_players) = parse_stats_submission(request.body)\r
+        (game_meta, raw_players, raw_teams) = parse_stats_submission(request.body)\r
         revision = game_meta.get('R', 'unknown')\r
         duration = game_meta.get('D', None)\r
 \r
@@ -815,6 +886,12 @@ def submit_stats(request):
                 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
@@ -824,4 +901,4 @@ def submit_stats(request):
     except Exception as e:\r
         if session:\r
             session.rollback()\r
-        return e\r
+        raise e\r