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 d5879af..c00fe2b 100644 (file)
@@ -1,4 +1,4 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: XonStat
 Version: 0.0
 Summary: XonStat
@@ -6,37 +6,45 @@ Home-page: UNKNOWN
 Author: UNKNOWN
 Author-email: UNKNOWN
 License: UNKNOWN
-Description: This is XonStat, the application in front of xonstatdb. XonStat handles the submission of statistical information from the open source first person shooter Xonotic. 
+Description: This is **XonStat**, the application in front of [xonstatdb][xonstatdb].  
+        [XonStat][xonstat] handles the submission of statistical information from the open source first person shooter [Xonotic][xonotic].
+        
+        ----
         
         To start, first run the following from the root directory to set up dependencies:
         
             python setup.py develop
         
-        Next you'll want to set up xonstatdb. This is maintained as a separate project here:
+        Next you'll want to set up [xonstatdb][xonstatdb]. This is maintained as a separate project here:
         
             https://github.com/antzucaro/xonstatdb
         
-        Next you'll want to open up development.ini and change a few things for added security. Chief among these is the "sqlalchemy.url" setting, which contains your username and password for the database. Change that match the new password you gave xonstat during the installation of xonstatdb. The other setting to change is "security.secret," which is used to keep your web session (cookies and such) secure. 
+        Next you'll want to open up development.ini and change a few things for added security.
+        Chief among these is the "sqlalchemy.url" setting, which contains your username and password for the database.
+        Change that match the new password you gave xonstat during the installation of xonstatdb.
+        The other setting to change is "session.secret," which is used to keep your web session (cookies and such) secure.
         
         To start the server run the following from the root directory. I recommend running this within a GNU screen session:
         
             paster serve development.ini #(or production.ini if you've configured that settings file instead)
         
-        To get a Xonotic server configured to use this server, change the CVAR "g_playerstats_uri" to point to the correct host, port, and URL path. By default this is:
+        To get a Xonotic server configured to use this server, change the CVAR `g_playerstats_uri` to point to the correct host, port, and URL path. By default this is:
         
             http://localhost:6543/stats/submit
         
-        ...so in the command line of the server (or in your config) you can put:
+        ...so in the server console (or in your config) you can put:
         
             set g_playerstats_uri http://localhost:6543/stats/submit
         
-        If you have any questions or issues please open up a bug report here, or - better yet ! - fork it and send me a pull request. 
+        If you have any questions or issues please open up a bug report here, or - better yet ! - fork it and send me a pull request.
         
-        TODO:
+        [xonstatdb]: https://github.com/antzucaro/xonstatdb
+        [xonstat]: http://stats.xonotic.org/
+        [xonotic]: http://www.xonotic.org/
         
-        - "e matches" and "e joins" seem to be mutually exclusive. Add a check for either (instead of just joins" before adding a player_game_stats record).
+        ----
         
-        - map names are being recorded multiple times in the maps table. They should be found when being played subsequent times. 
+        Project is licensed GPLv2+.
         
         
         0.0
index 84716af..6ae8789 100644 (file)
@@ -1,11 +1,11 @@
 CHANGES.txt
 COPYING.txt
 MANIFEST.in
-README.txt
 development.ini
 production.ini
 setup.cfg
 setup.py
+test.txt
 XonStat.egg-info/PKG-INFO
 XonStat.egg-info/SOURCES.txt
 XonStat.egg-info/dependency_links.txt
@@ -15,13 +15,281 @@ XonStat.egg-info/paster_plugins.txt
 XonStat.egg-info/requires.txt
 XonStat.egg-info/top_level.txt
 xonstat/__init__.py
+xonstat/d0_blind_id.py
+xonstat/elo.py
 xonstat/models.py
 xonstat/tests.py
 xonstat/util.py
+xonstat/batch/badges/img/asfalt.png
+xonstat/batch/badges/img/background_archer-v1.png
+xonstat/batch/badges/img/black_linen_v2.png
+xonstat/batch/badges/img/broken_noise.png
+xonstat/batch/badges/img/burried.png
+xonstat/batch/badges/img/dark_leather.png
+xonstat/batch/badges/img/dark_wall.png
+xonstat/batch/badges/img/overlay_classic.png
+xonstat/batch/badges/img/overlay_minimal.png
+xonstat/batch/badges/img/txture.png
+xonstat/batch/badges/output/10180.png
+xonstat/batch/badges/output/10427.png
+xonstat/batch/badges/output/10543.png
+xonstat/batch/badges/output/10551.png
+xonstat/batch/badges/output/1060.png
+xonstat/batch/badges/output/10627.png
+xonstat/batch/badges/output/10710.png
+xonstat/batch/badges/output/10738.png
+xonstat/batch/badges/output/10780.png
+xonstat/batch/badges/output/10814.png
+xonstat/batch/badges/output/10876.png
+xonstat/batch/badges/output/10939.png
+xonstat/batch/badges/output/10978.png
+xonstat/batch/badges/output/1101.png
+xonstat/batch/badges/output/1229.png
+xonstat/batch/badges/output/1543.png
+xonstat/batch/badges/output/1726.png
+xonstat/batch/badges/output/1789.png
+xonstat/batch/badges/output/1820.png
+xonstat/batch/badges/output/2024.png
+xonstat/batch/badges/output/2026.png
+xonstat/batch/badges/output/2148.png
+xonstat/batch/badges/output/2206.png
+xonstat/batch/badges/output/2247.png
+xonstat/batch/badges/output/2265.png
+xonstat/batch/badges/output/2311.png
+xonstat/batch/badges/output/2409.png
+xonstat/batch/badges/output/2485.png
+xonstat/batch/badges/output/251.png
+xonstat/batch/badges/output/26.png
+xonstat/batch/badges/output/2627.png
+xonstat/batch/badges/output/264.png
+xonstat/batch/badges/output/2640.png
+xonstat/batch/badges/output/2755.png
+xonstat/batch/badges/output/2769.png
+xonstat/batch/badges/output/2848.png
+xonstat/batch/badges/output/2961.png
+xonstat/batch/badges/output/3071.png
+xonstat/batch/badges/output/3322.png
+xonstat/batch/badges/output/3367.png
+xonstat/batch/badges/output/3370.png
+xonstat/batch/badges/output/359.png
+xonstat/batch/badges/output/3652.png
+xonstat/batch/badges/output/3666.png
+xonstat/batch/badges/output/3715.png
+xonstat/batch/badges/output/3781.png
+xonstat/batch/badges/output/3823.png
+xonstat/batch/badges/output/3903.png
+xonstat/batch/badges/output/3911.png
+xonstat/batch/badges/output/4057.png
+xonstat/batch/badges/output/4059.png
+xonstat/batch/badges/output/4083.png
+xonstat/batch/badges/output/4295.png
+xonstat/batch/badges/output/4321.png
+xonstat/batch/badges/output/4323.png
+xonstat/batch/badges/output/4349.png
+xonstat/batch/badges/output/4387.png
+xonstat/batch/badges/output/4388.png
+xonstat/batch/badges/output/4466.png
+xonstat/batch/badges/output/4718.png
+xonstat/batch/badges/output/4722.png
+xonstat/batch/badges/output/4875.png
+xonstat/batch/badges/output/4888.png
+xonstat/batch/badges/output/496.png
+xonstat/batch/badges/output/4996.png
+xonstat/batch/badges/output/5028.png
+xonstat/batch/badges/output/5116.png
+xonstat/batch/badges/output/5161.png
+xonstat/batch/badges/output/5411.png
+xonstat/batch/badges/output/5729.png
+xonstat/batch/badges/output/6312.png
+xonstat/batch/badges/output/6755.png
+xonstat/batch/badges/output/6810.png
+xonstat/batch/badges/output/6873.png
+xonstat/batch/badges/output/6967.png
+xonstat/batch/badges/output/7003.png
+xonstat/batch/badges/output/7080.png
+xonstat/batch/badges/output/7247.png
+xonstat/batch/badges/output/7416.png
+xonstat/batch/badges/output/7494.png
+xonstat/batch/badges/output/7519.png
+xonstat/batch/badges/output/7678.png
+xonstat/batch/badges/output/7942.png
+xonstat/batch/badges/output/7981.png
+xonstat/batch/badges/output/8087.png
+xonstat/batch/badges/output/8198.png
+xonstat/batch/badges/output/8519.png
+xonstat/batch/badges/output/8533.png
+xonstat/batch/badges/output/8761.png
+xonstat/batch/badges/output/8985.png
+xonstat/batch/badges/output/9203.png
+xonstat/batch/badges/output/9223.png
+xonstat/batch/badges/output/9230.png
+xonstat/batch/badges/output/9243.png
+xonstat/batch/badges/output/9344.png
+xonstat/batch/badges/output/9394.png
+xonstat/batch/badges/output/9545.png
+xonstat/batch/badges/output/9684.png
+xonstat/batch/badges/output/9803.png
+xonstat/batch/badges/output/9970.png
+xonstat/batch/badges/output/minimal/10180.png
+xonstat/batch/badges/output/minimal/10427.png
+xonstat/batch/badges/output/minimal/10543.png
+xonstat/batch/badges/output/minimal/10551.png
+xonstat/batch/badges/output/minimal/1060.png
+xonstat/batch/badges/output/minimal/10627.png
+xonstat/batch/badges/output/minimal/10710.png
+xonstat/batch/badges/output/minimal/10738.png
+xonstat/batch/badges/output/minimal/10780.png
+xonstat/batch/badges/output/minimal/10814.png
+xonstat/batch/badges/output/minimal/10876.png
+xonstat/batch/badges/output/minimal/10939.png
+xonstat/batch/badges/output/minimal/10978.png
+xonstat/batch/badges/output/minimal/1101.png
+xonstat/batch/badges/output/minimal/1229.png
+xonstat/batch/badges/output/minimal/1543.png
+xonstat/batch/badges/output/minimal/1726.png
+xonstat/batch/badges/output/minimal/1789.png
+xonstat/batch/badges/output/minimal/1820.png
+xonstat/batch/badges/output/minimal/2024.png
+xonstat/batch/badges/output/minimal/2026.png
+xonstat/batch/badges/output/minimal/2148.png
+xonstat/batch/badges/output/minimal/2206.png
+xonstat/batch/badges/output/minimal/2247.png
+xonstat/batch/badges/output/minimal/2265.png
+xonstat/batch/badges/output/minimal/2311.png
+xonstat/batch/badges/output/minimal/2409.png
+xonstat/batch/badges/output/minimal/2485.png
+xonstat/batch/badges/output/minimal/251.png
+xonstat/batch/badges/output/minimal/26.png
+xonstat/batch/badges/output/minimal/2627.png
+xonstat/batch/badges/output/minimal/264.png
+xonstat/batch/badges/output/minimal/2640.png
+xonstat/batch/badges/output/minimal/2755.png
+xonstat/batch/badges/output/minimal/2769.png
+xonstat/batch/badges/output/minimal/2848.png
+xonstat/batch/badges/output/minimal/2961.png
+xonstat/batch/badges/output/minimal/3071.png
+xonstat/batch/badges/output/minimal/3322.png
+xonstat/batch/badges/output/minimal/3367.png
+xonstat/batch/badges/output/minimal/3370.png
+xonstat/batch/badges/output/minimal/359.png
+xonstat/batch/badges/output/minimal/3652.png
+xonstat/batch/badges/output/minimal/3666.png
+xonstat/batch/badges/output/minimal/3715.png
+xonstat/batch/badges/output/minimal/3781.png
+xonstat/batch/badges/output/minimal/3823.png
+xonstat/batch/badges/output/minimal/3903.png
+xonstat/batch/badges/output/minimal/3911.png
+xonstat/batch/badges/output/minimal/4057.png
+xonstat/batch/badges/output/minimal/4059.png
+xonstat/batch/badges/output/minimal/4083.png
+xonstat/batch/badges/output/minimal/4295.png
+xonstat/batch/badges/output/minimal/4321.png
+xonstat/batch/badges/output/minimal/4323.png
+xonstat/batch/badges/output/minimal/4349.png
+xonstat/batch/badges/output/minimal/4387.png
+xonstat/batch/badges/output/minimal/4388.png
+xonstat/batch/badges/output/minimal/4466.png
+xonstat/batch/badges/output/minimal/4718.png
+xonstat/batch/badges/output/minimal/4722.png
+xonstat/batch/badges/output/minimal/4875.png
+xonstat/batch/badges/output/minimal/4888.png
+xonstat/batch/badges/output/minimal/496.png
+xonstat/batch/badges/output/minimal/4996.png
+xonstat/batch/badges/output/minimal/5028.png
+xonstat/batch/badges/output/minimal/5116.png
+xonstat/batch/badges/output/minimal/5161.png
+xonstat/batch/badges/output/minimal/5411.png
+xonstat/batch/badges/output/minimal/5729.png
+xonstat/batch/badges/output/minimal/6312.png
+xonstat/batch/badges/output/minimal/6755.png
+xonstat/batch/badges/output/minimal/6810.png
+xonstat/batch/badges/output/minimal/6873.png
+xonstat/batch/badges/output/minimal/6967.png
+xonstat/batch/badges/output/minimal/7003.png
+xonstat/batch/badges/output/minimal/7080.png
+xonstat/batch/badges/output/minimal/7247.png
+xonstat/batch/badges/output/minimal/7416.png
+xonstat/batch/badges/output/minimal/7494.png
+xonstat/batch/badges/output/minimal/7519.png
+xonstat/batch/badges/output/minimal/7678.png
+xonstat/batch/badges/output/minimal/7942.png
+xonstat/batch/badges/output/minimal/7981.png
+xonstat/batch/badges/output/minimal/8087.png
+xonstat/batch/badges/output/minimal/8198.png
+xonstat/batch/badges/output/minimal/8519.png
+xonstat/batch/badges/output/minimal/8533.png
+xonstat/batch/badges/output/minimal/8761.png
+xonstat/batch/badges/output/minimal/8985.png
+xonstat/batch/badges/output/minimal/9203.png
+xonstat/batch/badges/output/minimal/9223.png
+xonstat/batch/badges/output/minimal/9230.png
+xonstat/batch/badges/output/minimal/9243.png
+xonstat/batch/badges/output/minimal/9344.png
+xonstat/batch/badges/output/minimal/9394.png
+xonstat/batch/badges/output/minimal/9545.png
+xonstat/batch/badges/output/minimal/9684.png
+xonstat/batch/badges/output/minimal/9803.png
+xonstat/batch/badges/output/minimal/9970.png
 xonstat/static/favicon.ico
 xonstat/static/css/colorbox.css
-xonstat/static/css/normalize.css
+xonstat/static/css/reset.css
 xonstat/static/css/style.css
+xonstat/static/css/style.min.css
+xonstat/static/css/tables.css
+xonstat/static/css/img/Xonotic_icon.png
+xonstat/static/css/img/button_sprite.png
+xonstat/static/css/img/glyphicons-halflings-white.png
+xonstat/static/css/img/glyphicons-halflings.png
+xonstat/static/css/img/inputbox_sprite.png
+xonstat/static/css/img/web_background.png
+xonstat/static/css/img/web_background_2.jpg
+xonstat/static/css/img/web_background_3.jpg
+xonstat/static/css/img/web_background_l2.png
+xonstat/static/css/img/web_border.png
+xonstat/static/css/img/web_checkbox_c0.png
+xonstat/static/css/img/web_checkbox_c1.png
+xonstat/static/css/img/web_checkbox_d0.png
+xonstat/static/css/img/web_checkbox_d1.png
+xonstat/static/css/img/web_checkbox_f0.png
+xonstat/static/css/img/web_checkbox_f1.png
+xonstat/static/css/img/web_checkbox_n0.png
+xonstat/static/css/img/web_checkbox_n1.png
+xonstat/static/css/img/web_checkmark.png
+xonstat/static/css/img/web_closebutton_c.png
+xonstat/static/css/img/web_closebutton_f.png
+xonstat/static/css/img/web_closebutton_n.png
+xonstat/static/css/img/web_colorpicker.png
+xonstat/static/css/img/web_colorpicker_selected.png
+xonstat/static/css/img/web_cursor.png
+xonstat/static/css/img/web_cursor_move.png
+xonstat/static/css/img/web_cursor_resize.png
+xonstat/static/css/img/web_cursor_resize2.png
+xonstat/static/css/img/web_icon_aeslevel1.png
+xonstat/static/css/img/web_icon_aeslevel2.png
+xonstat/static/css/img/web_icon_aeslevel3.png
+xonstat/static/css/img/web_icon_aeslevel4.png
+xonstat/static/css/img/web_icon_aeslevel5.png
+xonstat/static/css/img/web_icon_ipv4.png
+xonstat/static/css/img/web_icon_ipv6.png
+xonstat/static/css/img/web_icon_pure1.png
+xonstat/static/css/img/web_radiobutton_c0.png
+xonstat/static/css/img/web_radiobutton_c1.png
+xonstat/static/css/img/web_radiobutton_d0.png
+xonstat/static/css/img/web_radiobutton_d1.png
+xonstat/static/css/img/web_radiobutton_f0.png
+xonstat/static/css/img/web_radiobutton_f1.png
+xonstat/static/css/img/web_radiobutton_n0.png
+xonstat/static/css/img/web_radiobutton_n1.png
+xonstat/static/css/img/web_scrollbar_c.png
+xonstat/static/css/img/web_scrollbar_f.png
+xonstat/static/css/img/web_scrollbar_n.png
+xonstat/static/css/img/web_scrollbar_s.png
+xonstat/static/css/img/web_slider_c.png
+xonstat/static/css/img/web_slider_d.png
+xonstat/static/css/img/web_slider_f.png
+xonstat/static/css/img/web_slider_n.png
+xonstat/static/css/img/web_slider_s.png
+xonstat/static/css/img/web_tooltip.png
 xonstat/static/images/border.png
 xonstat/static/images/controls.png
 xonstat/static/images/crylink.png
@@ -38,15 +306,75 @@ xonstat/static/images/minelayer.png
 xonstat/static/images/minstanex.png
 xonstat/static/images/nex.png
 xonstat/static/images/porto.png
+xonstat/static/images/rifle.png
 xonstat/static/images/rocketlauncher.png
 xonstat/static/images/seeker.png
 xonstat/static/images/shotgun.png
-xonstat/static/images/sniperrifle.png
 xonstat/static/images/tuba.png
 xonstat/static/images/uzi.png
+xonstat/static/images/icons/24x24/arena.png
+xonstat/static/images/icons/24x24/as.png
+xonstat/static/images/icons/24x24/assault.png
+xonstat/static/images/icons/24x24/ca.png
+xonstat/static/images/icons/24x24/ctf.png
+xonstat/static/images/icons/24x24/cts.png
+xonstat/static/images/icons/24x24/dm.png
+xonstat/static/images/icons/24x24/dom.png
+xonstat/static/images/icons/24x24/duel.png
+xonstat/static/images/icons/24x24/freezetag.png
+xonstat/static/images/icons/24x24/ft.png
+xonstat/static/images/icons/24x24/ka.png
+xonstat/static/images/icons/24x24/kh.png
+xonstat/static/images/icons/24x24/lms.png
+xonstat/static/images/icons/24x24/nb.png
+xonstat/static/images/icons/24x24/nexball.png
+xonstat/static/images/icons/24x24/ons.png
+xonstat/static/images/icons/24x24/overall.png
+xonstat/static/images/icons/24x24/rc.png
+xonstat/static/images/icons/24x24/rune.png
+xonstat/static/images/icons/24x24/tdm.png
+xonstat/static/images/icons/48x48/arena.png
+xonstat/static/images/icons/48x48/as.png
+xonstat/static/images/icons/48x48/assault.png
+xonstat/static/images/icons/48x48/ca.png
+xonstat/static/images/icons/48x48/ctf.png
+xonstat/static/images/icons/48x48/cts.png
+xonstat/static/images/icons/48x48/dm.png
+xonstat/static/images/icons/48x48/dom.png
+xonstat/static/images/icons/48x48/duel.png
+xonstat/static/images/icons/48x48/freezetag.png
+xonstat/static/images/icons/48x48/ft.png
+xonstat/static/images/icons/48x48/ka.png
+xonstat/static/images/icons/48x48/kh.png
+xonstat/static/images/icons/48x48/lms.png
+xonstat/static/images/icons/48x48/nb.png
+xonstat/static/images/icons/48x48/nexball.png
+xonstat/static/images/icons/48x48/ons.png
+xonstat/static/images/icons/48x48/overall.png
+xonstat/static/images/icons/48x48/rc.png
+xonstat/static/images/icons/48x48/rune.png
+xonstat/static/images/icons/48x48/tdm.png
+xonstat/static/js/bootstrap-alert.js
+xonstat/static/js/bootstrap-button.js
+xonstat/static/js/bootstrap-carousel.js
+xonstat/static/js/bootstrap-collapse.js
+xonstat/static/js/bootstrap-collapse.min.js
+xonstat/static/js/bootstrap-dropdown.js
+xonstat/static/js/bootstrap-modal.js
+xonstat/static/js/bootstrap-popover.js
+xonstat/static/js/bootstrap-scrollspy.js
+xonstat/static/js/bootstrap-tab.js
+xonstat/static/js/bootstrap-tabs.js
+xonstat/static/js/bootstrap-tooltip.js
+xonstat/static/js/bootstrap-transition.js
+xonstat/static/js/bootstrap-typeahead.js
+xonstat/static/js/default.js
+xonstat/static/js/jquery-1.6.1.min.js
+xonstat/static/js/jquery-1.7.1.min.js
 xonstat/static/js/jquery.colorbox-min.js
 xonstat/static/js/jquery.dataTables.min.js
-xonstat/static/js/jquery.js
+xonstat/static/js/jquery.dataTables.numHtml.js
+xonstat/static/js/jquery.flot.min.js
 xonstat/templates/accuracy.mako
 xonstat/templates/base.mako
 xonstat/templates/game_index.mako
@@ -55,10 +383,14 @@ xonstat/templates/main_index.mako
 xonstat/templates/map_index.mako
 xonstat/templates/map_info.mako
 xonstat/templates/mytemplate.pt
+xonstat/templates/nav.mako
+xonstat/templates/navlinks.mako
 xonstat/templates/player_game_index.mako
 xonstat/templates/player_index.mako
 xonstat/templates/player_info.mako
+xonstat/templates/rank_index.mako
 xonstat/templates/scoreboard.mako
+xonstat/templates/search.mako
 xonstat/templates/server_game_index.mako
 xonstat/templates/server_index.mako
 xonstat/templates/server_info.mako
@@ -67,5 +399,6 @@ xonstat/views/game.py
 xonstat/views/main.py
 xonstat/views/map.py
 xonstat/views/player.py
+xonstat/views/search.py
 xonstat/views/server.py
 xonstat/views/submission.py
\ No newline at end of file
index fdef497..8b1506d 100644 (file)
@@ -1,4 +1,4 @@
-pyramid>=1.1
+pyramid
 SQLAlchemy
 transaction
 repoze.tm2>=1.0b1
@@ -7,4 +7,3 @@ WebError
 sqlahelper
 webhelpers
 psycopg2
-PasteScript
index 38572a2..9394f20 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,6 @@ requires = [
     'repoze.tm2>=1.0b1', # default_commit_veto
     'zope.sqlalchemy',
     'WebError',
-    'pyramid-jinja2',
     'sqlahelper',
     'webhelpers',
     'psycopg2',
index bbd08bf..728426d 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 bda038b..c6d5790 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 30797d0..6a273b5 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 c3a1157..7306746 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 2d9bbfb..e498142 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 3a838f7..570eb0c 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 b400d2a..8af24ef 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 f1ba461..6c43352 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 a3b3f53..8a10a48 100644 (file)
@@ -119,12 +119,12 @@ def recent_games_q(server_id=None, map_id=None, player_id=None,
                 filter(PlayerGameStat.player_id==player_id)
         else:
             recent_games_q = recent_games_q.\
-                filter(PlayerGameStat.rank==1).\
+                filter(PlayerGameStat.scoreboardpos==1).\
                 filter(Game.game_id==pgstat_alias.game_id).\
                 filter(pgstat_alias.player_id==player_id)
     else:
         recent_games_q = recent_games_q.\
-            filter(PlayerGameStat.rank==1)
+            filter(PlayerGameStat.scoreboardpos==1)
 
     if game_type_cd is not None:
         recent_games_q = recent_games_q.\
index 96d007e..d299d88 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 fd70934..6c035fe 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