[
  {
    "path": ".gitignore",
    "content": "\n1.3.8\naudioCache\nreplays\n.idea\n.bsac\n.bsac2\n.bsuuid\n\n*.py.bak\n*.pyc\n\nconfig.json\nconfig.json.prev\n\nnode_modules\n"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>\n"
  },
  {
    "path": "README.md",
    "content": "<h2>\nNote: This repository is not maintained and is not compatible with current BombSquad versions (>v1.5).\nSee https://github.com/bombsquad-community/plugin-manager for a modern alternative.\n</h2>\n\n<h1>BombSquad Community Mod Manager</h1>\n\n<h2>What is it?</h2>\n\nThis is a mod for the game <a href=\"http://www.froemling.net/apps/bombsquad\">BombSquad</a> by <a href=\"http://www.froemling.net/about\">Eric Froemling</a> that aims to improve the management of community created content for BombSquad.\n\nIt's build using the modding api so it can be used on all platforms and should be compatible with all recent versions of BombSquad.\n\nA list of all mods can be viewed [here](http://mrmaxmeier.github.io/BombSquad-Community-Mod-Manager), you can also filter by category ([all minigames](http://mrmaxmeier.github.io/BombSquad-Community-Mod-Manager/#/category/minigames)) or view specific mods ([mod manager](http://mrmaxmeier.github.io/BombSquad-Community-Mod-Manager/#/mod/modManager)).\n\n<h2>Installation</h2>\n\nPut <a href=\"https://raw.githubusercontent.com/Mrmaxmeier/BombSquad-ModManager-and-Mods/master/utils/installer.py\">installer.py</a> in your mods folder. This file will download and install the Mod-Manager and its dependencies.\nYou can find your mods folder in Settings > Advanced > Show Mods Folder.\n\n<h6>Note:</h6>\nOn Android M or higher you'll need to give BombSquad access to the storage. You can do that by clicking on `Settings > Advanced > Show Mods Folder` or enabling it manually in the system settings.\n\n| Platform  | Path       |\n| --------- | ---------- |\n| OS X      | ~/Library/Containers/net.froemling.bombsquad/Data/Library/Application Support/BombSquad/mods |\n| Android   | *<*sdcard*>*/BombSquad/mods  |\n| Windows   | %appdata%/BombSquad/mods |\n| Linux     | ~/.bombsquad/mods            |\n\n<h4>One-Liners</h4>\n<table>\n  <tr>\n    <td>OSX</td>\n    <td>\n      <pre>cd ~/Library/Containers/net.froemling.bombsquad/Data/Library/Application\\ Support/BombSquad/mods && curl -O https://raw.githubusercontent.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/master/utils/installer.py</pre>\n    </td>\n  </tr>\n  <tr>\n    <td>Linux (wget)</td>\n    <td>\n      <pre>wget -P ~/.bombsquad/mods https://raw.githubusercontent.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/master/utils/installer.py</pre>\n    </td>\n  </tr>\n  <tr>\n    <td>Linux (curl)</td>\n    <td>\n      <pre>cd ~/.bombsquad/mods && curl -O https://raw.githubusercontent.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/master/utils/installer.py</pre>  \n    </td>\n  </tr>\n  <tr>\n    <td>Windows (PowerShell)</td>\n    <td>\n      <pre>wget https://raw.githubusercontent.com/Mrmaxmeier/BombSquad-ModManager-and-Mods/master/utils/installer.py -OutFile $env:APPDATA/BombSquad/mods/installer.py</pre>\n    </td>\n</table>\n\n<h2>Usage</h2>\n\nAfter restarting BombSquad there should be a new button in the settings window.\n![Settings Window](screenshots/SettingsWindow.png)\nUpon clicking this button a new window pops up.\n![ModManager Window](screenshots/ModManagerWindow.png)\nYou can download, install or delete mods here.\n\n\n<h4>Tabs</h4>\nThe mods are grouped in three categories:\n\n<table>\n  <tr>\n    <td>Minigames</td>\n    <td>Installing these will add games to the game select screen.</td>\n  </tr>\n  <tr>\n    <td>Utilities</td>\n    <td>These are mods that add UI elements or other non game related things </td>\n  </tr>\n  <tr>\n    <td>Libraries</td>\n    <td>These are mods that can be used as libraries by other mods.</td>\n  </tr>\n</table>\n\nYou can also view all mods using the 'all' tab.\n\n\n<h4>Settings</h4>\nThere is a settings button in the mod manager window.\n\n| Setting | More infos |\n| ---------- | ---------- |\n| Branch     |  A List of all available branches can be found [here](https://api.github.com/repos/Mrmaxmeier/BombSquad-Community-Mod-Manager/branches)   |\n| Auto check for updates | This will check for updates while BombSquad is starting |\n| Auto-update old mods | This will update mods with versions that are known to be old. <br \\>Mods you are developing won't get updated by this. |\n\n\n\n<h2>Contributing</h2>\n\nWant to contribute? Great!\n\n1. Fork it\n2. Create a new file in the mods folder\n3. Add a json file with additional infos (optional)\n5. Open a Pull Request\n6. Profit\n\n<h2>License</h2>\n\n```\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>\n```\n"
  },
  {
    "path": "index.json",
    "content": "{\n    \"mods\": {\n        \"AroundTheWorld\": {\n            \"changelog\": [\n                \"Create AroundTheWorld.pyThis mod allows players to race around the Happy Thoughts map, touching platforms as they go.  First one around the world wins!\"\n            ],\n            \"commit_sha\": \"0673756fe8376878d81f0a6d1e4c163c6750c9cb\",\n            \"filename\": \"AroundTheWorld.py\",\n            \"md5\": \"58bbe46603794fb20e018cf015387674\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/0673756fe8376878d81f0a6d1e4c163c6750c9cb/mods/AroundTheWorld.py\"\n        },\n        \"BackToYou\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\",\n                \"Add files via uploadHey, I have no idea if I did this right.  I made a few mods and got requests to add to your mod manager.  Let me know if I screwed up the process.\"\n            ],\n            \"commit_sha\": \"f0771dc63724a5b92c95a52d6ee74d4cab35eaf0\",\n            \"filename\": \"BackToYou.py\",\n            \"md5\": \"8b672f5a8d2876391bddbecdfed9b970\",\n            \"name\": \"Back To You\",\n            \"old_md5s\": [\n                \"ec65e09ff91f1535252718d3580ac927\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/BackToYou.py\"\n        },\n        \"Basketball\": {\n            \"author\": \"MattZ45986\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Add files via uploadAdd Basketball.py\"\n            ],\n            \"commit_sha\": \"de5422b29a3e8b7c2c5e63539698d3ecc8e8c0c0\",\n            \"filename\": \"Basketball.py\",\n            \"md5\": \"5b77f61eae97b0c379eff4b6ed0b242c\",\n            \"name\": \"Basketball\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/de5422b29a3e8b7c2c5e63539698d3ecc8e8c0c0/mods/Basketball.py\"\n        },\n        \"BuddyBunny\": {\n            \"author\": \"joshville79\",\n            \"category\": \"libraries\",\n            \"changelog\": [\n                \"convert a bunch of files to linux line feeds (#25)\",\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\"\n            ],\n            \"commit_sha\": \"a483c74a4b6a9c540ae58904bce5164dcfd5f4d4\",\n            \"filename\": \"BuddyBunny.py\",\n            \"md5\": \"794110f3e9c9cecb6e3294cf1c829b67\",\n            \"name\": \"Buddy Bunny powerup\",\n            \"old_md5s\": [\n                \"2e1bd9ed5b9dab8c2fb4eb21a88689ec\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/BuddyBunny.py\"\n        },\n        \"Collector\": {\n            \"author\": \"TheMikirog\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Colletor - (fixes continued)- Changed game music\\r- Made capsules fly from enemies much less violently, so they don't fall off cliff that easily\\r- Added some tips at the start of the game\",\n                \"Collector - fixesThis gamemode had some serious flaws that made it unplayable or unfair.\\r- Now deposit points in Team games are always in the center. Deposit points only change in FFA games.\\r- Now capsules don't roll around as much, so there's less risk they would fall down the pit.\"\n            ],\n            \"commit_sha\": \"0ebd80ae04e67629b2cc1be3e76fa96b68e2a42f\",\n            \"filename\": \"Collector.py\",\n            \"md5\": \"da460fcf6c5cc622709141a3aa133e48\",\n            \"name\": \"Collector\",\n            \"old_md5s\": [\n                \"9f38dfeff4590bd1073d2397867d2fc9\",\n                \"6fd8ec35e9ced9d961e2efbd7f96b990\",\n                \"571ec87186cd1b74a0ba196b2a263648\",\n                \"9db6ae29f084aaa7afdd81953b6f5937\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/0ebd80ae04e67629b2cc1be3e76fa96b68e2a42f/mods/Collector.py\"\n        },\n        \"FillErUp\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\"\n            ],\n            \"commit_sha\": \"f0771dc63724a5b92c95a52d6ee74d4cab35eaf0\",\n            \"filename\": \"FillErUp.py\",\n            \"md5\": \"e0f7e8c5b259c090269d269eca139106\",\n            \"name\": \"Fill 'Er Up\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/FillErUp.py\"\n        },\n        \"FlagDay\": {\n            \"changelog\": [\n                \"Create FlagDay.pyThis game lets players take turns choosing their fate, picking flags and facing challenges to earn points.  But beware...\"\n            ],\n            \"commit_sha\": \"55a13e781e5aca80752f59c5bb7f5ccb2a7b4042\",\n            \"filename\": \"FlagDay.py\",\n            \"md5\": \"0a15acdc7ff94ac6e3dff79b088338e0\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/55a13e781e5aca80752f59c5bb7f5ccb2a7b4042/mods/FlagDay.py\"\n        },\n        \"GravityFalls\": {\n            \"changelog\": [\n                \"Create GravityFalls.pyStay on the ground for as long as you can.  Don't fly away in this crazy mod.\"\n            ],\n            \"commit_sha\": \"729dc2dc599b17a889d1ebfcfef02bc52a0b6cef\",\n            \"filename\": \"GravityFalls.py\",\n            \"md5\": \"a7d7f9c4e898e18a758979bd7430b2b7\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/729dc2dc599b17a889d1ebfcfef02bc52a0b6cef/mods/GravityFalls.py\"\n        },\n        \"Greed\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\",\n                \"move new mods to mods folder\"\n            ],\n            \"commit_sha\": \"f0771dc63724a5b92c95a52d6ee74d4cab35eaf0\",\n            \"filename\": \"Greed.py\",\n            \"md5\": \"c997ab2e55fc0f52a015a0109e0dd6c2\",\n            \"name\": \"Greed\",\n            \"old_md5s\": [\n                \"716fc4ab7c3c5f2562540dd2aa4b9531\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/Greed.py\"\n        },\n        \"GuessTheBomb\": {\n            \"author\": \"Paolo Valerdi\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"convert a bunch of files to linux line feeds (#25)\",\n                \"New minigameThis is just another meteor shower game but the bombs fall randomly\"\n            ],\n            \"commit_sha\": \"a483c74a4b6a9c540ae58904bce5164dcfd5f4d4\",\n            \"filename\": \"GuessTheBomb.py\",\n            \"md5\": \"0c56aef73be562024b896e3ceef05cce\",\n            \"name\": \"Guess The Bomb\",\n            \"old_md5s\": [\n                \"069221cf6a656d3efe1ea69dabc12e8f\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/GuessTheBomb.py\"\n        },\n        \"HazardousCargo\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"convert a bunch of files to linux line feeds (#25)\",\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\"\n            ],\n            \"commit_sha\": \"a483c74a4b6a9c540ae58904bce5164dcfd5f4d4\",\n            \"filename\": \"HazardousCargo.py\",\n            \"md5\": \"1cb82c35c92a821a19d635854a1b7183\",\n            \"name\": \"Hazardous Cargo\",\n            \"old_md5s\": [\n                \"266a2b0a26d9a8c19fb8ab1afcaead83\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/HazardousCargo.py\"\n        },\n        \"Infection\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"convert a bunch of files to linux line feeds (#25)\",\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\"\n            ],\n            \"commit_sha\": \"a483c74a4b6a9c540ae58904bce5164dcfd5f4d4\",\n            \"filename\": \"Infection.py\",\n            \"md5\": \"b15d9384ea106b69b93c403d7a185f8f\",\n            \"name\": \"Infection\",\n            \"old_md5s\": [\n                \"a72d024cee252ae53039f2ad0936b198\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/Infection.py\"\n        },\n        \"JumpingContest\": {\n            \"changelog\": [\n                \"Create JumpingContest.pyThis mod tests an underappreciated aspect of the game: jumping.\"\n            ],\n            \"commit_sha\": \"163ead5ffd2592f11c6ae2600f3f138baabfb018\",\n            \"filename\": \"JumpingContest.py\",\n            \"md5\": \"9f78c4bec42990c2504f2014914e7c24\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/163ead5ffd2592f11c6ae2600f3f138baabfb018/mods/JumpingContest.py\"\n        },\n        \"LandGrab\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"rename \\\"Land Grab.py\\\" to \\\"LandGrab.py\\\" (#25)\"\n            ],\n            \"commit_sha\": \"8f28b96d4ae9df51bbc8718a932f5d17938c5ce4\",\n            \"filename\": \"LandGrab.py\",\n            \"md5\": \"95526447f0031c1fcb1e2488a50e0db9\",\n            \"name\": \"Land Grab\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8f28b96d4ae9df51bbc8718a932f5d17938c5ce4/mods/LandGrab.py\"\n        },\n        \"Paint\": {\n            \"changelog\": [\n                \"Create Paint.pyPaint is a Co-op game where artists can create true masterpieces ... Bombsquad style.\"\n            ],\n            \"commit_sha\": \"4ea62c8752eca7006fd7f0ecfed50b600b10b84a\",\n            \"filename\": \"Paint.py\",\n            \"md5\": \"12bc6efea06a65e10d571595e4824d8e\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/4ea62c8752eca7006fd7f0ecfed50b600b10b84a/mods/Paint.py\"\n        },\n        \"Protection\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\",\n                \"move new mods to mods folder\"\n            ],\n            \"commit_sha\": \"f0771dc63724a5b92c95a52d6ee74d4cab35eaf0\",\n            \"filename\": \"Protection.py\",\n            \"md5\": \"82d3509f2d8a99381a6b82db8a7ef716\",\n            \"name\": \"Protection\",\n            \"old_md5s\": [\n                \"a8fee2500811ab273f0db546d7dceba8\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/Protection.py\"\n        },\n        \"SharksAndMinnows\": {\n            \"changelog\": [\n                \"Create SharksAndMinnows.pyThis classic game allows teams to play a sharks-and-minnows spin-off, compatible with Bombsquad rules and regulations.\"\n            ],\n            \"commit_sha\": \"a3fe6d87b1e2cc1ed77cb44a0213b45eef4decb0\",\n            \"filename\": \"SharksAndMinnows.py\",\n            \"md5\": \"57ce4125981997c9367d0da439c56f55\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a3fe6d87b1e2cc1ed77cb44a0213b45eef4decb0/mods/SharksAndMinnows.py\"\n        },\n        \"Siege\": {\n            \"changelog\": [\n                \"Create Siege.pyThe object of the game is to break through the castle walls by whatever means possible to get at the flag inside.  First one to grab it wins!\"\n            ],\n            \"commit_sha\": \"e1439dc9eca46d99470c09b59fdc8028c6f3c550\",\n            \"filename\": \"Siege.py\",\n            \"md5\": \"7e4b7063579f43402df08a2d6afebf8a\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/e1439dc9eca46d99470c09b59fdc8028c6f3c550/mods/Siege.py\"\n        },\n        \"SimonSays\": {\n            \"changelog\": [\n                \"Create SimonSays.pyThis is a classic game of Simon Says ... Bombsquad style.  Follow the commands, but only when Simon says to.\"\n            ],\n            \"commit_sha\": \"025f68c78c76ec390cae327a1679a7da0d9e9b2b\",\n            \"filename\": \"SimonSays.py\",\n            \"md5\": \"3872e52eab13983507591b48ecf0a2b3\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/025f68c78c76ec390cae327a1679a7da0d9e9b2b/mods/SimonSays.py\"\n        },\n        \"SnoBallz\": {\n            \"author\": \"joshville79\",\n            \"category\": \"libraries\",\n            \"changelog\": [\n                \"convert a bunch of files to linux line feeds (#25)\",\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\"\n            ],\n            \"commit_sha\": \"a483c74a4b6a9c540ae58904bce5164dcfd5f4d4\",\n            \"filename\": \"SnoBallz.py\",\n            \"md5\": \"ad1b04613718179b8e901b7ceafd8384\",\n            \"name\": \"SnoBallz powerup\",\n            \"old_md5s\": [\n                \"a05749ae364883a4f23d05d6788c9edb\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/SnoBallz.py\"\n        },\n        \"SnowBallFight\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\"\n            ],\n            \"commit_sha\": \"f0771dc63724a5b92c95a52d6ee74d4cab35eaf0\",\n            \"filename\": \"SnowBallFight.py\",\n            \"md5\": \"aaaaa9c177f0726d761eb18782dbee9f\",\n            \"name\": \"Snowball Fight (requires SnoBallz.py)\",\n            \"old_md5s\": [],\n            \"requires\": [\n                \"SnoBallz\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/SnowBallFight.py\"\n        },\n        \"WizardWar\": {\n            \"author\": \"MattZ45986\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Update game for advent of WizardNow that the game has a \\\"Grumbledorf\\\" character, I've put together this little mod to show off the new guy.\",\n                \"Rename Wizard War.py to WizardWar.py\"\n            ],\n            \"commit_sha\": \"33e6d75ee504241c4206c6bd3e986e52ffa1b0d7\",\n            \"filename\": \"WizardWar.py\",\n            \"md5\": \"dd76a8d19ca21e33332153a0ce454ee1\",\n            \"name\": \"WizardWar\",\n            \"old_md5s\": [\n                \"46315134786af3bf46396918d663137d\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/33e6d75ee504241c4206c6bd3e986e52ffa1b0d7/mods/WizardWar.py\"\n        },\n        \"ZombieHorde\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Here's a bunch of new and updated minigames!Important notes:bsPowerup.py is a modified version of the game file. It has to replace the original game file.  Can mod manager do this? The modification is that it adds 2 new powerups: Snowballs and a Bunny helper bot that attacks other players (not you). For this to work it also requires BunnyBuddy.py and SnoBallz.py.  I put that requirement (I think) in the bsPowerup.json file. You can see these in action on my game server. I couldn't figure out any other way to add the new powerups to every game other than modifying the original bsPowerup file.The Snowball Fight minigame also requires SnoBallz.py.  I added a \\\"requires\\\" line in the .json file. It's the only minigame here that's not completely standalone.I called BuddyBunny, SnoBallz, and bsPowerup all \\\"utilities\\\" instead of \\\"minigames\\\" or \\\"libraries\\\" in the json files.  I wasn't sure what would be best.  If there's any way I can help, please let me know.\",\n                \"Add files via uploadHey, I have no idea if I did this right.  I made a few mods and got requests to add to your mod manager.  Let me know if I screwed up the process.\"\n            ],\n            \"commit_sha\": \"f0771dc63724a5b92c95a52d6ee74d4cab35eaf0\",\n            \"filename\": \"ZombieHorde.py\",\n            \"md5\": \"2117baf2a313305199b39ce75970aba4\",\n            \"name\": \"Zombie Horde\",\n            \"old_md5s\": [\n                \"716d5b347b42f18156e7cc85a4a175ee\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/ZombieHorde.py\"\n        },\n        \"airStrike\": {\n            \"author\": \"SoKpl\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"add Air Strike, see #4\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"airStrike.py\",\n            \"md5\": \"6cfa8b25079832edec4862958fc223f2\",\n            \"name\": \"Air Strike\",\n            \"old_md5s\": [\n                \"23a8cefb018aefae51442b0554fa6e64\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/airStrike.py\"\n        },\n        \"arms_race\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"prevent arms race upgrade on team kill (fixes #22)\",\n                \"bump all mod api versions\"\n            ],\n            \"commit_sha\": \"4044dd3363efc75421db36cef3f770d499811d47\",\n            \"filename\": \"arms_race.py\",\n            \"md5\": \"6ef1cf7e90801851caf145ec1cb24f06\",\n            \"name\": \"Arms Race\",\n            \"old_md5s\": [\n                \"812023fcef83769a5a937cb8e8b90d44\",\n                \"e23a86e0b95291192c1d3463df3b0605\",\n                \"6ff1810f9d73a2b698d19216b36a4413\",\n                \"b725e48f91fa04197aea4cc1eefc1cbf\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/4044dd3363efc75421db36cef3f770d499811d47/mods/arms_race.py\"\n        },\n        \"auto_reloader\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"utilities\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"rating ui\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"auto_reloader.py\",\n            \"md5\": \"c80cb4f40bf8f9bd9b7a4f75b0eb0a9d\",\n            \"name\": \"Auto Reloader\",\n            \"old_md5s\": [\n                \"d7924cf4c9e684120f376d0710114391\",\n                \"48611852f3386839093cd9115c2d6152\",\n                \"115932c45f0443290c3798e8762b6e9e\",\n                \"969de56c489c774d116eed0a40632b11\",\n                \"41588189e303988a9151523e71e689b0\"\n            ],\n            \"supports\": [\n                \"config_editor\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/auto_reloader.py\"\n        },\n        \"bomb_on_my_head\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"refactoring\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"bomb_on_my_head.py\",\n            \"md5\": \"0a2d23af4260764799268cf5bc3bdb43\",\n            \"name\": \"Bomb on my Head\",\n            \"old_md5s\": [\n                \"c16286fe16dd5f2594e0836953fdfd68\",\n                \"6963c68439371d19fb52458e064dc930\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/bomb_on_my_head.py\"\n        },\n        \"bomberman\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"refactoring\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"bomberman.py\",\n            \"md5\": \"b683a0481e1ef2f06525d6e3ff754fce\",\n            \"name\": \"Bomberman\",\n            \"old_md5s\": [\n                \"5019f3cb21e319b175bbeba4378e0926\",\n                \"1eafb570a01dcb157f737ff391424cdf\"\n            ],\n            \"tag\": \"experimental\",\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/bomberman.py\"\n        },\n        \"boxing\": {\n            \"author\": \"TheMikirog\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"Merge pull request #6 from TheMikirog/masterBoxing: Gameplay changes\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"boxing.py\",\n            \"md5\": \"119d3ea4808d79d6c92942821565aec5\",\n            \"name\": \"Boxing\",\n            \"old_md5s\": [\n                \"f60cfb347f93e1fa25f56cad057ab0d5\",\n                \"9485eac414e791f8eb06b7e93e0897e4\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/boxing.py\"\n        },\n        \"brainFreeze\": {\n            \"author\": \"TheMikirog\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"Rename brainFreeze to brainFreeze.py\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"brainFreeze.py\",\n            \"md5\": \"bc7b703a365c67c5b37d6c6cb2c94f02\",\n            \"name\": \"Brain Freeze\",\n            \"old_md5s\": [\n                \"3a9b5bbdb96deacdb4504a752b3618ee\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/brainFreeze.py\"\n        },\n        \"bsBoxingOfTheHill\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Add files via uploadHey, I have no idea if I did this right.  I made a few mods and got requests to add to your mod manager.  Let me know if I screwed up the process.\"\n            ],\n            \"commit_sha\": \"09be1d940c0db5f01587911930cc212982a7b020\",\n            \"filename\": \"bsBoxingOfTheHill.py\",\n            \"md5\": \"dd81f8f2ccb148a6e88dd96d9e91f248\",\n            \"name\": \"Boxing Of The Hill\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/09be1d940c0db5f01587911930cc212982a7b020/mods/bsBoxingOfTheHill.py\"\n        },\n        \"bsKillZone\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"convert a bunch of files to linux line feeds (#25)\",\n                \"Add files via uploadHey, I have no idea if I did this right.  I made a few mods and got requests to add to your mod manager.  Let me know if I screwed up the process.\"\n            ],\n            \"commit_sha\": \"a483c74a4b6a9c540ae58904bce5164dcfd5f4d4\",\n            \"filename\": \"bsKillZone.py\",\n            \"md5\": \"15040077256ea55a394cb28badd7ee06\",\n            \"name\": \"Kill Zone\",\n            \"old_md5s\": [\n                \"1b5c3d11a408f1cb78a09f0758b6e980\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/bsKillZone.py\"\n        },\n        \"catch_to_live\": {\n            \"author\": \"Deva\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"Add more settings and change color to normal\",\n                \"rename game\"\n            ],\n            \"commit_sha\": \"352e1a82a46c431459842c2a302a31385aa1e872\",\n            \"filename\": \"catch_to_live.py\",\n            \"md5\": \"23530bf4ea77e546dd99212203864409\",\n            \"name\": \"CatchToLive\",\n            \"old_md5s\": [\n                \"d279afd2952852bafb99d04a63d92e60\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/352e1a82a46c431459842c2a302a31385aa1e872/mods/catch_to_live.py\"\n        },\n        \"fightOfFaith\": {\n            \"author\": \"SoKpl\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"added 'Fight of Faith', see #1\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"fightOfFaith.py\",\n            \"md5\": \"8acf4285dee7fb154290b2709006521d\",\n            \"name\": \"Fight of Faith\",\n            \"old_md5s\": [\n                \"c7f6864575253c2d4f594cb08daf2769\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/fightOfFaith.py\"\n        },\n        \"frozenone\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"refactoring\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"frozenone.py\",\n            \"md5\": \"e37c20cad5f7e6e54c9961e15d30ce70\",\n            \"name\": \"The Frozen One\",\n            \"old_md5s\": [\n                \"c99d65d6b3051cb60d48c565c6b2b66e\",\n                \"483fe2ea852fe51525748f472d7d787b\",\n                \"0de2033b65a83260413a5a49607bb4c5\",\n                \"0a6a3366c7103712a7119e5efc4d4a15\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/frozenone.py\"\n        },\n        \"iceDeathmatch\": {\n            \"author\": \"SoKpl\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"add \\\"Ice Deathmatch\\\" by SoKpl#7\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"iceDeathmatch.py\",\n            \"md5\": \"052236109d110187da641b2dd5964ae1\",\n            \"name\": \"Ice Deathmatch\",\n            \"old_md5s\": [\n                \"9796fed0dd67c68a9e7eef95d4a51da1\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/iceDeathmatch.py\"\n        },\n        \"magic_box\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"cleanup magic_box\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"magic_box.py\",\n            \"md5\": \"15f629062771aa87e538dc697c5eb525\",\n            \"name\": \"Magic Box\",\n            \"old_md5s\": [\n                \"7fae4c59cb8bfe02936722c1ac17a4bc\",\n                \"244b4fcbdc2c74904254d47272bc4b9e\",\n                \"3aac96b22db7dbc1e6cf2306d3454228\",\n                \"2340f5eee0252ac43a2e2fb96e73700e\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/magic_box.py\"\n        },\n        \"modManager\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"utilities\",\n            \"changelog\": [\n                \"only generate uuid if the stat server is enabled\",\n                \"end smash game if team leaves completly\"\n            ],\n            \"commit_sha\": \"3672c8fdd3a6a26aa7321e264f33a9e9a83232e8\",\n            \"filename\": \"modManager.py\",\n            \"md5\": \"536b069e5b60bbeee052b03464991ce9\",\n            \"name\": \"Mod Manager (this thingy)\",\n            \"old_md5s\": [\n                \"79fad6241b448f45be7a97b7c5aa3dd9\",\n                \"95137096a9c415267e4bfb4ee75c9b73\",\n                \"5c0fac4ce24659ae410a56faced81d25\",\n                \"72efc28be645667697116e6191ababfe\",\n                \"d0619dbfde6337d656482312f38d4574\"\n            ],\n            \"requires\": [\n                \"ui_wrappers\",\n                \"settings_patcher\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/3672c8fdd3a6a26aa7321e264f33a9e9a83232e8/mods/modManager.py\"\n        },\n        \"puckDeathmatch\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"puck timeout\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"puckDeathmatch.py\",\n            \"md5\": \"4ee530ed550d4adcd1f5b2600688c066\",\n            \"name\": \"Puck Deathmatch\",\n            \"old_md5s\": [\n                \"8c4f61495cd1cb5221be02cfa8a58e45\",\n                \"034af0481765c2502dc184e5657dc435\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/puckDeathmatch.py\"\n        },\n        \"quickGameButton\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"utilities\",\n            \"changelog\": [\n                \"fix quick-game-button\",\n                \"update quickGameButton to API v4\"\n            ],\n            \"commit_sha\": \"1d1bb169d32004924d6457a5f8ae4109cfece58c\",\n            \"filename\": \"quickGameButton.py\",\n            \"md5\": \"da1b86df0af645733d050ee5a5831747\",\n            \"name\": \"Quick-Game Button\",\n            \"old_md5s\": [\n                \"090df7b52669e73b3a898788d73a6bf8\",\n                \"210c7ea410f847b8d4146801f7108471\",\n                \"5b22370f25e872746283d45790af7e67\",\n                \"9dbbc871cc8457bbced9420c11681c72\",\n                \"a9d97c3630a84baafc34e68968d86eb7\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/1d1bb169d32004924d6457a5f8ae4109cfece58c/mods/quickGameButton.py\"\n        },\n        \"settings_patcher\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"libraries\",\n            \"changelog\": [\n                \"replace bs.getResource calls\",\n                \"even more flake8\"\n            ],\n            \"commit_sha\": \"724587f96a6011b863903b389ef7babce70976a7\",\n            \"filename\": \"settings_patcher.py\",\n            \"md5\": \"2a39de960bfcc989876e6f843df456a2\",\n            \"name\": \"settings_patcher\",\n            \"old_md5s\": [\n                \"b47906864b52626ccde30eba89491674\",\n                \"fcfa6832da8c59e08afd1eef0d8139b5\",\n                \"85217ec90f53b02dc3646030796f3dd5\",\n                \"474dbf37a8b1f36e532e0e936f9c0f4b\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/724587f96a6011b863903b389ef7babce70976a7/mods/settings_patcher.py\"\n        },\n        \"smash\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"end smash game if team leaves completly\",\n                \"bump all mod api versions\"\n            ],\n            \"commit_sha\": \"86072bfdadbd9205f897396825a8104fe2cbb630\",\n            \"filename\": \"smash.py\",\n            \"md5\": \"1210d0a95ee6fd3c9e05066dbc8a7c0b\",\n            \"name\": \"Super Smash\",\n            \"old_md5s\": [\n                \"44452c0419bb398be64dbac6379ba560\",\n                \"339ab2df890ae78b57507224bab11985\",\n                \"5916f2a3a15b623802e454dba70fb7f2\",\n                \"5dd773cb8407f92618b29da5134cd6d2\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/86072bfdadbd9205f897396825a8104fe2cbb630/mods/smash.py\"\n        },\n        \"snake\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"bump all mod api versions\",\n                \"refactoring\"\n            ],\n            \"commit_sha\": \"8d599cb0829b4f28d03db30e61ac618e8f1e0779\",\n            \"filename\": \"snake.py\",\n            \"md5\": \"49d878c10214d4ccec5df70b17e3793a\",\n            \"name\": \"Snake\",\n            \"old_md5s\": [\n                \"b7ed1f0b4f5ad311b2f35406ff07588f\",\n                \"76032fe956ce6b9805bcb2453a1decf5\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/snake.py\"\n        },\n        \"snowyPowerup\": {\n            \"author\": \"joshville79\",\n            \"category\": \"libraries\",\n            \"changelog\": [\n                \"fix for bsFootball's powerup handling\",\n                \"Renabe bsPowerup to snowyPowerup, utilize monkey patching (#25)\"\n            ],\n            \"commit_sha\": \"017a13dbe85c78220175fb795bd81e1b5e66c0e9\",\n            \"filename\": \"snowyPowerup.py\",\n            \"md5\": \"ce805e9d11cef2002d24b9dab6654595\",\n            \"name\": \"Modified bsPowerup.py\",\n            \"old_md5s\": [\n                \"9a5abe159a10e15f5219dce1ac7e6176\"\n            ],\n            \"requires\": [\n                \"BuddyBunny\",\n                \"SnoBallz\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/017a13dbe85c78220175fb795bd81e1b5e66c0e9/mods/snowyPowerup.py\"\n        },\n        \"surviveCurse\": {\n            \"author\": \"joshville79\",\n            \"category\": \"minigames\",\n            \"changelog\": [\n                \"move new mods to mods folder\"\n            ],\n            \"commit_sha\": \"22846a813dc5a550a49a74307d86c3601d58cf4e\",\n            \"filename\": \"surviveCurse.py\",\n            \"md5\": \"29e21b16f3d7e4003532f0155a175ac3\",\n            \"name\": \"Survive the Curse!\",\n            \"old_md5s\": [],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/22846a813dc5a550a49a74307d86c3601d58cf4e/mods/surviveCurse.py\"\n        },\n        \"ui_wrappers\": {\n            \"author\": \"Mrmaxmeier\",\n            \"category\": \"libraries\",\n            \"changelog\": [\n                \"add note about textWidget.set(text=...)\",\n                \"more flake8\"\n            ],\n            \"commit_sha\": \"f2c1140ba899c0a5dbbe683fd767343449258942\",\n            \"filename\": \"ui_wrappers.py\",\n            \"md5\": \"421c7d87f4d832e2d2e42c462fc3d63a\",\n            \"name\": \"ui_wrappers\",\n            \"old_md5s\": [\n                \"9f813943333a21f139b45487c0579224\",\n                \"069d4605c92528211e0a8c8bb67a3c22\",\n                \"cbfce8f368e32a21159b1ed94f2d55b1\",\n                \"7df3a7c699903f8a0c8b8d6c847b0840\",\n                \"8d1e6e55ae9a953de95278d5317c3476\"\n            ],\n            \"url\": \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f2c1140ba899c0a5dbbe683fd767343449258942/mods/ui_wrappers.py\"\n        }\n    },\n    \"version\": 1.1\n}"
  },
  {
    "path": "mods/AroundTheWorld.py",
    "content": "import bs\nimport random\nimport bsUtils\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [AroundTheWorld]\n\nclass AroundTheWorld(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Around The World'\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Race around the world.'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Time',\n                'lowerIsBetter':True,\n                'scoreType':'milliseconds'}\n    \n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Happy Thoughts']\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Laps\",{'minValue':1,\"default\":3,\"increment\":1}),\n                    (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                              ('2 Minutes',120),('5 Minutes',300),\n                                              ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                    (\"Epic Mode\",{'default':False})]\n        \n        if issubclass(sessionType,bs.TeamsSession):\n            settings.append((\"Entire Team Must Finish\",{'default':False}))\n        return settings\n        \n    \n    def __init__(self,settings):\n        self._raceStarted = False\n        bs.TeamGameActivity.__init__(self,settings)\n        for player in self.players:\n            player.gameData['lastPoint'] = 0\n        self._scoreBoard = bs.ScoreBoard()\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n        self._scoreSound = bs.getSound(\"score\")\n        self._swipSound = bs.getSound(\"swip\")\n        self._lastTeamTime = None\n        self._frontRaceRegion = None\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n        \n\n    def getInstanceDescription(self):\n        if isinstance(self.getSession(),bs.TeamsSession) and self.settings.get('Entire Team Must Finish', False):\n            tStr = ' Your entire team has to finish.'\n        else: tStr = ''\n\n        if self.settings['Laps'] > 1: s = ('${ARG1} laps.'+tStr,self.settings['Laps'])\n        else: s = 'Fly 1 lap.'+tStr\n        return s\n\n    def getInstanceScoreBoardDescription(self):\n        if self.settings['Laps'] > 1: s = ('fly ${ARG1} laps',self.settings['Laps'])\n        else: s = 'fly 1 lap'\n        return s\n\n    def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None):\n        posList = ((0,5,0),(9,11,0),(0,12,0),(-11,11,0))\n        try: pos = posList[player.gameData['lastPoint']]\n        except: pos = (0,5,0)\n        position = (pos[0]+random.random()*2 -1 ,pos[1],pos[2])\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n\n        lightColor = bsUtils.getNormalizedColor(color)\n        displayColor = bs.getSafeColor(color,targetIntensity=0.75)\n        spaz = bs.PlayerSpaz(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n        if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        if self._raceStarted: spaz.connectControlsToPlayer()\n        spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound,1,position=spaz.node.position)\n        light = bs.newNode('light',attrs={'color':lightColor})\n        spaz.node.connectAttr('position',light,'position')\n        bsUtils.animate(light,'intensity',{0:0,250:1,500:0})\n        bs.gameTimer(500,light.delete)\n        if not self._raceStarted: player.gameData['lastPoint'] = 0\n        bs.gameTimer(250,bs.Call(self.checkPt,player))\n        return spaz\n        \n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic Race' if self.settings['Epic Mode'] else 'Race')\n\n        self._nubTex = bs.getTexture('nub')\n        self._beep1Sound = bs.getSound('raceBeep1')\n        self._beep2Sound = bs.getSound('raceBeep2')\n\n    def _flashPlayer(self,player,scale):\n        pos = player.actor.node.position\n        light = bs.newNode('light',\n                           attrs={'position':pos,\n                                  'color':(1,1,0),\n                                  'heightAttenuated':False,\n                                  'radius':0.4})\n        bs.gameTimer(500,light.delete)\n        bs.animate(light,'intensity',{0:0,100:1.0*scale,500:0})\n        \n                        \n    def onTeamJoin(self,team):\n        team.gameData['time'] = None\n        team.gameData['lap'] = 0\n        team.gameData['finished'] = False\n        self._updateScoreBoard()\n\n    def onPlayerJoin(self,player):\n        player.gameData['lastRegion'] = 0\n        player.gameData['lap'] = 0\n        player.gameData['distance'] = 0.0\n        player.gameData['finished'] = False\n        player.gameData['rank'] = None\n        bs.TeamGameActivity.onPlayerJoin(self,player)\n\n    def onPlayerLeave(self,player):\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n        if isinstance(self.getSession(),bs.TeamsSession) and self.settings.get('Entire Team Must Finish'):\n            bs.screenMessage(bs.Lstr(translate=('statements', '${TEAM} is disqualified because ${PLAYER} left'),\n                                     subs=[('${TEAM}',player.getTeam().name),\n                                           ('${PLAYER}',player.getName(full=True))]),color=(1,1,0))\n            player.getTeam().gameData['finished'] = True\n            player.getTeam().gameData['time'] = None\n            player.getTeam().gameData['lap'] = 0\n            bs.playSound(bs.getSound(\"boo\"))\n            for player in player.getTeam().players:\n                player.gameData['lap'] = 0\n                player.gameData['finished'] = True\n                try: player.actor.handleMessage(bs.DieMessage())\n                except Exception: pass\n        bs.gameTimer(1,self._checkEndGame)\n\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            distances = [player.gameData['distance'] for player in team.players]\n            if len(distances) == 0: teamDist = 0\n            else:\n                if isinstance(self.getSession(),bs.TeamsSession) and self.settings.get('Entire Team Must Finish'):\n                    teamDist = min(distances)\n                else:\n                    teamDist = max(distances)\n            self._scoreBoard.setTeamValue(team,teamDist,self.settings['Laps'],flash=(teamDist >= float(self.settings['Laps'])),showValue=False)\n            if (teamDist >= float(self.settings['Laps'])): self.checkEnd()\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        self.setupStandardPowerupDrops()\n        self._teamFinishPts = 100\n\n        # throw a timer up on-screen\n        self._timeText = bs.NodeActor(bs.newNode('text',\n                                                 attrs={'vAttach':'top','hAttach':'center','hAlign':'right',\n                                                        'color':(1,1,1,.5),'flatness':0.5,'shadow':0.5,\n                                                        'position':(600,-500),'scale':1.4,'text':'Touch\\nthe\\nright,\\ntop,\\nleft,\\nand\\nbottom\\nplatforms\\nin\\norder.'}))\n        self._timer = bs.OnScreenTimer()\n        \n\n        self._scoreBoardTimer = bs.Timer(250,self._updateScoreBoard,repeat=True)\n\n        if self._isSlowMotion:\n            tScale = 0.4\n            lightY = 50\n        else:\n            tScale = 1.0\n            lightY = 150\n        lStart = int(7100*tScale)\n        inc = int(1250*tScale)\n\n        bs.gameTimer(lStart,self._doLight1)\n        bs.gameTimer(lStart+inc,self._doLight2)\n        bs.gameTimer(lStart+2*inc,self._doLight3)\n        bs.gameTimer(lStart+3*inc,self._startRace)\n\n        self._startLights = []\n        for i in range(4):\n            l = bs.newNode('image',\n                           attrs={'texture':bs.getTexture('nub'),\n                                  'opacity':1.0,\n                                  'absoluteScale':True,\n                                  'position':(-75+i*50,lightY),\n                                  'scale':(50,50),\n                                  'attach':'center'})\n            bs.animate(l,'opacity',{4000*tScale:0,5000*tScale:1.0,12000*tScale:1.0,12500*tScale:0.0})\n            bs.gameTimer(int(13000*tScale),l.delete)\n            self._startLights.append(l)\n\n        self._startLights[0].color = (0.2,0,0)\n        self._startLights[1].color = (0.2,0,0)\n        self._startLights[2].color = (0.2,0.05,0)\n        self._startLights[3].color = (0.0,0.3,0)\n\n    def _doLight1(self):\n        self._startLights[0].color = (1.0,0,0)\n        bs.playSound(self._beep1Sound)\n    def _doLight2(self):\n        self._startLights[1].color = (1.0,0,0)\n        bs.playSound(self._beep1Sound)\n    def _doLight3(self):\n        self._startLights[2].color = (1.0,0.3,0)\n        bs.playSound(self._beep1Sound)\n    def _startRace(self):\n        self._startLights[3].color = (0.0,1.0,0)\n        bs.playSound(self._beep2Sound)\n        for player in self.players:\n            if player.actor is not None:\n                try:player.actor.connectControlsToPlayer()\n                except Exception,e: print 'Exception in race player connects:',e\n        self._timer.start()\n        \n        self._raceStarted = True\n\n    def checkPt(self,player):\n        if not player.isAlive(): return\n        pos = player.actor.node.positionCenter\n        if 8 < pos[0] < 11 and 10.5 < pos[1] < 13:\n            if player.gameData['lastPoint'] in (2,3):\n                self.killPlayer(player)\n                return\n            elif player.gameData['lastPoint'] == 0: player.gameData['distance'] += .25\n            player.gameData['lastPoint'] = 1\n        if -1 < pos[0] < 1 and 11.5 < pos[1] < 15:\n            if player.gameData['lastPoint'] in (3,0):\n                self.killPlayer(player)\n                return\n            elif player.gameData['lastPoint'] == 1: player.gameData['distance'] += .25\n            player.gameData['lastPoint'] = 2\n        if -12.5 < pos[0] < -10 and 10.5 < pos[1] < 13:\n            if player.gameData['lastPoint'] in (0,1):\n                self.killPlayer(player)\n                return\n            elif player.gameData['lastPoint'] == 2: player.gameData['distance'] += .25\n            player.gameData['lastPoint'] = 3\n        if -2 < pos[0] < 2 and 4.5 < pos[1] < 6.5:\n            if player.gameData['lastPoint'] in (1,2):\n                self.killPlayer(player)\n                return\n            elif player.gameData['lastPoint'] == 3: player.gameData['distance'] += .25\n            player.gameData['lastPoint'] = 0\n        \n        bs.gameTimer(250,bs.Call(self.checkPt,player))\n\n    def checkEnd(self):\n        for player in self.players:\n            if player.gameData['distance'] >= self.settings['Laps']:\n                player.getTeam().gameData['time'] = (bs.getGameTime() - self._timer.getStartTime())\n                player.actor.node.delete()\n                self.endGame()\n                \n    def killPlayer(self,player):\n        player.actor.handleMessage(bs.DieMessage())\n        bs.screenMessage(\"Killing \" + player.getName() + \" for skipping part of the track.\", (1,0,0))\n        \n    def endGame(self):\n        if self._timer.hasStarted():\n            self._timer.stop(endTime=None if self._lastTeamTime is None else (self._timer.getStartTime()+self._lastTeamTime))\n        \n        results = bs.TeamGameResults()\n        \n        for t in self.teams: results.setTeamScore(t,t.gameData['time'])\n        self.end(results=results,announceWinningTeam=True)\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m) # augment default\n            try:\n                player = m.spaz.getPlayer()\n                if player is None:\n                    bs.printError('FIXME: getPlayer() should no longer ever be returning None')\n                else:\n                    if not player.exists(): raise Exception()\n                team = player.getTeam()\n            except Exception: return\n            if not player.gameData['finished']: self.respawnPlayer(player,respawnTime=1000)\n        else:\n            bs.TeamGameActivity.handleMessage(self,m)\n"
  },
  {
    "path": "mods/BackToYou.json",
    "content": "{\n  \"name\": \"Back To You\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/BackToYou.py",
    "content": "import bs\nimport random\nimport bsUtils\n#import PlayerSpaz\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [BackToYou]\n\n\nclass Icon(bs.Actor):\n        \n    def __init__(self,player,position,scale,showLives=True,showDeath=True,\n                 nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0):\n        bs.Actor.__init__(self)\n\n        self._player = player\n        self._showLives = showLives\n        self._showDeath = showDeath\n        self._nameScale = nameScale\n\n        self._outlineTex = bs.getTexture('characterIconMask')\n        \n        icon = player.getIcon()\n        self.node = bs.newNode('image',\n                               owner=self,\n                               attrs={'texture':icon['texture'],\n                                      'tintTexture':icon['tintTexture'],\n                                      'tintColor':icon['tintColor'],\n                                      'vrDepth':400,\n                                      'tint2Color':icon['tint2Color'],\n                                      'maskTexture':self._outlineTex,\n                                      'opacity':1.0,\n                                      'absoluteScale':True,\n                                      'attach':'bottomCenter'})\n        self._nameText = bs.newNode('text',\n                                    owner=self.node,\n                                    attrs={'text':player.getName(),\n                                           'color':bs.getSafeColor(player.getTeam().color),\n                                           'hAlign':'center',\n                                           'vAlign':'center',\n                                           'vrDepth':410,\n                                           'maxWidth':nameMaxWidth,\n                                           'shadow':shadow,\n                                           'flatness':flatness,\n                                           'hAttach':'center',\n                                           'vAttach':'bottom'})\n        if self._showLives:\n            self._livesText = bs.newNode('text',\n                                         owner=self.node,\n                                         attrs={'text':'x0',\n                                                'color':(1,1,0.5),\n                                                'hAlign':'left',\n                                                'vrDepth':430,\n                                                'shadow':1.0,\n                                                'flatness':1.0,\n                                                'hAttach':'center',\n                                                'vAttach':'bottom'})\n        self.setPositionAndScale(position,scale)\n\n    def setPositionAndScale(self,position,scale):\n        self.node.position = position\n        self.node.scale = [70.0*scale]\n        self._nameText.position = (position[0],position[1]+scale*52.0)\n        self._nameText.scale = 1.0*scale*self._nameScale\n        if self._showLives:\n            self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0)\n            self._livesText.scale = 1.0*scale\n\n    def updateForLives(self):\n        if self._player.exists():\n            lives = self._player.gameData['lives']\n        else: lives = 0\n        if self._showLives:\n            if lives > 0: self._livesText.text = 'x'+str(lives-1)\n            else: self._livesText.text = ''\n        if lives == 0:\n            myAct = self._player.actor.getActivity()\n            if self._player in myAct.winners:\n                if myAct.winners[0] == self._player:\n                    self._livesText.text = \"1st\"\n                elif myAct.winners[1] == self._player:\n                    self._livesText.text = \"2nd\"\n                elif myAct.winners[2] == self._player:\n                    self._livesText.text = \"3rd\"\n                else:\n                    self._nameText.opacity = 0.2\n                    self.node.color = (0.7,0.3,0.3)\n                    self.node.opacity = 0.2\n        \n    def handlePlayerSpawned(self):\n        if not self.node.exists(): return\n        self.node.opacity = 1.0\n        self.updateForLives()\n\n    def handlePlayerDied(self):\n        if not self.node.exists(): return\n        if self._showDeath:\n            bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0,\n                                            300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2})\n            lives = self._player.gameData['lives']\n            if lives == 0: bs.gameTimer(600,self.updateForLives)\n        \nclass PlayerSpaz_BTY(bs.PlayerSpaz):\n    def handleMessage(self, m):\n        if isinstance(m, bs.HitMessage):\n            if not self.node.exists():\n                return\n            if not self.isAlive():\n                return #We don't want to be hitting corpses!\n            srcSpaz = None\n            theGame = self.getActivity()\n            for theSpaz in theGame.spazList:\n                if theSpaz.getPlayer() == m.sourcePlayer:\n                    srcSpaz = theSpaz\n                    break\n            #print([\"HitSrc\", srcSpaz])\n            #print([\"hitSpaz\", self])\n            if not srcSpaz == self:\n                if not srcSpaz == None:\n                    #We need to calculate new position for hit. Otherwise it won't\n                    #actually hit the source spaz if he's across the screen\n                    p1 = m.pos\n                    p2 = self.node.position\n                    p3 = srcSpaz.node.position\n                    hit2spaz = [p2[0]-p1[0],p2[1]-p1[1], p2[2]-p1[2]]\n                    m.pos = [p3[0]-hit2spaz[0], p3[1]-hit2spaz[1], p3[2]-hit2spaz[2]]\n                    m.sourcePlayer = self.getPlayer()\n                    #print(['sroucenode', m.srcNode])\n                    #print(['pos', m.pos])\n                    #print(['velocity', m.velocity])\n                    #print(['magnitude',m.magnitude])\n                    #print(['vMag', m.velocityMagnitude])\n                    #print(['radisu', m.radius])\n                    #print([m.sourcePlayer])\n                    #print(['kickback', m.kickBack])\n                    #print(['flat', m.flatDamage])\n                    #print(['hittype', m.hitType])\n                    #print(['forcedir', m.forceDirection])\n                    #print(['Hitsubtype', m.hitSubType])\n                    super(srcSpaz.__class__, srcSpaz).handleMessage(m)\n        #if isinstance(m, bs.ImpactDamageMessage):\n            #print([\"impact\", m.intensity])\n            #super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\n            \nclass BackToYou(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Back To You!'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Survived',\n                'scoreType':'seconds',\n                'noneIsWinner':False,\n                'lowerIsBetter':True}\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Damage others to kill yourself! First one out wins!'\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Lives Per Player\",{'default':1,'minValue':1,'maxValue':10,'increment':1}),\n                    (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                            ('2 Minutes',120),('5 Minutes',300),\n                                            ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Epic Mode\",{'default':False})]\n\n        if issubclass(sessionType,bs.TeamsSession):\n            settings.append((\"Solo Mode\",{'default':False}))\n            settings.append((\"Balance Total Lives\",{'default':False}))\n            \n        return settings\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # show messages when players die since it's meaningful here\n        self.announcePlayerDeaths = True\n        \n        try: self._soloMode = settings['Solo Mode']\n        except Exception: self._soloMode = False\n        self._scoreBoard = bs.ScoreBoard()\n        self.spazList = []\n        self.winners = []\n\n    def getInstanceDescription(self):\n        return 'First team out wins.' if isinstance(self.getSession(),bs.TeamsSession) else 'Damage others to kill yourself! First one out wins!'\n\n    def getInstanceScoreBoardDescription(self):\n        return 'first team out wins' if isinstance(self.getSession(),bs.TeamsSession) else 'Damage others to kill yourself! First one out wins!'\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n        self._startGameTime = bs.getGameTime()\n\n    def onTeamJoin(self,team):\n        team.gameData['survivalSeconds'] = None\n        team.gameData['spawnOrder'] = []\n\n    def onPlayerJoin(self, player):\n        \n        player.gameData['lives'] = self.settings['Lives Per Player']\n\n        if self._soloMode:\n            player.gameData['icons'] = []\n            player.getTeam().gameData['spawnOrder'].append(player)\n            self._updateSoloMode()\n        else:\n            # create our icon and spawn\n            player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)]\n            if player.gameData['lives'] > 0:\n                self.spawnPlayer(player)\n\n        # dont waste time doing this until begin\n        if self.hasBegun():\n            self._updateIcons()\n\n    def _updateSoloMode(self):\n        # for both teams, find the first player on the spawn order list with lives remaining\n        # and spawn them if they're not alive\n        for team in self.teams:\n            # prune dead players from the spawn order\n            team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n            for player in team.gameData['spawnOrder']:\n                if player.gameData['lives'] > 0:\n                    if not player.isAlive(): self.spawnPlayer(player)\n                    break\n\n    def _updateIcons(self):\n        # in free-for-all mode, everyone is just lined up along the bottom\n        if isinstance(self.getSession(),bs.FreeForAllSession):\n            count = len(self.teams)\n            xOffs = 85\n            x = xOffs*(count-1) * -0.5\n            for i,team in enumerate(self.teams):\n                if len(team.players) == 1:\n                    player = team.players[0]\n                    for icon in player.gameData['icons']:\n                        icon.setPositionAndScale((x,30),0.7)\n                        icon.updateForLives()\n                    x += xOffs\n\n        # in teams mode we split up teams\n        else:\n            if self._soloMode:\n                # first off, clear out all icons\n                for player in self.players:\n                    player.gameData['icons'] = []\n                # now for each team, cycle through our available players adding icons\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -60\n                        xOffs = -78\n                    else:\n                        x = 60\n                        xOffs = 78\n                    isFirst = True\n                    testLives = 1\n                    while True:\n                        playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives]\n                        if len(playersWithLives) == 0: break\n                        for player in playersWithLives:\n                            player.gameData['icons'].append(Icon(player,\n                                                                 position=(x,(40 if isFirst else 25)),\n                                                                 scale=1.0 if isFirst else 0.5,\n                                                                 nameMaxWidth=130 if isFirst else 75,\n                                                                 nameScale=0.8 if isFirst else 1.0,\n                                                                 flatness=0.0 if isFirst else 1.0,\n                                                                 shadow=0.5 if isFirst else 1.0,\n                                                                 showDeath=True if isFirst else False,\n                                                                 showLives=False))\n                            x += xOffs * (0.8 if isFirst else 0.56)\n                            isFirst = False\n                        testLives += 1\n            # non-solo mode\n            else:\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -50\n                        xOffs = -85\n                    else:\n                        x = 50\n                        xOffs = 85\n                    for player in team.players:\n                        for icon in player.gameData['icons']:\n                            icon.setPositionAndScale((x,30),0.7)\n                            icon.updateForLives()\n                        x += xOffs\n                    \n    def _getSpawnPoint(self,player):\n        # in solo-mode, if there's an existing live player on the map, spawn at whichever\n        # spot is farthest from them (keeps the action spread out)\n        if self._soloMode:\n            livingPlayer = None\n            for team in self.teams:\n                for player in team.players:\n                    if player.isAlive():\n                        p = player.actor.node.position\n                        livingPlayer = player\n                        livingPlayerPos = p\n                        break\n            if livingPlayer:\n                playerPos = bs.Vector(*livingPlayerPos)\n                points = []\n                for team in self.teams:\n                    startPos = bs.Vector(*self.getMap().getStartPosition(team.getID()))\n                    points.append([(startPos-playerPos).length(),startPos])\n                points.sort()\n                return points[-1][1]\n            else:\n                return None\n        else:\n            return None\n\n        \n    def spawnPlayer(self,player):\n        \"\"\"This next line is the default spawn line. But we need to spawn our special guy\"\"\"\n        #self.spawnPlayerSpaz(player,self._getSpawnPoint(player))\n        #position = self._getSpawnPoint(player)\n        #if isinstance(self.getSession(), bs.TeamsSession):\n        #    position = self.getMap().getStartPosition(player.getTeam().getID())\n        #else:\n        #\t# otherwise do free-for-all spawn locations\n        position = self.getMap().getFFAStartPosition(self.players)\n\n        angle = 20\n\n\n        #spaz = self.spawnPlayerSpaz(player)\n\n        # lets reconnect this player's controls to this\n        # spaz but *without* the ability to attack or pick stuff up\n        #spaz.connectControlsToPlayer(enablePunch=False,\n        #\t\t\t\t\t\t\t enableBomb=False,\n        #\t\t\t\t\t\t\t enablePickUp=False)\n\n        # also lets have them make some noise when they die..\n        #spaz.playBigDeathSound = True\n\n        name = player.getName()\n\n        lightColor = bsUtils.getNormalizedColor(player.color)\n        displayColor = bs.getSafeColor(player.color, targetIntensity=0.75)\n\n        spaz = PlayerSpaz_BTY(color=player.color,\n                             highlight=player.highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n        #For some reason, I can't figure out how to get a list of all spaz.\n        #Therefore, I am making the list here so I can get which spaz belongs\n        #to the player supplied by HitMessage.\n        self.spazList.append(spaz)\n        # we want a bigger area-of-interest in co-op mode\n        # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n        # else: spaz.node.areaOfInterestRadius = 5.0\n\n        # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n        # collide with the player-walls\n        # FIXME; need to generalize this\n        if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n\n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer()\n        self.scoreSet.playerGotNewSpaz(player, spaz)\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound, 1, position=spaz.node.position)\n        light = bs.newNode('light', attrs={'color': lightColor})\n        spaz.node.connectAttr('position', light, 'position')\n        bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0})\n        bs.gameTimer(500, light.delete)\n        #Start code to spawn special guy:\n        #End of code to spawn special guy\n        if not self._soloMode:\n            bs.gameTimer(300,bs.Call(self._printLives,player))\n\n        # if we have any icons, update their state\n        for icon in player.gameData['icons']:\n            icon.handlePlayerSpawned()\n\n    def _printLives(self,player):\n        if not player.exists() or not player.isAlive(): return\n        try: pos = player.actor.node.position\n        except Exception,e:\n            print 'EXC getting player pos in bsElim',e\n            return\n        bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1),\n                           offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain()\n\n    def onPlayerLeave(self,player):\n\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n\n        player.gameData['icons'] = None\n        if player in self.winners:\n            self.winners.remove(player)\n\n        # remove us from spawn-order\n        if self._soloMode:\n            if player in player.getTeam().gameData['spawnOrder']:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n\n        # update icons in a moment since our team will be gone from the list then\n        bs.gameTimer(0, self._updateIcons)\n\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        self.setupStandardPowerupDrops()\n\n        if self._soloMode:\n            self._vsText = bs.NodeActor(bs.newNode(\"text\",\n                                                   attrs={'position':(0,105),\n                                                          'hAttach':\"center\",\n                                                          'hAlign':'center',\n                                                          'maxWidth':200,\n                                                          'shadow':0.5,\n                                                          'vrDepth':390,\n                                                          'scale':0.6,\n                                                          'vAttach':\"bottom\",\n                                                          'color':(0.8,0.8,0.3,1.0),\n                                                          'text':bs.Lstr(resource='vsText')}))\n\n        # if balance-team-lives is on, add lives to the smaller team until total lives match\n        if (isinstance(self.getSession(),bs.TeamsSession)\n            and self.settings['Balance Total Lives']\n            and len(self.teams[0].players) > 0\n            and len(self.teams[1].players) > 0):\n            if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]):\n                lesserTeam = self.teams[0]\n                greaterTeam = self.teams[1]\n            else:\n                lesserTeam = self.teams[1]\n                greaterTeam = self.teams[0]\n            addIndex = 0\n            while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam):\n                lesserTeam.players[addIndex].gameData['lives'] += 1\n                addIndex = (addIndex + 1) % len(lesserTeam.players)\n\n        self._updateIcons()\n\n        # we could check game-over conditions at explicit trigger points,\n        # but lets just do the simple thing and poll it...\n        bs.gameTimer(1000, self._update, repeat=True)\n        \n    def _getTotalTeamLives(self,team):\n        return sum(player.gameData['lives'] for player in team.players)\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            \n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n            player = m.spaz.getPlayer()\n            respawnPoints = None\n            print([player, m.spaz.hitPoints, \"killed by\", m.killerPlayer])\n            if m.killerPlayer is None:\n                pass #Don't take away a life for non-violent death\n            elif m.killerPlayer == m.spaz.getPlayer():\n                pass #No credit for suicide!\n            elif m.spaz.hitPoints > 0: #Spaz died, but had positive hit points. Probably fell. Take points from player.\n                #tossing or knocking off a player respawns them w/o taking life.\n                print([player, \"died from fall.\", m.spaz.hitPoints])\n                pass\n                \n            else:\n                player.gameData['lives'] -= 1\n                \n            #Remove this spaz from the list of active spazzes\n            if m.spaz in self.spazList: self.spazList.remove(m.spaz)\n            if player.gameData['lives'] < 0:\n                bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode))\n                player.gameData['lives'] = 0\n\n            # if we have any icons, update their state\n            for icon in player.gameData['icons']:\n                icon.handlePlayerDied()\n\n            # play big death sound on our last death or for every one in solo mode\n            if self._soloMode or player.gameData['lives'] == 0:\n                bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound)\n\n            # if we hit zero lives, we're dead (and our team might be too)\n            if player.gameData['lives'] == 0:\n                # if the whole team is now dead, mark their survival time..\n                #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players):\n                if self._getTotalTeamLives(player.getTeam()) == 0:\n                    player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000\n                    self.winners.append(player)\n            else:\n                # otherwise, in regular mode, respawn..\n                if not self._soloMode:\n                    self.respawnPlayer(player)\n\n            # in solo, put ourself at the back of the spawn order\n            if self._soloMode:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n                player.getTeam().gameData['spawnOrder'].append(player)\n        else:\n            bs.TeamGameActivity.handleMessage(self, m)\n    def _update(self):\n\n        if self._soloMode:\n            # for both teams, find the first player on the spawn order list with lives remaining\n            # and spawn them if they're not alive\n            for team in self.teams:\n                # prune dead players from the spawn order\n                team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n                for player in team.gameData['spawnOrder']:\n                    if player.gameData['lives'] > 0:\n                        if not player.isAlive():\n                            self.spawnPlayer(player)\n                            self._updateIcons()\n                        break\n        \n        # if we're down to 1 or fewer living teams, start a timer to end the game\n        # (allows the dust to settle and draws to occur if deaths are close enough)\n        if (len(self._getLivingTeams()) < 2) or len(self.winners) > 2:\n            self._roundEndTimer = bs.Timer(500,self.endGame)\n\n\n    def _getLivingTeams(self):\n        return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)]\n\n    def endGame(self):\n        if self.hasEnded(): return\n        results = bs.TeamGameResults()\n        self._vsText = None # kill our 'vs' if its there\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['survivalSeconds'])\n        self.end(results=results)\n        \n"
  },
  {
    "path": "mods/Basketball.json",
    "content": "{\n  \"name\": \"Basketball\",\n  \"author\": \"MattZ45986\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/Basketball.py",
    "content": "#Basketball\r\nimport bs\r\nimport bsUtils\r\nimport math\r\nimport random\r\n\r\n# This game is played with the same rules as the classic American sport, Bombsquad style!\r\n# Featuring: a hoop, fouls, foul shots, jump-balls, three-pointers, a referee, and two teams ready to duke it out.\r\n# Dedicated to - David\r\n\r\ndef bsGetAPIVersion():\r\n    return 4\r\n\r\ndef bsGetGames():\r\n    return [Basketball]\r\n\r\nclass ImpactMessage(object):\r\n    pass\r\n\r\nclass Referee(bs.SpazBot):\r\n    character = 'Bernard'\r\n    chargeDistMax = 9999\r\n    throwDistMin = 9999\r\n    throwDistMax = 9999\r\n    color=(0,0,0)\r\n    highlight=(1,1,1)\r\n    punchiness = 0.0\r\n    chargeSpeedMin = 0.0\r\n    chargeSpeedMax = 0.0\r\n\r\nclass Hoop(bs.Actor):\r\n    def __init__(self,position=(0,5,-8),color=(1,1,1)):\r\n        self._r1 = 0.7\r\n        self._rFudge = 0.15\r\n        bs.Actor.__init__(self)\r\n        self._position = bs.Vector(*position)\r\n        self.color = color\r\n        p1 = position\r\n        p2 = (position[0]+1,position[1],position[2])\r\n        p3 = (position[0]-1,position[1],position[2])\r\n        showInSpace = False\r\n        self._hit = False\r\n        n1 = bs.newNode('locator',attrs={'shape':'circle','position':p1,\r\n                                         'color':self.color,'opacity':0.5,\r\n                                         'drawBeauty':showInSpace,'additive':True})\r\n        n2 = bs.newNode('locator',attrs={'shape':'circle','position':p2,\r\n                                         'color':self.color,'opacity':0.5,\r\n                                         'drawBeauty':showInSpace,'additive':True})\r\n        n3 = bs.newNode('locator',attrs={'shape':'circle','position':p3,\r\n                                         'color':self.color,'opacity':0.5,\r\n                                         'drawBeauty':showInSpace,'additive':True})\r\n        n4 = bs.newNode('light',attrs={'color':self.color,'position':p1,'intensity':.5})\r\n\r\n        bs.animateArray(n1,'size',1,{0:[0.0],200:[self._r1*2.0]})\r\n        bs.animateArray(n2,'size',1,{0:[0.0],200:[self._r1*2.0]})\r\n        bs.animateArray(n3,'size',1,{0:[0.0],200:[self._r1*2.0]})\r\n        self._nodes = [n1,n2,n3,n4]\r\n\r\nclass ThreePointLine(bs.Actor):\r\n    def __init__(self):\r\n        bs.Actor.__init__(self)\r\n        r1 = 6\r\n        n1 = bs.newNode('locator',attrs={'shape':'circleOutline','position':(0,4,-8),'color':(1,1,1),'opacity':.3,'drawBeauty':False,'additive':True})\r\n        self._nodes = [n1]\r\n        bs.animateArray(n1,'size',1,{50:[0.0],250:[r1*2.0]})\r\n\r\nclass BasketBallFactory(bs.BombFactory):\r\n    def __init__(self):\r\n        self.basketBallMaterial = bs.Material()\r\n        self.basketBallMaterial.addActions(conditions=(('weAreOlderThan',200),\r\n                        'and',('theyAreOlderThan',200),\r\n                        'and',('evalColliding',),\r\n                        'and',(('theyHaveMaterial',bs.getSharedObject('footingMaterial')),\r\n                               'or',('theyHaveMaterial',bs.getSharedObject('objectMaterial')))),\r\n            actions=(('message','ourNode','atConnect',ImpactMessage())))\r\n        bs.BombFactory.__init__(self)\r\n\r\nclass Baller(bs.PlayerSpaz):\r\n    \r\n    def onBombPress(self):\r\n        pass\r\n\r\n    def onPickUpPress(self):\r\n        bs.PlayerSpaz.onPickUpPress(self)\r\n        self.node.getDelegate()._pos = self.node.positionCenter\r\n    \r\nclass BasketBomb(bs.Bomb):\r\n    def __init__(self,position=(0,1,0),velocity=(0,0,0),bombType='normal',blastRadius=2.0,sourcePlayer=None,owner=None):\r\n        bs.Actor.__init__(self)\r\n        self.up = False\r\n        factory = BasketBallFactory()\r\n        self.bombType = 'basketball'\r\n        self._exploded = False\r\n        self.blastRadius = blastRadius\r\n        self._explodeCallbacks = []\r\n        self.sourcePlayer = sourcePlayer\r\n        self.hitType = 'impact'\r\n        self.hitSubType = 'basketball'\r\n        owner = bs.Node(None)\r\n        self.owner = owner\r\n        materials = (factory.bombMaterial, bs.getSharedObject('objectMaterial'))\r\n        materials = materials + (factory.normalSoundMaterial,)\r\n        materials = materials + (factory.basketBallMaterial,)\r\n        self.node = bs.newNode('prop',\r\n                                   delegate=self,\r\n                                   attrs={'position':position,\r\n                                          'velocity':velocity,\r\n                                          'body':'sphere',\r\n                                          'model':factory.bombModel,\r\n                                          'shadowSize':0.3,\r\n                                          'colorTexture':bs.getTexture('bonesColorMask'),\r\n                                          'reflection':'soft',\r\n                                          'reflectionScale':[1.5],\r\n                                          'materials':materials})\r\n        bsUtils.animate(self.node,\"modelScale\",{0:0, 200:1.3, 260:1})\r\n\r\n    def handleMessage(self, m):\r\n        if isinstance(m, bs.OutOfBoundsMessage):\r\n            self.getActivity().respawnBall((not self.getActivity().possession))\r\n            bs.Bomb.handleMessage(self, m)\r\n        elif isinstance(m, bs.PickedUpMessage):\r\n            self.heldLast = m.node.getDelegate().getPlayer()\r\n            self.getActivity().heldLast = self.heldLast\r\n            if self.heldLast in self.getActivity().teams[0].players: self.getActivity().possession = True\r\n            else: self.getActivity().possession = False\r\n            bs.Bomb.handleMessage(self, m)\r\n            if self.up == True:\r\n                activity = self.getActivity()\r\n                bs.gameTimer(3000,bs.WeakCall(activity.jumpBall))\r\n            self.up = True\r\n        elif isinstance(m, ImpactMessage): self.getActivity().handleShot(self)\r\n        elif isinstance(m, bs.DroppedMessage): self.up = False\r\n        else: bs.Bomb.handleMessage(self, m)\r\n\r\nclass Basketball(bs.TeamGameActivity):\r\n    @classmethod\r\n    def getName(cls):\r\n        return \"Basketball\"\r\n\r\n    @classmethod\r\n    def getDescription(cls, sessionType):\r\n        return \"A classic sport, Bombsquad style!\"\r\n\r\n    @classmethod\r\n    def getScoreInfo(cls):\r\n        return{'scoreType':'points'}\r\n\r\n    @classmethod\r\n    def getSettings(cls, sessionType):\r\n        return [(\"Epic Mode\", {'default': False}),\r\n                (\"Enable Running\", {'default': True}),\r\n                (\"Enable Jumping\", {'default': True}),\r\n                (\"Play To: \", {\r\n                    'choices': [\r\n                        ('1 point', 1),\r\n                        ('11 points', 11),\r\n                        ('21 points', 21),\r\n                        ('45 points', 45),\r\n                        ('100 points', 100)\r\n                        ],\r\n                    'default': 21})]\r\n    \r\n    @classmethod\r\n    def getSupportedMaps(cls, sessionType):\r\n        return ['Courtyard']\r\n\r\n    @classmethod\r\n    def supportsSessionType(cls, sessionType):\r\n        return True if issubclass(sessionType, bs.TeamsSession) else False\r\n\r\n    def __init__(self,settings):\r\n        bs.TeamGameActivity.__init__(self,settings)\r\n        if self.settings['Epic Mode']: self._isSlowMotion = True\r\n        self.info = bs.NodeActor(bs.newNode('text',\r\n                                                   attrs={'vAttach': 'bottom',\r\n                                                          'hAlign': 'center',\r\n                                                          'vrDepth': 0,\r\n                                                          'color': (0,.2,0),\r\n                                                          'shadow': 1.0,\r\n                                                          'flatness': 1.0,\r\n                                                          'position': (0,0),\r\n                                                          'scale': 0.8,\r\n                                                          'text': \"Created by MattZ45986 on Github\",\r\n                                                          }))\r\n        self.possession = True\r\n        self.heldLast = None\r\n        self.fouled = False\r\n        self.firstFoul = False\r\n        self.jb = True\r\n        self.blueBench = bs.newNode('light', attrs={\r\n            'color':(0,0,1),'intensity':1,'position':(-6.5,0,-2)})\r\n        self.redBench = bs.newNode('light', attrs={\r\n            'color':(1,0,0),'intensity':1,'position':(6.5,0,-2)})\r\n        self._bots = bs.BotSet()\r\n        self.hoop = Hoop((0,5,-8), (1,1,1))\r\n        self.threePointLine = ThreePointLine().autoRetain()\r\n        self._scoredis = bs.ScoreBoard()\r\n        self.referee = Referee\r\n\r\n        bs.gameTimer(10,bs.Call(self._bots.spawnBot,self.referee,pos=(-6,3,-6),spawnTime=1))\r\n        \r\n    def onTransitionIn(self):\r\n        bs.TeamGameActivity.onTransitionIn(self,music='Sports')\r\n\r\n    def onBegin(self):\r\n        bs.TeamGameActivity.onBegin(self)\r\n        s = self.settings\r\n        for player in self.players:\r\n            player.actor.connectControlsToPlayer(enableBomb=False, enableRun = s[\"Enable Running\"], enableJump = s[\"Enable Jumping\"])\r\n            player.sessionData['fouls'] = 0\r\n        self.respawnBall(None)\r\n        self.teams[0].gameData['score'] = 0\r\n        self.teams[1].gameData['score'] = 0\r\n        self._scoredis.setTeamValue(self.teams[0],self.teams[1].gameData['score'])\r\n        self._scoredis.setTeamValue(self.teams[1],self.teams[1].gameData['score'])\r\n        self.updateScore()\r\n        self.checkEnd()\r\n\r\n    def spawnPlayerSpaz(self,player,position=(0,5,-3),angle=None, killedDuringFoulShots = False):\r\n        name = player.getName()\r\n        color = player.color\r\n        highlight = player.highlight\r\n        spaz = Baller(color=color,\r\n                             highlight=highlight,\r\n                             character=player.character,\r\n                             player=player)\r\n        player.setActor(spaz)\r\n        if player in self.teams[0].players: position = (-6.5,3.2,(random.random()*5)-4.5)\r\n        else: position = (6.5,3.2,(random.random()*5)-4.5)\r\n        if self.fouled == True and killedDuringFoulShots == False: position = (0,3.2,-3)\r\n        s = self.settings\r\n        player.actor.connectControlsToPlayer(enableBomb=False, enableRun = s[\"Enable Running\"], enableJump = s[\"Enable Jumping\"])\r\n        spaz.handleMessage(bs.StandMessage(position,90))\r\n\r\n    def respawnBall(self, owner):\r\n        if owner == True:\r\n            self.basketball = BasketBomb(position=(-6,5,-3)).autoRetain()\r\n        elif owner == False:\r\n            self.basketball = BasketBomb(position=(6,5,-3)).autoRetain()\r\n        else:\r\n            self.basketball = BasketBomb(position=(0,5,-2.5)).autoRetain()\r\n\r\n    def handleMessage(self, m):\r\n        if isinstance(m, bs.SpazBotDeathMessage):\r\n            if m.killerPlayer in self.teams[0].players:\r\n                results = bs.TeamGameResults()\r\n                results.setTeamScore(self.teams[0],0)\r\n                results.setTeamScore(self.teams[1],100)\r\n                self.end(results=results)\r\n                bs.screenMessage(\"Don't take it out on the ref!\", color=(1,0,0))\r\n            elif m.killerPlayer in self.teams[1].players:\r\n                results = bs.TeamGameResults()\r\n                results.setTeamScore(self.teams[1],0)\r\n                results.setTeamScore(self.teams[0],100)\r\n                self.end(results=results)\r\n                bs.screenMessage(\"Don't take it out on the ref!\", color=(0,0,1))\r\n        elif isinstance(m, bs.PlayerSpazDeathMessage):\r\n            if m.killed:\r\n                if m.spaz.getPlayer() in self.teams[0].players:\r\n                    team = self.teams[0]\r\n                elif m.spaz.getPlayer() in self.teams[1].players: team = self.teams[1]\r\n                if m.killerPlayer not in team.players:\r\n                    m.killerPlayer.sessionData['fouls'] += 1\r\n                    m.killerPlayer.actor.setScoreText(\"FOUL \" + str(m.killerPlayer.sessionData['fouls']))\r\n                    bs.playSound(bs.getSound('bearDeath'))\r\n                    if m.killerPlayer.sessionData['fouls'] == 3: self.foulOut(m.killerPlayer)\r\n                    if self.fouled == True:\r\n                        self.spawnPlayerSpaz(player=m.spaz.getPlayer(),killedDuringFoulShots=True)\r\n                        return\r\n                    self.fouled = True\r\n                    self.giveFoulShots(m.spaz)\r\n                elif m.spaz.getPlayer().sessionData['fouls'] < 3: self.respawnPlayer(m.spaz.getPlayer())\r\n            elif m.spaz.getPlayer().sessionData['fouls'] < 3: self.respawnPlayer(m.spaz.getPlayer())\r\n            s = self.settings\r\n        else: bs.TeamGameActivity.handleMessage(self, m)\r\n\r\n    def giveFoulShots(self, player):\r\n        for p in self.players:\r\n            p.actor.disconnectControlsFromPlayer()\r\n            if p in self.teams[0].players and p != player: p.actor.node.handleMessage('stand',-6.5,3.2,(random.random()*5)-4.5, 0)\r\n            if p in self.teams[1].players and p != player: p.actor.node.handleMessage('stand',6.5,3.2,(random.random()*5)-4.5, 0)\r\n        self.spawnPlayerSpaz(player.getPlayer())\r\n        name = player.getPlayer().getName()\r\n        for p in self.players:\r\n            if p.getName() == name:\r\n                player = p.actor\r\n        s = self.settings\r\n        player.connectControlsToPlayer(enableBomb=False, enableRun = s[\"Enable Running\"], enableJump = s[\"Enable Jumping\"],enablePunch = False)\r\n        self.firstFoul = True\r\n        self.basketball.node.delete()\r\n        self.respawnBall(None)\r\n        sound = bs.getSound('announceTwo')\r\n        bs.gameTimer(1000, bs.Call(bs.playSound,sound))\r\n        player.node.handleMessage('stand',0,3.2,-3, 0)\r\n        player.onPickUpPress()\r\n        player.onPickUpRelease()\r\n        player.setScoreText(\"5 seconds to shoot\")\r\n        bs.gameTimer(6000, bs.Call(self.continueFoulShots,player))\r\n\r\n    def continueFoulShots(self, player):\r\n        if self.basketball.node.exists(): self.basketball.node.delete()\r\n        self.firstFoul = False\r\n        player.node.handleMessage('stand',0,3.2,-3, 0)\r\n        self.respawnBall(None)\r\n        bs.playSound(bs.getSound('announceOne'))\r\n        player.onPickUpPress()\r\n        player.onPickUpRelease()\r\n        bs.playSound(bs.getSound('bear1'))\r\n        player.setScoreText(\"5 seconds to shoot\")\r\n        bs.gameTimer(6000, bs.Call(self.continuePlay))\r\n\r\n    def continuePlay(self):\r\n        self.fouled = False\r\n        if self.basketball.node.exists(): self.basketball.node.delete()\r\n        self.respawnBall(not self.possession)\r\n        s = self.settings\r\n        for player in self.players:\r\n            player.actor.connectControlsToPlayer(enableBomb=False, enableRun = s[\"Enable Running\"], enableJump = s[\"Enable Jumping\"], enablePunch = True)\r\n        if player in self.teams[0].players:\r\n                player.actor.node.handleMessage('stand',-6.5,3.2,(random.random()*5)-4.5, 0)\r\n        elif player in self.teams[1].players:\r\n                player.actor.node.handleMessage('stand',6.5,3.2,(random.random()*5)-4.5, 0)\r\n        \r\n    def foulOut(self, player):\r\n        player.actor.shatter()\r\n        player.actor.setScoreText(\"FOULED OUT\")\r\n\r\n    def jumpBall(self):\r\n        ball = self.basketball\r\n        if ball.up == True:\r\n            self.basketball.heldLast.actor.setScoreText(\"Jump Ball\")\r\n            for player in self.teams[0].players:\r\n                player.actor.node.handleMessage('stand',-6.5,3.2,(random.random()*5)-4.5, 0)\r\n            for player in self.teams[1].players:\r\n                player.actor.node.handleMessage('stand',6.5,3.2,(random.random()*5)-4.5, 0)\r\n            ball.node.delete()\r\n            self.respawnBall(not self.jb)\r\n            self.jb = not self.jb\r\n            \r\n    def handleShot(self, ball):\r\n        if ball.node.position[0] > -1.5 and ball.node.position[0] < 1.5:\r\n            if ball.node.position[1] > 4 and ball.node.position[1] < 5:\r\n                if ball.node.position[2] > -9 and ball.node.position[2] < -8:\r\n                    if self.isTendingGoal(ball):\r\n                        ball.node.delete()\r\n                        self.respawnBall(not self.possession)\r\n                        bs.playSound(bs.getSound('bearDeath'))\r\n                        ball.heldLast.actor.shatter()\r\n                        return\r\n                    bs.playSound(bs.getSound('bear' +str(random.randint(1,4))))\r\n                    for node in self.hoop._nodes:\r\n                        node.delete()\r\n                    self.hoop = None\r\n                    if not self.fouled:\r\n                        if self.possession:\r\n                            pts = self.checkThreePoint(ball)\r\n                            self.teams[0].gameData['score'] += pts\r\n                            ball.heldLast.actor.setScoreText(str(pts) + \" Points\")\r\n                            self.hoop = Hoop((0,5,-8),(0,0,1))\r\n                            for player in self.teams[0].players:\r\n                                player.actor.node.handleMessage('stand',-6.5,3.2,(random.random()*5)-4.5, 0)\r\n                        else:\r\n                            pts = self.checkThreePoint(ball)\r\n                            self.teams[1].gameData['score'] += pts\r\n                            ball.heldLast.actor.setScoreText(str(pts) + \" Points\")\r\n                            self.hoop = Hoop((0,5,-8),(1,0,0))\r\n                            for player in self.teams[1].players:\r\n                                player.actor.node.handleMessage('stand',6.5,3.2,(random.random()*5)-4.5, 0)\r\n                        self.updateScore()\r\n                        ball.node.delete()\r\n                        self.respawnBall(not self.possession)\r\n                    else:\r\n                        if self.possession:\r\n                            self.hoop = Hoop((0,5,-8),(0,0,1))\r\n                            self.teams[0].gameData['score'] += 1\r\n                        else:\r\n                            self.hoop = Hoop((0,5,-8),(1,0,0))\r\n                            self.teams[1].gameData['score'] += 1\r\n                        ball.heldLast.actor.setScoreText(\"1 Point\")\r\n                        self.updateScore()\r\n                        ball.node.delete()\r\n\r\n    def checkThreePoint(self, ball):\r\n        pos = ball.heldLast.actor._pos\r\n        if pos[0]*pos[0] + (pos[2]+8)*(pos[2]+8) >= 36: return 3\r\n        else: return 2\r\n\r\n    def isTendingGoal(self,ball):\r\n        pos = ball.heldLast.actor._pos\r\n        if pos[0] > -1.5 and pos[0] < 1.5:\r\n            if pos[2] > -9 and pos[2] < -8: return True\r\n        return False\r\n    \r\n    def updateScore(self):\r\n        for team in self.teams:\r\n            self._scoredis.setTeamValue(team,team.gameData['score'])\r\n        self.checkEnd()\r\n\r\n    def checkEnd(self):\r\n        for team in self.teams:\r\n            i = 0\r\n            if team.gameData['score'] >= self.settings['Play To: ']: self.endGame()\r\n            for player in team.players:\r\n                if player.isAlive(): i = 1\r\n            if i == 0: self.endGame()\r\n        \r\n    def endGame(self):\r\n        results = bs.TeamGameResults()\r\n        for team in self.teams:\r\n            results.setTeamScore(team, team.gameData['score'])\r\n            i = 0\r\n            for player in team.players:\r\n                if player.isAlive(): i = 1\r\n            if i == 0: results.setTeamScore(team, 0)\r\n        self.end(results=results)\r\n"
  },
  {
    "path": "mods/BuddyBunny.json",
    "content": "{\n  \"name\": \"Buddy Bunny powerup\",\n  \"author\": \"joshville79\",\n  \"category\": \"libraries\"\n}"
  },
  {
    "path": "mods/BuddyBunny.py",
    "content": "import bsSpaz\nimport bs\nimport bsUtils\nimport weakref\nimport random\n\nclass BunnyBuddyBot(bsSpaz.SpazBot):\n    \"\"\"\n    category: Bot Classes\n    \n    A speedy attacking melee bot.\n    \"\"\"\n\n    color=(1,1,1)\n    highlight=(1.0,0.5,0.5)\n    character = 'Easter Bunny'\n    punchiness = 1.0\n    run = True\n    bouncy = True\n    defaultBoxingGloves = True\n    chargeDistMin = 1.0\n    chargeDistMax = 9999.0\n    chargeSpeedMin = 1.0\n    chargeSpeedMax = 1.0\n    throwDistMin = 3\n    throwDistMax = 6\n    pointsMult = 2\n    \n    def __init__(self,player):\n        \"\"\"\n        Instantiate a spaz-bot.\n        \"\"\"\n        self.color = player.color\n        self.highlight = player.highlight\n        bsSpaz.Spaz.__init__(self,color=self.color,highlight=self.highlight,character=self.character,\n                      sourcePlayer=None,startInvincible=False,canAcceptPowerups=False)\n\n        # if you need to add custom behavior to a bot, set this to a callable which takes one\n        # arg (the bot) and returns False if the bot's normal update should be run and True if not\n        self.updateCallback = None\n        self._map = weakref.ref(bs.getActivity().getMap())\n\n        self.lastPlayerAttackedBy = None # FIXME - should use empty player-refs\n        self.lastAttackedTime = 0\n        self.lastAttackedType = None\n        self.targetPointDefault = None\n        self.heldCount = 0\n        self.lastPlayerHeldBy = None # FIXME - should use empty player-refs here\n        self.targetFlag = None\n        self._chargeSpeed = 0.5*(self.chargeSpeedMin+self.chargeSpeedMax)\n        self._leadAmount = 0.5\n        self._mode = 'wait'\n        self._chargeClosingIn = False\n        self._lastChargeDist = 0.0\n        self._running = False\n        self._lastJumpTime = 0    \n        \nclass BunnyBotSet(bsSpaz.BotSet):\n    \"\"\"\n    category: Bot Classes\n    \n    A container/controller for one or more bs.SpazBots.\n    \"\"\"\n    def __init__(self, sourcePlayer):\n        \"\"\"\n        Create a bot-set.\n        \"\"\"\n        # we spread our bots out over a few lists so we can update them in a staggered fashion\n        self._botListCount = 5\n        self._botAddList = 0\n        self._botUpdateList = 0\n        self._botLists = [[] for i in range(self._botListCount)]\n        self._spawnSound = bs.getSound('spawn')\n        self._spawningCount = 0\n        self.startMovingBunnies()\n        self.sourcePlayer = sourcePlayer\n        \n\n    def doBunny(self):\n        self.spawnBot(BunnyBuddyBot, self.sourcePlayer.actor.node.position, 2000, self.setupBunny)\n        \n    def startMovingBunnies(self):\n        self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self._bUpdate),repeat=True)\n        \n    def _spawnBot(self,botType,pos,onSpawnCall):\n        spaz = botType(self.sourcePlayer)\n        bs.playSound(self._spawnSound,position=pos)\n        spaz.node.handleMessage(\"flash\")\n        spaz.node.isAreaOfInterest = 0\n        spaz.handleMessage(bs.StandMessage(pos,random.uniform(0,360)))\n        self.addBot(spaz)\n        self._spawningCount -= 1\n        if onSpawnCall is not None: onSpawnCall(spaz)\n        \n    def _bUpdate(self):\n\n        # update one of our bot lists each time through..\n        # first off, remove dead bots from the list\n        # (we check exists() here instead of dead.. we want to keep them around even if they're just a corpse)\n\n        try:\n            botList = self._botLists[self._botUpdateList] = [b for b in self._botLists[self._botUpdateList] if b.exists()]\n        except Exception:\n            bs.printException(\"error updating bot list: \"+str(self._botLists[self._botUpdateList]))\n        self._botUpdateList = (self._botUpdateList+1)%self._botListCount\n\n        # update our list of player points for the bots to use\n        playerPts = []\n\n        try:\n            #if player.isAlive() and not (player is self.sourcePlayer):\n            #    playerPts.append((bs.Vector(*player.actor.node.position),\n            #                     bs.Vector(*player.actor.node.velocity)))\n            for n in bs.getNodes():\n                if n.getNodeType() == 'spaz':\n                    s = n.getDelegate()\n                    if isinstance(s,bsSpaz.SpazBot):\n                        if not s in self.getLivingBots():\n                            if hasattr(s, 'sourcePlayer'):\n                                if not s.sourcePlayer is self.sourcePlayer:\n                                    playerPts.append((bs.Vector(*n.position), bs.Vector(*n.velocity)))\n                            else:\n                                playerPts.append((bs.Vector(*n.position), bs.Vector(*n.velocity)))\n                    elif isinstance(s, bsSpaz.PlayerSpaz):\n                        if not (s.getPlayer() is self.sourcePlayer):\n                            playerPts.append((bs.Vector(*n.position), bs.Vector(*n.velocity)))\n        except Exception:\n            bs.printException('error on bot-set _update')\n\n        for b in botList:\n            b._setPlayerPts(playerPts)\n            b._updateAI()\n    def setupBunny(self, spaz):\n        spaz.sourcePlayer = self.sourcePlayer\n        spaz.color = self.sourcePlayer.color\n        spaz.highlight = self.sourcePlayer.highlight\n        self.setBunnyText(spaz)\n    def setBunnyText(self, spaz):\n        m = bs.newNode('math', owner=spaz.node, attrs={'input1': (0, 0.7, 0), 'operation': 'add'})\n        spaz.node.connectAttr('position', m, 'input2')\n        spaz._bunnyText = bs.newNode('text',\n                                      owner=spaz.node,\n                                      attrs={'text':self.sourcePlayer.getName(),\n                                             'inWorld':True,\n                                             'shadow':1.0,\n                                             'flatness':1.0,\n                                             'color':self.sourcePlayer.color,\n                                             'scale':0.0,\n                                             'hAlign':'center'})\n        m.connectAttr('output', spaz._bunnyText, 'position')\n        bs.animate(spaz._bunnyText, 'scale', {0: 0.0, 1000: 0.01})\n\n"
  },
  {
    "path": "mods/Collector.json",
    "content": "{\n  \"name\": \"Collector\",\n  \"author\": \"TheMikirog\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/Collector.py",
    "content": "import bs\nimport random\nimport bsUtils\nimport weakref\n\n'''\n    Gamemode: Collector\n    Creator: TheMikirog\n    Website: https://bombsquadjoyride.blogspot.com/\n    \n    This is a gamemode purely made by me just to spite unchallenged modders out there that put out crap to the market. \n    We don't want gamemodes that are just the existing ones with some novelties! Gamers deserve more!\n    \n    In this gamemode you have to kill others in order to get their Capsules. Capsules can be collected and staked in your inventory, how many as you please.\n    After you kill an enemy that carries some of them, they drop a respective amount of Capsules they carried + two more.\n    Your task is to collect these Capsules, get to the flag and score them KOTH style. You can't score if you don't have any Capsules with you.\n    The first player or team to get to the required ammount wins.\n    This is a gamemode all about trying to stay alive and picking your battles in order to win. A rare skill in BombSquad, where everyone is overly aggressive.\n'''\n\n# scripts specify an API-version they were written against\n# so the game knows to ignore out-of-date ones.\ndef bsGetAPIVersion():\n    return 4\n# how BombSquad asks us what games we provide\ndef bsGetGames():\n    return [CollectorGame]\n\nclass CollectorGame(bs.TeamGameActivity):\n\n    tips = ['Making you opponent fall down the pit makes his Capsules wasted!\\nTry not to kill enemies by throwing them off the cliff.',\n            'Don\\'t be too reckless. You can lose your loot quite quickly!',\n            'Don\\'t let the leading player score his Capsules at the Deposit Point!\\nTry to catch him if you can!',\n            'Lucky Capsules give 4 to your inventory and they have 8% chance of spawning after kill!',\n            'Don\\t camp in one place! Make your move first, so hopefully you get some dough!']\n\n    FLAG_NEW = 0\n    FLAG_UNCONTESTED = 1\n    FLAG_CONTESTED = 2\n    FLAG_HELD = 3\n\n    @classmethod\n    def getName(cls):\n        return 'Collector'\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return ('Kill your opponents to steal their Capsules.\\n'\n               'Collect them and score at the Deposit point!')\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        self.setupStandardPowerupDrops()\n        if len(self.teams) > 0:\n            self._scoreToWin = self.settings['Capsules to Collect'] * max(1,max(len(t.players) for t in self.teams))\n        else: self._scoreToWin = self.settings['Capsules to Collect']\n        self._updateScoreBoard()\n        self._dingSound = bs.getSound('dingSmall')\n        if isinstance(bs.getActivity().getSession(),bs.FreeForAllSession):\n            self._flagNumber = random.randint(0,1)\n            self._flagPos = self.getMap().getFlagPosition(self._flagNumber)\n        else: self._flagPos = self.getMap().getFlagPosition(None)\n        bs.gameTimer(1000,self._tick,repeat=True)\n        self._flagState = self.FLAG_NEW\n        self.projectFlagStand(self._flagPos)\n\n        self._flag = bs.Flag(position=self._flagPos,\n                             touchable=False,\n                             color=(1,1,1))\n        self._flagLight = bs.newNode('light',\n                                     attrs={'position':self._flagPos,\n                                            'intensity':0.2,\n                                            'heightAttenuated':False,\n                                            'radius':0.4,\n                                            'color':(0.2,0.2,0.2)})\n\n        # flag region\n        bs.newNode('region',\n                   attrs={'position':self._flagPos,\n                          'scale': (1.8,1.8,1.8),\n                          'type': 'sphere',\n                          'materials':[self._flagRegionMaterial,bs.getSharedObject('regionMaterial')]})\n        self._updateFlagState()\n        \n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n                        \n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"keepAway\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Capsules to Collect\",{'minValue':1,'default':10,'increment':1}),\n                    (\"Capsules on Death\",{'minValue':1,'maxValue':10,'default':2,'increment':1}),\n                    (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                              ('2 Minutes',120),('5 Minutes',300),\n                                              ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Allow Lucky Capsules\",{'default':True}),\n                    (\"Epic Mode\",{'default':False})]\n        return settings\n        \n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # print messages when players die since it matters here..\n        self.announcePlayerDeaths = True\n        \n        self._scoreBoard = bs.ScoreBoard()\n        self._swipSound = bs.getSound(\"swip\")\n        self._tickSound = bs.getSound('tick')\n        \n        self._scoreToWin = self.settings['Capsules to Collect']\n        \n        self._capsuleModel = bs.getModel('bomb')\n        self._capsuleTex = bs.getTexture('bombColor')\n        self._capsuleLuckyTex = bs.getTexture('bombStickyColor')\n        \n        self._collectSound = bs.getSound('powerup01')\n        self._luckyCollectSound = bs.getSound('cashRegister2')\n        \n        self._capsuleMaterial = bs.Material()\n        self._capsuleMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('playerMaterial')),\n                                     actions=((\"call\",\"atConnect\",self._onCapsulePlayerCollide),))\n        self._capsules = []\n        \n        self._flagRegionMaterial = bs.Material()\n        self._flagRegionMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('playerMaterial')),\n                                            actions=((\"modifyPartCollision\",\"collide\",True),\n                                                     (\"modifyPartCollision\",\"physical\",False),\n                                                     (\"call\",\"atConnect\",bs.Call(self._handlePlayerFlagRegionCollide,1)),\n                                                     (\"call\",\"atDisconnect\",bs.Call(self._handlePlayerFlagRegionCollide,0))))\n        \n    def getInstanceDescription(self):\n        return ('Score ${ARG1} capsules from your enemies.',self._scoreToWin)\n\n    def getInstanceScoreBoardDescription(self):\n        return ('collect ${ARG1} capsules',self._scoreToWin)\n        \n    def onTeamJoin(self,team):\n        team.gameData['capsules'] = 0\n        self._updateScoreBoard()\n\n    def onPlayerJoin(self,player):\n        bs.TeamGameActivity.onPlayerJoin(self,player)\n        player.gameData['atFlag'] = 0\n        player.gameData['capsules'] = 0\n        \n    def spawnPlayer(self,player):\n        spaz = self.spawnPlayerSpaz(player)\n        spaz.connectControlsToPlayer()\n        player.gameData['capsules'] = 0\n        \n    def _tick(self):\n        self._updateFlagState()\n        scoringTeam = None if self._scoringTeam is None else self._scoringTeam()\n\n        # give holding players points\n        for player in self.players:\n            if player.gameData['atFlag'] > 0 and player.gameData['capsules'] > 0 and self._flagState == self.FLAG_HELD and scoringTeam.gameData['capsules'] < self._scoreToWin:\n                player.gameData['capsules'] -= 1\n                self._handleCapsuleStorage((self._flagPos[0],self._flagPos[1]+1,self._flagPos[2]),player)\n                self.scoreSet.playerScored(player,3,screenMessage=False,display=False)\n\n                if scoringTeam:\n        \n                    if scoringTeam.gameData['capsules'] < self._scoreToWin: bs.playSound(self._tickSound)\n        \n                    scoringTeam.gameData['capsules'] = max(0,scoringTeam.gameData['capsules']+1)\n                    self._updateScoreBoard()\n                    if scoringTeam.gameData['capsules'] > 0:\n                        self._flag.setScoreText(str(self._scoreToWin-scoringTeam.gameData['capsules']))\n        \n                    # winner\n                    if scoringTeam.gameData['capsules'] >= self._scoreToWin:\n                        self.endGame()\n                        \n                \n    def endGame(self):\n        results = bs.TeamGameResults()\n        for team in self.teams: results.setTeamScore(team,team.gameData['capsules'])\n        self.end(results=results,announceDelay=0)\n        \n    def _updateFlagState(self):\n        holdingTeams = set(player.getTeam() for player in self.players if player.gameData['atFlag'])\n        prevState = self._flagState\n        if len(holdingTeams) > 1:\n            self._flagState = self.FLAG_CONTESTED\n            self._scoringTeam = None\n            self._flagLight.color = (0.6,0.6,0.1)\n            self._flag.node.color = (1.0,1.0,0.4)\n        elif len(holdingTeams) == 1:\n            holdingTeam = list(holdingTeams)[0]\n            self._flagState = self.FLAG_HELD\n            self._scoringTeam = weakref.ref(holdingTeam)\n            self._flagLight.color = bs.getNormalizedColor(holdingTeam.color)\n            self._flag.node.color = holdingTeam.color\n        else:\n            self._flagState = self.FLAG_UNCONTESTED\n            self._scoringTeam = None\n            self._flagLight.color = (0.2,0.2,0.2)\n            self._flag.node.color = (1,1,1)\n        if self._flagState != prevState:\n            bs.playSound(self._swipSound)\n\n    def _handlePlayerFlagRegionCollide(self,colliding):\n        flagNode,playerNode = bs.getCollisionInfo(\"sourceNode\",\"opposingNode\")\n        try: player = playerNode.getDelegate().getPlayer()\n        except Exception: return\n\n        # different parts of us can collide so a single value isn't enough\n        # also don't count it if we're dead (flying heads shouldnt be able to win the game :-)\n        if colliding and player.isAlive(): player.gameData['atFlag'] += 1\n        else: player.gameData['atFlag'] = max(0,player.gameData['atFlag'] - 1)\n\n        self._updateFlagState()\n\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team,team.gameData['capsules'],self.settings['Capsules to Collect'],countdown=True)\n        \n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Scary')\n        \n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team,team.gameData['capsules'],self._scoreToWin)\n            \n    def _onCapsulePlayerCollide(self):\n        if not self.hasEnded():\n            capsuleNode, playerNode = bs.getCollisionInfo('sourceNode','opposingNode')\n            if capsuleNode is not None and playerNode is not None:\n                capsule = capsuleNode.getDelegate()\n                spaz = playerNode.getDelegate()\n                player = spaz.getPlayer() if hasattr(spaz,'getPlayer') else None\n                if player is not None and player.exists() and capsule is not None:\n                    if player.isAlive():\n                        if capsuleNode.colorTexture == self._capsuleLuckyTex:\n                            player.gameData['capsules'] += 4\n                            bsUtils.PopupText('BONUS!',\n                                              color=(1,1,0),\n                                              scale=1.5,\n                                              position=(capsuleNode.position)).autoRetain()\n                            bs.playSound(self._luckyCollectSound,1.0,position=capsuleNode.position)\n                            bs.emitBGDynamics(position=capsuleNode.position,velocity=(0,0,0),count=int(6.4+random.random()*24),scale=1.2, spread=2.0,chunkType='spark');\n                            bs.emitBGDynamics(position=capsuleNode.position,velocity=(0,0,0),count=int(4.0+random.random()*6),emitType='tendrils');\n                        else:\n                            player.gameData['capsules'] += 1\n                            bs.playSound(self._collectSound,0.6,position=capsuleNode.position)\n                        # create a flash\n                        light = bs.newNode('light',\n                                        attrs={'position': capsuleNode.position,\n                                                'heightAttenuated':False,\n                                                'radius':0.1,\n                                                'color':(1,1,0)})\n                        \n                        # Create a short text informing about your inventory\n                        self._handleCapsuleStorage(playerNode.position,player)\n                        \n                        bs.animate(light,'intensity',{0:0,100:0.5,200:0},loop=False)\n                        bs.gameTimer(200,light.delete)\n                        capsule.handleMessage(bs.DieMessage())\n                        \n    def _handleCapsuleStorage(self,pos,player):\n        self.capsules = player.gameData['capsules']\n        \n        if player.gameData['capsules'] > 10: \n            player.gameData['capsules'] = 10\n            self.capsules = 10\n            bsUtils.PopupText('Full Capacity!',\n                            color=(1,0.85,0),\n                            scale=1.75,\n                            position=(pos[0],pos[1]-1,pos[2])).autoRetain()\n        # Make a different color and size depending on the storage\n        if self.capsules > 7:\n            self.color = (1,0,0)\n            self.size = 2.4\n        elif self.capsules > 7:\n            self.color = (1,0.4,0.4)\n            self.size = 2.1\n        elif self.capsules > 4:\n            self.color = (1,1,0.4)\n            self.size = 2.0\n        else:\n            self.color = (1,1,1)\n            self.size = 1.9\n        if self.capsules < 10:\n            bsUtils.PopupText((str(player.gameData['capsules'])),\n                                            color=self.color,\n                                            scale=self.size+(0.02*self.capsules),\n                                            position=(pos[0],pos[1]-1,pos[2])).autoRetain()\n        \n    # various high-level game events come through this method\n    def handleMessage(self,m):\n\n        # respawn dead players\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior\n\n            player = m.spaz.getPlayer()\n            self.respawnPlayer(player) # Respawn the player\n            pt = m.spaz.node.position\n            \n            for i in range(player.gameData['capsules'] + self.settings['Capsules on Death']): # Throw out capsules that the victim has + 2 more to keep the game running\n                w = 0.6 # How far from each other these capsules should spawn\n                s = 0.005 - (player.gameData['capsules']*0.01) # How much these capsules should fly after spawning\n                self._capsules.append(Capsule(position=(pt[0]+random.uniform(-w,w),\n                                                pt[1]+0.75+random.uniform(-w,w),\n                                                pt[2]),\n                                                velocity=(random.uniform(-s,s),\n                                                random.uniform(-s,s),\n                                                random.uniform(-s,s)),\n                                                lucky=False))\n            if random.randint(1,12) == 1 and self.settings['Allow Lucky Capsules']:\n                w = 0.6 # How far from each other these capsules should spawn\n                s = 0.005 # How much these capsules should fly after spawning\n                self._capsules.append(Capsule(position=(pt[0]+random.uniform(-w,w),\n                                                pt[1]+0.75+random.uniform(-w,w),\n                                                pt[2]),\n                                                velocity=(random.uniform(-s,s),\n                                                random.uniform(-s,s),\n                                                random.uniform(-s,s)),\n                                                lucky=True))\n            player.gameData['atFlag'] = 0\n        else:\n            # default handler:\n            bs.TeamGameActivity.handleMessage(self,m)\n            \nclass Capsule(bs.Actor):\n\n    def __init__(self, position=(0,1,0), velocity=(0,0.5,0),lucky=False):\n        bs.Actor.__init__(self)\n        self._luckyAppearSound = bs.getSound('ding')\n        \n        activity = self.getActivity()\n        \n        # spawn just above the provided point\n        self._spawnPos = (position[0], position[1], position[2])\n        if lucky:\n            bs.playSound(self._luckyAppearSound,1.0,self._spawnPos)\n            self.capsule = bs.newNode(\"prop\",\n                                attrs={'model': activity._capsuleModel,\n                                        'colorTexture': activity._capsuleLuckyTex,\n                                        'body':'crate',\n                                        'reflection':'powerup',\n                                        'modelScale':0.8,\n                                        'bodyScale':0.65,\n                                        'density':6.0,\n                                        'reflectionScale':[0.15],\n                                        'shadowSize': 0.65,\n                                        'position':self._spawnPos,\n                                        'velocity':velocity,\n                                        'materials': [bs.getSharedObject('objectMaterial'),activity._capsuleMaterial]\n                                        },\n                                delegate=self)\n            bs.animate(self.capsule,\"modelScale\",{0:0, 100:0.9, 160:0.8})\n            self.lightCapsule = bs.newNode('light',\n                                            attrs={'position':self._spawnPos,\n                                                'heightAttenuated':False,\n                                                'radius':0.5,\n                                                'color':(0.2,0.2,0)})\n        else:\n            self.capsule = bs.newNode(\"prop\",\n                                attrs={'model': activity._capsuleModel,\n                                        'colorTexture': activity._capsuleTex,\n                                        'body':'capsule',\n                                        'reflection':'soft',\n                                        'modelScale':0.6,\n                                        'bodyScale':0.3,\n                                        'density':4.0,\n                                        'reflectionScale':[0.15],\n                                        'shadowSize': 0.6,\n                                        'position':self._spawnPos,\n                                        'velocity':velocity,\n                                        'materials': [bs.getSharedObject('objectMaterial'),activity._capsuleMaterial]\n                                        },\n                                delegate=self)\n            bs.animate(self.capsule,\"modelScale\",{0:0, 100:0.6, 160:0.5})\n            self.lightCapsule = bs.newNode('light',\n                                            attrs={'position':self._spawnPos,\n                                                'heightAttenuated':False,\n                                                'radius':0.1,\n                                                'color':(0.2,1,0.2)})\n        self.capsule.connectAttr('position',self.lightCapsule,'position')\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.DieMessage):\n            self.capsule.delete()\n            try:\n                bs.animate(self.lightCapsule,'intensity',{0:1.0,50:0.0},loop=False)\n                bs.gameTimer(50,self.lightCapsule.delete)\n            except AttributeError: pass\n        elif isinstance(m,bs.OutOfBoundsMessage):\n            self.handleMessage(bs.DieMessage())\n        elif isinstance(m,bs.HitMessage):\n            self.capsule.handleMessage(\"impulse\",m.pos[0],m.pos[1],m.pos[2],\n                                    m.velocity[0]/8,m.velocity[1]/8,m.velocity[2]/8,\n                                    1.0*m.magnitude,1.0*m.velocityMagnitude,m.radius,0,\n                                    m.forceDirection[0],m.forceDirection[1],m.forceDirection[2])\n        else:\n            bs.Actor.handleMessage(self,m)\n    \n"
  },
  {
    "path": "mods/FillErUp.json",
    "content": "{\n  \"name\": \"Fill 'Er Up\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/FillErUp.py",
    "content": "import bs\nimport bsSpaz\nimport random\nimport bsUtils\nimport bsPowerup\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [FillErUp]\n        \n        \nclass cargoBox(bs.Bomb):\n\n    def __init__(self,pos):\n        bs.Bomb.__init__(self,position=pos,bombType='tnt')\n        #self = box\n        self.node.maxSpeed = 0\n        self.node.damping = 100\n        #self.node.density = 10\n        pam = bs.Powerup.getFactory().powerupAcceptMaterial\n        nPum = self.getActivity().noPickMat\n        materials = getattr(self.node,'materials')\n        if not pam in materials:\n            setattr(self.node,'materials',materials + (pam,))\n        materials = getattr(self.node,'materials')\n        if not nPum in materials:\n            setattr(self.node,'materials',materials + (nPum,))\n    def handleMessage(self,m):\n        if isinstance(m, bs.HitMessage):\n            #We don't want crates taking damage.\n            return True\n        if isinstance(m, bs.PowerupMessage): #Give or take points, depending on powerup received.\n            for player in self.getActivity().players:\n                if player.gameData['crate'] == self:\n                    if m.powerupType == 'health':\n                        player.getTeam().gameData['score'] += 1\n                    else:\n                        player.getTeam().gameData['score'] += self.getActivity().settings['Curse Box Points']\n                    self.getActivity()._updateScoreBoard()\n                    self.setboxScoreText(str(player.getTeam().gameData['score']), player.color)\n            if m.sourceNode.exists():\n                m.sourceNode.handleMessage(bs.PowerupAcceptMessage())\n        else:\n            super(self.__class__, self).handleMessage(m)\n\n    def setboxScoreText(self,t,color=(1,1,0.4),flash=False):\n        \"\"\"\n        Utility func to show a message momentarily over our spaz that follows him around;\n        Handy for score updates and things.\n        \"\"\"\n        colorFin = bs.getSafeColor(color)[:3]\n        if not self.node.exists(): return\n        try: exists = self._scoreText.exists()\n        except Exception: exists = False\n        if not exists:\n            startScale = 0.0\n            m = bs.newNode('math',owner=self.node,attrs={'input1':(0,1.4,0),'operation':'add'})\n            self.node.connectAttr('position',m,'input2')\n            self._scoreText = bs.newNode('text',\n                                          owner=self.node,\n                                          attrs={'text':t,\n                                                 'inWorld':True,\n                                                 'shadow':1.0,\n                                                 'flatness':1.0,\n                                                 'color':colorFin,\n                                                 'scale':0.02,\n                                                 'hAlign':'center'})\n            m.connectAttr('output',self._scoreText,'position')\n        else:\n            self._scoreText.color = colorFin\n            startScale = self._scoreText.scale\n            self._scoreText.text = t\n        if flash:\n            combine = bs.newNode(\"combine\",owner=self._scoreText,attrs={'size':3})\n            sc = 1.8\n            offs = 0.5\n            t = 300\n            for i in range(3):\n                c1 = offs+sc*colorFin[i]\n                c2 = colorFin[i]\n                bs.animate(combine,'input'+str(i),{0.5*t:c2,\n                                                   0.75*t:c1,\n                                                   1.0*t:c2})\n            combine.connectAttr('output',self._scoreText,'color')\n            \n        bs.animate(self._scoreText,'scale',{0:startScale,200:0.02})\n        #self._scoreTextHideTimer = bs.Timer(1000,bs.WeakCall(self._hideScoreText))\n        \n    def setMovingText(self, theActor, theText, color):\n        m = bs.newNode('math', owner=theActor.node, attrs={'input1': (0, 0.7, 0), 'operation': 'add'})\n        theActor.node.connectAttr('position', m, 'input2')\n        theActor._movingText = bs.newNode('text',\n                                      owner=theActor.node,\n                                      attrs={'text':theText,\n                                             'inWorld':True,\n                                             'shadow':1.0,\n                                             'flatness':1.0,\n                                             'color':color,\n                                             'scale':0.0,\n                                             'hAlign':'center'})\n        m.connectAttr('output', theActor._movingText, 'position')\n        bs.animate(theActor._movingText, 'scale', {0: 0.0, 1000: 0.01})                      \n        \nclass FillErUp(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Fill \\'Er Up'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'score',\n                'scoreType':'points',\n                'noneIsWinner':False,\n                'lowerIsBetter':False}\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Fill your crate with boxes'\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Doom Shroom','Courtyard']\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Time Limit\",{'choices':[('30 Seconds',30),('1 Minute',60),\n                                            ('90 Seconds',90),('2 Minutes',120),\n                                            ('3 Minutes',180),('5 Minutes',300)],'default':60}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Curse Box Chance (lower = more chance)\",{'default':10,'minValue':5,'maxValue':15,'increment':1}),\n                    (\"Curse Box Points\",{'default':-2,'minValue':-10,'maxValue':-1,'increment':1}),\n                    (\"Boxes Per Player\",{'default':1.0,'minValue':0.5,'maxValue':3.0,'increment':0.5}),\n                    (\"Epic Mode\",{'default':False})]\n\n        if issubclass(sessionType,bs.TeamsSession):\n            settings.append((\"Solo Mode\",{'default':False}))\n            settings.append((\"Balance Total Lives\",{'default':False}))\n            \n        return settings\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # show messages when players die since it's meaningful here\n        self.announcePlayerDeaths = True\n        \n        try: self._soloMode = settings['Solo Mode']\n        except Exception: self._soloMode = False\n        self._scoreBoard = bs.ScoreBoard()\n        self.totBoxes = []\n        \n        #Create a special powerup material for our boxes that allows pickup.\n        self.fpowerupMaterial = bs.Material()\n\n        # pass a powerup-touched message to applicable stuff\n        pam = bs.Powerup.getFactory().powerupAcceptMaterial\n        self.fpowerupMaterial.addActions(\n            conditions=((\"theyHaveMaterial\",pam)),\n            actions=((\"modifyPartCollision\",\"collide\",True),\n                     (\"modifyPartCollision\",\"physical\",False),\n                     (\"message\",\"ourNode\",\"atConnect\",bsPowerup._TouchedMessage())))\n\n        # we DO wanna be picked up\n        #self.powerupMaterial.addActions(\n        #    conditions=(\"theyHaveMaterial\",bs.getSharedObject('pickupMaterial')),\n        #    actions=( (\"modifyPartCollision\",\"collide\",False)))\n\n        self.fpowerupMaterial.addActions(\n            conditions=(\"theyHaveMaterial\",bs.getSharedObject('footingMaterial')),\n            actions=((\"impactSound\",bs.Powerup.getFactory().dropSound,0.5,0.1)))\n        \n        #Create a material to prevent TNT box pickup\n        self.noPickMat = bs.Material()\n        self.noPickMat.addActions(\n            conditions=(\"theyHaveMaterial\",bs.getSharedObject('pickupMaterial')),\n            actions=( (\"modifyPartCollision\",\"collide\",False)))\n\n    def getInstanceDescription(self):\n        return 'Steal all the health boxes for yourself' if isinstance(self.getSession(),bs.TeamsSession) else 'Fill your crate with boxes'\n\n    def getInstanceScoreBoardDescription(self):\n        return 'Steal all the health boxes for yourself' if isinstance(self.getSession(),bs.TeamsSession) else 'Fill your crate with boxes'\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n        self._startGameTime = bs.getGameTime()\n\n    def onTeamJoin(self,team):\n        team.gameData['survivalSeconds'] = None\n        team.gameData['spawnOrder'] = []\n        team.gameData['score'] = 0\n\n\n    def onPlayerJoin(self, player):\n\n        #player.gameData['lives'] = 1\n        player.getTeam().gameData['score'] = 0\n        player.gameData['home'] = None\n        player.gameData['crate'] = None\n        self._updateScoreBoard()\n        \n        if self._soloMode:\n            #player.gameData['icons'] = []\n            player.getTeam().gameData['spawnOrder'].append(player)\n            self._updateSoloMode()\n        else:\n            # create our icon and spawn\n            #player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)]\n            self.spawnPlayer(player)\n\n        # dont waste time doing this until begin\n        if self.hasBegun():\n            pass#self._updateIcons()\n\n    def _updateSoloMode(self):\n        # for both teams, find the first player on the spawn order list with lives remaining\n        # and spawn them if they're not alive\n        for team in self.teams:\n            # prune dead players from the spawn order\n            team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n            for player in team.gameData['spawnOrder']:\n                if player.gameData['lives'] > 0:\n                    if not player.isAlive(): self.spawnPlayer(player)\n                    break\n\n    def _getSpawnPoint(self,player):\n        # in solo-mode, if there's an existing live player on the map, spawn at whichever\n        # spot is farthest from them (keeps the action spread out)\n        if self._soloMode:\n            livingPlayer = None\n            for team in self.teams:\n                for player in team.players:\n                    if player.isAlive():\n                        p = player.actor.node.position\n                        livingPlayer = player\n                        livingPlayerPos = p\n                        break\n            if livingPlayer:\n                playerPos = bs.Vector(*livingPlayerPos)\n                points = []\n                for team in self.teams:\n                    startPos = bs.Vector(*self.getMap().getStartPosition(team.getID()))\n                    points.append([(startPos-playerPos).length(),startPos])\n                points.sort()\n                return points[-1][1]\n            else:\n                return None\n        else:\n            return None\n\n    def spawnPlayer(self,player):\n        \"\"\"\n        Spawn *something* for the provided bs.Player.\n        The default implementation simply calls spawnPlayerSpaz().\n        \"\"\"\n        #Overloaded for this game to respawn at home instead of random FFA spots\n        if not player.exists():\n            bs.printError('spawnPlayer() called for nonexistant player')\n            return\n        if player.gameData['home'] is None:\n            pos = self.getMap().getFFAStartPosition(self.players)\n            if player.gameData['crate'] is None:\n                box = cargoBox(pos)\n                box.setMovingText(box,player.getName(),player.color)\n                player.gameData['crate'] = box\n            position = [pos[0],pos[1]+1.0,pos[2]]\n            player.gameData['home'] = position\n        else:\n            position = player.gameData['home']\n        spaz = self.spawnPlayerSpaz(player, position)\n        #Need to prevent accepting powerups:\n        pam = bs.Powerup.getFactory().powerupAcceptMaterial\n        for attr in ['materials','rollerMaterials','extrasMaterials']:\n                        materials = getattr(spaz.node,attr)\n                        if pam in materials:\n                            setattr(spaz.node,attr,tuple(m for m in materials if m != pam))\n        return spaz\n        \n\n\n    def _printLives(self,player):\n        if not player.exists() or not player.isAlive(): return\n        try: pos = player.actor.node.position\n        except Exception,e:\n            print 'EXC getting player pos in bsElim',e\n            return\n        bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1),\n                           offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain()\n\n    def onPlayerLeave(self,player):\n\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n\n        #player.gameData['icons'] = None\n        player.gameData['score'] = 0\n        if player.gameData['crate'].exists():\n            player.gameData['crate'].handleMessage(bs.DieMessage(immediate=True))\n        # remove us from spawn-order\n        if self._soloMode:\n            if player in player.getTeam().gameData['spawnOrder']:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        #self.setupStandardPowerupDrops()\n\n        if self._soloMode:\n            self._vsText = bs.NodeActor(bs.newNode(\"text\",\n                                                   attrs={'position':(0,105),\n                                                          'hAttach':\"center\",\n                                                          'hAlign':'center',\n                                                          'maxWidth':200,\n                                                          'shadow':0.5,\n                                                          'vrDepth':390,\n                                                          'scale':0.6,\n                                                          'vAttach':\"bottom\",\n                                                          'color':(0.8,0.8,0.3,1.0),\n                                                          'text':bs.Lstr(resource='vsText')}))\n\n        # if balance-team-lives is on, add lives to the smaller team until total lives match\n        if (isinstance(self.getSession(),bs.TeamsSession)\n            and self.settings['Balance Total Lives']\n            and len(self.teams[0].players) > 0\n            and len(self.teams[1].players) > 0):\n            if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]):\n                lesserTeam = self.teams[0]\n                greaterTeam = self.teams[1]\n            else:\n                lesserTeam = self.teams[1]\n                greaterTeam = self.teams[0]\n            addIndex = 0\n            while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam):\n                lesserTeam.players[addIndex].gameData['lives'] += 1\n                addIndex = (addIndex + 1) % len(lesserTeam.players)\n\n        #self._updateIcons()\n\n        # we could check game-over conditions at explicit trigger points,\n        # but lets just do the simple thing and poll it...\n        bs.gameTimer(1000, self._update, repeat=True)\n        \n    def _getTotalTeamLives(self,team):\n        return sum(player.gameData['lives'] for player in team.players)\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            \n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n            player = m.spaz.getPlayer()\n\n            self.respawnPlayer(player)\n\n            # in solo, put ourself at the back of the spawn order\n            if self._soloMode:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n                player.getTeam().gameData['spawnOrder'].append(player)\n        else:\n            bs.TeamGameActivity.handleMessage(self, m)\n    def _update(self):\n\n        if self._soloMode:\n            # for both teams, find the first player on the spawn order list with lives remaining\n            # and spawn them if they're not alive\n            for team in self.teams:\n                # prune dead players from the spawn order\n                team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n                for player in team.gameData['spawnOrder']:\n                    if player.gameData['lives'] > 0:\n                        if not player.isAlive():\n                            self.spawnPlayer(player)\n                            #self._updateIcons()\n                        break\n        \n        # if we're down to 1 or fewer living teams, start a timer to end the game\n        # (allows the dust to settle and draws to occur if deaths are close enough)\n        self.boxSpawn()\n\n    def boxSpawn(self):\n        Plyrs = 0\n        for team in self.teams:\n            for player in team.players:\n                Plyrs += 1\n                    \n        maxBoxes = int(Plyrs * self.settings[\"Boxes Per Player\"])\n        if maxBoxes > 16:\n            maxBoxes = 16\n        elif maxBoxes < 1:\n            maxBoxes = 1\n        for box in self.totBoxes:\n            if not box.exists():\n                self.totBoxes.remove(box)\n        while len(self.totBoxes) < maxBoxes:\n            #print([Plyrs, self.boxMult,len(self.totBoxes), maxBoxes])\n            if random.randint(1,self.settings[\"Curse Box Chance (lower = more chance)\"]) == 1:\n                type = 'curse'\n            else:\n                type = 'health'\n            box = bsPowerup.Powerup(position=self.getRandomPowerupPoint(), powerupType=type,expire=False).autoRetain()\n            #we have to remove the default powerup material because it doesn't allow for pickups.\n            #Then we add our own powerup material.\n            pm = box.getFactory().powerupMaterial\n            materials = getattr(box.node,'materials')\n            if pm in materials:\n                setattr(box.node,'materials',tuple(m for m in materials if m != pm))\n            materials = getattr(box.node,'materials')\n            if not self.fpowerupMaterial in materials:\n                setattr(box.node,'materials',materials + (self.fpowerupMaterial,))\n            self.totBoxes.append(box)\n            \n        #self.boxMult -= self.settings[\"Box Reduction Rate\"]\n        \n    def getRandomPowerupPoint(self):\n        myMap = self.getMap().getName()\n        #print(myMap)\n        if myMap == 'Doom Shroom':\n            while True:\n                x = random.uniform(-1.0,1.0)\n                y = random.uniform(-1.0,1.0)\n                if x*x+y*y < 1.0: break\n            return ((5.0*x,4.0,-3.5+3.0*y))\n        elif myMap == 'Courtyard':\n            x = random.uniform(-3.3,3.3)\n            y = random.uniform(-3.9,-0.2)\n            return ((x, 4.0, y))\n        else:\n            x = random.uniform(-5.0,5.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 8.0, y))\n    def _getLivingTeams(self):\n        return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)]\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team, team.gameData['score'])\n    def endGame(self):\n        if self.hasEnded(): return\n        results = bs.TeamGameResults()\n        self._vsText = None # kill our 'vs' if its there\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['score'])\n        self.end(results=results)\n        \n"
  },
  {
    "path": "mods/FlagDay.py",
    "content": "#FlagDay\nimport bs\nimport random\nimport bsUtils\nimport math\n\n# http://www.froemling.net/docs/bombsquad-python-api\n#if you really want in-depth explanations of specific terms, go here ^\n\n\n# fixing random generation of players in setupNextRound\n\nclass FlagBearer(bs.PlayerSpaz):\n    def handleMessage(self, m):\n        bs.PlayerSpaz.handleMessage(self, m)\n        if isinstance(m, bs.PowerupMessage):\n            if self.getActivity().lastPrize == 'curse':\n                self.getPlayer().getTeam().gameData['score'] += 25\n                self.getActivity().updateScore()\n            elif self.getActivity().lastPrize == 'landmines':\n                self.getPlayer().getTeam().gameData['score'] += 15\n                self.getActivity().updateScore()\n                self.connectControlsToPlayer()\n            elif self.getActivity().lastPrize == 'climb':\n                self.getPlayer().getTeam().gameData['score'] += 50\n                self.getActivity().updateScore()\n\n#This gives the API version to the game to make sure that we are using the right vocabulary\ndef bsGetAPIVersion():\n    return 4\n\n#This tells the game what kind of program this is\ndef bsGetGames():\n    return [FlagDay]\n\n#this gives the game a unique code for our game in this case: \"NewGame 124\" (One of my other games was NewGame123) P.S. Don't change this half-way through making it\ndef bsGetLevels():\n    return [bs.Level('FlagDay45986',\n                     displayName='${GAME}',\n                     gameType=FlagDay,\n                     settings={},\n                     previewTexName='courtyardPreview')]\n\n#this is the class that will actually be saved to the game as a mini-game\nclass FlagDay(bs.TeamGameActivity):\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n\n#gives it a name\n    @classmethod\n    def getName(cls):\n        return 'Flag Day'\n\n#Gives it how things are scored\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreType':'points'}\n\n#Gives a description of the game\n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Pick up flags to receive a prize.\\nBut beware...'\n\n#Gives which maps are supported, in this case only courtyard though you could probably try it with others, too\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Courtyard']\n\n#Tells the game what kinds of seesions are supported by this mini-game\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if issubclass(sessionType,bs.FreeForAllSession) or issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.CoopSession) else False\n\n#Tells the game what to do on the transition in\n    def onTransitionIn(self):\n        #Sets the music to \"To the Death\"\n        bs.TeamGameActivity.onTransitionIn(self,music='ToTheDeath')\n\n    def onPlayerJoin(self, player):\n        player.getTeam().gameData['score'] = 0\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText', subs=[('${PLAYER}', player.getName(full=True))]),\n                             color=(0, 1, 0))\n\n    def onPlayerLeave(self, player):\n        if player is self.currentPlayer:\n            self.setupNextRound()\n        self.checkEnd()\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n        self.queueLine.remove(player)\n\n    def onBegin(self):\n        self.bombSurvivor = None\n        self.light = None\n        self.set = False\n        #Do normal stuff: calls to the main class to operate everything that usually would be done\n        bs.TeamGameActivity.onBegin(self)\n        self.b = []\n        self.queueLine = []\n        self.playerIndex = 0\n        for player in self.players:\n            player.gameData['dead'] = False\n            if player.actor is not None:\n                player.actor.handleMessage(bs.DieMessage())\n                player.actor.node.delete()\n            self.queueLine.append(player)\n        self.spawnPlayerSpaz(self.queueLine[self.playerIndex%len(self.queueLine)],(0,3,-2))\n        self.lastPrize = 'none'\n        self.currentPlayer = self.queueLine[0]\n        #Declare a set of bots (enemies) that we will use later\n        self._bots = bs.BotSet()\n        #make another scoreboard? IDK why I did this, probably to make it easier to refer to in the future\n        self._scoredis = bs.ScoreBoard()\n        #for each team in the game's directory, give them a score of zero\n        for team in self.teams:\n            team.gameData['score'] = 0\n        #Now we go ahead and put that on the scoreboard\n        for player in self.queueLine:\n            self._scoredis.setTeamValue(player.getTeam(),player.getTeam().gameData['score'])\n        self.resetFlags()\n        \n        \n\n#This handles all the messages that the game throws at us\n    def handleMessage(self,m):\n        #If it's a flag picked up...\n        if isinstance(m,bs.FlagPickedUpMessage):\n            #Get the last player to hold that flag\n            m.flag._lastPlayerToHold = m.node.getDelegate().getPlayer()\n            #Get the last actor to hold that flag (If you are a player, then your body is the actor, think of it like that)\n            self._player = m.node.getDelegate()\n            #The person to last hold a flag gets the prize, not the person to hold that flag, note.\n            self._prizeRecipient = m.node.getDelegate().getPlayer()\n            #Call a method to kill the flags\n            self.killFlags()\n            self.givePrize(random.randint(1,8))\n            self.currentPlayer = self._prizeRecipient\n        #If a player died...\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            #give them a nice farewell\n            if bs.getGameTime() < 500: return\n            if m.how == 'game': return\n            guy = m.spaz.getPlayer()\n            bs.screenMessage(str(guy.getName()) + \" died!\",color=guy.color)\n            guy.gameData['dead'] = True\n            if guy is self.currentPlayer:\n                self.setupNextRound()\n            #check to see if we can end the game\n            self.checkEnd()\n\n        #If a bot died...\n        if isinstance(m,bs.SpazBotDeathMessage):\n            #find out which team the last person to hold a flag was on\n            team = self._prizeRecipient.getTeam()\n            #give them their points\n            team.gameData['score'] += self._badGuyCost\n            #update the scores\n            for team in self.teams:\n                self._scoredis.setTeamValue(team,team.gameData['score'])\n            bs.gameTimer(300,self.checkBots)\n\n    def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None):\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n\n        lightColor = bsUtils.getNormalizedColor(color)\n        displayColor = bs.getSafeColor(color,targetIntensity=0.75)\n        spaz = FlagBearer(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n        if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer()\n        spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound,1,position=spaz.node.position)\n        light = bs.newNode('light',attrs={'color':lightColor})\n        spaz.node.connectAttr('position',light,'position')\n        bsUtils.animate(light,'intensity',{0:0,250:1,500:0})\n        bs.gameTimer(500,light.delete)\n        return spaz\n\n\n\n#a method to remake the flags\n    def resetFlags(self):\n        #remake the flags\n        self._flag1 = bs.Flag(position=(0,3,1),touchable=True,color=(0,0,1))\n        self._flag2 = bs.Flag(position=(0,3,-5),touchable=True,color=(1,0,0))\n        self._flag3 = bs.Flag(position=(3,3,-2),touchable=True,color=(0,1,0))\n        self._flag4 = bs.Flag(position=(-3,3,-2),touchable=True,color=(1,1,1))\n        self._flag5 = bs.Flag(position=(1.8,3,.2),touchable=True,color=(0,1,1))\n        self._flag6 = bs.Flag(position=(-1.8,3,.2),touchable=True,color=(1,0,1))\n        self._flag7 = bs.Flag(position=(1.8,3,-3.8),touchable=True,color=(1,1,0))\n        self._flag8 = bs.Flag(position=(-1.8,3,-3.8),touchable=True,color=(0,0,0))\n\n#a method to kill the flags\n    def killFlags(self):\n        #destroy all the flags by erasing all references to them, indicated by None similar to null\n        self._flag1 = None\n        self._flag2 = None\n        self._flag3 = None\n        self._flag4 = None\n        self._flag5 = None # 132, 210 ,12\n        self._flag6 = None\n        self._flag7 = None\n        self._flag8 = None\n\n    def setupNextRound(self):\n        if self.light is not None: self.light.delete()\n        for bomb in self.b:\n            bomb.handleMessage(bs.DieMessage())\n        self.killFlags()\n        self._bots.clear()\n        self.resetFlags()\n        self.currentPlayer.actor.handleMessage(bs.DieMessage(how='game'))\n        self.currentPlayer.actor.node.delete()\n        c = 0\n        self.playerIndex += 1\n        self.playerIndex %= len(self.queueLine)\n        if len(self.queueLine) > 0:\n            while self.queueLine[self.playerIndex].gameData['dead']:\n                if c > len(self.queueLine): return\n                self.playerIndex += 1\n                self.playerIndex %= len(self.queueLine)\n                c += 1\n            self.spawnPlayerSpaz(self.queueLine[self.playerIndex],(0,3,-2))\n            self.currentPlayer = self.queueLine[self.playerIndex]\n        self.lastPrize = 'none'\n                \n        \n\n#a method to give the prize recipient a prize depending on what flag he took (not really).\n    def givePrize(self, prize):\n        if prize == 1:\n            #Curse him aka make him blow up in 5 seconds\n            #give them a nice message\n            bs.screenMessage(\"You were\", color=(1,0,0))\n            bs.screenMessage(\"CURSED\", color=(.1,.1,.1))\n            self.makeHealthBox((0,0,0))\n            self.lastPrize = 'curse'\n            self._prizeRecipient.actor.curse()\n            bs.gameTimer(5500,self.setupNextRound)\n        if prize == 2:\n            self.setupROF()\n            bs.screenMessage(\"RUN\", color=(1,.2,.1))\n            self.lastPrize = 'ringoffire'\n        if prize == 3:\n            self.lastPrize = 'climb'\n            self.light =bs.newNode('locator',attrs={'shape':'circle','position':(0,3,-9),\n                                    'color':(1,1,1),'opacity':1,\n                                    'drawBeauty':True,'additive':True})\n            bs.screenMessage(\"Climb to the top\",color=(.5,.5,.5))\n            bs.gameTimer(3000, bs.Call(self.makeHealthBox,(0,6,-9)))\n            bs.gameTimer(10000, self.setupNextRound)\n        if prize == 4:\n            self.lastPrize = 'landmines'\n            self.makeHealthBox((6,5,-2))\n            self.makeLandMines()\n            self._prizeRecipient.actor.node.getDelegate().connectControlsToPlayer(enableBomb=False)\n            self._prizeRecipient.actor.node.handleMessage(bs.StandMessage(position=(-6,3,-2)))\n            bs.gameTimer(7000,self.setupNextRound)\n        if prize == 5:\n            #Make it rain bombs\n            self.bombSurvivor = self._prizeRecipient\n            bs.screenMessage(\"BOMB RAIN!\", color=(1,.5,.16))\n            #Set positions for the bombs to drop\n            for bzz in range(-5,6):\n                for azz in range(-5,2):\n                    #for each position make a bomb drop there\n                    self.makeBomb(bzz,azz)\n            bs.gameTimer(3300,self.givePoints)\n            self.lastPrize = 'bombrain'\n        if prize == 6:\n            self.setupBR()\n            self.bombSurvivor = self._prizeRecipient\n            bs.gameTimer(7000,self.givePoints)\n            self.lastPrize = 'bombroad'\n        if prize == 7:\n            #makes killing a bad guy worth ten points\n            self._badGuyCost = 2\n            bs.screenMessage(\"Lame Guys\", color=(1,.5,.16))\n            #makes a set of nine positions\n            for a in range(-1,2):\n                for b in range(-3,0):\n                    #and spawns one in each position\n                    self._bots.spawnBot(bs.ToughGuyBotLame,pos=(a,2.5,b))\n                    #and we give our player boxing gloves and a shield\n            self._player.equipBoxingGloves()\n            self._player.equipShields()\n            self.lastPrize = 'lameguys'\n        if prize == 8:\n            bs.screenMessage(\"!JACKPOT!\", color=(1,0,0))\n            bs.screenMessage(\"!JACKPOT!\", color=(0,1,0))\n            bs.screenMessage(\"!JACKPOT!\", color=(0,0,1))\n            team = self._prizeRecipient.getTeam()\n            #GIVE THEM A WHOPPING 50 POINTS!!!\n            team.gameData['score'] += 50\n            # and update the scores\n            self.updateScore()\n            self.lastPrize = 'jackpot'\n            bs.gameTimer(2000,self.setupNextRound)\n\n    def updateScore(self):\n        for player in self.queueLine:\n                self._scoredis.setTeamValue(player.getTeam(),player.getTeam().gameData['score'])\n\n    def checkBots(self):\n        if not self._bots.haveLivingBots():\n            self.setupNextRound()\n\n    def makeLandMines(self):\n        self.b = []\n        for i in range(-11,7):\n            self.b.append(bs.Bomb(position=(0, 6, i/2.0), bombType='landMine', blastRadius=2.0))\n            self.b[i+10].arm()\n\n    def givePoints(self):\n        if self.bombSurvivor is not None and self.bombSurvivor.isAlive():\n            self.bombSurvivor.getTeam().gameData['score'] += 20\n            self.updateScore()\n\n    def makeHealthBox(self, position=(0,3,0)):\n        if position == (0,3,0):\n            position = (random.randint(-6,6),6,random.randint(-6,4))\n        elif position == (0,0,0):\n            position = random.choice(((-7,6,-5),(7,6,-5),(-7,6,1),(7,6,1)))\n        self.healthBox = bs.Powerup(position=position,powerupType='health').autoRetain()\n\n#called in prize #5\n    def makeBomb(self,xpos,zpos):\n        #makes a bomb at the given position then auto-retains it aka: makes sure it doesn't disappear because there is no reference to it\n        b=bs.Bomb(position=(xpos, 12, zpos)).autoRetain()\n\n    def setupBR(self):\n        self.makeBombRow(6)\n        self._prizeRecipient.actor.handleMessage(bs.StandMessage(position=(6,3,-2)))\n\n    def makeBombRow(self, num):\n        if num == 0:\n            bs.gameTimer(1000, self.setupNextRound)\n            return\n        for i in range(-11,7):\n            self.b.append(bs.Bomb(position=(-3, 3, i/2.0), velocity=(12,0,0),bombType='normal', blastRadius=1.2))\n        if self._prizeRecipient.isAlive(): bs.gameTimer(1000,bs.Call(self.makeBombRow,num-1))\n        else: self.setupNextRound()\n\n    def setupROF(self):\n        self.makeBlastRing(10)\n        self._prizeRecipient.actor.handleMessage(bs.StandMessage(position=(0,3,-2)))\n\n    def makeBlastRing(self,length):\n        if length == 0:\n            self.setupNextRound()\n            self._prizeRecipient.getTeam().gameData['score'] += 50\n            self.updateScore()\n            return\n        for angle in range(0,360,45):\n            angle += random.randint(0,45)\n            angle %= 360\n            x = length * math.cos(math.radians(angle))\n            z = length * math.sin(math.radians(angle))\n            blast = bs.Blast(position=(x,2.2,z-2),blastRadius=3.5)\n        if self._prizeRecipient.isAlive(): bs.gameTimer(750,bs.Call(self.makeBlastRing,length-1))\n        else: self.setupNextRound()\n\n#checks to see if we should end the game\n    def checkEnd(self):\n        for player in self.queueLine:\n            if not player.gameData['dead']: return\n        self.endGame()\n\n#called when ready to end the game\n    def endGame(self):\n        if self.set == True: return\n        self.set = True\n        results = bs.TeamGameResults()\n        #Set the results for the game to display at the end of the game\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['score'])\n        self.end(results=results)\n"
  },
  {
    "path": "mods/GravityFalls.py",
    "content": "import bs\nimport bsUtils\nimport random\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [GravityFalls]\n\nclass FlyMessage(object):\n    pass\n\nclass Icon(bs.Actor):\n        \n    def __init__(self,player,position,scale,showLives=True,showDeath=True,\n                 nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0):\n        bs.Actor.__init__(self)\n\n        self._player = player\n        self._showLives = showLives\n        self._showDeath = showDeath\n        self._nameScale = nameScale\n\n        self._outlineTex = bs.getTexture('characterIconMask')\n        \n        icon = player.getIcon()\n        self.node = bs.newNode('image',\n                               owner=self,\n                               attrs={'texture':icon['texture'],\n                                      'tintTexture':icon['tintTexture'],\n                                      'tintColor':icon['tintColor'],\n                                      'vrDepth':400,\n                                      'tint2Color':icon['tint2Color'],\n                                      'maskTexture':self._outlineTex,\n                                      'opacity':1.0,\n                                      'absoluteScale':True,\n                                      'attach':'bottomCenter'})\n        self._nameText = bs.newNode('text',\n                                    owner=self.node,\n                                    attrs={'text':bs.Lstr(value=player.getName()),\n                                           'color':bs.getSafeColor(player.getTeam().color),\n                                           'hAlign':'center',\n                                           'vAlign':'center',\n                                           'vrDepth':410,\n                                           'maxWidth':nameMaxWidth,\n                                           'shadow':shadow,\n                                           'flatness':flatness,\n                                           'hAttach':'center',\n                                           'vAttach':'bottom'})\n        if self._showLives:\n            self._livesText = bs.newNode('text',\n                                         owner=self.node,\n                                         attrs={'text':'x0',\n                                                'color':(1,1,0.5),\n                                                'hAlign':'left',\n                                                'vrDepth':430,\n                                                'shadow':1.0,\n                                                'flatness':1.0,\n                                                'hAttach':'center',\n                                                'vAttach':'bottom'})\n        self.setPositionAndScale(position,scale)\n\n    def setPositionAndScale(self,position,scale):\n        self.node.position = position\n        self.node.scale = [70.0*scale]\n        self._nameText.position = (position[0],position[1]+scale*52.0)\n        self._nameText.scale = 1.0*scale*self._nameScale\n        if self._showLives:\n            self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0)\n            self._livesText.scale = 1.0*scale\n\n    def updateForLives(self):\n        if self._player.exists():\n            lives = self._player.gameData['lives']\n        else: lives = 0\n        if self._showLives:\n            if lives > 0: self._livesText.text = 'x'+str(lives-1)\n            else: self._livesText.text = ''\n        if lives == 0:\n            self._nameText.opacity = 0.2\n            self.node.color = (0.7,0.3,0.3)\n            self.node.opacity = 0.2\n        \n    def handlePlayerSpawned(self):\n        if not self.node.exists(): return\n        self.node.opacity = 1.0\n        self.updateForLives()\n\n    def handlePlayerDied(self):\n        if not self.node.exists(): return\n        if self._showDeath:\n            bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0,\n                                            300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2})\n            lives = self._player.gameData['lives']\n            if lives == 0: bs.gameTimer(600,self.updateForLives)\n\nclass AntiGravityPlayerSpaz(bs.PlayerSpaz):\n    def handleMessage(self,m):\n        if isinstance(m, FlyMessage):\n            try:\n                self.node.handleMessage(\"impulse\",self.node.position[0],self.node.position[1]+.5,self.node.position[2],0,5,0,\n                                        3,10,0,0,\n                                        0,5,0)\n            except Exception: pass\n                \n        else: bs.PlayerSpaz.handleMessage(self,m)\n\nclass GravityFalls(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Gravity Falls'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Survived',\n                'scoreType':'seconds',\n                'noneIsWinner':True}\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Last remaining alive wins.'\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Lives Per Player\",{'default':1,'minValue':1,'maxValue':10,'increment':1}),\n                    (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                            ('2 Minutes',120),('5 Minutes',300),\n                                            ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Epic Mode\",{'default':False})]\n\n        if issubclass(sessionType,bs.TeamsSession):\n            settings.append((\"Solo Mode\",{'default':False}))\n            settings.append((\"Balance Total Lives\",{'default':False}))\n            \n        return settings\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n        self.announcePlayerDeaths = True\n        \n        try: self._soloMode = settings['Solo Mode']\n        except Exception: self._soloMode = False\n        self._scoreBoard = bs.ScoreBoard()\n\n    def getInstanceDescription(self):\n        return 'Last team standing wins.' if isinstance(self.getSession(),bs.TeamsSession) else 'Last one standing wins.'\n\n    def getInstanceScoreBoardDescription(self):\n        return 'last team standing wins' if isinstance(self.getSession(),bs.TeamsSession) else 'last one standing wins'\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n        self._startGameTime = bs.getGameTime()\n\n    def onTeamJoin(self,team):\n        team.gameData['survivalSeconds'] = None\n        team.gameData['spawnOrder'] = []\n\n    def _updateSoloMode(self):\n        # for both teams, find the first player on the spawn order list with lives remaining\n        # and spawn them if they're not alive\n        for team in self.teams:\n            # prune dead players from the spawn order\n            team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n            for player in team.gameData['spawnOrder']:\n                if player.gameData['lives'] > 0:\n                    if not player.isAlive(): self.spawnPlayer(player)\n                    break\n\n    def _updateIcons(self):\n        # in free-for-all mode, everyone is just lined up along the bottom\n        if isinstance(self.getSession(),bs.FreeForAllSession):\n            count = len(self.teams)\n            xOffs = 85\n            x = xOffs*(count-1) * -0.5\n            for i,team in enumerate(self.teams):\n                if len(team.players) == 1:\n                    player = team.players[0]\n                    for icon in player.gameData['icons']:\n                        icon.setPositionAndScale((x,30),0.7)\n                        icon.updateForLives()\n                    x += xOffs\n\n        # in teams mode we split up teams\n        else:\n            if self._soloMode:\n                # first off, clear out all icons\n                for player in self.players:\n                    player.gameData['icons'] = []\n                # now for each team, cycle through our available players adding icons\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -60\n                        xOffs = -78\n                    else:\n                        x = 60\n                        xOffs = 78\n                    isFirst = True\n                    testLives = 1\n                    while True:\n                        playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives]\n                        if len(playersWithLives) == 0: break\n                        for player in playersWithLives:\n                            player.gameData['icons'].append(Icon(player,\n                                                                 position=(x,(40 if isFirst else 25)),\n                                                                 scale=1.0 if isFirst else 0.5,\n                                                                 nameMaxWidth=130 if isFirst else 75,\n                                                                 nameScale=0.8 if isFirst else 1.0,\n                                                                 flatness=0.0 if isFirst else 1.0,\n                                                                 shadow=0.5 if isFirst else 1.0,\n                                                                 showDeath=True if isFirst else False,\n                                                                 showLives=False))\n                            x += xOffs * (0.8 if isFirst else 0.56)\n                            isFirst = False\n                        testLives += 1\n            # non-solo mode\n            else:\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -50\n                        xOffs = -85\n                    else:\n                        x = 50\n                        xOffs = 85\n                    for player in team.players:\n                        for icon in player.gameData['icons']:\n                            icon.setPositionAndScale((x,30),0.7)\n                            icon.updateForLives()\n                        x += xOffs\n                    \n    def _getSpawnPoint(self,player):\n        # in solo-mode, if there's an existing live player on the map, spawn at whichever\n        # spot is farthest from them (keeps the action spread out)\n        if self._soloMode:\n            livingPlayer = None\n            for team in self.teams:\n                for player in team.players:\n                    if player.isAlive():\n                        p = player.actor.node.position\n                        livingPlayer = player\n                        livingPlayerPos = p\n                        break\n            if livingPlayer:\n                playerPos = bs.Vector(*livingPlayerPos)\n                points = []\n                for team in self.teams:\n                    startPos = bs.Vector(*self.getMap().getStartPosition(team.getID()))\n                    points.append([(startPos-playerPos).length(),startPos])\n                points.sort()\n                return points[-1][1]\n            else:\n                return None\n        else:\n            return None\n\n        \n    def spawnPlayer(self,player):\n        \n        self.spawnPlayerSpaz(player,(0,5,0))\n        if not self._soloMode:\n            bs.gameTimer(300,bs.Call(self._printLives,player))\n\n        # if we have any icons, update their state\n        for icon in player.gameData['icons']:\n            icon.handlePlayerSpawned()\n\n    def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None):\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n\n        lightColor = bsUtils.getNormalizedColor(color)\n        displayColor = bs.getSafeColor(color,targetIntensity=0.75)\n        spaz = AntiGravityPlayerSpaz(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n        if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer()\n        spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound,1,position=spaz.node.position)\n        light = bs.newNode('light',attrs={'color':lightColor})\n        spaz.node.connectAttr('position',light,'position')\n        bsUtils.animate(light,'intensity',{0:0,250:1,500:0})\n        bs.gameTimer(500,light.delete)\n        bs.gameTimer(1000,bs.Call(self.raisePlayer, player))\n        return spaz\n\n    def _printLives(self,player):\n        if not player.exists() or not player.isAlive(): return\n        try: pos = player.actor.node.position\n        except Exception,e:\n            print 'EXC getting player pos in bsElim',e\n            return\n        bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1),\n                           offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain()\n\n    def onPlayerJoin(self, player):\n\n        # no longer allowing mid-game joiners here... too easy to exploit\n        if self.hasBegun():\n            player.gameData['lives'] = 0\n            player.gameData['icons'] = []\n            # make sure our team has survival seconds set if they're all dead\n            # (otherwise blocked new ffa players would be considered 'still alive' in score tallying)\n            if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None:\n                player.getTeam().gameData['survivalSeconds'] = 0\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            return\n\n        player.gameData['lives'] = self.settings['Lives Per Player']\n\n        if self._soloMode:\n            player.gameData['icons'] = []\n            player.getTeam().gameData['spawnOrder'].append(player)\n            self._updateSoloMode()\n        else:\n            # create our icon and spawn\n            player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)]\n            if player.gameData['lives'] > 0:\n                self.spawnPlayer(player)\n\n        # dont waste time doing this until begin\n        if self.hasBegun():\n            self._updateIcons()\n\n    def onPlayerLeave(self,player):\n\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n\n        player.gameData['icons'] = None\n\n        # remove us from spawn-order\n        if self._soloMode:\n            if player in player.getTeam().gameData['spawnOrder']:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n\n        # update icons in a moment since our team will be gone from the list then\n        bs.gameTimer(0, self._updateIcons)\n\n    def raisePlayer(self, player):\n        player.actor.handleMessage(FlyMessage())\n        if player.isAlive():\n            bs.gameTimer(50,bs.Call(self.raisePlayer,player))\n        \"\"\"spaz.node.handleMessage(\"impulse\",spaz.node.position[0],spaz.node.position[1],spaz.node.position[2],\n                                        0,8,0,\n                                        2,6,0,0,0,8,0)\"\"\"\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        self.setupStandardPowerupDrops()\n\n        if self._soloMode:\n            self._vsText = bs.NodeActor(bs.newNode(\"text\",\n                                                   attrs={'position':(0,105),\n                                                          'hAttach':\"center\",\n                                                          'hAlign':'center',\n                                                          'maxWidth':200,\n                                                          'shadow':0.5,\n                                                          'vrDepth':390,\n                                                          'scale':0.6,\n                                                          'vAttach':\"bottom\",\n                                                          'color':(0.8,0.8,0.3,1.0),\n                                                          'text':bs.Lstr(resource='vsText')}))\n\n        # if balance-team-lives is on, add lives to the smaller team until total lives match\n        if (isinstance(self.getSession(),bs.TeamsSession)\n            and self.settings['Balance Total Lives']\n            and len(self.teams[0].players) > 0\n            and len(self.teams[1].players) > 0):\n            if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]):\n                lesserTeam = self.teams[0]\n                greaterTeam = self.teams[1]\n            else:\n                lesserTeam = self.teams[1]\n                greaterTeam = self.teams[0]\n            addIndex = 0\n            while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam):\n                lesserTeam.players[addIndex].gameData['lives'] += 1\n                addIndex = (addIndex + 1) % len(lesserTeam.players)\n\n        self._updateIcons()\n\n        # we could check game-over conditions at explicit trigger points,\n        # but lets just do the simple thing and poll it...\n        bs.gameTimer(1000, self._update, repeat=True)\n        \n    def _getTotalTeamLives(self,team):\n        return sum(player.gameData['lives'] for player in team.players)\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            \n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n            player = m.spaz.getPlayer()\n\n            player.gameData['lives'] -= 1\n            if player.gameData['lives'] < 0:\n                bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode))\n                player.gameData['lives'] = 0\n\n            # if we have any icons, update their state\n            for icon in player.gameData['icons']:\n                icon.handlePlayerDied()\n\n            # play big death sound on our last death or for every one in solo mode\n            if self._soloMode or player.gameData['lives'] == 0:\n                bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound)\n\n            # if we hit zero lives, we're dead (and our team might be too)\n            if player.gameData['lives'] == 0:\n                # if the whole team is now dead, mark their survival time..\n                #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players):\n                if self._getTotalTeamLives(player.getTeam()) == 0:\n                    player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000\n            else:\n                # otherwise, in regular mode, respawn..\n                if not self._soloMode:\n                    self.respawnPlayer(player)\n\n            # in solo, put ourself at the back of the spawn order\n            if self._soloMode:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n                player.getTeam().gameData['spawnOrder'].append(player)\n\n    def _update(self):\n\n        if self._soloMode:\n            # for both teams, find the first player on the spawn order list with lives remaining\n            # and spawn them if they're not alive\n            for team in self.teams:\n                # prune dead players from the spawn order\n                team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n                for player in team.gameData['spawnOrder']:\n                    if player.gameData['lives'] > 0:\n                        if not player.isAlive():\n                            self.spawnPlayer(player)\n                            self._updateIcons()\n                        break\n        \n        # if we're down to 1 or fewer living teams, start a timer to end the game\n        # (allows the dust to settle and draws to occur if deaths are close enough)\n        if len(self._getLivingTeams()) < 2:\n            self._roundEndTimer = bs.Timer(500,self.endGame)\n\n\n    def _getLivingTeams(self):\n        return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)]\n\n    def endGame(self):\n        if self.hasEnded(): return\n        results = bs.TeamGameResults()\n        self._vsText = None # kill our 'vs' if its there\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['survivalSeconds'])\n        self.end(results=results)\n"
  },
  {
    "path": "mods/Greed.json",
    "content": "{\n  \"name\": \"Greed\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/Greed.py",
    "content": "import bs\nimport random\nimport bsUtils\nimport bsPowerup\nfrom bsSpaz import PlayerSpazHurtMessage\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [Greed]\n        \nclass PlayerSpaz_Greed(bs.PlayerSpaz):\n    def handleMessage(self, m):\n        #print(m)\n\n        #First we copy handling from PlayerSpaz, then almost the whole HitMessage handling from bsSpaz, but just to calculate the \"damage\".\n        if isinstance(m,bs.HitMessage):\n            #Damage is calculated in the bsSpaz message handling, which happens\n            #after the PlayerSpaz message handling.  Here we have to pull the code\n            #from PlayerSpaz in order to give kill credit (and avoid suicide), then the code from Spaz. \n            if m.sourcePlayer is not None and m.sourcePlayer.exists():\n                self.lastPlayerAttackedBy = m.sourcePlayer\n                self.lastAttackedTime = bs.getGameTime()\n                self.lastAttackedType = (m.hitType,m.hitSubType)\n            #self.__superHandleMessage(m) # augment standard behavior #super is not needed anymore due to being copied here below\n            activity = self._activity()\n            if activity is not None:\n                activity.handleMessage(PlayerSpazHurtMessage(self))\n            #End of code from bsPlayerSpaz\n            #Now the code from bsSpaz\n            boxDamage = 0\n            if not self.node.exists(): return\n            if self.node.invincible == True:\n                bs.playSound(self.getFactory().blockSound,1.0,position=self.node.position)\n                return True\n\n            # if we were recently hit, don't count this as another\n            # (so punch flurries and bomb pileups essentially count as 1 hit)\n            gameTime = bs.getGameTime()\n            if self._lastHitTime is None or gameTime-self._lastHitTime > 1000:\n                self._numTimesHit += 1\n                self._lastHitTime = gameTime\n            \n            mag = m.magnitude * self._impactScale\n            velocityMag = m.velocityMagnitude * self._impactScale\n\n            damageScale = 0.22\n\n            # if they've got a shield, deliver it to that instead..\n            if self.shield is not None:\n\n                if m.flatDamage: damage = m.flatDamage * self._impactScale\n                else:\n                    # hit our spaz with an impulse but tell it to only return theoretical damage; not apply the impulse..\n                    self.node.handleMessage(\"impulse\",m.pos[0],m.pos[1],m.pos[2],\n                                            m.velocity[0],m.velocity[1],m.velocity[2],\n                                            mag,velocityMag,m.radius,1,m.forceDirection[0],m.forceDirection[1],m.forceDirection[2])\n                    damage = damageScale * self.node.damage\n\n                self.shieldHitPoints -= damage\n\n                self.shield.hurt = 1.0 - float(self.shieldHitPoints)/self.shieldHitPointsMax\n                # its a cleaner event if a hit just kills the shield without damaging the player..\n                # however, massive damage events should still be able to damage the player..\n                # this hopefully gives us a happy medium.\n                # maxSpillover = 500\n                maxSpillover = self.getFactory().maxShieldSpilloverDamage\n                if self.shieldHitPoints <= 0:\n                    # fixme - transition out perhaps?..\n                    self.shield.delete()\n                    self.shield = None\n                    bs.playSound(self.getFactory().shieldDownSound,1.0,position=self.node.position)\n                    # emit some cool lookin sparks when the shield dies\n                    t = self.node.position\n                    bs.emitBGDynamics(position=(t[0],t[1]+0.9,t[2]),\n                                      velocity=self.node.velocity,\n                                      count=random.randrange(20,30),scale=1.0,spread=0.6,chunkType='spark')\n\n                else:\n                    bs.playSound(self.getFactory().shieldHitSound,0.5,position=self.node.position)\n\n                # emit some cool lookin sparks on shield hit\n                bs.emitBGDynamics(position=m.pos,\n                                  velocity=(m.forceDirection[0]*1.0,\n                                            m.forceDirection[1]*1.0,\n                                            m.forceDirection[2]*1.0),\n                                  count=min(30,5+int(damage*0.005)),scale=0.5,spread=0.3,chunkType='spark')\n\n\n                # if they passed our spillover threshold, pass damage along to spaz\n                if self.shieldHitPoints <= -maxSpillover:\n                    leftoverDamage = -maxSpillover-self.shieldHitPoints\n                    shieldLeftoverRatio = leftoverDamage/damage\n\n                    # scale down the magnitudes applied to spaz accordingly..\n                    mag *= shieldLeftoverRatio\n                    velocityMag *= shieldLeftoverRatio\n                else:\n                    return True # good job shield!\n            else: shieldLeftoverRatio = 1.0\n\n            if m.flatDamage:\n                damage = m.flatDamage * self._impactScale * shieldLeftoverRatio\n            else:\n                # hit it with an impulse and get the resulting damage\n                self.node.handleMessage(\"impulse\",m.pos[0],m.pos[1],m.pos[2],\n                                        m.velocity[0],m.velocity[1],m.velocity[2],\n                                        mag,velocityMag,m.radius,0,m.forceDirection[0],m.forceDirection[1],m.forceDirection[2])\n\n                damage = damageScale * self.node.damage\n            self.node.handleMessage(\"hurtSound\")\n\n            # play punch impact sound based on damage if it was a punch\n            if m.hitType == 'punch':\n\n                self.onPunched(damage)\n\n                # if damage was significant, lets show it\n                if damage > 350: bsUtils.showDamageCount('-'+str(int(damage/10))+\"%\",m.pos,m.forceDirection)\n                                               \n                # lets always add in a super-punch sound with boxing gloves just to differentiate them\n                if m.hitSubType == 'superPunch':\n                    bs.playSound(self.getFactory().punchSoundStronger,1.0,\n                                 position=self.node.position)\n\n                if damage > 500:\n                    sounds = self.getFactory().punchSoundsStrong\n                    sound = sounds[random.randrange(len(sounds))]\n                else: sound = self.getFactory().punchSound\n                bs.playSound(sound,1.0,position=self.node.position)\n\n                # throw up some chunks\n                bs.emitBGDynamics(position=m.pos,\n                                  velocity=(m.forceDirection[0]*0.5,\n                                            m.forceDirection[1]*0.5,\n                                            m.forceDirection[2]*0.5),\n                                  count=min(10,1+int(damage*0.0025)),scale=0.3,spread=0.03);\n\n                bs.emitBGDynamics(position=m.pos,\n                                  chunkType='sweat',\n                                  velocity=(m.forceDirection[0]*1.3,\n                                            m.forceDirection[1]*1.3+5.0,\n                                            m.forceDirection[2]*1.3),\n                                  count=min(30,1+int(damage*0.04)),\n                                  scale=0.9,\n                                  spread=0.28);\n                # momentary flash\n                hurtiness = damage*0.003\n                punchPos = (m.pos[0]+m.forceDirection[0]*0.02,\n                            m.pos[1]+m.forceDirection[1]*0.02,\n                            m.pos[2]+m.forceDirection[2]*0.02)\n                flashColor = (1.0,0.8,0.4)\n                light = bs.newNode(\"light\",\n                                   attrs={'position':punchPos,\n                                          'radius':0.12+hurtiness*0.12,\n                                          'intensity':0.3*(1.0+1.0*hurtiness),\n                                          'heightAttenuated':False,\n                                          'color':flashColor})\n                bs.gameTimer(60,light.delete)\n\n\n                flash = bs.newNode(\"flash\",\n                                   attrs={'position':punchPos,\n                                          'size':0.17+0.17*hurtiness,\n                                          'color':flashColor})\n                bs.gameTimer(60,flash.delete)\n\n            if m.hitType == 'impact':\n                bs.emitBGDynamics(position=m.pos,\n                                  velocity=(m.forceDirection[0]*2.0,\n                                            m.forceDirection[1]*2.0,\n                                            m.forceDirection[2]*2.0),\n                                  count=min(10,1+int(damage*0.01)),scale=0.4,spread=0.1);\n  \n            if self.hitPoints > 0:\n                # its kinda crappy to die from impacts, so lets reduce impact damage\n                # by a reasonable amount if it'll keep us alive\n                if m.hitType == 'impact' and damage > self.hitPoints:\n                    # drop damage to whatever puts us at 10 hit points, or 200 less than it used to be\n                    # whichever is greater (so it *can* still kill us if its high enough)\n                    newDamage = max(damage-200,self.hitPoints-10)\n                    damage = newDamage\n\n                self.node.handleMessage(\"flash\")\n                # if we're holding something, drop it\n                if damage > 0.0 and self.node.holdNode.exists():\n                    self.node.holdNode = bs.Node(None)\n                #################################\n                boxDamage = damage\n                if bs.getActivity().settings['Hits damage players']:\n                    if damage > self.hitPoints:\n                        boxDamage = self.hitPoints  #Only take boxes for the remaining health\n                        if boxDamage < 0:\n                            boxDamage = 0 #might be hitting a corpse\n                    self.hitPoints -= damage  #Only take hit points for damage if settings allow\n                    self.node.hurt = 1.0 - float(self.hitPoints)/self.hitPointsMax\n                # if we're cursed, *any* damage blows us up\n                if self._cursed and damage > 0:\n                    bs.gameTimer(50,bs.WeakCall(self.curseExplode,m.sourcePlayer))\n                # if we're frozen, shatter.. otherwise die if we hit zero\n                if self.frozen and (damage > 200 or self.hitPoints <= 0):\n                    self.shatter()\n                elif self.hitPoints <= 0:\n                    self.node.handleMessage(bs.DieMessage(how='impact'))\n            # if we're dead, take a look at the smoothed damage val\n            # (which gives us a smoothed average of recent damage) and shatter\n            # us if its grown high enough\n            if self.hitPoints <= 0:\n                damageAvg = self.node.damageSmoothed * damageScale\n                if damageAvg > 1000:\n                    self.shatter()\n            \n            #Now calculate the number of boxes to spew.  Idea is to spew half of boxes if damage is 1000 (basically one0shot kill)\n            player = self.getPlayer()\n            if player.exists(): #Don't spew boxes for hitting a corpse!\n                ptot = 0\n                for p2 in player.getTeam().players:\n                    if p2.exists():\n                        ptot +=1\n                if (player.getTeam().gameData['score']/ptot <= 2) and boxDamage >= 800:\n                    boxes = 1\n                else:\n                    boxes = int(player.getTeam().gameData['score']/ptot  * boxDamage / 2000)\n                if boxes >= player.getTeam().gameData['score']: #somehow the team loses more than all theri boxes?\n                    boxes = player.getTeam().gameData['score']\n                #print([damage, boxes])\n                if boxes > 0:\n                    player.getTeam().gameData['score'] -= boxes\n                    self.setScoreText(str(player.getTeam().gameData['score']), player.color)\n                    bs.gameTimer(50,bs.WeakCall(self.getActivity().spewBoxes,self.node.position, boxes))\n                    if player.getTeam().gameData['score'] == 0:\n                        self.handleMessage(bs.DieMessage())\n                    self.getActivity()._updateScoreBoard()\n            return    \n        if isinstance(m,bs.PowerupMessage): #Have to handle health powerups ourselves\n            if m.powerupType == 'health':\n                if self._dead: return True\n                if self.pickUpPowerupCallback is not None:\n                    self.pickUpPowerupCallback(self)\n                player = self.getPlayer()\n                if player.exists():\n                    player.getTeam().gameData['score'] += 1 \n                    self.getActivity()._updateScoreBoard()\n                    self.setScoreText(str(player.getTeam().gameData['score']), player.color)\n                self.node.handleMessage(\"flash\")\n                if m.sourceNode.exists():\n                    m.sourceNode.handleMessage(bs.PowerupAcceptMessage())\n                return True\n            else:\n                super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\n            \nclass Greed(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Greed'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'score',\n                'scoreType':'points',\n                'noneIsWinner':False,\n                'lowerIsBetter':False}\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Steal all the health boxes for yourself'\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Time Limit\",{'choices':[('30 Seconds',30),('1 Minute',60),\n                                            ('90 Seconds',90),('2 Minutes',120),\n                                            ('3 Minutes',180),('5 Minutes',300)],'default':60}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Starting Points\", {'default':10,'minValue':5, 'maxValue':50,'increment':5}),\n                    (\"Gloves:\",{'choices':[('Everyone',1),('Spawn',2),('None',3)],'default':3}),\n                    (\"Hits damage players\",{'default':True}),\n                    (\"Epic Mode\",{'default':False})]\n\n        if issubclass(sessionType,bs.TeamsSession):\n            settings.append((\"Solo Mode\",{'default':False}))\n            settings.append((\"Balance Total Lives\",{'default':False}))\n            \n        return settings\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # show messages when players die since it's meaningful here\n        self.announcePlayerDeaths = True\n        \n        try: self._soloMode = settings['Solo Mode']\n        except Exception: self._soloMode = False\n        self._scoreBoard = bs.ScoreBoard()\n\n    def getInstanceDescription(self):\n        return 'Steal all the health boxes for yourself' if isinstance(self.getSession(),bs.TeamsSession) else 'Steal all the health boxes for yourself'\n\n    def getInstanceScoreBoardDescription(self):\n        return 'Steal all the health boxes for yourself' if isinstance(self.getSession(),bs.TeamsSession) else 'Steal all the health boxes for yourself'\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n        self._startGameTime = bs.getGameTime()\n\n    def onTeamJoin(self,team):\n        team.gameData['survivalSeconds'] = None\n        team.gameData['spawnOrder'] = []\n        team.gameData['score'] = 0\n\n\n    def onPlayerJoin(self, player):\n\n        # no longer allowing mid-game joiners here... too easy to exploit\n        if self.hasBegun():\n            player.gameData['lives'] = 0\n            player.gameData['icons'] = []\n            # make sure our team has survival seconds set if they're all dead\n            # (otherwise blocked new ffa players would be considered 'still alive' in score tallying)\n            if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None:\n                player.getTeam().gameData['survivalSeconds'] = 1000\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            return\n        player.gameData['lives'] = 1\n        player.getTeam().gameData['score'] = self.settings['Starting Points']\n        self._updateScoreBoard()\n        \n        if self._soloMode:\n            #player.gameData['icons'] = []\n            player.getTeam().gameData['spawnOrder'].append(player)\n            self._updateSoloMode()\n        else:\n            # create our icon and spawn\n            #player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)]\n            if player.gameData['lives'] > 0:\n                self.spawnPlayer(player)\n\n        # dont waste time doing this until begin\n        if self.hasBegun():\n            pass#self._updateIcons()\n\n    def _updateSoloMode(self):\n        # for both teams, find the first player on the spawn order list with lives remaining\n        # and spawn them if they're not alive\n        for team in self.teams:\n            # prune dead players from the spawn order\n            team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n            for player in team.gameData['spawnOrder']:\n                if player.gameData['lives'] > 0:\n                    if not player.isAlive(): self.spawnPlayer(player)\n                    break\n\n    def _updateIcons(self):\n        # in free-for-all mode, everyone is just lined up along the bottom\n        if isinstance(self.getSession(),bs.FreeForAllSession):\n            count = len(self.teams)\n            xOffs = 85\n            x = xOffs*(count-1) * -0.5\n            for i,team in enumerate(self.teams):\n                if len(team.players) == 1:\n                    player = team.players[0]\n                    for icon in player.gameData['icons']:\n                        icon.setPositionAndScale((x,30),0.7)\n                        icon.updateForLives()\n                    x += xOffs\n\n        # in teams mode we split up teams\n        else:\n            if self._soloMode:\n                # first off, clear out all icons\n                for player in self.players:\n                    player.gameData['icons'] = []\n                # now for each team, cycle through our available players adding icons\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -60\n                        xOffs = -78\n                    else:\n                        x = 60\n                        xOffs = 78\n                    isFirst = True\n                    testLives = 1\n                    while True:\n                        playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives]\n                        if len(playersWithLives) == 0: break\n                        for player in playersWithLives:\n                            player.gameData['icons'].append(Icon(player,\n                                                                 position=(x,(40 if isFirst else 25)),\n                                                                 scale=1.0 if isFirst else 0.5,\n                                                                 nameMaxWidth=130 if isFirst else 75,\n                                                                 nameScale=0.8 if isFirst else 1.0,\n                                                                 flatness=0.0 if isFirst else 1.0,\n                                                                 shadow=0.5 if isFirst else 1.0,\n                                                                 showDeath=True if isFirst else False,\n                                                                 showLives=False))\n                            x += xOffs * (0.8 if isFirst else 0.56)\n                            isFirst = False\n                        testLives += 1\n            # non-solo mode\n            else:\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -50\n                        xOffs = -85\n                    else:\n                        x = 50\n                        xOffs = 85\n                    for player in team.players:\n                        for icon in player.gameData['icons']:\n                            icon.setPositionAndScale((x,30),0.7)\n                            icon.updateForLives()\n                        x += xOffs\n                    \n    def _getSpawnPoint(self,player):\n        # in solo-mode, if there's an existing live player on the map, spawn at whichever\n        # spot is farthest from them (keeps the action spread out)\n        if self._soloMode:\n            livingPlayer = None\n            for team in self.teams:\n                for player in team.players:\n                    if player.isAlive():\n                        p = player.actor.node.position\n                        livingPlayer = player\n                        livingPlayerPos = p\n                        break\n            if livingPlayer:\n                playerPos = bs.Vector(*livingPlayerPos)\n                points = []\n                for team in self.teams:\n                    startPos = bs.Vector(*self.getMap().getStartPosition(team.getID()))\n                    points.append([(startPos-playerPos).length(),startPos])\n                points.sort()\n                return points[-1][1]\n            else:\n                return None\n        else:\n            return None\n\n        \n    def spawnPlayer(self,player):\n        \"\"\"This next line is the default spawn line. But we need to spawn our special guy\"\"\"\n        #self.spawnPlayerSpaz(player,self._getSpawnPoint(player))\n        #position = self._getSpawnPoint(player)\n        #if isinstance(self.getSession(), bs.TeamsSession):\n        #    position = self.getMap().getStartPosition(player.getTeam().getID())\n        #else:\n        #\t# otherwise do free-for-all spawn locations\n        position = self.getMap().getFFAStartPosition(self.players)\n\n        angle = 20\n\n\n        #spaz = self.spawnPlayerSpaz(player)\n\n        # lets reconnect this player's controls to this\n        # spaz but *without* the ability to attack or pick stuff up\n        #spaz.connectControlsToPlayer(enablePunch=False,\n        #\t\t\t\t\t\t\t enableBomb=False,\n        #\t\t\t\t\t\t\t enablePickUp=False)\n\n        # also lets have them make some noise when they die..\n        #spaz.playBigDeathSound = True\n\n        name = player.getName()\n\n        lightColor = bsUtils.getNormalizedColor(player.color)\n        displayColor = bs.getSafeColor(player.color, targetIntensity=0.75)\n\n        spaz = PlayerSpaz_Greed(color=player.color,\n                             highlight=player.highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n        # we want a bigger area-of-interest in co-op mode\n        # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n        # else: spaz.node.areaOfInterestRadius = 5.0\n\n        # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n        # collide with the player-walls\n        # FIXME; need to generalize this\n        if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n\n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer()\n        self.scoreSet.playerGotNewSpaz(player, spaz)\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound, 1, position=spaz.node.position)\n        light = bs.newNode('light', attrs={'color': lightColor})\n        spaz.node.connectAttr('position', light, 'position')\n        bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0})\n        bs.gameTimer(500, light.delete)\n        #Start code to spawn special guy:\n        #End of code to spawn special guy\n        if not self._soloMode:\n            bs.gameTimer(1000,bs.WeakCall(self.updateSpazScore, player,spaz), repeat=True)\n            #spaz.setScoreText(str(player.getTeam().gameData['score']), player.color)\n        if self.settings['Gloves:'] == 1:\n            spaz.equipBoxingGloves()    \n        # if we have any icons, update their state\n        #for icon in player.gameData['icons']:\n        #    icon.handlePlayerSpawned()\n        \n    def updateSpazScore(self,player,spaz):\n        if player.isAlive():\n            if spaz.isAlive():\n                spaz.setScoreText(str(player.getTeam().gameData['score']), player.color)\n\n    def _printLives(self,player):\n        if not player.exists() or not player.isAlive(): return\n        try: pos = player.actor.node.position\n        except Exception,e:\n            print 'EXC getting player pos in bsElim',e\n            return\n        bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1),\n                           offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain()\n\n    def onPlayerLeave(self,player):\n\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n\n        #player.gameData['icons'] = None\n        player.gameData['score'] = 0\n\n        # remove us from spawn-order\n        if self._soloMode:\n            if player in player.getTeam().gameData['spawnOrder']:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n\n        # update icons in a moment since our team will be gone from the list then\n        #bs.gameTimer(0, self._updateIcons)\n\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        self.setupStandardPowerupDrops()\n\n        if self._soloMode:\n            self._vsText = bs.NodeActor(bs.newNode(\"text\",\n                                                   attrs={'position':(0,105),\n                                                          'hAttach':\"center\",\n                                                          'hAlign':'center',\n                                                          'maxWidth':200,\n                                                          'shadow':0.5,\n                                                          'vrDepth':390,\n                                                          'scale':0.6,\n                                                          'vAttach':\"bottom\",\n                                                          'color':(0.8,0.8,0.3,1.0),\n                                                          'text':bs.Lstr(resource='vsText')}))\n\n        # if balance-team-lives is on, add lives to the smaller team until total lives match\n        if (isinstance(self.getSession(),bs.TeamsSession)\n            and self.settings['Balance Total Lives']\n            and len(self.teams[0].players) > 0\n            and len(self.teams[1].players) > 0):\n            if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]):\n                lesserTeam = self.teams[0]\n                greaterTeam = self.teams[1]\n            else:\n                lesserTeam = self.teams[1]\n                greaterTeam = self.teams[0]\n            addIndex = 0\n            while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam):\n                lesserTeam.players[addIndex].gameData['lives'] += 1\n                addIndex = (addIndex + 1) % len(lesserTeam.players)\n        self.excludePowerups = ['health','iceBombs','curse']\n        if self.settings['Gloves:'] == 3:\n            self.excludePowerups.append('punch')\n        #self._updateIcons()\n\n        # we could check game-over conditions at explicit trigger points,\n        # but lets just do the simple thing and poll it...\n        bs.gameTimer(1000, self._update, repeat=True)\n        \n    def _getTotalTeamLives(self,team):\n        return sum(player.gameData['lives'] for player in team.players)\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n            player = m.spaz.getPlayer()\n            if m.killerPlayer == player:\n                player.getTeam().gameData['score'] /= 2\n                self._updateScoreBoard()\n            #print([player, m.spaz.hitPoints, \"killed by\", m.killerPlayer])\n            if player.getTeam().gameData['score'] == 0: #Only permadeath if they lost all their boxes.\n                player.gameData['lives'] -= 1\n                \n            if player.gameData['lives'] < 0:\n                bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode))\n                player.gameData['lives'] = 0\n\n            # if we have any icons, update their state\n            #for icon in player.gameData['icons']:\n            #    icon.handlePlayerDied()\n\n            # play big death sound on our last death or for every one in solo mode\n            if self._soloMode or player.gameData['lives'] == 0:\n                bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound)\n\n            # if we hit zero lives, we're dead (and our whole team is too, since 0 boxes left)\n            if player.gameData['lives'] == 0:\n                pass\n                # if the whole team is now dead, mark their survival time..\n                #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players):\n                #if self._getTotalTeamLives(player.getTeam()) == 0:\n                #    player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000\n                #for p2 in player.getTeam().players:\n                #    p2.gameData['lives'] = 0\n                #    self.scoreSet._players[player.getName()].getSpaz().handleMessage(bs.DieMessage())\n            else:\n                # otherwise, in regular mode, respawn..\n                if not self._soloMode:\n                    self.respawnPlayer(player)\n\n            # in solo, put ourself at the back of the spawn order\n            if self._soloMode:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n                player.getTeam().gameData['spawnOrder'].append(player)\n        else:\n            bs.TeamGameActivity.handleMessage(self, m)\n    def _update(self):\n\n        if self._soloMode:\n            # for both teams, find the first player on the spawn order list with lives remaining\n            # and spawn them if they're not alive\n            for team in self.teams:\n                # prune dead players from the spawn order\n                team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n                for player in team.gameData['spawnOrder']:\n                    if player.gameData['lives'] > 0:\n                        if not player.isAlive():\n                            self.spawnPlayer(player)\n                            #self._updateIcons()\n                        break\n        \n        # if we're down to 1 or fewer living teams, start a timer to end the game\n        # (allows the dust to settle and draws to occur if deaths are close enough)\n        if (len(self._getLivingTeams()) < 2):\n            self._roundEndTimer = bs.Timer(500,self.endGame)\n            \n    def spewBoxes(self, pos, boxes):\n        p2 = list(pos)\n        p2[1] += 1\n        for n in range(0,boxes):\n            #print([\"spewbox\", [p2[0], p2[1] + 1.5 + (x*0.4), p2[2]]])\n            #Let's create a spiral, just for fun\n            gap = 0.1\n            x=gap*float(n+2)*((-1.0)**float(int((n % 4)/2)))\n            #x = (-1.0)^float(3)\n            y=gap*float(n+2)*((-1.0)**float(int((n + 1 % 4)/2)))\n            bs.gameTimer(int((1+n)*25), bs.Call(self.spewWithTimer, [p2[0]+x, p2[1] + 1.0 + (n*gap), p2[2]+y]))\n    def spewWithTimer(self,pos):\n        bsPowerup.Powerup(position=pos, powerupType='health',expire=False).autoRetain()\n    def _standardDropPowerup(self,index,expire=True): #Overloaded from bsGame in order to randomize without curse, health, or freeze.\n        bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[index],\n                          powerupType=bs.Powerup.getFactory().getRandomPowerupType(forceType=None, excludeTypes=self.excludePowerups),expire=expire).autoRetain()\n    def _getLivingTeams(self):\n        return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)]\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team, team.gameData['score'])\n    def endGame(self):\n        if self.hasEnded(): return\n        results = bs.TeamGameResults()\n        self._vsText = None # kill our 'vs' if its there\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['score'])\n        self.end(results=results)\n        \n"
  },
  {
    "path": "mods/GuessTheBomb.json",
    "content": "{\n  \"name\": \"Guess The Bomb\",\n  \"author\": \"Paolo Valerdi\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/GuessTheBomb.py",
    "content": "#Made by Paolo Valerdi\nimport bs\nimport random\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [GuessTheBombGame]\n\ndef bsGetLevels():\n    return [bs.Level('Guess The Bomb',displayName='${GAME}',gameType=GuessTheBombGame,settings={},previewTexName='rampagePreview'),\n            bs.Level('Epic Guess The Bomb',displayName='${GAME}',gameType=GuessTheBombGame,settings={'Epic Mode':True},previewTexName='rampagePreview')]\n\nclass GuessTheBombGame(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Guess The Bomb'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Survived',\n                'scoreType':'milliseconds',\n                'scoreVersion':'B'}\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Dodge the falling bombs.'\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Rampage']\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        return [(\"Epic Mode\",{'default':False})]\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)\n                        or issubclass(sessionType,bs.CoopSession)) else False\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        self.announcePlayerDeaths = True\n\n        self._lastPlayerDeathTime = None\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self._meteorTime = 3000\n        t = 7500 if len(self.players) > 2 else 4000\n        if self.settings['Epic Mode']: t /= 4\n        bs.gameTimer(t,self._decrementMeteorTime,repeat=True)\n\n        t = 3000\n        if self.settings['Epic Mode']: t /= 4\n        bs.gameTimer(t,self._setMeteorTimer)\n\n        self._timer = bs.OnScreenTimer()\n        self._timer.start()\n\n    def spawnPlayer(self,player):\n        spaz = self.spawnPlayerSpaz(player)\n        spaz.connectControlsToPlayer(enablePunch=False,\n                                     enableBomb=False,\n                                     enablePickUp=False)\n\n        spaz.playBigDeathSound = True\n\n    def handleMessage(self,m):\n\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n\n            bs.TeamGameActivity.handleMessage(self,m)\n\n            deathTime = bs.getGameTime()\n\n            m.spaz.getPlayer().gameData['deathTime'] = deathTime\n\n            if isinstance(self.getSession(),bs.CoopSession):\n                bs.pushCall(self._checkEndGame)\n                self._lastPlayerDeathTime = deathTime\n            else:\n                bs.gameTimer(1000,self._checkEndGame)\n\n        else:\n            bs.TeamGameActivity.handleMessage(self,m)\n\n    def _checkEndGame(self):\n        livingTeamCount = 0\n        for team in self.teams:\n            for player in team.players:\n                if player.isAlive():\n                    livingTeamCount += 1\n                    break\n\n        if isinstance(self.getSession(),bs.CoopSession):\n            if livingTeamCount <= 0: self.endGame()\n        else:\n            if livingTeamCount <= 1: self.endGame()\n\n    def _setMeteorTimer(self):\n        bs.gameTimer(int((1.0+0.2*random.random())*self._meteorTime),self._dropBombCluster)\n\n    def _dropBombCluster(self):\n\n        if False:\n            bs.newNode('locator',attrs={'position':(8,6,-5.5)})\n            bs.newNode('locator',attrs={'position':(8,6,-2.3)})\n            bs.newNode('locator',attrs={'position':(-7.3,6,-5.5)})\n            bs.newNode('locator',attrs={'position':(-7.3,6,-2.3)})\n\n        delay = 0\n        for i in range(random.randrange(1,3)):\n            types = [\"normal\", \"ice\", \"sticky\", \"impact\"]\n            magic = random.choice(types)\n            bt = magic\n            pos = (-7.3+15.3*random.random(),11,-5.5+2.1*random.random())\n            vel = ((-5.0+random.random()*30.0) * (-1.0 if pos[0] > 0 else 1.0), -4.0,0)\n            bs.gameTimer(delay,bs.Call(self._dropBomb,pos,vel,bt))\n            delay += 100\n        self._setMeteorTimer()\n\n    def _dropBomb(self,position,velocity,bombType):\n        b = bs.Bomb(position=position,velocity=velocity,bombType=bombType).autoRetain()\n\n    def _decrementMeteorTime(self):\n        self._meteorTime = max(10,int(self._meteorTime*0.9))\n\n    def endGame(self):\n\n        curTime = bs.getGameTime()\n\n        for team in self.teams:\n            for player in team.players:\n\n                if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime+1\n\n                score = (player.gameData['deathTime']-self._timer.getStartTime())/1000\n                if 'deathTime' not in player.gameData: score += 50\n                self.scoreSet.playerScored(player,score,screenMessage=False)\n\n        self._timer.stop(endTime=self._lastPlayerDeathTime)\n\n        results = bs.TeamGameResults()\n\n        for team in self.teams:\n\n            longestLife = 0\n            for player in team.players:\n                longestLife = max(longestLife,(player.gameData['deathTime'] - self._timer.getStartTime()))\n            results.setTeamScore(team,longestLife)\n\n        self.end(results=results)"
  },
  {
    "path": "mods/HazardousCargo.json",
    "content": "{\n  \"name\": \"Hazardous Cargo\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/HazardousCargo.py",
    "content": "import bs\nimport random\nimport bsUtils\nimport bsBomb\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [HazardousCargo]\n\nclass boxCrossMessage(object):\n    pass\n\nclass myMine(bs.Bomb):\n    #reason for the mine class is so we can get the HitMessage.\n    #We need the HitMessage to prevent chain reactions from pretty\n    #much blowing the whole map.\n    def __init__(self,pos):\n        bs.Bomb.__init__(self,position=pos,bombType='landMine')\n        self.hitSubType = 1 #Startt SubType at 1\n\n    def handleMessage(self,m):\n        #print(m)\n        if isinstance(m, bs.HitMessage):\n            #print(['hit',m.hitSubType])\n            #hitSubType comes from the thing doing the hitting.\n            #In our case, all the mines start with 1.  We want to increment\n            #hitSubType with each successive hitter so that we can limit\n            #chain reactions that pretty much clear the whole map.\n            if m.hitSubType < 4:\n                self.hitSubType = m.hitSubType + 1\n                m.hitSubType = self.hitSubType\n                super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\n            \nclass cargoBox(bs.Bomb):\n\n    def __init__(self,pos):\n        bs.Bomb.__init__(self,position=pos,bombType='tnt')\n        self.claimedBy = None\n        self.scored = False\n        #self = box\n        fm = bs.Flag.getFactory().flagMaterial\n        materials = getattr(self.node,'materials')\n        if not fm in materials:\n            setattr(self.node,'materials',materials + (fm,))\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.HitMessage):\n            if m.hitSubType == 'tnt':\n                return True\n            else:\n                super(self.__class__, self).handleMessage(m)\n        if isinstance(m, boxCrossMessage):\n            self.getActivity().checkForScore(self)\n        if isinstance(m, bs.PickedUpMessage):\n            self._updateBoxState()\n        if isinstance(m, bs.DieMessage):\n            act = self.getActivity()\n            act.updateBoxTimer()\n            super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\n\n    def _updateBoxState(self):\n        claimerHold = False\n        userperHold = False\n        for player in self.getActivity().players:\n            try:\n                if player.actor.isAlive() and player.actor.node.holdNode.exists():\n                    holdingBox = (player.actor.node.holdNode == self.node)\n                else: holdingBox = False\n            except Exception:\n                bs.printException(\"exception checking hold flag\")\n            if holdingBox:\n                if self.claimedBy is None:\n                    self.claimedBy = player\n                    claimerHold = True\n                else:\n                    if self.claimedBy == player:\n                        claimerHold = True\n                    else:\n                        userperHold = True\n        #release claim on any other existing boxes\n        for box in self.getActivity().boxes:\n            if box <> self and box.claimedBy == self.claimedBy:\n                box.claimedBy = None\n        #Blow up this box if it belongs to someone else\n        if (not claimerHold) and userperHold:\n            self.handleMessage(bs.HitMessage())\n        \n\n                \nclass HazardousCargo(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Hazardous Cargo'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Survived',\n                'scoreType':'seconds',\n                'noneIsWinner':False,\n                'lowerIsBetter':True}\n                \n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        # we support teams, free-for-all\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Go get a TNT box and return to the end zone.'\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType('football')\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        return [(\"Score to Win\",{'minValue':1,'default':1,'increment':1}),\n                (\"Max Mines\",{'minValue':20,'default':80,'increment':10}),\n                (\"Start Mines\",{'minValue':20,'default':40,'increment':10}),\n                (\"Mines per Second\",{'minValue':1,'default':5,'increment':1}),\n                (\"Enable Bombs\",{'default':False}),\n                (\"One-Way Trip\",{'default':False}),\n                (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                        ('2 Minutes',120),('5 Minutes',300),\n                                        ('10 Minutes',600),('20 Minutes',1200)],'default':120}),\n                (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                ]\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        self._scoreBoard = bs.ScoreBoard()\n        self.boxes = []\n        self.mines = []\n        # load some media we need\n        self._cheerSound = bs.getSound(\"cheer\")\n        self._chantSound = bs.getSound(\"crowdChant\")\n        self._scoreSound = bs.getSound(\"score\")\n        self._swipSound = bs.getSound(\"swip\")\n        self._whistleSound = bs.getSound(\"refWhistle\")\n\n        self.scoreRegionMaterial = bs.Material()\n        self.scoreRegionMaterial.addActions(\n            conditions=(\"theyHaveMaterial\",bs.Flag.getFactory().flagMaterial),\n            actions=((\"modifyPartCollision\",\"collide\",True),\n                     (\"modifyPartCollision\",\"physical\",False),\n                     (\"message\",'theirNode','atConnect',boxCrossMessage())))\n\n    def getInstanceDescription(self):\n        return ('Go get a TNT box and carry it to the end zone.')\n\n    def getInstanceScoreBoardDescription(self):\n        tds = self.settings['Score to Win']\n        if tds > 1: return ('Bring back ${ARG1} TNT boxes. Better get your own!',tds)\n        else: return ('Bring back a TNT box. Better get your own!')\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Football')\n        self._startGameTime = bs.getGameTime()\n        \n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.winners = []\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n\n        # set up the score region\n        self._scoreRegions = []\n\n        defs = self.getMap().defs\n        self._scoreRegions.append(bs.NodeActor(bs.newNode('region',\n                                                          attrs={'position':defs.boxes['goal1'][0:3],\n                                                                 'scale':defs.boxes['goal1'][6:9],\n                                                                 'type': 'box',\n                                                                 'materials':(self.scoreRegionMaterial,)})))\n\n        self._updateScoreBoard()\n        self.updateBoxes()\n        #Preload the play area with mines\n        while len(self.mines) < self.settings['Start Mines']:\n            x = random.uniform(-11.3, 11.3)\n            y = random.uniform(-5.2,5.7)\n            pos = [x,0.32,y]\n            self._makeMine(pos)\n        bs.playSound(self._chantSound)\n        #Set up the timer for mine spawning\n        bs.gameTimer(int(1000/self.settings['Mines per Second']), bs.WeakCall(self.mineUpdate), repeat=True)\n        bs.gameTimer(1000, self._update, repeat=True)\n        \n    def updateBoxTimer(self):\n        bs.gameTimer(50, self.updateBoxes)\n    def updateBoxes(self):\n        for box in self.boxes:\n            if not box.exists():\n                self.boxes.remove(box)\n        while len(self.boxes) < len(self.teams):\n            #x = random.uniform(-12.5,-11.3)\n            y = random.uniform(-5,5.5)\n            self.boxes.append(cargoBox([-12.3,0.4,y]))\n            \n    def onTeamJoin(self,team):\n        team.gameData['score'] = 0\n        team.gameData['survivalSeconds'] = None\n        self._updateScoreBoard()\n        self.updateBoxes()\n        \n    def checkForScore(self, box):\n        if box.scored: return\n        player = box.claimedBy\n        if player.actor.isAlive() and player.actor.node.holdNode.exists():\n            if player.actor.node.holdNode == box.node:\n                box.scored = True\n                player.getTeam().gameData['score'] +=1\n                bsUtils.animate(box.node, \"modelScale\", {0:1.0, 150:0.5, 300:0.0})\n                bs.gameTimer(300, bs.WeakCall(box.handleMessage, bs.DieMessage()))\n                self._updateScoreBoard()\n                for playa in player.getTeam().players:\n                    try: playa.actor.node.handleMessage('celebrate',2000)\n                    except Exception: pass\n                if player.getTeam().gameData['score'] == self.settings['Score to Win']:\n                    player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000\n                    self.winners.append(player.getTeam())\n                    for playa in player.getTeam().players:\n                        self._flashPlayer(playa,1.0)\n                        playa.actor.handleMessage(bs.DieMessage(immediate=True))\n                bs.playSound(self._scoreSound)\n                bs.playSound(self._cheerSound)\n            else:\n                #print('nobody scored')\n                box.handleMessage(bs.HitMessage())\n        else:\n            box.handleMessage(bs.HitMessage())\n            \n    def _update(self):\n        \n        # if we're down to 1 or fewer living teams, start a timer to end the game\n        # (allows the dust to settle and draws to occur if deaths are close enough)\n        if (len([team for team in self.teams if team.gameData['score'] < self.settings['Score to Win'] ]) < 2) or len(self.winners) > 2:\n            self._roundEndTimer = bs.Timer(500,self.endGame)\n    def mineUpdate(self):\n        #purge dead mines\n        for m in self.mines:\n            if not m.exists():\n                self.mines.remove(m)\n        #Remove an old mine (if needed) and make a new mine\n        if len(self.mines) > self.settings['Max Mines']:\n            self.mines[0].handleMessage(bs.DieMessage(immediate=True))\n            del self.mines[0]\n        x = random.uniform(-10.3, 11.3)\n        y = random.uniform(-5.2,5.7)\n        pos = [x,0.32,y]\n        self._flashMine(pos)\n        bs.gameTimer(950,bs.Call(self._makeMine,pos))\n    \n    def _makeMine(self,posn):\n        m = myMine(pos=posn)\n        m.arm()\n        self.mines.append(m)\n        \n\n    def _flashMine(self,pos):\n        light = bs.newNode(\"light\",\n                           attrs={'position':pos,\n                                  'color':(1,0.2,0.2),\n                                  'radius':0.1,\n                                  'heightAttenuated':False})\n        bs.animate(light,\"intensity\",{0:0,100:1.0,200:0},loop=True)\n        bs.gameTimer(1000,light.delete)\n        \n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for t in self.teams: results.setTeamScore(t,t.gameData['survivalSeconds'])\n        self.end(results=results,announceDelay=800)\n        \n    def _flashPlayer(self,player,scale):\n        pos = player.actor.node.position\n        light = bs.newNode('light',\n                           attrs={'position':pos,\n                                  'color':(1,1,0),\n                                  'heightAttenuated':False,\n                                  'radius':0.4})\n        bs.gameTimer(500,light.delete)\n        bs.animate(light,'intensity',{0:0,100:1.0*scale,500:0})\n\n    def _updateScoreBoard(self):\n        winScore = self.settings['Score to Win']\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team,team.gameData['score'],winScore)\n\n    def handleMessage(self,m):\n\n        # respawn dead players if they're still in the game\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior\n            self.respawnPlayer(m.spaz.getPlayer())\n\n        else:\n            bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior\n\n    def spawnPlayer(self,player):\n        \"\"\"\n        Spawn *something* for the provided bs.Player.\n        The default implementation simply calls spawnPlayerSpaz().\n        \"\"\"\n        #Overloaded for this game to spawn players in the end zone instead of FFA spots\n        if not player.exists():\n            bs.printError('spawnPlayer() called for nonexistant player')\n            return\n        y = random.uniform(-5,5.5)\n        if self.settings['One-Way Trip'] == True:\n            x = -11.8\n        else:\n            x = 12.0\n        spz = self.spawnPlayerSpaz(player, position=[x,0.35,y])\n        spz.connectControlsToPlayer(enablePunch=True,\n                                     enableBomb=self.settings['Enable Bombs'],\n                                     enablePickUp=True)\n        return spz\n"
  },
  {
    "path": "mods/Infection.json",
    "content": "{\n  \"name\": \"Infection\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/Infection.py",
    "content": "import bs\nimport random\nimport bsUtils\nimport bsBomb\nimport bsVector\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [Infection]\n\ndef bsGetLevels():\n    return [ bs.Level('Infection', displayName='${GAME}', gameType=Infection,\n                      settings={}, previewTexName='footballStadiumPreview') ]\n\nclass PlayerSpaz_Infection(bs.PlayerSpaz):\n    def handleMessage(self, m):\n        if isinstance(m, bs.HitMessage):\n            if not self.node.exists():\n                return True\n            if m.sourcePlayer != self.getPlayer():\n                return True\n            else:\n                super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\nclass myMine(bs.Bomb):\n    #reason for the mine class is so we can add the death zone\n    def __init__(self,pos):\n        bs.Bomb.__init__(self,position=pos,bombType='landMine')\n        showInSpace = False\n        self.died = False\n        self.rad = 0.3\n        self.zone = bs.newNode('locator',attrs={'shape':'circle','position':self.node.position,'color':(1,0,0),'opacity':0.5,'drawBeauty':showInSpace,'additive':True})\n        bs.animateArray(self.zone,'size',1,{0:[0.0],50:[2*self.rad]})\n    \n    def handleMessage(self,m):\n        if isinstance(m,bs.DieMessage):\n            if not self.died:\n                self.getActivity().mineCount -= 1\n                self.died = True\n                bs.animateArray(self.zone,'size',1,{0:[2*self.rad],50:[0]})\n                self.zone = None\n            super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\n                \nclass Infection(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Infection'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return { 'scoreName':'Survived',\n                 'scoreType':'milliseconds',\n                 'scoreVersion':'B' }\n                \n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)\n                        or issubclass(sessionType,bs.CoopSession)) else False\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return \"It's spreading!\"\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Crag Castle', 'Big G', 'Football Stadium']\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        return [(\"Mines\",{'minValue':5,'default':10,'increment':5}),\n                (\"Enable Bombs\",{'default':True}),\n                (\"Sec/Extra Mine\",{'minValue':1,'default':10,'increment':1}),\n                (\"Max Infected Size\",{'minValue':4,'default':6,'increment':1}),\n                (\"Max Size Increases Every\",{'choices':[('10s',10),('20s',20),\n                                              ('30s',30),('Minute',60)],'default':20}),\n                (\"Infection Spread Rate\",{'choices':[('Slowest',0.01),('Slow',0.02),('Normal',0.03),('Fast',0.04),('Faster',0.05),('Insane',0.08)],'default':0.03}),\n                ]\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self, settings)\n        \n        # print messages when players die (since its meaningful in this game)\n        self.announcePlayerDeaths = True\n\n        self._lastPlayerDeathTime = None    \n\n        self.mines = []\n        self.maxMines = settings['Mines']\n        self.updateRate = 100 #update the mine radii etc every this many milliseconds\n        self.growthRate = self.settings['Infection Spread Rate'] #grow the radius of each death zone by this much every update\n        self.maxSize = self.settings['Max Infected Size'] #We'll set a timer later to increase this\n\n    def getInstanceDescription(self):\n        return ('Avoid the spread!')\n\n    def getInstanceScoreBoardDescription(self):\n        return ('Avoid the spread!')\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Survival')\n        self._startGameTime = bs.getGameTime()\n        \n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n\n        self.mineCount = 0\n        bs.gameTimer(self.updateRate, bs.WeakCall(self.mineUpdate), repeat=True)\n        bs.gameTimer(self.settings['Max Size Increases Every']*1000, bs.WeakCall(self.maxSizeUpdate), repeat=True)\n        bs.gameTimer(self.settings['Sec/Extra Mine']*1000, bs.WeakCall(self.maxMineUpdate), repeat=True)\n        self._timer = bs.OnScreenTimer()\n        self._timer.start()\n        # check for immediate end (if we've only got 1 player, etc)\n        bs.gameTimer(5000, self._checkEndGame)\n        \n        \n    def onPlayerJoin(self, player):\n        # don't allow joining after we start\n        # (would enable leave/rejoin tomfoolery)\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            # for score purposes, mark them as having died right as the game started\n            player.gameData['deathTime'] = self._timer.getStartTime()\n            return\n        self.spawnPlayer(player)\n        \n    def onPlayerLeave(self, player):\n         # augment default behavior...\n        bs.TeamGameActivity.onPlayerLeave(self, player)\n        # a departing player may trigger game-over\n        self._checkEndGame()\n\n    def maxMineUpdate(self):\n        self.maxMines += 1\n    \n    def maxSizeUpdate(self):\n        self.maxSize += 1\n        \n    def mineUpdate(self):\n        #print self.mineCount\n        #purge dead mines, update their animantion, check if players died\n        for m in self.mines:\n            if not m.exists():\n                self.mines.remove(m)\n            else:\n                #First, check if any player is within the current death zone\n                for player in self.players:\n                    if not player.actor is None:\n                        if player.actor.isAlive():\n                            p1 = player.actor.node.position\n                            p2 = m.node.position\n                            diff = (bs.Vector(p1[0]-p2[0],0.0,p1[2]-p2[2]))\n                            dist = (diff.length())\n                            if dist < m.rad:\n                                player.actor.handleMessage(bs.DieMessage())\n                #Now tell the circle to grow to the new size\n                if m.rad < self.maxSize:\n                    bs.animateArray(m.zone,'size',1,{0:[m.rad*2],self.updateRate:[(m.rad+self.growthRate)*2]})\n                    #Tell the circle to be the new size. This will be the new check radius next time.\n                    m.rad +=self.growthRate\n        #make a new mine if needed.\n        if self.mineCount < self.maxMines:\n            pos = self.getRandomPowerupPoint()\n            self.mineCount += 1\n            self._flashMine(pos)\n            bs.gameTimer(950,bs.Call(self._makeMine,pos))\n    \n    def _makeMine(self,posn):\n        m = myMine(pos=posn)\n        m.arm()\n        self.mines.append(m)\n        \n\n    def _flashMine(self,pos):\n        light = bs.newNode(\"light\",\n                           attrs={'position':pos,\n                                  'color':(1,0.2,0.2),\n                                  'radius':0.1,\n                                  'heightAttenuated':False})\n        bs.animate(light,\"intensity\",{0:0,100:1.0,200:0},loop=True)\n        bs.gameTimer(1000,light.delete)\n        \n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for t in self.teams: results.setTeamScore(t,t.gameData['survivalSeconds'])\n        self.end(results=results,announceDelay=800)\n        \n    def _flashPlayer(self,player,scale):\n        pos = player.actor.node.position\n        light = bs.newNode('light',\n                           attrs={'position':pos,\n                                  'color':(1,1,0),\n                                  'heightAttenuated':False,\n                                  'radius':0.4})\n        bs.gameTimer(500,light.delete)\n        bs.animate(light,'intensity',{0:0,100:1.0*scale,500:0})\n\n\n    def handleMessage(self,m):\n\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n\n            bs.TeamGameActivity.handleMessage(self,m) # (augment standard behavior)\n\n            deathTime = bs.getGameTime()\n            \n            # record the player's moment of death\n            m.spaz.getPlayer().gameData['deathTime'] = deathTime\n\n            # in co-op mode, end the game the instant everyone dies (more accurate looking)\n            # in teams/ffa, allow a one-second fudge-factor so we can get more draws\n            if isinstance(self.getSession(),bs.CoopSession):\n                # teams will still show up if we check now.. check in the next cycle\n                bs.pushCall(self._checkEndGame)\n                self._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock..\n            else:\n                bs.gameTimer(1000, self._checkEndGame)\n\n        else:\n            # default handler:\n            bs.TeamGameActivity.handleMessage(self,m)\n\n    def _checkEndGame(self):\n        livingTeamCount = 0\n        for team in self.teams:\n            for player in team.players:\n                if player.isAlive():\n                    livingTeamCount += 1\n                    break\n\n        # in co-op, we go till everyone is dead.. otherwise we go until one team remains\n        if isinstance(self.getSession(),bs.CoopSession):\n            if livingTeamCount <= 0: self.endGame()\n        else:\n            if livingTeamCount <= 1: self.endGame()\n\n    def spawnPlayer(self, player):\n\n        spaz = self.spawnPlayerSpaz(player)\n\n        # lets reconnect this player's controls to this\n        # spaz but *without* the ability to attack or pick stuff up\n        spaz.connectControlsToPlayer(enablePunch=False,\n                                     enableBomb=self.settings['Enable Bombs'],\n                                     enablePickUp=False)\n\n        # also lets have them make some noise when they die..\n        spaz.playBigDeathSound = True\n        \n    def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None):\n        \"\"\"\n        Create and wire up a bs.PlayerSpaz for the provide bs.Player.\n        \"\"\"\n        position = self.getMap().getFFAStartPosition(self.players)\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n\n        lightColor = bsUtils.getNormalizedColor(color)\n        displayColor = bs.getSafeColor(color,targetIntensity=0.75)\n        spaz = PlayerSpaz_Infection(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n\n        # we want a bigger area-of-interest in co-op mode\n        # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n        # else: spaz.node.areaOfInterestRadius = 5.0\n\n        # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n        # collide with the player-walls\n        # FIXME; need to generalize this\n        if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n        \n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer()\n        self.scoreSet.playerGotNewSpaz(player,spaz)\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound,1,position=spaz.node.position)\n        light = bs.newNode('light',attrs={'color':lightColor})\n        spaz.node.connectAttr('position',light,'position')\n        bsUtils.animate(light,'intensity',{0:0,250:1,500:0})\n        bs.gameTimer(500,light.delete)\n        return spaz\n    def getRandomPowerupPoint(self):\n        #So far, randomized points only figured out for mostly rectangular maps.\n        #Boxes will still fall through holes, but shouldn't be terrible problem (hopefully)\n        #If you add stuff here, need to add to \"supported maps\" above.\n        #['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium']\n        myMap = self.getMap().getName()\n        #print(myMap)\n        if myMap == 'Doom Shroom':\n            while True:\n                x = random.uniform(-1.0,1.0)\n                y = random.uniform(-1.0,1.0)\n                if x*x+y*y < 1.0: break\n            return ((8.0*x,2.5,-3.5+5.0*y))\n        elif myMap == 'Rampage':\n            x = random.uniform(-6.0,7.0)\n            y = random.uniform(-6.0,-2.5)\n            return ((x, 5.2, y))\n        elif myMap == 'Hockey Stadium':\n            x = random.uniform(-11.5,11.5)\n            y = random.uniform(-4.5,4.5)\n            return ((x, 0.2, y))\n        elif myMap == 'Courtyard':\n            x = random.uniform(-4.3,4.3)\n            y = random.uniform(-4.4,0.3)\n            return ((x, 3.0, y))\n        elif myMap == 'Crag Castle':\n            x = random.uniform(-6.7,8.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 10.0, y))\n        elif myMap == 'Big G':\n            x = random.uniform(-8.7,8.0)\n            y = random.uniform(-7.5,6.5)\n            return ((x, 3.5, y))\n        elif myMap == 'Football Stadium':\n            x = random.uniform(-12.5,12.5)\n            y = random.uniform(-5.0,5.5)\n            return ((x, 0.32, y))\n        else:\n            x = random.uniform(-5.0,5.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 8.0, y))\n    def endGame(self):\n\n        curTime = bs.getGameTime()\n\n        # mark 'death-time' as now for any still-living players\n        # and award players points for how long they lasted.\n        # (these per-player scores are only meaningful in team-games)\n        for team in self.teams:\n            for player in team.players:\n\n                # throw an extra fudge factor +1 in so teams that\n                # didn't die come out ahead of teams that did\n                if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime+1\n                    \n                # award a per-player score depending on how many seconds they lasted\n                # (per-player scores only affect teams mode; everywhere else just looks at the per-team score)\n                score = (player.gameData['deathTime']-self._timer.getStartTime())/1000\n                if 'deathTime' not in player.gameData: score += 50 # a bit extra for survivors\n                self.scoreSet.playerScored(player,score,screenMessage=False)\n\n        # stop updating our time text, and set the final time to match\n        # exactly when our last guy died.\n        self._timer.stop(endTime=self._lastPlayerDeathTime)\n        \n        # ok now calc game results: set a score for each team and then tell the game to end\n        results = bs.TeamGameResults()\n\n        # remember that 'free-for-all' mode is simply a special form of 'teams' mode\n        # where each player gets their own team, so we can just always deal in teams\n        # and have all cases covered\n        for team in self.teams:\n\n            # set the team score to the max time survived by any player on that team\n            longestLife = 0\n            for player in team.players:\n                longestLife = max(longestLife,(player.gameData['deathTime'] - self._timer.getStartTime()))\n            results.setTeamScore(team,longestLife)\n\n        self.end(results=results)\n\n\n\n\n"
  },
  {
    "path": "mods/JumpingContest.py",
    "content": "# Jumping Contest\n\n# This simple game tests each player's ability in an underappreciated aspect of the game: jumping.\nimport bs\nimport bsUtils\nimport random\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [JumpingContest]\n\ndef bsGetLevels():\n    return [bs.Level('Jumping Contest',\n                     displayName='${GAME}',\n                     gameType=JumpingContest,\n                     settings={},\n                     previewTexName='courtyardPreview')]\n\n\nclass RaceTimer:\n\t# the race timer to start things off\n\tdef __init__(self, incTime=1000):\n\t\tlightY = 150\n\t\tself.pos = 0\n\t\tself._beep1Sound = bs.getSound('raceBeep1')\n\t\tself._beep2Sound = bs.getSound('raceBeep2')\n\t\tself.lights = []\n\t\tfor i in range(4):\n\t\t\tl = bs.newNode('image',\n\t\t\t\t\t\t   attrs={'texture':bs.getTexture('nub'),\n\t\t\t\t\t\t\t\t  'opacity':1.0,\n\t\t\t\t\t\t\t\t  'absoluteScale':True,\n\t\t\t\t\t\t\t\t  'position':(-75+i*50, lightY),\n\t\t\t\t\t\t\t\t  'scale':(50, 50),\n\t\t\t\t\t\t\t\t  'attach':'center'})\n\t\t\tbs.animate(l, 'opacity', {10:0, 1000:1.0})\n\t\t\tself.lights.append(l)\n\t\tself.lights[0].color = (0.2, 0, 0)\n\t\tself.lights[1].color = (0.2, 0, 0)\n\t\tself.lights[2].color = (0.2, 0.05, 0)\n\t\tself.lights[3].color = (0.0, 0.3, 0)\n\t\tself.cases = {1: self._doLight1, 2: self._doLight2, 3: self._doLight3, 4: self._doLight4}\n\t\tself.incTimer = None\n\t\tself.incTime = incTime\n\n\tdef start(self):\n\t\tself.incTimer = bs.Timer(self.incTime, bs.WeakCall(self.increment), timeType=\"game\", repeat=True)\n\n\tdef _doLight1(self):\n\t\tself.lights[0].color = (1.0, 0, 0)\n\t\tbs.playSound(self._beep1Sound)\n\n\tdef _doLight2(self):\n\t\tself.lights[1].color = (1.0, 0, 0)\n\t\tbs.playSound(self._beep1Sound)\n\n\tdef _doLight3(self):\n\t\tself.lights[2].color = (1.0, 0.3, 0)\n\t\tbs.playSound(self._beep1Sound)\n\n\tdef _doLight4(self):\n\t\tself.lights[3].color = (0.0, 1.0, 0)\n\t\tbs.playSound(self._beep2Sound)\n\t\tfor l in self.lights:\n\t\t\tbs.animate(l, 'opacity', {0: 1.0, 1000: 0.0})\n\t\t\tbs.gameTimer(1000, l.delete)\n\t\tself.incTimer = None\n\t\tself.onFinish()\n\t\tdel self\n\n\tdef onFinish(self):\n\t\tpass\n\n\tdef onIncrement(self):\n\t\tpass\n\n\tdef increment(self):\n\t\tself.pos += 1\n\t\tif self.pos in self.cases:\n\t\t\tself.cases[self.pos]()\n\t\tself.onIncrement()\n\nclass JumpSpaz(bs.PlayerSpaz):\n    def onMove(self,x,y):\n        pass\n    def onMoveLeftRight(self,value):\n        pass\n    def onMoveUpDown(self,value):\n        pass\n    def onPunchPress(self):\n        self.getActivity().setEndHeight(self)\n    def onJumpPress(self):\n        self.getActivity().setStartHeight(self)\n        bs.PlayerSpaz.onJumpPress(self)\n\nclass JumpingContest(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return \"Jumping Contest\"\n\n    @classmethod\n    def getDescription(cls, sessionType):\n        return \"Jump as high as you can.\"\n\n    @classmethod\n    def getScoreInfo(cls):\n        return{'scoreType':'points'}\n\n    @classmethod\n    def getSettings(cls, sessionType):\n        return [(\"Epic Mode\", {'default': False})]\n    \n    @classmethod\n    def getSupportedMaps(cls, sessionType):\n        listy = bs.getMapsSupportingPlayType('melee')\n        listy.remove(\"Happy Thoughts\")\n        return listy\n\n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if issubclass(sessionType, bs.FreeForAllSession) or issubclass(sessionType, bs.TeamsSession) else False\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        self.called = False\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n        self._scoredis = bs.ScoreBoard()\n        self.timer = bs.OnScreenCountdown(30,self.endGame)\n        \n        \n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self,music='FlagCatcher')\n\n    def getInstanceScoreBoardDescription(self):\n        return ('Punch to lock in your score')\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        s = self.settings\n        bs.gameTimer(3500, bs.Call(self.doRaceTimer))\n        for team in self.teams:\n            team.gameData['score'] = 0\n        self.updateScore()\n\n    def onPlayerJoin(self, player):\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            self.checkEnd()\n            return\n        else:\n            self.spawnPlayerSpaz(player)\n    def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None):\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n\n        lightColor = bsUtils.getNormalizedColor(color)\n        displayColor = bs.getSafeColor(color,targetIntensity=0.75)\n        position = self.getMap().getFFAStartPosition(self.players)\n        spaz = JumpSpaz(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n        spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360)))\n\n    def handleMessage(self, m):\n        if isinstance(m, bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m)\n            m.spaz.getPlayer().actor.disconnectControlsFromPlayer()\n            m.spaz.getPlayer().sessionData['score'] = 0\n\n    def updateScore(self):\n        for team in self.teams:\n            self._scoredis.setTeamValue(team,round(team.gameData['score'],2))\n\n    def startJump(self):\n        for player in self.players:\n            player.actor.connectControlsToPlayer(enableBomb=False, enablePunch=True, enablePickUp=False, enableRun=False, enableFly = False)\n            player.sessionData['jumped'] = False\n        self.timer.start()\n        self.backupTimer = bs.gameTimer(30000,self.backupEnd)\n\n    def doRaceTimer(self):\n\tself.raceTimer = RaceTimer()\n\tbs.gameTimer(1000, bs.Call(self.raceTimer.start))\n\tself.raceTimer.onFinish = bs.WeakCall(self.startJump)\n\n    def setStartHeight(self, player):\n        player = player.getPlayer()\n        player.sessionData['height'] = player.actor.node.positionCenter[1]\n        player.sessionData['jumped'] = True\n\n    def setEndHeight(self,player):\n        player = player.getPlayer()\n        if not player.sessionData['jumped']: return\n        player.sessionData['score'] = (player.actor.node.positionCenter[1] - player.sessionData['height']) * 10\n        if player.sessionData['score'] > player.getTeam().gameData['score']: player.getTeam().gameData['score'] = player.sessionData['score']\n        self.updateScore()\n\n    def backupEnd(self):\n        if not self.called: self.endGame()\n\n    def endGame(self):\n        self.called = True\n        results = bs.TeamGameResults()\n        for team in self.teams:\n            results.setTeamScore(team, round(team.gameData['score'],2))\n        self.end(results=results)\n"
  },
  {
    "path": "mods/LandGrab.json",
    "content": "{\n  \"name\": \"Land Grab\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/LandGrab.py",
    "content": "import bs\nimport random\nimport math\nimport bsUtils\nimport bsBomb\nimport bsVector\nimport bsSpaz\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [LandGrab]\n\nclass PlayerSpaz_Grab(bs.PlayerSpaz):\n\n    def dropBomb(self):\n        \"\"\"\n        Tell the spaz to drop one of his bombs, and returns\n        the resulting bomb object.\n        If the spaz has no bombs or is otherwise unable to\n        drop a bomb, returns None.\n        \n        Overridden for Land Grab: \n        -Add condition for mineTimeout,\n        -make it create myMine instead of regular mine\n        -set this spaz's last mine time to current time\n        -Don't decrement LandMineCount.  We'll set to 0 when spaz double-punches.\n        \"\"\"\n        t = bs.getGameTime()\n        if ((self.landMineCount <= 0 or t-self.lastMine < self.mineTimeout) and self.bombCount <= 0) or self.frozen: return\n        p = self.node.positionForward\n        v = self.node.velocity\n\n        if self.landMineCount > 0:\n            droppingBomb = False\n            #self.setLandMineCount(self.landMineCount-1) #Don't decrement mine count. Unlimited mines.\n            if t - self.lastMine < self.mineTimeout:\n                return #Last time we dropped  mine was too short ago. Don't drop another one.\n            else:\n                self.lastMine = t\n                self.node.billboardCrossOut = True\n                bs.gameTimer(self.mineTimeout,bs.WeakCall(self.unCrossBillboard))\n                bomb = myMine(pos=(p[0],p[1] - 0.0,p[2]),\n                           vel=(v[0],v[1],v[2]),\n                           bRad=self.blastRadius,\n                           sPlay=self.sourcePlayer,\n                           own=self.node).autoRetain()\n                self.getPlayer().gameData['mines'].append(bomb)\n        elif self.dropEggs:\n            if len(self.getPlayer().gameData['bots']) > 0 : return #Only allow one snowman at a time.\n            droppingBomb = True\n            bomb = Egg(position=(p[0],p[1] - 0.0,p[2]), sourcePlayer=self.sourcePlayer,owner=self.node).autoRetain()\n            \n        else:\n            droppingBomb = True\n            bombType = self.bombType\n\n            bomb = bs.Bomb(position=(p[0],p[1] - 0.0,p[2]),\n                       velocity=(v[0],v[1],v[2]),\n                       bombType=bombType,\n                       blastRadius=self.blastRadius,\n                       sourcePlayer=self.sourcePlayer,\n                       owner=self.node).autoRetain()\n\n        if droppingBomb:\n            self.bombCount -= 1\n            bomb.node.addDeathAction(bs.WeakCall(self.handleMessage,bsSpaz._BombDiedMessage()))\n            if not self.eggsHatch:\n                bomb.hatch = False\n            else:\n                bomb.hatch = True\n        self._pickUp(bomb.node)\n\n        for c in self._droppedBombCallbacks: c(self,bomb)\n        \n        return bomb\n    def unCrossBillboard(self):\n        if self.node.exists():\n            self.node.billboardCrossOut = False\n    def onPunchPress(self):\n        \"\"\"\n        Called to 'press punch' on this spaz;\n        used for player or AI connections.\n        Override for land grab: catch double-punch to switch bombs!\n        \"\"\"\n        if not self.node.exists() or self.frozen or self.node.knockout > 0.0: return\n        \n        if self.punchCallback is not None:\n            self.punchCallback(self)\n        t = bs.getGameTime()\n        self._punchedNodes = set() # reset this..\n        ########This catches punches and switches between bombs and mines\n        #if t - self.lastPunchTime < 500:\n        if self.landMineCount < 1:\n            self.landMineCount = 1\n            bs.animate(self.node,\"billboardOpacity\",{0:0.0,100:1.0,400:1.0})\n        else:\n            self.landMineCount = 0\n            bs.animate(self.node,\"billboardOpacity\",{0:1.0,400:0.0})\n        if t - self.lastPunchTime > self._punchCooldown:\n            self.lastPunchTime = t\n            self.node.punchPressed = True\n            if not self.node.holdNode.exists():\n                bs.gameTimer(100,bs.WeakCall(self._safePlaySound,self.getFactory().swishSound,0.8))\n    def handleMessage(self, m):\n        #print m.sourcePlayer\n        if isinstance(m, bs.HitMessage):\n            #print m.sourcePlayer.getName()\n            if not self.node.exists():\n                return True\n            if m.sourcePlayer != self.getPlayer():\n                return True\n            else:\n                super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\nclass myMine(bs.Bomb):\n    #reason for the mine class is so we can intercept messages.\n    def __init__(self,pos,vel,bRad,sPlay,own):\n        bs.Bomb.__init__(self,position=pos,velocity=vel,bombType='landMine',blastRadius=bRad,sourcePlayer=sPlay,owner=own)\n        self.isHome = False\n        self.died = False\n        self.activated = False\n        self.defRad = self.getActivity().claimRad\n        self.rad = 0.0# Will set to self.getActivity().settings['Claim Size'] when arming\n        #Don't do this until mine arms\n        self.zone = None \n        fm = bs.getSharedObject('footingMaterial')\n        materials = getattr(self.node,'materials')\n        if not fm in materials:\n            setattr(self.node,'materials',materials + (fm,))\n    \n    def handleMessage(self,m):\n        if isinstance(m,bsBomb.ArmMessage): \n            self.arm()#This is all the  main bs.Bomb does.  All below is extra\n            self.activateArea()\n        elif isinstance(m, bs.HitMessage):\n            #print m.hitType, m.hitSubType\n            if self.isHome: return True\n            if m.sourcePlayer == self.sourcePlayer:\n                return True #I think this should stop mines from exploding due to self activity or chain reactions?.\n            if not self.activated: return True\n            else:\n                super(self.__class__, self).handleMessage(m)\n        elif isinstance(m,bsBomb.ImpactMessage):\n            if self.isHome: return True #Never explode the home bomb.\n            super(self.__class__, self).handleMessage(m)\n        elif isinstance(m,bs.DieMessage):\n            if self.isHome: return True #Home never dies (even if player leaves, I guess...)\n            if self.exists() and not self.died:\n                self.died = True\n                self.rad = 0.0\n                if self.zone.exists():\n                    bs.animateArray(self.zone,'size',1,{0:[2*self.rad],1:[0]})\n                self.zone = None\n            super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\n    \n    def activateArea(self):\n        mineOK = False\n        if self.exists():\n            r = self.defRad\n            fudge = self.getActivity().minOverlap #This is the minimum overlap to join owner's territory (not used to check enemy overlap)\n            p1 = self.node.position\n            self.node.maxSpeed = 0.0 #We don't want mines moving around. They could leave their zone.\n            self.damping = 100\n        #First, confirm that this mine \"touches\" owner's mines\n        if self.sourcePlayer.exists():\n            for m in self.sourcePlayer.gameData['mines']:\n                if m.exists() and not m.died:\n                    if m.rad != 0: #Don't check un-activated mines\n                        p2 = m.node.position\n                        diff = (bs.Vector(p1[0]-p2[0],0.0,p1[2]-p2[2]))\n                        dist = (diff.length())\n                        if dist < (m.rad + r)-fudge: #We check m.rad just in case it's somehow different. However, this probably shouldn't happen. Unless I change gameplay later.\n                            mineOK = True #mine adjoins owner's territory. Will set to false if it also adjoin's enemy though.\n                            break #Get out of the loop\n        takeovers = []\n        if mineOK:\n            for p in self.getActivity().players:\n                if not p is self.sourcePlayer:\n                    if p.exists():\n                        for m in p.gameData['mines']:\n                            if m.rad != 0.0: #Don't check un-activated mines\n                                p2 = m.node.position\n                                diff = (bs.Vector(p1[0]-p2[0],0.0,p1[2]-p2[2]))\n                                dist = (diff.length())\n                                if dist < m.rad + r: #We check m.rad just in case it's somehowdifferent. However, this probably shouldn't happen. Unless I change gameplay later.\n                                    mineOK = False\n                                    takeovers = []\n                                    break\n\n        #If we made it to here and mineOK is true, we can activate.  Otherwise, we'll flash red and die.\n        self.zone = bs.newNode('locator',attrs={'shape':'circle','position':self.node.position,'color':self.sourcePlayer.color,'opacity':0.5,'drawBeauty':False,'additive':True})\n        bs.animateArray(self.zone,'size',1,{0:[0.0],150:[2*r]}) #Make circle at the default radius to show players where it would go if OK\n        if mineOK or self.isHome:\n            self.activated = True\n            self.rad = r #Immediately set this mine's radius\n        else: #mine was not OK\n            keys = {0:(1,0,0),49:(1,0,0),50:(1,1,1),100:(0,1,0)}\n            bs.animateArray(self.zone,'color',3,keys,loop=True)\n            bs.gameTimer(800, bs.WeakCall(self.handleMessage, bs.DieMessage()), repeat=False)\n        #Takeovers didn't work so well.  Very confusing.\n        #if len(takeovers) > 0:\n        #    #Flash it red and kill it\n        #    for m in takeovers:\n        #        if m.exists():\n        #            if not m._exploded:\n        #                if not m.died:\n        #                    keys = {0:(1,0,0),49:(1,0,0),50:(1,1,1),100:(0,1,0)}\n        #                    if m.zone.exists():\n        #                        bs.animateArray(m.zone,'color',3,keys,loop=True)\n        #                    bs.gameTimer(800, bs.WeakCall(m.handleMessage, bs.DieMessage()), repeat=False)\n    def _handleHit(self,m):\n        #This one is overloaded to prevent chaining of explosions\n        isPunch = (m.srcNode.exists() and m.srcNode.getNodeType() == 'spaz')\n\n        # normal bombs are triggered by non-punch impacts..  impact-bombs by all impacts\n        if not self._exploded and not isPunch or self.bombType in ['impact','landMine']:\n            # also lets change the owner of the bomb to whoever is setting us off..\n            # (this way points for big chain reactions go to the person causing them)\n            if m.sourcePlayer not in [None]:\n                #self.sourcePlayer = m.sourcePlayer\n\n                # also inherit the hit type (if a landmine sets off by a bomb, the credit should go to the mine)\n                # the exception is TNT.  TNT always gets credit.\n                #if self.bombType != 'tnt':\n                #    self.hitType = m.hitType\n                #    self.hitSubType = m.hitSubType\n                pass\n            bs.gameTimer(100+int(random.random()*100),bs.WeakCall(self.handleMessage,bsBomb.ExplodeMessage()))\n        self.node.handleMessage(\"impulse\",m.pos[0],m.pos[1],m.pos[2],\n                                m.velocity[0],m.velocity[1],m.velocity[2],\n                                m.magnitude,m.velocityMagnitude,m.radius,0,m.velocity[0],m.velocity[1],m.velocity[2])\n\n        if m.srcNode.exists():\n            pass\n            #print 'FIXME HANDLE KICKBACK ON BOMB IMPACT'\n            # bs.nodeMessage(m.srcNode,\"impulse\",m.srcBody,m.pos[0],m.pos[1],m.pos[2],\n            #                     -0.5*m.force[0],-0.75*m.force[1],-0.5*m.force[2])\n    def _handleImpact(self,m):\n        #This is overridden so that we can keep from exploding due to own player's activity.\n        node,body = bs.getCollisionInfo(\"opposingNode\",\"opposingBody\")\n        # if we're an impact bomb and we came from this node, don't explode...\n        # alternately if we're hitting another impact-bomb from the same source, don't explode...\n        \n        try: nodeDelegate = node.getDelegate() #This could be a bomb or a spaz (or none)\n        except Exception: nodeDelegate = None\n        if node is not None and node.exists():\n            if isinstance(nodeDelegate, PlayerSpaz_Grab):\n                if nodeDelegate.getPlayer() is self.sourcePlayer:\n                    #print(\"Hit by own self, don't blow\")\n                    return True\n            if (node is self.owner) or ((isinstance(nodeDelegate,bs.Bomb) or isinstance(nodeDelegate, Egg) or isinstance(nodeDelegate,bs.SpazBot)) and nodeDelegate.sourcePlayer is self.sourcePlayer): \n                #print(\"Hit by owr own bomb\")\n                return\n            else: \n                #print 'exploded handling impact'\n                self.handleMessage(bsBomb.ExplodeMessage())            \n\nclass Egg(bs.Actor):\n\n    def __init__(self, position=(0,1,0), sourcePlayer=None, owner=None):\n        bs.Actor.__init__(self)\n\n        activity = self.getActivity()\n        \n        # spawn just above the provided point\n        self._spawnPos = (position[0], position[1]+1.0, position[2])\n        #This line was replaced by 'color' belwo: 'colorTexture': bsBomb.BombFactory().impactTex,\n        self.node = bs.newNode(\"prop\",\n                               attrs={'model': activity._ballModel,\n                                      'body':'sphere',\n                                      'colorTexture': bs.getTexture(\"frostyColor\"),\n                                      'reflection':'soft',\n                                      'modelScale':2.0,\n                                      'bodyScale':2.0,\n                                      'density':0.08,\n                                      'reflectionScale':[0.15],\n                                      'shadowSize': 0.6,\n                                      'position':self._spawnPos,\n                                      'materials': [bs.getSharedObject('objectMaterial'),activity._bombMat]\n                                      },\n                               delegate=self)\n        self.sourcePlayer = sourcePlayer\n        self.owner = owner\n    def handleMessage(self,m):\n        if isinstance(m,bs.DieMessage):\n            self.node.delete()\n        elif isinstance(m,bs.DroppedMessage): self._handleDropped(m)\n        elif isinstance(m,bs.OutOfBoundsMessage):\n            self.handleMessage(bs.DieMessage())\n        elif isinstance(m,bs.HitMessage):\n            self.node.handleMessage(\"impulse\",m.pos[0],m.pos[1],m.pos[2],\n                                    m.velocity[0],m.velocity[1],m.velocity[2],\n                                    1.0*m.magnitude,1.0*m.velocityMagnitude,m.radius,0,\n                                    m.forceDirection[0],m.forceDirection[1],m.forceDirection[2])\n        else:\n            bs.Actor.handleMessage(self,m)\n    def _handleDropped(self,m):\n        if self.exists():\n            bs.gameTimer(int(self.getActivity().settings['Egg Lifetime']*1000),self._disappear)\n    def _disappear(self):\n        if self.node.exists():\n            scl = self.node.modelScale\n            bsUtils.animate(self.node,\"modelScale\",{0:scl*1.0, 300:scl*0.5, 500:0.0})\n            self.maxSpeed = 0\n            if self.hatch and self.sourcePlayer.exists():\n                if len(self.sourcePlayer.gameData['bots']) < 3:\n                    self.materials = []\n                    p = self.node.position\n                    #self.getActivity()._bots.spawnBot(ToughGuyFrostBot,pos=(p[0],p[1]-0.8,p[2]),spawnTime=0, onSpawnCall=self.setupFrosty)\n                    self.sourcePlayer.gameData['bset'].spawnBot(ToughGuyFrostBot,pos=(p[0],p[1]-0.8,p[2]),spawnTime=0, onSpawnCall=self.setupFrosty)\n            bs.gameTimer(550,bs.WeakCall(self.handleMessage,bs.DieMessage()))\n    def setupFrosty(self,spaz):\n        spaz.sourcePlayer = self.sourcePlayer\n        spaz.sourcePlayer.gameData['bots'].append(spaz)\n        bs.gameTimer(5000,bs.WeakCall(spaz.handleMessage,bs.DieMessage())) #Kill spaz after 5 seconds\n        #bsUtils.animate(spaz.node, \"modelScale\",{0:0.1, 500:0.3, 800:1.2, 1000:1.0})\n\nclass zBotSet(bs.BotSet):   #the botset is overloaded to prevent adding players to the bots' targets if they are zombies too.         \n    def startMoving(self): #here we overload the default startMoving, which normally calls _update.\n        #self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self._update),repeat=True)\n        self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self.zUpdate),repeat=True)\n        \n    def zUpdate(self):\n\n        # update one of our bot lists each time through..\n        # first off, remove dead bots from the list\n        # (we check exists() here instead of dead.. we want to keep them around even if they're just a corpse)\n        #####This is overloaded from bsSpaz to walk over other players' mines, but not source player.\n        try:\n            botList = self._botLists[self._botUpdateList] = [b for b in self._botLists[self._botUpdateList] if b.exists()]\n        except Exception:\n            bs.printException(\"error updating bot list: \"+str(self._botLists[self._botUpdateList]))\n        self._botUpdateList = (self._botUpdateList+1)%self._botListCount\n\n        # update our list of player points for the bots to use\n        playerPts = []\n        for player in bs.getActivity().players:\n            try:\n                if player.exists():\n                    if not player is self.sourcePlayer:  #If the player has lives, add to attack points\n                        for m in player.gameData['mines']:\n                            if not m.isHome and m.exists():\n                                playerPts.append((bs.Vector(*m.node.position),\n                                        bs.Vector(0,0,0)))\n            except Exception:\n                bs.printException('error on bot-set _update')\n\n        for b in botList:\n            b._setPlayerPts(playerPts)\n            b._updateAI()\n        \nclass ToughGuyFrostBot(bsSpaz.SpazBot):\n    \"\"\"\n    category: Bot Classes\n    \n    A manly bot who walks and punches things.\n    \"\"\"\n    character = 'Frosty'\n    color = (1,1,1)\n    highlight = (1,1,1)\n    punchiness = 0.0\n    chargeDistMax = 9999.0\n    chargeSpeedMin = 1.0\n    chargeSpeedMax = 1.0\n    throwDistMin = 9999\n    throwDistMax = 9999\n    \n    def handleMessage(self,m):\n        if isinstance(m, bs.PickedUpMessage):\n            self.handleMessage(bs.DieMessage())\n        super(self.__class__, self).handleMessage(m)\n\n    \nclass LandGrab(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Land Grab'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'score',\n                'scoreType':'points',\n                'noneIsWinner':False,\n                'lowerIsBetter':False}\n                \n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if issubclass(sessionType,bs.FreeForAllSession) else False\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Grow your territory'\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Crag Castle', 'Big G', 'Football Stadium']\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        return [(\"Claim Size\",{'minValue':2,'default':5,'increment':1}),\n                (\"Min Sec btw Claims\",{'minValue':1,'default':3,'increment':1}),\n                (\"Eggs Not Bombs\",{'default':True}),\n                (\"Snowman Eggs\",{'default':True}),\n                (\"Egg Lifetime\",{'minValue':0.5,'default':2.0,'increment':0.5}),\n                (\"Time Limit\",{'choices':[('30 Seconds',30),('1 Minute',60),\n                                            ('90 Seconds',90),('2 Minutes',120),\n                                            ('3 Minutes',180),('5 Minutes',300)],'default':60}),\n                (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                (\"Epic Mode\",{'default':False})]\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self, settings)\n\n        if self.settings['Epic Mode']: self._isSlowMotion = True        \n        # print messages when players die (since its meaningful in this game)\n        self.announcePlayerDeaths = True\n        self._scoreBoard = bs.ScoreBoard()\n        #self._lastPlayerDeathTime = None    \n\n        self.minOverlap = 0.2 # This is the minimum amount of linear overlap for a spaz's own area to guarantee they can walk to it\n        self.claimRad = math.sqrt(self.settings['Claim Size']/3.1416) #This is so that the settings can be in units of area, same as score\n        self.updateRate = 200 #update the mine radii etc every this many milliseconds\n        #This game's score calculation is very processor intensive.\n        #Score only updated 2x per second during game, at lower resolution\n        self.scoreUpdateRate = 1000\n        self.inGameScoreRes = 40\n        self.finalScoreRes = 300\n        self._eggModel = bs.getModel('egg')\n        try: myFactory = self._sharedSpazFactory\n        except Exception:\n            myFactory = self._sharedSpazFactory = bsSpaz.SpazFactory()\n        m=myFactory._getMedia('Frosty')\n        self._ballModel = m['pelvisModel']\n        self._bombMat = bsBomb.BombFactory().bombMaterial\n        self._mineIconTex=bs.Powerup.getFactory().texLandMines\n\n    def getInstanceDescription(self):\n        return ('Control territory with mines')\n\n    def getInstanceScoreBoardDescription(self):\n        return ('Control the most territory with mines\\nDouble punch to switch between mines and bombs\\n')\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n        self._startGameTime = bs.getGameTime()\n        \n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        bs.gameTimer(self.scoreUpdateRate, bs.WeakCall(self._updateScoreBoard), repeat=True)\n        bs.gameTimer(1000, bs.WeakCall(self.startUpdating), repeat=False)#Delay to allow for home mine to spawn\n        #self._bots = bs.BotSet() \n        # check for immediate end (if we've only got 1 player, etc)\n        #bs.gameTimer(5000, self._checkEndGame)\n\n    def onTeamJoin(self,team):\n        team.gameData['spawnOrder'] = []\n        team.gameData['score'] = 0        \n        \n    def onPlayerJoin(self, player):\n        # don't allow joining after we start\n        # (would enable leave/rejoin tomfoolery)\n        player.gameData['mines'] = []\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            # for score purposes, mark them as having died right as the game started\n            #player.gameData['deathTime'] = self._timer.getStartTime()\n            return\n        player.gameData['home'] = None\n        player.gameData['bots'] = []\n        player.gameData['bset'] = zBotSet()\n        player.gameData['bset'].sourcePlayer = player\n        self.spawnPlayer(player)\n        \n    def onPlayerLeave(self, player):\n         # augment default behavior...\n        for m in player.gameData['mines']:\n            m.handleMessage(bs.DieMessage())\n        player.gameData['mines'] = []\n        bs.TeamGameActivity.onPlayerLeave(self, player)\n        # a departing player may trigger game-over\n        self._checkEndGame()\n\n    def startUpdating(self):\n        bs.gameTimer(self.updateRate, bs.WeakCall(self.mineUpdate), repeat=True)\n\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            team.gameData['score'] = self.areaCalc(team,self.inGameScoreRes)\n            self._scoreBoard.setTeamValue(team,team.gameData['score'])\n        \n    def mineUpdate(self):\n        for player in self.players:\n            #Need to purge mines, whether or not player is living\n            for m in player.gameData['mines']:\n                if not m.exists():\n                    player.gameData['mines'].remove(m)\n            if not player.actor is None:\n                if player.actor.isAlive():\n                    pSafe = False\n                    p1 = player.actor.node.position\n                    for teamP in player.getTeam().players:\n                        for m in teamP.gameData['mines']:\n                            if m.exists():\n                                if not m._exploded:\n                                    p2 = m.node.position\n                                    diff = (bs.Vector(p1[0]-p2[0],0.0,p1[2]-p2[2]))\n                                    dist = (diff.length())\n                                    if dist < m.rad:\n                                        pSafe = True\n                                        break\n                    if not pSafe:\n                        #print player.getName(), \"died with mines:\", len(player.gameData['mines'])\n                        player.actor.handleMessage(bs.DieMessage())\n        \n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for t in self.teams: results.setTeamScore(t,t.gameData['score'])\n        self.end(results=results,announceDelay=800)\n        \n    def _flashPlayer(self,player,scale):\n        pos = player.actor.node.position\n        light = bs.newNode('light',\n                           attrs={'position':pos,\n                                  'color':(1,1,0),\n                                  'heightAttenuated':False,\n                                  'radius':0.4})\n        bs.gameTimer(500,light.delete)\n        bs.animate(light,'intensity',{0:0,100:1.0*scale,500:0})\n\n\n    def handleMessage(self,m):\n\n        if isinstance(m, bs.SpazBotDeathMessage):\n            if m.badGuy.sourcePlayer.exists():\n                m.badGuy.sourcePlayer.gameData['bots'].remove(m.badGuy)\n        elif isinstance(m,bs.PlayerSpazDeathMessage):\n\n            bs.TeamGameActivity.handleMessage(self,m) # (augment standard behavior)\n            self.respawnPlayer(m.spaz.getPlayer())\n            #deathTime = bs.getGameTime()\n            \n            # record the player's moment of death\n            #m.spaz.getPlayer().gameData['deathTime'] = deathTime\n\n            # in co-op mode, end the game the instant everyone dies (more accurate looking)\n            # in teams/ffa, allow a one-second fudge-factor so we can get more draws\n            #if isinstance(self.getSession(),bs.CoopSession):\n                # teams will still show up if we check now.. check in the next cycle\n            #    bs.pushCall(self._checkEndGame)\n            #    self._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock..\n            #else:\n                #bs.gameTimer(1000, self._checkEndGame)\n\n        else:\n            # default handler:\n            bs.TeamGameActivity.handleMessage(self,m)\n\n    def _checkEndGame(self):\n        livingTeamCount = 0\n        for team in self.teams:\n            for player in team.players:\n                if player.isAlive():\n                    livingTeamCount += 1\n                    break\n\n        # in co-op, we go till everyone is dead.. otherwise we go until one team remains\n        if isinstance(self.getSession(),bs.CoopSession):\n            if livingTeamCount <= 0: self.endGame()\n        else:\n            if livingTeamCount <= 1: self.endGame()\n\n    def spawnPlayer(self, player):\n        #Overloaded for this game to respawn at home instead of random FFA spots\n        if not player.exists():\n            bs.printError('spawnPlayer() called for nonexistant player')\n            return\n        if player.gameData['home'] is None:\n            pos = self.getMap().getFFAStartPosition(self.players)\n            bomb = myMine(pos,\n                           (0.0,0.0,0.0),\n                           0.0,\n                           player,\n                           None).autoRetain()\n            bomb.isHome = True\n            bomb.handleMessage(bsBomb.ArmMessage())\n            position = [pos[0],pos[1]+0.3,pos[2]]\n            player.gameData['home'] = position\n            player.gameData['mines'].append(bomb)\n        else:\n            position = player.gameData['home']\n        spaz = self.spawnPlayerSpaz(player, position)\n\n        # lets reconnect this player's controls to this\n        # spaz but *without* the ability to attack or pick stuff up\n        spaz.connectControlsToPlayer(enablePunch=True,\n                                     enableBomb=True,\n                                     enablePickUp=True)\n        #Wire up the spaz with mines\n        spaz.landMineCount = 1\n        spaz.node.billboardTexture = self._mineIconTex\n        bs.animate(spaz.node,\"billboardOpacity\",{0:0.0,100:1.0,400:1.0})\n        t = bs.getGameTime()\n        if t - spaz.lastMine < spaz.mineTimeout:\n            spaz.node.billboardCrossOut = True\n            bs.gameTimer((spaz.mineTimeout-t+spaz.lastMine),bs.WeakCall(spaz.unCrossBillboard))\n        spaz.dropEggs = self.settings['Eggs Not Bombs']\n        spaz.eggsHatch = self.settings['Snowman Eggs']\n\n        # also lets have them make some noise when they die..\n        spaz.playBigDeathSound = True  \n      \n    def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None):\n        \"\"\"\n        Create and wire up a bs.PlayerSpaz for the provide bs.Player.\n        \"\"\"\n        #position = self.getMap().getFFAStartPosition(self.players)\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n\n        lightColor = bsUtils.getNormalizedColor(color)\n        displayColor = bs.getSafeColor(color,targetIntensity=0.75)\n        spaz = PlayerSpaz_Grab(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n\n        # we want a bigger area-of-interest in co-op mode\n        # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n        # else: spaz.node.areaOfInterestRadius = 5.0\n\n        # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n        # collide with the player-walls\n        # FIXME; need to generalize this\n        if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n        \n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer()\n        \n        ###These special attributes are for Land Grab:\n        spaz.lastMine = 0\n        spaz.mineTimeout = self.settings['Min Sec btw Claims'] * 1000\n        \n        self.scoreSet.playerGotNewSpaz(player,spaz)\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound,1,position=spaz.node.position)\n        light = bs.newNode('light',attrs={'color':lightColor})\n        spaz.node.connectAttr('position',light,'position')\n        bsUtils.animate(light,'intensity',{0:0,250:1,500:0})\n        bs.gameTimer(500,light.delete)\n        return spaz\n    def getRandomPowerupPoint(self):\n        #So far, randomized points only figured out for mostly rectangular maps.\n        #Boxes will still fall through holes, but shouldn't be terrible problem (hopefully)\n        #If you add stuff here, need to add to \"supported maps\" above.\n        #['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium']\n        myMap = self.getMap().getName()\n        #print(myMap)\n        if myMap == 'Doom Shroom':\n            while True:\n                x = random.uniform(-1.0,1.0)\n                y = random.uniform(-1.0,1.0)\n                if x*x+y*y < 1.0: break\n            return ((8.0*x,2.5,-3.5+5.0*y))\n        elif myMap == 'Rampage':\n            x = random.uniform(-6.0,7.0)\n            y = random.uniform(-6.0,-2.5)\n            return ((x, 5.2, y))\n        elif myMap == 'Hockey Stadium':\n            x = random.uniform(-11.5,11.5)\n            y = random.uniform(-4.5,4.5)\n            return ((x, 0.2, y))\n        elif myMap == 'Courtyard':\n            x = random.uniform(-4.3,4.3)\n            y = random.uniform(-4.4,0.3)\n            return ((x, 3.0, y))\n        elif myMap == 'Crag Castle':\n            x = random.uniform(-6.7,8.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 10.0, y))\n        elif myMap == 'Big G':\n            x = random.uniform(-8.7,8.0)\n            y = random.uniform(-7.5,6.5)\n            return ((x, 3.5, y))\n        elif myMap == 'Football Stadium':\n            x = random.uniform(-12.5,12.5)\n            y = random.uniform(-5.0,5.5)\n            return ((x, 0.32, y))\n        else:\n            x = random.uniform(-5.0,5.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 8.0, y))\n\n    def areaCalc(self,team,res):\n        ##This routine calculates (well, approximates) the area covered by a team\n        ##and returns their score.  the \"res\" argument is the resolution.  Higher res,\n        ##better approximation.\n        ##Most of this code was stolen from rosettacode.org/wiki/Total_circles_area\n        circles = ()\n        for p in team.players:\n            for m in p.gameData['mines']:\n                if m.exists():\n                    if m.rad != 0:\n                        if not m._exploded:\n                            circles += ((m.node.position[0],m.node.position[2], m.rad),)\n        # compute the bounding box of the circles\n        if len(circles) == 0: return 0\n        x_min = min(c[0] - c[2] for c in circles)\n        x_max = max(c[0] + c[2] for c in circles)\n        y_min = min(c[1] - c[2] for c in circles)\n        y_max = max(c[1] + c[2] for c in circles)\n     \n        box_side = res\n     \n        dx = (x_max - x_min) / box_side\n        dy = (y_max - y_min) / box_side\n     \n        count = 0\n     \n        for r in xrange(box_side):\n            y = y_min + r * dy\n            for c in xrange(box_side):\n                x = x_min + c * dx\n                if any((x-circle[0])**2 + (y-circle[1])**2 <= (circle[2] ** 2)\n                       for circle in circles):\n                    count += 1\n     \n        return int(count * dx * dy  *10)    \n            \n    def endGame(self):\n\n        if self.hasEnded(): return\n        #sorryTxt = bsUtils.Text('Calculating final scores!...')\n        for team in self.teams:\n            team.gameData['score'] = str(round(self.areaCalc(team,self.finalScoreRes),2))\n        #sorryTxt.handleMessage(bs.DieMessage())\n        #print 'calc time:', (bs.getRealTime() - t)\n        bs.gameTimer(300, bs.Call(self.waitForScores))\n    def waitForScores(self):\n        results = bs.TeamGameResults()\n        self._vsText = None # kill our 'vs' if its there\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['score'])\n        self.end(results=results)\n\n\n\n\n"
  },
  {
    "path": "mods/Paint.py",
    "content": "#Canvas\n\nimport bs\nimport random\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [Paint]\n\ndef bsGetLevels():\n    return [bs.Level('Paint',\n                     displayName='${GAME}',\n                     gameType=Paint,\n                     settings={},\n                     previewTexName='courtyardPreview')]\n\n\nclass Dot(bs.Actor):\n    def __init__(self, position=(0,0,0), color=(0,0,0), radius=(.5)):\n        bs.Actor.__init__(self)\n        self._r1 = radius\n        if radius < 0: self._r1 = 0\n        self.position = position\n        self.color = color\n        n1 = bs.newNode('locator',attrs={'shape':'circle','position':position,\n                                         'color':self.color,'opacity':1,\n                                         'drawBeauty':True,'additive':True})\n        bs.animateArray(n1,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        self._node = [n1]\n\nclass Artist(bs.PlayerSpaz):\n    def __init__(self, color=(1,1,1), highlight=(0.5,0.5,0.5), character=\"Spaz\", sourcePlayer=None, startInvincible=True,\n                 canAcceptPowerups=True, powerupsExpire=False, demoMode=False):\n        self._player = sourcePlayer\n        self.mode = 'Draw'\n        self.dotRadius = .5\n        self.red = True\n        self.blue = True\n        self.green = True\n        self.value = 1\n        self.color = [1.0,0.0,0.0]\n        bs.PlayerSpaz.__init__(self, color, highlight, character, sourcePlayer, powerupsExpire)\n        \n    def onBombPress(self):\n        if self.mode == 'Draw':\n            self.dotRadius += .1\n            self.setScoreText(\"Radius: \" + str(self.dotRadius), (1,1,1))\n        elif self.mode == \"Color\":\n            if self.color[0] >= 1:\n                if self.color[2] == 0: self.color[1] += .1\n                else: self.color[2] -= .1\n            if self.color[1] >= 1:\n                if self.color[0] == 0: self.color[2] += .1\n                else: self.color[0] -= .1\n            if self.color[2] >= 1:\n                if self.color[1] == 0: self.color[0] += .1\n                else: self.color[1] -= .1\n                \n            for i in range(len(self.color)):\n                if self.color[i] < 0: self.color[i] = 0\n                if self.color[i] > 1: self.color[i] = 1\n\n            color = (self.color[0]*self.value, self.color[1]*self.value, self.color[2]*self.value) \n                    \n            self.setScoreText(\"COLOR\", color)\n    def onPunchPress(self):\n        if self.mode == 'Draw':\n            self.dotRadius -= .1\n            if self.dotRadius < .05: self.dotRadius = 0\n            self.setScoreText(\"Radius: \" + str(self.dotRadius), (1,1,1))\n        elif self.mode == \"Color\":\n            if self.color[0] >= 1:\n                if self.color[1] == 0: self.color[2] += .1\n                else: self.color[1] -= .1\n            if self.color[1] >= 1:\n                if self.color[2] == 0: self.color[0] += .1\n                else: self.color[2] -= .1\n            if self.color[2] >= 1:\n                if self.color[0] == 0: self.color[1] += .1\n                else: self.color[0] -= .1\n            for i in range(len(self.color)):\n                if self.color[i] < 0: self.color[i] = 0\n                if self.color[i] > 1: self.color[i] = 1\n            color = (self.color[0]*self.value, self.color[1]*self.value, self.color[2]*self.value) \n            self.setScoreText(\"COLOR\", color)\n    def onJumpPress(self):\n        if self.mode == 'Draw':\n            color = (self.color[0]*self.value, self.color[1]*self.value, self.color[2]*self.value)\n            pos = (self.node.positionCenter[0], self.node.positionCenter[1]-2, self.node.positionCenter[2])\n            dot = Dot(position=pos, color = color, radius=self.dotRadius)\n        elif self.mode == \"Color\":\n            self.value += .1\n            if self.value > 1 : self.value = 0\n            self.setScoreText(\"Value: \" + str(round(self.value,2)), (self.color[0]*self.value, self.color[1]*self.value, self.color[2]*self.value))\n    def onPickUpPress(self):\n        if self.mode == 'Draw': self.mode = 'Color'\n        elif self.mode == \"Color\": self.mode = \"Draw\"\n        self.setScoreText(self.mode + \" Mode\", (1,1,1))\n\nclass Paint(bs.CoopGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Paint'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreType':'points'}\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Create a masterpiece.'\n    \n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Doom Shroom']\n        \n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if issubclass(sessionType,bs.CoopSession) else False\n\n    def __init__(self, settings):\n        bs.CoopGameActivity.__init__(self, settings)\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n\n    def onTransitionIn(self):\n        bs.CoopGameActivity.onTransitionIn(self,music='ForwardMarch')\n\n    def onBegin(self):\n        bs.CoopGameActivity.onBegin(self)\n\n    def spawnPlayerSpaz(self,player,position=(0,5,-3),angle=None):\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n        spaz = Artist(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             sourcePlayer=player)\n        player.setActor(spaz)\n        player.actor.connectControlsToPlayer()\n        spaz.handleMessage(bs.StandMessage((0,3,0),90))\n"
  },
  {
    "path": "mods/Portal.json",
    "content": "{\n  \"name\": \"Portal powerup\",\n  \"author\": \"The Great\",\n  \"category\": \"libraries\"\n}"
  },
  {
    "path": "mods/Portal.py",
    "content": "import bs\nimport random\nimport bsUtils\nimport copy\nimport types\n\n# Following are necessary variables for portal\nmaxportals = 3\ncurrentnum = 0\nlastpos = [(0,0,0), (1, 0, 2)] # initial values for test\ndefi = [(0, 1, 2), (1, 0, 2)] # initial values for test\n\nclass Portal(bs.Actor):\n    def __init__(self,position1 = (0,1,0),color = (random.random(),random.random(),random.random()),r = 1.0,activity = None):\n        bs.Actor.__init__(self)\n        \n        self.radius = r\n        if position1 is None:\n            self.position1 = self.getRandomPosition(activity)\n        else :\n            self.position1 = position1\n        self.position2 = self.getRandomPosition(activity)\n        \n        self.portal1Material = bs.Material()\n        self.portal1Material.addActions(conditions=(('theyHaveMaterial', bs.getSharedObject('playerMaterial'))),actions=((\"modifyPartCollision\",\"collide\",True),\n                                                      (\"modifyPartCollision\",\"physical\",False),\n                                                      (\"call\",\"atConnect\", self.Portal1)))\n\n        self.portal2Material = bs.Material()\n        self.portal2Material.addActions(conditions=(('theyHaveMaterial', bs.getSharedObject('playerMaterial'))),actions=((\"modifyPartCollision\",\"collide\",True),\n                                                      (\"modifyPartCollision\",\"physical\",False),\n                                                      (\"call\",\"atConnect\", self.Portal2)))\n        # uncomment the following lines to teleport objects also\n        # self.portal1Material.addActions(conditions=(('theyHaveMaterial', bs.getSharedObject('objectMaterial')),'and',('theyDontHaveMaterial', bs.getSharedObject('playerMaterial'))),actions=((\"modifyPartCollision\",\"collide\",True),\n                                                      # (\"modifyPartCollision\",\"physical\",False),\n                                                      # (\"call\",\"atConnect\", self.objPortal1)))\n        # self.portal2Material.addActions(conditions=(('theyHaveMaterial', bs.getSharedObject('objectMaterial')),'and',('theyDontHaveMaterial', bs.getSharedObject('playerMaterial'))),actions=((\"modifyPartCollision\",\"collide\",True),\n                                                      # (\"modifyPartCollision\",\"physical\",False),\n                                                      # (\"call\",\"atConnect\", self.objPortal2)))\n                                                      \n                                                 \n        self.node1 = bs.newNode('region',\n                       attrs={'position':(self.position1[0],self.position1[1],self.position1[2]),\n                              'scale':(self.radius,self.radius,self.radius),\n                              'type':'sphere',\n                              'materials':[self.portal1Material]})\n        self.visualRadius = bs.newNode('shield',attrs={'position':self.position1,'color':color,'radius':0.1})\n        bsUtils.animate(self.visualRadius,\"radius\",{0:0,500:self.radius*2})\n        bsUtils.animateArray(self.node1,\"scale\",3,{0:(0,0,0),500:(self.radius,self.radius,self.radius)})\n        \n        \n        self.node2 = bs.newNode('region',\n                       attrs={'position':(self.position2[0],self.position2[1],self.position2[2]),\n                              'scale':(self.radius,self.radius,self.radius),\n                              'type':'sphere',\n                              'materials':[self.portal2Material]})\n        self.visualRadius2 = bs.newNode('shield',attrs={'position':self.position2,'color':color,'radius':0.1})\n        bsUtils.animate(self.visualRadius2,\"radius\",{0:0,500:self.radius*2})\n        bsUtils.animateArray(self.node2,\"scale\",3,{0:(0,0,0),500:(self.radius,self.radius,self.radius)})\n        \n        \n    def Portal1(self):\n        node = bs.getCollisionInfo('opposingNode')\n        node.handleMessage(bs.StandMessage(position = self.node2.position))\n        \n    def Portal2(self):\n        node = bs.getCollisionInfo('opposingNode')\n        node.handleMessage(bs.StandMessage(position = self.node1.position))\n        \n    def objPortal1(self):\n        node = bs.getCollisionInfo('opposingNode')\n        node.position = self.position2\n    \n    def objPortal2(self):\n        node = bs.getCollisionInfo('opposingNode')\n        node.position = self.position1\n\n    def delete(self):\n        if self.node1.exists() and self.node2.exists():\n            self.node1.delete()\n            self.node2.delete()\n            self.visualRadius.delete()\n            self.visualRadius2.delete()\n            if self.position1 in lastpos:\n                lastpos.remove(self.position1)\n            defi.remove(self.node2.position)\n\n    def posn(self, s , act):\n        ru = random.uniform\n        rc = random.choice\n        f = rc([(s[0], s[1], s[2]-ru(0.1, 0.6)), (s[0], s[1], s[2]+ru(0.1, 0.6)), (s[0]-ru(0.1, 0.6), s[1], s[2]), (s[0]+ru(0.1, 0.6), s[1], s[2])])\n        if f in defi or f in lastpos :\n            return self.getRandomPosition(act)\n        else :\n            defi.append(f)\n            return f\n\n    def getRandomPosition(self, activity):\n\n        pts = copy.copy(activity.getMap().ffaSpawnPoints)\n        pts2 = activity.getMap().powerupSpawnPoints\n        for i in pts2:\n            pts.append(i)\n        pos = [[999, -999], [999, -999], [999, -999]]\n        for pt in pts:\n            for i in range(3):\n                pos[i][0] = min(pos[i][0], pt[i])\n                pos[i][1] = max(pos[i][1], pt[i])\n        # The credit of this random position finder goes to Deva but I did some changes too.\n        ru = random.uniform\n        ps = pos\n        t = ru(ps[0][0] - 1.0, ps[0][1] + 1.0), ps[1][1] + ru(0.1, 1.5), ru(ps[2][0] - 1.0, ps[2][1] + 1.0)\n        s = (t[0],t[1]-ru(1.0,1.3),t[2])\n        if s in defi or s in lastpos :\n            return self.posn(s, activity)\n        else :\n            defi.append(s)\n            return s\n"
  },
  {
    "path": "mods/Protection.json",
    "content": "{\n  \"name\": \"Protection\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/Protection.py",
    "content": "import bs\nimport random\nimport bsUtils\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [ProtectionGame]\n\nclass SpazClone(bs.SpazBot):\n    \n    def __init__(self, player):\n        self.character = player.character\n        self.color = player.color\n        self.highlight = player.highlight\n        bs.SpazBot.__init__(self)\n        self.setMovingText(self, player.getName(), player.color)\n        self.sourcePlayer = player\n    def setMovingText(self, theActor, theText, color):\n        m = bs.newNode('math', owner=theActor.node, attrs={'input1': (0, 0.7, 0), 'operation': 'add'})\n        theActor.node.connectAttr('position', m, 'input2')\n        theActor._movingText = bs.newNode('text',\n                                      owner=theActor.node,\n                                      attrs={'text':theText,\n                                             'inWorld':True,\n                                             'shadow':1.0,\n                                             'flatness':1.0,\n                                             'color':color,\n                                             'scale':0.0,\n                                             'hAlign':'center'})\n        m.connectAttr('output', theActor._movingText, 'position')\n        bs.animate(theActor._movingText, 'scale', {0: 0.0, 1000: 0.01})    \nclass myBots(bs.BotSet):\n\n    def spawnCBot(self,player,botType,pos,spawnTime=3000,onSpawnCall=None):\n        bsUtils.Spawner(pt=pos,\n                        spawnTime=spawnTime,\n                        sendSpawnMessage=False,\n                        spawnCallback=bs.Call(self._spawnCBot,player,botType,pos,onSpawnCall))\n        self._spawningCount += 1\n\n    def _spawnCBot(self,player,botType,pos,onSpawnCall):\n        spaz = botType(player)\n        bs.playSound(self._spawnSound,position=pos)\n        spaz.node.handleMessage(\"flash\")\n        spaz.node.isAreaOfInterest = 0\n        spaz.handleMessage(bs.StandMessage(pos,random.uniform(0,360)))\n        self.addBot(spaz)\n        self._spawningCount -= 1\n        if onSpawnCall is not None: onSpawnCall(spaz)\n\n\nclass Icon(bs.Actor):\n        \n    def __init__(self,player,position,scale,showLives=True,showDeath=True,\n                 nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0):\n        bs.Actor.__init__(self)\n\n        self._player = player\n        self._showLives = showLives\n        self._showDeath = showDeath\n        self._nameScale = nameScale\n\n        self._outlineTex = bs.getTexture('characterIconMask')\n        \n        icon = player.getIcon()\n        self.node = bs.newNode('image',\n                               owner=self,\n                               attrs={'texture':icon['texture'],\n                                      'tintTexture':icon['tintTexture'],\n                                      'tintColor':icon['tintColor'],\n                                      'vrDepth':400,\n                                      'tint2Color':icon['tint2Color'],\n                                      'maskTexture':self._outlineTex,\n                                      'opacity':1.0,\n                                      'absoluteScale':True,\n                                      'attach':'bottomCenter'})\n        self._nameText = bs.newNode('text',\n                                    owner=self.node,\n                                    attrs={'text':player.getName(),\n                                           'color':bs.getSafeColor(player.getTeam().color),\n                                           'hAlign':'center',\n                                           'vAlign':'center',\n                                           'vrDepth':410,\n                                           'maxWidth':nameMaxWidth,\n                                           'shadow':shadow,\n                                           'flatness':flatness,\n                                           'hAttach':'center',\n                                           'vAttach':'bottom'})\n        if self._showLives:\n            self._livesText = bs.newNode('text',\n                                         owner=self.node,\n                                         attrs={'text':'x0',\n                                                'color':(1,1,0.5),\n                                                'hAlign':'left',\n                                                'vrDepth':430,\n                                                'shadow':1.0,\n                                                'flatness':1.0,\n                                                'hAttach':'center',\n                                                'vAttach':'bottom'})\n        self.setPositionAndScale(position,scale)\n\n    def setPositionAndScale(self,position,scale):\n        self.node.position = position\n        self.node.scale = [70.0*scale]\n        self._nameText.position = (position[0],position[1]+scale*52.0)\n        self._nameText.scale = 1.0*scale*self._nameScale\n        if self._showLives:\n            self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0)\n            self._livesText.scale = 1.0*scale\n\n    def updateForLives(self):\n        if self._player.exists():\n            lives = self._player.gameData['lives']\n        else: lives = 0\n        if self._showLives:\n            if lives > 0: self._livesText.text = 'x'+str(lives-1)\n            else: self._livesText.text = ''\n        if lives == 0:\n            self._nameText.opacity = 0.2\n            self.node.color = (0.7,0.3,0.3)\n            self.node.opacity = 0.2\n        \n    def handlePlayerSpawned(self):\n        if not self.node.exists(): return\n        self.node.opacity = 1.0\n        self.updateForLives()\n\n    def handlePlayerDied(self):\n        if not self.node.exists(): return\n        if self._showDeath:\n            bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0,\n                                            300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2})\n            lives = self._player.gameData['lives']\n            if lives == 0: bs.gameTimer(600,self.updateForLives)\n        \n\nclass ProtectionGame(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Protection'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Survived',\n                'scoreType':'seconds',\n                'noneIsWinner':True}\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Protect your twin.'\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Lives Per Player\",{'default':1,'minValue':1,'maxValue':10,'increment':1}),\n                    (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                            ('2 Minutes',120),('5 Minutes',300),\n                                            ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Epic Mode\",{'default':False})]\n\n        if issubclass(sessionType,bs.TeamsSession):\n            settings.append((\"Solo Mode\",{'default':False}))\n            settings.append((\"Balance Total Lives\",{'default':False}))\n            \n        return settings\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # show messages when players die since it's meaningful here\n        self.announcePlayerDeaths = True\n        \n        try: self._soloMode = settings['Solo Mode']\n        except Exception: self._soloMode = False\n        self._scoreBoard = bs.ScoreBoard()\n        self._bots = myBots()\n        self.spazClones = []\n\n    def getInstanceDescription(self):\n        return 'Last team standing wins.' if isinstance(self.getSession(),bs.TeamsSession) else 'Protect your twin!'\n\n    def getInstanceScoreBoardDescription(self):\n        return 'last team standing wins' if isinstance(self.getSession(),bs.TeamsSession) else 'Protect your twin!'\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n        self._startGameTime = bs.getGameTime()\n\n    def onTeamJoin(self,team):\n        team.gameData['survivalSeconds'] = None\n        team.gameData['spawnOrder'] = []\n\n    def onPlayerJoin(self, player):\n\n        # no longer allowing mid-game joiners here... too easy to exploit\n        if self.hasBegun():\n            player.gameData['lives'] = 0\n            player.gameData['icons'] = []\n            \n            # make sure our team has survival seconds set if they're all dead\n            # (otherwise blocked new ffa players would be considered 'still alive' in score tallying)\n            if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None:\n                player.getTeam().gameData['survivalSeconds'] = 0\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            return\n        \n        player.gameData['lives'] = self.settings['Lives Per Player']\n        player.gameData['cloneSpawnTime'] = 0\n        \n        if self._soloMode:\n            player.gameData['icons'] = []\n            player.getTeam().gameData['spawnOrder'].append(player)\n            self._updateSoloMode()\n        else:\n            # create our icon and spawn\n            player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)]\n            if player.gameData['lives'] > 0:\n                self.spawnPlayer(player)\n\n        # dont waste time doing this until begin\n        if self.hasBegun():\n            self._updateIcons()\n\n    def _updateSoloMode(self):\n        # for both teams, find the first player on the spawn order list with lives remaining\n        # and spawn them if they're not alive\n        for team in self.teams:\n            # prune dead players from the spawn order\n            team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n            for player in team.gameData['spawnOrder']:\n                if player.gameData['lives'] > 0:\n                    if not player.isAlive(): self.spawnPlayer(player)\n                    break\n\n    def _updateIcons(self):\n        # in free-for-all mode, everyone is just lined up along the bottom\n        if isinstance(self.getSession(),bs.FreeForAllSession):\n            count = len(self.teams)\n            xOffs = 85\n            x = xOffs*(count-1) * -0.5\n            for i,team in enumerate(self.teams):\n                if len(team.players) == 1:\n                    player = team.players[0]\n                    for icon in player.gameData['icons']:\n                        icon.setPositionAndScale((x,30),0.7)\n                        icon.updateForLives()\n                    x += xOffs\n\n        # in teams mode we split up teams\n        else:\n            if self._soloMode:\n                # first off, clear out all icons\n                for player in self.players:\n                    player.gameData['icons'] = []\n                # now for each team, cycle through our available players adding icons\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -60\n                        xOffs = -78\n                    else:\n                        x = 60\n                        xOffs = 78\n                    isFirst = True\n                    testLives = 1\n                    while True:\n                        playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives]\n                        if len(playersWithLives) == 0: break\n                        for player in playersWithLives:\n                            player.gameData['icons'].append(Icon(player,\n                                                                 position=(x,(40 if isFirst else 25)),\n                                                                 scale=1.0 if isFirst else 0.5,\n                                                                 nameMaxWidth=130 if isFirst else 75,\n                                                                 nameScale=0.8 if isFirst else 1.0,\n                                                                 flatness=0.0 if isFirst else 1.0,\n                                                                 shadow=0.5 if isFirst else 1.0,\n                                                                 showDeath=True if isFirst else False,\n                                                                 showLives=False))\n                            x += xOffs * (0.8 if isFirst else 0.56)\n                            isFirst = False\n                        testLives += 1\n            # non-solo mode\n            else:\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -50\n                        xOffs = -85\n                    else:\n                        x = 50\n                        xOffs = 85\n                    for player in team.players:\n                        for icon in player.gameData['icons']:\n                            icon.setPositionAndScale((x,30),0.7)\n                            icon.updateForLives()\n                        x += xOffs\n                    \n    def _getSpawnPoint(self,player):\n        # in solo-mode, if there's an existing live player on the map, spawn at whichever\n        # spot is farthest from them (keeps the action spread out)\n        if self._soloMode:\n            livingPlayer = None\n            for team in self.teams:\n                for player in team.players:\n                    if player.isAlive():\n                        p = player.actor.node.position\n                        livingPlayer = player\n                        livingPlayerPos = p\n                        break\n            if livingPlayer:\n                playerPos = bs.Vector(*livingPlayerPos)\n                points = []\n                for team in self.teams:\n                    startPos = bs.Vector(*self.getMap().getStartPosition(team.getID()))\n                    points.append([(startPos-playerPos).length(),startPos])\n                points.sort()\n                return points[-1][1]\n            else:\n                return None\n        else:\n            return None\n\n        \n    def spawnPlayer(self,player):\n        self.spawnPlayerSpaz(player,self._getSpawnPoint(player))\n        if not self._soloMode:\n            bs.gameTimer(300,bs.Call(self._printLives,player))\n\n        # if we have any icons, update their state\n        for icon in player.gameData['icons']:\n            icon.handlePlayerSpawned()\n    def spawnPlayerByDummy(self,player):\n        if player.gameData['lives'] > 0: #only try to spawn if player still has lives. Might have died during recheck delay...\n            found = False\n            for bot in self._bots.getLivingBots(): #Look for bot to spawn by\n                if bot.sourcePlayer == player:\n                    found = True\n                    #print(\"Found player\")\n                    self.spawnPlayerSpaz(player,bot.node.position)\n                    if not self._soloMode:\n                        bs.gameTimer(300,bs.Call(self._printLives,player))\n\n                    # if we have any icons, update their state\n                    for icon in player.gameData['icons']:\n                        icon.handlePlayerSpawned()\n            if not found: #Didn't find clone.  This can happen if player dies btw clone death and clone spawn\n                #print('did not find player.')\n                bs.gameTimer(1000,bs.Call(self.spawnPlayerByDummy, player)) #wait one second and try again for spawn\n        \n    def spawnDummy(self,player, myClone):\n        #playerNode = bs.getActivity()._getPlayerNode(player)\n        spz = self.scoreSet._players[player.getName()].getSpaz()\n        t = bs.getGameTime()\n        if t - player.gameData['cloneSpawnTime'] > self.minLife or player.gameData['cloneSpawnTime']==0:\n            if not spz is None:\n                pos = spz.node.position\n                player.gameData['lastSpawn']= pos\n            else:\n                pos = player.gameData['lastSpawn']\n        else:\n            player.gameData['lastSpawn'] = player.gameData['safeSpawn']\n            pos = player.gameData['safeSpawn']\n        bs.gameTimer(1000,bs.Call(self._bots.spawnCBot, player,myClone,pos=pos,spawnTime=1000, onSpawnCall=self.setSpawnTime))\n\n    def setSpawnTime(self,spaz):\n        if spaz.sourcePlayer.exists():\n            spaz.sourcePlayer.gameData['cloneSpawnTime'] = bs.getGameTime()\n            bs.gameTimer(self.minLife, bs.WeakCall(self.setSafeSpawn, spaz))\n        else:\n            spaz.handleMessage(bs.DieMessage())\n            \n    def setSafeSpawn(self, spaz):\n        if spaz.isAlive():\n            if spaz.sourcePlayer.exists():\n                spaz.sourcePlayer.gameData['safeSpawn'] = spaz.sourcePlayer.gameData['lastSpawn']\n        \n    def _printLives(self,player):\n        if not player.exists() or not player.isAlive(): return\n        try: pos = player.actor.node.position\n        except Exception,e:\n            print 'EXC getting player pos in bsElim',e\n            return\n        bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1),\n                           offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain()\n\n    def onPlayerLeave(self,player):\n\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n\n        player.gameData['icons'] = None\n\n        # remove us from spawn-order\n        if self._soloMode:\n            if player in player.getTeam().gameData['spawnOrder']:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n\n        # update icons in a moment since our team will be gone from the list then\n        bs.gameTimer(0, self._updateIcons)\n\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        self.setupStandardPowerupDrops()\n\n        if self._soloMode:\n            self._vsText = bs.NodeActor(bs.newNode(\"text\",\n                                                   attrs={'position':(0,105),\n                                                          'hAttach':\"center\",\n                                                          'hAlign':'center',\n                                                          'maxWidth':200,\n                                                          'shadow':0.5,\n                                                          'vrDepth':390,\n                                                          'scale':0.6,\n                                                          'vAttach':\"bottom\",\n                                                          'color':(0.8,0.8,0.3,1.0),\n                                                          'text':bs.Lstr(resource='vsText')}))\n\n        # if balance-team-lives is on, add lives to the smaller team until total lives match\n        if (isinstance(self.getSession(),bs.TeamsSession)\n            and self.settings['Balance Total Lives']\n            and len(self.teams[0].players) > 0\n            and len(self.teams[1].players) > 0):\n            if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]):\n                lesserTeam = self.teams[0]\n                greaterTeam = self.teams[1]\n            else:\n                lesserTeam = self.teams[1]\n                greaterTeam = self.teams[0]\n            addIndex = 0\n            while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam):\n                lesserTeam.players[addIndex].gameData['lives'] += 1\n                addIndex = (addIndex + 1) % len(lesserTeam.players)\n\n        self._updateIcons()\n        \n        self._bots.finalCelebrate()\n        activity = bs.getActivity()\n        try: myFactory = activity._sharedSpazFactory\n        except Exception:\n            myFactory = activity._sharedSpazFactory = bsSpaz.SpazFactory()\n\n        # we could check game-over conditions at explicit trigger points,\n        # but lets just do the simple thing and poll it...\n        self.minLife = 1000 #A clone's life must be at least this long for death to count.\n        for player in self.players:\n            self.spawnDummy(player,SpazClone)\n        bs.gameTimer(1000, self._update, repeat=True)\n        \n    def _getTotalTeamLives(self,team):\n        return sum(player.gameData['lives'] for player in team.players)\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            \n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n            player = m.spaz.getPlayer()\n            #player.gameData['lives'] -= 1 #only drop a life if dummy was kilt\n            if player.gameData['lives'] < 0:\n                bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode))\n                player.gameData['lives'] = 0\n\n            # play big death sound on our last death or for every one in solo mode\n            if self._soloMode or player.gameData['lives'] == 0:\n                bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound)\n\n            \n            else:\n                # otherwise, in regular mode, respawn..\n                if not self._soloMode:\n                    self.spawnPlayerByDummy(player)\n\n            # in solo, put ourself at the back of the spawn order\n            if self._soloMode:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n                player.getTeam().gameData['spawnOrder'].append(player)\n        if isinstance(m,bs.SpazBotDeathMessage):\n            if isinstance(m.badGuy, SpazClone):\n                player = m.badGuy.sourcePlayer\n                if player in self.players:\n                    t = bs.getGameTime()\n                    #only take away a life if clone lifed longer than minimum length\n                    if t - player.gameData['cloneSpawnTime'] > self.minLife:\n                        player.gameData['lives'] -=1\n                # if we hit zero lives, we're dead (and our team might be too)\n                    if player.gameData['lives'] < 1:\n                        # if the whole team is now dead, mark their survival time..\n                        #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players):\n                        if self._getTotalTeamLives(player.getTeam()) == 0:\n                            player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000\n                            playerNode = bs.getActivity()._getPlayerNode(player)\n                            spz = self.scoreSet._players[player.getName()].getSpaz()\n                            if not spz is None:\n                                spz.handleMessage(bs.DieMessage())\n                            for icon in player.gameData['icons']:\n                                icon.handlePlayerDied()\n                    else:\n                        self.spawnDummy(player, SpazClone)\n                    # if we have any icons, update their state\n                    for icon in player.gameData['icons']:\n                        icon.updateForLives()\n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior    \n            \n    def _update(self):\n\n        if self._soloMode:\n            # for both teams, find the first player on the spawn order list with lives remaining\n            # and spawn them if they're not alive\n            for team in self.teams:\n                # prune dead players from the spawn order\n                team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n                for player in team.gameData['spawnOrder']:\n                    if player.gameData['lives'] > 0:\n                        if not player.isAlive():\n                            self.spawnPlayer(player)\n                            self._updateIcons()\n                        break\n        \n        # if we're down to 1 or fewer living teams, start a timer to end the game\n        # (allows the dust to settle and draws to occur if deaths are close enough)\n        if len(self._getLivingTeams()) < 2:\n            self._roundEndTimer = bs.Timer(500,self.endGame)\n\n\n    def _getLivingTeams(self):\n        return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)]\n\n    def endGame(self):\n        if self.hasEnded(): return\n        results = bs.TeamGameResults()\n        self._vsText = None # kill our 'vs' if its there\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['survivalSeconds'])\n        self.end(results=results)\n        \n"
  },
  {
    "path": "mods/SharksAndMinnows.py",
    "content": "#SharksAndMinnows\n\nimport bs\nimport bsUtils\nimport random\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [SharksAndMinnows]\n\nclass Minnow(bs.PlayerSpaz):\n    isShark = False\n    nextZone = 2\n    def handleMessage(self, m):\n        if isinstance(m, bs.PickedUpMessage):\n            self.disconnectControlsFromPlayer()\n            self.node.delete()\n            self.getActivity().sharkify(self.getPlayer())\n        bs.PlayerSpaz.handleMessage(self, m)\n\nclass Shark(bs.PlayerSpaz):\n    isShark = True\n    def handleMessage(self, m):\n        if isinstance(m, bs.PickUpMessage):\n            if not m.node.getDelegate().isShark:\n                if self.getPlayer().getTeam() is m.node.getDelegate().getPlayer().getTeam(): points = 5\n                else: points = 20\n                self.getPlayer().getTeam().gameData['score'] += points\n                self.getActivity().scoreSet.playerScored(self.getPlayer(),20,screenMessage=False,display=False)\n                self.getActivity().updateScore()\n        bs.PlayerSpaz.handleMessage(self, m)\n\nclass SharksAndMinnows(bs.TeamGameActivity):\n    \n    @classmethod\n    def getName(cls):\n        return \"Sharks and Minnows\"\n\n    @classmethod\n    def getDescription(cls, sessionType):\n        return \"Eat or be eaten.\"\n\n    @classmethod\n    def getScoreInfo(cls):\n        return{'scoreType':'points'}\n\n    @classmethod\n    def getSupportedMaps(cls, sessionType):\n        return ['Football Stadium']\n\n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if issubclass(sessionType, bs.FreeForAllSession) or issubclass(sessionType, bs.TeamsSession)else False\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n        self._safeZoneMaterial = bs.Material()\n        self._scoredis = bs.ScoreBoard()\n        self._safeZoneMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('playerMaterial')),\n                                            actions=((\"modifyPartCollision\",\"collide\",True),\n                                                     (\"modifyPartCollision\",\"physical\",False),\n                                                     (\"call\",\"atConnect\",bs.Call(self.handleSafeZoneEnter))))\n        self.safeZone1 = bs.newNode('region',\n                   attrs={'position':(-11,0,0),\n                          'scale': (1.8,1.8,1.8),\n                          'type': 'sphere',\n                          'materials':[self._safeZoneMaterial,bs.getSharedObject('regionMaterial')]})\n        self.safeZone2 = bs.newNode('region',\n                   attrs={'position':(11,0,0),\n                          'scale': (1.8,1.8,1.8),\n                          'type': 'sphere',\n                          'materials':[self._safeZoneMaterial,bs.getSharedObject('regionMaterial')]})\n        self.zoneLocator1 = bs.newNode('locator',attrs={'shape':'circle','position':(-11,0,0),\n                                         'color':(1,1,1),'opacity':1,\n                                         'drawBeauty':True,'additive':True})\n        bs.animateArray(self.zoneLocator1,'size',1,{0:[0.0],200:[1.8*2.0]})\n        self.zoneLocator2 = bs.newNode('locator',attrs={'shape':'circle','position':(11,0,0),\n                                         'color':(1,1,1),'opacity':1,\n                                         'drawBeauty':True,'additive':True})\n        bs.animateArray(self.zoneLocator2,'size',1,{0:[0.0],200:[1.8*2.0]})\n        \n    def getInstanceScoreBoardDescription(self):\n        return ('Sharks pick up minnows to score')\n        \n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self,music='FlagCatcher')\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        for team in self.teams:\n            team.gameData['score'] = 0\n        teamNum = random.randint(0,len(self.teams)-1)\n        for player in self.players:\n            if player in self.teams[teamNum].players:\n                bs.gameTimer(100, bs.Call(self.setupSharks, player=player))\n            else:\n                self.spawnPlayerSpaz(player)\n        self.setupStandardPowerupDrops(True)\n        for team in self.teams:\n            self._scoredis.setTeamValue(team,team.gameData['score'])\n\n    def setupSharks(self, player):\n        self.sharkify(player)\n\n    def respawnPlayer(self, player):\n        if player.actor.node.getDelegate().isShark:\n            self.sharkify(player)\n            return\n        else: bs.TeamGameActivity.respawnPlayer(self,player)\n        \n    def spawnPlayerSpaz(self,player,position=(-10,1,0),angle=None):\n        try:\n            if player.actor.node.getDelegate().isShark:\n                self.sharkify(player)\n                return\n        except:\n            pass\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n        spaz = Minnow(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n        pos = [0,1,0]\n        pos[0] = position[0] + random.random() * 2 * (-1)**random.randint(0,1)\n        pos[2] = position[2] + random.random() * 2 * (-1)**random.randint(0,1)\n        player.actor.connectControlsToPlayer(enableBomb=True, enableRun = True, enableJump = True, enablePickUp = False, enablePunch=True)\n        spaz.handleMessage(bs.StandMessage(pos,90))\n\n    def sharkify(self, player):\n        name = player.getName()\n        color = (0,0,0)\n        highlight = player.getTeam().color\n        spaz = Shark(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        if player.actor is not None: player.actor.node.delete()\n        player.setActor(spaz)\n        pos = [0,.5,-5+(random.random()*10)]\n        player.actor.connectControlsToPlayer(enableBomb=False, enableRun = True, enableJump = False, enablePickUp = True,enablePunch = False)\n        spaz.handleMessage(bs.StandMessage(pos,90))\n        self.checkEnd()\n\n    def onPlayerJoin(self, player):\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            return\n\n    def updateScore(self):\n        for team in self.teams:\n            self._scoredis.setTeamValue(team,team.gameData['score'])\n\n    def handleSafeZoneEnter(self):\n        self.checkEnd()\n        zoneNode,playerNode = bs.getCollisionInfo(\"sourceNode\",\"opposingNode\")\n        try: player = playerNode.getDelegate().getPlayer()\n        except Exception: return\n        if player.isAlive() and player.actor.node.getDelegate().isShark:\n            player.actor.handleMessage(bs.DieMessage())\n            self.sharkify(player)\n        elif player.isAlive() and not player.actor.node.getDelegate().isShark:\n            if player.actor.node.positionCenter[0] < 0 and player.actor.node.getDelegate().nextZone == 1:\n                player.getTeam().gameData['score'] += 10\n                self.scoreSet.playerScored(player,10,screenMessage=False,display=False)\n                self.updateScore()\n                player.actor.node.getDelegate().nextZone = 2\n            elif player.actor.node.positionCenter[0] > 0 and player.actor.node.getDelegate().nextZone == 2:\n                player.getTeam().gameData['score'] += 10\n                self.scoreSet.playerScored(player,10,screenMessage=False,display=False)\n                self.updateScore()\n                player.actor.node.getDelegate().nextZone = 1\n\n    def handleMessage(self,m):\n        if isinstance(m, bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m)\n            bs.gameTimer(3100, self.checkEnd)\n            if not m.spaz.isShark:\n                self.respawnPlayer(m.spaz.getPlayer())\n        else: bs.TeamGameActivity.handleMessage(self, m)\n\n    def checkEnd(self):\n        if bs.getGameTime() < 5000: return\n        count = 0\n        for player in self.players:\n            if player.isAlive() and player.actor.node.getDelegate().isShark:\n                count += 1\n        if count < 1: self.endGame()\n        count = 0\n        for player in self.players:\n            if player.isAlive(): count += 1\n        if count < 2: self.endGame()\n        count = 0\n        for player in self.players:\n            if player.isAlive() and player.actor.node.getDelegate().isShark == False:\n                count += 1\n        if count < 1: self.endGame()\n        for team in self.teams:\n            if team.gameData['score'] >= 300:\n                self.endGame()\n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for team in self.teams:\n            score = team.gameData['score']\n            results.setTeamScore(team,score)\n        self.end(results=results,announceDelay=10)\n"
  },
  {
    "path": "mods/Siege.py",
    "content": "#Siege\nimport bs\nimport bsUtils\nimport random\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [Siege]\n\nclass SiegePowerupFactory(bs.PowerupFactory):\n    def getRandomPowerupType(self,forceType=None,excludeTypes=['tripleBombs','iceBombs','impactBombs','shield','health','curse','snoball','bunny']):\n        while True:\n            t = self._powerupDist[random.randint(0,len(self._powerupDist)-1)]\n            if t not in excludeTypes:\n                break\n        self._lastPowerupType = t\n        return t\n\nclass Puck(bs.Actor): # Borrowed from the hockey game\n\n    def __init__(self, position=(0,1,0)):\n        bs.Actor.__init__(self)\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n        activity = self.getActivity()\n        self._spawnPos = (position[0], position[1]+1.0, position[2])\n        self.lastPlayersToTouch = {}\n        self.node = bs.newNode(\"prop\",\n                               attrs={'model': bs.getModel('puck'),\n                                      'colorTexture': bs.getTexture('puckColor'),\n                                      'body':'puck',\n                                      'reflection':'soft',\n                                      'reflectionScale':[0.2],\n                                      'shadowSize': 1.0,\n                                      'gravityScale':2.5,\n                                      'isAreaOfInterest':True,\n                                      'position':self._spawnPos,\n                                      'materials': [bs.getSharedObject('objectMaterial'),activity._puckMaterial]\n                                      },\n                               delegate=self)\n\nclass Siege(bs.TeamGameActivity):\n    @classmethod\n    def getName(cls):\n        return \"Siege\"\n\n    @classmethod\n    def getDescription(cls, sessionType):\n        return \"Get the flag from the castle!\"\n\n    @classmethod\n    def getScoreInfo(cls):\n        return{'scoreType':'points'}\n\n    @classmethod\n    def getSupportedMaps(cls, sessionType):\n        return ['Football Stadium']\n\n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if issubclass(sessionType, bs.FreeForAllSession) or issubclass(sessionType, bs.TeamsSession) else False\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        self._puckMaterial = bs.Material()\n        self._puckMaterial.addActions(actions=( (\"modifyPartCollision\",\"friction\",100000)))\n        self._puckMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('pickupMaterial')),\n                                      actions=( (\"modifyPartCollision\",\"collide\",False)))\n        self._puckMaterial.addActions(conditions=( (\"weAreYoungerThan\",100),'and',\n                                                   (\"theyHaveMaterial\",bs.getSharedObject('objectMaterial')) ),\n                                      actions=( (\"modifyNodeCollision\",\"collide\",False)))\n        self.pucks = []\n        self.flag = bs.Flag(color=(1,1,1),\n                                     position=(0,1,-2),\n                                     touchable=True)\n        \n        \n        \n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self,music='FlagCatcher')\n\n    def _standardDropPowerup(self,index,expire=True):\n        import bsPowerup\n        bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[index],\n                          powerupType=SiegePowerupFactory().getRandomPowerupType(),expire=expire).autoRetain()\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardPowerupDrops(True)\n        for j in range(0,12,3):\n            for i in range(-6,4,3):\n                self.pucks.append(Puck((3,j/4.0,i/2.0)))\n                self.pucks.append(Puck((-3,j/4.0,i/2.0)))\n            for i in range(-3,4,2):\n                self.pucks.append(Puck((i/2.0,j/4.0,-3)))\n                self.pucks.append(Puck((i/2.0,j/4.0,1.75)))\n    def handleMessage(self,m):\n        if isinstance(m,bs.FlagPickedUpMessage):\n            winner = m.node.getDelegate()\n            self.endGame(winner)\n        elif isinstance(m,bs.PlayerSpazDeathMessage):\n            self.respawnPlayer(m.spaz.getPlayer())\n        else: bs.TeamGameActivity.handleMessage(self,m)\n\n    def endGame(self, winner):\n        results = bs.TeamGameResults()\n        for team in self.teams:\n            if winner.getPlayer() in team.players: score = 50\n            else: score = 0\n            results.setTeamScore(team,score)\n        self.end(results=results,announceDelay=10)\n"
  },
  {
    "path": "mods/SimonSays.py",
    "content": "#SimonSays\n# you had really better do what Simon says...\nimport bs\nimport random\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [SimonSays]\n\nclass SimonSays(bs.TeamGameActivity):\n    @classmethod\n    def getName(cls):\n        return \"Simon Says\"\n\n    @classmethod\n    def getDescription(cls, sessionType):\n        return \"You had better do what Simon says...\"\n\n    @classmethod\n    def getScoreInfo(cls):\n        return{'scoreType':'points'}\n\n    @classmethod\n    def getSettings(cls, sessionType):\n        return [(\"Epic Mode\", {'default': False}),\n                (\"Enable Jumping\", {'default': False}),\n                (\"Enable Punching\", {'default': False}),\n                (\"Enable Picking Up\", {'default': False})]\n    \n    @classmethod\n    def getSupportedMaps(cls, sessionType):\n        return [\"Courtyard\"]\n\n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if issubclass(sessionType, bs.FreeForAllSession) else False\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n        self.roundNum = 0\n        self.simon = False\n        self.time = 5000\n        self._r1 = 2\n        n1 = bs.newNode('locator',attrs={'shape':'circle','position':(-4,0,-6),\n                                         'color':(1,0,0),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        n2 = bs.newNode('locator',attrs={'shape':'circle','position':(0,0,-6),\n                                         'color':(0,1,0),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        n3 = bs.newNode('locator',attrs={'shape':'circle','position':(4,0,-6),\n                                         'color':(0,0,1),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        n4 = bs.newNode('locator',attrs={'shape':'circle','position':(-4,0,-2),\n                                         'color':(1,1,0),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        n5 = bs.newNode('locator',attrs={'shape':'circle','position':(0,0,-2),\n                                         'color':(0,1,1),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        n6 = bs.newNode('locator',attrs={'shape':'circle','position':(4,0,-2),\n                                         'color':(1,0,1),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        n7 = bs.newNode('locator',attrs={'shape':'circle','position':(-4,0,2),\n                                         'color':(.5,.5,.5),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        n8 = bs.newNode('locator',attrs={'shape':'circle','position':(0,0,2),\n                                         'color':(.5,.325,0),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        n9 = bs.newNode('locator',attrs={'shape':'circle','position':(4,0,2),\n                                         'color':(1,1,1),'opacity':0.5,\n                                         'drawBeauty':True,'additive':True})\n        bs.animateArray(n1,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n2,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n3,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n4,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n5,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n6,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n7,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n8,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n9,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        self.options = [\"red\", \"green\", \"blue\", \"yellow\", \"teal\", \"purple\", \"gray\", \"orange\", \"white\", \"top\", \"bottom\", \"middle row\", \"left\", \"right\", \"center column\", \"outside\"]\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self,music='FlagCatcher')\n        \n    def onPlayerJoin(self, player):\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText', subs=[('${PLAYER}', player.getName(full=True))]),\n                             color=(0, 1, 0))\n\n    def onBegin(self):\n        s = self.settings\n        bs.TeamGameActivity.onBegin(self)\n        for team in self.teams:\n            team.gameData['score'] = 0\n        for player in self.players:\n            player.gameData['score'] = 0\n            self.spawnPlayerSpaz(player,self.getMap().getFFAStartPosition(self.players))\n            player.actor.connectControlsToPlayer(enableBomb=False, enablePunch = s[\"Enable Punching\"], enablePickUp = s[\"Enable Picking Up\"], enableRun = True, enableJump = s[\"Enable Jumping\"])\n        self.explainGame()\n\n    def explainGame(self):\n        bs.screenMessage(\"Follow the commands...\")\n        bs.screenMessage(\"but only when Simon says!\")\n        bs.gameTimer(5000, self.callRound)\n\n    def callRound(self):\n        self.roundNum += 1\n        self.num = random.randint(0, 15)\n        num = self.num\n        self.simon = random.choice([True, False])\n        if num < 9: line = \"Run to the \" + self.options[num] + \" circle!\"\n        elif num < 15: line = \"Run to the \" + self.options[num] + \"!\"\n        else: line = \"Run outside of the circles!\"\n        if self.simon: line = \"Simon says \" + line[0].lower() + line[1:]\n        self.text = bs.PopupText(line, position=(0, 5, -4), color=(1, 1, 1), randomOffset=0.5, offset=(0, 0, 0), scale=2.0).autoRetain()\n        self.time -= 100\n        bs.gameTimer(self.time, self.checkRound)\n\n    def checkRound(self):\n        for player in self.players:\n            if player.isAlive():\n                safe = True if self.options[self.num] in self.inCircle(player.actor.node.positionCenter) else False\n                if ((self.simon and safe == False) or ((not self.simon) and safe == True)):\n                    player.getTeam().gameData[\"score\"] = self.roundNum\n                    player.actor.handleMessage(bs.DieMessage())\n        self.callRound()\n                        \n\n    def inCircle(self, pos):\n        circles = []\n        x = pos[0]\n        z = pos[2]\n        if (x + 4) ** 2 + (z + 6) ** 2 < 4: circles.append(\"red\")\n        elif (x) ** 2 + (z + 6) ** 2 < 4: circles.append(\"green\")\n        elif (x - 4) ** 2 + (z + 6) ** 2 < 4: circles.append(\"blue\")\n        elif (x + 4) ** 2 + (z + 2) ** 2 < 4: circles.append(\"yellow\")\n        elif (x) ** 2 + (z + 2) ** 2 < 4: circles.append(\"teal\")\n        elif (x - 4) ** 2 + (z + 2) ** 2 < 4: circles.append(\"purple\")\n        elif (x + 4) ** 2 + (z - 2) ** 2 < 4: circles.append(\"gray\")\n        elif (x) ** 2 + (z - 2) ** 2 < 4: circles.append(\"orange\")\n        elif (x - 4) ** 2 + (z - 2) ** 2 < 4: circles.append(\"white\")\n        else: circles.append(\"outside\")\n        if x < -2: circles.append(\"left\")\n        if x > 2: circles.append(\"right\")\n        if x > -2 and x < 2: circles.append(\"center column\")\n        if z > 0: circles.append(\"bottom\")\n        if z < -4: circles.append(\"top\")\n        if z < 0 and z > -4: circles.append(\"middle row\")\n        return circles\n\n    def handleMessage(self, m):\n        if isinstance(m, bs.PlayerSpazDeathMessage): self.checkEnd()\n        else: bs.TeamGameActivity.handleMessage(self, m)\n\n    def onPlayerJoin(self, player):\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            return\n\n    def checkEnd(self):\n        i = 0\n        for player in self.players:\n            if player.isAlive(): i += 1\n        if i < 2: self.endGame()\n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['score'])\n        self.end(results=results)\n"
  },
  {
    "path": "mods/SnoBallz.json",
    "content": "{\n  \"name\": \"SnoBallz powerup\",\n  \"author\": \"joshville79\",\n  \"category\": \"libraries\"\n}"
  },
  {
    "path": "mods/SnoBallz.py",
    "content": "#This is not a minigame mod script.  It's a powerup.\nimport bs\nimport bsUtils\nimport bsVector\nimport bsBomb\nfrom math import cos\nfrom random import randrange\nimport weakref\n\n\n\n    \nclass snoMessage(object):\n    #Message passed to a snoBall by collision with a spaz\n    pass\nclass otherHitMessage(object):\n    #Message passed when we hit some other object.  Lets us poof into thin air.\n    pass\nclass snoBall(bs.Actor):\n\n    def __init__(self, position=(0,1,0), velocity=(5,0,5), sourcePlayer=None, owner=None, explode=False):\n        bs.Actor.__init__(self)\n\n        activity = bs.getActivity()\n        factory = self.getFactory()\n        # spawn at the provided point\n        self._spawnPos = (position[0], position[1]+0.1, position[2])\n        self.node = bs.newNode(\"prop\",\n                               attrs={'model': factory.snoModel,\n                                      'body':'sphere',\n                                      'colorTexture': factory.texSno,\n                                      'reflection':'soft',\n                                      'modelScale':0.8,\n                                      'bodyScale':0.8,\n                                      'density':1,\n                                      'reflectionScale':[0.15],\n                                      'shadowSize': 0.6,\n                                      'position':self._spawnPos,\n                                      'velocity':velocity,\n                                      'materials': [bs.getSharedObject('objectMaterial'), factory.ballMaterial]\n                                      },\n                               delegate=self)\n        self.sourcePlayer = sourcePlayer\n        self.owner = owner\n        if factory._ballsMelt: #defaults to True.\n            #Snowballs should melt after some time\n            bs.gameTimer(1500, bs.WeakCall(self._disappear))\n        self._hitNodes = set()\n        self._exploded = False\n        if factory._ballsBust:\n            self.shouldBust = True\n        else:\n            self.shouldBust = False\n        if explode:\n            self.shouldExplode = True\n        else:\n            self.shouldExplode = False\n        \n    def handleMessage(self,m):\n        super(self.__class__, self).handleMessage(m)\n        if isinstance(m, otherHitMessage):\n            if self._exploded: return #Don't bother with calcs if we've done blowed up or busted.\n            if self.shouldBust:\n                myVel = self.node.velocity\n                #Get the velocity at the instant of impact.  We'll check it after 20ms (after bounce). If it has changed a lot, bust.\n                bs.gameTimer(10, bs.WeakCall(self.calcBust,myVel))\n            else:\n                return\n        if isinstance(m,bs.DieMessage):\n            self.node.delete()\n        elif isinstance(m,bs.OutOfBoundsMessage):\n            self.handleMessage(bs.DieMessage())\n        elif isinstance(m,bs.HitMessage):\n            self.node.handleMessage(\"impulse\",m.pos[0],m.pos[1],m.pos[2],\n                                    m.velocity[0],m.velocity[1],m.velocity[2],\n                                    1.0*m.magnitude,1.0*m.velocityMagnitude,m.radius,0,\n                                    m.forceDirection[0],m.forceDirection[1],m.forceDirection[2])\n        elif isinstance(m, bs.ImpactDamageMessage):\n            print [dir(m), m.intensity]\n        elif isinstance(m,snoMessage):\n            #We should get a snoMessage any time a snowball hits a spaz.\n            #We'll either explode (if we're exploding) or calculate like a punch.\n            #We have to do this the hard way because the ImpactMessage won't do it for us (no source)\n            #Below is modified pretty much from bsSpaz handling of a _punchHitMessage\n            #print bsVector.Vector(*self.node.velocity).length()\n            if self._exploded: return #Don't do anything if we've already done our damage, or if we've busted already\n            if self.shouldExplode:\n                \"\"\"\n                Blows up the ball if it has not yet done so.\n                \"\"\"\n                if self._exploded: return\n                self._exploded = True\n                activity = self.getActivity()\n                if activity is not None and self.node.exists():\n                    blast = bsBomb.Blast(position=self.node.position,velocity=self.node.velocity,\n                                  blastRadius=0.7,blastType='impact',sourcePlayer=self.sourcePlayer,hitType='snoBall',hitSubType='explode').autoRetain()\n                    \n                # we blew up so we need to go away\n                bs.gameTimer(1,bs.WeakCall(self.handleMessage,bs.DieMessage()))\n            else:\n                v = self.node.velocity\n                #Only try to damage if the ball is moving at some reasonable rate of speed\n                if bs.Vector(*v).length() > 5.0:\n                    node = bs.getCollisionInfo(\"opposingNode\")\n\n                    # only allow one hit per node per ball\n                    if node is not None and node.exists() and not node in self._hitNodes:\n\n                        t = self.node.position #was punchPosition\n                        hitDir = self.node.velocity \n\n                        self._hitNodes.add(node)\n                        node.handleMessage(bs.HitMessage(pos=t,\n                                                         velocity=v,\n                                                         magnitude=bsVector.Vector(*v).length()*0.5,\n                                                         velocityMagnitude=bsVector.Vector(*v).length()*0.5,\n                                                         radius=0,\n                                                         srcNode=self.node,\n                                                         sourcePlayer=self.sourcePlayer,\n                                                         forceDirection = hitDir,\n                                                         hitType='snoBall',\n                                                         hitSubType='default'))\n\n                if self.shouldBust:\n                    #Since we hit someone, let's bust:\n                    #Add a very short timer to allow one ball to hit more than one spaz if almost simultaneous.\n                    bs.gameTimer(50, bs.WeakCall(self.doBust))\n        else:\n            bs.Actor.handleMessage(self,m)\n    def doBust(self):\n        if self.exists():\n            if not self._exploded:\n                self._exploded = True\n                bs.emitBGDynamics(position=self.node.position,velocity=[v*0.1 for v in self.node.velocity],count=10,spread=0.1,scale=0.4,chunkType='ice')\n                #Do a muffled punch sound\n                sound = self.getFactory().impactSound\n                bs.playSound(sound,1.0,position=self.node.position)\n                scl = self.node.modelScale\n                bsUtils.animate(self.node,\"modelScale\",{0:scl*1.0, 20:scl*0.5, 50:0.0})\n                bs.gameTimer(80,bs.WeakCall(self.handleMessage,bs.DieMessage()))\n        \n    def calcBust(self, oVel):\n        #Original speed (magnitude of velocity)\n        oSpd = bs.Vector(*oVel).length()\n        #Now get the dot product of the original velocity and current velocity.\n        dot = sum(x*y for x,y in zip(oVel,self.node.velocity))\n        if oSpd*oSpd - dot > 50.0:\n            #Basically, the more different the dot product from the square of the original\n            #velocity vector, the more the ball trajectory changed when it it something.\n            #This is the best way I could figure out how \"hard\" the ball hit. \n            #A difference value was a pretty arbitrary choice.\n            #Add a very short timer to allow just a couple of hits.\n            bs.gameTimer(50, bs.WeakCall(self.doBust))\n            \n    def _disappear(self):\n        self._exploded = True #don't try to damage stuff anymore because we should be melting.\n        if self.exists():\n            scl = self.node.modelScale\n            bsUtils.animate(self.node,\"modelScale\",{0:scl*1.0, 300:scl*0.5, 500:0.0})\n            bs.gameTimer(550,bs.WeakCall(self.handleMessage,bs.DieMessage()))\n            \n    def getFactory(cls):\n        \"\"\"\n        Returns a shared SnoBallz.SnoBallFactory object, creating it if necessary.\n        \"\"\"\n        activity = bs.getActivity()\n        if activity is None: raise Exception(\"no current activity\")\n        try: return activity._sharedSnoBallFactory\n        except Exception:\n            f = activity._sharedSnoBallFactory = SnoBallFactory()\n            return f            \n            \nclass SnoBallFactory(object):\n\n\n    def __init__(self):\n        \"\"\"\n        Instantiate a SnoBallFactory.\n        You shouldn't need to do this; call snoBallz.snoBall.getFactory() to get a shared instance.\n        \"\"\"\n\n        self.texSno = bs.getTexture(\"bunnyColor\")\n        self.snoModel = bs.getModel(\"frostyPelvis\")\n        self.ballMaterial = bs.Material()\n        self.impactSound = bs.getSound('impactMedium')\n        #First condition keeps balls from damaging originating player by preventing collisions immediately after they appear.\n        #Time is very short due to balls move fast.\n        self.ballMaterial.addActions(\n            conditions=((('weAreYoungerThan',5),'or',('theyAreYoungerThan',100)),\n                        'and',('theyHaveMaterial',bs.getSharedObject('objectMaterial'))),\n            actions=(('modifyNodeCollision','collide',False)))\n        # we want pickup materials to always hit us even if we're currently not\n        # colliding with their node (generally due to the above rule)\n        self.ballMaterial.addActions(\n            conditions=('theyHaveMaterial',bs.getSharedObject('pickupMaterial')),\n            actions=(('modifyPartCollision','useNodeCollide',False)))\n        self.ballMaterial.addActions(actions=('modifyPartCollision','friction',0.3))\n        #This action disables default physics when the ball hits a spaz. Sends a snoMessage to \n        #itself so that it can try to damage spazzes.\n        self.ballMaterial.addActions(conditions=('theyHaveMaterial', bs.getSharedObject('playerMaterial')), actions=(('modifyPartCollision','physical',False),('message', 'ourNode', 'atConnect', snoMessage())))\n        #This message sends a different message to our ball just to see if it should bust or not\n        self.ballMaterial.addActions(conditions=(\n                                                ('theyDontHaveMaterial', bs.getSharedObject('playerMaterial')), 'and',\n                                                ('theyHaveMaterial', bs.getSharedObject('objectMaterial')), 'or',\n                                                ('theyHaveMaterial', bs.getSharedObject('footingMaterial'))),\n                                                actions=('message', 'ourNode', 'atConnect', otherHitMessage()))\n        #The below can be changed after the factory is created\n        self.defaultBallTimeout = 300\n        self._ballsMelt = True\n        self._ballsBust = True\n        self._powerExpire = True\n        self._powerLife = 20000\n        \n    def giveBallz(self,spaz):\n        spaz.punchCallback = self.throwBall\n        spaz.lastBallTime = bs.getGameTime()\n        if self._powerExpire:\n            weakSpaz = weakref.ref(spaz)\n            spaz.snoExpireTimer = bs.Timer(self._powerLife, bs.WeakCall(self.takeBallz, weakSpaz))\n\n    def takeBallz(self,weakSpaz):\n        if not weakSpaz() is None:\n            weakSpaz().punchCallback = None\n        \n    def throwBall(self, spaz):\n        t = bs.getGameTime()\n        #Figure bomb timeout based on other owned powerups:\n        bTime = self.defaultBallTimeout\n        if spaz.bombType == 'impact':\n            bTime *= 2\n        if spaz.bombCount > 1:\n            bTime /= 2\n        if t - spaz.lastBallTime > bTime:\n            spaz.lastBallTime = t\n            #Figure out which way spaz is facing\n            p1 = spaz.node.positionCenter\n            p2 = spaz.node.positionForward\n            direction = [p1[0]-p2[0],p2[1]-p1[1],p1[2]-p2[2]]\n            direction[1] = 0.03 #This is the upward throw angle\n            #Make a velocity vector for the snowball\n            mag = 20.0/bsVector.Vector(*direction).length()\n            vel = [v * mag for v in direction]\n            #print vel\n            if spaz.bombType == 'impact':\n                explodeIt = True\n            else:\n                explodeIt = False\n            snoBall(spaz.node.position,vel,spaz.getPlayer(),spaz.getPlayer(),explodeIt).autoRetain()\n"
  },
  {
    "path": "mods/SnowBallFight.json",
    "content": "{\n  \"name\": \"Snowball Fight (requires SnoBallz.py)\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\",\n  \"requires\": [\"SnoBallz\"]\n}"
  },
  {
    "path": "mods/SnowBallFight.py",
    "content": "import bs\nimport bsVector\nimport bsSpaz\nimport bsBomb\nimport bsUtils\nimport random\nimport SnoBallz\n\n#please note that this Minigame requires the separate file SnoBallz.py.\n#It's a separate file to allow for snowballs as a powerup in any game.\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [SnowBallFightGame]\n\nclass PlayerSpaz_Sno(bs.PlayerSpaz):\n    def handleMessage(self,m):\n        #print m, self.hitPoints\n        if isinstance(m,bsSpaz._PunchHitMessage): return True #Nullify punches\n\n        super(self.__class__, self).handleMessage(m)\n\n                \nclass SnowBallFightGame(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Snowball Fight'\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Kill a set number of enemies to win.'\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Kills to Win Per Player\",{'minValue':1,'default':5,'increment':1}),\n                    (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                              ('2 Minutes',120),('5 Minutes',300),\n                                              ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Snowball Rate\",{'choices':[('Slowest',500),('Slow',400),('Normal',300),('Fast',200),('Lag City',100)],'default':300}),\n                    (\"Snowballs Melt\",{'default':True}),\n                    (\"Snowballs Bust\",{'default':True}),\n                    (\"Epic Mode\",{'default':False})]\n        \n        # In teams mode, a suicide gives a point to the other team, but in free-for-all it\n        # subtracts from your own score. By default we clamp this at zero to benefit new players,\n        # but pro players might like to be able to go negative. (to avoid a strategy of just\n        # suiciding until you get a good drop)\n        if issubclass(sessionType, bs.FreeForAllSession):\n            settings.append((\"Allow Negative Scores\",{'default':False}))\n\n        return settings\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # print messages when players die since it matters here..\n        self.announcePlayerDeaths = True\n        \n        self._scoreBoard = bs.ScoreBoard()\n        \n        #Initiate the SnoBall factory\n        self.snoFact = SnoBallz.snoBall().getFactory()\n        self.snoFact.defaultBallTimeout = self.settings['Snowball Rate']\n        self.snoFact._ballsMelt = self.settings['Snowballs Melt']\n        self.snoFact._ballsBust = self.settings['Snowballs Bust']\n        self.snoFact._powerExpire = False\n    def getInstanceDescription(self):\n        return ('Crush ${ARG1} of your enemies.',self._scoreToWin)\n\n    def getInstanceScoreBoardDescription(self):\n        return ('kill ${ARG1} enemies',self._scoreToWin)\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'ToTheDeath')\n\n    def onTeamJoin(self,team):\n        team.gameData['score'] = 0\n        if self.hasBegun(): self._updateScoreBoard()\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        self.setupStandardPowerupDrops()\n        if len(self.teams) > 0:\n            self._scoreToWin = self.settings['Kills to Win Per Player'] * max(1,max(len(t.players) for t in self.teams))\n        else: self._scoreToWin = self.settings['Kills to Win Per Player']\n        self._updateScoreBoard()\n        self._dingSound = bs.getSound('dingSmall')\n        \n    def _standardDropPowerup(self,index,expire=True):\n        import bsPowerup\n        forbidded = ['iceBombs','punch','stickyBombs','landMines', 'snoball']\n        bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[index],\n                          powerupType=bs.Powerup.getFactory().getRandomPowerupType(None,forbidded),expire=expire).autoRetain()    \n                          \n    def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None):\n        \"\"\"\n        Create and wire up a bs.PlayerSpaz for the provide bs.Player.\n        \"\"\"\n        position = self.getMap().getFFAStartPosition(self.players)\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n\n        lightColor = bsUtils.getNormalizedColor(color)\n        displayColor = bs.getSafeColor(color,targetIntensity=0.75)\n        spaz = PlayerSpaz_Sno(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n\n        # we want a bigger area-of-interest in co-op mode\n        # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n        # else: spaz.node.areaOfInterestRadius = 5.0\n\n        # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n        # collide with the player-walls\n        # FIXME; need to generalize this\n        if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n        \n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer(enableBomb=False, enablePickUp=False)\n        self.snoFact.giveBallz(spaz) \n        self.scoreSet.playerGotNewSpaz(player,spaz)\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound,1,position=spaz.node.position)\n        light = bs.newNode('light',attrs={'color':lightColor})\n        spaz.node.connectAttr('position',light,'position')\n        bsUtils.animate(light,'intensity',{0:0,250:1,500:0})\n        bs.gameTimer(500,light.delete)\n        return spaz\n    \n    def handleMessage(self,m):\n\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior\n\n            player = m.spaz.getPlayer()\n            self.respawnPlayer(player)\n\n            killer = m.killerPlayer\n            if killer is None: return\n\n            # handle team-kills\n            if killer.getTeam() is player.getTeam():\n\n                # in free-for-all, killing yourself loses you a point\n                if isinstance(self.getSession(),bs.FreeForAllSession):\n                    newScore = player.getTeam().gameData['score'] - 1\n                    if not self.settings['Allow Negative Scores']: newScore = max(0, newScore)\n                    player.getTeam().gameData['score'] = newScore\n\n                # in teams-mode it gives a point to the other team\n                else:\n                    bs.playSound(self._dingSound)\n                    for team in self.teams:\n                        if team is not killer.getTeam():\n                            team.gameData['score'] += 1\n\n            # killing someone on another team nets a kill\n            else:\n                killer.getTeam().gameData['score'] += 1\n                bs.playSound(self._dingSound)\n                # in FFA show our score since its hard to find on the scoreboard\n                try: killer.actor.setScoreText(str(killer.getTeam().gameData['score'])+'/'+str(self._scoreToWin),color=killer.getTeam().color,flash=True)\n                except Exception: pass\n\n            self._updateScoreBoard()\n\n            # if someone has won, set a timer to end shortly\n            # (allows the dust to clear and draws to occur if deaths are close enough)\n            if any(team.gameData['score'] >= self._scoreToWin for team in self.teams):\n                bs.gameTimer(500,self.endGame)\n\n        else: bs.TeamGameActivity.handleMessage(self,m)\n\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team,team.gameData['score'],self._scoreToWin)\n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for t in self.teams: results.setTeamScore(t,t.gameData['score'])\n        self.end(results=results)\n"
  },
  {
    "path": "mods/WizardWar.json",
    "content": "{\n  \"name\": \"WizardWar\",\n  \"author\": \"MattZ45986\",\n  \"category\": \"minigames\" \n}\n"
  },
  {
    "path": "mods/WizardWar.py",
    "content": "# Wizard War\n# In light of the new \"Grumbledorf\" character\n# I have made a war just for him.\nimport bs\nimport random\nimport math\nimport bsVector\nimport bsUtils\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [WizardWar]\n\ndef bsGetLevels():\n    return [bs.Level('Wizard War',\n                     displayName='${GAME}',\n                     gameType=WizardWar,\n                     settings={},\n                     previewTexName='courtyardPreview')]\n\nclass ExplodeMessage(object):\n    pass\nclass ArmMessage(object):\n    pass\nclass WarnMessage(object):\n    pass\nclass DieMessage(object):\n    pass\nclass _BombDiedMessage(object):\n    pass\n\n\n# A special type of bomb that is cast from the player's body.\n# It is shaped like an orb and is colored according to the player's team\nclass WWBomb(bs.Bomb):\n\n    def __init__(self,position=(0,1,0),velocity=(0,0,0),bombType='normal',blastRadius=2,sourcePlayer=None,owner=None):\n        if not sourcePlayer.isAlive(): return\n        bs.Actor.__init__(self)\n        factory = self.getFactory()\n        if not bombType in ('ice','impact','landMine','normal','sticky','tnt'): raise Exception(\"invalid bomb type: \" + bombType)\n        self.bombType = bombType\n        self._exploded = False\n        self.blastRadius = blastRadius\n        self._explodeCallbacks = []\n        self.sourcePlayer = sourcePlayer\n        self.hitType = 'explosion'\n        self.hitSubType = self.bombType\n        if owner is None: owner = bs.Node(None)\n        self.owner = owner\n        materials = (factory.bombMaterial, bs.getSharedObject('objectMaterial'))\n        materials = materials + (factory.impactBlastMaterial,)\n        players = self.getActivity().players\n        i = 0\n        # This gives each player a unique orb color, made possible by the powerup textures within the game.\n        while players[i] != sourcePlayer:\n            i+=1\n        color = (\"powerupIceBombs\",\"powerupPunch\",\"powerupStickyBombs\",\"powerupBomb\",\"powerupCurse\",\"powerupHealth\",\"powerupShield\",\"powerupLandMines\")[i]\n        if isinstance(self.getActivity().getSession(), bs.TeamsSession): # unless we're on teams, so we'll overide the color to be the team's color\n            if sourcePlayer in self.getActivity().teams[0].players:\n                color = \"powerupIceBombs\" # for blue\n            else:\n                color = \"powerupPunch\" # for red\n        self.node = bs.newNode('prop',\n                                   delegate=self,\n                                   attrs={'position':position,\n                                          'velocity':velocity,\n                                          'body':'sphere',\n                                          'model':bs.getModel(\"shield\"),\n                                          'shadowSize':0.3,\n                                          'density':1,\n                                          'bodyScale':3,\n                                          'colorTexture':bs.getTexture(color),\n                                          'reflection':'soft',\n                                          'reflectionScale':[1.5],\n                                          'materials':materials})\n        self.armTimer = bs.Timer(200,bs.WeakCall(self.handleMessage,ArmMessage()))\n        self.node.addDeathAction(bs.WeakCall(self.handleMessage,_BombDiedMessage()))\n\n\n        bsUtils.animate(self.node,\"modelScale\",{0:0, 200:1.3, 260:1})\n\n    \n    def _handleImpact(self,m):\n        node,body = bs.getCollisionInfo(\"opposingNode\",\"opposingBody\")\n        if node is None: return\n        try: player = node.getDelegate().getPlayer()\n        except Exception: player = None\n        if self.getActivity().settings[\"Orbs Explode Other Orbs\"]:\n            if isinstance(node.getDelegate(),WWBomb) and node.getNodeType() == 'prop' and (node.getDelegate().sourcePlayer.getTeam() is not self.sourcePlayer.getTeam()):\n                self.handleMessage(ExplodeMessage())\n        if (player is not None) and (player.getTeam() is not self.sourcePlayer.getTeam()):\n            self.handleMessage(ExplodeMessage())\n                    \n    def handleMessage(self, m):\n        if isinstance(m, _BombDiedMessage):\n            self.sourcePlayer.actor._orbNum -= 1\n        elif isinstance(m, ExplodeMessage): self.explode()\n        else:\n            bs.Bomb.handleMessage(self,m)\n\nclass WWSpaz(bs.PlayerSpaz):\n    def onPunchPress(self):\n        self.getActivity().shootBomb(self)\n\nclass WizardWar(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return \"Wizard War\"\n\n    @classmethod\n    def getDescription(cls, sessionType):\n        return \"Punch to summon magic orbs\\nLast mage standing wins\"\n\n    @classmethod\n    def getScoreInfo(cls):\n        return{'scoreType':'points'}\n\n    @classmethod\n    def getSettings(cls, sessionType):\n        return [(\"Epic Mode\", {'default': False}),\n                (\"Enable Running\", {'default': False}),\n                (\"Enable Jumping\", {'default': False}),\n                (\"Enable Punching\", {'default': False}),\n                (\"Enable Picking Up\", {'default': False}),\n                (\"Orbs Explode Other Orbs\", {'default':True}),\n                (\"Orb Limit\", {\n                    'choices': [\n                        ('1 Orb', 1),\n                        ('2 Orbs', 2),\n                        ('3 Orbs', 3),\n                        ('5 Orbs', 5),\n                        ('Infinite', 300)\n                    ],\n                    'default': 3})]\n    @classmethod\n    def getSupportedMaps(cls, sessionType):\n        return bs.getMapsSupportingPlayType('melee')\n\n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if issubclass(sessionType, bs.FreeForAllSession) or issubclass(sessionType, bs.TeamsSession) else False\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n        self.info = bs.NodeActor(bs.newNode('text',\n                                                   attrs={'vAttach': 'bottom',\n                                                          'hAlign': 'center',\n                                                          'vrDepth': 0,\n                                                          'color': (0,.2,0),\n                                                          'shadow': 1.0,\n                                                          'flatness': 1.0,\n                                                          'position': (0,0),\n                                                          'scale': 0.8,\n                                                          'text': \"Created by MattZ45986 on Github\",\n                                                          }))\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self,music='ToTheDeath')\n        \n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        s = self.settings\n        for player in self.players:\n            player.actor.connectControlsToPlayer(enableBomb=False, enablePickUp = s[\"Enable Picking Up\"], enableRun = s[\"Enable Running\"], enableJump = s[\"Enable Jumping\"])\n            player.gameData['score'] = 0\n            pos = player.actor.node.positionCenter\n            t = bs.newNode('text',\n                                 owner=player.actor.node,\n                                 attrs={'text':player.getName(),\n                                        'inWorld':True,\n                                        'color':player.color,\n                                        'scale':0.015,\n                                        'hAlign':'center',\n                                        'position':(pos)})\n            l = bs.newNode('light',\n                                 owner=player.actor.node,\n                                 attrs={'color':player.color,\n                                        'position':(pos),\n                                        'intensity':0.5})\n            bs.gameTimer(2000,l.delete)\n            bs.gameTimer(2000,t.delete)\n\n\n    def onPlayerJoin(self, player):\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            self.checkEnd()\n            return\n        else:\n            self.spawnPlayerSpaz(player)\n    \n    def spawnPlayerSpaz(self,player,position=(0,5,-3),angle=None):\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n        players = self.players\n        i = 0\n        position = self.getMap().getFFAStartPosition(self.players)\n        angle = 0\n        spaz = WWSpaz(color=color,\n                             highlight=highlight,\n                             character=\"Grumbledorf\",\n                             player=player)\n        player.setActor(spaz)\n        spaz.handleMessage(bs.StandMessage(position,angle))\n        spaz._orbNum = 0\n    def shootBomb(self, spaz):\n        if spaz._orbNum >= self.settings[\"Orb Limit\"]: return\n        spaz._orbNum += 1\n        try: spaz.node.handleMessage('celebrate',100)\n        except Exception: pass\n        cen = spaz.node.positionCenter\n        frw = spaz.node.positionForward\n        direction = [cen[0]-frw[0],frw[1]-cen[1],cen[2]-frw[2]]\n        direction[1] *= .03 \n        mag = 20.0/bsVector.Vector(*direction).length()\n        vel = [v * mag for v in direction]\n        WWBomb(position=(spaz.node.position[0],spaz.node.position[1]+1,spaz.node.position[2]), velocity=vel, sourcePlayer = spaz.getPlayer(), bombType = 'impact').autoRetain()\n\n    def handleMessage(self, m):\n        if isinstance(m, bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m)\n            bs.gameTimer(1000,bs.Call(self.checkEnd))\n            m.spaz.getPlayer().actor.disconnectControlsFromPlayer()\n            if m.how == \"fall\": pts = 10\n            elif m.how == \"impact\": pts = 50\n            else: pts = 0\n            self.scoreSet.playerScored(m.killerPlayer,pts,screenMessage=False)\n            bs.screenMessage(str(m.spaz.getPlayer().getName()) + \" died!\", m.spaz.getPlayer().color, top=True)\n\n    def checkEnd(self):\n        if isinstance(self.getSession(), bs.FreeForAllSession):\n            i = 0\n            for player in self.players:\n                if player.isAlive():\n                    i += 1\n            if i <= 1:\n                self.endGame()\n        if isinstance(self.getSession(), bs.TeamsSession):\n            for team in self.teams:\n                i=0\n                team.gameData['score'] = 1\n                for player in team.players:\n                    if player.isAlive():\n                        i += 1\n                if i == 0:\n                    team.gameData['score'] -= 1\n            for team in self.teams:\n                if team.gameData['score'] == 0:\n                    self.endGame()\n                        \n                        \n\n    def endGame(self):\n        if isinstance(self.getSession(), bs.FreeForAllSession):\n            for player in self.players:\n                if player.isAlive():\n                    player.gameData['score'] = 1\n                else:\n                    player.gameData['score'] = 0\n            results = bs.TeamGameResults()\n            for team in self.teams:\n                for player in team.players:\n                    if player.isAlive():\n                        results.setTeamScore(team, 5)\n                        break\n                    else:\n                        results.setTeamScore(team, 0)\n        else:\n            results = bs.TeamGameResults()\n            for team in self.teams:\n                results.setTeamScore(team, 0)\n                for player in team.players:\n                    if player.isAlive():\n                        team.gameData['score'] = 1\n                        results.setTeamScore(team, 10)\n        self.end(results=results)\n\n"
  },
  {
    "path": "mods/ZombieHorde.json",
    "content": "{\n  \"name\": \"Zombie Horde\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/ZombieHorde.py",
    "content": "import bs\nimport random\nimport bsUtils\nimport bsSpaz\nimport copy\n#import PlayerSpaz\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [ZombieHorde]\n\n\nclass Icon(bs.Actor):\n        \n    def __init__(self,player,position,scale,showLives=True,showDeath=True,\n                 nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0):\n        bs.Actor.__init__(self)\n\n        self._player = player\n        self._showLives = showLives\n        self._showDeath = showDeath\n        self._nameScale = nameScale\n\n        self._outlineTex = bs.getTexture('characterIconMask')\n        \n        icon = player.getIcon()\n        self.node = bs.newNode('image',\n                               owner=self,\n                               attrs={'texture':icon['texture'],\n                                      'tintTexture':icon['tintTexture'],\n                                      'tintColor':icon['tintColor'],\n                                      'vrDepth':400,\n                                      'tint2Color':icon['tint2Color'],\n                                      'maskTexture':self._outlineTex,\n                                      'opacity':1.0,\n                                      'absoluteScale':True,\n                                      'attach':'bottomCenter'})\n        self._nameText = bs.newNode('text',\n                                    owner=self.node,\n                                    attrs={'text':player.getName(),\n                                           'color':bs.getSafeColor(player.getTeam().color),\n                                           'hAlign':'center',\n                                           'vAlign':'center',\n                                           'vrDepth':410,\n                                           'maxWidth':nameMaxWidth,\n                                           'shadow':shadow,\n                                           'flatness':flatness,\n                                           'hAttach':'center',\n                                           'vAttach':'bottom'})\n        if self._showLives:\n            self._livesText = bs.newNode('text',\n                                         owner=self.node,\n                                         attrs={'text':'x0',\n                                                'color':(1,1,0.5),\n                                                'hAlign':'left',\n                                                'vrDepth':430,\n                                                'shadow':1.0,\n                                                'flatness':1.0,\n                                                'hAttach':'center',\n                                                'vAttach':'bottom'})\n        self.setPositionAndScale(position,scale)\n\n    def setPositionAndScale(self,position,scale):\n        self.node.position = position\n        self.node.scale = [70.0*scale]\n        self._nameText.position = (position[0],position[1]+scale*52.0)\n        self._nameText.scale = 1.0*scale*self._nameScale\n        if self._showLives:\n            self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0)\n            self._livesText.scale = 1.0*scale\n\n    def updateForLives(self):\n        if self._player.exists():\n            lives = self._player.gameData['lives']\n        else: lives = 0\n        if self._showLives:\n            if lives > 0: self._livesText.text = 'x'+str(lives-1)\n            else: self._livesText.text = ''\n        if lives == 0:\n            self._nameText.opacity = 0.2\n            self.node.color = (0.7,0.3,0.3)\n            self.node.opacity = 0.2\n        \n    def handlePlayerSpawned(self):\n        if not self.node.exists(): return\n        self.node.opacity = 1.0\n        self.updateForLives()\n\n    def handlePlayerDied(self):\n        if not self.node.exists(): return\n        if self._showDeath:\n            bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0,\n                                            300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2})\n            lives = self._player.gameData['lives']\n            if lives == 0: bs.gameTimer(600,self.updateForLives)\n        \nclass PlayerSpaz_Zom(bs.PlayerSpaz):\n    def handleMessage(self, m):\n        if isinstance(m, bs.HitMessage):\n            if not self.node.exists():\n                return\n            if not m.sourcePlayer is None:\n                #it seems as though spazBots are actually players, but with invalid names... Do a try for invalid name?\n                try:\n                    playa = m.sourcePlayer.getName(True, False) # Long name, no icons\n                    if not playa is None:\n                        #Player had a name.  Hit by a person. No damage unless player is also zombie (zero lives).\n                        if m.sourcePlayer.gameData['lives'] < 1:\n                            super(self.__class__, self).handleMessage(m)\n                except:\n                    super(self.__class__, self).handleMessage(m)\n            else:\n                super(self.__class__, self).handleMessage(m)\n        elif isinstance(m,bs.FreezeMessage):\n            pass #Can't be frozen.  Would allow self-freeze, but can't prevent others from freezing.\n        elif isinstance(m,bsSpaz._PickupMessage): #Complete copy from bsSpaz except for added section to prevent picking players\n            opposingNode,opposingBody = bs.getCollisionInfo('opposingNode','opposingBody')\n            if opposingNode is None or not opposingNode.exists(): return True\n\n            # dont allow picking up of invincible dudes\n            try:\n                if opposingNode.invincible == True: return True\n            except Exception: pass\n            ####ADDED SECTION - Don't allow picking up of non-Zombie dudes\n            try:\n                playa = opposingNode.sourcePlayer.getName(True, False) # Long name, no icons\n                if not playa is None:\n                    #Player had a name.  Prevent pickup unless player is also zombie (zero lives).\n                    if opposingNode.sourcePlayer.gameData['lives'] > 0:\n                        return True\n            except Exception: pass\n            #####\n            # if we're grabbing the pelvis of a non-shattered spaz, we wanna grab the torso instead\n            if opposingNode.getNodeType() == 'spaz' and not opposingNode.shattered and opposingBody == 4:\n                opposingBody = 1\n\n            # special case - if we're holding a flag, dont replace it\n            # ( hmm - should make this customizable or more low level )\n            held = self.node.holdNode\n            if held is not None and held.exists() and held.getNodeType() == 'flag':\n                return True\n\n            self.node.holdBody = opposingBody # needs to be set before holdNode\n            self.node.holdNode = opposingNode\n        else:\n            super(self.__class__, self).handleMessage(m)\n            \nclass PlayerZombie(bs.PlayerSpaz):\n    def handleMessage(self, m):\n        if isinstance(m, bs.HitMessage):\n            if not self.node.exists():\n                return\n            if not m.sourcePlayer is None:\n                #it seems as though spazBots are actually players, but with invalid names... Do a try for invalid name?\n                #print(['hit by]', m.sourcePlayer.getName(True,False)])\n                try:\n                    playa = m.sourcePlayer.getName(True, False) # Long name, no icons\n                    if playa is None:\n                        #Player had no name.  Hit by a Zombie. No damage.\n                        pass\n                    else:\n                        super(self.__class__, self).handleMessage(m)\n                except:\n                    super(self.__class__, self).handleMessage(m)\n            else:\n                super(self.__class__, self).handleMessage(m)\n        else:\n            super(self.__class__, self).handleMessage(m)\n            \nclass zBotSet(bs.BotSet):   #the botset is overloaded to prevent adding players to the bots' targets if they are zombies too.         \n    def startMoving(self): #here we overload the default startMoving, which normally calls _update.\n        #self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self._update),repeat=True)\n        self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self.zUpdate),repeat=True)\n        \n    def zUpdate(self):\n\n        # update one of our bot lists each time through..\n        # first off, remove dead bots from the list\n        # (we check exists() here instead of dead.. we want to keep them around even if they're just a corpse)\n        #####This is overloaded from bsSpaz to prevent zombies from attacking player Zombies.\n        try:\n            botList = self._botLists[self._botUpdateList] = [b for b in self._botLists[self._botUpdateList] if b.exists()]\n        except Exception:\n            bs.printException(\"error updating bot list: \"+str(self._botLists[self._botUpdateList]))\n        self._botUpdateList = (self._botUpdateList+1)%self._botListCount\n\n        # update our list of player points for the bots to use\n        playerPts = []\n        for player in bs.getActivity().players:\n            try:\n                if player.isAlive():\n                    if player.gameData['lives'] > 0:  #If the player has lives, add to attack points\n                        playerPts.append((bs.Vector(*player.actor.node.position),\n                                        bs.Vector(*player.actor.node.velocity)))\n            except Exception:\n                bs.printException('error on bot-set _update')\n\n        for b in botList:\n            b._setPlayerPts(playerPts)\n            b._updateAI()\n            \nclass ZombieHorde(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Zombie Horde'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'score',\n                'scoreType':'points',\n                'noneIsWinner':False,\n                'lowerIsBetter':False}\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Kill walkers for points!'\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Lives Per Player\",{'default':1,'minValue':1,'maxValue':10,'increment':1}),\n                    (\"Max Zombies\", {'default':10,'minValue':5, 'maxValue':50,'increment':5}),\n                    (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                            ('2 Minutes',120),('5 Minutes',300),\n                                            ('10 Minutes',600),('20 Minutes',1200)],'default':120}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Epic Mode\",{'default':False})]\n\n        if issubclass(sessionType,bs.TeamsSession):\n            settings.append((\"Solo Mode\",{'default':False}))\n            settings.append((\"Balance Total Lives\",{'default':False}))\n            \n        return settings\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n        \n        # show messages when players die since it's meaningful here\n        self.announcePlayerDeaths = True\n        \n        #Need to create our Zombie character.  It's a composite of several.\n        #We'll name it 'Kronk2'\n        try: self._soloMode = settings['Solo Mode']\n        except Exception: self._soloMode = False\n        self._scoreBoard = bs.ScoreBoard()\n        self.spazList = []\n        self.zombieQ = 0\n        activity = bs.getActivity()\n        try: myFactory = activity._sharedSpazFactory\n        except Exception:\n            myFactory = activity._sharedSpazFactory = bsSpaz.SpazFactory()\n        #Load up resources for our composite model\n        appears=['Kronk','Zoe','Pixel','Agent Johnson','Bones','Frosty','Kronk2']\n        myAppear = copy.copy(bsSpaz.appearances['Kronk'])\n        myAppear.name = 'Kronk2'\n        bsSpaz.appearances['Kronk2'] = myAppear\n        for appear in appears:\n            myFactory._getMedia(appear)\n        #Now all the media is loaded up for the spazzes we are pulling from. \n        med = myFactory.spazMedia\n        med['Kronk2']['headModel'] = med['Zoe']['headModel']\n        med['Kronk2']['colorTexture']=med['Agent Johnson']['colorTexture']\n        med['Kronk2']['colorMaskTexture']=med['Pixel']['colorMaskTexture']\n        med['Kronk2']['torsoModel'] = med['Bones']['torsoModel']\n        med['Kronk2']['pelvisModel'] = med['Pixel']['pelvisModel']\n        med['Kronk2']['upperArmModel'] = med['Frosty']['upperArmModel']\n        med['Kronk2']['foreArmModel'] = med['Frosty']['foreArmModel']\n        med['Kronk2']['handModel'] = med['Bones']['handModel']\n        med['Kronk2']['upperLegModel'] = med['Bones']['upperLegModel']\n        med['Kronk2']['lowerLegModel'] = med['Pixel']['lowerLegModel']\n        med['Kronk2']['toesModel'] = med['Bones']['toesModel']\n\n    def getInstanceDescription(self):\n        return 'Kill walkers for points! Dead player walker: 2 points!' if isinstance(self.getSession(),bs.TeamsSession) else 'Kill walkers for points! Dead player walker: 2 points!'\n\n    def getInstanceScoreBoardDescription(self):\n        return 'Kill walkers for points! Dead player walker: 2 points!' if isinstance(self.getSession(),bs.TeamsSession) else 'Kill walkers for points! Dead player walker: 2 points!'\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n        self._startGameTime = bs.getGameTime()\n\n    def onTeamJoin(self,team):\n        team.gameData['score'] = 0\n        team.gameData['spawnOrder'] = []\n        self._updateScoreBoard()\n\n    def onPlayerJoin(self, player):\n\n        # no longer allowing mid-game joiners here... too easy to exploit\n        if self.hasBegun():\n            player.gameData['lives'] = 0\n            player.gameData['icons'] = []\n            # make sure our team has survival seconds set if they're all dead\n            # (otherwise blocked new ffa players would be considered 'still alive' in score tallying)\n            #if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None:\n            #    player.getTeam().gameData['survivalSeconds'] = 1000\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            return\n        \n        player.gameData['lives'] = self.settings['Lives Per Player']\n\n        if self._soloMode:\n            player.gameData['icons'] = []\n            player.getTeam().gameData['spawnOrder'].append(player)\n            self._updateSoloMode()\n        else:\n            # create our icon and spawn\n            player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)]\n            if player.gameData['lives'] > 0:\n                self.spawnPlayer(player)\n\n        # dont waste time doing this until begin\n        if self.hasBegun():\n            self._updateIcons()\n\n    def _updateSoloMode(self):\n        # for both teams, find the first player on the spawn order list with lives remaining\n        # and spawn them if they're not alive\n        for team in self.teams:\n            # prune dead players from the spawn order\n            team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n            for player in team.gameData['spawnOrder']:\n                if player.gameData['lives'] > 0:\n                    if not player.isAlive(): self.spawnPlayer(player)\n                    break\n\n    def _updateIcons(self):\n        # in free-for-all mode, everyone is just lined up along the bottom\n        if isinstance(self.getSession(),bs.FreeForAllSession):\n            count = len(self.teams)\n            xOffs = 85\n            x = xOffs*(count-1) * -0.5\n            for i,team in enumerate(self.teams):\n                if len(team.players) == 1:\n                    player = team.players[0]\n                    for icon in player.gameData['icons']:\n                        icon.setPositionAndScale((x,30),0.7)\n                        icon.updateForLives()\n                    x += xOffs\n\n        # in teams mode we split up teams\n        else:\n            if self._soloMode:\n                # first off, clear out all icons\n                for player in self.players:\n                    player.gameData['icons'] = []\n                # now for each team, cycle through our available players adding icons\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -60\n                        xOffs = -78\n                    else:\n                        x = 60\n                        xOffs = 78\n                    isFirst = True\n                    testLives = 1\n                    while True:\n                        playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives]\n                        if len(playersWithLives) == 0: break\n                        for player in playersWithLives:\n                            player.gameData['icons'].append(Icon(player,\n                                                                 position=(x,(40 if isFirst else 25)),\n                                                                 scale=1.0 if isFirst else 0.5,\n                                                                 nameMaxWidth=130 if isFirst else 75,\n                                                                 nameScale=0.8 if isFirst else 1.0,\n                                                                 flatness=0.0 if isFirst else 1.0,\n                                                                 shadow=0.5 if isFirst else 1.0,\n                                                                 showDeath=True if isFirst else False,\n                                                                 showLives=False))\n                            x += xOffs * (0.8 if isFirst else 0.56)\n                            isFirst = False\n                        testLives += 1\n            # non-solo mode\n            else:\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -50\n                        xOffs = -85\n                    else:\n                        x = 50\n                        xOffs = 85\n                    for player in team.players:\n                        for icon in player.gameData['icons']:\n                            icon.setPositionAndScale((x,30),0.7)\n                            icon.updateForLives()\n                        x += xOffs\n                    \n    def _getSpawnPoint(self,player):\n        # in solo-mode, if there's an existing live player on the map, spawn at whichever\n        # spot is farthest from them (keeps the action spread out)\n        if self._soloMode:\n            livingPlayer = None\n            for team in self.teams:\n                for player in team.players:\n                    if player.isAlive():\n                        p = player.actor.node.position\n                        livingPlayer = player\n                        livingPlayerPos = p\n                        break\n            if livingPlayer:\n                playerPos = bs.Vector(*livingPlayerPos)\n                points = []\n                for team in self.teams:\n                    startPos = bs.Vector(*self.getMap().getStartPosition(team.getID()))\n                    points.append([(startPos-playerPos).length(),startPos])\n                points.sort()\n                return points[-1][1]\n            else:\n                return None\n        else:\n            return None\n\n        \n    def spawnPlayer(self,player):\n        \"\"\"This next line is the default spawn line. But we need to spawn our special guy\"\"\"\n        #self.spawnPlayerSpaz(player,self._getSpawnPoint(player))\n        #position = self._getSpawnPoint(player)\n        #if isinstance(self.getSession(), bs.TeamsSession):\n        #    position = self.getMap().getStartPosition(player.getTeam().getID())\n        #else:\n        #\t# otherwise do free-for-all spawn locations\n        position = self.getMap().getFFAStartPosition(self.players)\n\n        angle = 20\n\n\n        #spaz = self.spawnPlayerSpaz(player)\n\n        # lets reconnect this player's controls to this\n        # spaz but *without* the ability to attack or pick stuff up\n        #spaz.connectControlsToPlayer(enablePunch=False,\n        #\t\t\t\t\t\t\t enableBomb=False,\n        #\t\t\t\t\t\t\t enablePickUp=False)\n\n        # also lets have them make some noise when they die..\n        #spaz.playBigDeathSound = True\n\n        name = player.getName()\n\n        lightColor = bsUtils.getNormalizedColor(player.color)\n        displayColor = bs.getSafeColor(player.color, targetIntensity=0.75)\n\n        spaz = PlayerSpaz_Zom(color=player.color,\n                             highlight=player.highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n        #For some reason, I can't figure out how to get a list of all spaz.\n        #Therefore, I am making the list here so I can get which spaz belongs\n        #to the player supplied by HitMessage.\n        self.spazList.append(spaz)\n        # we want a bigger area-of-interest in co-op mode\n        # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n        # else: spaz.node.areaOfInterestRadius = 5.0\n\n        # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n        # collide with the player-walls\n        # FIXME; need to generalize this\n        if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n\n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer() #Unfortunately, I can't figure out how to prevent picking up other player but allow other pickup.\n        factory = spaz.getFactory()\n        self.scoreSet.playerGotNewSpaz(player, spaz)\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound, 1, position=spaz.node.position)\n        light = bs.newNode('light', attrs={'color': lightColor})\n        spaz.node.connectAttr('position', light, 'position')\n        bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0})\n        bs.gameTimer(500, light.delete)\n        #Start code to spawn special guy:\n        #End of code to spawn special guy\n        if not self._soloMode:\n            bs.gameTimer(300,bs.Call(self._printLives,player))\n\n        # if we have any icons, update their state\n        for icon in player.gameData['icons']:\n            icon.handlePlayerSpawned()\n            \n    def respawnPlayerZombie(self,player,respawnTime=None):\n        \"\"\"\n        Given a bs.Player, sets up a standard respawn timer,\n        along with the standard counter display, etc.\n        At the end of the respawn period spawnPlayer() will\n        be called if the Player still exists.\n        An explicit 'respawnTime' can optionally be provided\n        (in milliseconds).\n        \"\"\"\n        \n        if player is None or not player.exists():\n            if player is None: bs.printError('None passed as player to respawnPlayer()')\n            else: bs.printError('Nonexistant bs.Player passed to respawnPlayer(); call player.exists() to make sure a player is still there.')\n            return\n        \n        if player.getTeam() is None:\n            bs.printError('player has no team in respawnPlayer()')\n            return\n            \n        if respawnTime is None:\n            if len(player.getTeam().players) == 1: respawnTime = 3000\n            elif len(player.getTeam().players) == 2: respawnTime = 5000\n            elif len(player.getTeam().players) == 3: respawnTime = 6000\n            else: respawnTime = 7000\n\n        # if this standard setting is present, factor it in\n        if 'Respawn Times' in self.settings: respawnTime *= self.settings['Respawn Times']\n\n        respawnTime = int(max(1000,respawnTime))\n        if respawnTime%1000 != 0: respawnTime -= respawnTime%1000 # we want whole seconds\n\n        if player.actor and not self.hasEnded():\n            import bsSpaz\n            player.gameData['respawnTimer'] = bs.Timer(respawnTime,bs.WeakCall(self.spawnPlayerIfExistsAsZombie,player))\n            player.gameData['respawnIcon'] = bsSpaz.RespawnIcon(player,respawnTime)\n    def spawnPlayerIfExistsAsZombie(self,player):\n        \"\"\"\n        A utility method which calls self.spawnPlayer() *only* if the bs.Player\n        provided still exists; handy for use in timers and whatnot.\n\n        There is no need to override this; just override spawnPlayer().\n        \"\"\"\n        if player.exists(): self.spawnPlayerZombie(player)  \n        \n    def spawnPlayerZombie(self,player):\n        position = self.getMap().getFFAStartPosition(self.players)\n        angle = 20\n        name = player.getName()\n        lightColor = bsUtils.getNormalizedColor(player.color)\n        displayColor = bs.getSafeColor(player.color, targetIntensity=0.75)\n        spaz = PlayerZombie(color=player.color,\n                             highlight=player.highlight,\n                             character='Kronk2',\n                             player=player)\n        player.setActor(spaz)\n        #For some reason, I can't figure out how to get a list of all spaz.\n        #Therefore, I am making the list here so I can get which spaz belongs\n        #to the player supplied by HitMessage.\n        self.spazList.append(spaz)\n        # we want a bigger area-of-interest in co-op mode\n        # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n        # else: spaz.node.areaOfInterestRadius = 5.0\n\n        # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n        # collide with the player-walls\n        # FIXME; need to generalize this\n        if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n        #Need to prevent picking up powerups:\n        pam = bs.Powerup.getFactory().powerupAcceptMaterial\n        for attr in ['materials','rollerMaterials','extrasMaterials']:\n                        materials = getattr(spaz.node,attr)\n                        if pam in materials:\n                            setattr(spaz.node,attr,tuple(m for m in materials if m != pam))\n        #spaz.node.materials.remove(pam)\n        #spaz.node.rollerMaterials.remove(pam)\n        #spaz.node.extrasMaterials.remove(pam)\n        \n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer(enablePunch=True,\n                                       enableBomb=False,\n                                       enablePickUp=False) #Unfortunately, I can't figure out how to prevent picking up other player but allow other pickup.\n        self.scoreSet.playerGotNewSpaz(player, spaz)\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound, 1, position=spaz.node.position)\n        light = bs.newNode('light', attrs={'color': lightColor})\n        spaz.node.connectAttr('position', light, 'position')\n        bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0})\n        bs.gameTimer(500, light.delete)\n        #Start code to spawn special guy:\n        #End of code to spawn special guy\n        if not self._soloMode:\n            bs.gameTimer(300,bs.Call(self._printLives,player))\n\n        # if we have any icons, update their state\n        for icon in player.gameData['icons']:\n            icon.handlePlayerSpawned()\n\n    def _printLives(self,player):\n        if not player.exists() or not player.isAlive(): return\n        try: pos = player.actor.node.position\n        except Exception,e:\n            print 'EXC getting player pos in bsElim',e\n            return\n        if player.gameData['lives'] > 0:\n            bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1),\n                            offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain()\n        else:\n            bs.PopupText('Dead!',color=(1,1,0,1),\n                            offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain()\n\n    def onPlayerLeave(self,player):\n\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n\n        player.gameData['icons'] = None\n\n        # remove us from spawn-order\n        if self._soloMode:\n            if player in player.getTeam().gameData['spawnOrder']:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n\n        # update icons in a moment since our team will be gone from the list then\n        bs.gameTimer(0, self._updateIcons)\n\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        self.setupStandardPowerupDrops()\n        self.zombieQ = 1 # queue of zombies to spawn. this will increment/decrement \n        if self._soloMode:\n            self._vsText = bs.NodeActor(bs.newNode(\"text\",\n                                                   attrs={'position':(0,105),\n                                                          'hAttach':\"center\",\n                                                          'hAlign':'center',\n                                                          'maxWidth':200,\n                                                          'shadow':0.5,\n                                                          'vrDepth':390,\n                                                          'scale':0.6,\n                                                          'vAttach':\"bottom\",\n                                                          'color':(0.8,0.8,0.3,1.0),\n                                                          'text':bs.Lstr(resource='vsText')}))\n\n        # if balance-team-lives is on, add lives to the smaller team until total lives match\n        if (isinstance(self.getSession(),bs.TeamsSession)\n            and self.settings['Balance Total Lives']\n            and len(self.teams[0].players) > 0\n            and len(self.teams[1].players) > 0):\n            if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]):\n                lesserTeam = self.teams[0]\n                greaterTeam = self.teams[1]\n            else:\n                lesserTeam = self.teams[1]\n                greaterTeam = self.teams[0]\n            addIndex = 0\n            while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam):\n                lesserTeam.players[addIndex].gameData['lives'] += 1\n                addIndex = (addIndex + 1) % len(lesserTeam.players)\n        #Let's add a couple of bots\n        # this wrangles our bots\n        self._bots = zBotSet()\n        \n        #Set colors and character for ToughGuyBot to be zombie\n        setattr(bs.ToughGuyBot, 'color', (0.4,0.1,0.05))\n        setattr(bs.ToughGuyBot, 'highlight', (0.2,0.4,0.3))\n        setattr(bs.ToughGuyBot, 'character', 'Kronk2')\n        # start some timers to spawn bots\n        thePt = self.getMap().getFFAStartPosition(self.players)\n        #bs.gameTimer(1000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=thePt,spawnTime=3000))\n        \n        self._updateIcons()\n        self._updateScoreBoard\n\n        # we could check game-over conditions at explicit trigger points,\n        # but lets just do the simple thing and poll it...\n        bs.gameTimer(1000, self._update, repeat=True)\n        \n    def _getTotalTeamLives(self,team):\n        return sum(player.gameData['lives'] for player in team.players)\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            \n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n            player = m.spaz.getPlayer()\n            #print([player, m.spaz.hitPoints, \"killed by\", m.killerPlayer])\n            if player.gameData['lives'] > 0: #Dying player was not zombie. Remove a life\n                player.gameData['lives'] -= 1\n            else:   #Dying player was a zombie.  Give points to killer\n                if m.killerPlayer.exists():\n                    if m.killerPlayer.gameData['lives'] > 0:\n                        m.killerPlayer.getTeam().gameData['score'] += 2\n                        self._updateScoreBoard()\n                \n            #Remove this spaz from the list of active spazzes\n            if m.spaz in self.spazList: self.spazList.remove(m.spaz)\n            if player.gameData['lives'] < 0:\n                bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode))\n                player.gameData['lives'] = 0\n\n            # if we have any icons, update their state\n            for icon in player.gameData['icons']:\n                icon.handlePlayerDied()\n\n            # play big death sound on our last death or for every one in solo mode\n            if self._soloMode or player.gameData['lives'] == 0:\n                bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound)\n\n            # if we hit zero lives, we're dead. Become a zombie.\n            if player.gameData['lives'] == 0:\n                self.respawnPlayerZombie(player)\n            else:\n                # otherwise, in regular mode, respawn..\n                if not self._soloMode:\n                    self.respawnPlayer(player)\n\n            # in solo, put ourself at the back of the spawn order\n            if self._soloMode:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n                player.getTeam().gameData['spawnOrder'].append(player)\n        elif isinstance(m,bs.SpazBotDeathMessage):\n            self._onSpazBotDied(m)\n            bs.TeamGameActivity.handleMessage(self,m)\n            #bs.PopupText(\"died\",position=self._position,color=popupColor,scale=popupScale).autoRetain()\n        else:\n            bs.TeamGameActivity.handleMessage(self,m)\n    def _update(self):\n        #self.randZombie()\n        #Check if we neeed more zombies\n        if self.zombieQ > 0:\n            self.zombieQ -= 1\n            self.spawnZombie()\n        if self._soloMode:\n            # for both teams, find the first player on the spawn order list with lives remaining\n            # and spawn them if they're not alive\n            for team in self.teams:\n                # prune dead players from the spawn order\n                team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n                for player in team.gameData['spawnOrder']:\n                    if player.gameData['lives'] > 0:\n                        if not player.isAlive():\n                            self.spawnPlayer(player)\n                            self._updateIcons()\n                        break\n        \n        # if we're down to 1 or fewer living teams, start a timer to end the game\n        # (allows the dust to settle and draws to occur if deaths are close enough)\n        #Actually, since this is Zombie Horde, let's allow the last player to die in case they are behind. So less than 1 living team.\n        teamsRemain = self._getLivingTeams()\n        if len(teamsRemain) < 2:\n            if len(teamsRemain) == 1:\n                theScores = []\n                for team in self.teams:\n                    theScores.append(team.gameData['score'])\n                if teamsRemain[0].gameData['score']< max(theScores):\n                    pass # the last guy doesn't have the best score\n                elif teamsRemain[0].gameData['score'] == max(theScores) and theScores.count(max(theScores)) > 1:\n                    pass #The last guy left is tied for the lead!  Can he get one more?\n                else:\n                    self._roundEndTimer = bs.Timer(500,self.endGame)\n            else:\n                self._roundEndTimer = bs.Timer(500,self.endGame)\n                \n\n    def spawnZombie(self):\n        #We need a Z height...\n        thePt = list(self.getRandomPointInPlay())\n        thePt2 = self.getMap().getFFAStartPosition(self.players)\n        thePt[1] = thePt2[1]\n        bs.gameTimer(100,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=thePt,spawnTime=1000))\n            \n    def _onSpazBotDied(self,DeathMsg):\n        #Just in case we are over max...\n        if len(self._bots.getLivingBots()) < self.settings['Max Zombies']:\n            #Go ahead and replace dead zombie, no matter how it died\n            self.zombieQ +=1\n\n            if DeathMsg.killerPlayer is None:\n                pass\n            else:\n                player = DeathMsg.killerPlayer\n                #print(player)\n                if not player.exists(): return # could happen if they leave after throwing a bomb..\n                if player.gameData['lives'] < 1: return #You only get a point if you have lives\n                player.getTeam().gameData['score'] += 1\n                #if kill was legit, spawn additional zombie!\n                self.zombieQ += 1\n                self._updateScoreBoard()\n                \n    def getRandomPointInPlay(self):\n        #So far, randomized points only figured out for mostly rectangular maps.\n        #Boxes will still fall through holes, but shouldn't be terrible problem (hopefully)\n        #If you add stuff here, need to add to \"supported maps\" above.\n        #['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium']\n        myMap = self.getMap().getName()\n        #print(myMap)\n        if myMap == 'Doom Shroom':\n            while True:\n                x = random.uniform(-1.0,1.0)\n                y = random.uniform(-1.0,1.0)\n                if x*x+y*y < 1.0: break\n            return ((8.0*x,8.0,-3.5+5.0*y))\n        elif myMap == 'Rampage':\n            x = random.uniform(-6.0,7.0)\n            y = random.uniform(-6.0,-2.5)\n            return ((x, 8.0, y))\n        elif myMap == 'Hockey Stadium':\n            x = random.uniform(-11.5,11.5)\n            y = random.uniform(-4.5,4.5)\n            return ((x, 5.0, y))\n        elif myMap == 'Courtyard':\n            x = random.uniform(-4.3,4.3)\n            y = random.uniform(-4.4,0.3)\n            return ((x, 8.0, y))\n        elif myMap == 'Crag Castle':\n            x = random.uniform(-6.7,8.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 12.0, y))\n        elif myMap == 'Big G':\n            x = random.uniform(-8.7,8.0)\n            y = random.uniform(-7.5,6.5)\n            return ((x, 8.0, y))\n        elif myMap == 'Football Stadium':\n            x = random.uniform(-12.5,12.5)\n            y = random.uniform(-5.0,5.5)\n            return ((x, 8.0, y))\n        else:\n            x = random.uniform(-5.0,5.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 8.0, y))            \n            \n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team, team.gameData['score'])\n    def _getLivingTeams(self):\n        return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)]\n\n    def endGame(self):\n        if self.hasEnded(): return\n        #Reset the default color for the ToughGuyBot\n        setattr(bs.ToughGuyBot, 'color', (0.6,0.6,0.6))\n        setattr(bs.ToughGuyBot, 'highlight', (0.6,0.6,0.6))\n        setattr(bs.ToughGuyBot, 'character', 'Kronk')\n        results = bs.TeamGameResults()\n        self._vsText = None # kill our 'vs' if its there\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['score'])\n        self.end(results=results)\n        \n"
  },
  {
    "path": "mods/airStrike.json",
    "content": "{\n  \"name\": \"Air Strike\",\n  \"author\": \"SoKpl\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/airStrike.py",
    "content": "import bs\nimport random\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [AirStrikeGame]\n\ndef bsGetLevels():\n\t# Levels are unique named instances of a particular game with particular settings.\n\t# They show up as buttons in the co-op section, get high-score lists associated with them, etc.\n\treturn [bs.Level('Air Strike', # globally-unique name for this level (not seen by user)\n\t\t\tdisplayName='${GAME}', # ${GAME} will be replaced by the results of the game's getName() call\n\t\t\tgameType=AirStrikeGame,\n\t\t\tsettings={}, # we currently dont have any settings; we'd specify them here if we did.\n\t\t\tpreviewTexName='courtyardPreview')]\n\nclass AirStrikeGame(bs.TeamGameActivity):\n\t# name seen by the user\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Air Strike'\n\n\t@classmethod\n\tdef getScoreInfo(cls):\n\t\treturn {'scoreType':'milliseconds',\n\t\t\t\t'lowerIsBetter':True,\n\t\t\t\t'scoreName':'Time'}\n\n\t@classmethod\n\tdef getDescription(cls,sessionType):\n\t\treturn 'Enemies are coming from air! Kill them all!'\n\n\t@classmethod\n\tdef getSupportedMaps(cls,sessionType):\n\t\t# for now we're hard-coding spawn positions and whatnot\n\t\t# so we need to be sure to specity that we only support\n\t\t# a specific map..\n\t\treturn ['Courtyard']\n\n\t@classmethod\n\tdef supportsSessionType(cls,sessionType):\n\t\t# we currently support Co-Op only\n\t\treturn True if issubclass(sessionType,bs.CoopSession) else False\n\n\t# in the constructor we should load any media we need/etc.\n\t# but not actually create anything yet.\n\tdef __init__(self,settings):\n\t\tbs.TeamGameActivity.__init__(self,settings)\n\t\tself._winSound = bs.getSound(\"score\")\n\n\t# called when our game is transitioning in but not ready to start..\n\t# ..we can go ahead and start creating stuff, playing music, etc.\n\tdef onTransitionIn(self):\n\t\tbs.TeamGameActivity.onTransitionIn(self, music='ToTheDeath')\n\n\t# called when our game actually starts\n\tdef onBegin(self):\n\t\tbs.TeamGameActivity.onBegin(self)\n\n\t\tself._won = False\n\n\t\tself.setupStandardPowerupDrops()\n\n\t\t# make our on-screen timer and start it roughly when our bots appear\n\t\tself._timer = bs.OnScreenTimer()\n\t\tbs.gameTimer(4000,self._timer.start)\n\n\n\t\t# this wrangles our bots\n\t\tself._bots = bs.BotSet()\n\n\t\t# spawn some baddies\n\t\tself._bots = bs.BotSet()\n\t\tbs.gameTimer(1000,bs.Call(self._bots.spawnBot,bs.MelBotStatic,pos=(6,7,-6),spawnTime=3000))\n\t\tbs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.ToughGuyBotProShielded,pos=(-3,10,-2),spawnTime=3000))\n\t\tbs.gameTimer(3000,bs.Call(self._bots.spawnBot,bs.NinjaBotProShielded,pos=(5,6,-2),spawnTime=3000))\n\t\tbs.gameTimer(4000,bs.Call(self._bots.spawnBot,bs.BomberBotProShielded,pos=(-5,6,-2),spawnTime=3000))\n\t\tbs.gameTimer(5000,bs.Call(self._bots.spawnBot,bs.ChickBotStatic,pos=(-6,7,-6),spawnTime=3000))\n\n\t\t# note: if spawns were spread out more we'd probably want to set some sort of flag on the\n\t\t# last spawn to ensure we don't inadvertantly allow a 'win' before every bot is spawned.\n\t\t# (ie: if bot 1, 2, and 3 got killed but 4 hadn't spawned yet, the game might end because\n\t\t# it sees no remaining bots.\n\n\t# called for each spawning player\n\tdef spawnPlayer(self,player):\n\n\t\t# lets spawn close to the center\n\t\tspawnCenter = (0,3,-2)\n\t\tpos = (spawnCenter[0]+random.uniform(-1.5,1.5),spawnCenter[1],spawnCenter[2]+random.uniform(-1.5,1.5))\n\n\t\tself.spawnPlayerSpaz(player,position=pos)\n\n\tdef _checkIfWon(self):\n\t\t# simply end the game if there's no living bots..\n\t\tif not self._bots.haveLivingBots():\n\t\t\tself._won = True\n\t\t\tself.endGame()\n\n\t# called for miscellaneous events\n\tdef handleMessage(self,m):\n\n\t\t# a player has died\n\t\tif isinstance(m,bs.PlayerSpazDeathMessage):\n\t\t\tbs.TeamGameActivity.handleMessage(self,m) # do standard stuff\n\t\t\tself.respawnPlayer(m.spaz.getPlayer()) # kick off a respawn\n\n\t\t# a spaz-bot has died\n\t\telif isinstance(m,bs.SpazBotDeathMessage):\n\t\t\t# unfortunately the bot-set will always tell us there are living\n\t\t\t# bots if we ask here (the currently-dying bot isn't officially marked dead yet)\n\t\t\t# ..so lets push a call into the event loop to check once this guy has finished dying.\n\t\t\tbs.pushCall(self._checkIfWon)\n\n\t\telse:\n\t\t\t# let the base class handle anything we don't..\n\t\t\tbs.TeamGameActivity.handleMessage(self,m)\n\n\t# when this is called, we should fill out results and end the game\n\t# *regardless* of whether is has been won. (this may be called due\n\t# to a tournament ending or other external reason)\n\tdef endGame(self):\n\n\t\t# stop our on-screen timer so players can see what they got\n\t\tself._timer.stop()\n\n\t\tresults = bs.TeamGameResults()\n\n\t\t# if we won, set our score to the elapsed time\n\t\t# (there should just be 1 team here since this is co-op)\n\t\t# ..if we didn't win, leave scores as default (None) which means we lost\n\t\tif self._won:\n\t\t\telapsedTime = bs.getGameTime()-self._timer.getStartTime()\n\t\t\tself.cameraFlash()\n\t\t\tbs.playSound(self._winSound)\n\t\t\tfor team in self.teams:\n\t\t\t\tteam.celebrate() # woooo! par-tay!\n\t\t\t\tresults.setTeamScore(team,elapsedTime)\n\n\t\t# ends this activity..\n\t\tself.end(results)\n"
  },
  {
    "path": "mods/arms_race.json",
    "content": "{\n  \"name\": \"Arms Race\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/arms_race.py",
    "content": "import bs\nimport bsUtils\nimport random\n\n\nclass State:\n\tdef __init__(self, bomb=None, grab=False, punch=False, curse=False, required=False, final=False, name=\"\"):\n\t\tself.bomb = bomb\n\t\tself.grab = grab\n\t\tself.punch = punch\n\t\tself.pickup = False\n\t\tself.curse = curse\n\t\tself.required = required or final\n\t\tself.final = final\n\t\tself.name = name\n\t\tself.next = None\n\t\tself.index = None\n\n\tdef apply(self, spaz):\n\t\tspaz.disconnectControlsFromPlayer()\n\t\tspaz.connectControlsToPlayer(enablePunch=self.punch,\n\t\t\t\t\t\t\t\t\t enableBomb=bool(self.bomb),\n\t\t\t\t\t\t\t\t\t enablePickUp=self.grab)\n\t\tif self.curse:\n\t\t\tspaz.curseTime = -1\n\t\t\tspaz.curse()\n\t\tif self.bomb:\n\t\t\tspaz.bombType = self.bomb\n\t\tspaz.setScoreText(self.name)\n\n\n\tdef getSetting(self):\n\t\treturn (self.name, {'default': True})\n\n\nclass ArmsRace(bs.TeamGameActivity):\n\tstates = [\n\t\tState(bomb='normal', name='Basic Bombs'),\n\t\tState(bomb='ice', name='Frozen Bombs'),\n\t\tState(bomb='sticky', name='Sticky Bombs'),\n\t\tState(bomb='impact', name='Impact Bombs'),\n\t\tState(grab=True, name='Grabbing only'),\n\t\tState(punch=True, name='Punching only'),\n\t\tState(curse=True, name='Cursed', final=True)\n\t]\n\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Arms Race'\n\n\t@classmethod\n\tdef getScoreInfo(cls):\n\t\treturn {'scoreType': 'points',\n\t\t\t\t'lowerIsBetter': False,\n\t\t\t\t'scoreName': 'Score'}\n\n\t@classmethod\n\tdef getDescription(cls, sessionType):\n\t\treturn \"Upgrade your weapon by eliminating enemies.\\nWin the match by being the first player\\nto get a kill while cursed.\"\n\n\tdef getInstanceDescription(self):\n\t\treturn 'Upgrade your weapon by eliminating enemies.'\n\n\tdef getInstanceScoreBoardDescription(self):\n\t\treturn 'Kill {} Players to win'.format(len(self.states))\n\n\t@classmethod\n\tdef supportsSessionType(cls, sessionType):\n\t\treturn True if (issubclass(sessionType, bs.TeamsSession)\n\t\t\t\t\t\tor issubclass(sessionType, bs.FreeForAllSession)) else False\n\n\t@classmethod\n\tdef getSupportedMaps(cls, sessionType):\n\t\treturn bs.getMapsSupportingPlayType(\"melee\")\n\n\t@classmethod\n\tdef getSettings(cls, sessionType):\n\t\tsettings = [(\"Epic Mode\", {'default': False}),\n\t\t\t\t\t(\"Time Limit\", {'choices': [('None', 0), ('1 Minute', 60),\n\t\t\t\t\t\t\t\t\t\t\t\t('2 Minutes', 120), ('5 Minutes', 300)],\n\t\t\t\t\t\t\t\t\t\t\t\t'default': 0})]\n\t\tfor state in cls.states:\n\t\t\tif not state.required:\n\t\t\t\tsettings.append(state.getSetting())\n\t\treturn settings\n\n\tdef __init__(self, settings):\n\t\tself.states = [s for s in self.states if settings.get(s.name, True)]\n\t\tfor i, state in enumerate(self.states):\n\t\t\tif i < len(self.states) and not state.final:\n\t\t\t\tstate.next = self.states[i + 1]\n\t\t\tstate.index = i\n\t\tbs.TeamGameActivity.__init__(self, settings)\n\t\tself.announcePlayerDeaths = True\n\t\tif self.settings['Epic Mode']:\n\t\t\tself._isSlowMotion = True\n\n\tdef onTransitionIn(self):\n\t\tbs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n\t\tself._startGameTime = bs.getGameTime()\n\n\tdef onBegin(self):\n\t\tbs.TeamGameActivity.onBegin(self)\n\t\tself.setupStandardTimeLimit(self.settings['Time Limit'])\n\t\t#self.setupStandardPowerupDrops(enableTNT=False)\n\n\tdef onPlayerJoin(self, player):\n\t\tif 'state' not in player.gameData:\n\t\t\tplayer.gameData['state'] = self.states[0]\n\t\tself.spawnPlayer(player)\n\n\t# overriding the default character spawning..\n\tdef spawnPlayer(self, player):\n\t\tstate = player.gameData['state']\n\t\tsuper(self.__class__, self).spawnPlayer(player)\n\t\tstate.apply(player.actor)\n\n\tdef isValidKill(self, m):\n\t\treturn all([\n\t\t\tm.killed,\n\t\t\tm.spaz.getPlayer() is not m.killerPlayer,\n\t\t\tm.killerPlayer is not None,\n\t\t\tm.spaz.getPlayer().color is not m.killerPlayer.color\n\t\t])\n\n\t# various high-level game events come through this method\n\tdef handleMessage(self,m):\n\t\tif isinstance(m, bs.PlayerSpazDeathMessage):\n\n\t\t\tbs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n\n\t\t\tif self.isValidKill(m):\n\t\t\t\tif not m.killerPlayer.gameData[\"state\"].final:\n\t\t\t\t\tm.killerPlayer.gameData[\"state\"] = m.killerPlayer.gameData[\"state\"].next\n\t\t\t\t\tm.killerPlayer.gameData[\"state\"].apply(m.killerPlayer.actor)\n\t\t\t\telse:\n\t\t\t\t\tself.endGame()\n\t\t\tself.respawnPlayer(m.spaz.getPlayer())\n\t\telse:\n\t\t\tsuper(self.__class__, self).handleMessage(m)\n\n\tdef endGame(self):\n\t\tresults = bs.TeamGameResults()\n\t\tfor team in self.teams:\n\t\t\tscore = max([player.gameData[\"state\"].index for player in team.players])\n\t\t\tresults.setTeamScore(team, score)\n\t\tself.end(results=results)\n\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [ArmsRace]\n"
  },
  {
    "path": "mods/auto_reloader.json",
    "content": "{\n  \"name\": \"Auto Reloader\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"utilities\",\n  \"supports\": [\"config_editor\"]\n}\n"
  },
  {
    "path": "mods/auto_reloader.py",
    "content": "import bs\nimport os\nimport os.path\nfrom md5 import md5\nimport weakref\nimport imp\nimport sys\n\ndefault = {\n\t\"_version\": 1,\n\t\"_name\": \"Auto Reload\",\n\t\"enabled\": dict(_default=True, _name=\"Enable\"),\n\t\"check_interval\": dict(_default=2.5, _min=1, _inc=0.5, _max=10, _name=\"Check interval\"),\n\t\"folder\": dict(_default=\"auto_reloader_mods\", _name=\"Folder\")\n}\n\nif bs.getConfig().get(\"auto_reloader\", default)[\"_version\"] != default[\"_version\"]:\n\tbs.getConfig()[\"auto_reloader\"] = default\nbs.getConfig()[\"auto_reloader\"] = bs.getConfig().get(\"auto_reloader\", default)\n\ndef cfg(key):\n\treturn bs.getConfig()[\"auto_reloader\"][key].get(\"_value\", bs.getConfig()[\"auto_reloader\"][key][\"_default\"])\n\nCHECK_INTERVAL = int(cfg(\"check_interval\") * 1000)\nIMPORT_FOLDER = bs.getEnvironment()['userScriptsDirectory'] + \"/\" + cfg(\"folder\") + \"/\"\nsys.path.append(IMPORT_FOLDER) # FIXME\n\nclass GameWrapper(object):\n\t_game = None\n\t_type = None\n\t_instances = weakref.WeakSet()\n\tdef __init__(self, filename):\n\t\tself._filename = filename\n\t\twith open(IMPORT_FOLDER + self._filename, \"r\") as f:\n\t\t\tself._module_md5 = md5(f.read()).hexdigest()\n\t\tself._did_print_error = False\n\t\tself._import_module()\n\t\tif self._is_available() and self._type == \"game\":\n\t\t\tself._game = self._module.bsGetGames()[0]\n\t\telse:\n\t\t\tself._game = None\n\n\tdef _import_module(self):\n\t\ttry:\n\t\t\tdata = imp.find_module(self._filename[:-3])\n\t\t\tself._module = imp.load_module(self._filename[:-3], *data)\n\t\texcept Exception, e:\n\t\t\timport traceback\n\t\t\ttraceback.print_exc()\n\t\t\tself._module = None\n\t\t\tself._game = None\n\t\t\tself._module_error(str(e))\n\n\tdef _module_error(self, *args):\n\t\tprint(self._filename + \": \" + \" \".join(args))\n\t\tif not self._did_print_error:\n\t\t\tbs.screenMessage(self._filename + \": \" + \" \".join(args), color=(1, 0, 0))\n\t\t\tself._did_print_error = True\n\n\tdef _is_available(self):\n\t\tif not self._module:\n\t\t\treturn False\n\t\tif not hasattr(self._module, '_supports_auto_reloading'):\n\t\t\tself._module_error('missing _supports_auto_reloading')\n\t\t\treturn False\n\t\tif not hasattr(self._module, '_auto_reloader_type'):\n\t\t\tself._module_error('missing _auto_reloader_type')\n\t\t\treturn False\n\t\tself._type = self._module._auto_reloader_type\n\n\t\tif not hasattr(self._module, '_prepare_reload'):\n\t\t\tself._module_error('missing _prepare_reload')\n\t\t\treturn False\n\t\tif not hasattr(self._module, 'bsGetAPIVersion'):\n\t\t\tself._module_error('missing bsGetAPIVersion')\n\t\t\treturn False\n\t\tif self._type == \"game\" and not hasattr(self._module, 'bsGetGames'):\n\t\t\tself._module_error('missing bsGetGames')\n\t\t\treturn False\n\n\n\t\tif not self._module._supports_auto_reloading:\n\t\t\tself._module_error('doesnt support auto reloading')\n\t\t\treturn False\n\t\tif not self._module.bsGetAPIVersion() == 3:\n\t\t\tself._module_error('missing wrong API Version', self._module.bsGetAPIVersion())\n\t\t\treturn False\n\t\tif self._type == \"game\" and len(self._module.bsGetGames()) != 1:\n\t\t\tself._module_error(\"more than 1 game isnt supported\") # FIXME\n\t\t\treturn False\n\t\treturn True\n\n\tdef _prepare_reload(self):\n\t\ttry:\n\t\t\tif hasattr(self._module, \"_prepare_reload\"):\n\t\t\t\tself._module._prepare_reload()\n\t\t\tfor instance in self._instances:\n\t\t\t\tif instance and hasattr(instance, \"_prepare_reload\"):\n\t\t\t\t\tinstance._prepare_reload()\n\t\texcept Exception, e:\n\t\t\tprint(e)\n\t\t\tself._module_error(\"_prepare_reload failed\")\n\n\tdef _reload_module(self):\n\t\tbs.screenMessage(\"reloading \" + self._filename)\n\t\tself._prepare_reload()\n\t\tself._import_module()\n\t\t#self._module = import_module(self._filename[:-3], package=IMPORT_FOLDER.split(\"/\")[-2])\n\t\twith open(IMPORT_FOLDER + self._filename, \"r\") as f:\n\t\t\tself._module_md5 = md5(f.read()).hexdigest()\n\t\tself._did_print_error = False\n\t\tif self._is_available() and self._type == \"game\":\n\t\t\tself._game = self._module.bsGetGames()[0]\n\t\telse:\n\t\t\tself._game = None\n\t\tbs.playSound(bs.getSound('swish'))\n\n\tdef _check_update(self):\n\t\twith open(IMPORT_FOLDER + self._filename, \"r\") as f:\n\t\t\tdata = f.read()\n\t\t\tif self._module_md5 != md5(data).hexdigest():\n\t\t\t\tself._reload_module()\n\n\tdef __call__(self, *args, **kwargs):\n\t\tif not self._type == \"game\":\n\t\t\tself._module_error(\"non games can't be called\")\n\t\tinstance = self._game(*args, **kwargs)\n\t\tself._instances.add(instance)\n\t\treturn instance\n\n\tdef __getattr__(self, key):\n\t\t\"pass static methods\"\n\t\treturn getattr(self._game, key)\n\nwrappers = []\n\nif cfg(\"enabled\"):\n\tif not os.path.isdir(IMPORT_FOLDER):\n\t\tos.mkdir(IMPORT_FOLDER)\n\n\tfor file in os.listdir(IMPORT_FOLDER):\n\t\tif not file.endswith(\".py\"):\n\t\t\tcontinue\n\t\tif file.startswith(\".\"):\n\t\t\tcontinue\n\t\twrappers.append(GameWrapper(file))\n\n\twrappers = [w for w in wrappers if w._is_available()]\n\t# print(\"tracking mods:\", [wrapper._filename for wrapper in wrappers])\n\n\tdef check_wrappers():\n\t\tfor wrapper in wrappers:\n\t\t\twrapper._check_update()\n\t\tbs.realTimer(CHECK_INTERVAL, check_wrappers)\n\n\tif CHECK_INTERVAL:\n\t\tbs.realTimer(CHECK_INTERVAL, check_wrappers)\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [wrapper for wrapper in wrappers if wrapper._type == \"game\"]\n"
  },
  {
    "path": "mods/bomb_on_my_head.json",
    "content": "{\n  \"name\": \"Bomb on my Head\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/bomb_on_my_head.py",
    "content": "import random\n\nimport bs\nimport bsUtils\nfrom bsSpaz import _BombDiedMessage\n\nclass PlayerSpazBombOnMyHead(bs.PlayerSpaz):\n\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, _BombDiedMessage):\n\t\t\t#bs.screenMessage('recyceling')\n\t\t\tself.bombCount += 1\n\t\t\tself.checkAvalibleBombs()\n\t\telse:\n\t\t\tsuper(self.__class__, self).handleMessage(m)\n\n\tdef checkAvalibleBombs(self):\n\t\tif self.exists():\n\t\t\tif self.bombCount >= 1:\n\t\t\t\tif not self.node.holdNode.exists():\n\t\t\t\t\tself.onBombPress()\n\t\t\t\t\tself.onBombRelease()\n\n\tdef startBombChecking(self):\n\t\tself.checkAvalibleBombs()\n\t\tself._bombCheckTimer = bs.gameTimer(500, bs.WeakCall(self.checkAvalibleBombs), repeat=True)\n\n\tdef dropBomb(self):\n\t\tlifespan = 3000\n\n\t\tif (self.bombCount <= 0) or self.frozen:\n\t\t\treturn\n\t\tp = self.node.positionForward\n\t\tv = self.node.velocity\n\n\t\tbombType = \"normal\"\n\n\t\tbomb = bs.Bomb(position=(p[0], p[1] - 0.0, p[2]),\n\t\t\t\t\t   velocity=(v[0], v[1], v[2]),\n\t\t\t\t\t   bombType=bombType,\n\t\t\t\t\t   blastRadius=self.blastRadius,\n\t\t\t\t\t   sourcePlayer=self.sourcePlayer,\n\t\t\t\t\t   owner=self.node).autoRetain()\n\n\t\tbsUtils.animate(bomb.node, 'modelScale', {0:0.0,\n\t\t\t\t\t\t\t\t   lifespan*0.1:1.5,\n\t\t\t\t\t\t\t\t   lifespan*0.5:1.0})\n\n\n\n\t\tself.bombCount -= 1\n\t\tbomb.node.addDeathAction(bs.WeakCall(self.handleMessage, _BombDiedMessage()))\n\n\t\tself._pickUp(bomb.node)\n\n\t\tfor meth in self._droppedBombCallbacks:\n\t\t\tmeth(self, bomb)\n\n\t\treturn bomb\n\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [BombOnMyHead]\n\nclass BombOnMyHead(bs.TeamGameActivity):\n\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Bomb on my Head'\n\n\t@classmethod\n\tdef getScoreInfo(cls):\n\t\treturn {'scoreName':'Survived',\n\t\t\t\t'scoreType':'milliseconds',\n\t\t\t\t'scoreVersion':'B'}\n\n\t@classmethod\n\tdef getDescription(cls, sessionType):\n\t\treturn \"You'll always have a bomb on your head. \\n Survive as long as you can!\"\n\n\n\tdef getInstanceDescription(self):\n\t\treturn 'Survive as long as you can'\n\n\t@classmethod\n\tdef supportsSessionType(cls, sessionType):\n\t\treturn True if (issubclass(sessionType, bs.TeamsSession)\n\t\t\t\t\t\tor issubclass(sessionType, bs.FreeForAllSession)) else False\n\n\t@classmethod\n\tdef getSupportedMaps(cls, sessionType):\n\t\treturn bs.getMapsSupportingPlayType(\"melee\")\n\n\t@classmethod\n\tdef getSettings(cls,sessionType):\n\t\treturn [(\"Time Limit\", {'choices':[('None', 0), ('1 Minute', 60),\n\t\t\t\t\t\t\t\t\t\t('2 Minutes', 120), ('5 Minutes', 300),\n\t\t\t\t\t\t\t\t\t\t('10 Minutes', 600), ('20 Minutes', 1200)], 'default':0}),\n\t\t\t\t(\"Max Bomb Limit\", {'choices':[('Normal', 1.0), ('Two', 2.0), ('Three', 3.0), ('Four', 4.0)], 'default':1.0}),\n\t\t\t\t(\"Epic Mode\", {'default':False})]\n\n\tdef __init__(self, settings):\n\t\tbs.TeamGameActivity.__init__(self, settings)\n\n\t\tif self.settings['Epic Mode']:\n\t\t\tself._isSlowMotion = True\n\n\t\t# print messages when players die (since its meaningful in this game)\n\t\tself.announcePlayerDeaths = True\n\n\t\tself._lastPlayerDeathTime = None\n\n\t\tself.startTime = 1000\n\n\n\tdef onTransitionIn(self):\n\t\tbs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Chosen One')\n\n\tdef onBegin(self):\n\n\t\tbs.TeamGameActivity.onBegin(self)\n\n\n\t# overriding the default character spawning..\n\tdef spawnPlayer(self, player):\n\n\n\n\t\tif isinstance(self.getSession(), bs.TeamsSession):\n\t\t\tposition = self.getMap().getStartPosition(player.getTeam().getID())\n\t\telse:\n\t\t\t# otherwise do free-for-all spawn locations\n\t\t\tposition = self.getMap().getFFAStartPosition(self.players)\n\n\t\tangle = None\n\n\n\t\t#spaz = self.spawnPlayerSpaz(player)\n\n\t\t# lets reconnect this player's controls to this\n\t\t# spaz but *without* the ability to attack or pick stuff up\n\t\t#spaz.connectControlsToPlayer(enablePunch=False,\n\t\t#\t\t\t\t\t\t\t enableBomb=False,\n\t\t#\t\t\t\t\t\t\t enablePickUp=False)\n\n\t\t# also lets have them make some noise when they die..\n\t\t#spaz.playBigDeathSound = True\n\n\t\tname = player.getName()\n\n\t\tlightColor = bsUtils.getNormalizedColor(player.color)\n\t\tdisplayColor = bs.getSafeColor(player.color, targetIntensity=0.75)\n\n\t\tspaz = PlayerSpazBombOnMyHead(color=player.color,\n\t\t\t\t\t\t\t highlight=player.highlight,\n\t\t\t\t\t\t\t character=player.character,\n\t\t\t\t\t\t\t player=player)\n\t\tplayer.setActor(spaz)\n\n\t\t# we want a bigger area-of-interest in co-op mode\n\t\t# if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n\t\t# else: spaz.node.areaOfInterestRadius = 5.0\n\n\t\t# if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n\t\t# collide with the player-walls\n\t\t# FIXME; need to generalize this\n\t\tif isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']:\n\t\t\tmat = self.getMap().preloadData['collideWithWallMaterial']\n\t\t\tspaz.node.materials += (mat,)\n\t\t\tspaz.node.rollerMaterials += (mat,)\n\n\t\tspaz.node.name = name\n\t\tspaz.node.nameColor = displayColor\n\t\tspaz.connectControlsToPlayer()\n\t\tself.scoreSet.playerGotNewSpaz(player, spaz)\n\n\t\t# move to the stand position and add a flash of light\n\t\tspaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360)))\n\t\tt = bs.getGameTime()\n\t\tbs.playSound(self._spawnSound, 1, position=spaz.node.position)\n\t\tlight = bs.newNode('light', attrs={'color':lightColor})\n\t\tspaz.node.connectAttr('position', light, 'position')\n\t\tbsUtils.animate(light, 'intensity', {0:0, 250:1, 500:0})\n\t\tbs.gameTimer(500, light.delete)\n\n\n\t\t#bs.gameTimer(1000, bs.WeakCall(spaz.onBombPress))\n\t\tbs.gameTimer(self.startTime, bs.WeakCall(spaz.startBombChecking))\n\t\tspaz.setBombCount(self.settings['Max Bomb Limit'])\n\n\n\t# various high-level game events come through this method\n\tdef handleMessage(self,m):\n\t\tif isinstance(m, bs.PlayerSpazDeathMessage):\n\n\t\t\tsuper(self.__class__, self).handleMessage(m)#bs.TeamGameActivity.handleMessage(self,m) # (augment standard behavior)\n\n\t\t\tdeathTime = bs.getGameTime()\n\n\t\t\t# record the player's moment of death\n\t\t\tm.spaz.getPlayer().gameData['deathTime'] = deathTime\n\n\t\t\t# in co-op mode, end the game the instant everyone dies (more accurate looking)\n\t\t\t# in teams/ffa, allow a one-second fudge-factor so we can get more draws\n\t\t\tif isinstance(self.getSession(), bs.CoopSession):\n\t\t\t\t# teams will still show up if we check now.. check in the next cycle\n\t\t\t\tbs.pushCall(self._checkEndGame)\n\t\t\t\tself._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock..\n\t\t\telse:\n\t\t\t\tbs.gameTimer(1000, self._checkEndGame)\n\n\t\telse:\n\t\t\t# default handler:\n\t\t\tsuper(self.__class__, self).handleMessage(m)#bs.TeamGameActivity.handleMessage(self,m)\n\n\tdef _checkEndGame(self):\n\t\tlivingTeamCount = 0\n\t\tfor team in self.teams:\n\t\t\tfor player in team.players:\n\t\t\t\tif player.isAlive():\n\t\t\t\t\tlivingTeamCount += 1\n\t\t\t\t\tbreak\n\n\t\t# in co-op, we go till everyone is dead.. otherwise we go until one team remains\n\t\tif isinstance(self.getSession(), bs.CoopSession):\n\t\t\tif livingTeamCount <= 0:\n\t\t\t\tself.endGame()\n\t\telse:\n\t\t\tif livingTeamCount <= 1:\n\t\t\t\tself.endGame()\n\n\tdef endGame(self):\n\n\t\tcurTime = bs.getGameTime()\n\n\t\t# mark 'death-time' as now for any still-living players\n\t\t# and award players points for how long they lasted.\n\t\t# (these per-player scores are only meaningful in team-games)\n\t\tfor team in self.teams:\n\t\t\tfor player in team.players:\n\n\t\t\t\t# throw an extra fudge factor +1 in so teams that\n\t\t\t\t# didn't die come out ahead of teams that did\n\t\t\t\tif 'deathTime' not in player.gameData:\n\t\t\t\t\tplayer.gameData['deathTime'] = curTime+1 - self.startTime\n\n\t\t\t\t# award a per-player score depending on how many seconds they lasted\n\t\t\t\t# (per-player scores only affect teams mode; everywhere else just looks at the per-team score)\n\t\t\t\tscore = (player.gameData['deathTime'])\n\t\t\t\tif 'deathTime' not in player.gameData:\n\t\t\t\t\tscore += 50 # a bit extra for survivors\n\t\t\t\tself.scoreSet.playerScored(player, score, screenMessage=False)\n\n\n\t\t# ok now calc game results: set a score for each team and then tell the game to end\n\t\tresults = bs.TeamGameResults()\n\n\t\t# remember that 'free-for-all' mode is simply a special form of 'teams' mode\n\t\t# where each player gets their own team, so we can just always deal in teams\n\t\t# and have all cases covered\n\t\tfor team in self.teams:\n\n\t\t\t# set the team score to the max time survived by any player on that team\n\t\t\tlongestLife = 0\n\t\t\tfor player in team.players:\n\t\t\t\tlongestLife = max(longestLife, (player.gameData['deathTime'] - self.startTime))\n\t\t\tresults.setTeamScore(team, longestLife)\n\n\t\tself.end(results=results)\n"
  },
  {
    "path": "mods/bomberman.json",
    "content": "{\n  \"name\": \"Bomberman\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"minigames\",\n  \"tag\": \"experimental\"\n}\n"
  },
  {
    "path": "mods/bomberman.py",
    "content": "import bs\nimport bsUtils\nimport bsElimination\nimport bsBomb\nimport bsSpaz\nimport random\nimport math\n\n\nclass Map:\n\tcenter = (0, 3, -4)\n\tradius = 8\n\n\t@classmethod\n\tdef inBounds(cls, pos):\n\t\tdx, dy, dz = pos[0] - cls.center[0], pos[1] - cls.center[1], pos[2] - cls.center[2],\n\t\treturn cls.radius >= math.sqrt(dx**2 + dy**2 + dz**2)\n\n\n\nclass Crate(bsBomb.Bomb):\n\tdef __init__(self, position=(0, 1, 0), velocity=(0, 0, 0)):\n\t\tself.position = position\n\t\tbsBomb.Bomb.__init__(self, position, velocity,\n\t\t\t\t\t\tbombType='tnt', blastRadius=0.0,\n\t\t\t\t\t\tsourcePlayer=None, owner=None)\n\t\tself.node.extraAcceleration = (0, -50, 0)\n\n\tdef handleMessage(self, m):\n\t\t#if isinstance(m, bs.PickedUpMessage):\n\t\t#\tself._heldBy = m.node\n\t\t#elif isinstance(m, bs.DroppedMessage):\n\t\t#\tbs.animate(self._powText, 'scale', {0:0.01, 500: 0.03})\n\t\t#\tbs.gameTimer(500, bs.WeakCall(self.pow))\n\t\tbsBomb.Bomb.handleMessage(self, m)\n\n\tdef explode(self):\n\t\tpos = self.position\n\t\tbs.gameTimer(100, bs.WeakCall(bs.getActivity().dropPowerup, pos))\n\t\tbs.gameTimer(1, bs.WeakCall(self.handleMessage, bs.DieMessage()))\n\nclass Bomb(bsBomb.Bomb):\n\tdef explode(self):\n\t\tif self._exploded:\n\t\t\treturn\n\t\tself._exploded = True\n\t\tsize = int(self.blastRadius)\n\t\tfor mod in range(-size, size+1):\n\t\t\tpos = self.node.position\n\t\t\tposX = (pos[0] + mod*1.0, pos[1], pos[2])\n\t\t\tposY = (pos[0], pos[1], pos[2] + mod*1.0)\n\t\t\tif Map.inBounds(posX):\n\t\t\t\tbs.gameTimer(abs(mod)*150, bs.Call(blast, posX, self.bombType, self.sourcePlayer, self.hitType, self.hitSubType))\n\t\t\tif Map.inBounds(posY):\n\t\t\t\tbs.gameTimer(abs(mod)*150, bs.Call(blast, posY, self.bombType, self.sourcePlayer, self.hitType, self.hitSubType))\n\n\t\tbs.gameTimer(1, bs.WeakCall(self.handleMessage, bs.DieMessage()))\n\n\nclass Blast(bsBomb.Blast):\n\t# all that code to reduce the camera shake effect\n\tdef __init__(self,position=(0,1,0),velocity=(0,0,0),blastRadius=2.0,blastType=\"normal\",sourcePlayer=None,hitType='explosion',hitSubType='normal'):\n\t\t\"\"\"\n\t\tInstantiate with given values.\n\t\t\"\"\"\n\t\tbs.Actor.__init__(self)\n\n\n\t\tfactory = Bomb.getFactory()\n\n\t\tself.blastType = blastType\n\t\tself.sourcePlayer = sourcePlayer\n\n\t\tself.hitType = hitType;\n\t\tself.hitSubType = hitSubType;\n\n\t\t# blast radius\n\t\tself.radius = blastRadius\n\n\t\tself.node = bs.newNode('region',\n\t\t\t\t\t\t\t   attrs={'position':(position[0],position[1]-0.1,position[2]), # move down a bit so we throw more stuff upward\n\t\t\t\t\t\t\t\t\t  'scale':(self.radius,self.radius,self.radius),\n\t\t\t\t\t\t\t\t\t  'type':'sphere',\n\t\t\t\t\t\t\t\t\t  'materials':(factory.blastMaterial,bs.getSharedObject('attackMaterial'))},\n\t\t\t\t\t\t\t   delegate=self)\n\n\t\tbs.gameTimer(50,self.node.delete)\n\n\t\t# throw in an explosion and flash\n\t\texplosion = bs.newNode(\"explosion\",\n\t\t\t\t\t\t\t   attrs={'position':position,\n\t\t\t\t\t\t\t\t\t  'velocity':(velocity[0],max(-1.0,velocity[1]),velocity[2]),\n\t\t\t\t\t\t\t\t\t  'radius':self.radius,\n\t\t\t\t\t\t\t\t\t  'big':(self.blastType == 'tnt')})\n\t\tif self.blastType == \"ice\":\n\t\t\texplosion.color = (0,0.05,0.4)\n\n\t\tbs.gameTimer(1000,explosion.delete)\n\n\t\tif self.blastType != 'ice': bs.emitBGDynamics(position=position,velocity=velocity,count=int(1.0+random.random()*4),emitType='tendrils',tendrilType='thinSmoke')\n\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*4),emitType='tendrils',tendrilType='ice' if self.blastType == 'ice' else 'smoke')\n\t\tbs.emitBGDynamics(position=position,emitType='distortion',spread=1.0 if self.blastType == 'tnt' else 2.0)\n\n\t\t# and emit some shrapnel..\n\t\tif self.blastType == 'ice':\n\t\t\tdef _doEmit():\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=30,spread=2.0,scale=0.4,chunkType='ice',emitType='stickers');\n\t\t\tbs.gameTimer(50,_doEmit) # looks better if we delay a bit\n\n\n\t\telif self.blastType == 'sticky':\n\t\t\tdef _doEmit():\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),spread=0.7,chunkType='slime');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),scale=0.5, spread=0.7,chunkType='slime');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=15,scale=0.6,chunkType='slime',emitType='stickers');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=20,scale=0.7,chunkType='spark',emitType='stickers');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(6.0+random.random()*12),scale=0.8,spread=1.5,chunkType='spark');\n\t\t\tbs.gameTimer(50,_doEmit) # looks better if we delay a bit\n\n\t\telif self.blastType == 'impact': # regular bomb shrapnel\n\t\t\tdef _doEmit():\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),scale=0.8,chunkType='metal');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),scale=0.4,chunkType='metal');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=20,scale=0.7,chunkType='spark',emitType='stickers');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(8.0+random.random()*15),scale=0.8,spread=1.5,chunkType='spark');\n\t\t\tbs.gameTimer(50,_doEmit) # looks better if we delay a bit\n\n\t\telse: # regular or land mine bomb shrapnel\n\t\t\tdef _doEmit():\n\t\t\t\tif self.blastType != 'tnt':\n\t\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),chunkType='rock');\n\t\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),scale=0.5,chunkType='rock');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=30,scale=1.0 if self.blastType=='tnt' else 0.7,chunkType='spark',emitType='stickers');\n\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(18.0+random.random()*20),scale=1.0 if self.blastType == 'tnt' else 0.8,spread=1.5,chunkType='spark');\n\n\t\t\t\t# tnt throws splintery chunks\n\t\t\t\tif self.blastType == 'tnt':\n\t\t\t\t\tdef _emitSplinters():\n\t\t\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(20.0+random.random()*25),scale=0.8,spread=1.0,chunkType='splinter');\n\t\t\t\t\tbs.gameTimer(10,_emitSplinters)\n\n\t\t\t\t# every now and then do a sparky one\n\t\t\t\tif self.blastType == 'tnt' or random.random() < 0.1:\n\t\t\t\t\tdef _emitExtraSparks():\n\t\t\t\t\t\tbs.emitBGDynamics(position=position,velocity=velocity,count=int(10.0+random.random()*20),scale=0.8,spread=1.5,chunkType='spark');\n\t\t\t\t\tbs.gameTimer(20,_emitExtraSparks)\n\n\t\t\tbs.gameTimer(50,_doEmit) # looks better if we delay a bit\n\n\t\tlight = bs.newNode('light',\n\t\t\t\t\t\t   attrs={'position':position,\n\t\t\t\t\t\t\t\t  'color': (0.6,0.6,1.0) if self.blastType == 'ice' else (1,0.3,0.1),\n\t\t\t\t\t\t\t\t  'volumeIntensityScale': 10.0})\n\n\t\ts = random.uniform(0.6,0.9)\n\t\tscorchRadius = lightRadius = self.radius\n\t\tif self.blastType == 'tnt':\n\t\t\tlightRadius *= 1.4\n\t\t\tscorchRadius *= 1.15\n\t\t\ts *= 3.0\n\n\t\tiScale = 1.6\n\t\tbsUtils.animate(light,\"intensity\",{0:2.0*iScale, int(s*20):0.1*iScale, int(s*25):0.2*iScale, int(s*50):17.0*iScale, int(s*60):5.0*iScale, int(s*80):4.0*iScale, int(s*200):0.6*iScale, int(s*2000):0.00*iScale, int(s*3000):0.0})\n\t\tbsUtils.animate(light,\"radius\",{0:lightRadius*0.2, int(s*50):lightRadius*0.55, int(s*100):lightRadius*0.3, int(s*300):lightRadius*0.15, int(s*1000):lightRadius*0.05})\n\t\tbs.gameTimer(int(s*3000),light.delete)\n\n\t\t# make a scorch that fades over time\n\t\tscorch = bs.newNode('scorch',\n\t\t\t\t\t\t\tattrs={'position':position,'size':scorchRadius*0.5,'big':(self.blastType == 'tnt')})\n\t\tif self.blastType == 'ice':\n\t\t\tscorch.color = (1,1,1.5)\n\n\t\tbsUtils.animate(scorch,\"presence\",{3000:1, 13000:0})\n\t\tbs.gameTimer(13000,scorch.delete)\n\n\t\tif self.blastType == 'ice':\n\t\t\tbs.playSound(factory.hissSound,position=light.position)\n\n\t\tp = light.position\n\t\tbs.playSound(factory.getRandomExplodeSound(),position=p)\n\t\tbs.playSound(factory.debrisFallSound,position=p)\n\n\t\t########\n\t\tbs.shakeCamera(intensity=5.0 if self.blastType == 'tnt' else 0.05)\n\t\t########\n\n\t\t# tnt is more epic..\n\t\tif self.blastType == 'tnt':\n\t\t\tbs.playSound(factory.getRandomExplodeSound(),position=p)\n\t\t\tdef _extraBoom():\n\t\t\t\tbs.playSound(factory.getRandomExplodeSound(),position=p)\n\t\t\tbs.gameTimer(250,_extraBoom)\n\t\t\tdef _extraDebrisSound():\n\t\t\t\tbs.playSound(factory.debrisFallSound,position=p)\n\t\t\t\tbs.playSound(factory.woodDebrisFallSound,position=p)\n\t\t\tbs.gameTimer(400,_extraDebrisSound)\n\n\ndef blast(pos, blastType, sourcePlayer, hitType, hitSubType):\n\tBlast(position=pos, velocity=(0, 1, 0),\n\t\t  blastRadius=0.5,blastType=blastType,\n\t\t  sourcePlayer=sourcePlayer,hitType=hitType,\n\t\t  hitSubType=hitSubType).autoRetain()\n\nclass Player(bs.PlayerSpaz):\n\tisDead = False\n\n\t#def __init__(self, *args, **kwargs):\n\t#\tsuper(self.__class__, self).init(*args, **kwargs)\n\t#\tself.multiplyer = 0\n\n\n\tdef handleMessage(self, m):\n\t\tif False:\n\t\t\tpass\n\t\telif isinstance(m, bs.PowerupMessage):\n\t\t\tif m.powerupType == 'punch':\n\t\t\t\tself.blastRadius += 1.0\n\t\t\t\tself.setScoreText(\"range up\")\n\t\t\tsuper(self.__class__, self).handleMessage(m)\n\t\telse:\n\t\t\tsuper(self.__class__, self).handleMessage(m)\n\n\tdef dropBomb(self):\n\t\t\"\"\"\n\t\tTell the spaz to drop one of his bombs, and returns\n\t\tthe resulting bomb object.\n\t\tIf the spaz has no bombs or is otherwise unable to\n\t\tdrop a bomb, returns None.\n\t\t\"\"\"\n\n\t\tif (self.landMineCount <= 0 and self.bombCount <= 0) or self.frozen: return\n\t\tp = self.node.positionForward\n\t\tv = self.node.velocity\n\n\t\tif self.landMineCount > 0:\n\t\t\tdroppingBomb = False\n\t\t\tself.setLandMineCount(self.landMineCount-1)\n\t\t\tbombType = 'landMine'\n\t\telse:\n\t\t\tdroppingBomb = True\n\t\t\tbombType = self.bombType\n\n\t\tbomb = Bomb(position=(p[0],p[1] - 0.0,p[2]),\n\t\t\t\t\t   velocity=(v[0],v[1],v[2]),\n\t\t\t\t\t   bombType=bombType,\n\t\t\t\t\t   blastRadius=self.blastRadius,\n\t\t\t\t\t   sourcePlayer=self.sourcePlayer,\n\t\t\t\t\t   owner=self.node).autoRetain()\n\n\t\tif droppingBomb:\n\t\t\tself.bombCount -= 1\n\t\t\tbomb.node.addDeathAction(bs.WeakCall(self.handleMessage,bsSpaz._BombDiedMessage()))\n\n\t\tself._pickUp(bomb.node)\n\n\t\tfor c in self._droppedBombCallbacks: c(self,bomb)\n\n\t\treturn bomb\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [Bomberman]\n\nclass Bomberman(bs.TeamGameActivity):\n\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Bomberman'\n\n\t@classmethod\n\tdef getScoreInfo(cls):\n\t\treturn {'scoreName':'Survived',\n\t\t\t'scoreType':'seconds',\n\t\t\t'scoreVersion':'B',\n\t\t\t'noneIsWinner':True}\n\n\t@classmethod\n\tdef getDescription(cls, sessionType):\n\t\treturn \"Destroy crates and collect powerups\"\n\n\n\tdef getInstanceDescription(self):\n\t\treturn 'Destroy crates and collect powerups'\n\n\t@classmethod\n\tdef supportsSessionType(cls, sessionType):\n\t\treturn True if (issubclass(sessionType, bs.TeamsSession)\n\t\t\t\t\t\tor issubclass(sessionType, bs.FreeForAllSession)) else False\n\n\t@classmethod\n\tdef getSupportedMaps(cls, sessionType):\n\t\treturn [\"Doom Shroom\"]\n\n\t@classmethod\n\tdef getSettings(cls, sessionType):\n\t\treturn [(\"Time Limit\",{'choices':[('None',0),('1 Minute',60),('2 Minutes',120),\n\t\t\t\t\t\t\t\t\t\t\t('5 Minutes',300)],'default':0}),\n\t\t\t\t(\"Lives (0 = Unlimited)\",{'minValue':0,'default':3,'increment':1}),\n\t\t\t\t(\"Epic Mode\",{'default':False})]\n\n\tdef __init__(self, settings):\n\t\tbs.TeamGameActivity.__init__(self,settings)\n\t\tif self.settings['Epic Mode']:\n\t\t\tself._isSlowMotion = True\n\n\t\t# print messages when players die (since its meaningful in this game)\n\t\tself.announcePlayerDeaths = True\n\n\t\tself._lastPlayerDeathTime = None\n\n\t\tself._startGameTime = 1000\n\t\tself.gridsize = (1.0, 1.0)\n\t\tself.gridnum = (18, 18)\n\n\n\tdef onTransitionIn(self):\n\t\tbs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n\t\tself._startGameTime = bs.getGameTime()\n\n\tdef onBegin(self):\n\t\tbs.TeamGameActivity.onBegin(self)\n\t\tself.setupStandardTimeLimit(self.settings['Time Limit'])\n\t\tfor x in range(self.gridnum[0]):\n\t\t\tfor y in range(self.gridnum[1]):\n\t\t\t\tself.dropCrate(x, y)\n\n\n\tdef dropCrate(self, gridX, gridY):\n\t\tpos = (Map.center[0] + self.gridsize[0]*gridX - self.gridnum[0]*self.gridsize[0]*0.5,\n\t\t\t\tMap.center[1],\n\t\t\t\tMap.center[2] + self.gridsize[1]*gridY - self.gridnum[1]*self.gridsize[1]*0.5)\n\t\t#print('dropped crate @', pos)\n\t\tif Map.inBounds(pos):\n\t\t\tCrate(position=pos).autoRetain()\n\n\tdef dropPowerup(self, position):\n\t\tpowerupType = random.choice([\"punch\", \"tripleBombs\", \"health\"])\n\t\tbs.Powerup(position=position, powerupType=powerupType, expire=False).autoRetain()\n\n\tdef onPlayerJoin(self, player):\n\t\tself.spawnPlayer(player)\n\n\tdef onPlayerLeave(self, player):\n\t\tbs.TeamGameActivity.onPlayerLeave(self, player)\n\n\t# overriding the default character spawning..\n\tdef spawnPlayer(self, player):\n\n\n\n\t\tif isinstance(self.getSession(), bs.TeamsSession):\n\t\t\tposition = self.getMap().getStartPosition(player.getTeam().getID())\n\t\telse:\n\t\t\t# otherwise do free-for-all spawn locations\n\t\t\tposition = self.getMap().getFFAStartPosition(self.players)\n\n\t\tangle = None\n\n\n\t\t#spaz = self.spawnPlayerSpaz(player)\n\n\t\t# lets reconnect this player's controls to this\n\t\t# spaz but *without* the ability to attack or pick stuff up\n\t\t#spaz.connectControlsToPlayer(enablePunch=False,\n\t\t#\t\t\t\t\t\t\t enableBomb=False,\n\t\t#\t\t\t\t\t\t\t enablePickUp=False)\n\n\t\t# also lets have them make some noise when they die..\n\t\t#spaz.playBigDeathSound = True\n\n\t\tname = player.getName()\n\n\t\tlightColor = bsUtils.getNormalizedColor(player.color)\n\t\tdisplayColor = bs.getSafeColor(player.color, targetIntensity=0.75)\n\n\t\tspaz = Player(color=player.color,\n\t\t\t\t\t\t\t highlight=player.highlight,\n\t\t\t\t\t\t\t character=player.character,\n\t\t\t\t\t\t\t player=player)\n\t\tplayer.setActor(spaz)\n\n\t\t# we want a bigger area-of-interest in co-op mode\n\t\t# if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n\t\t# else: spaz.node.areaOfInterestRadius = 5.0\n\n\t\t# if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n\t\t# collide with the player-walls\n\t\t# FIXME; need to generalize this\n\t\tif isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']:\n\t\t\tmat = self.getMap().preloadData['collideWithWallMaterial']\n\t\t\tspaz.node.materials += (mat,)\n\t\t\tspaz.node.rollerMaterials += (mat,)\n\n\t\tspaz.node.name = name\n\t\tspaz.node.nameColor = displayColor\n\t\tspaz.connectControlsToPlayer( enableJump=True, enablePunch=True, enablePickUp=False, enableBomb=True, enableRun=True, enableFly=False)\n\t\tself.scoreSet.playerGotNewSpaz(player,spaz)\n\n\t\t# move to the stand position and add a flash of light\n\t\tspaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0, 360)))\n\t\tt = bs.getGameTime()\n\t\tbs.playSound(self._spawnSound, 1, position=spaz.node.position)\n\t\tlight = bs.newNode('light', attrs={'color': lightColor})\n\t\tspaz.node.connectAttr('position', light, 'position')\n\t\tbsUtils.animate(light, 'intensity', {0:0, 250:1, 500:0})\n\t\tbs.gameTimer(500, light.delete)\n\n\n\n\t# various high-level game events come through this method\n\tdef handleMessage(self,m):\n\t\tif isinstance(m, bs.PlayerSpazDeathMessage):\n\n\t\t\tbs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n\t\t\tplayer = m.spaz.getPlayer()\n\t\t\tplayer.gameData[\"survivalSeconds\"] = bs.getGameTime()\n\n\t\t\tif len(self._getLivingTeams()) < 2:\n\t\t\t\tself._roundEndTimer = bs.Timer(1000, self.endGame)\n\n\t\telse:\n\t\t\t# default handler:\n\t\t\tsuper(self.__class__, self).handleMessage(m)#bs.TeamGameActivity.handleMessage(self,m)\n\n\tdef endGame(self):\n\n\t\tcurTime = bs.getGameTime()\n\t\t# mark 'death-time' as now for any still-living players\n\t\t# and award players points for how long they lasted.\n\t\t# (these per-player scores are only meaningful in team-games)\n\t\tfor team in self.teams:\n\t\t\tfor player in team.players:\n\n\t\t\t\t# throw an extra fudge factor +1 in so teams that\n\t\t\t\t# didn't die come out ahead of teams that did\n\t\t\t\tif 'survivalSeconds' in player.gameData:\n\t\t\t\t\tscore = player.gameData['survivalSeconds']\n\t\t\t\telif 'survivalSeconds' in team.gameData:\n\t\t\t\t\tscore = team.gameData['survivalSeconds']\n\t\t\t\telse:\n\t\t\t\t\tscore = (curTime - self._startGameTime)/1000 + 1\n\n\t\t\t\t#if 'survivalSeconds' not in player.gameData:\n\t\t\t\t#\tplayer.gameData['survivalSeconds'] = (curTime - self._startGameTime)/1000 + 1\n\t\t\t\t#\tprint('extraBonusSwag for player')\n\n\t\t\t\t# award a per-player score depending on how many seconds they lasted\n\t\t\t\t# (per-player scores only affect teams mode; everywhere else just looks at the per-team score)\n\t\t\t\t#score = (player.gameData['survivalSeconds'])\n\t\t\t\tself.scoreSet.playerScored(player, score, screenMessage=False)\n\n\n\t\t# ok now calc game results: set a score for each team and then tell the game to end\n\t\tresults = bs.TeamGameResults()\n\n\t\t# remember that 'free-for-all' mode is simply a special form of 'teams' mode\n\t\t# where each player gets their own team, so we can just always deal in teams\n\t\t# and have all cases covered\n\t\tfor team in self.teams:\n\n\t\t\t# set the team score to the max time survived by any player on that team\n\t\t\tlongestLife = 0\n\t\t\tfor player in team.players:\n\t\t\t\tif 'survivalSeconds' in player.gameData:\n\t\t\t\t\ttime = player.gameData['survivalSeconds']\n\t\t\t\telif 'survivalSeconds' in team.gameData:\n\t\t\t\t\ttime = team.gameData['survivalSeconds']\n\t\t\t\telse:\n\t\t\t\t\ttime = (curTime - self._startGameTime)/1000 + 1\n\t\t\t\tlongestLife = max(longestLife, time)\n\t\t\tresults.setTeamScore(team, longestLife)\n\n\t\tself.end(results=results)\n\n\tdef _getLivingTeams(self):\n\t\treturn [team for team in self.teams if len(team.players) > 0 and any('survivalSeconds' not in player.gameData for player in team.players)]\n\n"
  },
  {
    "path": "mods/boxing.json",
    "content": "{\n  \"name\": \"Boxing\",\n  \"author\": \"TheMikirog\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/boxing.py",
    "content": "import bs\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [DeathMatchGame]\n\nclass DeathMatchGame(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Boxing'\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return ('No bombs!\\n'\n\t\t\t\t'Knock out your enemies using your bare hands!\\n'\n\t\t\t\t'Powerups not included.')\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        return [(\"KOs to Win Per Player\",{'minValue':1,'default':5,'increment':1}),\n                (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                        ('2 Minutes',120),('5 Minutes',300),\n                                        ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                (\"Epic Mode\",{'default':False})]\n\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # print messages when players die since it matters here..\n        self.announcePlayerDeaths = True\n        \n        self._scoreBoard = bs.ScoreBoard()\n\n    def getInstanceDescription(self):\n        return ('KO ${ARG1} of your enemies.',self._scoreToWin)\n\n    def getInstanceScoreBoardDescription(self):\n        return ('KO ${ARG1} enemies',self._scoreToWin)\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'GrandRomp')\n\n    def onTeamJoin(self,team):\n        team.gameData['score'] = 0\n        if self.hasBegun(): self._updateScoreBoard()\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        if len(self.teams) > 0:\n            self._scoreToWin = self.settings['KOs to Win Per Player'] * max(1,max(len(t.players) for t in self.teams))\n        else: self._scoreToWin = self.settings['KOs to Win Per Player']\n        self._updateScoreBoard()\n        self._dingSound = bs.getSound('dingSmall')\n\n    def spawnPlayer(self,player):\n\n        spaz = self.spawnPlayerSpaz(player)\n        spaz.connectControlsToPlayer(enablePunch=True,\n                                     enableBomb=False,\n                                     enablePickUp=True)\n\n        spaz.equipBoxingGloves()\n\n    def handleMessage(self,m):\n\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior\n\n            player = m.spaz.getPlayer()\n            self.respawnPlayer(player)\n\n            killer = m.killerPlayer\n            if killer is None: return\n\n            # handle team-kills\n            if killer.getTeam() is player.getTeam():\n\n                # in free-for-all, killing yourself loses you a point\n                if isinstance(self.getSession(),bs.FreeForAllSession):\n                    player.getTeam().gameData['score'] = max(0,player.getTeam().gameData['score']-1)\n\n                # in teams-mode it gives a point to the other team\n                else:\n                    bs.playSound(self._dingSound)\n                    for team in self.teams:\n                        if team is not killer.getTeam():\n                            team.gameData['score'] += 1\n\n            # killing someone on another team nets a kill\n            else:\n                killer.getTeam().gameData['score'] += 1\n                bs.playSound(self._dingSound)\n                # in FFA show our score since its hard to find on the scoreboard\n                try: killer.actor.setScoreText(str(killer.getTeam().gameData['score'])+'/'+str(self._scoreToWin),color=killer.getTeam().color,flash=True)\n                except Exception: pass\n\n            self._updateScoreBoard()\n\n            # if someone has won, set a timer to end shortly\n            # (allows the dust to clear and draws to occur if deaths are close enough)\n            if any(team.gameData['score'] >= self._scoreToWin for team in self.teams):\n                bs.gameTimer(500,self.endGame)\n\n        else: bs.TeamGameActivity.handleMessage(self,m)\n\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team,team.gameData['score'],self._scoreToWin)\n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for t in self.teams: results.setTeamScore(t,t.gameData['score'])\n        self.end(results=results)\n"
  },
  {
    "path": "mods/brainFreeze.json",
    "content": "{\n  \"name\": \"Brain Freeze\",\n  \"author\": \"TheMikirog\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/brainFreeze.py",
    "content": "import bs\nimport random\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [BrainFreezeGame]\n\ndef bsGetLevels():\n    return [bs.Level('Brain Freeze',displayName='${GAME}',gameType=BrainFreezeGame,settings={},previewTexName='rampagePreview'),\n            bs.Level('Epic Brain Freeze',displayName='${GAME}',gameType=BrainFreezeGame,settings={'Epic Mode':True},previewTexName='rampagePreview')]\n\nclass BrainFreezeGame(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Brain Freeze'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Survived',\n                'scoreType':'milliseconds',\n                'scoreVersion':'B'}\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Dodge the falling ice bombs.'\n\n    # we're currently hard-coded for one map..\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Rampage']\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        return [(\"Epic Mode\",{'default':False})]\n    \n    # we support teams, free-for-all, and co-op sessions\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)\n                        or issubclass(sessionType,bs.CoopSession)) else False\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n        \n        # print messages when players die (since its meaningful in this game)\n        self.announcePlayerDeaths = True\n\n        self._lastPlayerDeathTime = None\n        \n    # called when our game is transitioning in but not ready to start..\n    # ..we can go ahead and set our music and whatnot\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n\n\n    # called when our game actually starts\n    def onBegin(self):\n\n        bs.TeamGameActivity.onBegin(self)\n        # drop a wave every few seconds.. and every so often drop the time between waves\n        # ..lets have things increase faster if we have fewer players\n        self._meteorTime = 3000\n        t = 7500 if len(self.players) > 2 else 4000\n        if self.settings['Epic Mode']: t /= 4\n        bs.gameTimer(t,self._decrementMeteorTime,repeat=True)\n\n        # kick off the first wave in a few seconds\n        t = 3000\n        if self.settings['Epic Mode']: t /= 4\n        bs.gameTimer(t,self._setMeteorTimer)\n\n        self._timer = bs.OnScreenTimer()\n        self._timer.start()\n        \n        \n    # overriding the default character spawning..\n    def spawnPlayer(self,player):\n\n        spaz = self.spawnPlayerSpaz(player)\n\n        # lets reconnect this player's controls to this\n        # spaz but *without* the ability to attack or pick stuff up\n        spaz.connectControlsToPlayer(enablePunch=False,\n                                     enableBomb=False,\n                                     enablePickUp=False)\n\n        # also lets have them make some noise when they die..\n        spaz.playBigDeathSound = True\n\n\n    # various high-level game events come through this method\n    def handleMessage(self,m):\n\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n\n            bs.TeamGameActivity.handleMessage(self,m) # (augment standard behavior)\n\n            deathTime = bs.getGameTime()\n            \n            # record the player's moment of death\n            m.spaz.getPlayer().gameData['deathTime'] = deathTime\n\n            # in co-op mode, end the game the instant everyone dies (more accurate looking)\n            # in teams/ffa, allow a one-second fudge-factor so we can get more draws\n            if isinstance(self.getSession(),bs.CoopSession):\n                # teams will still show up if we check now.. check in the next cycle\n                bs.pushCall(self._checkEndGame)\n                self._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock..\n            else:\n                bs.gameTimer(1000,self._checkEndGame)\n\n        else:\n            # default handler:\n            bs.TeamGameActivity.handleMessage(self,m)\n\n    def _checkEndGame(self):\n        livingTeamCount = 0\n        for team in self.teams:\n            for player in team.players:\n                if player.isAlive():\n                    livingTeamCount += 1\n                    break\n\n        # in co-op, we go till everyone is dead.. otherwise we go until one team remains\n        if isinstance(self.getSession(),bs.CoopSession):\n            if livingTeamCount <= 0: self.endGame()\n        else:\n            if livingTeamCount <= 1: self.endGame()\n        \n    def _setMeteorTimer(self):\n        bs.gameTimer(int((1.0+0.2*random.random())*self._meteorTime),self._dropBombCluster)\n        \n    def _dropBombCluster(self):\n\n        # random note: code like this is a handy way to plot out extents and debug things\n        if False:\n            bs.newNode('locator',attrs={'position':(8,6,-5.5)})\n            bs.newNode('locator',attrs={'position':(8,6,-2.3)})\n            bs.newNode('locator',attrs={'position':(-7.3,6,-5.5)})\n            bs.newNode('locator',attrs={'position':(-7.3,6,-2.3)})\n\n        # drop several bombs in series..\n\t\t\n        delay = 0\n        for i in range(random.randrange(1,3)):\n            # drop them somewhere within our bounds with velocity pointing toward the opposite side\n            pos = (-7.3+15.3*random.random(),11,-5.5+2.1*random.random())\n            vel = ((-5.0+random.random()*30.0) * (-1.0 if pos[0] > 0 else 1.0), -4.0,0)\n            bs.gameTimer(delay,bs.Call(self._dropBomb,pos,vel))\n            delay += 100\n        self._setMeteorTimer()\n\n    def _dropBomb(self,position,velocity):\n        b = bs.Bomb(position=position,velocity=velocity,bombType='ice').autoRetain()\n\n    def _decrementMeteorTime(self):\n        self._meteorTime = max(10,int(self._meteorTime*0.9))\n\n    def endGame(self):\n\n        curTime = bs.getGameTime()\n        \n        # mark 'death-time' as now for any still-living players\n        # and award players points for how long they lasted.\n        # (these per-player scores are only meaningful in team-games)\n        for team in self.teams:\n            for player in team.players:\n\n                # throw an extra fudge factor +1 in so teams that\n                # didn't die come out ahead of teams that did\n                if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime+1\n                    \n                # award a per-player score depending on how many seconds they lasted\n                # (per-player scores only affect teams mode; everywhere else just looks at the per-team score)\n                score = (player.gameData['deathTime']-self._timer.getStartTime())/1000\n                if 'deathTime' not in player.gameData: score += 50 # a bit extra for survivors\n                self.scoreSet.playerScored(player,score,screenMessage=False)\n\n        # stop updating our time text, and set the final time to match\n        # exactly when our last guy died.\n        self._timer.stop(endTime=self._lastPlayerDeathTime)\n        \n        # ok now calc game results: set a score for each team and then tell the game to end\n        results = bs.TeamGameResults()\n\n        # remember that 'free-for-all' mode is simply a special form of 'teams' mode\n        # where each player gets their own team, so we can just always deal in teams\n        # and have all cases covered\n        for team in self.teams:\n\n            # set the team score to the max time survived by any player on that team\n            longestLife = 0\n            for player in team.players:\n                longestLife = max(longestLife,(player.gameData['deathTime'] - self._timer.getStartTime()))\n            results.setTeamScore(team,longestLife)\n\n        self.end(results=results)\n"
  },
  {
    "path": "mods/bsBoxingOfTheHill.json",
    "content": "{\n  \"name\": \"Boxing Of The Hill\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/bsBoxingOfTheHill.py",
    "content": "import bs\nimport weakref\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [BoxingOfTheHillGame]\n\nclass BoxingOfTheHillGame(bs.TeamGameActivity):\n\n    FLAG_NEW = 0\n    FLAG_UNCONTESTED = 1\n    FLAG_CONTESTED = 2\n    FLAG_HELD = 3\n\n    @classmethod\n    def getName(cls):\n        return 'Boxing of the Hill'\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Secure the flag for a set length of time. Gloves only!'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Time Held'}\n    \n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"kingOfTheHill\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        return [(\"Hold Time\",{'minValue':10,'default':30,'increment':10}),\n                (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                        ('2 Minutes',120),('5 Minutes',300),\n                                        ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0})]\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        self._scoreBoard = bs.ScoreBoard()\n        self._swipSound = bs.getSound(\"swip\")\n        self._tickSound = bs.getSound('tick')\n        self._countDownSounds = {10:bs.getSound('announceTen'),\n                                 9:bs.getSound('announceNine'),\n                                 8:bs.getSound('announceEight'),\n                                 7:bs.getSound('announceSeven'),\n                                 6:bs.getSound('announceSix'),\n                                 5:bs.getSound('announceFive'),\n                                 4:bs.getSound('announceFour'),\n                                 3:bs.getSound('announceThree'),\n                                 2:bs.getSound('announceTwo'),\n                                 1:bs.getSound('announceOne')}\n\n        self._flagRegionMaterial = bs.Material()\n        self._flagRegionMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('playerMaterial')),\n                                            actions=((\"modifyPartCollision\",\"collide\",True),\n                                                     (\"modifyPartCollision\",\"physical\",False),\n                                                     (\"call\",\"atConnect\",bs.Call(self._handlePlayerFlagRegionCollide,1)),\n                                                     (\"call\",\"atDisconnect\",bs.Call(self._handlePlayerFlagRegionCollide,0))))\n\n    def getInstanceDescription(self):\n        return ('Secure the flag for ${ARG1} seconds.',self.settings['Hold Time'])\n\n    def getInstanceScoreBoardDescription(self):\n        return ('secure the flag for ${ARG1} seconds',self.settings['Hold Time'])\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Scary')\n\n    def onTeamJoin(self,team):\n        team.gameData['timeRemaining'] = self.settings[\"Hold Time\"]\n        self._updateScoreBoard()\n\n    def onPlayerJoin(self,player):\n        bs.TeamGameActivity.onPlayerJoin(self,player)\n        player.gameData['atFlag'] = 0\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        # self.setupStandardPowerupDrops() #no powerups due to boxing\n        self._flagPos = self.getMap().getFlagPosition(None)\n        bs.gameTimer(1000,self._tick,repeat=True)\n        self._flagState = self.FLAG_NEW\n        self.projectFlagStand(self._flagPos)\n\n        self._flag = bs.Flag(position=self._flagPos,\n                             touchable=False,\n                             color=(1,1,1))\n        self._flagLight = bs.newNode('light',\n                                     attrs={'position':self._flagPos,\n                                            'intensity':0.2,\n                                            'heightAttenuated':False,\n                                            'radius':0.4,\n                                            'color':(0.2,0.2,0.2)})\n\n        # flag region\n        bs.newNode('region',\n                   attrs={'position':self._flagPos,\n                          'scale': (1.8,1.8,1.8),\n                          'type': 'sphere',\n                          'materials':[self._flagRegionMaterial,bs.getSharedObject('regionMaterial')]})\n        self._updateFlagState()\n\n    def spawnPlayer(self,player):\n\n        spaz = self.spawnPlayerSpaz(player)\n        spaz.connectControlsToPlayer(enablePunch=True,\n                                     enableBomb=False,\n                                     enablePickUp=True)\n\n        spaz.equipBoxingGloves()\n\n    \n\n    def _tick(self):\n        self._updateFlagState()\n\n        # give holding players points\n        for player in self.players:\n            if player.gameData['atFlag'] > 0:\n                self.scoreSet.playerScored(player,3,screenMessage=False,display=False)\n\n        scoringTeam = None if self._scoringTeam is None else self._scoringTeam()\n        if scoringTeam:\n\n            if scoringTeam.gameData['timeRemaining'] > 0: bs.playSound(self._tickSound)\n\n            scoringTeam.gameData['timeRemaining'] = max(0,scoringTeam.gameData['timeRemaining']-1)\n            self._updateScoreBoard()\n            if scoringTeam.gameData['timeRemaining'] > 0:\n                self._flag.setScoreText(str(scoringTeam.gameData['timeRemaining']))\n\n            # announce numbers we have sounds for\n            try: bs.playSound(self._countDownSounds[scoringTeam.gameData['timeRemaining']])\n            except Exception: pass\n\n            # winner\n            if scoringTeam.gameData['timeRemaining'] <= 0:\n                self.endGame()\n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for team in self.teams: results.setTeamScore(team,self.settings['Hold Time'] - team.gameData['timeRemaining'])\n        self.end(results=results,announceDelay=0)\n        \n    def _updateFlagState(self):\n        holdingTeams = set(player.getTeam() for player in self.players if player.gameData['atFlag'])\n        prevState = self._flagState\n        if len(holdingTeams) > 1:\n            self._flagState = self.FLAG_CONTESTED\n            self._scoringTeam = None\n            self._flagLight.color = (0.6,0.6,0.1)\n            self._flag.node.color = (1.0,1.0,0.4)\n        elif len(holdingTeams) == 1:\n            holdingTeam = list(holdingTeams)[0]\n            self._flagState = self.FLAG_HELD\n            self._scoringTeam = weakref.ref(holdingTeam)\n            self._flagLight.color = bs.getNormalizedColor(holdingTeam.color)\n            self._flag.node.color = holdingTeam.color\n        else:\n            self._flagState = self.FLAG_UNCONTESTED\n            self._scoringTeam = None\n            self._flagLight.color = (0.2,0.2,0.2)\n            self._flag.node.color = (1,1,1)\n        if self._flagState != prevState:\n            bs.playSound(self._swipSound)\n\n    def _handlePlayerFlagRegionCollide(self,colliding):\n        flagNode,playerNode = bs.getCollisionInfo(\"sourceNode\",\"opposingNode\")\n        try: player = playerNode.getDelegate().getPlayer()\n        except Exception: return\n\n        # different parts of us can collide so a single value isn't enough\n        # also don't count it if we're dead (flying heads shouldnt be able to win the game :-)\n        if colliding and player.isAlive(): player.gameData['atFlag'] += 1\n        else: player.gameData['atFlag'] = max(0,player.gameData['atFlag'] - 1)\n\n        self._updateFlagState()\n\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team,team.gameData['timeRemaining'],self.settings['Hold Time'],countdown=True)\n\n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m) # augment default\n            \n            # no longer can count as atFlag once dead\n            player = m.spaz.getPlayer()\n            player.gameData['atFlag'] = 0\n            self._updateFlagState()\n            self.respawnPlayer(player)"
  },
  {
    "path": "mods/bsKillZone.json",
    "content": "{\n  \"name\": \"Kill Zone\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/bsKillZone.py",
    "content": "import bs\nimport random\nimport math\n\ndef bsGetAPIVersion():\n    return 4\n\ndef bsGetGames():\n    return [KillZoneGame]\n\ndef bsGetLevels():\n    return [bs.Level('Kill Zone',displayName='${GAME}',gameType=KillZoneGame,settings={},previewTexName='doomShroomPreview')]\n\n\nclass KillZoneGame(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Kill Zone - Kill no-shirts on targets'\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Get points for killing enemies within the targets.'\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Doom Shroom']\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        # we support teams, co-op, and free-for-all\n        return True if (issubclass(sessionType,bs.CoopSession)\n                        or issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n    \n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n\n        self._scoreBoard = bs.ScoreBoard()\n        \n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='ForwardMarch')\n\n    def onTeamJoin(self,team):\n        team.gameData['score'] = 0\n        if self.hasBegun(): self._updateScoreBoard()\n        \n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self._updateScoreBoard()\n\n        self._targets = []\n\n        # number of targets is based on player count\n        numTargets = min(5,len(self.initialPlayerInfo)+2)\n        for i in range(numTargets):\n            bs.gameTimer(5000+i*1000,self._spawnTarget)\n        \n        \n        # this wrangles our bots\n        self._bots = bs.BotSet()\n\n        # start some timers to spawn bots\n        bs.gameTimer(1000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=(3,3,-2),spawnTime=3000))\n        #bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=(-3,3,-2),spawnTime=3000))\n        #bs.gameTimer(3000,bs.Call(self._bots.spawnBot,bs.NinjaBot,pos=(5,3,-2),spawnTime=3000))\n        #bs.gameTimer(4000,bs.Call(self._bots.spawnBot,bs.NinjaBot,pos=(-5,3,-2),spawnTime=3000))\n\n        # add a few extras for multiplayer\n        if len(self.initialPlayerInfo) > 2:\n            bs.gameTimer(5000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=(0,3,-5),spawnTime=3000))\n        if len(self.initialPlayerInfo) > 3:\n            bs.gameTimer(6000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=(0,3,1),spawnTime=3000))\n\n        # note: if spawns were spread out more we'd probably want to set some sort of flag on the\n        # last spawn to ensure we don't inadvertantly allow a 'win' before every bot is spawned.\n        # (ie: if bot 1, 2, and 3 got killed but 4 hadn't spawned yet, the game might end because\n        # it sees no remaining bots.\n        self._updateTimer = bs.Timer(1000,self._update,repeat=True)\n\n        self._countdown = bs.OnScreenCountdown(150,endCall=self.endGame)\n        bs.gameTimer(4000,self._countdown.start)\n        \n    def spawnPlayer(self,player):\n        spawnCenter = (0,3,-5)\n        pos = (spawnCenter[0]+random.uniform(-1.5,1.5),spawnCenter[1],spawnCenter[2]+random.uniform(-1.5,1.5))\n\n        # reset their streak\n        player.gameData['streak'] = 0\n        \n        spaz = self.spawnPlayerSpaz(player,position=pos)\n        spaz.equipBoxingGloves()\n        spaz.connectControlsToPlayer(enablePunch=True,\n                                     enableBomb=False,\n                                     enablePickUp=True)\n        \n        # give players permanent triple impact bombs and wire them up\n        # to tell us when they drop a bomb\n        spaz.bombType = 'impact'\n        spaz.setBombCount(3)\n        spaz.addDroppedBombCallback(self._onSpazDroppedBomb)\n\n    def _spawnTarget(self):\n\n        # gen a few random points; we'll use whichever one is farthest from\n        # our existing targets. (dont want overlapping targets)\n        points = []\n        \n        for i in range(4):\n            # calc a random point within a circle\n            while True:\n                x = random.uniform(-1.0,1.0)\n                y = random.uniform(-1.0,1.0)\n                if x*x+y*y < 1.0: break\n            points.append((8.0*x,2.2,-3.5+5.0*y))\n            \n        def getMinDistFromTarget(point):\n            return min((t.getDistFromPoint(point) for t in self._targets))\n\n        # if we have existing targets, use the point with the highest min-distance-from-targets\n        if self._targets: point = max(points,key=getMinDistFromTarget)\n        else: point = points[0]\n        \n        self._targets.append(Target(position=point))\n        \n    def _onSpazDroppedBomb(self,spaz,bomb):\n        # wire up this bomb to inform us when it blows up\n        pass\n        #bomb.addExplodeCallback(self._onBombExploded)#Commented out to prevent bomb wiring. Get info from spazbot death message instead\n        \n    def _onSpazBotDied(self,DeathMsg):\n        x = random.uniform(-1.0,1.0)\n        y = random.uniform(-1.0,1.0)\n        self._bots.spawnBot(bs.ToughGuyBot,pos=(8.0*x,3,5.0*y),spawnTime=1000)\n        pos = DeathMsg.badGuy.node.position\n\n        # debugging: throw a locator down where we landed..\n        #bs.newNode('locator',attrs={'position':blast.node.position})\n\n        # feed the explosion point to all our targets and get points in return..\n        # note: we operate on a copy of self._targets since the list may change\n        # under us if we hit stuff (dont wanna get points for new targets)\n        print(DeathMsg.how)\n        if DeathMsg.killerPlayer is None:\n            #print(\"No killer\")\n            pass\n        else:\n            player = DeathMsg.killerPlayer\n            #print(player)\n            if not player.exists(): return # could happen if they leave after throwing a bomb..\n            #print(\"got here\")\n            bullsEye = any(target.doHitAtPosition(pos,player) for target in list(self._targets))\n\n            if bullsEye: player.gameData['streak'] += 1\n            else: player.gameData['streak'] = 0\n        \n    def _update(self):\n\n        # misc. periodic updating..\n        \n        # clear out targets that have died\n        self._targets = [t for t in self._targets if t.exists()]\n        \n    def handleMessage(self,m):\n        \n        # when players die, respawn them\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m) # do standard stuff\n            self.respawnPlayer(m.spaz.getPlayer()) # kick off a respawn\n        elif isinstance(m,Target.TargetHitMessage):\n            # a target is telling us it was hit and will die soon..\n            # ..so make another one.\n            self._spawnTarget()\n        elif isinstance(m,bs.SpazBotDeathMessage):\n            self._onSpazBotDied(m)\n            bs.TeamGameActivity.handleMessage(self,m)\n            #bs.PopupText(\"died\",position=self._position,color=popupColor,scale=popupScale).autoRetain()\n        else:\n            bs.TeamGameActivity.handleMessage(self,m)\n            \n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team,team.gameData['score'])\n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for team in self.teams:\n            results.setTeamScore(team,team.gameData['score'])\n        self.end(results)\n        \nclass Target (bs.Actor):\n\n    class TargetHitMessage(object):\n        pass\n    \n    def __init__(self,position):\n        self._r1 = 0.45\n        self._r2 = 1.1\n        self._r3 = 2.0\n        self._rFudge = 0.15\n        bs.Actor.__init__(self)\n        self._position = bs.Vector(*position)\n        self._hit = False\n        showInSpace = False # it can be handy to test with this on to make sure the projection isn't too far off from the actual object..\n        n1 = bs.newNode('locator',attrs={'shape':'circle','position':position,'color':(0,1,0),'opacity':0.5,'drawBeauty':showInSpace,'additive':True})\n        n2 = bs.newNode('locator',attrs={'shape':'circleOutline','position':position,'color':(0,1,0),'opacity':0.3,'drawBeauty':False,'additive':True})\n        n3 = bs.newNode('locator',attrs={'shape':'circleOutline','position':position,'color':(0,1,0),'opacity':0.1,'drawBeauty':False,'additive':True})\n        self._nodes = [n1,n2,n3]\n        bs.animateArray(n1,'size',1,{0:[0.0],200:[self._r1*2.0]})\n        bs.animateArray(n2,'size',1,{50:[0.0],250:[self._r2*2.0]})\n        bs.animateArray(n3,'size',1,{100:[0.0],300:[self._r3*2.0]})\n        bs.playSound(bs.getSound('laserReverse'))\n\n    def exists(self):\n        return True if self._nodes else False\n    \n    def handleMessage(self,m):\n        if isinstance(m,bs.DieMessage):\n            for node in self._nodes: node.delete()\n            self._nodes = []\n        else:\n            bs.Actor.handleMessage(self,m)\n\n    def getDistFromPoint(self,pos):\n        'Given a point, returns distance squared from it'\n        return (bs.Vector(*pos)-self._position).length()\n        \n    def doHitAtPosition(self,pos,player):\n        activity = self.getActivity()\n        #print(\"Hit target?\")\n\n        # ignore hits if the game is over or if we've already been hit\n        if activity.hasEnded() or self._hit or not self._nodes: return 0\n\n        diff = (bs.Vector(*pos)-self._position)\n        diff[1] = 0.0 # disregard y difference (our target point probably isnt exactly on the ground anyway)\n        dist = diff.length()\n\n        bullsEye = False\n        points = 0\n        if dist <= self._r3+self._rFudge:\n            # inform our activity that we were hit\n            self._hit = True\n            self.getActivity().handleMessage(self.TargetHitMessage())\n            keys = {0:(1,0,0),49:(1,0,0),50:(1,1,1),100:(0,1,0)}\n            cDull = (0.3,0.3,0.3)\n            if dist <= self._r1+self._rFudge:\n                bullsEye = True\n                self._nodes[1].color = cDull\n                self._nodes[2].color = cDull\n                bs.animateArray(self._nodes[0],'color',3,keys,loop=True)\n                popupScale = 1.8\n                popupColor = (1,1,0,1)\n                streak = player.gameData['streak']\n                points = 10 + min(20,streak * 2)\n                bs.playSound(bs.getSound('bellHigh'))\n                if streak > 0:\n                    bs.playSound(bs.getSound('orchestraHit4' if streak > 3\n                                             else 'orchestraHit3' if streak > 2\n                                             else 'orchestraHit2' if streak > 1\n                                             else 'orchestraHit'))\n            elif dist <= self._r2+self._rFudge:\n                self._nodes[0].color = cDull\n                self._nodes[2].color = cDull\n                bs.animateArray(self._nodes[1],'color',3,keys,loop=True)\n                popupScale = 1.25\n                popupColor = (1,0.5,0.2,1)\n                points = 4\n                bs.playSound(bs.getSound('bellMed'))\n            else:\n                self._nodes[0].color = cDull\n                self._nodes[1].color = cDull\n                bs.animateArray(self._nodes[2],'color',3,keys,loop=True)\n                popupScale= 1.0\n                popupColor = (0.8,0.3,0.3,1)\n                points = 2\n                bs.playSound(bs.getSound('bellLow'))\n\n            # award points/etc.. (technically should probably leave this up to the activity)\n            popupStr = \"+\"+str(points)\n            \n            # if there's more than 1 player in the game, include their names and colors\n            # so they know who got the hit\n            if len(activity.players) > 1:\n                popupColor = bs.getSafeColor(player.color,targetIntensity=0.75)\n                popupStr += ' '+player.getName()\n            bs.PopupText(popupStr,position=self._position,color=popupColor,scale=popupScale).autoRetain()\n\n            # give this player's team points and update the score-board\n            player.getTeam().gameData['score'] += points\n            activity._updateScoreBoard()\n\n            # also give this individual player points (only applies in teams mode)\n            activity.scoreSet.playerScored(player,points,showPoints=False,screenMessage=False)\n                \n            bs.animateArray(self._nodes[0],'size',1,{800:self._nodes[0].size,1000:[0.0]})\n            bs.animateArray(self._nodes[1],'size',1,{850:self._nodes[1].size,1050:[0.0]})\n            bs.animateArray(self._nodes[2],'size',1,{900:self._nodes[2].size,1100:[0.0]})\n            bs.gameTimer(1100,bs.Call(self.handleMessage,bs.DieMessage()))\n            \n        return bullsEye\n"
  },
  {
    "path": "mods/catch_to_live.json",
    "content": "{\n  \"name\": \"CatchToLive\",\n  \"author\": \"Deva\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/catch_to_live.py",
    "content": "# coding=utf-8\n# coding=utf8\nimport bs\nimport bsUtils\nimport random\n\n\nclass CheckNeedNewMadMessage(object):\n    def __init__(self, spaz=None):\n        self.spaz = spaz\n\n\nclass ClearProtectMessage(object):\n    def __init__(self):\n        pass\n\n\nclass grimPlayer(bs.PlayerSpaz):\n    def __init__(self, color, highlight, character, player, gameProtectionTime=3, hitPoints=5):\n        bs.PlayerSpaz.__init__(self, color=color, highlight=highlight, character=character, player=player)\n        self._inmad = False  # 默认不是处于疯狂状态\n        self._madProtect = False  # 默认处于无保护状态\n        self.hitPoints = hitPoints * 1000\n        self.hitPointsMax = self.hitPoints\n        self.gameProtectionTime = gameProtectionTime\n        self._startMadTime = None\n        self._allMadTime = None\n        self._startProtectTime = None\n\n        self.normalColor = color\n        self.madColor = (1, 0, 0)\n        self.protectColor = (0, 0, 1)\n\n    def handleMessage(self, m):\n        if isinstance(m, bs.PickedUpMessage):\n            if not self.getPlayer().isAlive():\n                return\n            oppoSpaz = m.node.getDelegate()\n            if not oppoSpaz.getPlayer().isAlive():\n                return\n\n            # 让对方放手\n            oppoSpaz.onPickUpRelease()\n            oppoSpaz.onPickUpPress()\n            oppoSpaz.onPickUpRelease()\n            if self._madProtect:\n                bs.PlayerSpaz.handleMessage(self, m)\n                return\n            if oppoSpaz._inmad:\n                oppoSpaz.stopMad()\n                oppoSpaz.protectAdd()\n                leftTime = (oppoSpaz._allMadTime - bs.getGameTime() + oppoSpaz._startMadTime)\n                self.onMad(leftTime)\n\n            bs.PlayerSpaz.handleMessage(self, m)\n        elif isinstance(m, bs.DieMessage):\n            self._inmad = False\n            if not self._dead and not m.immediate:\n                self._activity().handleMessage(CheckNeedNewMadMessage(self))\n            bs.PlayerSpaz.handleMessage(self, m)\n        else:\n            bs.PlayerSpaz.handleMessage(self, m)\n\n    def protectAdd(self):\n        # self.setScoreText(str(self.gameProtectionTime) + 's Crazy Protection')\n        self.setScoreText('Anti-Crazy')\n        self.node.color = self.protectColor\n        self._madProtect = True\n        self._startProtectTime = bs.getGameTime()\n        bs.gameTimer(self.gameProtectionTime * 1000, bs.Call(self.protectClear, self._startProtectTime))\n\n        # add hockey\n        self.node.hockey = True\n\n    def protectClear(self, checkProtectStartTime):\n        if self._madProtect and self._startProtectTime == checkProtectStartTime:\n            self._madProtect = False\n            if self._inmad:\n                # 躲不了系统给的MAD\n                return\n            self.node.color = self.normalColor\n\n            # add hockey\n            self.node.hockey = False\n\n    def onMad(self, madTime=10000):\n        # 10秒后炸掉\n        if self._inmad:\n            return\n        self._inmad = True\n        self.getPlayer().assignInputCall('pickUpPress', self.onPickUpPress)\n        self.getPlayer().assignInputCall('pickUpRelease', self.onPickUpRelease)\n        self.node.hockey = True\n        self.node.color = self.madColor\n        self._startMadTime = bs.getGameTime()\n        self._allMadTime = madTime\n        bs.gameTimer(madTime, bs.WeakCall(self.madExplode, self._startMadTime))\n\n    def stopMad(self):\n        self._inmad = False\n        self.getPlayer().assignInputCall('pickUpPress', lambda: None)\n        self.getPlayer().assignInputCall('pickUpRelease', lambda: None)\n        self.node.hockey = False\n        self.node.color = self.normalColor\n\n    def madExplode(self, checkStartTime):\n        if self._inmad and self._startMadTime == checkStartTime:\n            self.shatter(extreme=True)\n            self.handleMessage(bs.DieMessage())\n\n\ndef bsGetAPIVersion():\n    return 4\n\n\ndef bsGetGames():\n    return [CatchToLiveGame]\n\n\nclass CatchToLiveGame(bs.TeamGameActivity):\n    @classmethod\n    def getName(cls):\n        return 'Catch To Live'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName': 'Survived',\n                'scoreType': 'milliseconds',\n                'scoreVersion': 'B'}\n\n    @classmethod\n    def getDescription(cls, sessionType):\n        return 'If you\\'re CRAZY and don\\'t wanna die\\nThen PICKUP others!'\n\n    @classmethod\n    def getSupportedMaps(cls, sessionType):\n        # return ['Rampage']\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls, sessionType):\n        return [(\"Mad Time To Die (Approximate)\", {'minValue': 5, 'default': 10, 'increment': 1}),\n                (\"Protection Time After Catching\", {'minValue': 1, 'default': 3, 'increment': 1}),\n                (\"Player HP\", {\n                    'choices': [('normal', 1), ('2 times', 2), ('3 times', 3), ('5 times', 5), ('7 times', 7),\n                                ('10 times', 10)], 'default': 5}),\n                (\"Epic Mode\", {'default': False}),\n                (\"Allow Landmine\", {'default': True})]\n\n    # we support teams, free-for-all, and co-op sessions\n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if (issubclass(sessionType, bs.FreeForAllSession)) else False\n\n    def __init__(self, settings):\n        bs.TeamGameActivity.__init__(self, settings)\n\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # print messages when players die (since its meaningful in this game)\n        self.announcePlayerDeaths = True\n\n        self._lastPlayerDeathTime = None\n\n    # called when our game is transitioning in but not ready to start..\n    # ..we can go ahead and set our music and whatnot\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n\n    # called when our game actually starts\n    def onBegin(self):\n        # self.playerList = []\n        bs.TeamGameActivity.onBegin(self)\n        self._madTime = self.settings['Mad Time To Die (Approximate)'] * 1000\n\n        # bs.gameTimer(t,self._decrementMeteorTime,repeat=True)\n\n        # kick off the first wave in a few seconds\n        t = 3000\n        if self.settings['Epic Mode']: t /= 4\n        # bs.gameTimer(t,self._setMeteorTimer)\n\n        self._timer = bs.OnScreenTimer()\n        self._timer.start()\n\n        bs.gameTimer(10, bs.WeakCall(self.updateSpazText), repeat=True)\n\n        bs.gameTimer(t, bs.WeakCall(self.handleMessage, CheckNeedNewMadMessage()), repeat=False)\n        bs.gameTimer(1000, self._checkNeedMad, repeat=True)\n\n        # bs.gameTimer(5000, self._checkEndGame)  # 4秒之后检测一波\n\n    def updateSpazText(self):\n        for team in self.teams:\n            for player in team.players:\n                try:\n                    if player.actor._inmad:\n                        leftTime = (player.actor._allMadTime - bs.getGameTime() + player.actor._startMadTime) / 1000.0\n                        if leftTime > 0.0:\n                            player.actor.setScoreText('Crazy')\n                            # player.actor.setScoreText('%.2f' % (leftTime), color=(1, 1, 1))\n                        else:\n                            player.actor.setScoreText('')\n                    elif player.actor._madProtect:\n                        player.actor.setScoreText('Anti-Crazy')\n                    else:\n                        player.actor.setScoreText('')\n                except:\n                    pass\n\n    # overriding the default character spawning..\n    def spawnPlayer(self, player):\n\n        position = self.getMap().getFFAStartPosition(self.players)\n        angle = 20\n        name = player.getName()\n\n        lightColor = bsUtils.getNormalizedColor(player.color)\n        displayColor = bs.getSafeColor(player.color, targetIntensity=0.75)\n\n        spaz = grimPlayer(color=player.color,\n                          highlight=player.highlight,\n                          character=player.character,\n                          player=player,\n                          gameProtectionTime=self.settings['Protection Time After Catching'],\n                          hitPoints=self.settings['Player HP'])\n        player.setActor(spaz)\n        # For some reason, I can't figure out how to get a list of all spaz.\n        # Therefore, I am making the list here so I can get which spaz belongs\n        # to the player supplied by HitMessage.\n        # self.playerList.append(spaz)\n\n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer()\n        self.scoreSet.playerGotNewSpaz(player, spaz)\n\n        # add landmine\n        spaz.bombTypeDefault = 'landMine'  # random.choice(['ice', 'impact', 'landMine', 'normal', 'sticky', 'tnt'])\n        spaz.bombType = spaz.bombTypeDefault\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound, 1, position=spaz.node.position)\n        light = bs.newNode('light', attrs={'color': lightColor})\n        spaz.node.connectAttr('position', light, 'position')\n        bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0})\n        bs.gameTimer(500, light.delete)\n\n        # lets reconnect this player's controls to this\n        # spaz but *without* the ability to attack or pick stuff up\n        spaz.connectControlsToPlayer(enablePunch=False,\n                                     enableBomb=self.settings['Allow Landmine'],\n                                     enablePickUp=False)\n        # player.assignInputCall('pickUpPress', lambda: None)\n        # player.assignInputCall('pickUpRelease', lambda: None)\n        # also lets have them make some noise when they die..\n        spaz.playBigDeathSound = True\n\n        return spaz\n\n    def onPlayerJoin(self, player):\n        # don't allow joining after we start\n        # (would enable leave/rejoin tomfoolery)\n        if self.hasBegun():\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText', subs=[('${PLAYER}', player.getName(full=True))]),\n                             color=(0, 1, 0))\n            # for score purposes, mark them as having died right as the game started\n            player.gameData['noScore'] = True\n            return\n        self.spawnPlayer(player)\n\n    def onPlayerLeave(self, player):\n        # augment default behavior...\n        bs.TeamGameActivity.onPlayerLeave(self, player)\n        # a departing player may trigger game-over\n        bs.gameTimer(100, bs.Call(self._checkEndGame))\n\n    # various high-level game events come through this method\n    def handleMessage(self, m):\n\n        if isinstance(m, bs.PlayerSpazDeathMessage):\n\n            bs.TeamGameActivity.handleMessage(self, m)  # (augment standard behavior)\n\n            deathTime = bs.getGameTime()\n\n            # record the player's moment of death\n            m.spaz.getPlayer().gameData['deathTime'] = deathTime\n\n            # in co-op mode, end the game the instant everyone dies (more accurate looking)\n            # in teams/ffa, allow a one-second fudge-factor so we can get more draws\n            if isinstance(self.getSession(), bs.CoopSession):\n                # teams will still show up if we check now.. check in the next cycle\n                bs.pushCall(self._checkEndGame)\n                self._lastPlayerDeathTime = deathTime  # also record this for a final setting of the clock..\n            else:\n                bs.gameTimer(1000, self._checkEndGame)\n\n        elif isinstance(m, CheckNeedNewMadMessage):\n            self._checkNeedMad()\n        else:\n            # default handler:\n            bs.TeamGameActivity.handleMessage(self, m)\n\n    def _checkNeedMad(self):\n        # print('check if we need a new mad')\n        alivePlayers = []\n        for team in self.teams:\n            for player in team.players:\n                if player.isAlive():\n                    alivePlayers.append(player)\n                    if player.actor._inmad:\n                        # print('no need for new mad')\n                        return\n\n        if len(alivePlayers) == 0:\n            return\n\n        selectedPlayer = random.choice(alivePlayers)\n        selectedPlayer.actor.onMad(random.randint(self._madTime - 2500, self._madTime + 2500))\n\n    def _checkEndGame(self):\n        livingTeamCount = 0\n        for team in self.teams:\n            for player in team.players:\n                if player.isAlive():\n                    livingTeamCount += 1\n                    break\n\n        # in co-op, we go till everyone is dead.. otherwise we go until one team remains\n        if isinstance(self.getSession(), bs.CoopSession):\n            if livingTeamCount <= 0: self.endGame()\n        else:\n            if livingTeamCount <= 1: self.endGame()\n\n    def endGame(self):\n\n        curTime = bs.getGameTime()\n\n        # mark 'death-time' as now for any still-living players\n        # and award players points for how long they lasted.\n        # (these per-player scores are only meaningful in team-games)\n        for team in self.teams:\n            for player in team.players:\n\n                # throw an extra fudge factor +1 in so teams that\n                # didn't die come out ahead of teams that did\n                if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime + 1\n                if 'noScore' in player.gameData: player.gameData['deathTime'] = self._timer.getStartTime()\n\n                # award a per-player score depending on how many seconds they lasted\n                # (per-player scores only affect teams mode; everywhere else just looks at the per-team score)\n                score = (player.gameData['deathTime'] - self._timer.getStartTime()) / 1000\n                if 'deathTime' not in player.gameData: score += 50  # a bit extra for survivors\n                self.scoreSet.playerScored(player, score, screenMessage=False)\n\n        # stop updating our time text, and set the final time to match\n        # exactly when our last guy died.\n        self._timer.stop(endTime=self._lastPlayerDeathTime)\n\n        # ok now calc game results: set a score for each team and then tell the game to end\n        results = bs.TeamGameResults()\n\n        # remember that 'free-for-all' mode is simply a special form of 'teams' mode\n        # where each player gets their own team, so we can just always deal in teams\n        # and have all cases covered\n        for team in self.teams:\n\n            # set the team score to the max time survived by any player on that team\n            longestLife = 0\n            for player in team.players:\n                longestLife = max(longestLife, (player.gameData['deathTime'] - self._timer.getStartTime()))\n            results.setTeamScore(team, longestLife)\n\n        self.end(results=results)\n"
  },
  {
    "path": "mods/fightOfFaith.json",
    "content": "{\n  \"name\": \"Fight of Faith\",\n  \"author\": \"SoKpl\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/fightOfFaith.py",
    "content": "import bs\nimport random\n\ndef bsGetAPIVersion():\n    # return the api-version this script expects.\n    # this prevents it from attempting to run in newer versions of the game\n    # where changes have been made to the modding APIs\n    return 4\n\ndef bsGetGames():\n    return [FightOfFaithGame]\n\ndef bsGetLevels():\n    # Levels are unique named instances of a particular game with particular settings.\n    # They show up as buttons in the co-op section, get high-score lists associated with them, etc.\n    return [bs.Level('Fight of Faith', # globally-unique name for this level (not seen by user)\n            displayName='${GAME}', # ${GAME} will be replaced by the results of the game's getName() call\n            gameType=FightOfFaithGame,\n            settings={}, # we currently dont have any settings; we'd specify them here if we did.\n            previewTexName='courtyardPreview')]\n\nclass FightOfFaithGame(bs.TeamGameActivity):\n    # name seen by the user\n    @classmethod\n    def getName(cls):\n        return 'Fight of Faith'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreType':'milliseconds',\n                'lowerIsBetter':True,\n                'scoreName':'Time'}\n\n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'How quickly you kill THEM?'\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        # for now we're hard-coding spawn positions and whatnot\n        # so we need to be sure to specity that we only support\n        # a specific map..\n        return ['Courtyard']\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        # we currently support Co-Op only\n        return True if issubclass(sessionType,bs.CoopSession) else False\n\n    # in the constructor we should load any media we need/etc.\n    # but not actually create anything yet.\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        self._winSound = bs.getSound(\"score\")\n\n    # called when our game is transitioning in but not ready to start..\n    # ..we can go ahead and start creating stuff, playing music, etc.\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='ToTheDeath')\n\n    # called when our game actually starts\n    def onBegin(self):\n\n        bs.TeamGameActivity.onBegin(self)\n\n        self._won = False\n\n        self.setupStandardPowerupDrops()\n\n        # make our on-screen timer and start it roughly when our bots appear\n        self._timer = bs.OnScreenTimer()\n        bs.gameTimer(4000,self._timer.start)\n\n        # this wrangles our bots\n        self._bots = bs.BotSet()\n\n        # start some timers to spawn bots\n        bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.MelBot,pos=(3,3,-2),spawnTime=3000))\n        bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.ChickBot,pos=(-3,3,-2),spawnTime=3000))\n        bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.ToughGuyBotPro,pos=(5,3,-2),spawnTime=3000))\n        bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.BomberBotPro,pos=(-5,3,-2),spawnTime=3000))\n        bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.BomberBot,pos=(0,3,-5),spawnTime=3000))\n        bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.PirateBotNoTimeLimit,pos=(0,3,1),spawnTime=10000))\n\n        # note: if spawns were spread out more we'd probably want to set some sort of flag on the\n        # last spawn to ensure we don't inadvertantly allow a 'win' before every bot is spawned.\n        # (ie: if bot 1, 2, and 3 got killed but 4 hadn't spawned yet, the game might end because\n        # it sees no remaining bots.\n\n    # called for each spawning player\n    def spawnPlayer(self,player):\n\n        # lets spawn close to the center\n        spawnCenter = (0,3,-2)\n        pos = (spawnCenter[0]+random.uniform(-1.5,1.5),spawnCenter[1],spawnCenter[2]+random.uniform(-1.5,1.5))\n\n        self.spawnPlayerSpaz(player,position=pos)\n\n    def _checkIfWon(self):\n        # simply end the game if there's no living bots..\n        if not self._bots.haveLivingBots():\n            self._won = True\n            self.endGame()\n\n    # called for miscellaneous events\n    def handleMessage(self,m):\n\n        # a player has died\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self,m) # do standard stuff\n            self.respawnPlayer(m.spaz.getPlayer()) # kick off a respawn\n\n        # a spaz-bot has died\n        elif isinstance(m,bs.SpazBotDeathMessage):\n            # unfortunately the bot-set will always tell us there are living\n            # bots if we ask here (the currently-dying bot isn't officially marked dead yet)\n            # ..so lets push a call into the event loop to check once this guy has finished dying.\n            bs.pushCall(self._checkIfWon)\n\n        else:\n            # let the base class handle anything we don't..\n            bs.TeamGameActivity.handleMessage(self,m)\n\n    # when this is called, we should fill out results and end the game\n    # *regardless* of whether is has been won. (this may be called due\n    # to a tournament ending or other external reason)\n    def endGame(self):\n\n        # stop our on-screen timer so players can see what they got\n        self._timer.stop()\n\n        results = bs.TeamGameResults()\n\n        # if we won, set our score to the elapsed time\n        # (there should just be 1 team here since this is co-op)\n        # ..if we didn't win, leave scores as default (None) which means we lost\n        if self._won:\n            elapsedTime = bs.getGameTime()-self._timer.getStartTime()\n            self.cameraFlash()\n            bs.playSound(self._winSound)\n            for team in self.teams:\n                team.celebrate() # woooo! par-tay!\n                results.setTeamScore(team,elapsedTime)\n\n        # ends this activity..\n        self.end(results)\n"
  },
  {
    "path": "mods/frozenone.json",
    "content": "{\n  \"name\": \"The Frozen One\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/frozenone.py",
    "content": "import bs\nfrom bsChosenOne import ChosenOneGame\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [FrozenOneGame]\n\nclass FrozenOneGame(ChosenOneGame):\n\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Frozen One'\n\n\t@classmethod\n\tdef getDescription(cls,sessionType):\n\t\treturn ('Be the Frozen one for a length of time to win.\\n'\n\t\t\t\t'Kill the Frozen one to become it.')\n\n\t@classmethod\n\tdef getSettings(cls, sessionType):\n\t\treturn [('Frozen One Time', {'default': 30, 'increment': 10, 'minValue': 10}),\n\t\t\t\t('Frozen One Gets Gloves', {'default': True}),\n\t\t\t\t('Time Limit',\n\t\t\t\t {'choices': [('None', 0),\n\t\t\t\t\t\t\t  ('1 Minute', 60),\n\t\t\t\t\t\t\t  ('2 Minutes', 120),\n\t\t\t\t\t\t\t  ('5 Minutes', 300),\n\t\t\t\t\t\t\t  ('10 Minutes', 600),\n\t\t\t\t\t\t\t  ('20 Minutes', 1200)],\n\t\t\t\t  'default': 0}),\n\t\t\t\t('Respawn Times',\n\t\t\t\t {'choices': [('Shorter', 0.25),\n\t\t\t\t\t\t\t  ('Short', 0.5),\n\t\t\t\t\t\t\t  ('Normal', 1.0),\n\t\t\t\t\t\t\t  ('Long', 2.0),\n\t\t\t\t\t\t\t  ('Longer', 4.0)],\n\t\t\t\t  'default': 1.0}),\n\t\t\t\t('Epic Mode', {'default': False})]\n\n\tdef onTeamJoin(self,team):\n\t\tteam.gameData['timeRemaining'] = self.settings[\"Frozen One Time\"]\n\t\tself._updateScoreBoard()\n\n\tdef endGame(self):\n\t\tresults = bs.TeamGameResults()\n\t\tfor team in self.teams: results.setTeamScore(team,self.settings['Frozen One Time'] - team.gameData['timeRemaining'])\n\t\tself.end(results=results,announceDelay=0)\n\n\tdef _setChosenOnePlayer(self, player):\n\t\ttry:\n\t\t\tfor p in self.players: p.gameData['FrozenLight'] = None\n\t\t\tbs.playSound(self._swipSound)\n\t\t\tif player is None or not player.exists():\n\t\t\t\tself._flag = bs.Flag(color=(1,0.9,0.2),\n\t\t\t\t\t\t\t\t\t position=self._flagSpawnPos,\n\t\t\t\t\t\t\t\t\t touchable=False)\n\t\t\t\tself._chosenOnePlayer = None\n\n\t\t\t\tl = bs.newNode('light',\n\t\t\t\t\t\t\t   owner=self._flag.node,\n\t\t\t\t\t\t\t   attrs={'position': self._flagSpawnPos,\n\t\t\t\t\t\t\t\t\t  'intensity':0.6,\n\t\t\t\t\t\t\t\t\t  'heightAttenuated':False,\n\t\t\t\t\t\t\t\t\t  'volumeIntensityScale':0.1,\n\t\t\t\t\t\t\t\t\t  'radius':0.1,\n\t\t\t\t\t\t\t\t\t  'color': (1.2,1.2,0.4)})\n\n\t\t\t\tself._flashFlagSpawn()\n\t\t\telse:\n\t\t\t\tif player.actor is not None:\n\t\t\t\t\tself._flag = None\n\t\t\t\t\tself._chosenOnePlayer = player\n\n\t\t\t\t\tif player.actor.node.exists():\n\t\t\t\t\t\tif self.settings['Frozen One Gets Gloves']: player.actor.handleMessage(bs.PowerupMessage('punch'))\n\n\t\t\t\t\t\tplayer.actor.frozen = True\n\t\t\t\t\t\tplayer.actor.node.frozen = 1\n\t\t\t\t\t\t# use a color that's partway between their team color and white\n\t\t\t\t\t\tcolor = [0.3+c*0.7 for c in bs.getNormalizedColor(player.getTeam().color)]\n\t\t\t\t\t\tl = player.gameData['FrozenLight'] = bs.NodeActor(bs.newNode('light',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t attrs={\"intensity\":0.6,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"heightAttenuated\":False,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"volumeIntensityScale\":0.1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"radius\":0.13,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"color\": color}))\n\n\t\t\t\t\t\tbs.animate(l.node, 'intensity', {0:1.0, 200:0.4, 400:1.0}, loop=True)\n\t\t\t\t\t\tplayer.actor.node.connectAttr('position',l.node,'position')\n\t\texcept Exception, e:\n\t\t\timport traceback\n\t\t\tprint 'EXC in _setChosenOnePlayer'\n\t\t\ttraceback.print_exc(e)\n\t\t\ttraceback.print_stack()\n\n\tdef _updateScoreBoard(self):\n\t\tfor team in self.teams:\n\t\t\tself._scoreBoard.setTeamValue(team,team.gameData['timeRemaining'],self.settings['Frozen One Time'], countdown=True)\n"
  },
  {
    "path": "mods/iceDeathmatch.json",
    "content": "{\n  \"name\": \"Ice Deathmatch\",\n  \"author\": \"SoKpl\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/iceDeathmatch.py",
    "content": "import bs\n\n\ndef bsGetAPIVersion():\n    return 4\n\n\ndef bsGetGames():\n    return [IceDeathMatchGame]\n\n\nclass IceDeathMatchGame(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Ice Deathmatch'\n\n    @classmethod\n    def getDescription(cls, sessionType):\n        return 'Freeze enemies and dump them off the map'\n\n    @classmethod\n    def supportsSessionType(cls, sessionType):\n        return True if (issubclass(sessionType, bs.TeamsSession)\n                        or issubclass(sessionType, bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return bs.getMapsSupportingPlayType(\"melee\")\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        return [(\"Kills to Win Per Player\",{'minValue':1,'default':5,'increment':1}),\n                (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                        ('2 Minutes',120),('5 Minutes',300),\n                                        ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                (\"Epic Mode\",{'default':False})]\n\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # print messages when players die since it matters here..\n        self.announcePlayerDeaths = True\n\n        self._scoreBoard = bs.ScoreBoard()\n\n    def getInstanceDescription(self):\n        return ('Crush ${ARG1} of your enemies.',self._scoreToWin)\n\n    def getInstanceScoreBoardDescription(self):\n        return ('kill ${ARG1} enemies',self._scoreToWin)\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Onslaught')\n\n    def onTeamJoin(self, team):\n        team.gameData['score'] = 0\n        if self.hasBegun(): self._updateScoreBoard()\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        if len(self.teams) > 0:\n            self._scoreToWin = self.settings['Kills to Win Per Player'] * max(1,max(len(t.players) for t in self.teams))\n        else: self._scoreToWin = self.settings['Kills to Win Per Player']\n        self._updateScoreBoard()\n        self._dingSound = bs.getSound('dingSmall')\n\n    def spawnPlayer(self, player):\n\n        spaz = self.spawnPlayerSpaz(player)\n\n        # lets reconnect this player's controls to this\n        # spaz but *without* the ability to pick stuff up\n        spaz.connectControlsToPlayer(enablePunch=False,\n                                     enableBomb=True,\n                                     enablePickUp=True)\n\n        # also lets have them make some noise when they die..\n        spaz.playBigDeathSound = True\n\n        # give players permanent triple impact bombs and wire them up\n        # to tell us when they drop a bomb\n        spaz.bombType = 'ice'\n        spaz.setBombCount(2)\n        spaz.addDroppedBombCallback(self._onSpazDroppedBomb)\n\n    def handleMessage(self, m):\n        if isinstance(m, bs.PlayerSpazDeathMessage):\n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n\n            player = m.spaz.getPlayer()\n            self.respawnPlayer(player)\n\n            killer = m.killerPlayer\n            if killer is None: return\n\n            # handle team-kills\n            if killer.getTeam() is player.getTeam():\n\n                # in free-for-all, killing yourself loses you a point\n                if isinstance(self.getSession(), bs.FreeForAllSession):\n                    player.getTeam().gameData['score'] = max(0, player.getTeam().gameData['score']-1)\n\n                # in teams-mode it gives a point to the other team\n                else:\n                    bs.playSound(self._dingSound)\n                    for team in self.teams:\n                        if team is not killer.getTeam():\n                            team.gameData['score'] += 1\n\n            # killing someone on another team nets a kill\n            else:\n                killer.getTeam().gameData['score'] += 1\n                bs.playSound(self._dingSound)\n                # in FFA show our score since its hard to find on the scoreboard\n                try: killer.actor.setScoreText(str(killer.getTeam().gameData['score'])+'/'+str(self._scoreToWin),color=killer.getTeam().color,flash=True)\n                except Exception: pass\n\n            self._updateScoreBoard()\n\n            # if someone has won, set a timer to end shortly\n            # (allows the dust to clear and draws to occur if deaths are close enough)\n            if any(team.gameData['score'] >= self._scoreToWin for team in self.teams):\n                bs.gameTimer(500, self.endGame)\n\n        else: bs.TeamGameActivity.handleMessage(self, m)\n\n    def _updateScoreBoard(self):\n        for team in self.teams:\n            self._scoreBoard.setTeamValue(team, team.gameData['score'], self._scoreToWin)\n\n    def endGame(self):\n        results = bs.TeamGameResults()\n        for t in self.teams: results.setTeamScore(t, t.gameData['score'])\n        self.end(results=results)\n"
  },
  {
    "path": "mods/magic_box.json",
    "content": "{\n  \"name\": \"Magic Box\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/magic_box.py",
    "content": "import bs\nimport bsUtils\n\nclass MagicBox(bs.Bomb):\n\tdef __init__(self, position=(0, 1, 0), velocity=(0, 0, 0), bombType='tnt', blastRadius=2.0, sourcePlayer=None, owner=None):\n\t\t\"\"\"\n\t\tCreate a new Bomb.\n\n\t\tbombType can be 'ice','impact','landMine','normal','sticky', or 'tnt'.\n\t\tNote that for impact or landMine bombs you have to call arm()\n\t\tbefore they will go off.\n\t\t\"\"\"\n\t\tbs.Actor.__init__(self)\n\n\t\tfactory = self.getFactory()\n\n\t\tself.bombType = 'tnt'\n\n\t\tself._exploded = False\n\n\t\tself.blastRadius = blastRadius\n\n\t\t# TNT\n\t\tself.blastRadius *= 1.45\n\n\t\tself._explodeCallbacks = []\n\n\t\t# the player this came from\n\t\tself.sourcePlayer = sourcePlayer\n\n\t\t# by default our hit type/subtype is our own, but we pick up types of whoever\n\t\t# sets us off so we know what caused a chain reaction\n\t\tself.hitType = 'explosion'\n\t\tself.hitSubType = self.bombType\n\n\t\t# if no owner was provided, use an unconnected node ref\n\t\tif owner is None:\n\t\t\towner = bs.Node(None)\n\n\t\t# the node this came from\n\t\tself.owner = owner\n\n\n\t\t# TNT\n\t\tmaterials = (factory.bombMaterial, bs.getSharedObject('footingMaterial'), bs.getSharedObject('objectMaterial'))\n\t\tmaterials = materials + (factory.normalSoundMaterial,)\n\n\n\t\tself.node = bs.newNode('prop',\n\t\t\t\t\t\t\t   delegate=self,\n\t\t\t\t\t\t\t   attrs={'position':position,\n\t\t\t\t\t\t\t\t\t  'velocity':velocity,\n\t\t\t\t\t\t\t\t\t  'model':factory.tntModel,\n\t\t\t\t\t\t\t\t\t  'lightModel':factory.tntModel,\n\t\t\t\t\t\t\t\t\t  'body':'crate',\n\t\t\t\t\t\t\t\t\t  'shadowSize':0.5,\n\t\t\t\t\t\t\t\t\t  'colorTexture':factory.tntTex,\n\t\t\t\t\t\t\t\t\t  'reflection':'soft',\n\t\t\t\t\t\t\t\t\t  'reflectionScale':[0.23],\n\t\t\t\t\t\t\t\t\t  'materials':materials})\n\n\n\t\t#self.node.extraAcceleration = (0, 40, 0)\n\t\tself.heldBy = 0\n\t\tself._isDead = False\n\n\n\t\tbsUtils.animate(self.node, \"modelScale\", {0:0, 200:1.3, 260:1})\n\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, bs.PickedUpMessage):\n\t\t\tbs.getActivity()._updateBoxState()\n\t\telif isinstance(m, bs.DroppedMessage):\n\t\t\tself.heldBy -= 1\n\t\t\tself.updateFloatyness()\n\t\t\tbs.gameTimer(200, bs.getActivity()._updateBoxState)\n\t\telif isinstance(m, bs.DieMessage):\n\t\t\tif not self._isDead:\n\t\t\t\tbs.gameTimer(1000, bs.getActivity()._spawnBox)\n\t\t\tself._isDead = True\n\t\tsuper(self.__class__, self).handleMessage(m)\n\n\tdef updateFloatyness(self):\n\t\toldY = self.node.extraAcceleration[1]\n\t\tnewY = {0: 0, 1: 39, 2: 19 + 20 * 2, 3: 19 + 20 * 3}.get(self.heldBy, 0) # needs more science\n\t\ttime = 300 if (oldY >= newY) else 1000\n\t\tkeys = {0: (0, oldY, 0), time: (0, newY, 0)}\n\t\tbs.animateArray(self.node, 'extraAcceleration', 3, keys)\n\n\tdef _hideScoreText(self):\n\t\ttry:\n\t\t\texists = self._scoreText.exists()\n\t\texcept Exception:\n\t\t\texists = False\n\t\tif exists:\n\t\t\tbs.animate(self._scoreText, 'scale', {0: self._scoreText.scale, 200: 0})\n\n\tdef setScoreText(self, text):\n\t\t\"\"\"\n\t\tUtility func to show a message over the flag; handy for scores.\n\t\t\"\"\"\n\t\tif not self.node.exists():\n\t\t\treturn\n\t\ttry:\n\t\t\texists = self._scoreText.exists()\n\t\texcept Exception:\n\t\t\texists = False\n\t\tif not exists:\n\t\t\tstartScale = 0.0\n\t\t\tmath = bs.newNode('math', owner=self.node, attrs={'input1': (0, 0.6, 0), 'operation': 'add'})\n\t\t\tself.node.connectAttr('position', math, 'input2')\n\t\t\tself._scoreText = bs.newNode('text',\n\t\t\t\t\t\t\t\t\t\t  owner=self.node,\n\t\t\t\t\t\t\t\t\t\t  attrs={'text':text,\n\t\t\t\t\t\t\t\t\t\t\t\t 'inWorld':True,\n\t\t\t\t\t\t\t\t\t\t\t\t 'scale':0.02,\n\t\t\t\t\t\t\t\t\t\t\t\t 'shadow':0.5,\n\t\t\t\t\t\t\t\t\t\t\t\t 'flatness':1.0,\n\t\t\t\t\t\t\t\t\t\t\t\t 'hAlign':'center'})\n\t\t\tmath.connectAttr('output', self._scoreText, 'position')\n\t\telse:\n\t\t\tstartScale = self._scoreText.scale\n\t\t\tself._scoreText.text = text\n\t\tself._scoreText.color = bs.getSafeColor((1.0, 1.0, 0.4))\n\t\tbs.animate(self._scoreText, 'scale', {0: startScale, 200: 0.02})\n\t\tself._scoreTextHideTimer = bs.Timer(1000, bs.WeakCall(self._hideScoreText))\n\n\ndef bsGetAPIVersion():\n\treturn 4\n\n\ndef bsGetGames():\n\treturn [MagicBoxGame]\n\n\nclass MagicBoxGame(bs.TeamGameActivity):\n\n\tBOX_NEW = 0\n\tBOX_UNCONTESTED = 1\n\tBOX_CONTESTED = 2\n\tBOX_HELD = 3\n\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Magic Box'\n\n\t@classmethod\n\tdef getDescription(cls, sessionType):\n\t\treturn 'Grab the box and start flying.'\n\n\t@classmethod\n\tdef getScoreInfo(cls):\n\t\treturn {'scoreName': 'Time Held'}\n\n\t@classmethod\n\tdef supportsSessionType(cls, sessionType):\n\t\treturn True if (issubclass(sessionType, bs.TeamsSession)\n\t\t\t\t\t\tor issubclass(sessionType, bs.FreeForAllSession)) else False\n\n\t@classmethod\n\tdef getSupportedMaps(cls, sessionType):\n\t\treturn bs.getMapsSupportingPlayType(\"keepAway\")\n\n\t@classmethod\n\tdef getSettings(cls, sessionType):\n\t\treturn [(\"Hold Time\", {'minValue': 30, 'default': 60, 'increment': 10}),\n\t\t\t\t(\"Time Limit\", {'choices': [('None', 0), ('1 Minute', 60),\n\t\t\t\t\t\t\t\t\t\t('2 Minutes', 120), ('5 Minutes', 300)], 'default': 0}),\n\t\t\t\t(\"Respawn Times\", {'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0), ('Long', 2.0), ('Longer', 4.0)], 'default': 1.0})]\n\n\tdef __init__(self, settings):\n\t\tbs.TeamGameActivity.__init__(self, settings)\n\t\tself._scoreBoard = bs.ScoreBoard()\n\t\tself._swipSound = bs.getSound(\"swip\")\n\t\tself._tickSound = bs.getSound('tick')\n\t\tself._countDownSounds = {10:bs.getSound('announceTen'),\n\t\t\t\t\t\t\t\t 9:bs.getSound('announceNine'),\n\t\t\t\t\t\t\t\t 8:bs.getSound('announceEight'),\n\t\t\t\t\t\t\t\t 7:bs.getSound('announceSeven'),\n\t\t\t\t\t\t\t\t 6:bs.getSound('announceSix'),\n\t\t\t\t\t\t\t\t 5:bs.getSound('announceFive'),\n\t\t\t\t\t\t\t\t 4:bs.getSound('announceFour'),\n\t\t\t\t\t\t\t\t 3:bs.getSound('announceThree'),\n\t\t\t\t\t\t\t\t 2:bs.getSound('announceTwo'),\n\t\t\t\t\t\t\t\t 1:bs.getSound('announceOne')}\n\n\tdef getInstanceDescription(self):\n\t\treturn ('Hold the magic box for ${ARG1} seconds.', self.settings['Hold Time'])\n\n\tdef getInstanceScoreBoardDescription(self):\n\t\treturn ('Hold the magic box for ${ARG1} seconds', self.settings['Hold Time'])\n\n\tdef onTransitionIn(self):\n\t\tbs.TeamGameActivity.onTransitionIn(self, music='Keep Away')\n\n\tdef onTeamJoin(self, team):\n\t\tteam.gameData['timeRemaining'] = self.settings[\"Hold Time\"]\n\t\tself._updateScoreBoard()\n\n\tdef onBegin(self):\n\t\tbs.TeamGameActivity.onBegin(self)\n\t\tself.setupStandardTimeLimit(self.settings['Time Limit'])\n\t\tself.setupStandardPowerupDrops(enableTNT=False)\n\t\tself._boxSpawnPos = self.getMap().getFlagPosition(None)\n\t\tself._spawnBox()\n\t\tself._updateTimer = bs.Timer(1000, call=self._tick, repeat=True)\n\t\tself._updateBoxState()\n\n\tdef _tick(self):\n\t\tself._updateBoxState()\n\n\t\t# award points to all living players holding the flag\n\t\tfor player in self._holdingPlayers:\n\t\t\tif player.exists():\n\t\t\t\tself.scoreSet.playerScored(player, 3, screenMessage=False, display=False)\n\n\t\tscoringTeam = self._scoringTeam\n\n\t\tif scoringTeam is not None:\n\n\t\t\tif scoringTeam.gameData['timeRemaining'] > 0:\n\t\t\t\tbs.playSound(self._tickSound)\n\n\t\t\tscoringTeam.gameData['timeRemaining'] = max(0, scoringTeam.gameData['timeRemaining'] - 1)\n\t\t\tself._updateScoreBoard()\n\t\t\tif scoringTeam.gameData['timeRemaining'] > 0:\n\t\t\t\tself._box.setScoreText(str(scoringTeam.gameData['timeRemaining']))\n\n\t\t\t# announce numbers we have sounds for\n\t\t\ttry:\n\t\t\t\tbs.playSound(self._countDownSounds[scoringTeam.gameData['timeRemaining']])\n\t\t\texcept Exception:\n\t\t\t\tpass\n\n\t\t\t# winner\n\t\t\tif scoringTeam.gameData['timeRemaining'] <= 0:\n\t\t\t\tself.endGame()\n\n\tdef endGame(self):\n\t\tresults = bs.TeamGameResults()\n\t\tfor team in self.teams:\n\t\t\tresults.setTeamScore(team, self.settings['Hold Time'] - team.gameData['timeRemaining'])\n\t\tself.end(results=results, announceDelay=0)\n\n\tdef _updateBoxState(self):\n\t\tfor team in self.teams:\n\t\t\tteam.gameData['holdingBox'] = False\n\t\tself._holdingPlayers = []\n\t\tfor player in self.players:\n\t\t\ttry:\n\t\t\t\tif player.actor.isAlive() and player.actor.node.holdNode.exists():\n\t\t\t\t\tholdingBox = (player.actor.node.holdNode == self._box.node)\n\t\t\t\telse: holdingBox = False\n\t\t\texcept Exception:\n\t\t\t\tbs.printException(\"exception checking hold flag\")\n\t\t\tif holdingBox:\n\t\t\t\tself._holdingPlayers.append(player)\n\t\t\t\tplayer.getTeam().gameData['holdingBox'] = True\n\n\t\tif self._box is not None and self._box.exists():\n\t\t\tself._box.heldBy = len(self._holdingPlayers)\n\t\t\tself._box.updateFloatyness()\n\n\t\tholdingTeams = set(t for t in self.teams if t.gameData['holdingBox'])\n\t\tprevState = self._boxState\n\t\tif len(holdingTeams) > 1:\n\t\t\tself._boxState = self.BOX_CONTESTED\n\t\t\tself._scoringTeam = None\n\t\telif len(holdingTeams) == 1:\n\t\t\tholdingTeam = list(holdingTeams)[0]\n\t\t\tself._boxState = self.BOX_HELD\n\t\t\tself._scoringTeam = holdingTeam\n\t\telse:\n\t\t\tself._boxState = self.BOX_UNCONTESTED\n\t\t\tself._scoringTeam = None\n\n\t\tif self._boxState != prevState:\n\t\t\tbs.playSound(self._swipSound)\n\n\tdef _spawnBox(self):\n\t\tbs.playSound(self._swipSound)\n\t\tself._flashBoxSpawn()\n\t\tself._box = MagicBox(position=self._boxSpawnPos)\n\t\tself._boxState = self.BOX_NEW\n\t\tself._box.light = bs.newNode('light',\n\t\t\t\t\t\t\t\t\t  owner=self._box.node,\n\t\t\t\t\t\t\t\t\t  attrs={'intensity':0.2,\n\t\t\t\t\t\t\t\t\t\t\t 'radius':0.3,\n\t\t\t\t\t\t\t\t\t\t\t 'color': (0.2, 0.2, 0.2)})\n\t\tself._box.node.connectAttr('position', self._box.light, 'position')\n\t\tself._updateBoxState()\n\n\tdef _flashBoxSpawn(self):\n\t\tlight = bs.newNode('light',\n\t\t\t\t\t\t   attrs={'position':self._boxSpawnPos, 'color':(1, 1, 1),\n\t\t\t\t\t\t\t\t  'radius':0.3, 'heightAttenuated':False})\n\t\tbs.animate(light, 'intensity', {0:0, 250:0.5, 500:0}, loop=True)\n\t\tbs.gameTimer(1000, light.delete)\n\n\tdef _updateScoreBoard(self):\n\t\tfor team in self.teams:\n\t\t\tself._scoreBoard.setTeamValue(team, team.gameData['timeRemaining'], self.settings['Hold Time'], countdown=True)\n\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, bs.PlayerSpazDeathMessage):\n\t\t\tbs.TeamGameActivity.handleMessage(self, m) # augment default\n\t\t\tself.respawnPlayer(m.spaz.getPlayer())\n\t\telse:\n\t\t\tbs.TeamGameActivity.handleMessage(self, m)\n"
  },
  {
    "path": "mods/modManager.json",
    "content": "{\n  \"name\": \"Mod Manager (this thingy)\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"utilities\",\n  \"requires\": [\"ui_wrappers\", \"settings_patcher\"]\n}\n"
  },
  {
    "path": "mods/modManager.py",
    "content": "from __future__ import print_function\nimport bs\nimport bsInternal\nimport os\nimport urllib\nimport urllib2\nimport httplib\nimport json\nimport random\nimport time\nimport threading\nimport weakref\nfrom md5 import md5\nfrom bsUI import gSmallUI, gMedUI, gHeadingColor, uiGlobals, ConfirmWindow, StoreWindow, MainMenuWindow, Window\nfrom functools import partial\n\ntry:\n    from settings_patcher import SettingsButton\nexcept ImportError:\n    bs.screenMessage(\"library settings_patcher missing\", color=(1, 0, 0))\n    raise\ntry:\n    from ui_wrappers import TextWidget, ContainerWidget, ButtonWidget, CheckBoxWidget, ScrollWidget, ColumnWidget, Widget\nexcept ImportError:\n    bs.screenMessage(\"library ui_wrappers missing\", color=(1, 0, 0))\n    raise\n\n\n# roll own uuid4 implementation because uuid module might not be available\n# this is broken on android/1.4.216 due to 16**8 == 0 o.O\ndef uuid4():\n    components = [8, 4, 4, 4, 12]\n    return \"-\".join([('%012x' % random.randrange(16**a))[12 - a:] for a in components])\n\nPROTOCOL_VERSION = 1.1\nSTAT_SERVER_URI = None # \"http://bsmm.thuermchen.com\"\nSUPPORTS_HTTPS = hasattr(httplib, 'HTTPS')\nUSER_REPO = \"Mrmaxmeier/BombSquad-Community-Mod-Manager\"\n\n_supports_auto_reloading = True\n_auto_reloader_type = \"patching\"\nStoreWindow_setTab = StoreWindow._setTab\nMainMenuWindow__init__ = MainMenuWindow.__init__\n\n\ndef _prepare_reload():\n    settingsButton.remove()\n    MainMenuWindow.__init__ = MainMenuWindow__init__\n    del MainMenuWindow._cb_checkUpdateData\n    StoreWindow._setTab = StoreWindow_setTab\n    del StoreWindow._onGetMoreGamesPress\n\n\ndef bsGetAPIVersion():\n    return 4\n\nquittoapply = None\ncheckedMainMenu = False\n\n\nif 'mod_manager_config' not in bs.getConfig():\n    bs.getConfig()['mod_manager_config'] = {}\n    bs.writeConfig()\n\nconfig = bs.getConfig()['mod_manager_config']\n\n\ndef index_url(branch=None):\n    if not branch:\n        branch = config.get(\"branch\", \"master\")\n    if SUPPORTS_HTTPS:\n        yield \"https://raw.githubusercontent.com/{}/{}/index.json\".format(USER_REPO, branch)\n        yield \"https://rawgit.com/{}/{}/index.json\".format(USER_REPO, branch)\n    yield \"http://raw.githack.com/{}/{}/index.json\".format(USER_REPO, branch)\n    yield \"http://rawgit.com/{}/{}/index.json\".format(USER_REPO, branch)\n\n\ndef mod_url(data):\n    if \"commit_sha\" in data and \"filename\" in data:\n        commit_hexsha = data[\"commit_sha\"]\n        filename = data[\"filename\"]\n        if SUPPORTS_HTTPS:\n            yield \"https://cdn.rawgit.com/{}/{}/mods/{}\".format(USER_REPO, commit_hexsha, filename)\n        yield \"http://rawcdn.githack.com/{}/{}/mods/{}\".format(USER_REPO, commit_hexsha, filename)\n    if \"url\" in data:\n        if SUPPORTS_HTTPS:\n            yield data[\"url\"]\n        yield data[\"url\"].replace(\"https\", \"http\")\n\n\ndef try_fetch_cb(generator, callback, **kwargs):\n    def f(data, status_code):\n        if data:\n            callback(data, status_code)\n        else:\n            try:\n                get_cached(next(generator), f, **kwargs)\n            except StopIteration:\n                callback(None, None)\n    get_cached(next(generator), f, **kwargs)\n\n\nweb_cache = config.get(\"web_cache\", {})\nconfig[\"web_cache\"] = web_cache\n\nif STAT_SERVER_URI and 'uuid' not in config:\n    config['uuid'] = uuid4()\n    bs.writeConfig()\n\n\ndef get_cached(url, callback, force_fresh=False, fallback_to_outdated=True):\n    def cache(data, status_code):\n        if data:\n            web_cache[url] = (data, time.time())\n            bs.writeConfig()\n\n    def f(data, status_code):\n        # TODO: cancel prev fetchs\n        callback(data, status_code)\n        cache(data, status_code)\n\n    if force_fresh:\n        mm_serverGet(url, {}, f)\n        return\n\n    if url in web_cache:\n        data, timestamp = web_cache[url]\n        if timestamp + 10 * 30 > time.time():\n            mm_serverGet(url, {}, cache)\n        if fallback_to_outdated or timestamp + 10 * 60 > time.time():\n            callback(data, None)\n            return\n\n    mm_serverGet(url, {}, f)\n\n\ndef get_index(callback, branch=None, **kwargs):\n    try_fetch_cb(index_url(branch), callback, **kwargs)\n\n\ndef fetch_stats(callback, **kwargs):\n    if STAT_SERVER_URI:\n        url = STAT_SERVER_URI + \"/stats?uuid=\" + config['uuid']\n        get_cached(url, callback, **kwargs)\n\n\ndef stats_cached():\n    if not STAT_SERVER_URI:\n        return False\n    url = STAT_SERVER_URI + \"/stats?uuid=\" + config['uuid']\n    return url in web_cache\n\n\ndef submit_mod_rating(mod, rating, callback):\n    if not STAT_SERVER_URI:\n        return bs.screenMessage('rating submission disabled')\n    url = STAT_SERVER_URI + \"/submit_rating\"\n    data = {\n        \"uuid\": config['uuid'],\n        \"mod_str\": mod.base,\n        \"rating\": rating,\n    }\n\n    def cb(data, status_code):\n        if status_code == 200:\n            bs.screenMessage(\"rating submitted\")\n            callback()\n        else:\n            bs.screenMessage(\"failed to submit rating\")\n\n    mm_serverPost(url, data, cb, eval_data=False)\n\n\ndef submit_download(mod):\n    if not config.get('submit-download-statistics', True) or not STAT_SERVER_URI:\n        return\n\n    url = STAT_SERVER_URI + \"/submit_download\"\n    data = {\n        \"uuid\": config.get('uuid'),\n        \"mod_str\": mod.base,\n    }\n\n    def cb(data, status_code):\n        if status_code != 200:\n            print(\"failed to submit download stats\")\n\n    mm_serverPost(url, data, cb, eval_data=False)\n\n\ndef fetch_mod(data, callback):\n    generator = mod_url(data)\n\n    def f(data, status_code):\n        if data:\n            callback(data, status_code)\n        else:\n            try:\n                mm_serverGet(next(generator), {}, f, eval_data=False)\n            except StopIteration:\n                callback(None, None)\n\n    mm_serverGet(next(generator), {}, f, eval_data=False)\n\n\ndef process_server_data(data):\n    mods = data[\"mods\"]\n    version = data[\"version\"]\n    if version - 0.5 > PROTOCOL_VERSION:\n        print(\"version diff:\", version, PROTOCOL_VERSION)\n        bs.screenMessage(\"please manually update the mod manager\")\n    return mods, version\n\n\ndef _cb_checkUpdateData(self, data, status_code):\n    try:\n        if data:\n            m, v = process_server_data(data)\n            mods = [Mod(d) for d in m.values()]\n            for mod in mods:\n                mod._mods = {m.base: m for m in mods}\n                if mod.is_installed() and mod.is_outdated():\n                    if config.get(\"auto-update-old-mods\", True):\n                        bs.screenMessage(\"updating mod '{}'...\".format(mod.name))\n\n                        def cb(mod, success):\n                            if success:\n                                bs.screenMessage(\"updated mod '{}'.\".format(mod.name))\n\n                        mod.install(cb)\n                    else:\n                        bs.screenMessage(\"an update for mod '{}' is available!\".format(mod.name))\n    except:\n        bs.printException()\n        bs.screenMessage(\"failed to check for mod updates\")\n\n\noldMainInit = MainMenuWindow.__init__\n\n\ndef newMainInit(self, transition='inRight'):\n    global checkedMainMenu\n    oldMainInit(self, transition)\n    if checkedMainMenu:\n        return\n    checkedMainMenu = True\n    if config.get(\"auto-check-updates\", True):\n        get_index(self._cb_checkUpdateData, force_fresh=True)\n\nMainMenuWindow.__init__ = newMainInit\nMainMenuWindow._cb_checkUpdateData = _cb_checkUpdateData\n\n\ndef _doModManager(swinstance):\n    swinstance._saveState()\n    bs.containerWidget(edit=swinstance._rootWidget, transition='outLeft')\n    mm_window = ModManagerWindow(backLocationCls=swinstance.__class__)\n    uiGlobals['mainMenuWindow'] = mm_window.getRootWidget()\n\nsettingsButton = SettingsButton(id=\"ModManager\", icon=\"heart\", sorting_position=6) \\\n    .setCallback(_doModManager) \\\n    .setText(\"Mod Manager\") \\\n    .add()\n\n\nclass ModManager_ServerCallThread(threading.Thread):\n\n    def __init__(self, request, requestType, data, callback, eval_data=True):\n        threading.Thread.__init__(self)\n        self._request = request.encode(\"ascii\")  # embedded python2.7 has weird encoding issues\n        self._requestType = requestType\n        self._data = {} if data is None else data\n        self._eval_data = eval_data\n        self._callback = callback\n\n        self._context = bs.Context('current')\n\n        # save and restore the context we were created from\n        activity = bs.getActivity(exceptionOnNone=False)\n        self._activity = weakref.ref(activity) if activity is not None else None\n\n    def _runCallback(self, *args):\n\n        # if we were created in an activity context and that activity has since died, do nothing\n        # (hmm should we be using a context-call instead of doing this manually?)\n        if self._activity is not None and (self._activity() is None or self._activity().isFinalized()):\n            return\n\n        # (technically we could do the same check for session contexts, but not gonna worry about it for now)\n        with self._context:\n            self._callback(*args)\n\n    def run(self):\n        try:\n            bsInternal._setThreadName(\"ModManager_ServerCallThread\")  # FIXME: using protected apis\n            env = {'User-Agent': bs.getEnvironment()['userAgentString']}\n            if self._requestType != \"get\" or self._data:\n                if self._requestType == 'get':\n                    if self._data:\n                        request = urllib2.Request(self._request + '?' + urllib.urlencode(self._data), None, env)\n                    else:\n                        request = urllib2.Request(self._request, None, env)\n                elif self._requestType == 'post':\n                    request = urllib2.Request(self._request, json.dumps(self._data), env)\n                else:\n                    raise RuntimeError(\"Invalid requestType: \" + self._requestType)\n                response = urllib2.urlopen(request)\n            else:\n                response = urllib2.urlopen(self._request)\n\n            if self._eval_data:\n                responseData = json.loads(response.read())\n            else:\n                responseData = response.read()\n            if self._callback is not None:\n                bs.callInGameThread(bs.Call(self._runCallback, responseData, response.getcode()))\n\n        except:\n            bs.printException()\n            if self._callback is not None:\n                bs.callInGameThread(bs.Call(self._runCallback, None, None))\n\n\ndef mm_serverGet(request, data, callback=None, eval_data=True):\n    ModManager_ServerCallThread(request, 'get', data, callback, eval_data=eval_data).start()\n\n\ndef mm_serverPost(request, data, callback=None, eval_data=True):\n    ModManager_ServerCallThread(request, 'post', data, callback, eval_data=eval_data).start()\n\n\nclass ModManagerWindow(Window):\n    _selectedMod, _selectedModIndex = None, None\n    categories = set([\"all\"])\n    tabs = []\n    tabheight = 35\n    mods = []\n    _modWidgets = []\n    currently_fetching = False\n    timers = {}\n\n    def __init__(self, transition='inRight', modal=False, showTab=\"all\", onCloseCall=None, backLocationCls=None, originWidget=None):\n\n        # if they provided an origin-widget, scale up from that\n        if originWidget is not None:\n            self._transitionOut = 'outScale'\n            transition = 'inScale'\n        else:\n            self._transitionOut = 'outRight'\n\n        self._backLocationCls = backLocationCls\n        self._onCloseCall = onCloseCall\n        self._showTab = showTab\n        self._selectedTab = {'label': showTab}\n        if showTab != \"all\":\n            def check_tab_available():\n                if not self._rootWidget.exists():\n                    return\n                if any([mod.category == showTab for mod in self.mods]):\n                    return\n                if \"button\" in self._selectedTab:\n                    return\n                self._selectedTab = {\"label\": \"all\"}\n                self._refresh()\n            self.timers[\"check_tab_available\"] = bs.Timer(300, check_tab_available, timeType='real')\n        self._modal = modal\n\n        self._windowTitleName = \"Community Mod Manager\"\n\n        def sort_rating(mods):\n            mods = sorted(mods, key=lambda mod: mod.rating_submissions, reverse=True)\n            return sorted(mods, key=lambda mod: mod.rating, reverse=True)\n\n        def sort_downloads(mods):\n            return sorted(mods, key=lambda mod: mod.downloads, reverse=True)\n\n        def sort_alphabetical(mods):\n            return sorted(mods, key=lambda mod: mod.name.lower())\n\n        _sortModes = [\n            ('Rating', sort_rating, lambda m: stats_cached()),\n            ('Downloads', sort_downloads, lambda m: stats_cached()),\n            ('Alphabetical', sort_alphabetical),\n        ]\n\n        self.sortModes = {}\n        for i, sortMode in enumerate(_sortModes):\n            name, func = sortMode[:2]\n            next_sortMode = _sortModes[(i + 1) % len(_sortModes)]\n            condition = sortMode[2] if len(sortMode) > 2 else (lambda mods: True)\n            self.sortModes[name] = {\n                'func': func,\n                'condition': condition,\n                'next': next_sortMode[0],\n                'name': name,\n                'index': i,\n            }\n\n        sortMode = config.get('sortMode')\n        if not sortMode or sortMode not in self.sortModes:\n            sortMode = _sortModes[0][0]\n        self.sortMode = self.sortModes[sortMode]\n\n        self._width = 650\n        self._height = 380 if gSmallUI else 420 if gMedUI else 500\n        topExtra = 20 if gSmallUI else 0\n\n        self._rootWidget = ContainerWidget(size=(self._width, self._height + topExtra), transition=transition,\n                                           scale=2.05 if gSmallUI else 1.5 if gMedUI else 1.0,\n                                           stackOffset=(0, -10) if gSmallUI else (0, 0))\n\n        self._backButton = backButton = ButtonWidget(parent=self._rootWidget, position=(self._width - 160, self._height - 60),\n                                                     size=(160, 68), scale=0.77,\n                                                     autoSelect=True, textScale=1.3,\n                                                     label=bs.Lstr(resource='doneText' if self._modal else 'backText'),\n                                                     onActivateCall=self._back)\n        self._rootWidget.cancelButton = backButton\n        TextWidget(parent=self._rootWidget, position=(0, self._height - 47),\n                   size=(self._width, 25),\n                   text=self._windowTitleName, color=gHeadingColor,\n                   maxWidth=290,\n                   hAlign=\"center\", vAlign=\"center\")\n\n        v = self._height - 59\n        h = 41\n        bColor = (0.6, 0.53, 0.63)\n        bTextColor = (0.75, 0.7, 0.8)\n\n        s = 1.1 if gSmallUI else 1.27 if gMedUI else 1.57\n        v -= 63.0 * s\n        self.refreshButton = ButtonWidget(parent=self._rootWidget,\n                                          position=(h, v),\n                                          size=(90, 58.0 * s),\n                                          onActivateCall=bs.Call(self._cb_refresh, force_fresh=True),\n                                          color=bColor,\n                                          autoSelect=True,\n                                          buttonType='square',\n                                          textColor=bTextColor,\n                                          textScale=0.7,\n                                          label=\"Reload List\")\n\n        v -= 63.0 * s\n        self.modInfoButton = ButtonWidget(parent=self._rootWidget, position=(h, v), size=(90, 58.0 * s),\n                                          onActivateCall=bs.Call(self._cb_info),\n                                          color=bColor,\n                                          autoSelect=True,\n                                          textColor=bTextColor,\n                                          buttonType='square',\n                                          textScale=0.7,\n                                          label=\"Mod Info\")\n\n        v -= 63.0 * s\n        self.sortButtonData = {\"s\": s, \"h\": h, \"v\": v, \"bColor\": bColor, \"bTextColor\": bTextColor}\n        self.sortButton = ButtonWidget(parent=self._rootWidget, position=(h, v), size=(90, 58.0 * s),\n                                       onActivateCall=bs.Call(self._cb_sorting),\n                                       color=bColor,\n                                       autoSelect=True,\n                                       textColor=bTextColor,\n                                       buttonType='square',\n                                       textScale=0.7,\n                                       label=\"Sorting:\\n\" + self.sortMode['name'])\n\n        v -= 63.0 * s\n        self.settingsButton = ButtonWidget(parent=self._rootWidget, position=(h, v), size=(90, 58.0 * s),\n                                           onActivateCall=bs.Call(self._cb_settings),\n                                           color=bColor,\n                                           autoSelect=True,\n                                           textColor=bTextColor,\n                                           buttonType='square',\n                                           textScale=0.7,\n                                           label=\"Settings\")\n\n        v = self._height - 75\n        self.columnPosY = self._height - 75 - self.tabheight\n        self._scrollHeight = self._height - 119 - self.tabheight\n        scrollWidget = ScrollWidget(parent=self._rootWidget, position=(140, self.columnPosY - self._scrollHeight),\n                                    size=(self._width - 180, self._scrollHeight + 10))\n        backButton.set(downWidget=scrollWidget, leftWidget=scrollWidget)\n        self._columnWidget = ColumnWidget(parent=scrollWidget)\n\n        for b in [self.refreshButton, self.modInfoButton, self.settingsButton]:\n            b.rightWidget = scrollWidget\n        scrollWidget.leftWidget = self.refreshButton\n\n        self._cb_refresh()\n\n        backButton.onActivateCall = self._back\n        self._rootWidget.startButton = backButton\n        self._rootWidget.onCancelCall = backButton.activate\n        self._rootWidget.selectedChild = scrollWidget\n\n    def _refresh(self, refreshTabs=True):\n        while len(self._modWidgets) > 0:\n            self._modWidgets.pop().delete()\n\n        for mod in self.mods:\n            if mod.category:\n                self.categories.add(mod.category)\n        if refreshTabs:\n            self._refreshTabs()\n\n        while not self.sortMode['condition'](self.mods):\n            self.sortMode = self.sortModes[self.sortMode['next']]\n            self.sortButton.label = \"Sorting:\\n\" + self.sortMode['name']\n\n        self.mods = self.sortMode[\"func\"](self.mods)\n        visible = self.mods[:]\n        if self._selectedTab[\"label\"] != \"all\":\n            visible = [m for m in visible if m.category == self._selectedTab[\"label\"]]\n\n        for index, mod in enumerate(visible):\n            color = (0.6, 0.6, 0.7, 1.0)\n            if mod.is_installed():\n                color = (0.85, 0.85, 0.85, 1)\n                if mod.checkUpdate():\n                    if mod.is_outdated():\n                        color = (0.85, 0.3, 0.3, 1)\n                    else:\n                        color = (1, 0.84, 0, 1)\n\n            w = TextWidget(parent=self._columnWidget, size=(self._width - 40, 24),\n                           maxWidth=self._width - 110,\n                           text=mod.name,\n                           hAlign='left', vAlign='center',\n                           color=color,\n                           alwaysHighlight=True,\n                           onSelectCall=bs.Call(self._cb_select, index, mod),\n                           onActivateCall=bs.Call(self._cb_info, True),\n                           selectable=True)\n            w.showBufferTop = 50\n            w.showBufferBottom = 50\n            # hitting up from top widget shoud jump to 'back;\n            if index == 0:\n                tab_button = self.tabs[int((len(self.tabs) - 1) / 2)][\"button\"]\n                w.upWidget = tab_button\n\n            if self._selectedMod and mod.filename == self._selectedMod.filename:\n                self._columnWidget.set(selectedChild=w, visibleChild=w)\n\n            self._modWidgets.append(w)\n\n    def _refreshTabs(self):\n        if not self._rootWidget.exists():\n            return\n        for t in self.tabs:\n            for widget in t.values():\n                if isinstance(widget, bs.Widget) or isinstance(widget, Widget):\n                    widget.delete()\n        self.tabs = []\n        total = len(self.categories)\n        columnWidth = self._width - 180\n        tabWidth = 100\n        tabSpacing = 12\n        # _______/-minigames-\\_/-utilities-\\_______\n        for i, tab in enumerate(sorted(list(self.categories))):\n            px = 140 + columnWidth / 2 - tabWidth * total / 2 + tabWidth * i\n            pos = (px, self.columnPosY + 5)\n            size = (tabWidth - tabSpacing, self.tabheight + 10)\n            rad = 10\n            center = (pos[0] + 0.1 * size[0], pos[1] + 0.9 * size[1])\n            txt = TextWidget(parent=self._rootWidget, position=center, size=(0, 0),\n                             hAlign='center', vAlign='center',\n                             maxWidth=1.4 * rad, scale=0.6, shadow=1.0, flatness=1.0)\n            button = ButtonWidget(parent=self._rootWidget, position=pos, autoSelect=True,\n                                  buttonType='tab', size=size, label=tab, enableSound=False,\n                                  onActivateCall=bs.Call(self._cb_select_tab, i),\n                                  color=(0.52, 0.48, 0.63), textColor=(0.65, 0.6, 0.7))\n            self.tabs.append({'text': txt,\n                              'button': button,\n                              'label': tab})\n\n        for i, tab in enumerate(self.tabs):\n            if self._selectedTab[\"label\"] == tab[\"label\"]:\n                self._cb_select_tab(i, refresh=False)\n\n    def _cb_select_tab(self, index, refresh=True):\n        bs.playSound(bs.getSound('click01'))\n        self._selectedTab = self.tabs[index]\n\n        for i, tab in enumerate(self.tabs):\n            button = tab[\"button\"]\n            if i == index:\n                button.set(color=(0.5, 0.4, 0.93), textColor=(0.85, 0.75, 0.95))  # lit\n            else:\n                button.set(color=(0.52, 0.48, 0.63), textColor=(0.65, 0.6, 0.7))  # unlit\n        if refresh:\n            self._refresh(refreshTabs=False)\n\n    def _cb_select(self, index, mod):\n        self._selectedModIndex = index\n        self._selectedMod = mod\n\n    def _cb_refresh(self, force_fresh=False):\n        self.mods = []\n        localfiles = os.listdir(bs.getEnvironment()['userScriptsDirectory'] + \"/\")\n        for file in localfiles:\n            if file.endswith(\".py\"):\n                self.mods.append(LocalMod(file))\n\n        # if CHECK_FOR_UPDATES:\n        #     for mod in self.mods:\n        #         if mod.checkUpdate():\n        #             bs.screenMessage('Update available for ' + mod.filename)\n        #             UpdateModWindow(mod, self._cb_refresh)\n\n        self._refresh()\n        self.currently_fetching = True\n\n        def f(*args, **kwargs):\n            kwargs[\"force_fresh\"] = force_fresh\n            self._cb_serverdata(*args, **kwargs)\n        get_index(f, force_fresh=force_fresh)\n        self.timers[\"showFetchingIndicator\"] = bs.Timer(500, bs.WeakCall(self._showFetchingIndicator), timeType='real')\n\n    def _cb_serverdata(self, data, status_code, force_fresh=False):\n        if not self._rootWidget.exists():\n            return\n        self.currently_fetching = False\n        if data:\n            m, v = process_server_data(data)\n            # when we got network add the network mods\n            localMods = self.mods[:]\n            netMods = [Mod(d) for d in m.values()]\n            self.mods = netMods\n            netFilenames = [m.filename for m in netMods]\n            for localmod in localMods:\n                if localmod.filename not in netFilenames:\n                    self.mods.append(localmod)\n            for mod in self.mods:\n                mod._mods = {m.base: m for m in self.mods}\n            self._refresh()\n        else:\n            bs.screenMessage('network error :(')\n        fetch_stats(self._cb_stats, force_fresh=force_fresh)\n\n    def _cb_stats(self, data, status_code):\n        if not self._rootWidget.exists() or not data:\n            return\n\n        def fill_mods_with(d, attr):\n            for mod_id, value in d.items():\n                for mod in self.mods:\n                    if mod.base == mod_id:\n                        setattr(mod, attr, value)\n\n        fill_mods_with(data.get('average_ratings', {}), 'rating')\n        fill_mods_with(data.get('own_ratings', {}), 'own_rating')\n        fill_mods_with(data.get('amount_ratings', {}), 'rating_submissions')\n        fill_mods_with(data.get('downloads', {}), 'downloads')\n\n        self._refresh()\n\n    def _showFetchingIndicator(self):\n        if self.currently_fetching:\n            bs.screenMessage(\"loading...\")\n\n    def _cb_info(self, withSound=False):\n        if withSound:\n            bs.playSound(bs.getSound('swish'))\n        ModInfoWindow(self._selectedMod, self, originWidget=self.modInfoButton)\n\n    def _cb_settings(self):\n        SettingsWindow(self._selectedMod, self, originWidget=self.settingsButton)\n\n    def _cb_sorting(self):\n        self.sortMode = self.sortModes[self.sortMode['next']]\n        while not self.sortMode['condition'](self.mods):\n            self.sortMode = self.sortModes[self.sortMode['next']]\n        config['sortMode'] = self.sortMode['name']\n        bs.writeConfig()\n        self.sortButton.label = \"Sorting:\\n\" + self.sortMode['name']\n        self._cb_refresh()\n\n    def _back(self):\n        self._rootWidget.doTransition(self._transitionOut)\n        if not self._modal:\n            uiGlobals['mainMenuWindow'] = self._backLocationCls(transition='inLeft').getRootWidget()\n        if self._onCloseCall is not None:\n            self._onCloseCall()\n\n\nclass UpdateModWindow(Window):\n\n    def __init__(self, mod, onok, swish=True, back=False):\n        self._back = back\n        self.mod = mod\n        self.onok = bs.WeakCall(onok)\n        if swish:\n            bs.playSound(bs.getSound('swish'))\n        text = \"Do you want to update %s?\" if mod.is_installed() else \"Do you want to install %s?\"\n        text = text % (mod.filename)\n        if mod.changelog and mod.is_installed():\n            text += \"\\n\\nChangelog:\"\n            for change in mod.changelog:\n                text += \"\\n\" + change\n        height = 100 * (1 + len(mod.changelog) * 0.3) if mod.is_installed() else 100\n        width = 360 * (1 + len(mod.changelog) * 0.15) if mod.is_installed() else 360\n        self._rootWidget = ConfirmWindow(text, self.ok, height=height, width=width).getRootWidget()\n\n    def ok(self):\n        self.mod.install(lambda mod, success: self.onok())\n\n\nclass DeleteModWindow(Window):\n\n    def __init__(self, mod, onok, swish=True, back=False):\n        self._back = back\n        self.mod = mod\n        self.onok = bs.WeakCall(onok)\n        if swish:\n            bs.playSound(bs.getSound('swish'))\n\n        self._rootWidget = ConfirmWindow(\"Are you sure you want to delete \" + mod.filename, self.ok).getRootWidget()\n\n    def ok(self):\n        self.mod.delete(self.onok)\n        QuitToApplyWindow()\n\n\nclass RateModWindow(Window):\n    levels = [\"Poor\", \"Below Average\", \"Average\", \"Above Average\", \"Excellent\"]\n    icons = [\"trophy0b\", \"trophy1\", \"trophy2\", \"trophy3\", \"trophy4\"]\n\n    def __init__(self, mod, onok, swish=True, back=False):\n        self._back = back\n        self.mod = mod\n        self.onok = onok\n        if swish:\n            bs.playSound(bs.getSound('swish'))\n        text = \"How do you want to rate {}?\".format(mod.name)\n\n        okText = bs.Lstr(resource='okText')\n        cancelText = bs.Lstr(resource='cancelText')\n        width = 360\n        height = 330\n\n        self._rootWidget = ContainerWidget(size=(width, height), transition='inRight',\n                                           scale=2.1 if gSmallUI else 1.5 if gMedUI else 1.0)\n\n        TextWidget(parent=self._rootWidget, position=(width * 0.5, height - 30), size=(0, 0),\n                   hAlign=\"center\", vAlign=\"center\", text=text, maxWidth=width * 0.9, maxHeight=height - 75)\n\n        b = ButtonWidget(parent=self._rootWidget, autoSelect=True, position=(20, 20), size=(150, 50), label=cancelText, onActivateCall=self._cancel)\n        self._rootWidget.set(cancelButton=b)\n        okButtonH = width - 175\n\n        b = ButtonWidget(parent=self._rootWidget, autoSelect=True, position=(okButtonH, 20), size=(150, 50), label=okText, onActivateCall=self._ok)\n\n        self._rootWidget.set(selectedChild=b, startButton=b)\n\n        columnPosY = height - 75\n        _scrollHeight = height - 150\n\n        scrollWidget = ScrollWidget(parent=self._rootWidget, position=(20, columnPosY - _scrollHeight), size=(width - 40, _scrollHeight + 10))\n        columnWidget = ColumnWidget(parent=scrollWidget)\n\n        self._rootWidget.set(selectedChild=columnWidget)\n\n        self.selected = self.mod.own_rating or 2\n        for num, name in enumerate(self.levels):\n            s = bs.getSpecialChar(self.icons[num]) + name\n            w = TextWidget(parent=columnWidget, size=(width - 40, 24 + 8),\n                           maxWidth=width - 110,\n                           text=s,\n                           scale=0.85,\n                           hAlign='left', vAlign='center',\n                           alwaysHighlight=True,\n                           onSelectCall=bs.Call(self._select, num),\n                           onActivateCall=bs.Call(self._ok),\n                           selectable=True)\n            w.showBufferTop = 50\n            w.showBufferBottom = 50\n\n            if num == self.selected:\n                columnWidget.set(selectedChild=w, visibleChild=w)\n                self._rootWidget.set(selectedChild=w)\n            elif num == 4:\n                w.downWidget = b\n\n    def _select(self, index):\n        self.selected = index\n\n    def _cancel(self):\n        self._rootWidget.doTransition('outRight')\n\n    def _ok(self):\n        if not self._rootWidget.exists():\n            return\n        self._rootWidget.doTransition('outLeft')\n        self.onok(self.selected)\n\n\nclass QuitToApplyWindow(Window):\n\n    def __init__(self):\n        global quittoapply\n        if quittoapply is not None:\n            quittoapply.delete()\n            quittoapply = None\n        bs.playSound(bs.getSound('swish'))\n        text = \"Quit BS to reload mods?\"\n        if bs.getEnvironment()[\"platform\"] == \"android\":\n            text += \"\\n(On Android you have to close the activity)\"\n        self._rootWidget = quittoapply = ConfirmWindow(text, self._doFadeAndQuit).getRootWidget()\n\n    def _doFadeAndQuit(self):\n        # FIXME: using protected apis\n        bsInternal._fadeScreen(False, time=200, endCall=bs.Call(bs.quit, soft=True))\n        bsInternal._lockAllInput()\n        # unlock and fade back in shortly.. just in case something goes wrong\n        # (or on android where quit just backs out of our activity and we may come back)\n        bs.realTimer(300, bsInternal._unlockAllInput)\n        # bs.realTimer(300, bs.Call(bsInternal._fadeScreen,True))\n\n\nclass ModInfoWindow(Window):\n    def __init__(self, mod, modManagerWindow, originWidget=None):\n        # TODO: cleanup\n        self.modManagerWindow = modManagerWindow\n        self.mod = mod\n        s = 1.1 if gSmallUI else 1.27 if gMedUI else 1.57\n        bColor = (0.6, 0.53, 0.63)\n        bTextColor = (0.75, 0.7, 0.8)\n        width = 360 * s\n        height = 40 + 100 * s\n        if mod.author:\n            height += 25\n        if not mod.isLocal:\n            height += 50\n        if mod.rating is not None:\n            height += 50\n        if mod.downloads:\n            height += 50\n\n        buttons = sum([(mod.checkUpdate() or not mod.is_installed()), mod.is_installed(), mod.is_installed(), True])\n\n        color = (1, 1, 1)\n        textScale = 0.7 * s\n\n        # if they provided an origin-widget, scale up from that\n        if originWidget is not None:\n            self._transitionOut = 'outScale'\n            scaleOrigin = originWidget.getScreenSpaceCenter()\n            transition = 'inScale'\n        else:\n            self._transitionOut = None\n            scaleOrigin = None\n            transition = 'inRight'\n\n        self._rootWidget = ContainerWidget(size=(width, height), transition=transition,\n                                           scale=2.1 if gSmallUI else 1.5 if gMedUI else 1.0,\n                                           scaleOriginStackOffset=scaleOrigin)\n\n        pos = height * 0.9\n        labelspacing = height / (7.0 if (mod.rating is None and not mod.downloads) else 7.5)\n\n        if mod.tag:\n            TextWidget(parent=self._rootWidget, position=(width * 0.49, pos), size=(0, 0),\n                       hAlign=\"right\", vAlign=\"center\", text=mod.name, scale=textScale * 1.5,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            TextWidget(parent=self._rootWidget, position=(width * 0.51, pos - labelspacing * 0.1),\n                       hAlign=\"left\", vAlign=\"center\", text=mod.tag, scale=textScale * 0.9,\n                       color=(1, 0.3, 0.3), big=True, size=(0, 0))\n        else:\n            TextWidget(parent=self._rootWidget, position=(width * 0.5, pos), size=(0, 0),\n                       hAlign=\"center\", vAlign=\"center\", text=mod.name, scale=textScale * 1.5,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n\n        pos -= labelspacing\n\n        if mod.author:\n            TextWidget(parent=self._rootWidget, position=(width * 0.5, pos), size=(0, 0),\n                       hAlign=\"center\", vAlign=\"center\", text=\"by \" + mod.author, scale=textScale,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            pos -= labelspacing\n        if not mod.isLocal:\n            if mod.checkUpdate():\n                if mod.is_outdated():\n                    status = \"update available\"\n                else:\n                    status = \"unrecognized version\"\n            else:\n                status = \"installed\"\n            if not mod.is_installed():\n                status = \"not installed\"\n            TextWidget(parent=self._rootWidget, position=(width * 0.45, pos), size=(0, 0),\n                       hAlign=\"right\", vAlign=\"center\", text=\"Status:\", scale=textScale,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            status = TextWidget(parent=self._rootWidget, position=(width * 0.55, pos), size=(0, 0),\n                                hAlign=\"left\", vAlign=\"center\", text=status, scale=textScale,\n                                color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            pos -= labelspacing * 0.775\n\n        if mod.downloads:\n            TextWidget(parent=self._rootWidget, position=(width * 0.45, pos), size=(0, 0),\n                       hAlign=\"right\", vAlign=\"center\", text=\"Downloads:\", scale=textScale,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            TextWidget(parent=self._rootWidget, position=(width * 0.55, pos), size=(0, 0),\n                       hAlign=\"left\", vAlign=\"center\", text=str(mod.downloads), scale=textScale,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            pos -= labelspacing * 0.775\n\n        if mod.rating is not None:\n            TextWidget(parent=self._rootWidget, position=(width * 0.45, pos), size=(0, 0),\n                       hAlign=\"right\", vAlign=\"center\", text=\"Rating:\", scale=textScale,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            rating_str = bs.getSpecialChar(RateModWindow.icons[mod.rating]) + RateModWindow.levels[mod.rating]\n            TextWidget(parent=self._rootWidget, position=(width * 0.4725, pos), size=(0, 0),\n                       hAlign=\"left\", vAlign=\"center\", text=rating_str, scale=textScale,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            pos -= labelspacing * 0.775\n            submissions = \"({} {})\".format(mod.rating_submissions, \"submission\" if mod.rating_submissions < 2 else \"submissions\")\n            TextWidget(parent=self._rootWidget, position=(width * 0.4725, pos), size=(0, 0),\n                       hAlign=\"left\", vAlign=\"center\", text=submissions, scale=textScale,\n                       color=color, maxWidth=width * 0.9, maxHeight=height - 75)\n            pos += labelspacing * 0.3\n\n        if not mod.author and mod.isLocal:\n            pos -= labelspacing\n\n        if not (gSmallUI or gMedUI):\n            pos -= labelspacing * 0.25\n\n        pos -= labelspacing * 2.55\n\n        self.button_index = -1\n\n        def button_pos():\n            self.button_index += 1\n            d = {\n                1: [0.5],\n                2: [0.3, 0.7],\n                3: [0.2, 0.45, 0.8],\n                4: [0.17, 0.390, 0.61, 0.825],\n            }\n            x = width * d[buttons][self.button_index]\n            y = pos\n            sx, sy = button_size()\n            x -= sx / 2\n            y += sy / 2\n            return x, y\n\n        def button_size():\n            sx = {1: 100, 2: 80, 3: 80, 4: 75}[buttons] * s\n            sy = 40 * s\n            return sx, sy\n\n        def button_text_size():\n            return {1: 0.8, 2: 1.0, 3: 1.2, 4: 1.2}[buttons]\n\n        if mod.checkUpdate() or not mod.is_installed():\n            text = \"Download Mod\"\n            if mod.is_outdated():\n                text = \"Update Mod\"\n            elif mod.checkUpdate():\n                text = \"Reset Mod\"\n            self.downloadButton = ButtonWidget(parent=self._rootWidget,\n                                               position=button_pos(), size=button_size(),\n                                               onActivateCall=bs.Call(self._download,),\n                                               color=bColor,\n                                               autoSelect=True,\n                                               textColor=bTextColor,\n                                               buttonType='square',\n                                               textScale=button_text_size(),\n                                               label=text)\n\n        if mod.is_installed():\n            self.deleteButton = ButtonWidget(parent=self._rootWidget,\n                                             position=button_pos(), size=button_size(),\n                                             onActivateCall=bs.Call(self._delete),\n                                             color=bColor,\n                                             autoSelect=True,\n                                             textColor=bTextColor,\n                                             buttonType='square',\n                                             textScale=button_text_size(),\n                                             label=\"Delete Mod\")\n\n            self.rateButton = ButtonWidget(parent=self._rootWidget,\n                                           position=button_pos(), size=button_size(),\n                                           onActivateCall=bs.Call(self._rate),\n                                           color=bColor,\n                                           autoSelect=True,\n                                           textColor=bTextColor,\n                                           buttonType='square',\n                                           textScale=button_text_size(),\n                                           label=\"Rate Mod\" if mod.own_rating is None else \"Change Rating\")\n\n        okButtonSize = button_size()\n        okButtonPos = button_pos()\n        okText = bs.Lstr(resource='okText')\n        b = ButtonWidget(parent=self._rootWidget, autoSelect=True, position=okButtonPos, size=okButtonSize, label=okText, onActivateCall=self._ok)\n\n        self._rootWidget.onCancelCall = b.activate\n        self._rootWidget.selectedChild = b\n        self._rootWidget.startButton = b\n\n    def _ok(self):\n        self._rootWidget.doTransition('outLeft' if self._transitionOut is None else self._transitionOut)\n\n    def _delete(self):\n        DeleteModWindow(self.mod, self.modManagerWindow._cb_refresh)\n        self._ok()\n\n    def _download(self):\n        UpdateModWindow(self.mod, self.modManagerWindow._cb_refresh)\n        self._ok()\n\n    def _rate(self):\n\n        def submit_cb():\n            self.modManagerWindow._cb_refresh(force_fresh=True)\n\n        def cb(rating):\n            submit_mod_rating(self.mod, rating, submit_cb)\n\n        RateModWindow(self.mod, cb)\n        self._ok()\n\n\nclass SettingsWindow(Window):\n    def __init__(self, mod, modManagerWindow, originWidget=None):\n        self.modManagerWindow = modManagerWindow\n        self.mod = mod\n        s = 1.1 if gSmallUI else 1.27 if gMedUI else 1.57\n        bTextColor = (0.75, 0.7, 0.8)\n        width = 380 * s\n        height = 240 * s\n        textScale = 0.7 * s\n\n        # if they provided an origin-widget, scale up from that\n        if originWidget is not None:\n            self._transitionOut = 'outScale'\n            scaleOrigin = originWidget.getScreenSpaceCenter()\n            transition = 'inScale'\n        else:\n            self._transitionOut = None\n            scaleOrigin = None\n            transition = 'inRight'\n\n        self._rootWidget = ContainerWidget(size=(width, height), transition=transition,\n                                           scale=2.1 if gSmallUI else 1.5 if gMedUI else 1.0,\n                                           scaleOriginStackOffset=scaleOrigin)\n\n        self._titleText = TextWidget(parent=self._rootWidget, position=(0, height - 52),\n                                     size=(width, 30), text=\"ModManager Settings\", color=(1.0, 1.0, 1.0),\n                                     hAlign=\"center\", vAlign=\"top\", scale=1.5 * textScale)\n\n        pos = height * 0.65\n        TextWidget(parent=self._rootWidget, position=(width * 0.35, pos), size=(0, 40),\n                   hAlign=\"right\", vAlign=\"center\",\n                   text=\"Branch:\", scale=textScale,\n                   color=bTextColor, maxWidth=width * 0.9, maxHeight=(height - 75))\n        self.branch = TextWidget(parent=self._rootWidget, position=(width * 0.4, pos),\n                                 size=(width * 0.4, 40), text=config.get(\"branch\", \"master\"),\n                                 hAlign=\"left\", vAlign=\"center\",\n                                 editable=True, padding=4,\n                                 onReturnPressCall=self.setBranch)\n\n        pos -= height * 0.125\n        checkUpdatesValue = config.get(\"submit-download-statistics\", True)\n        self.downloadStats = CheckBoxWidget(parent=self._rootWidget, text=\"submit download statistics\",\n                                            position=(width * 0.2, pos), size=(170, 30),\n                                            textColor=(0.8, 0.8, 0.8),\n                                            value=checkUpdatesValue,\n                                            onValueChangeCall=self.setDownloadStats)\n\n        pos -= height * 0.125\n        checkUpdatesValue = config.get(\"auto-check-updates\", True)\n        self.checkUpdates = CheckBoxWidget(parent=self._rootWidget, text=\"automatically check for updates\",\n                                           position=(width * 0.2, pos), size=(170, 30),\n                                           textColor=(0.8, 0.8, 0.8),\n                                           value=checkUpdatesValue,\n                                           onValueChangeCall=self.setCheckUpdate)\n\n        pos -= height * 0.125\n        autoUpdatesValue = config.get(\"auto-update-old-mods\", True)\n        self.autoUpdates = CheckBoxWidget(parent=self._rootWidget, text=\"auto-update outdated mods\",\n                                          position=(width * 0.2, pos), size=(170, 30),\n                                          textColor=(0.8, 0.8, 0.8),\n                                          value=autoUpdatesValue,\n                                          onValueChangeCall=self.setAutoUpdate)\n        self.checkAutoUpdateState()\n\n        okButtonSize = (150, 50)\n        okButtonPos = (width * 0.5 - okButtonSize[0] / 2, 20)\n        okText = bs.Lstr(resource='okText')\n        okButton = ButtonWidget(parent=self._rootWidget, position=okButtonPos, size=okButtonSize, label=okText, onActivateCall=self._ok)\n\n        self._rootWidget.set(onCancelCall=okButton.activate, selectedChild=okButton, startButton=okButton)\n\n    def _ok(self):\n        if self.branch.text() != config.get(\"branch\", \"master\"):\n            self.setBranch()\n        self._rootWidget.doTransition('outLeft' if self._transitionOut is None else self._transitionOut)\n\n    def setBranch(self):\n        branch = self.branch.text()\n        if branch == '':\n            branch = \"master\"\n        bs.screenMessage(\"fetching branch '\" + branch + \"'\")\n\n        def cb(data, status_code):\n            newBranch = branch\n            if data:\n                bs.screenMessage('ok')\n            else:\n                bs.screenMessage('failed to fetch branch')\n                newBranch = \"master\"\n            bs.screenMessage(\"set branch to \" + newBranch)\n            config[\"branch\"] = newBranch\n            bs.writeConfig()\n            self.modManagerWindow._cb_refresh()\n\n        get_index(cb, branch=branch)\n\n    def setCheckUpdate(self, val):\n        config[\"auto-check-updates\"] = bool(val)\n        bs.writeConfig()\n        self.checkAutoUpdateState()\n\n    def checkAutoUpdateState(self):\n        if not self.checkUpdates.value:\n            # FIXME: properly disable checkbox\n            self.autoUpdates.set(value=False,\n                                 color=(0.65, 0.65, 0.65),\n                                 textColor=(0.65, 0.65, 0.65))\n        else:\n            # FIXME: match original color\n            autoUpdatesValue = config.get(\"auto-update-old-mods\", True)\n            self.autoUpdates.set(value=autoUpdatesValue,\n                                 color=(0.475, 0.6, 0.2),\n                                 textColor=(0.8, 0.8, 0.8))\n\n    def setAutoUpdate(self, val):\n        # FIXME: properly disable checkbox\n        if not self.checkUpdates.value:\n            bs.playSound(bs.getSound(\"error\"))\n            self.autoUpdates.value = False\n            return\n        config[\"auto-update-old-mods\"] = bool(val)\n        bs.writeConfig()\n\n    def setDownloadStats(self, val):\n        config[\"submit-download-statistics\"] = bool(val)\n        bs.writeConfig()\n\n\nclass Mod:\n    name = False\n    author = None\n    filename = None\n    base = None\n    changelog = []\n    old_md5s = []\n    url = False\n    isLocal = False\n    category = None\n    requires = []\n    supports = []\n    rating = None\n    rating_submissions = 0\n    own_rating = None\n    downloads = None\n    tag = None\n    data = None\n\n    def __init__(self, d):\n        self.data = d\n        self.author = d.get('author')\n        if 'filename' in d:\n            self.filename = d['filename']\n            self.base = self.filename[:-3]\n        else:\n            raise RuntimeError('mod without filename')\n        if 'name' in d:\n            self.name = d['name']\n        else:\n            self.name = self.filename\n        if 'md5' in d:\n            self.md5 = d['md5']\n        else:\n            raise RuntimeError('mod without md5')\n\n        self.changelog = d.get('changelog', [])\n        self.old_md5s = d.get('old_md5s', [])\n        self.category = d.get('category', None)\n        self.requires = d.get('requires', [])\n        self.supports = d.get('supports', [])\n        self.tag = d.get('tag', None)\n\n    def writeData(self, callback, doQuitWindow, data, status_code):\n        path = bs.getEnvironment()['userScriptsDirectory'] + \"/\" + self.filename\n\n        if data:\n            if self.is_installed():\n                os.rename(path, path + \".bak\")  # rename the old file to be able to recover it if something goes wrong\n            with open(path, 'w') as f:\n                f.write(data)\n        else:\n            bs.screenMessage(\"Failed to write mod\")\n\n        if callback:\n            callback(self, data is not None)\n        if doQuitWindow:\n            QuitToApplyWindow()\n\n        submit_download(self)\n\n    def install(self, callback, doQuitWindow=True):\n        def check_deps_and_install(mod=None, succeded=True):\n            if any([dep not in self._mods for dep in self.requires]):\n                raise Exception(\"dependency inconsistencies\")\n            if not all([self._mods[dep].up_to_date() for dep in self.requires]) or not succeded:\n                return\n\n            fetch_mod(self.data, partial(self.writeData, callback, doQuitWindow))\n        if len(self.requires) < 1:\n            check_deps_and_install()\n        else:\n            for dep in self.requires:\n                bs.screenMessage(self.name + \" requires \" + dep + \"; installing...\")\n                if not self._mods:\n                    raise Exception(\"missing mod._mods\")\n                if dep not in self._mods:\n                    raise Exception(\"dependency inconsistencies (missing \" + dep + \")\")\n                self._mods[dep].install(check_deps_and_install, False)\n\n    @property\n    def ownData(self):\n        path = bs.getEnvironment()['userScriptsDirectory'] + \"/\" + self.filename\n        if os.path.exists(path):\n            with open(path, \"r\") as ownFile:\n                return ownFile.read()\n\n    def delete(self, cb=None):\n        path = bs.getEnvironment()['userScriptsDirectory'] + \"/\" + self.filename\n        os.rename(path, path + \".bak\")  # rename the old file to be able to recover it if something goes wrong\n        if os.path.exists(path + \"c\"): # check for python bytecode\n            os.remove(path + \"c\")  # remove python bytecode because importing still works without .py file\n        if cb:\n            cb()\n\n    def checkUpdate(self):\n        if not self.is_installed():\n            return False\n        if self.local_md5() != self.md5:\n            return True\n        return False\n\n    def up_to_date(self):\n        return self.is_installed() and self.local_md5() == self.md5\n\n    def is_installed(self):\n        return os.path.exists(bs.getEnvironment()['userScriptsDirectory'] + \"/\" + self.filename)\n\n    def local_md5(self):\n        return md5(self.ownData).hexdigest()\n\n    def is_outdated(self):\n        if not self.old_md5s or not self.is_installed():\n            return False\n        local_md5 = self.local_md5()\n        for old_md5 in self.old_md5s:\n            if local_md5.startswith(old_md5):\n                return True\n        return False\n\n\nclass LocalMod(Mod):\n    isLocal = True\n\n    def __init__(self, filename):\n        self.filename = filename\n        self.base = self.filename[:-3]\n        self.name = filename + \" (Local Only)\"\n        with open(bs.getEnvironment()['userScriptsDirectory'] + \"/\" + filename, \"r\") as ownFile:\n            self.ownData = ownFile.read()\n\n    def checkUpdate(self):\n        return False\n\n    def is_installed(self):\n        return True\n\n    def up_to_date(self):\n        return True\n\n    def getData(self):\n        return False\n\n    def writeData(self, data=None):\n        bs.screenMessage(\"Can't update local-only mod!\")\n\n\n_setTabOld = StoreWindow._setTab\n\n\ndef _setTab(self, tab):\n    _setTabOld(self, tab)\n    if hasattr(self, \"_getMoreGamesButton\"):\n        if self._getMoreGamesButton.exists():\n            self._getMoreGamesButton.delete()\n    if tab == \"minigames\":\n        self._getMoreGamesButton = bs.buttonWidget(parent=self._rootWidget, autoSelect=True,\n                                                   label=bs.Lstr(resource='addGameWindow.getMoreGamesText'),\n                                                   color=(0.54, 0.52, 0.67),\n                                                   textColor=(0.7, 0.65, 0.7),\n                                                   onActivateCall=self._onGetMoreGamesPress,\n                                                   size=(178, 50), position=(70, 60))\n        # TODO: transitions\n\n\ndef _onGetMoreGamesPress(self):\n    if not self._modal:\n        bs.containerWidget(edit=self._rootWidget, transition='outLeft')\n    mm_window = ModManagerWindow(modal=self._modal, backLocationCls=self.__class__, showTab=\"minigames\")\n    if not self._modal:\n        uiGlobals['mainMenuWindow'] = mm_window.getRootWidget()\n\nStoreWindow._setTab = _setTab\nStoreWindow._onGetMoreGamesPress = _onGetMoreGamesPress\n"
  },
  {
    "path": "mods/puckDeathmatch.json",
    "content": "{\n  \"name\": \"Puck Deathmatch\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/puckDeathmatch.py",
    "content": "import bs\nimport bsHockey\n\nimport random\n\n\nclass PuckTouchedMessage(object):\n\tpass\n\nclass Puck(bsHockey.Puck):\n\tdef __init__(self, position, team):\n\t\tbsHockey.Puck.__init__(self, position)\n\t\tself.team = team\n\n\t\tself.tickrate = 100\n\t\tself._timeout = 5000 / self.tickrate\n\t\tself._count = self._timeout\n\t\tself._tickTimer = bs.Timer(self.tickrate, call=bs.WeakCall(self._tick), repeat=True)\n\t\tself._counter = bs.newNode('text', owner=self.node, attrs={'inWorld':True, 'color': (1, 1, 1, 0.7), 'scale': 0.015, 'shadow': 0.5, 'flatness': 1.0, 'hAlign':'center'})\n\t\tself.age = 0\n\t\tself.scored = False\n\t\tself.lastHoldingPlayer = None\n\t\tself.light = None\n\t\tself.movedSinceSpawn = False\n\n\tdef _tick(self):\n\t\tself.age += 1\n\t\tif self.node.exists():\n\t\t\tif sum([abs(v) for v in self.node.velocity]) > 0.5:\n\t\t\t\tif self.age > 3000 / self.tickrate:\n\t\t\t\t\tself.movedSinceSpawn = True\n\t\t\t\tself._count = self._timeout\n\t\t\t\tself._counter.text = ''\n\t\t\telse:\n\t\t\t\tself._count -= 1\n\t\t\t\tif self._count <= 10 * self.tickrate and self.movedSinceSpawn:\n\t\t\t\t\tt = self.node.position\n\t\t\t\t\tself._counter.position = (t[0], t[1]+1.0, t[2])\n\t\t\t\t\tself._counter.text = str(round(self._count * self.tickrate / 1000.0, 2))\n\t\t\t\t\tif self._count < 1:\n\t\t\t\t\t\tself.handleMessage(bs.OutOfBoundsMessage())\n\t\t\t\telse:\n\t\t\t\t\tself._counter.text = ''\n\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, PuckTouchedMessage):\n\t\t\tnode = bs.getCollisionInfo(\"opposingNode\")\n\t\t\t#bs.screenMessage(str(node.position))\n\t\t\t#node.sourcePlayer\n\t\t\tif node.sourcePlayer.getTeam() == self.team:\n\t\t\t\treturn\n\n\n\t\t\t#Score - isAlive to avoid multiple kills per death\n\t\t\tif 'notKilled' not in node.sourcePlayer.gameData:\n\t\t\t\tnode.sourcePlayer.gameData['notKilled'] = True\n\t\t\tif node.sourcePlayer.gameData['notKilled']:\n\t\t\t\t#node.sourcePlayer.getTeam().gameData['timesKilled'] += 1\n\t\t\t\tself.team.gameData['score'] += 1\n\t\t\t\tbs.getActivity()._updateScoreBoard()\n\t\t\tnode.sourcePlayer.gameData['notKilled'] = False\n\n\t\t\tx, y, z = node.position\n\t\t\tnode.handleMessage(\"impulse\", x, y, z,\n\t\t\t\t\t\t\t0, 0, 0, #velocity\n\t\t\t\t\t\t\t1000.0, 0, 3, 0,\n\t\t\t\t\t\t\t0, 0, 0) # forceDirection\n\t\t\tnode.frozen = True\n\t\t\tbs.gameTimer(1000, node.sourcePlayer.actor.shatter)\n\t\tif isinstance(m, bs.OutOfBoundsMessage):\n\t\t\tself.node.position = self._spawnPos\n\t\t\tself.movedSinceSpawn = False\n\t\t\tself.age = 0\n\t\telse:\n\t\t\tbsHockey.Puck.handleMessage(self, m)\n\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [PuckDeathMatch]\n\n\nclass PuckDeathMatch(bs.TeamGameActivity):\n\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Puck Deathmatch'\n\n\t@classmethod\n\tdef getScoreInfo(cls):\n\t\treturn {'scoreType':'points',\n\t\t\t\t'lowerIsBetter':False,\n\t\t\t\t'scoreName':'Score'}\n\n\t@classmethod\n\tdef getDescription(cls, sessionType):\n\t\treturn 'Kill everyone with your Puck'\n\n\t@classmethod\n\tdef getSupportedMaps(cls, sessionType):\n\t\treturn bs.getMapsSupportingPlayType(\"melee\")\n\n\t@classmethod\n\tdef supportsSessionType(cls, sessionType):\n\t\treturn True if (issubclass(sessionType, bs.TeamsSession)\n\t\t\t\t\t\tor issubclass(sessionType, bs.FreeForAllSession)) else False\n\n\t@classmethod\n\tdef getSettings(cls, sessionType):\n\t\treturn [(\"Kills to Win\", {'minValue': 1, 'default': 5, 'increment': 1})]\n\n\t# in the constructor we should load any media we need/etc.\n\t# but not actually create anything yet.\n\tdef __init__(self, settings):\n\t\tbs.TeamGameActivity.__init__(self, settings)\n\t\tself._winSound = bs.getSound(\"score\")\n\t\tself._cheerSound = bs.getSound(\"cheer\")\n\t\tself._chantSound = bs.getSound(\"crowdChant\")\n\t\tself._foghornSound = bs.getSound(\"foghorn\")\n\t\tself._swipSound = bs.getSound(\"swip\")\n\t\tself._whistleSound = bs.getSound(\"refWhistle\")\n\t\tself._puckModel = bs.getModel(\"puck\")\n\t\tself._puckTex = bs.getTexture(\"puckColor\")\n\t\tself._puckSound = bs.getSound(\"metalHit\")\n\n\t\tself._puckMaterial = bs.Material()\n\t\tself._puckMaterial.addActions(actions=( (\"modifyPartCollision\",\"friction\",0.1)))\n\t\tself._puckMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('pickupMaterial')),\n\t\t\t\t\t\t\t\t\t  actions=( (\"modifyPartCollision\",\"collide\",False) ) )\n\t\tself._puckMaterial.addActions(conditions=( (\"weAreYoungerThan\",100),'and',\n\t\t\t\t\t\t\t\t\t\t\t\t   (\"theyHaveMaterial\",bs.getSharedObject('objectMaterial')) ),\n\t\t\t\t\t\t\t\t\t  actions=( (\"modifyNodeCollision\",\"collide\",False) ) )\n\t\tself._puckMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('footingMaterial')),\n\t\t\t\t\t\t\t\t\t  actions=((\"impactSound\",self._puckSound,0.2,5)))\n\t\t# keep track of which player last touched the puck\n\t\tself._puckMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('playerMaterial')),\n\t\t\t\t\t\t\t\t\t  actions=((\"call\",\"atConnect\",self._handlePuckPlayerCollide),))\n\n\t\t# we want the puck to kill powerups; not get stopped by them\n\t\tself._puckMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.Powerup.getFactory().powerupMaterial),\n\t\t\t\t\t\t\t\t\t  actions=((\"modifyPartCollision\",\"physical\",False),\n\t\t\t\t\t\t\t\t\t\t\t   (\"message\",\"theirNode\",\"atConnect\",bs.DieMessage())))\n\n\n\n\t\t# dis is kill\n\t\tself._puckMaterial.addActions(conditions=(\"theyHaveMaterial\",bs.getSharedObject('playerMaterial')),\n\t\t\t\t\t\t\t\t\t  actions=((\"modifyPartCollision\",\"physical\",False),\n\t\t\t\t\t\t\t\t\t\t\t   (\"message\", \"ourNode\", \"atConnect\", PuckTouchedMessage())))\n\n\n\t\tself._scoreBoard = bs.ScoreBoard()\n\t\tself._killsToWin = self.settings['Kills to Win']\n\t\tself._scoreSound = bs.getSound(\"score\")\n\n\t\tself.pucks = []\n\n\t# called when our game is transitioning in but not ready to start..\n\t# ..we can go ahead and start creating stuff, playing music, etc.\n\tdef onTransitionIn(self):\n\t\tbs.TeamGameActivity.onTransitionIn(self, music='ToTheDeath')\n\n\t# called when our game actually starts\n\tdef onBegin(self):\n\t\tbs.TeamGameActivity.onBegin(self)\n\n\t\tself._won = False\n\t\t#for team in self.teams:\n\t\t#\tteam.gameData['timesKilled'] = 0\n\t\t#self._updateScoreBoard()\n\t\t#for team in self.teams:\n\t\t#\tself._spawnPuck(team.getID())\n\n\t\tself.setupStandardPowerupDrops()\n\n\tdef onPlayerJoin(self, player):\n\t\tself._spawnPuck(player.getTeam())\n\t\tself._updateScoreBoard()\n\t\tbs.TeamGameActivity.onPlayerJoin(self, player)\n\n\tdef onTeamJoin(self, team):\n\t\tteam.gameData['score'] = 0\n\t\tbs.TeamGameActivity.onTeamJoin(self, team)\n\n\t# called for each spawning player\n\tdef spawnPlayer(self, player):\n\t\t# lets spawn close to the center\n\t\t#spawnCenter = (1,4,0)\n\t\t#pos = (spawnCenter[0]+random.uniform(-1.5,1.5),spawnCenter[1],spawnCenter[2]+random.uniform(-1.5,1.5))\n\t\tpos = self.getMap().getStartPosition(player.getTeam().getID())\n\t\tspaz = self.spawnPlayerSpaz(player, position=pos)\n\n\t\tspaz.connectControlsToPlayer(enablePunch=True,\n\t\t\t\t\t\t\t\t\t enableBomb=False,\n\t\t\t\t\t\t\t\t\t enablePickUp=True)\n\t\tplayer.gameData['notKilled'] = True\n\n\tdef _flashPuckSpawn(self, pos):\n\t\tlight = bs.newNode('light',\n\t\t\t\t\t\t   attrs={'position': pos,\n\t\t\t\t\t\t\t\t  'heightAttenuated':False,\n\t\t\t\t\t\t\t\t  'color': (1, 0, 0)})\n\t\tbs.animate(light, 'intensity', {0: 0, 250: 1, 500: 0}, loop=True)\n\t\tbs.gameTimer(1000, light.delete)\n\n\tdef _spawnPuck(self, team):\n\t\tpuckPos = self.getMap().getStartPosition(team.getID())\n\t\tlightcolor = team.color\n\t\tbs.playSound(self._swipSound)\n\t\tbs.playSound(self._whistleSound)\n\t\tself._flashPuckSpawn(puckPos)\n\n\t\tpuck = Puck(position=puckPos, team=team)\n\t\tpuck.light = bs.newNode('light',\n\t\t\t\t\t\t\t\towner=puck.node,\n\t\t\t\t\t\t\t\tattrs={'intensity':0.3,\n\t\t\t\t\t\t\t\t\t\t'heightAttenuated':False,\n\t\t\t\t\t\t\t\t\t\t'radius':0.2,\n\t\t\t\t\t\t\t\t\t\t'color': lightcolor})\n\t\tpuck.node.connectAttr('position', puck.light, 'position')\n\t\tself.pucks.append(puck)\n\n\tdef _handlePuckPlayerCollide(self):\n\t\ttry:\n\t\t\tpuckNode, playerNode = bs.getCollisionInfo('sourceNode', 'opposingNode')\n\t\t\tpuck = puckNode.getDelegate()\n\t\t\tplayer = playerNode.getDelegate().getPlayer()\n\t\texcept Exception:\n\t\t\tplayer = puck = None\n\n\t\tif player is not None and player.exists() and puck is not None:\n\t\t\tpuck.lastPlayersToTouch[player.getTeam().getID()] = player\n\n\n\tdef _checkIfWon(self):\n\t\t# simply end the game if there's no living bots..\n\t\tfor team in self.teams:\n\t\t\tif team.gameData['score'] >= self._killsToWin:\n\t\t\t\tself._won = True\n\t\t\t\tself.endGame()\n\n\tdef _updateScoreBoard(self):\n\t\tfor team in self.teams:\n\t\t\tself._scoreBoard.setTeamValue(team, team.gameData['score'], self._killsToWin)\n\t\tself._checkIfWon()\n\n\n\n\t# called for miscellaneous events\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, bs.PlayerSpazDeathMessage):\n\t\t\tbs.TeamGameActivity.handleMessage(self, m) # do standard stuff\n\t\t\tself.respawnPlayer(m.spaz.getPlayer()) # kick off a respawn\n\n\t\telse:\n\t\t\t# let the base class handle anything we don't..\n\t\t\tbs.TeamGameActivity.handleMessage(self, m)\n\n\t# when this is called, we should fill out results and end the game\n\t# *regardless* of whether is has been won. (this may be called due\n\t# to a tournament ending or other external reason)\n\tdef endGame(self):\n\t\tresults = bs.TeamGameResults()\n\t\tfor team in self.teams:\n\t\t\tresults.setTeamScore(team, team.gameData['score'])\n\t\tself.end(results=results)\n"
  },
  {
    "path": "mods/quickGameButton.json",
    "content": "{\n  \"name\": \"Quick-Game Button\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"utilities\"\n}\n"
  },
  {
    "path": "mods/quickGameButton.py",
    "content": "import bs\nimport bsInternal\nimport bsTeamGame\nfrom bsUI import PlayWindow, AddGameWindow, gSmallUI, gMedUI, gTitleColor, uiGlobals, gWindowStates\nimport bsUtils\n\n_supports_auto_reloading = True\n_auto_reloader_type = \"patching\"\nPlayWindow__init__ = PlayWindow.__init__\nPlayWindow_save_state = PlayWindow._save_state\nPlayWindow_restore_state = PlayWindow._restore_state\n\n\ndef _prepare_reload():\n    PlayWindow.__init__ = PlayWindow__init__\n    PlayWindow._save_state = PlayWindow_save_state\n    PlayWindow._restore_state = PlayWindow_restore_state\n\n# TODO: support other gametypes than free-for-all\n\nif \"quickGameButton\" in bs.getConfig():\n    config = bs.getConfig()[\"quickGameButton\"]\nelse:\n    config = {\"selected\": None, \"config\": None}\n    bs.getConfig()[\"quickGameButton\"] = config\n    bs.writeConfig()\n\n\ndef startGame(session, fadeout=True):\n    def callback():\n        if fadeout:\n            bsInternal._unlockAllInput()\n        try:\n            bsInternal._newHostSession(session)\n        except Exception:\n            import bsMainMenu\n            bs.printException(\"exception running session\", session)\n            # drop back into a main menu session..\n            bsInternal._newHostSession(bsMainMenu.MainMenuSession)\n\n    if fadeout:\n        bsInternal._fadeScreen(False, time=250, endCall=callback)\n        bsInternal._lockAllInput()\n    else:\n        callback()\n\n\nclass SimplePlaylist(object):\n    def __init__(self, settings, gameType):\n        self.settings = settings\n        self.gameType = gameType\n\n    def pullNext(self):\n        if \"map\" not in self.settings[\"settings\"]:\n            settings = dict(map=self.settings[\"map\"], **self.settings[\"settings\"])\n        else:\n            settings = self.settings[\"settings\"]\n        return dict(resolvedType=self.gameType, settings=settings)\n\n\nclass CustomSession(bsTeamGame.FreeForAllSession):\n    def __init__(self, *args, **kwargs):\n        self._useTeams = False\n        self._tutorialActivityInstance = None\n        bs.Session.__init__(self, teamNames=None,\n                            teamColors=None,\n                            useTeamColors=False,\n                            minPlayers=1,\n                            maxPlayers=self.getMaxPlayers())\n\n        self._haveShownControlsHelpOverlay = False\n\n        self._seriesLength = 1\n        self._ffaSeriesLength = 1\n\n        # which game activity we're on\n        self._gameNumber = 0\n        self._playlist = SimplePlaylist(self._config, self._gameType)\n        config[\"selected\"] = self._gameType.__name__\n        config[\"config\"] = self._config\n        bs.writeConfig()\n\n        # get a game on deck ready to go\n        self._currentGameSpec = None\n        self._nextGameSpec = self._playlist.pullNext()\n        self._nextGame = self._nextGameSpec[\"resolvedType\"]\n\n        # go ahead and instantiate the next game we'll use so it has lots of time to load\n        self._instantiateNextGame()\n\n        # start in our custom join screen\n        self.setActivity(bs.newActivity(bsTeamGame.TeamJoiningActivity))\n\n\nclass SelectGameWindow(AddGameWindow):\n    def __init__(self, transition='inRight'):\n        class EditSession:\n            _sessionType = bs.FreeForAllSession\n\n            def getSessionType(self): return self._sessionType\n\n        self._editSession = EditSession()\n        self._width = 650\n        self._height = 346 if gSmallUI else 380 if gMedUI else 440\n        topExtra = 30 if gSmallUI else 20\n\n        self._scrollWidth = 210\n\n        self._rootWidget = bs.containerWidget(size=(self._width, self._height+topExtra), transition=transition,\n                                              scale=2.17 if gSmallUI else 1.5 if gMedUI else 1.0,\n                                              stackOffset=(0, 1) if gSmallUI else (0, 0))\n\n        self._backButton = bs.buttonWidget(parent=self._rootWidget, position=(58, self._height-53),\n                                           size=(165, 70), scale=0.75, textScale=1.2, label=bs.Lstr(resource='backText'),\n                                           autoSelect=True,\n                                           buttonType='back', onActivateCall=self._back)\n        self._selectButton = selectButton = bs.buttonWidget(parent=self._rootWidget, position=(self._width-172, self._height-50),\n                                                            autoSelect=True, size=(160, 60), scale=0.75, textScale=1.2,\n                                                            label=bs.Lstr(resource='selectText'), onActivateCall=self._add)\n        bs.textWidget(parent=self._rootWidget, position=(self._width*0.5, self._height-28), size=(0, 0), scale=1.0,\n                      text=\"Select Game\", hAlign='center', color=gTitleColor, maxWidth=250, vAlign='center')\n        v = self._height - 64\n\n        self._selectedTitleText = bs.textWidget(parent=self._rootWidget, position=(self._scrollWidth+50+30, v-15), size=(0, 0),\n                                                scale=1.0, color=(0.7, 1.0, 0.7, 1.0), maxWidth=self._width-self._scrollWidth-150,\n                                                hAlign='left', vAlign='center')\n        v -= 30\n\n        self._selectedDescriptionText = bs.textWidget(parent=self._rootWidget, position=(self._scrollWidth+50+30, v), size=(0, 0),\n                                                      scale=0.7, color=(0.5, 0.8, 0.5, 1.0), maxWidth=self._width-self._scrollWidth-150,\n                                                      hAlign='left')\n\n        scrollHeight = self._height-100\n\n        v = self._height - 60\n\n        self._scrollWidget = bs.scrollWidget(parent=self._rootWidget, position=(61, v-scrollHeight), size=(self._scrollWidth, scrollHeight))\n        bs.widget(edit=self._scrollWidget, upWidget=self._backButton, leftWidget=self._backButton, rightWidget=selectButton)\n        self._column = None\n\n        v -= 35\n        bs.containerWidget(edit=self._rootWidget, cancelButton=self._backButton, startButton=selectButton)\n        self._selectedGameType = None\n\n        bs.containerWidget(edit=self._rootWidget, selectedChild=self._scrollWidget)\n\n        self._refresh()\n        if config[\"selected\"]:\n            for gt in bsUtils.getGameTypes():\n                if not gt.supportsSessionType(self._editSession._sessionType):\n                    continue\n                if gt.__name__ == config[\"selected\"]:\n                    self._refresh(selected=gt)\n                    self._setSelectedGameType(gt)\n\n    def _refresh(self, selectGetMoreGamesButton=False, selected=None):\n\n        if self._column is not None:\n            self._column.delete()\n\n        self._column = bs.columnWidget(parent=self._scrollWidget)\n        gameTypes = [gt for gt in bsUtils.getGameTypes() if gt.supportsSessionType(self._editSession._sessionType)]\n        # sort in this language\n        gameTypes.sort(key=lambda g: g.getDisplayString())\n\n        for i, gameType in enumerate(gameTypes):\n            t = bs.textWidget(parent=self._column, position=(0, 0), size=(self._width-88, 24), text=gameType.getDisplayString(),\n                              hAlign=\"left\", vAlign=\"center\",\n                              color=(0.8, 0.8, 0.8, 1.0),\n                              maxWidth=self._scrollWidth*0.8,\n                              onSelectCall=bs.Call(self._setSelectedGameType, gameType),\n                              alwaysHighlight=True,\n                              selectable=True, onActivateCall=bs.Call(bs.realTimer, 100, self._selectButton.activate))\n            if i == 0:\n                bs.widget(edit=t, upWidget=self._backButton)\n            if gameType == selected:\n                bs.containerWidget(edit=self._column, selectedChild=t, visibleChild=t)\n\n        self._getMoreGamesButton = bs.buttonWidget(parent=self._column, autoSelect=True,\n                                                   label=bs.Lstr(resource='addGameWindow.getMoreGamesText'),\n                                                   color=(0.54, 0.52, 0.67),\n                                                   textColor=(0.7, 0.65, 0.7),\n                                                   onActivateCall=self._onGetMoreGamesPress,\n                                                   size=(178, 50))\n        if selectGetMoreGamesButton:\n            bs.containerWidget(edit=self._column, selectedChild=self._getMoreGamesButton,\n                               visibleChild=self._getMoreGamesButton)\n\n    def _add(self):\n        bsInternal._lockAllInput()  # make sure no more commands happen\n        bs.realTimer(100, bsInternal._unlockAllInput)\n        gameconfig = {}\n        if config[\"selected\"] == self._selectedGameType.__name__:\n            if config[\"config\"]:\n                gameconfig = config[\"config\"]\n        if \"map\" in gameconfig:\n            gameconfig[\"settings\"][\"map\"] = gameconfig.pop(\"map\")\n        self._selectedGameType.createConfigUI(self._editSession._sessionType, gameconfig, self.onEditGameDone)\n\n    def onEditGameDone(self, config):\n        if config:\n            CustomSession._config = config\n            CustomSession._gameType = self._selectedGameType\n            startGame(CustomSession)\n        else:\n            bs.containerWidget(edit=uiGlobals[\"mainMenuWindow\"], transition='outRight')\n            uiGlobals[\"mainMenuWindow\"] = SelectGameWindow(transition=\"inLeft\").getRootWidget()\n\n    def _back(self):\n        bs.containerWidget(edit=self._rootWidget, transition='outRight')\n        uiGlobals[\"mainMenuWindow\"] = PlayWindow(transition=\"inLeft\").getRootWidget()\n\n\noldInit = PlayWindow.__init__\n\n\ndef newInit(self, *args, **kwargs):\n    oldInit(self, *args, **kwargs)\n\n    width = 800\n    height = 550\n\n    def doQuickGame():\n        self._save_state()\n        uiGlobals[\"mainMenuWindow\"] = SelectGameWindow().getRootWidget()\n        bs.containerWidget(edit=self._rootWidget, transition='outLeft')\n\n    self._quickGameButton = bs.buttonWidget(parent=self._rootWidget, autoSelect=True,\n                                            position=(width - 55 - 120, height - 132), size=(120, 60),\n                                            scale=1.1, textScale=1.2,\n                                            label=\"custom...\", onActivateCall=doQuickGame,\n                                            color=(0.54, 0.52, 0.67),\n                                            textColor=(0.7, 0.65, 0.7))\n    self._restore_state()\n\nPlayWindow.__init__ = newInit\n\n\ndef states(self):\n    return {\n        \"Team Games\": self._teamsButton,\n        \"Co-op Games\": self._coopButton,\n        \"Free-for-All Games\": self._freeForAllButton,\n        \"Back\": self._backButton,\n        \"Quick Game\": self._quickGameButton\n    }\n\n\ndef _save_state(self):\n    swapped = {v: k for k, v in states(self).items()}\n    if self._rootWidget.getSelectedChild() in swapped:\n        gWindowStates[self.__class__.__name__] = swapped[self._rootWidget.getSelectedChild()]\n    else:\n        print(\"error saving state for \", self.__class__, self._rootWidget.getSelectedChild())\nPlayWindow._save_state = _save_state\n\n\ndef _restore_state(self):\n    if not hasattr(self, \"_quickGameButton\"):\n        return  # ensure that our monkey patched init ran\n    if self.__class__.__name__ not in gWindowStates:\n        bs.containerWidget(edit=self._rootWidget, selectedChild=self._coopButton)\n        return\n    sel = states(self).get(gWindowStates[self.__class__.__name__], None)\n    if sel:\n        bs.containerWidget(edit=self._rootWidget, selectedChild=sel)\n    else:\n        bs.containerWidget(edit=self._rootWidget, selectedChild=self._coopButton)\n        print('error restoring state (', gWindowStates[self.__class__.__name__], ') for', self.__class__)\nPlayWindow._restore_state = _restore_state\n"
  },
  {
    "path": "mods/settings_patcher.json",
    "content": "{\n  \"name\": \"settings_patcher\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"libraries\"\n}\n"
  },
  {
    "path": "mods/settings_patcher.py",
    "content": "import bs\nfrom bsUI import SettingsWindow, gSmallUI, gMedUI, gTitleColor, gDoAndroidNav, gWindowStates\n\ntry:\n    from ui_wrappers import TextWidget, ButtonWidget, ImageWidget\nexcept ImportError:\n    bs.screenMessage(\"ui_wrappers missing\", color=(1, 0, 0))\n    raise\n\n\nclass SettingsButton:\n    def __init__(self, id, text=None, icon=None, iconColor=None, sorting_position=None):\n        self.id = id\n        self.text = text\n        self.icon = icon\n        self.iconColor = iconColor\n        self.sorting_position = sorting_position\n        self.textOnly = icon is None\n        self._cb = lambda x: None\n        self._buttonInstance = None\n        self.instanceLocals = {}\n\n    def setText(self, text):\n        self.text = text\n        return self\n\n    def setCallback(self, cb):\n        self._cb = cb\n        return self\n\n    def add(self):\n        buttons.append(self)\n        return self\n\n    def remove(self):\n        buttons.remove(self)\n        return self\n\n    def setLocals(self, swinstance=None, **kwargs):\n        self.instanceLocals.update(kwargs)\n        if swinstance:\n            if \"button\" in self.instanceLocals:\n                setattr(swinstance, self.instanceLocals[\"button\"], self._buttonInstance)\n        return self\n\n    def x(self, swinstance, index, bw, wmodsmallui=0.4, wmod=0.2):\n        if self.icon:\n            layout = iconbuttonlayouts[sum([b.icon is not None for b in buttons])]\n        else:\n            layout = textbuttonlayouts[sum([b.textOnly for b in buttons])]\n        bw += wmodsmallui if gSmallUI else wmod\n        for i in range(len(layout) + 1):\n            if sum(layout[:i]) > index:\n                row = i - 1\n                pos = index - sum(layout[:i - 1])\n                return swinstance._width / 2 + bw * (pos - layout[row] / 2.0) * (1.0 if self.icon else 1.05)\n\n    def _create_icon_button(self, swinstance, index):\n        width, height = swinstance._width, swinstance._gOnlyHeight\n        layout = iconbuttonlayouts[sum([b.icon is not None for b in buttons])]\n        bw = width / (max(layout) + (0.4 if gSmallUI else 0.2))\n        bwx = bw\n        bh = height / (len(layout) + (0.5 if gSmallUI else 0.4))\n        # try to keep it squared\n        if abs(1 - bw / bh) > 0.1:\n            bwx *= (bh / bw - 1) / 2 + 1\n            bw = bh = min(bw, bh)\n\n        for i in range(len(layout) + 1):\n            if sum(layout[:i]) > index:\n                row = i - 1\n                break\n\n        x = self.x(swinstance, index, bwx)\n        y = swinstance._height - 95 - (row + 0.8) * (bh - 10)\n\n        button = ButtonWidget(parent=swinstance._rootWidget, autoSelect=True,\n                              position=(x, y), size=(bwx, bh), buttonType='square',\n                              label='')\n        button.onActivateCall = lambda: self._cb(swinstance)\n\n        x += (bwx - bw) / 2\n        TextWidget(parent=swinstance._rootWidget, text=self.text,\n                   position=(x + bw * 0.47, y + bh * 0.22),\n                   maxWidth=bw * 0.7, size=(0, 0), hAlign='center', vAlign='center',\n                   drawController=button,\n                   color=(0.7, 0.9, 0.7, 1.0))\n\n        iw, ih = bw * 0.65, bh * 0.65\n        i = ImageWidget(parent=swinstance._rootWidget, position=(x + bw * 0.49 - iw * 0.5, y + 43),\n                        size=(iw, ih), texture=bs.getTexture(self.icon),\n                        drawController=button)\n        if self.iconColor:\n            i.color = self.iconColor\n        self._buttonInstance = button._instance\n        return x, y\n\n    def _create_text_button(self, swinstance, index, start_y):\n        width, height = swinstance._width, swinstance._height - swinstance._gOnlyHeight\n        layout = textbuttonlayouts[sum([b.textOnly for b in buttons])]\n        bw = width / (max(layout) + (0.7 if gSmallUI else 0.4))\n        bh = height / len(layout)\n\n        for i in range(len(layout) + 1):\n            if sum(layout[:i]) > index:\n                row = i - 1\n                break\n\n        x = self.x(swinstance, index, bw, 0.7, 0.4)\n        y = start_y - (row + 1) * bh\n\n        button = ButtonWidget(parent=swinstance._rootWidget, autoSelect=True,\n                              position=(x, y), size=(bw, bh),\n                              label=self.text, color=ButtonWidget.COLOR_GREY,\n                              textColor=ButtonWidget.TEXTCOLOR_GREY)\n        button.onActivateCall = lambda: self._cb(swinstance)\n\n        self._buttonInstance = button._instance\n        return x, y\n\n\nbuttons = []\niconbuttonlayouts = {\n    0: [],\n    1: [1],\n    2: [2],\n    3: [2, 1],\n    4: [2, 2],\n    5: [3, 2],\n    6: [3, 3],\n    7: [4, 3],\n    8: [3, 3, 2],\n    9: [3, 3, 3]\n}\ntextbuttonlayouts = {\n    1: [1],\n    2: [2],\n    3: [3],\n    4: [2, 2],\n    5: [3, 2],\n    6: [3, 3],\n    7: [4, 3]\n}\n\nif hasattr(SettingsWindow, \"_doProfiles\"):\n    SettingsButton(id=\"Profiles\", icon=\"cuteSpaz\") \\\n        .setCallback(lambda swinstance: swinstance._doProfiles()) \\\n        .setText(bs.Lstr(resource='settingsWindow.playerProfilesText')) \\\n        .add()\n\nif hasattr(SettingsWindow, \"_doControllers\"):\n    SettingsButton(id=\"Controllers\", icon=\"controllerIcon\") \\\n        .setCallback(lambda swinstance: swinstance._doControllers()) \\\n        .setText(bs.Lstr(resource='settingsWindow.controllersText')) \\\n        .setLocals(button=\"_controllersButton\") \\\n        .add()\n\nif hasattr(SettingsWindow, \"_doGraphics\"):\n    SettingsButton(id=\"Graphics\", icon=\"graphicsIcon\") \\\n        .setCallback(lambda swinstance: swinstance._doGraphics()) \\\n        .setText(bs.Lstr(resource='settingsWindow.graphicsText')) \\\n        .setLocals(button=\"_graphicsButton\") \\\n        .add()\n\nif hasattr(SettingsWindow, \"_doAudio\"):\n    SettingsButton(id=\"Audio\", icon=\"audioIcon\", iconColor=(1, 1, 0)) \\\n        .setCallback(lambda swinstance: swinstance._doAudio()) \\\n        .setText(bs.Lstr(resource='settingsWindow.audioText')) \\\n        .setLocals(button=\"_audioButton\") \\\n        .add()\n\nif hasattr(SettingsWindow, \"_doAdvanced\"):\n    SettingsButton(id=\"Advanced\", icon=\"advancedIcon\", iconColor=(0.8, 0.95, 1)) \\\n        .setCallback(lambda swinstance: swinstance._doAdvanced()) \\\n        .setText(bs.Lstr(resource='settingsWindow.advancedText')) \\\n        .setLocals(button=\"_advancedButton\") \\\n        .add()\n\nfor i, button in enumerate(buttons):\n    button.sorting_position = i\n\n\ndef newInit(self, transition='inRight', originWidget=None):\n    if originWidget is not None:\n        self._transitionOut = 'outScale'\n        scaleOrigin = originWidget.getScreenSpaceCenter()\n        transition = 'inScale'\n    else:\n        self._transitionOut = 'outRight'\n        scaleOrigin = None\n\n    width = 600 if gSmallUI else 600\n    height = 360 if gSmallUI else 435\n    self._gOnlyHeight = height\n    if any([b.textOnly for b in buttons]):\n        if len(textbuttonlayouts[sum([b.textOnly for b in buttons])]) > 1:\n            height += 80 if gSmallUI else 120\n        else:\n            height += 60 if gSmallUI else 80\n    self._width, self._height = width, height\n\n    R = bs.Lstr(resource='settingsWindow')\n\n    topExtra = 20 if gSmallUI else 0\n    if originWidget is not None:\n        self._rootWidget = bs.containerWidget(size=(width, height+topExtra), transition=transition,\n                                              scaleOriginStackOffset=scaleOrigin,\n                                              scale=1.75 if gSmallUI else 1.35 if gMedUI else 1.0,\n                                              stackOffset=(0, -8) if gSmallUI else (0, 0))\n    else:\n        self._rootWidget = bs.containerWidget(size=(width, height+topExtra), transition=transition,\n                                              scale=1.75 if gSmallUI else 1.35 if gMedUI else 1.0,\n                                              stackOffset=(0, -8) if gSmallUI else (0, 0))\n\n    self._backButton = b = bs.buttonWidget(parent=self._rootWidget, autoSelect=True, position=(40, height-55),\n                                           size=(130, 60), scale=0.8, textScale=1.2, label=bs.Lstr(resource='backText'),\n                                           buttonType='back', onActivateCall=self._doBack)\n    bs.containerWidget(edit=self._rootWidget, cancelButton=b)\n\n    t = bs.textWidget(parent=self._rootWidget, position=(0, height-44), size=(width, 25),\n                      text=bs.Lstr(resource='settingsWindow.titleText'), color=gTitleColor,\n                      hAlign=\"center\", vAlign=\"center\", maxWidth=130)\n\n    if gDoAndroidNav:\n        bs.buttonWidget(edit=b, buttonType='backSmall', size=(60, 60), label=bs.getSpecialChar('logoFlat'))\n        bs.textWidget(edit=t, hAlign='left', position=(93, height-44))\n\n    icon_buttons = sorted([b for b in buttons if b.icon], key=lambda b: b.sorting_position)\n    for i, button in enumerate(icon_buttons):\n        x, y = button._create_icon_button(self, i)\n    text_buttons = sorted([b for b in buttons if b.textOnly], key=lambda b: b.sorting_position)\n    for i, button in enumerate(text_buttons):\n        button._create_text_button(self, i, y)\n\n    for button in buttons:\n        button.setLocals(self)\n\n    self._restoreState()\n\nSettingsWindow.__init__ = newInit\n\n\ndef statedict(self):\n    d = {button._buttonInstance: button.id for button in buttons}\n    d.update({self._backButton: \"Back\"})\n    return d\n\n\ndef _saveState(self):\n    w = self._rootWidget.getSelectedChild()\n    for k, v in statedict(self).items():\n        if w == k:\n            gWindowStates[self.__class__.__name__] = {'selName': v}\n            return\n    bs.printError('error saving state for ' + str(self.__class__))\nSettingsWindow._saveState = _saveState\n\n\ndef _restoreState(self):\n    sel = None\n    if self.__class__.__name__ in gWindowStates and 'selName' in gWindowStates[self.__class__.__name__]:\n        selName = gWindowStates[self.__class__.__name__]['selName']\n        for k, v in statedict(self).items():\n            if selName == v:\n                sel = k\n    sel = sel or buttons[0]._buttonInstance\n    bs.containerWidget(edit=self._rootWidget, selectedChild=sel)\nSettingsWindow._restoreState = _restoreState\n"
  },
  {
    "path": "mods/smash.json",
    "content": "{\n  \"name\": \"Super Smash\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/smash.py",
    "content": "import random\nimport bs\nimport bsUtils\nimport bsElimination\nimport bsBomb\n\nclass Icon(bsElimination.Icon):\n\tdef updateForLives(self):\n\t\tif self._player.exists():\n\t\t\tlives = self._player.gameData['lives']\n\t\telse: lives = 0\n\t\tif self._showLives:\n\t\t\tif lives > 0:\n\t\t\t\tself._livesText.text = 'x' + str(lives - 1)\n\t\t\telif lives < 0:\n\t\t\t\tself._livesText.text = str(lives)\n\t\t\telse:\n\t\t\t\tself._livesText.text = ''\n\t\tif lives == 0:\n\t\t\tif hasattr(bs.getActivity(), 'timeLimitOnly'):\n\t\t\t\tif not bs.getActivity().timeLimitOnly:\n\t\t\t\t\tself._nameText.opacity = 0.2\n\t\t\t\t\tself.node.color = (0.7, 0.3, 0.3)\n\t\t\t\t\tself.node.opacity = 0.2\n\nclass PowBox(bsBomb.Bomb):\n\tdef __init__(self, position=(0, 1, 0), velocity=(0, 0, 0)):\n\t\tbsBomb.Bomb.__init__(self, position, velocity,\n\t\t\t\t\t\tbombType='tnt', blastRadius=2.5,\n\t\t\t\t\t\tsourcePlayer=None, owner=None)\n\t\tself.setPowText()\n\n\n\tdef setPowText(self, color=(1, 1, 0.4)):\n\t\tm = bs.newNode('math', owner=self.node, attrs={'input1': (0, 0.7, 0), 'operation': 'add'})\n\t\tself.node.connectAttr('position', m, 'input2')\n\t\tself._powText = bs.newNode('text',\n\t\t\t\t\t\t\t\t\t  owner=self.node,\n\t\t\t\t\t\t\t\t\t  attrs={'text':'POW!',\n\t\t\t\t\t\t\t\t\t\t\t 'inWorld':True,\n\t\t\t\t\t\t\t\t\t\t\t 'shadow':1.0,\n\t\t\t\t\t\t\t\t\t\t\t 'flatness':1.0,\n\t\t\t\t\t\t\t\t\t\t\t 'color':color,\n\t\t\t\t\t\t\t\t\t\t\t 'scale':0.0,\n\t\t\t\t\t\t\t\t\t\t\t 'hAlign':'center'})\n\t\tm.connectAttr('output', self._powText, 'position')\n\t\tbs.animate(self._powText, 'scale', {0: 0.0, 1000: 0.01})\n\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, bs.PickedUpMessage):\n\t\t\tself._heldBy = m.node\n\t\telif isinstance(m, bs.DroppedMessage):\n\t\t\tbs.animate(self._powText, 'scale', {0:0.01, 600: 0.03})\n\t\t\tbs.gameTimer(600, bs.WeakCall(self.pow))\n\t\tbsBomb.Bomb.handleMessage(self, m)\n\n\tdef pow(self):\n\t\tself.explode()\n\n\nclass PlayerSpaz_Smash(bs.PlayerSpaz):\n\tmultiplyer = 1\n\tisDead = False\n\n\t#def __init__(self, *args, **kwargs):\n\t#\tsuper(self.__class__, self).init(*args, **kwargs)\n\t#\tself.multiplyer = 0\n\n\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, bs.HitMessage):\n\t\t\tif not self.node.exists():\n\t\t\t\treturn\n\t\t\tif self.node.invincible == True:\n\t\t\t\tbs.playSound(self.getFactory().blockSound, 1.0, position=self.node.position)\n\t\t\t\treturn True\n\n\t\t\t# if we were recently hit, don't count this as another\n\t\t\t# (so punch flurries and bomb pileups essentially count as 1 hit)\n\t\t\tgameTime = bs.getGameTime()\n\t\t\tif self._lastHitTime is None or gameTime - self._lastHitTime > 1000:\n\t\t\t\tself._numTimesHit += 1\n\t\t\t\tself._lastHitTime = gameTime\n\n\t\t\tmag = m.magnitude * self._impactScale\n\t\t\tvelocityMag = m.velocityMagnitude * self._impactScale\n\n\t\t\tdamageScale = 0.22\n\n\t\t\t# if they've got a shield, deliver it to that instead..\n\t\t\tif self.shield is not None:\n\t\t\t\tif m.flatDamage:\n\t\t\t\t\tdamage = m.flatDamage * self._impactScale\n\t\t\t\telse:\n\t\t\t\t\t# hit our spaz with an impulse but tell it to only return theoretical damage; not apply the impulse..\n\t\t\t\t\tself.node.handleMessage(\"impulse\", m.pos[0], m.pos[1], m.pos[2],\n\t\t\t\t\t\t\t\t\t\t\tm.velocity[0], m.velocity[1], m.velocity[2],\n\t\t\t\t\t\t\t\t\t\t\tmag, velocityMag, m.radius, 1,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[0], m.forceDirection[1], m.forceDirection[2])\n\t\t\t\t\tdamage = damageScale * self.node.damage\n\n\t\t\t\tself.shieldHitPoints -= damage\n\n\t\t\t\tself.shield.hurt = 1.0 - self.shieldHitPoints / self.shieldHitPointsMax\n\t\t\t\t# its a cleaner event if a hit just kills the shield without damaging the player..\n\t\t\t\t# however, massive damage events should still be able to damage the player..\n\t\t\t\t# this hopefully gives us a happy medium.\n\t\t\t\tmaxSpillover = 500\n\t\t\t\tif self.shieldHitPoints <= 0:\n\t\t\t\t\t# fixme - transition out perhaps?..\n\t\t\t\t\tself.shield.delete()\n\t\t\t\t\tself.shield = None\n\t\t\t\t\tbs.playSound(self.getFactory().shieldDownSound, 1.0, position=self.node.position)\n\t\t\t\t\t# emit some cool lookin sparks when the shield dies\n\t\t\t\t\tt = self.node.position\n\t\t\t\t\tbs.emitBGDynamics(position=(t[0], t[1]+0.9, t[2]),\n\t\t\t\t\t\t\t\t\t  velocity=self.node.velocity,\n\t\t\t\t\t\t\t\t\t  count=random.randrange(20, 30), scale=1.0,\n\t\t\t\t\t\t\t\t\t  spread=0.6, chunkType='spark')\n\n\t\t\t\telse:\n\t\t\t\t\tbs.playSound(self.getFactory().shieldHitSound, 0.5, position=self.node.position)\n\n\t\t\t\t# emit some cool lookin sparks on shield hit\n\t\t\t\tbs.emitBGDynamics(position=m.pos,\n\t\t\t\t\t\t\t\t  velocity=(m.forceDirection[0]*1.0,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[1]*1.0,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[2]*1.0),\n\t\t\t\t\t\t\t\t  count=min(30, 5+int(damage*0.005)), scale=0.5, spread=0.3, chunkType='spark')\n\n\n\t\t\t\t# if they passed our spillover threshold, pass damage along to spaz\n\t\t\t\tif self.shieldHitPoints <= -maxSpillover:\n\t\t\t\t\tleftoverDamage = -maxSpillover - self.shieldHitPoints\n\t\t\t\t\tshieldLeftoverRatio = leftoverDamage / damage\n\n\t\t\t\t\t# scale down the magnitudes applied to spaz accordingly..\n\t\t\t\t\tmag *= shieldLeftoverRatio\n\t\t\t\t\tvelocityMag *= shieldLeftoverRatio\n\t\t\t\telse:\n\t\t\t\t\treturn True # good job shield!\n\t\t\telse: shieldLeftoverRatio = 1.0\n\n\t\t\tif m.flatDamage:\n\t\t\t\tdamage = m.flatDamage * self._impactScale * shieldLeftoverRatio\n\t\t\telse:\n\t\t\t\t# hit it with an impulse and get the resulting damage\n\t\t\t\t#bs.screenMessage(str(velocityMag))\n\t\t\t\tif self.multiplyer > 3.0:\n\t\t\t\t\t# at about 8.0 the physics glitch out\n\t\t\t\t\tvelocityMag *= min((3.0 + (self.multiplyer-3.0)/4), 7.5) ** 1.9\n\t\t\t\telse:\n\t\t\t\t\tvelocityMag *= self.multiplyer ** 1.9\n\t\t\t\tself.node.handleMessage(\"impulse\", m.pos[0], m.pos[1], m.pos[2],\n\t\t\t\t\t\t\t\t\t\tm.velocity[0], m.velocity[1], m.velocity[2],\n\t\t\t\t\t\t\t\t\t\tmag, velocityMag, m.radius, 0,\n\t\t\t\t\t\t\t\t\t\tm.forceDirection[0], m.forceDirection[1], m.forceDirection[2])\n\n\t\t\t\tdamage = damageScale * self.node.damage\n\t\t\tself.node.handleMessage(\"hurtSound\")\n\n\t\t\t# play punch impact sound based on damage if it was a punch\n\t\t\tif m.hitType == 'punch':\n\n\t\t\t\tself.onPunched(damage)\n\n\t\t\t\t# if damage was significant, lets show it\n\t\t\t\t#if damage > 350: bsUtils.showDamageCount('-'+str(int(damage/10))+\"%\",m.pos,m.forceDirection)\n\n\t\t\t\t# lets always add in a super-punch sound with boxing gloves just to differentiate them\n\t\t\t\tif m.hitSubType == 'superPunch':\n\t\t\t\t\tbs.playSound(self.getFactory().punchSoundStronger, 1.0,\n\t\t\t\t\t\t\t\t position=self.node.position)\n\n\t\t\t\tif damage > 500:\n\t\t\t\t\tsounds = self.getFactory().punchSoundsStrong\n\t\t\t\t\tsound = sounds[random.randrange(len(sounds))]\n\t\t\t\telse: sound = self.getFactory().punchSound\n\t\t\t\tbs.playSound(sound, 1.0, position=self.node.position)\n\n\t\t\t\t# throw up some chunks\n\t\t\t\tbs.emitBGDynamics(position=m.pos,\n\t\t\t\t\t\t\t\t  velocity=(m.forceDirection[0]*0.5,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[1]*0.5,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[2]*0.5),\n\t\t\t\t\t\t\t\t  count=min(10, 1+int(damage*0.0025)), scale=0.3, spread=0.03)\n\n\t\t\t\tbs.emitBGDynamics(position=m.pos,\n\t\t\t\t\t\t\t\t  chunkType='sweat',\n\t\t\t\t\t\t\t\t  velocity=(m.forceDirection[0]*1.3,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[1]*1.3+5.0,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[2]*1.3),\n\t\t\t\t\t\t\t\t  count=min(30, 1 + int(damage * 0.04)),\n\t\t\t\t\t\t\t\t  scale=0.9,\n\t\t\t\t\t\t\t\t  spread=0.28)\n\t\t\t\t# momentary flash\n\t\t\t\thurtiness = damage*0.003\n\t\t\t\thurtiness = min(hurtiness, 750 * 0.003)\n\t\t\t\tpunchPos = (m.pos[0]+m.forceDirection[0]*0.02,\n\t\t\t\t\t\t\tm.pos[1]+m.forceDirection[1]*0.02,\n\t\t\t\t\t\t\tm.pos[2]+m.forceDirection[2]*0.02)\n\t\t\t\tflashColor = (1.0, 0.8, 0.4)\n\t\t\t\tlight = bs.newNode(\"light\",\n\t\t\t\t\t\t\t\t   attrs={'position':punchPos,\n\t\t\t\t\t\t\t\t\t\t  'radius':0.12+hurtiness*0.12,\n\t\t\t\t\t\t\t\t\t\t  'intensity':0.3*(1.0+1.0*hurtiness),\n\t\t\t\t\t\t\t\t\t\t  'heightAttenuated':False,\n\t\t\t\t\t\t\t\t\t\t  'color':flashColor})\n\t\t\t\tbs.gameTimer(60, light.delete)\n\n\n\t\t\t\tflash = bs.newNode(\"flash\",\n\t\t\t\t\t\t\t\t   attrs={'position':punchPos,\n\t\t\t\t\t\t\t\t\t\t  'size':0.17+0.17*hurtiness,\n\t\t\t\t\t\t\t\t\t\t  'color':flashColor})\n\t\t\t\tbs.gameTimer(60, flash.delete)\n\n\t\t\tif m.hitType == 'impact':\n\t\t\t\tbs.emitBGDynamics(position=m.pos,\n\t\t\t\t\t\t\t\t  velocity=(m.forceDirection[0]*2.0,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[1]*2.0,\n\t\t\t\t\t\t\t\t\t\t\tm.forceDirection[2]*2.0),\n\t\t\t\t\t\t\t\t  count=min(10, 1 + int(damage * 0.01)), scale=0.4, spread=0.1)\n\n\t\t\tif self.hitPoints > 0:\n\n\t\t\t\t# its kinda crappy to die from impacts, so lets reduce impact damage\n\t\t\t\t# by a reasonable amount if it'll keep us alive\n\t\t\t\tif m.hitType == 'impact' and damage > self.hitPoints:\n\t\t\t\t\t# drop damage to whatever puts us at 10 hit points, or 200 less than it used to be\n\t\t\t\t\t# whichever is greater (so it *can* still kill us if its high enough)\n\t\t\t\t\tnewDamage = max(damage-200, self.hitPoints-10)\n\t\t\t\t\tdamage = newDamage\n\n\t\t\t\tself.node.handleMessage(\"flash\")\n\t\t\t\t# if we're holding something, drop it\n\t\t\t\tif damage > 0.0 and self.node.holdNode.exists():\n\t\t\t\t\tself.node.holdNode = bs.Node(None)\n\t\t\t\t#self.hitPoints -= damage\n\t\t\t\tself.multiplyer += min(damage / 2000, 0.15)\n\t\t\t\tif damage/2000 > 0.05:\n\t\t\t\t\tself.setScoreText(str(int((self.multiplyer-1)*100))+\"%\")\n\t\t\t\t#self.node.hurt = 1.0 - self.hitPoints/self.hitPointsMax\n\t\t\t\tself.node.hurt = 0.0\n\t\t\t\t# if we're cursed, *any* damage blows us up\n\t\t\t\tif self._cursed and damage > 0:\n\t\t\t\t\tbs.gameTimer(50, bs.WeakCall(self.curseExplode, m.sourcePlayer))\n\t\t\t\t# if we're frozen, shatter.. otherwise die if we hit zero\n\t\t\t\t#if self.frozen and (damage > 200 or self.hitPoints <= 0):\n\t\t\t\t#\tself.shatter()\n\t\t\t\t#elif self.hitPoints <= 0:\n\t\t\t\t#\tself.node.handleMessage(bs.DieMessage(how='impact'))\n\n\t\t\t# if we're dead, take a look at the smoothed damage val\n\t\t\t# (which gives us a smoothed average of recent damage) and shatter\n\t\t\t# us if its grown high enough\n\t\t\t#if self.hitPoints <= 0:\n\t\t\t#\tdamageAvg = self.node.damageSmoothed * damageScale\n\t\t\t#\tif damageAvg > 1000:\n\t\t\t#\t\tself.shatter()\n\t\telif isinstance(m, bs.DieMessage):\n\t\t\tself.oob_effect()\n\t\t\tsuper(self.__class__, self).handleMessage(m)\n\t\telif isinstance(m, bs.PowerupMessage):\n\t\t\tif m.powerupType == 'health':\n\t\t\t\tif self.multiplyer > 2:\n\t\t\t\t\tself.multiplyer *= 0.5\n\t\t\t\telse:\n\t\t\t\t\tself.multiplyer *= 0.75\n\t\t\t\tself.multiplyer = max(1, self.multiplyer)\n\t\t\t\tself.setScoreText(str(int((self.multiplyer-1)*100))+\"%\")\n\t\t\tsuper(self.__class__, self).handleMessage(m)\n\t\telse:\n\t\t\tsuper(self.__class__, self).handleMessage(m)\n\n\tdef oob_effect(self):\n\t\tif self.isDead:\n\t\t\treturn\n\t\tself.isDead = True\n\t\tif self.multiplyer > 1.25:\n\t\t\tblastType = 'tnt'\n\t\t\tradius = min(self.multiplyer * 5, 20)\n\t\telse:\n\t\t\t# penalty for killing people with low multiplyer\n\t\t\tblastType = 'ice'\n\t\t\tradius = 7.5\n\n\t\tbs.Blast(position=self.node.position, blastRadius=radius, blastType=blastType).autoRetain()\n\n\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [SuperSmash]\n\nclass SuperSmash(bs.TeamGameActivity):\n\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Super Smash'\n\n\t@classmethod\n\tdef getScoreInfo(cls):\n\t\tif cls == SuperSmash:\n\t\t\tif bs.getActivity().__class__ == SuperSmash and hasattr(bs.getActivity(), 'timeLimitOnly'):\n\t\t\t\t# some sanity checks that probably arent needed\n\t\t\t\tif bs.getActivity().timeLimitOnly:\n\t\t\t\t\t# if its timeonlymode return different scoreinfo\n\t\t\t\t\treturn {'scoreName':'Deaths',\n\t\t\t\t\t\t\t'scoreType':'points',\n\t\t\t\t\t\t\t'scoreVersion':'B',\n\t\t\t\t\t\t\t'noneIsWinner':True,\n\t\t\t\t\t\t\t'lowerIsBetter': True}\n\n\t\treturn {'scoreName':'Survived',\n\t\t\t'scoreType':'seconds',\n\t\t\t'scoreVersion':'B',\n\t\t\t'noneIsWinner':True}\n\n\t@classmethod\n\tdef getDescription(cls, sessionType):\n\t\treturn \"Kill everyone with your knockback.\"\n\n\n\tdef getInstanceDescription(self):\n\t\treturn 'Knock everyone off the map.'\n\n\tdef getInstanceScoreBoardDescription(self):\n\t\tif self.timeLimitOnly:\n\t\t\treturn 'Knock everyone off the map.'\n\t\telse:\n\t\t\tif self.settings['Lives'] > 1:\n\t\t\t\treturn ('Knock the others off ${ARG1} times.', self.settings['Lives'])\n\t\t\telse:\n\t\t\t\treturn 'Knock everyone off once.'\n\n\n\t@classmethod\n\tdef supportsSessionType(cls, sessionType):\n\t\treturn True if (issubclass(sessionType, bs.TeamsSession)\n\t\t\t\t\t\tor issubclass(sessionType, bs.FreeForAllSession)) else False\n\n\t@classmethod\n\tdef getSupportedMaps(cls, sessionType):\n\t\tmaps = bs.getMapsSupportingPlayType(\"melee\")\n\t\tfor m in ['Lake Frigid', 'Hockey Stadium', 'Football Stadium']:\n\t\t\t# remove maps without bounds\n\t\t\tmaps.remove(m)\n\t\treturn maps\n\n\t@classmethod\n\tdef getSettings(cls, sessionType):\n\t\treturn [(\"Time Limit\", {'choices':[('None', 0), ('1 Minute', 60), ('2 Minutes', 120),\n\t\t\t\t\t\t\t\t\t\t\t('5 Minutes', 300)], 'default': 0}),\n\t\t\t\t(\"Lives (0 = Unlimited)\", {'minValue': 0, 'default': 3, 'increment': 1}),\n\t\t\t\t(\"Epic Mode\", {'default': False})]\n\n\tdef __init__(self, settings):\n\t\tbs.TeamGameActivity.__init__(self, settings)\n\t\tself.settings['Lives'] = self.settings[\"Lives (0 = Unlimited)\"]\n\t\tself.timeLimitOnly = (self.settings['Lives'] == 0)\n\t\tif self.timeLimitOnly:\n\t\t\tself.settings['Time Limit'] = max(60, self.settings['Time Limit'])\n\n\t\tif self.settings['Epic Mode']:\n\t\t\tself._isSlowMotion = True\n\n\t\t# print messages when players die (since its meaningful in this game)\n\t\tself.announcePlayerDeaths = True\n\n\t\tself._lastPlayerDeathTime = None\n\n\t\tself._startGameTime = 1000\n\n\n\tdef onTransitionIn(self):\n\t\tbs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n\t\tself._startGameTime = bs.getGameTime()\n\n\tdef onBegin(self):\n\t\tbs.TeamGameActivity.onBegin(self)\n\t\tself.setupStandardTimeLimit(self.settings['Time Limit'])\n\t\tself.setupStandardPowerupDrops(enableTNT=False)\n\t\tself._pow = None\n\t\tself._tntDropTimer = bs.Timer(1000 * 30, bs.WeakCall(self._dropPowBox), repeat=True)\n\t\tself._updateIcons()\n\n\tdef _dropPowBox(self):\n\t\tif self._pow is not None and self._pow.exists():\n\t\t\treturn\n\t\tif len(self.getMap().tntPoints) == 0:\n\t\t\treturn\n\t\tpos = random.choice(self.getMap().tntPoints)\n\t\tpos = (pos[0], pos[1] + 1, pos[2])\n\t\tself._pow = PowBox(position=pos, velocity=(0, 1, 0))\n\n\tdef onPlayerJoin(self, player):\n\t\tif 'lives' not in player.gameData:\n\t\t\tplayer.gameData['lives'] = self.settings['Lives']\n\t\t# create our icon and spawn\n\t\tplayer.gameData['icons'] = [Icon(player, position=(0, 50), scale=0.8)]\n\t\tif player.gameData['lives'] > 0 or self.timeLimitOnly:\n\t\t\tself.spawnPlayer(player)\n\n\t\t# dont waste time doing this until begin\n\t\tif self.hasBegun():\n\t\t\tself._updateIcons()\n\n\tdef onPlayerLeave(self, player):\n\t\tbs.TeamGameActivity.onPlayerLeave(self, player)\n\n\t\tplayer.gameData['icons'] = None\n\n\t\t# update icons in a moment since our team will be gone from the list then\n\t\tbs.gameTimer(0, self._updateIcons)\n\n\t\tif sum([len(team.players) >= 1 for team in self.teams]) < 2:\n\t\t\tself.endGame()\n\n\tdef _updateIcons(self):\n\t\t# in free-for-all mode, everyone is just lined up along the bottom\n\t\tif isinstance(self.getSession(), bs.FreeForAllSession):\n\t\t\tcount = len(self.teams)\n\t\t\txOffs = 85\n\t\t\tx = xOffs*(count-1) * -0.5\n\t\t\tfor team in self.teams:\n\t\t\t\tif len(team.players) > 1:\n\t\t\t\t\tprint('WTF have', len(team.players), 'players in ffa team')\n\t\t\t\telif len(team.players) == 1:\n\t\t\t\t\tplayer = team.players[0]\n\t\t\t\t\tif len(player.gameData['icons']) != 1:\n\t\t\t\t\t\tprint('WTF have', len(player.gameData['icons']), 'icons in non-solo elim')\n\t\t\t\t\tfor icon in player.gameData['icons']:\n\t\t\t\t\t\ticon.setPositionAndScale((x, 30), 0.7)\n\t\t\t\t\t\ticon.updateForLives()\n\t\t\t\t\tx += xOffs\n\n\t\t# in teams mode we split up teams\n\t\telse:\n\t\t\tfor team in self.teams:\n\t\t\t\tif team.getID() == 0:\n\t\t\t\t\tx = -50\n\t\t\t\t\txOffs = -85\n\t\t\t\telse:\n\t\t\t\t\tx = 50\n\t\t\t\t\txOffs = 85\n\t\t\t\tfor player in team.players:\n\t\t\t\t\tif len(player.gameData['icons']) != 1:\n\t\t\t\t\t\tprint('WTF have', len(player.gameData['icons']), 'icons in non-solo elim')\n\t\t\t\t\tfor icon in player.gameData['icons']:\n\t\t\t\t\t\ticon.setPositionAndScale((x, 30), 0.7)\n\t\t\t\t\t\ticon.updateForLives()\n\t\t\t\t\tx += xOffs\n\n\n\t# overriding the default character spawning..\n\tdef spawnPlayer(self, player):\n\t\tif isinstance(self.getSession(), bs.TeamsSession):\n\t\t\tposition = self.getMap().getStartPosition(player.getTeam().getID())\n\t\telse:\n\t\t\t# otherwise do free-for-all spawn locations\n\t\t\tposition = self.getMap().getFFAStartPosition(self.players)\n\n\t\tangle = None\n\n\n\t\t#spaz = self.spawnPlayerSpaz(player)\n\n\t\t# lets reconnect this player's controls to this\n\t\t# spaz but *without* the ability to attack or pick stuff up\n\t\t#spaz.connectControlsToPlayer(enablePunch=False,\n\t\t#\t\t\t\t\t\t\t enableBomb=False,\n\t\t#\t\t\t\t\t\t\t enablePickUp=False)\n\n\t\t# also lets have them make some noise when they die..\n\t\t#spaz.playBigDeathSound = True\n\n\t\tname = player.getName()\n\n\t\tlightColor = bsUtils.getNormalizedColor(player.color)\n\t\tdisplayColor = bs.getSafeColor(player.color, targetIntensity=0.75)\n\n\t\tspaz = PlayerSpaz_Smash(color=player.color,\n\t\t\t\t\t\t\t highlight=player.highlight,\n\t\t\t\t\t\t\t character=player.character,\n\t\t\t\t\t\t\t player=player)\n\t\tplayer.setActor(spaz)\n\n\t\t# we want a bigger area-of-interest in co-op mode\n\t\t# if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n\t\t# else: spaz.node.areaOfInterestRadius = 5.0\n\n\t\t# if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n\t\t# collide with the player-walls\n\t\t# FIXME; need to generalize this\n\t\tif isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']:\n\t\t\tmat = self.getMap().preloadData['collideWithWallMaterial']\n\t\t\tspaz.node.materials += (mat,)\n\t\t\tspaz.node.rollerMaterials += (mat,)\n\n\t\tspaz.node.name = name\n\t\tspaz.node.nameColor = displayColor\n\t\tspaz.connectControlsToPlayer()\n\t\tself.scoreSet.playerGotNewSpaz(player, spaz)\n\n\t\t# move to the stand position and add a flash of light\n\t\tspaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360)))\n\t\tt = bs.getGameTime()\n\t\tbs.playSound(self._spawnSound, 1, position=spaz.node.position)\n\t\tlight = bs.newNode('light', attrs={'color': lightColor})\n\t\tspaz.node.connectAttr('position', light, 'position')\n\t\tbsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0})\n\t\tbs.gameTimer(500, light.delete)\n\n\n\t\t# if we have any icons, update their state\n\t\tfor icon in player.gameData['icons']:\n\t\t\ticon.handlePlayerSpawned()\n\n\n\n\t# various high-level game events come through this method\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, bs.PlayerSpazDeathMessage):\n\n\t\t\tbs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n\t\t\tplayer = m.spaz.getPlayer()\n\n\t\t\tplayer.gameData['lives'] -= 1\n\n\t\t\t# if we have any icons, update their state\n\t\t\tfor icon in player.gameData['icons']:\n\t\t\t\ticon.handlePlayerDied()\n\n\t\t\t# play big death sound on our last death or for every one in solo mode\n\t\t\tif  player.gameData['lives'] == 0:\n\t\t\t\tbs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound)\n\n\t\t\t# if we hit zero lives we're dead and the game might be over\n\t\t\tif player.gameData['lives'] == 0 and not self.timeLimitOnly:\n\n\t\t\t\t# if the whole team is dead, make note of how long they lasted\n\t\t\t\tif all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players):\n\n\t\t\t\t\t# log the team survival if we're the last player on the team\n\t\t\t\t\tplayer.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000\n\n\t\t\t\t\t# if someone has won, set a timer to end shortly\n\t\t\t\t\t# (allows the dust to settle and draws to occur if deaths are close enough)\n\t\t\t\t\tif len(self._getLivingTeams()) < 2:\n\t\t\t\t\t\tself._roundEndTimer = bs.Timer(1000, self.endGame)\n\n\t\t\t# we still have lives; yay!\n\t\t\telse:\n\t\t\t\tself.respawnPlayer(player)\n\n\t\telse:\n\t\t\t# default handler:\n\t\t\tsuper(self.__class__, self).handleMessage(m)#bs.TeamGameActivity.handleMessage(self,m)\n\n\tdef endGame(self):\n\n\t\tcurTime = bs.getGameTime()\n\t\tif not self.timeLimitOnly:\n\t\t\t# mark 'death-time' as now for any still-living players\n\t\t\t# and award players points for how long they lasted.\n\t\t\t# (these per-player scores are only meaningful in team-games)\n\t\t\tfor team in self.teams:\n\t\t\t\tfor player in team.players:\n\n\t\t\t\t\t# throw an extra fudge factor +1 in so teams that\n\t\t\t\t\t# didn't die come out ahead of teams that did\n\t\t\t\t\tif 'survivalSeconds' in player.gameData:\n\t\t\t\t\t\tscore = player.gameData['survivalSeconds']\n\t\t\t\t\telif 'survivalSeconds' in team.gameData:\n\t\t\t\t\t\tscore = team.gameData['survivalSeconds']\n\t\t\t\t\telse:\n\t\t\t\t\t\tscore = (curTime - self._startGameTime)/1000 + 1\n\n\t\t\t\t\t#if 'survivalSeconds' not in player.gameData:\n\t\t\t\t\t#\tplayer.gameData['survivalSeconds'] = (curTime - self._startGameTime)/1000 + 1\n\t\t\t\t\t#\tprint('extraBonusSwag for player')\n\n\t\t\t\t\t# award a per-player score depending on how many seconds they lasted\n\t\t\t\t\t# (per-player scores only affect teams mode; everywhere else just looks at the per-team score)\n\t\t\t\t\t#score = (player.gameData['survivalSeconds'])\n\t\t\t\t\tself.scoreSet.playerScored(player, score, screenMessage=False)\n\n\n\t\t\t# ok now calc game results: set a score for each team and then tell the game to end\n\t\t\tresults = bs.TeamGameResults()\n\n\t\t\t# remember that 'free-for-all' mode is simply a special form of 'teams' mode\n\t\t\t# where each player gets their own team, so we can just always deal in teams\n\t\t\t# and have all cases covered\n\t\t\tfor team in self.teams:\n\n\t\t\t\t# set the team score to the max time survived by any player on that team\n\t\t\t\tlongestLife = 0\n\t\t\t\tfor player in team.players:\n\t\t\t\t\tif 'survivalSeconds' in player.gameData:\n\t\t\t\t\t\ttime = player.gameData['survivalSeconds']\n\t\t\t\t\telif 'survivalSeconds' in team.gameData:\n\t\t\t\t\t\ttime = team.gameData['survivalSeconds']\n\t\t\t\t\telse:\n\t\t\t\t\t\ttime = (curTime - self._startGameTime)/1000 + 1\n\t\t\t\t\tlongestLife = max(longestLife, time)\n\t\t\t\tresults.setTeamScore(team, longestLife)\n\n\t\t\tself.end(results=results)\n\t\telse:\n\t\t\tresults = bs.TeamGameResults()\n\t\t\tfor team in self.teams:\n\t\t\t\tdeaths = sum([0 - player.gameData['lives'] for player in team.players])\n\t\t\t\tresults.setTeamScore(team, deaths)\n\t\t\tself.end(results=results)\n\n\tdef _getLivingTeams(self):\n\t\treturn [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)]\n"
  },
  {
    "path": "mods/snake.json",
    "content": "{\n  \"name\": \"Snake\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"minigames\"\n}\n"
  },
  {
    "path": "mods/snake.py",
    "content": "import bs\n\n\ndef bsGetAPIVersion():\n\treturn 4\n\ndef bsGetGames():\n\treturn [SnakeGame]\n\n\nclass RaceTimer:\n\t\"\"\"Basicly the onscreen timer from bsRace...\"\"\"\n\tdef __init__(self, incTime=1000):\n\n\t\tlightY = 150\n\n\t\tself.pos = 0\n\n\t\tself._beep1Sound = bs.getSound('raceBeep1')\n\t\tself._beep2Sound = bs.getSound('raceBeep2')\n\n\t\tself.lights = []\n\t\tfor i in range(4):\n\t\t\tl = bs.newNode('image',\n\t\t\t\t\t\t   attrs={'texture':bs.getTexture('nub'),\n\t\t\t\t\t\t\t\t  'opacity':1.0,\n\t\t\t\t\t\t\t\t  'absoluteScale':True,\n\t\t\t\t\t\t\t\t  'position':(-75+i*50, lightY),\n\t\t\t\t\t\t\t\t  'scale':(50, 50),\n\t\t\t\t\t\t\t\t  'attach':'center'})\n\t\t\tbs.animate(l, 'opacity', {10:0, 1000:1.0})\n\t\t\tself.lights.append(l)\n\n\t\tself.lights[0].color = (0.2, 0, 0)\n\t\tself.lights[1].color = (0.2, 0, 0)\n\t\tself.lights[2].color = (0.2, 0.05, 0)\n\t\tself.lights[3].color = (0.0, 0.3, 0)\n\n\n\t\tself.cases = {1: self._doLight1, 2: self._doLight2, 3: self._doLight3, 4: self._doLight4}\n\t\tself.incTimer = None\n\t\tself.incTime = incTime\n\n\tdef start(self):\n\t\tself.incTimer = bs.Timer(self.incTime, bs.WeakCall(self.increment), timeType=\"game\", repeat=True)\n\n\tdef _doLight1(self):\n\t\tself.lights[0].color = (1.0, 0, 0)\n\t\tbs.playSound(self._beep1Sound)\n\n\tdef _doLight2(self):\n\t\tself.lights[1].color = (1.0, 0, 0)\n\t\tbs.playSound(self._beep1Sound)\n\n\tdef _doLight3(self):\n\t\tself.lights[2].color = (1.0, 0.3, 0)\n\t\tbs.playSound(self._beep1Sound)\n\n\tdef _doLight4(self):\n\t\tself.lights[3].color = (0.0, 1.0, 0)\n\t\tbs.playSound(self._beep2Sound)\n\t\tfor l in self.lights:\n\t\t\tbs.animate(l, 'opacity', {0: 1.0, 1000: 0.0})\n\t\t\tbs.gameTimer(1000, l.delete)\n\t\tself.incTimer = None\n\t\tself.onFinish()\n\t\tdel self\n\n\tdef onFinish(self):\n\t\tpass\n\n\tdef onIncrement(self):\n\t\tpass\n\n\tdef increment(self):\n\t\tself.pos += 1\n\t\tif self.pos in self.cases:\n\t\t\tself.cases[self.pos]()\n\t\tself.onIncrement()\n\nclass SnakeGame(bs.TeamGameActivity):\n\ttailIncrease = 0.2\n\tmaxTailLength = 30\n\tmineDelay = 0.5\n\n\t@classmethod\n\tdef getName(cls):\n\t\treturn 'Snake'\n\n\t@classmethod\n\tdef getScoreInfo(cls):\n\t\treturn {'scoreName':'Mines Planted'}\n\n\t@classmethod\n\tdef getDescription(cls, sessionType):\n\t\treturn 'Plant as many Mines as you can'\n\n\t@classmethod\n\tdef supportsSessionType(cls, sessionType):\n\t\treturn True if (issubclass(sessionType, bs.TeamsSession)\n\t\t\t\t\t\tor issubclass(sessionType, bs.FreeForAllSession)) else False\n\n\t@classmethod\n\tdef getSupportedMaps(cls, sessionType):\n\t\treturn bs.getMapsSupportingPlayType(\"keepAway\")\n\n\t@classmethod\n\tdef getSettings(cls, sessionType):\n\t\treturn [(\"Mines to win\", {'choices':[('Few', 60), ('Some', 80), ('Some more', 120), ('Many much', 140), ('wow', 200)], 'default': 80}),\n\t\t\t\t(\"Epic Mode\", {'default':False})]\n\n\tdef __init__(self, settings):\n\t\tbs.TeamGameActivity.__init__(self, settings)\n\t\tif self.settings['Epic Mode']:\n\t\t\tself._isSlowMotion = True\n\t\tself._scoreBoard = bs.ScoreBoard()\n\t\tself._swipSound = bs.getSound(\"swip\")\n\t\tself._countDownSounds = {10:bs.getSound('announceTen'),\n\t\t\t\t\t\t\t\t 9:bs.getSound('announceNine'),\n\t\t\t\t\t\t\t\t 8:bs.getSound('announceEight'),\n\t\t\t\t\t\t\t\t 7:bs.getSound('announceSeven'),\n\t\t\t\t\t\t\t\t 6:bs.getSound('announceSix'),\n\t\t\t\t\t\t\t\t 5:bs.getSound('announceFive'),\n\t\t\t\t\t\t\t\t 4:bs.getSound('announceFour'),\n\t\t\t\t\t\t\t\t 3:bs.getSound('announceThree'),\n\t\t\t\t\t\t\t\t 2:bs.getSound('announceTwo'),\n\t\t\t\t\t\t\t\t 1:bs.getSound('announceOne')}\n\t\tself.maxTailLength = self.settings['Mines to win'] * self.tailIncrease\n\t\tself.isFinished = False\n\t\tself.hasStarted = False\n\n\tdef getInstanceDescription(self):\n\t\treturn 'Run around and don\\'t get killed.'\n\n\n\tdef getInstanceScoreBoardDescription(self):\n\t\treturn ('Survive ${ARG1} mines', self.settings['Mines to win'])\n\n\tdef onTransitionIn(self):\n\t\tbs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Chosen One')\n\n\tdef onTeamJoin(self, team):\n\t\tteam.gameData['tailLength'] = 0\n\t\tteam.gameData['minesPlanted'] = 0\n\t\tself._updateScoreBoard()\n\n\tdef onPlayerJoin(self, player):\n\t\tbs.TeamGameActivity.onPlayerJoin(self, player)\n\t\tplayer.gameData['mines'] = []\n\t\tif self.hasStarted:\n\t\t\tcall = bs.WeakCall(self._spawnMine, player)\n\t\t\tself.mineTimers.append(bs.Timer(int(self.mineDelay * 1000), call, repeat=True))\n\n\tdef onPlayerLeave(self, player):\n\t\tbs.TeamGameActivity.onPlayerLeave(self, player)\n\n\tdef onBegin(self):\n\t\tself.mineTimers = []\n\n\t\tbs.gameTimer(3500, bs.Call(self.doRaceTimer))\n\t\t# test...\n\t\tif not all(player.exists() for player in self.players):\n\t\t\tbs.printError(\"Nonexistant player in onBegin: \"+str([str(p) for p in self.players]))\n\n\n\t\tbs.TeamGameActivity.onBegin(self)\n\n\tdef doRaceTimer(self):\n\t\tself.raceTimer = RaceTimer()\n\t\tself.raceTimer.onFinish = bs.WeakCall(self.timerCallback)\n\t\tbs.gameTimer(1000, bs.Call(self.raceTimer.start))\n\n\tdef timerCallback(self):\n\t\tfor player in self.players:\n\t\t\tcall = bs.WeakCall(self._spawnMine, player)\n\t\t\tself.mineTimers.append(bs.Timer(int(self.mineDelay * 1000), call, repeat=True))\n\t\tself.hasStarted = True\n\n\n\tdef _spawnMine(self, player):\n\t\t#Don't spawn mines if player is dead\n\t\tif not player.exists() or not player.isAlive():\n\t\t\treturn\n\n\t\tgameData = player.getTeam().gameData\n\n\t\t# no more mines for players who've already won\n\t\t# to get a working draw\n\t\tif gameData['minesPlanted'] >= self.settings['Mines to win']:\n\t\t\treturn\n\n\t\tgameData['minesPlanted'] += 1\n\t\tgameData['tailLength'] = gameData['minesPlanted'] * self.tailIncrease + 2\n\t\tif gameData['minesPlanted'] >= self.settings['Mines to win'] - 10:\n\t\t\tnum2win = self.settings['Mines to win'] - gameData['minesPlanted'] + 1\n\t\t\tif num2win in self._countDownSounds:\n\t\t\t\tbs.playSound(self._countDownSounds[num2win])\n\n\n\n\t\tself._updateScoreBoard()\n\n\t\tif player.getTeam().gameData['tailLength'] < 2:\n\t\t\treturn\n\n\t\tpos = player.actor.node.position\n\t\tpos = (pos[0], pos[1] + 2, pos[2])\n\t\tmine = bs.Bomb(position=pos, velocity=(0, 0, 0), bombType='landMine', blastRadius=2.0, sourcePlayer=player, owner=player).autoRetain()\n\t\tplayer.gameData['mines'].append(mine)\n\t\tbs.gameTimer(int(self.mineDelay * 1000), bs.WeakCall(mine.arm))\n\t\tbs.gameTimer(int(int(player.getTeam().gameData['tailLength'] + 1) * self.mineDelay * 1000), bs.WeakCall(self._removeMine, player, mine))\n\n\tdef _removeMine(self, player, mine):\n\t\t#kill it with(out) fire\n\t\tif mine in player.gameData:\n\t\t\tplayer.gameData['mines'].remove(mine)\n\t\tmine.handleMessage(bs.DieMessage())\n\t\tmine = None\n\n\n\n\tdef endGame(self):\n\t\tresults = bs.TeamGameResults()\n\t\tfor team in self.teams:\n\t\t\tresults.setTeamScore(team, min(int(team.gameData['minesPlanted']), self.settings['Mines to win']))\n\t\tself.end(results=results, announceDelay=0)\n\n\n\tdef handleMessage(self, m):\n\t\tif isinstance(m, bs.PlayerSpazDeathMessage):\n\t\t\tbs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n\t\t\tplayer = m.spaz.getPlayer()\n\t\t\tfor mine in player.gameData['mines']:\n\t\t\t\tself._removeMine(player, mine)\n\t\t\tself.respawnPlayer(player)\n\t\telse: bs.TeamGameActivity.handleMessage(self, m)\n\n\tdef _updateScoreBoard(self):\n\t\tfor team in self.teams:\n\t\t\tself._scoreBoard.setTeamValue(team, min(int(team.gameData['minesPlanted']), self.settings['Mines to win']), self.settings['Mines to win'], countdown=False)\n\t\t\tif int(team.gameData['minesPlanted']) >= self.settings['Mines to win']:\n\t\t\t\tbs.gameTimer(500, bs.WeakCall(self.endGame))\n\t\t\t\tself.isFinished = True\n"
  },
  {
    "path": "mods/snowyPowerup.json",
    "content": "{\n  \"name\": \"Modified bsPowerup.py\",\n  \"author\": \"joshville79\",\n  \"category\": \"libraries\",\n  \"requires\": [\"BuddyBunny\", \"SnoBallz\", \"Portal\"]\n}"
  },
  {
    "path": "mods/snowyPowerup.py",
    "content": "import bs\nimport random\n#add for bunny buddy:\nimport BuddyBunny\nimport SnoBallz\nimport bsPowerup\nimport bsSpaz\nimport Portal\nfrom bsPowerup import PowerupMessage, PowerupAcceptMessage, _TouchedMessage, PowerupFactory, Powerup\n\n\ndefaultPowerupInterval = 8000\n\nclass NewPowerupFactory(PowerupFactory):\n    def __init__(self):\n        self._lastPowerupType = None\n\n        self.model = bs.getModel(\"powerup\")\n        self.modelSimple = bs.getModel(\"powerupSimple\")\n\n        self.texBomb = bs.getTexture(\"powerupBomb\")\n        self.texPunch = bs.getTexture(\"powerupPunch\")\n        self.texIceBombs = bs.getTexture(\"powerupIceBombs\")\n        self.texStickyBombs = bs.getTexture(\"powerupStickyBombs\")\n        self.texShield = bs.getTexture(\"powerupShield\")\n        self.texImpactBombs = bs.getTexture(\"powerupImpactBombs\")\n        self.texHealth = bs.getTexture(\"powerupHealth\")\n        self.texLandMines = bs.getTexture(\"powerupLandMines\")\n        self.texCurse = bs.getTexture(\"powerupCurse\")\n        #Add for Bunnybot:\n        self.eggModel = bs.getModel('egg')\n        self.texEgg = bs.getTexture('eggTex1')\n        #Add for snoBalls:\n        self.texSno = bs.getTexture(\"bunnyColor\") #Bunny is most uniform plain white color.\n        self.snoModel = bs.getModel(\"frostyPelvis\") #Frosty pelvis is very nice and round...\n        self.healthPowerupSound = bs.getSound(\"healthPowerup\")\n        self.powerupSound = bs.getSound(\"powerup01\")\n        self.powerdownSound = bs.getSound(\"powerdown01\")\n        self.dropSound = bs.getSound(\"boxDrop\")\n        self.texPort = bs.getTexture(\"ouyaOButton\")\n\n        # material for powerups\n        self.powerupMaterial = bs.Material()\n\n        # material for anyone wanting to accept powerups\n        self.powerupAcceptMaterial = bs.Material()\n\n        # pass a powerup-touched message to applicable stuff\n        self.powerupMaterial.addActions(\n            conditions=((\"theyHaveMaterial\",self.powerupAcceptMaterial)),\n            actions=((\"modifyPartCollision\",\"collide\",True),\n                     (\"modifyPartCollision\",\"physical\",False),\n                     (\"message\",\"ourNode\",\"atConnect\",_TouchedMessage())))\n\n        # we dont wanna be picked up\n        self.powerupMaterial.addActions(\n            conditions=(\"theyHaveMaterial\",bs.getSharedObject('pickupMaterial')),\n            actions=( (\"modifyPartCollision\",\"collide\",False)))\n\n        self.powerupMaterial.addActions(\n            conditions=(\"theyHaveMaterial\",bs.getSharedObject('footingMaterial')),\n            actions=((\"impactSound\",self.dropSound,0.5,0.1)))\n\n        self._powerupDist = []\n        for p,freq in getDefaultPowerupDistribution():\n            for i in range(int(freq)):\n                self._powerupDist.append(p)\n\n    def getRandomPowerupType(self, forceType=None, excludeTypes=None):\n        if excludeTypes:\n            # exclude custom powerups if there is some custom powerup logic\n            # example: bsFootball.py:456\n            excludeTypes.append('snoball')\n            excludeTypes.append('bunny')\n        else:\n            excludeTypes = []\n        return PowerupFactory.getRandomPowerupType(self, forceType, excludeTypes)\n\n\ndef getDefaultPowerupDistribution():\n    return (('tripleBombs',3),\n            ('iceBombs',3),\n            ('punch',3),\n            ('impactBombs',3),\n            ('landMines',2),\n            ('stickyBombs',3),\n            ('shield',2),\n            ('health',1),\n            ('bunny',2),\n            ('portal',2),\n            ('curse',1),\n            ('snoball',3))\n\nclass NewPowerup(Powerup):\n    def __init__(self,position=(0,1,0),powerupType='tripleBombs',expire=True):\n        \"\"\"\n        Create a powerup-box of the requested type at the requested position.\n\n        see bs.Powerup.powerupType for valid type strings.\n        \"\"\"\n        bs.Actor.__init__(self)\n\n        factory = self.getFactory()\n        self.powerupType = powerupType;\n        self._powersGiven = False\n\n        mod = factory.model\n        mScl = 1\n        if powerupType == 'tripleBombs': tex = factory.texBomb\n        elif powerupType == 'punch': tex = factory.texPunch\n        elif powerupType == 'iceBombs': tex = factory.texIceBombs\n        elif powerupType == 'impactBombs': tex = factory.texImpactBombs\n        elif powerupType == 'landMines': tex = factory.texLandMines\n        elif powerupType == 'stickyBombs': tex = factory.texStickyBombs\n        elif powerupType == 'shield': tex = factory.texShield\n        elif powerupType == 'health': tex = factory.texHealth\n        elif powerupType == 'curse': tex = factory.texCurse\n        elif powerupType == 'portal': tex = factory.texPort\n        elif powerupType == 'bunny': \n            tex = factory.texEgg\n            mod = factory.eggModel\n            mScl = 0.7\n        elif powerupType == 'snoball': \n            tex = factory.texSno\n            mod = factory.snoModel\n        else: raise Exception(\"invalid powerupType: \"+str(powerupType))\n\n        if len(position) != 3: raise Exception(\"expected 3 floats for position\")\n        \n        self.node = bs.newNode('prop',\n                               delegate=self,\n                               attrs={'body':'box',\n                                      'position':position,\n                                      'model':mod,\n                                      'lightModel':factory.modelSimple,\n                                      'shadowSize':0.5,\n                                      'colorTexture':tex,\n                                      'reflection':'powerup',\n                                      'reflectionScale':[1.0],\n                                      'materials':(factory.powerupMaterial,bs.getSharedObject('objectMaterial'))})\n\n        # animate in..\n        curve = bs.animate(self.node,\"modelScale\",{0:0,140:1.6,200:mScl})\n        bs.gameTimer(200,curve.delete)\n\n        if expire:\n            bs.gameTimer(defaultPowerupInterval-2500,bs.WeakCall(self._startFlashing))\n            bs.gameTimer(defaultPowerupInterval-1000,bs.WeakCall(self.handleMessage,bs.DieMessage()))\n\n    def delpor(self):\n        Portal.currentnum -= 1\n        self.port.delete()\n\n    def handleMessage(self,m):\n        self._handleMessageSanityCheck()\n\n        if isinstance(m,PowerupAcceptMessage):\n            factory = self.getFactory()\n            if self.powerupType == 'health':\n                bs.playSound(factory.healthPowerupSound,3,position=self.node.position)\n            bs.playSound(factory.powerupSound,3,position=self.node.position)\n            self._powersGiven = True\n            self.handleMessage(bs.DieMessage())\n\n        elif isinstance(m,_TouchedMessage):\n            if not self._powersGiven:\n                node = bs.getCollisionInfo(\"opposingNode\")\n                if node is not None and node.exists():\n                    #We won't tell the spaz about the bunny.  It'll just happen.\n                    if self.powerupType == 'bunny':\n                        p=node.getDelegate().getPlayer()\n                        if 'bunnies' not in p.gameData:\n                            p.gameData['bunnies'] = BuddyBunny.BunnyBotSet(p)\n                        p.gameData['bunnies'].doBunny()\n                        self._powersGiven = True\n                        self.handleMessage(bs.DieMessage())\n                    #a Spaz doesn't know what to do with a snoball powerup. All the snowball functionality\n                    #is handled through SnoBallz.py to minimize modifications to the original game files\n                    elif self.powerupType == 'snoball':\n                        spaz=node.getDelegate()\n                        SnoBallz.snoBall().getFactory().giveBallz(spaz)\n                        self._powersGiven = True\n                        self.handleMessage(bs.DieMessage())\n                    elif self.powerupType == 'portal':\n                        t = bsSpaz.gPowerupWearOffTime\n                        if Portal.currentnum < Portal.maxportals :\n                            Portal.currentnum += 1\n                            if self.node.position in Portal.lastpos :\n                                self.port = Portal.Portal(position1 = None,r = 0.9,color = (random.random(),random.random(),random.random()),activity = bs.getActivity())\n                                bs.gameTimer(t,bs.Call(self.delpor))\n                            else :\n                                m = self.node.position\n                                Portal.lastpos.append(m)\n                                self.port = Portal.Portal(position1 = self.node.position,r = 0.9,color = (random.random(),random.random(),random.random()),activity = bs.getActivity())\n                                bs.gameTimer(t,bs.Call(self.delpor))\n                        self._powersGiven = True\n                        self.handleMessage(bs.DieMessage())\n                    else:\n                        node.handleMessage(PowerupMessage(self.powerupType,sourceNode=self.node))\n                        \n        elif isinstance(m,bs.DieMessage):\n            if self.node.exists():\n                if (m.immediate):\n                    self.node.delete()\n                else:\n                    curve = bs.animate(self.node,\"modelScale\",{0:1,100:0})\n                    bs.gameTimer(100,self.node.delete)\n\n        elif isinstance(m,bs.OutOfBoundsMessage):\n            self.handleMessage(bs.DieMessage())\n\n        elif isinstance(m,bs.HitMessage):\n            # dont die on punches (thats annoying)\n            if m.hitType != 'punch':\n                self.handleMessage(bs.DieMessage())\n        else:\n            bs.Actor.handleMessage(self,m)\n\nbsPowerup.PowerupFactory = NewPowerupFactory\nbsPowerup.Powerup = NewPowerup\n"
  },
  {
    "path": "mods/surviveCurse.json",
    "content": "{\n  \"name\": \"Survive the Curse!\",\n  \"author\": \"joshville79\",\n  \"category\": \"minigames\"\n}"
  },
  {
    "path": "mods/surviveCurse.py",
    "content": "import bs\nimport random\nimport bsUtils\nimport bsPowerup\n\ndef bsGetAPIVersion():\n    # see bombsquadgame.com/apichanges\n    return 4\n\ndef bsGetGames():\n    return [SurviveCurseGame]\n\n\nclass Icon(bs.Actor):\n        \n    def __init__(self,player,position,scale,showLives=True,showDeath=True,\n                 nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0):\n        bs.Actor.__init__(self)\n\n        self._player = player\n        self._showLives = showLives\n        self._showDeath = showDeath\n        self._nameScale = nameScale\n\n        self._outlineTex = bs.getTexture('characterIconMask')\n        \n        icon = player.getIcon()\n        self.node = bs.newNode('image',\n                               owner=self,\n                               attrs={'texture':icon['texture'],\n                                      'tintTexture':icon['tintTexture'],\n                                      'tintColor':icon['tintColor'],\n                                      'vrDepth':400,\n                                      'tint2Color':icon['tint2Color'],\n                                      'maskTexture':self._outlineTex,\n                                      'opacity':1.0,\n                                      'absoluteScale':True,\n                                      'attach':'bottomCenter'})\n        self._nameText = bs.newNode('text',\n                                    owner=self.node,\n                                    attrs={'text':player.getName(),\n                                           'color':bs.getSafeColor(player.getTeam().color),\n                                           'hAlign':'center',\n                                           'vAlign':'center',\n                                           'vrDepth':410,\n                                           'maxWidth':nameMaxWidth,\n                                           'shadow':shadow,\n                                           'flatness':flatness,\n                                           'hAttach':'center',\n                                           'vAttach':'bottom'})\n        if self._showLives:\n            self._livesText = bs.newNode('text',\n                                         owner=self.node,\n                                         attrs={'text':'x0',\n                                                'color':(1,1,0.5),\n                                                'hAlign':'left',\n                                                'vrDepth':430,\n                                                'shadow':1.0,\n                                                'flatness':1.0,\n                                                'hAttach':'center',\n                                                'vAttach':'bottom'})\n        self.setPositionAndScale(position,scale)\n\n    def setPositionAndScale(self,position,scale):\n        self.node.position = position\n        self.node.scale = [70.0*scale]\n        self._nameText.position = (position[0],position[1]+scale*52.0)\n        self._nameText.scale = 1.0*scale*self._nameScale\n        if self._showLives:\n            self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0)\n            self._livesText.scale = 1.0*scale\n\n    def updateForLives(self):\n        if self._player.exists():\n            lives = self._player.gameData['lives']\n        else: lives = 0\n        if self._showLives:\n            if lives > 0: self._livesText.text = 'x'+str(lives-1)\n            else: self._livesText.text = ''\n        if lives == 0:\n            self._nameText.opacity = 0.2\n            self.node.color = (0.7,0.3,0.3)\n            self.node.opacity = 0.2\n        \n    def handlePlayerSpawned(self):\n        if not self.node.exists(): return\n        self.node.opacity = 1.0\n        self.updateForLives()\n\n    def handlePlayerDied(self):\n        if not self.node.exists(): return\n        if self._showDeath:\n            bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0,\n                                            300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2})\n            lives = self._player.gameData['lives']\n            if lives == 0: bs.gameTimer(600,self.updateForLives)\n        \nclass PlayerSpaz_Curse(bs.PlayerSpaz):\n    minExplodeTime = 0\n    curseDamageExplode = 350 #This is done to reduce likelihood of dying by damage. Normal curse is any damage at all.\n    def onJumpPress(self):\n        \"\"\"\n        Called to 'press jump' on this spaz;\n        used by player or AI connections.\n        This was just overridden to provide an easy way to get map extents.\n        \"\"\"\n        if not self.node.exists(): return\n        self.node.jumpPressed = True\n        #print(self.node.position)\n    def handleMessage(self,m):\n        if isinstance(m,bs.PowerupMessage): #Have to handle powerups ourselves\n            if self._dead: return True\n            if self.pickUpPowerupCallback is not None:\n                self.pickUpPowerupCallback(self)\n\n            if (m.powerupType == 'health'):\n                self.reCurse() #Just reset the curse timer\n            elif (m.powerupType == 'curse'):\n                self.curseExplodeNoShrapnel()\n            self.node.handleMessage(\"flash\")\n            if m.sourceNode.exists():\n                m.sourceNode.handleMessage(bs.PowerupAcceptMessage())\n            return True\n        elif isinstance(m,bs.HitMessage): #Have to override this whole message handling just to reduce chance of dying by damage while cursed\n            if not self.node.exists(): return\n            if self.node.invincible == True:\n                bs.playSound(self.getFactory().blockSound,1.0,position=self.node.position)\n                return True\n\n            # if we were recently hit, don't count this as another\n            # (so punch flurries and bomb pileups essentially count as 1 hit)\n            gameTime = bs.getGameTime()\n            if self._lastHitTime is None or gameTime-self._lastHitTime > 1000:\n                self._numTimesHit += 1\n                self._lastHitTime = gameTime\n            \n            mag = m.magnitude * self._impactScale\n            velocityMag = m.velocityMagnitude * self._impactScale\n\n            damageScale = 0.22\n\n            # if they've got a shield, deliver it to that instead..\n            if self.shield is not None:\n\n                if m.flatDamage: damage = m.flatDamage * self._impactScale\n                else:\n                    # hit our spaz with an impulse but tell it to only return theoretical damage; not apply the impulse..\n                    self.node.handleMessage(\"impulse\",m.pos[0],m.pos[1],m.pos[2],\n                                            m.velocity[0],m.velocity[1],m.velocity[2],\n                                            mag,velocityMag,m.radius,1,m.forceDirection[0],m.forceDirection[1],m.forceDirection[2])\n                    damage = damageScale * self.node.damage\n\n                self.shieldHitPoints -= damage\n\n                self.shield.hurt = 1.0 - float(self.shieldHitPoints)/self.shieldHitPointsMax\n                # its a cleaner event if a hit just kills the shield without damaging the player..\n                # however, massive damage events should still be able to damage the player..\n                # this hopefully gives us a happy medium.\n                # maxSpillover = 500\n                maxSpillover = self.getFactory().maxShieldSpilloverDamage\n                if self.shieldHitPoints <= 0:\n                    # fixme - transition out perhaps?..\n                    self.shield.delete()\n                    self.shield = None\n                    bs.playSound(self.getFactory().shieldDownSound,1.0,position=self.node.position)\n                    # emit some cool lookin sparks when the shield dies\n                    t = self.node.position\n                    bs.emitBGDynamics(position=(t[0],t[1]+0.9,t[2]),\n                                      velocity=self.node.velocity,\n                                      count=random.randrange(20,30),scale=1.0,spread=0.6,chunkType='spark')\n\n                else:\n                    bs.playSound(self.getFactory().shieldHitSound,0.5,position=self.node.position)\n\n                # emit some cool lookin sparks on shield hit\n                bs.emitBGDynamics(position=m.pos,\n                                  velocity=(m.forceDirection[0]*1.0,\n                                            m.forceDirection[1]*1.0,\n                                            m.forceDirection[2]*1.0),\n                                  count=min(30,5+int(damage*0.005)),scale=0.5,spread=0.3,chunkType='spark')\n\n\n                # if they passed our spillover threshold, pass damage along to spaz\n                if self.shieldHitPoints <= -maxSpillover:\n                    leftoverDamage = -maxSpillover-self.shieldHitPoints\n                    shieldLeftoverRatio = leftoverDamage/damage\n\n                    # scale down the magnitudes applied to spaz accordingly..\n                    mag *= shieldLeftoverRatio\n                    velocityMag *= shieldLeftoverRatio\n                else:\n                    return True # good job shield!\n            else: shieldLeftoverRatio = 1.0\n\n            if m.flatDamage:\n                damage = m.flatDamage * self._impactScale * shieldLeftoverRatio\n            else:\n                # hit it with an impulse and get the resulting damage\n                self.node.handleMessage(\"impulse\",m.pos[0],m.pos[1],m.pos[2],\n                                        m.velocity[0],m.velocity[1],m.velocity[2],\n                                        mag,velocityMag,m.radius,0,m.forceDirection[0],m.forceDirection[1],m.forceDirection[2])\n\n                damage = damageScale * self.node.damage\n            self.node.handleMessage(\"hurtSound\")\n\n            # play punch impact sound based on damage if it was a punch\n            if m.hitType == 'punch':\n\n                self.onPunched(damage)\n\n                # if damage was significant, lets show it\n                if damage > 350: bsUtils.showDamageCount('-'+str(int(damage/10))+\"%\",m.pos,m.forceDirection)\n                                               \n                # lets always add in a super-punch sound with boxing gloves just to differentiate them\n                if m.hitSubType == 'superPunch':\n                    bs.playSound(self.getFactory().punchSoundStronger,1.0,\n                                 position=self.node.position)\n\n                if damage > 500:\n                    sounds = self.getFactory().punchSoundsStrong\n                    sound = sounds[random.randrange(len(sounds))]\n                else: sound = self.getFactory().punchSound\n                bs.playSound(sound,1.0,position=self.node.position)\n\n                # throw up some chunks\n                bs.emitBGDynamics(position=m.pos,\n                                  velocity=(m.forceDirection[0]*0.5,\n                                            m.forceDirection[1]*0.5,\n                                            m.forceDirection[2]*0.5),\n                                  count=min(10,1+int(damage*0.0025)),scale=0.3,spread=0.03);\n\n                bs.emitBGDynamics(position=m.pos,\n                                  chunkType='sweat',\n                                  velocity=(m.forceDirection[0]*1.3,\n                                            m.forceDirection[1]*1.3+5.0,\n                                            m.forceDirection[2]*1.3),\n                                  count=min(30,1+int(damage*0.04)),\n                                  scale=0.9,\n                                  spread=0.28);\n                # momentary flash\n                hurtiness = damage*0.003\n                punchPos = (m.pos[0]+m.forceDirection[0]*0.02,\n                            m.pos[1]+m.forceDirection[1]*0.02,\n                            m.pos[2]+m.forceDirection[2]*0.02)\n                flashColor = (1.0,0.8,0.4)\n                light = bs.newNode(\"light\",\n                                   attrs={'position':punchPos,\n                                          'radius':0.12+hurtiness*0.12,\n                                          'intensity':0.3*(1.0+1.0*hurtiness),\n                                          'heightAttenuated':False,\n                                          'color':flashColor})\n                bs.gameTimer(60,light.delete)\n\n\n                flash = bs.newNode(\"flash\",\n                                   attrs={'position':punchPos,\n                                          'size':0.17+0.17*hurtiness,\n                                          'color':flashColor})\n                bs.gameTimer(60,flash.delete)\n\n            if m.hitType == 'impact':\n                bs.emitBGDynamics(position=m.pos,\n                                  velocity=(m.forceDirection[0]*2.0,\n                                            m.forceDirection[1]*2.0,\n                                            m.forceDirection[2]*2.0),\n                                  count=min(10,1+int(damage*0.01)),scale=0.4,spread=0.1);\n                \n            if self.hitPoints > 0:\n\n                # its kinda crappy to die from impacts, so lets reduce impact damage\n                # by a reasonable amount if it'll keep us alive\n                if m.hitType == 'impact' and damage > self.hitPoints:\n                    # drop damage to whatever puts us at 10 hit points, or 200 less than it used to be\n                    # whichever is greater (so it *can* still kill us if its high enough)\n                    newDamage = max(damage-200,self.hitPoints-10)\n                    damage = newDamage\n\n                self.node.handleMessage(\"flash\")\n                # if we're holding something, drop it\n                if damage > 0.0 and self.node.holdNode.exists():\n                    self.node.holdNode = bs.Node(None)\n                self.hitPoints -= damage\n                self.node.hurt = 1.0 - float(self.hitPoints)/self.hitPointsMax\n                # if we're cursed, *any* damage blows us up\n                if self._cursed and damage > self.curseDamageExplode:\n                    bs.gameTimer(50,bs.WeakCall(self.curseExplode,m.sourcePlayer))\n                # if we're frozen, shatter.. otherwise die if we hit zero\n                if self.frozen and (damage > 200 or self.hitPoints <= 0):\n                    self.shatter()\n                elif self.hitPoints <= 0:\n                    self.node.handleMessage(bs.DieMessage(how='impact'))\n\n            # if we're dead, take a look at the smoothed damage val\n            # (which gives us a smoothed average of recent damage) and shatter\n            # us if its grown high enough\n            if self.hitPoints <= 0:\n                damageAvg = self.node.damageSmoothed * damageScale\n                if damageAvg > 1000:\n                    self.shatter()\n        else:\n            super(self.__class__, self).handleMessage(m)\n    def reCurse(self):\n        self.node.curseDeathTime = bs.getGameTime()+5000\n        bs.gameTimer(5000,bs.WeakCall(self.curseExplodeIfNotReset))\n    \n    def curse(self):\n        \"\"\"\n        Give this poor spaz a curse;\n        he will explode in 5 seconds.\n        We have to override this from the parent class to allow\n        for resetting the curse timing.  Changed the WeakCall at the end\n        to curseExplodeIfNotReset instead of straight curseExplode.\n        \"\"\"\n        if not self._cursed:\n            factory = self.getFactory()\n            self._cursed = True\n            # add the curse material..\n            for attr in ['materials','rollerMaterials']:\n                materials = getattr(self.node,attr)\n                if not factory.curseMaterial in materials:\n                    setattr(self.node,attr,materials + (factory.curseMaterial,))\n\n            # -1 specifies no time limit\n            if self.curseTime == -1:\n                self.node.curseDeathTime = -1\n            else:\n                self.node.curseDeathTime = bs.getGameTime()+5000\n                bs.gameTimer(5000,bs.WeakCall(self.curseExplodeIfNotReset))\n                \n    def curseExplodeIfNotReset(self):\n        if self.node.exists():\n            if self.node.curseDeathTime <= bs.getGameTime():\n                self.curseExplodeNoShrapnel()\n    def curseExplodeNoShrapnel(self,sourcePlayer=None):\n        \"\"\"\n        Explode the poor spaz as happens when\n        a curse timer runs out. Less shrapnel for surviveCurse, just explode.\n        Otherwise, shrapnel hits other players too much. Player shrapnel causes instant\n        curse explosion of other players.  Over too quickly.\n        I could probably figure out how to prevent.  However, too lazy.\n        Making immediate=True in the DieMessage prevents shrapnel. \n        However, spaz node disappears instantly and no cleanup happens.\n        \"\"\"\n        # convert None to an empty player-ref\n        if sourcePlayer is None: sourcePlayer = bs.Player(None)\n        \n        if self._cursed and self.node.exists():\n            #self.shatter(extreme=True)\n            self.handleMessage(bs.DieMessage(immediate=False))\n            activity = self._activity()\n            if activity:\n                bs.Blast(position=self.node.position,\n                         velocity=self.node.velocity,\n                         blastRadius=3.0,blastType='normal',\n                         sourcePlayer=sourcePlayer if sourcePlayer.exists() else self.sourcePlayer).autoRetain()\n            self._cursed = False            \n        \nclass SurviveCurseGame(bs.TeamGameActivity):\n\n    @classmethod\n    def getName(cls):\n        return 'Survive the Curse!'\n\n    @classmethod\n    def getScoreInfo(cls):\n        return {'scoreName':'Survived',\n                'scoreType':'seconds',\n                'noneIsWinner':True}\n    \n    @classmethod\n    def getDescription(cls,sessionType):\n        return 'Last remaining alive wins.'\n\n    @classmethod\n    def supportsSessionType(cls,sessionType):\n        return True if (issubclass(sessionType,bs.TeamsSession)\n                        or issubclass(sessionType,bs.FreeForAllSession)) else False\n\n    @classmethod\n    def getSupportedMaps(cls,sessionType):\n        return ['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium']\n\n    @classmethod\n    def getSettings(cls,sessionType):\n        settings = [(\"Lives Per Player\",{'default':1,'minValue':1,'maxValue':1,'increment':1}),\n                    (\"Time Limit\",{'choices':[('None',0),('1 Minute',60),\n                                            ('2 Minutes',120),('5 Minutes',300),\n                                            ('10 Minutes',600),('20 Minutes',1200)],'default':0}),\n                    (\"Respawn Times\",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}),\n                    (\"Box Reduction Rate\",{'choices':[('Faster',0.1),('Fast',0.07),('Normal',0.05),('Slow',0.03),('Slower',0.01)],'default':0.05}),\n                    (\"Curse Box Chance (lower = more chance)\",{'default':10,'minValue':5,'maxValue':15,'increment':1}),\n                    (\"Epic Mode\",{'default':False})]\n\n        if issubclass(sessionType,bs.TeamsSession):\n            settings.append((\"Solo Mode\",{'default':False}))\n            settings.append((\"Balance Total Lives\",{'default':False}))\n            \n        return settings\n\n    def __init__(self,settings):\n        bs.TeamGameActivity.__init__(self,settings)\n        if self.settings['Epic Mode']: self._isSlowMotion = True\n\n        # show messages when players die since it's meaningful here\n        self.announcePlayerDeaths = True\n        \n        try: self._soloMode = settings['Solo Mode']\n        except Exception: self._soloMode = False\n        self._scoreBoard = bs.ScoreBoard()\n\n    def getInstanceDescription(self):\n        return 'Last team standing wins.' if isinstance(self.getSession(),bs.TeamsSession) else 'Last one standing wins.'\n\n    def getInstanceScoreBoardDescription(self):\n        return 'last team standing wins' if isinstance(self.getSession(),bs.TeamsSession) else 'last one standing wins'\n\n    def onTransitionIn(self):\n        bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival')\n        self._startGameTime = bs.getGameTime()\n\n    def onTeamJoin(self,team):\n        team.gameData['survivalSeconds'] = None\n        team.gameData['spawnOrder'] = []\n\n    def onPlayerJoin(self, player):\n\n        # no longer allowing mid-game joiners here... too easy to exploit\n        if self.hasBegun():\n            player.gameData['lives'] = 0\n            player.gameData['icons'] = []\n            # make sure our team has survival seconds set if they're all dead\n            # (otherwise blocked new ffa players would be considered 'still alive' in score tallying)\n            if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None:\n                player.getTeam().gameData['survivalSeconds'] = 0\n            bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0))\n            return\n        \n        player.gameData['lives'] = self.settings['Lives Per Player']\n\n        if self._soloMode:\n            player.gameData['icons'] = []\n            player.getTeam().gameData['spawnOrder'].append(player)\n            self._updateSoloMode()\n        else:\n            # create our icon and spawn\n            player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)]\n            if player.gameData['lives'] > 0:\n                self.spawnPlayer(player)\n\n        # dont waste time doing this until begin\n        if self.hasBegun():\n            self._updateIcons()\n\n    def _updateSoloMode(self):\n        # for both teams, find the first player on the spawn order list with lives remaining\n        # and spawn them if they're not alive\n        for team in self.teams:\n            # prune dead players from the spawn order\n            team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n            for player in team.gameData['spawnOrder']:\n                if player.gameData['lives'] > 0:\n                    if not player.isAlive(): self.spawnPlayer(player)\n                    break\n\n    def _updateIcons(self):\n        # in free-for-all mode, everyone is just lined up along the bottom\n        if isinstance(self.getSession(),bs.FreeForAllSession):\n            count = len(self.teams)\n            xOffs = 85\n            x = xOffs*(count-1) * -0.5\n            for i,team in enumerate(self.teams):\n                if len(team.players) == 1:\n                    player = team.players[0]\n                    for icon in player.gameData['icons']:\n                        icon.setPositionAndScale((x,30),0.7)\n                        icon.updateForLives()\n                    x += xOffs\n\n        # in teams mode we split up teams\n        else:\n            if self._soloMode:\n                # first off, clear out all icons\n                for player in self.players:\n                    player.gameData['icons'] = []\n                # now for each team, cycle through our available players adding icons\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -60\n                        xOffs = -78\n                    else:\n                        x = 60\n                        xOffs = 78\n                    isFirst = True\n                    testLives = 1\n                    while True:\n                        playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives]\n                        if len(playersWithLives) == 0: break\n                        for player in playersWithLives:\n                            player.gameData['icons'].append(Icon(player,\n                                                                 position=(x,(40 if isFirst else 25)),\n                                                                 scale=1.0 if isFirst else 0.5,\n                                                                 nameMaxWidth=130 if isFirst else 75,\n                                                                 nameScale=0.8 if isFirst else 1.0,\n                                                                 flatness=0.0 if isFirst else 1.0,\n                                                                 shadow=0.5 if isFirst else 1.0,\n                                                                 showDeath=True if isFirst else False,\n                                                                 showLives=False))\n                            x += xOffs * (0.8 if isFirst else 0.56)\n                            isFirst = False\n                        testLives += 1\n            # non-solo mode\n            else:\n                for team in self.teams:\n                    if team.getID() == 0:\n                        x = -50\n                        xOffs = -85\n                    else:\n                        x = 50\n                        xOffs = 85\n                    for player in team.players:\n                        for icon in player.gameData['icons']:\n                            icon.setPositionAndScale((x,30),0.7)\n                            icon.updateForLives()\n                        x += xOffs\n                    \n    def _getSpawnPoint(self,player):\n        # in solo-mode, if there's an existing live player on the map, spawn at whichever\n        # spot is farthest from them (keeps the action spread out)\n        if self._soloMode:\n            livingPlayer = None\n            for team in self.teams:\n                for player in team.players:\n                    if player.isAlive():\n                        p = player.actor.node.position\n                        livingPlayer = player\n                        livingPlayerPos = p\n                        break\n            if livingPlayer:\n                playerPos = bs.Vector(*livingPlayerPos)\n                points = []\n                for team in self.teams:\n                    startPos = bs.Vector(*self.getMap().getStartPosition(team.getID()))\n                    points.append([(startPos-playerPos).length(),startPos])\n                points.sort()\n                return points[-1][1]\n            else:\n                return None\n        else:\n            return None\n\n        \n    def spawnPlayer(self,player):\n        self.spawnPlayerSpaz(player,self._getSpawnPoint(player))\n        if not self._soloMode:\n            bs.gameTimer(300,bs.Call(self._printLives,player))\n\n        # if we have any icons, update their state\n        for icon in player.gameData['icons']:\n            icon.handlePlayerSpawned()\n            \n    def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None):\n        \"\"\"\n        Create and wire up a bs.PlayerSpaz for the provide bs.Player.\n        \"\"\"\n        position = self.getMap().getFFAStartPosition(self.players)\n        name = player.getName()\n        color = player.color\n        highlight = player.highlight\n\n        lightColor = bsUtils.getNormalizedColor(color)\n        displayColor = bs.getSafeColor(color,targetIntensity=0.75)\n        spaz = PlayerSpaz_Curse(color=color,\n                             highlight=highlight,\n                             character=player.character,\n                             player=player)\n        player.setActor(spaz)\n\n        # we want a bigger area-of-interest in co-op mode\n        # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0\n        # else: spaz.node.areaOfInterestRadius = 5.0\n\n        # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to\n        # collide with the player-walls\n        # FIXME; need to generalize this\n        if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']:\n            mat = self.getMap().preloadData['collideWithWallMaterial']\n            spaz.node.materials += (mat,)\n            spaz.node.rollerMaterials += (mat,)\n        \n        spaz.node.name = name\n        spaz.node.nameColor = displayColor\n        spaz.connectControlsToPlayer()\n        self.scoreSet.playerGotNewSpaz(player,spaz)\n\n        # move to the stand position and add a flash of light\n        spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360)))\n        t = bs.getGameTime()\n        bs.playSound(self._spawnSound,1,position=spaz.node.position)\n        light = bs.newNode('light',attrs={'color':lightColor})\n        spaz.node.connectAttr('position',light,'position')\n        bsUtils.animate(light,'intensity',{0:0,250:1,500:0})\n        bs.gameTimer(500,light.delete)\n        return spaz\n        \n    def _printLives(self,player):\n        if not player.exists() or not player.isAlive(): return\n        try: pos = player.actor.node.position\n        except Exception,e:\n            print 'EXC getting player pos in bsElim',e\n            return\n        bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1),\n                           offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain()\n\n    def onPlayerLeave(self,player):\n\n        bs.TeamGameActivity.onPlayerLeave(self,player)\n\n        player.gameData['icons'] = None\n\n        # remove us from spawn-order\n        if self._soloMode:\n            if player in player.getTeam().gameData['spawnOrder']:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n\n        # update icons in a moment since our team will be gone from the list then\n        bs.gameTimer(0, self._updateIcons)\n\n\n    def onBegin(self):\n        bs.TeamGameActivity.onBegin(self)\n        self.setupStandardTimeLimit(self.settings['Time Limit'])\n        #self.setupStandardPowerupDrops() #No standard powerups.  We'll drop 'em from the sky.\n\n        if self._soloMode:\n            self._vsText = bs.NodeActor(bs.newNode(\"text\",\n                                                   attrs={'position':(0,105),\n                                                          'hAttach':\"center\",\n                                                          'hAlign':'center',\n                                                          'maxWidth':200,\n                                                          'shadow':0.5,\n                                                          'vrDepth':390,\n                                                          'scale':0.6,\n                                                          'vAttach':\"bottom\",\n                                                          'color':(0.8,0.8,0.3,1.0),\n                                                          'text':bs.Lstr(resource='vsText')}))\n\n        # if balance-team-lives is on, add lives to the smaller team until total lives match\n        if (isinstance(self.getSession(),bs.TeamsSession)\n            and self.settings['Balance Total Lives']\n            and len(self.teams[0].players) > 0\n            and len(self.teams[1].players) > 0):\n            if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]):\n                lesserTeam = self.teams[0]\n                greaterTeam = self.teams[1]\n            else:\n                lesserTeam = self.teams[1]\n                greaterTeam = self.teams[0]\n            addIndex = 0\n            while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam):\n                lesserTeam.players[addIndex].gameData['lives'] += 1\n                addIndex = (addIndex + 1) % len(lesserTeam.players)\n\n        self._updateIcons()\n        for pName in self.scoreSet._players:\n            spz = self.scoreSet._players[pName].getSpaz()\n            if not spz is None:\n                bs.gameTimer(1500,bs.WeakCall(spz.curse)) #Curse you all!\n            \n        #bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[0], powerupType='health',expire=False).autoRetain()\n        #bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[1], powerupType='curse',expire=False).autoRetain()\n        #bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[3], powerupType='health',expire=False).autoRetain()\n        #bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[2], powerupType='curse',expire=False).autoRetain()\n        self.boxMult = 4.0\n        self.totBoxes = []\n        self.boxSpawn()\n        # we could check game-over conditions at explicit trigger points,\n        # but lets just do the simple thing and poll it...\n        bs.gameTimer(1000, self._update, repeat=True)\n        \n        \n    def _getTotalTeamLives(self,team):\n        return sum(player.gameData['lives'] for player in team.players)\n        \n\n        \n    def handleMessage(self,m):\n        if isinstance(m,bs.PlayerSpazDeathMessage):\n            \n            bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior\n            player = m.spaz.getPlayer()\n\n            player.gameData['lives'] -= 1\n            if player.gameData['lives'] < 0:\n                bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode))\n                player.gameData['lives'] = 0\n\n            # if we have any icons, update their state\n            for icon in player.gameData['icons']:\n                icon.handlePlayerDied()\n\n            # play big death sound on our last death or for every one in solo mode\n            if self._soloMode or player.gameData['lives'] == 0:\n                bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound)\n\n            # if we hit zero lives, we're dead (and our team might be too)\n            if player.gameData['lives'] == 0:\n                # if the whole team is now dead, mark their survival time..\n                #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players):\n                if self._getTotalTeamLives(player.getTeam()) == 0:\n                    player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000\n            else:\n                # otherwise, in regular mode, respawn..\n                if not self._soloMode:\n                    self.respawnPlayer(player)\n\n            # in solo, put ourself at the back of the spawn order\n            if self._soloMode:\n                player.getTeam().gameData['spawnOrder'].remove(player)\n                player.getTeam().gameData['spawnOrder'].append(player)\n                \n    def boxSpawn(self):\n        Plyrs = 0\n        for team in self.teams:\n            for player in team.players:\n                if player.gameData['lives'] > 0:\n                    Plyrs += 1\n                    \n        maxBoxes = Plyrs * self.boxMult\n        if maxBoxes > 16:\n            maxBoxes = 16\n        for box in self.totBoxes:\n            if not box.exists():\n                self.totBoxes.remove(box)\n        while len(self.totBoxes) < maxBoxes:\n            #print([Plyrs, self.boxMult,len(self.totBoxes), maxBoxes])\n            if random.randint(1,self.settings[\"Curse Box Chance (lower = more chance)\"]) == 1:\n                self.totBoxes.append(bsPowerup.Powerup(position=self.getRandomPowerupPoint(), powerupType='curse',expire=False).autoRetain())\n            else:\n                self.totBoxes.append(bsPowerup.Powerup(position=self.getRandomPowerupPoint(), powerupType='health',expire=False).autoRetain())\n        self.boxMult -= self.settings[\"Box Reduction Rate\"]\n        \n    def getRandomPowerupPoint(self):\n        #So far, randomized points only figured out for mostly rectangular maps.\n        #Boxes will still fall through holes, but shouldn't be terrible problem (hopefully)\n        #If you add stuff here, need to add to \"supported maps\" above.\n        #['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium']\n        myMap = self.getMap().getName()\n        #print(myMap)\n        if myMap == 'Doom Shroom':\n            while True:\n                x = random.uniform(-1.0,1.0)\n                y = random.uniform(-1.0,1.0)\n                if x*x+y*y < 1.0: break\n            return ((8.0*x,8.0,-3.5+5.0*y))\n        elif myMap == 'Rampage':\n            x = random.uniform(-6.0,7.0)\n            y = random.uniform(-6.0,-2.5)\n            return ((x, 8.0, y))\n        elif myMap == 'Hockey Stadium':\n            x = random.uniform(-11.5,11.5)\n            y = random.uniform(-4.5,4.5)\n            return ((x, 5.0, y))\n        elif myMap == 'Courtyard':\n            x = random.uniform(-4.3,4.3)\n            y = random.uniform(-4.4,0.3)\n            return ((x, 8.0, y))\n        elif myMap == 'Crag Castle':\n            x = random.uniform(-6.7,8.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 12.0, y))\n        elif myMap == 'Big G':\n            x = random.uniform(-8.7,8.0)\n            y = random.uniform(-7.5,6.5)\n            return ((x, 8.0, y))\n        elif myMap == 'Football Stadium':\n            x = random.uniform(-12.5,12.5)\n            y = random.uniform(-5.0,5.5)\n            return ((x, 8.0, y))\n        else:\n            x = random.uniform(-5.0,5.0)\n            y = random.uniform(-6.0,0.0)\n            return ((x, 8.0, y))\n    def _update(self):\n\n        if self._soloMode:\n            # for both teams, find the first player on the spawn order list with lives remaining\n            # and spawn them if they're not alive\n            for team in self.teams:\n                # prune dead players from the spawn order\n                team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()]\n                for player in team.gameData['spawnOrder']:\n                    if player.gameData['lives'] > 0:\n                        if not player.isAlive():\n                            self.spawnPlayer(player)\n                            self._updateIcons()\n                        break\n        \n        # if we're down to 1 or fewer living teams, start a timer to end the game\n        # (allows the dust to settle and draws to occur if deaths are close enough)\n        self.boxSpawn()\n        if len(self._getLivingTeams()) < 2:\n            self._roundEndTimer = bs.Timer(500,self.endGame)\n\n\n    def _getLivingTeams(self):\n        return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)]\n\n    def endGame(self):\n        if self.hasEnded(): return\n        results = bs.TeamGameResults()\n        self._vsText = None # kill our 'vs' if its there\n        for team in self.teams:\n            results.setTeamScore(team, team.gameData['survivalSeconds'])\n        self.end(results=results)\n        \n"
  },
  {
    "path": "mods/ui_wrappers.json",
    "content": "{\n  \"name\": \"ui_wrappers\",\n  \"author\": \"Mrmaxmeier\",\n  \"category\": \"libraries\"\n}\n"
  },
  {
    "path": "mods/ui_wrappers.py",
    "content": "import bs\n\nDEBUG = False\n\n\nclass Widget(bs.Widget):\n    _instance = None\n    _values = dict(upWidget=None, downWidget=None, leftWidget=None, rightWidget=None,\n                   showBufferTop=None, showBufferBottom=None, showBufferLeft=None,\n                   showBufferRight=None, autoSelect=None)\n    _required = []\n    _func = bs.widget\n    _can_create = False\n    _values_funcs = {}\n\n    def __init__(self, **kwargs):\n        if not self._can_create:\n            raise Exception(\"cant create widget of type \" + str(self.__class__))\n        for key in self._required:\n            if key not in kwargs:\n                raise ValueError(\"expected \" + key)\n        self._instance = self._call_func(self._func, kwargs)\n        self._values_funcs = {}\n        self._values = {}\n        for cls in [self.__class__] + list(self.__class__.__bases__):\n            self._values_funcs[cls._func] = cls._values\n            self._values.update(cls._values)\n        self._values.update(kwargs)\n\n    def _call_func(self, func, kwargs):\n        d = {}\n        for key, value in kwargs.items():\n            d[key] = value\n            if isinstance(value, Widget):\n                d[key] = value._instance\n        if DEBUG:\n            print(\"bs.{}(**{})\".format(func.__name__, d))\n        return func(**d)\n\n    def set(self, **kwargs):\n        for key, value in kwargs.items():\n            setattr(self, key, value)\n\n    def reset_value(self, key):\n        setattr(self, key, self.__class__._values[key])\n\n    def activate(self, *args, **kwargs):\n        return self._instance.activate(*args, **kwargs)\n\n    def delete(self, *args, **kwargs):\n        return self._instance.delete(*args, **kwargs)\n\n    def exists(self, *args, **kwargs):\n        return self._instance.exists(*args, **kwargs)\n\n    def getChildren(self, *args, **kwargs):\n        return self._instance.getChildren(*args, **kwargs)\n\n    def getScreenSpaceCenter(self, *args, **kwargs):\n        return self._instance.getScreenSpaceCenter(*args, **kwargs)\n\n    def getSelectedChild(self, *args, **kwargs):\n        return self._instance.getSelectedChild(*args, **kwargs)\n\n    def getWidgetType(self, *args, **kwargs):\n        return self._instance.getWidgetType(*args, **kwargs)\n\n    def __getattr__(self, key):\n        if hasattr(self._instance, key):\n            return getattr(self._instance, key)\n        if key in self._values:\n            return self._values[key]\n        raise AttributeError(\"type object '{}' has no attribute '{}'\".format(type(self), key))\n\n    def __setattr__(self, key, value):\n        if DEBUG:\n            print(\"__setattr__({}, {})\".format(repr(key), value))\n        for func, values in self._values_funcs.items():\n            if key in values:\n                self._call_func(func, {\"edit\": self._instance, key: value})\n                self._values[key] = value\n                return\n        self.__dict__[key] = value\n\n    def __repr__(self):\n        return object.__repr__(self)\n\n    def __str__(self):\n        return object.__str__(self)\n\n\nclass TextWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, vAlign=None, hAlign=None, editable=False,\n                   padding=None, onReturnPressCall=None, selectable=None, onActivateCall=None,\n                   query=None, maxChars=None, color=None, clickActivate=None, scale=None,\n                   alwaysHighlight=None, drawController=None, description=None, transitionDelay=None,\n                   flatness=None, enabled=None, forceInternalEditing=False, alwaysShowCarat=None,\n                   maxWidth=None, maxHeight=None, big=False)  # FIXME: check default values\n    _required = [\"parent\"]\n    _func = bs.textWidget\n    _can_create = True\n\n    # FIXME: textWidget.set(text=...) shadows instance method\n    def text(self):\n        return self._func(query=self._instance)\n\n\nclass ButtonWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, onActivateCall=None, label=None,\n                   color=None, texture=None, textScale=None, enableSound=True, modelTransparent=None,\n                   modelOpaque=None, transitionDelay=None, onSelectCall=None, extraTouchBorderScale=None,\n                   buttonType=None, touchOnly=None, showBufferTop=None, icon=None, iconScale=None,\n                   iconTint=None, iconColor=None, autoSelect=None, repeat=None, maskTexture=None,\n                   tintTexture=None, tintColor=None)  # FIXME: check default values\n    _required = [\"parent\", \"position\", \"size\"]\n    _func = bs.buttonWidget\n    _can_create = True\n    COLOR_GREY = (0.52, 0.48, 0.63)\n    TEXTCOLOR_GREY = (0.65, 0.6, 0.7)\n\n\nclass CheckBoxWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, value=None, clickSelect=None,\n                   onActivateCall=None, onValueChangeCall=None, onSelectCall=None,\n                   isRadioButton=False, scale=None, maxWidth=None, autoSelect=None, color=None)  # FIXME: check default values\n    _required = [\"parent\", \"position\"]\n    _func = bs.checkBoxWidget\n    _can_create = True\n\n    def __init__(self, **kwargs):\n        super(self.__class__, self).__init__(**kwargs)\n        if not self.onValueChangeCall:\n            def f(val):\n                print(val)\n                self._values[\"value\"] = val\n            self._func(edit=self._instance, onValueChangeCall=f)\n\n    def _call_func(self, func, kwargs):\n        d = {}\n        for key, value in kwargs.items():\n            d[key] = value\n            if isinstance(value, Widget):\n                d[key] = value._instance\n            if key == \"onValueChangeCall\":\n                def w(value):\n                    def f(val):\n                        self._values[\"value\"] = val\n                        value(val)\n                    return f\n                d[key] = w(value)\n        return func(**d)\n\n\nclass ContainerWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, selectedChild=None, transition=None,\n                   cancelButton=None, startButton=None, rootSelectable=None, onActivateCall=None,\n                   claimsLeftRight=None, claimsTab=None, selectionLoops=None, selectionLoopToParent=None,\n                   scale=None, type=None, onOutsideClickCall=None, singleDepth=None, visibleChild=None,\n                   stackOffset=None, color=None, onCancelCall=None, printListExitInstructions=None,\n                   clickActivate=None, alwaysHighlight=None, selectable=None, scaleOriginStackOffset=None)  # FIXME: check default values\n    _required = [\"size\"]\n    _func = bs.containerWidget\n    _can_create = True\n\n    def doTransition(self, transition):\n        self.set(transition=transition)\n\n\nclass ScrollWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, captureArrows=False, onSelectCall=None,\n                   centerSmallContent=None, color=None, highlight=None, borderOpacity=None)  # FIXME: check default values\n    _required = [\"parent\", \"position\", \"size\"]\n    _func = bs.scrollWidget\n    _can_create = True\n\n\nclass ColumnWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, singleDepth=None,\n                   printListExitInstructions=None, leftBorder=None,\n                   selectedChild=None, visibleChild=None)  # FIXME: check default values\n    _required = [\"parent\"]\n    _func = bs.columnWidget\n    _can_create = True\n\n\nclass HScrollWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, captureArrows=False, onSelectCall=None,\n                   centerSmallContent=None, color=None, highlight=None, borderOpacity=None)  # FIXME: check default values\n    _required = [\"parent\", \"position\", \"size\"]\n    _func = bs.hScrollWidget\n    _can_create = True\n\n\nclass ImageWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, color=None, texture=None,\n                   model=None, modelTransparent=None, modelOpaque=None, hasAlphaChannel=True,\n                   tintTexture=None, tintColor=None, transitionDelay=None, drawController=None,\n                   tiltScale=None, maskTexture=None)  # FIXME: check default values\n    _required = [\"parent\", \"size\", \"position\"]\n    _func = bs.imageWidget\n    _can_create = True\n\n\nclass RowWidget(Widget):\n    _values = dict(parent=None, size=None, position=None, selectable=False)\n    _required = [\"parent\", \"size\", \"position\"]\n    _func = bs.rowWidget\n    _can_create = True\n"
  },
  {
    "path": "requirements.txt",
    "content": "gitpython>=0.3.5"
  },
  {
    "path": "server/.gitignore",
    "content": "target\n"
  },
  {
    "path": "server/Cargo.toml",
    "content": "[package]\nauthors = [\"Mrmaxmeier <Mrmaxmeier@gmail.com>\"]\nname = \"server\"\nversion = \"0.1.0\"\n\n[dependencies]\nnickel = \"^0.8.0\"\nplugin = \"^0.2.6\"\nr2d2 = \"^0.7.0\"\nr2d2_redis = \"0.4.0\"\nredis = \"^0.6.0\"\nrustc-serialize = \"^0.3.19\"\ntypemap = \"^0.3.3\"\n"
  },
  {
    "path": "server/src/main.rs",
    "content": "extern crate rustc_serialize;\n#[macro_use]\nextern crate nickel;\nextern crate redis;\nextern crate r2d2;\nextern crate r2d2_redis;\nextern crate plugin;\nextern crate typemap;\n\nextern crate core;\n\nuse std::env;\nuse std::collections::HashMap;\nuse r2d2::NopErrorHandler;\nuse nickel::{Nickel, HttpRouter, JsonBody, MediaType, QueryString};\nuse nickel::status::StatusCode;\nuse core::ops::Deref;\nuse redis::{Commands, Connection, RedisError};\nuse rustc_serialize::{Decodable, Decoder, Encodable, Encoder};\nuse rustc_serialize::json;\n\nuse redis_middleware::{RedisMiddleware, RedisRequestExtensions};\nmod redis_middleware;\n\n#[derive(RustcDecodable, RustcEncodable)]\nstruct RatingSubmission {\n    uuid: String,\n    mod_str: String,\n    rating: Rating,\n}\n\n#[derive(RustcDecodable, RustcEncodable)]\nstruct DownloadSubmission {\n    uuid: String,\n    mod_str: String,\n}\n\n#[derive(Clone, Copy, Debug)]\nenum Rating {\n    Poor,\n    BelowAverage,\n    Average,\n    AboveAverage,\n    Excellent,\n}\n\nimpl From<usize> for Rating {\n    fn from(rating: usize) -> Self {\n        match rating {\n            0 => Rating::Poor,\n            1 => Rating::BelowAverage,\n            2 => Rating::Average,\n            3 => Rating::AboveAverage,\n            _ => Rating::Excellent,\n        }\n    }\n}\n\nimpl Decodable for Rating {\n    fn decode<D: Decoder>(d: &mut D) -> Result<Rating, D::Error> {\n        let r = try!(d.read_usize());\n        Ok(Rating::from(r))\n    }\n}\n\nimpl Encodable for Rating {\n    fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {\n        let as_usize = *self as usize;\n        s.emit_usize(as_usize)\n    }\n}\n\n#[derive(RustcEncodable)]\nstruct StatsResults {\n    average_ratings: HashMap<String, Rating>,\n    amount_ratings: HashMap<String, usize>,\n    own_ratings: Option<HashMap<String, Rating>>,\n    downloads: HashMap<String, usize>,\n}\n\nfn incr_requests(conn: &Connection) {\n    if let Err(_) = conn.incr::<_, _, usize>(\"requests\", 1) {\n        println!(\"failed to incr request counter.\")\n    }\n}\n\nfn get_mod_rating(conn: &Connection, mod_str: &str) -> Result<(Rating, usize), RedisError> {\n    let ratings = try!(conn.hvals::<_, Vec<usize>>(format!(\"{}_ratings\", mod_str)));\n    let length = ratings.len();\n    let mut sum = 0;\n    for rating in ratings {\n        sum += rating;\n    }\n    match length {\n        0 => Ok((Rating::Poor, length)),\n        _ => Ok((Rating::from(sum / length), length)),\n    }\n}\n\nfn get_mod_downloads(conn: &Connection, mod_str: &str) -> Result<usize, RedisError> {\n    let downloads = try!(conn.hvals::<_, Vec<usize>>(format!(\"{}_downloads\", mod_str)));\n    Ok(downloads.iter().fold(0, |acc, &x| acc + x))\n}\n\n\nconst OK_RESP: (StatusCode, &'static str) = (StatusCode::Ok, \"ok\");\n\nfn main() {\n    let mut webserver = Nickel::new();\n\n    let redis_url = env::var(\"DATABASE_URL\").unwrap_or(\"redis://localhost/3\".to_owned());\n    println!(\"connecting to redis @ {}\", redis_url);\n\n    let redispool = RedisMiddleware::new(&*redis_url, 3, Box::new(NopErrorHandler)).unwrap();\n    webserver.utilize(redispool);\n\n    webserver.post(\"/submit_rating\",\n                   middleware! { |request, response|\n        let rcn_ref = request.redis_conn();\n        let redis_conn = rcn_ref.deref();\n        incr_requests(redis_conn);\n        let sbm = try_with!(response, {\n            request.json_as::<RatingSubmission>().map_err(|e| (StatusCode::BadRequest, e))\n        });\n        println!(\"{} rates {} as {:?}\", sbm.uuid, sbm.mod_str, sbm.rating);\n        let _: bool = try_with!(response, {\n            redis_conn.hset(\"mods\", &*sbm.mod_str, true).map_err(|e| (StatusCode::BadRequest, e))\n        });\n        let _: usize = try_with!(response, {\n            let key = format!(\"{}_ratings\", sbm.mod_str);\n            redis_conn.hset(key, sbm.uuid, sbm.rating as usize).map_err(|e|\n                (StatusCode::BadRequest, e)\n            )\n        });\n        OK_RESP\n    });\n\n    webserver.get(\"/rating/:mod\",\n                  middleware! { |request, response|\n        let rcn_ref = request.redis_conn();\n        let redis_conn = rcn_ref.deref();\n        incr_requests(redis_conn);\n        let mod_str = request.param(\"mod\").unwrap();\n        let result = try_with!(response, {\n            get_mod_rating(redis_conn, mod_str).map_err(|e| (StatusCode::BadRequest, e))\n        });\n        match result {\n            (_, 0) => (StatusCode::NotFound, \"Not Found!\".to_owned()),\n            (rating, sbm) => (StatusCode::Ok, format!(\"{:?}, {} submissions\", rating, sbm)),\n        }\n    });\n\n    webserver.get(\"/stats\",\n                  middleware! { |request, mut response|\n        let rcn_ref = request.redis_conn();\n        let redis_conn = rcn_ref.deref();\n        incr_requests(&redis_conn);\n\n        let mods = try_with!(response, {\n            redis_conn.hkeys::<_, Vec<String>>(\"mods\").map_err(|e| (StatusCode::BadRequest, e))\n        });\n\n        let mut own_ratings: HashMap<String, Rating> = HashMap::new();\n        let mut amount_ratings: HashMap<String, usize> = HashMap::new();\n        let mut average_ratings: HashMap<String, Rating> = HashMap::new();\n        let mut mod_downloads: HashMap<String, usize> = HashMap::new();\n\n        for mod_str in mods {\n            let mod_str = mod_str.as_str();\n\n            let downloads = try_with!(response, {\n                get_mod_downloads(redis_conn, mod_str).map_err(|e| (StatusCode::BadRequest, e))\n            });\n            mod_downloads.insert(mod_str.to_owned(), downloads);\n\n            let (rating, sbm) = try_with!(response, {\n                get_mod_rating(redis_conn, mod_str).map_err(|e| (StatusCode::BadRequest, e))\n            });\n            if sbm == 0 {\n                continue;\n            }\n            average_ratings.insert(mod_str.to_owned(), rating);\n            amount_ratings.insert(mod_str.to_owned(), sbm);\n            if let Some(uuid) = request.query().get(\"uuid\") {\n                if let Ok(rating) = redis_conn.hget::<_, _, usize>(mod_str, uuid) {\n                    own_ratings.insert(mod_str.to_owned(), Rating::from(rating));\n                }\n            }\n        }\n\n        let result = StatsResults {\n            average_ratings: average_ratings,\n            amount_ratings: amount_ratings,\n            own_ratings: match request.query().get(\"uuid\") {\n                Some(_) => Some(own_ratings),\n                None => None,\n            },\n            downloads: mod_downloads,\n        };\n        response.set(MediaType::Json);\n        json::encode(&result).unwrap()\n    });\n\n    webserver.post(\"/submit_download\",\n                   middleware! { |request, response|\n        let rcn_ref = request.redis_conn();\n        let redis_conn = rcn_ref.deref();\n        incr_requests(redis_conn);\n        let sbm = try_with!(response, {\n            request.json_as::<DownloadSubmission>().map_err(|e| (StatusCode::BadRequest, e))\n        });\n        println!(\"{} downloaded {}\", sbm.uuid, sbm.mod_str);\n        let _: bool = try_with!(response, {\n            redis_conn.hset(\"mods\", &*sbm.mod_str, true).map_err(|e| (StatusCode::BadRequest, e))\n        });\n        let _: usize = try_with!(response, {\n            redis_conn.hincr(format!(\"{}_downloads\", sbm.mod_str), sbm.uuid, 1).map_err(|e|\n                (StatusCode::BadRequest, e)\n            )\n        });\n        OK_RESP\n    });\n\n    webserver.listen(\"127.0.0.1:7998\")\n}\n"
  },
  {
    "path": "server/src/redis_middleware.rs",
    "content": "\nuse std::sync::Arc;\nuse std::error::Error as StdError;\n\nuse nickel::{Request, Response, Middleware, Continue, MiddlewareResult};\nuse r2d2_redis::RedisConnectionManager;\nuse r2d2::{Pool, HandleError, Config, PooledConnection};\nuse typemap::Key;\nuse plugin::Extensible;\n\npub struct RedisMiddleware {\n    pub pool: Arc<Pool<RedisConnectionManager>>,\n}\n\nimpl RedisMiddleware {\n    pub fn new(connect_str: &str,\n               num_connections: u32,\n               error_handler: Box<HandleError<::r2d2_redis::Error>>)\n               -> Result<RedisMiddleware, Box<StdError>> {\n        let manager = try!(RedisConnectionManager::new(connect_str));\n\n        let config = Config::builder()\n            .pool_size(num_connections)\n            .error_handler(error_handler)\n            .build();\n\n        let pool = try!(Pool::new(config, manager));\n\n        Ok(RedisMiddleware { pool: Arc::new(pool) })\n    }\n}\n\nimpl Key for RedisMiddleware {\n    type Value = Arc<Pool<RedisConnectionManager>>;\n}\n\nimpl<D> Middleware<D> for RedisMiddleware {\n    fn invoke<'mw, 'conn>(&self,\n                          req: &mut Request<'mw, 'conn, D>,\n                          res: Response<'mw, D>)\n                          -> MiddlewareResult<'mw, D> {\n        req.extensions_mut().insert::<RedisMiddleware>(self.pool.clone());\n        Ok(Continue(res))\n    }\n}\n\npub trait RedisRequestExtensions {\n    fn redis_conn(&self) -> PooledConnection<RedisConnectionManager>;\n}\n\nimpl<'a, 'b, D> RedisRequestExtensions for Request<'a, 'b, D> {\n    fn redis_conn(&self) -> PooledConnection<RedisConnectionManager> {\n        self.extensions().get::<RedisMiddleware>().unwrap().get().unwrap()\n    }\n}\n"
  },
  {
    "path": "update_index.py",
    "content": "import os\nimport os.path\nimport json\nimport hashlib\nimport git\n\nPROTOCOL_VERSION = 1.1\n\ndef normalize_path(p):\n    # handles git renames:\n    # a/{b.py => c.py}\n    # b.py => a/c.py\n    if \"=>\" not in p:\n        return p\n    if \"/{\" in p:\n        p = p.split(\"/\")\n        p[-1] = p[-1].strip(\"{}\").split(\" => \")[1]\n        return '/'.join(p)\n    else:\n        return p.split(\" => \")[1]\n\ngitRepo = git.Repo(\"./\")\n\nmods = {}\n\ncurrent_commit = gitRepo.rev_parse(\"HEAD\")\n\nurl_base = \"https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/\"\nmodurl = url_base + current_commit.hexsha + \"/mods/\"\n\nold_data = None\n\nfor blob in gitRepo.head.object.tree.traverse():\n    if blob.path.startswith(\"mods/\") and blob.path.endswith(\".py\"):\n        filename = blob.path[5:]\n        base = filename[:-3]\n        data = blob.data_stream.read()\n        md5 = hashlib.md5(data).hexdigest()\n        mod = {\n            \"changelog\": [],\n            \"md5\": md5,\n            \"url\": modurl + filename,  # TODO: remove url field and bump version to 1.6\n            \"filename\": filename,\n            \"commit_sha\": current_commit.hexsha,\n            \"old_md5s\": [],\n        }\n        if os.path.isfile(\"mods/\" + base + \".json\"):\n            with open(\"mods/\" + base + \".json\", \"r\") as json_file:\n                try:\n                    data = json.load(json_file)\n                except Exception as e:\n                    print(f\"failed to read {base}.json\")\n                    raise e\n                mod.update(data)\n        if mod.get(\"index\", True):\n            mods[base] = mod\n    elif blob.path == \"index.json\":\n        old_data = json.loads(blob.data_stream.read().decode(\"UTF-8\"))\n\nspecific_sha = set()\n\nfor commit in gitRepo.iter_commits(max_count=1000, paths=\"mods/\"):\n    for filename in commit.stats.files:\n        filename = normalize_path(filename)\n        if not filename.startswith(\"mods/\") or not filename.endswith(\".py\"):\n            continue\n        filename = filename[5:]\n        if filename[:-3] not in mods:\n            continue\n        txt = commit.message.replace(\"\\n\", \"\")\n        mod_slug = filename[:-3]\n        mods[mod_slug][\"changelog\"].append(txt)\n        if filename not in specific_sha:\n            # TODO: remove url field and bump version to 1.6\n            mods[mod_slug][\"url\"] = url_base + commit.hexsha + \"/mods/\" + filename\n            mods[mod_slug][\"commit_sha\"] = commit.hexsha\n            specific_sha.add(filename)\n\n    for blob in commit.tree[\"mods\"].blobs:\n        if not blob.path.endswith(\".py\"):\n            continue\n        name = blob.path[5:-3]\n        if name in mods:\n            data = blob.data_stream.read()\n            md5 = hashlib.md5(data).hexdigest()\n            if md5 not in mods[name][\"old_md5s\"] and md5 != mods[name][\"md5\"] and len(mods[name]['old_md5s']) < 5:\n                mods[name][\"old_md5s\"].append(md5)\n\nfor mod in mods:\n    if not mod + \".py\" in specific_sha:\n        print(\"didnt find latest commit for\", mod + \", head is used\")\n\nfor mod in mods.values():\n    mod[\"changelog\"] = mod[\"changelog\"][:2]\n    # TODO: if the index.json gets too big\n    # mod[\"old_md5s\"] = [md5[:10] for md5 in mod[\"old_md5s\"]]\n\nindex_data = {\"mods\": mods, \"version\": PROTOCOL_VERSION}\nwith open(\"index.json\", \"w\") as f:\n    json.dump(index_data, f, indent=4, sort_keys=True)\n\nif old_data:\n    old_mods = old_data[\"mods\"]\n    text = \"\"\n\n    def add(text, mod, spacer, *args):\n        if spacer:\n            text += \" \" * (spacer + 2)\n        else:\n            text += mod + \": \"\n            spacer = len(mod)\n        text += \" \".join(args) + \"\\n\"\n        return text, spacer\n    spacer = None\n    for mod in set(list(old_mods.keys()) + list(mods.keys())):\n        if spacer:\n            spacer = None\n            text += \"\\n\"\n        if mod in mods and mod in old_mods:\n            md, omd = mods[mod], old_mods[mod]\n            for key in set(list(md.keys()) + list(omd.keys())):\n                if key in md and key in omd:\n                    if md[key] != omd[key]:\n                        text, spacer = add(text, mod, spacer, 'updated', key)\n                elif key not in md:\n                    text, spacer = add(text, mod, spacer, 'removed', key)\n                else:\n                    text, spacer = add(text, mod, spacer, 'added', key)\n        elif mod in mods:\n            text, spacer = add(text, mod, spacer, 'added')\n        else:\n            text, spacer = add(text, mod, spacer, 'removed')\n\n    if len(text) == 0:\n        print(\"no changes.\")\n    else:\n        text = \"update index.json\\n\\n\" + text\n        print(text)\n        if input(\"Do commit? [Yn]\") in [\"\", \"Y\", \"y\"]:\n            print(\"staging index.json\")\n            gitRepo.index.add([\"index.json\"])\n            print(\"committing\")\n            gitRepo.index.commit(text)\n        else:\n            print(\"didnt commit changes.\")\n"
  },
  {
    "path": "utils/blender/README.md",
    "content": "\n1. Download [this file](https://github.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/blob/master/utils/blender/bob_plugin.py).\n2. Open Blender. (tested using version 2.77)\n3. Go to File > User Preferences... > Addons tab.\n4. On the bottom, click `Install from File...`\n5. Select the `bob_plugin.py` from this project.\n6. Enable the plugin by checking the checkbox.\n7. Now you should now have new import/export menu items for .bob files.\n\nTo-Dos:\n- [x] Import\n\t- [x] Mesh\n\t- [x] UV-Maps\n\t\t- [x] fix material loading\n\t\t- [ ] allow specifying texture files\n\t- [ ] import normals?\n- [x] Export\n\t- [x] Mesh\n\t- [x] Normals\n\t- [x] UV-Maps\n- [x] Cob\n\t- [x] Import\n\t\t- [ ] import normals?\n\t- [x] Export\n- [x] Import Level-Defs\n- [x] Export Level-Defs\n"
  },
  {
    "path": "utils/blender/bob_plugin.py",
    "content": "import os\nimport os.path\nimport bpy\nimport bmesh\nimport struct\nfrom mathutils import Vector\nfrom bpy.props import StringProperty, BoolProperty\nfrom bpy_extras.io_utils import ImportHelper, ExportHelper, axis_conversion\n\nfrom contextlib import contextmanager\nfrom collections import defaultdict\n\nbl_info = {\n    \"name\": \"BOB format\",\n    \"description\": \"Import-Export BombSquad .bob files.\",\n    \"author\": \"Mrmaxmeier\",\n    \"version\": (0, 0),\n    \"blender\": (2, 77, 0),\n    \"location\": \"File > Import-Export\",\n    \"warning\": \"\",\n    \"wiki_url\": \"\",\n    \"category\": \"Import-Export\"\n}\n\nBOB_FILE_ID = 45623\nCOB_FILE_ID = 13466\n\n\"\"\"\n.BOB File Structure:\n\nMAGIC 45623 (I)\nmeshFormat  (I)\nvertexCount (I)\nfaceCount   (I)\nVertexObject x vertexCount (fff HH hhh xx)\nindex x faceCount*3 (b / H)\n\nstruct VertexObjectFull {\n    float position[3];\n    bs_uint16 uv[2]; // normalized to 16 bit unsigned ints 0 - 65535\n    bs_sint16  normal[3]; // normalized to 16 bit signed ints -32768 - 32767\n    bs_uint8 _padding[2];\n};\n\n\n.COB File Structure:\n\nMAGIC 13466 (I)\nvertexCount (I)\nfaceCount   (I)\nvertexPos x vertexCount (fff)\nindex x faceCount*3 (I)\nnormal x faceCount (fff)\n\"\"\"\n\n\n@contextmanager\ndef to_bmesh(mesh, save=False):\n    try:\n        bm = bmesh.new()\n        bm.from_mesh(mesh)\n        bm.faces.ensure_lookup_table()\n        yield bm\n    finally:\n        if save:\n            bm.to_mesh(mesh)\n        bm.free()\n        del bm\n\n\ndef clamp(val, minimum=0, maximum=1):\n    if max(min(val, maximum), minimum) != val:\n        print(\"clamped\", val, \"to\", max(min(val, maximum), minimum))\n    return max(min(val, maximum), minimum)\n\n\nclass ImportBOB(bpy.types.Operator, ImportHelper):\n    \"\"\"Load an Bombsquad Mesh file\"\"\"\n    bl_idname = \"import_mesh.bob\"\n    bl_label = \"Import Bombsquad Mesh\"\n    filename_ext = \".bob\"\n    filter_glob = StringProperty(\n        default=\"*.bob\",\n        options={'HIDDEN'},\n    )\n\n    def execute(self, context):\n        keywords = self.as_keywords(ignore=('filter_glob',))\n        mesh = load(self, context, **keywords)\n        if not mesh:\n            return {'CANCELLED'}\n\n        scene = bpy.context.scene\n        obj = bpy.data.objects.new(mesh.name, mesh)\n        scene.objects.link(obj)\n        scene.objects.active = obj\n        obj.select = True\n        obj.matrix_world = axis_conversion(from_forward='-Z', from_up='Y').to_4x4()\n        scene.update()\n        return {'FINISHED'}\n\n\nclass ExportBOB(bpy.types.Operator, ExportHelper):\n    \"\"\"Save an Bombsquad Mesh file\"\"\"\n    bl_idname = \"export_mesh.bob\"\n    bl_label = \"Export Bombsquad Mesh\"\n    filter_glob = StringProperty(\n        default=\"*.bob\",\n        options={'HIDDEN'},\n    )\n    check_extension = True\n    filename_ext = \".bob\"\n\n    triangulate = BoolProperty(\n        name=\"Force Triangulation\",\n        description=\"force triangulation of .bob files\",\n        default=False,\n    )\n\n    def execute(self, context):\n        keywords = self.as_keywords(ignore=('filter_glob',))\n        return save(self, context, **keywords)\n\n\ndef import_bob_menu(self, context):\n    self.layout.operator(ImportBOB.bl_idname, text=\"Bombsquad Mesh (.bob)\")\n\n\ndef export_bob_menu(self, context):\n    self.layout.operator(ExportBOB.bl_idname, text=\"Bombsquad Mesh (.bob)\")\n\n\nclass ImportCOB(bpy.types.Operator, ImportHelper):\n    \"\"\"Load an Bombsquad Collision Mesh\"\"\"\n    bl_idname = \"import_mesh.cob\"\n    bl_label = \"Import Bombsquad Collision Mesh\"\n    filename_ext = \".cob\"\n    filter_glob = StringProperty(\n        default=\"*.cob\",\n        options={'HIDDEN'},\n    )\n\n    def execute(self, context):\n        keywords = self.as_keywords(ignore=('filter_glob',))\n        mesh = loadcob(self, context, **keywords)\n        if not mesh:\n            return {'CANCELLED'}\n\n        scene = bpy.context.scene\n        obj = bpy.data.objects.new(mesh.name, mesh)\n        scene.objects.link(obj)\n        scene.objects.active = obj\n        obj.select = True\n        obj.draw_type = \"SOLID\"\n        obj.matrix_world = axis_conversion(from_forward='-Z', from_up='Y').to_4x4()\n        scene.update()\n        return {'FINISHED'}\n\n\nclass ExportCOB(bpy.types.Operator, ExportHelper):\n    \"\"\"Save an Bombsquad Collision Mesh file\"\"\"\n    bl_idname = \"export_mesh.cob\"\n    bl_label = \"Export Bombsquad Collision Mesh\"\n    filter_glob = StringProperty(\n        default=\"*.cob\",\n        options={'HIDDEN'},\n    )\n    check_extension = True\n    filename_ext = \".cob\"\n\n    triangulate = BoolProperty(\n        name=\"Force Triangulation\",\n        description=\"force triangulation of .cob files\",\n        default=False,\n    )\n\n    def execute(self, context):\n        keywords = self.as_keywords(ignore=('filter_glob',))\n        return savecob(self, context, **keywords)\n\n\ndef import_cob_menu(self, context):\n    self.layout.operator(ImportCOB.bl_idname, text=\"Bombsquad Collision Mesh (.cob)\")\n\n\ndef export_cob_menu(self, context):\n    self.layout.operator(ExportCOB.bl_idname, text=\"Bombsquad Collision Mesh (.cob)\")\n\n\ndef import_leveldefs(self, context):\n    self.layout.operator(ImportLevelDefs.bl_idname, text=\"Bombsquad Level Definitions (.py)\")\n\n\ndef export_leveldefs(self, context):\n    self.layout.operator(ExportLevelDefs.bl_idname, text=\"Bombsquad Level Definitions (.py)\")\n\n\ndef register():\n    bpy.utils.register_module(__name__)\n    bpy.types.INFO_MT_file_import.append(import_bob_menu)\n    bpy.types.INFO_MT_file_export.append(export_bob_menu)\n    bpy.types.INFO_MT_file_import.append(import_cob_menu)\n    bpy.types.INFO_MT_file_export.append(export_cob_menu)\n    bpy.types.INFO_MT_file_import.append(import_leveldefs)\n    bpy.types.INFO_MT_file_export.append(export_leveldefs)\n\n\ndef unregister():\n    bpy.utils.unregister_module(__name__)\n    bpy.types.INFO_MT_file_import.remove(import_bob_menu)\n    bpy.types.INFO_MT_file_export.remove(export_bob_menu)\n    bpy.types.INFO_MT_file_import.remove(import_cob_menu)\n    bpy.types.INFO_MT_file_export.remove(export_cob_menu)\n    bpy.types.INFO_MT_file_import.remove(import_leveldefs)\n    bpy.types.INFO_MT_file_export.remove(export_leveldefs)\n\n\ndef load(operator, context, filepath):\n    filepath = os.fsencode(filepath)\n    bs_dir = os.path.dirname(os.path.dirname(filepath))\n    texname = os.path.basename(filepath).rstrip(b\".bob\") + b\".dds\"\n    texpath = os.path.join(bs_dir, b\"textures\", texname)\n    print(texpath)\n    has_texture = os.path.isfile(texpath)\n    print(\"texture file found:\", has_texture)\n\n    with open(filepath, 'rb') as file:\n        def readstruct(s):\n            tup = struct.unpack(s, file.read(struct.calcsize(s)))\n            return tup[0] if len(tup) == 1 else tup\n\n        assert readstruct(\"I\") == BOB_FILE_ID\n        meshFormat = readstruct(\"I\")\n        assert meshFormat in [0, 1]\n\n        vertexCount = readstruct(\"I\")\n        faceCount = readstruct(\"I\")\n\n        verts = []\n        faces = []\n        edges = []\n        indices = []\n        uv_list = []\n        normal_list = []\n\n        for i in range(vertexCount):\n            vertexObj = readstruct(\"fff HH hhh xx\")\n            position = (vertexObj[0], vertexObj[1], vertexObj[2])\n            uv = (vertexObj[3] / 65535, vertexObj[4] / 65535)\n            normal = (vertexObj[5] / 32767, vertexObj[6] / 32767, vertexObj[7] / 32767)\n            verts.append(position)\n            uv_list.append(uv)\n            normal_list.append(normal)\n\n        for i in range(faceCount * 3):\n            if meshFormat == 0:\n                # MESH_FORMAT_UV16_N8_INDEX8\n                indices.append(readstruct(\"b\"))\n            elif meshFormat == 1:\n                # MESH_FORMAT_UV16_N8_INDEX16\n                indices.append(readstruct(\"H\"))\n\n        for i in range(faceCount):\n            faces.append((indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2]))\n\n        bob_name = bpy.path.display_name_from_filepath(filepath)\n        mesh = bpy.data.meshes.new(name=bob_name)\n        mesh.from_pydata(verts, edges, faces)\n\n        with to_bmesh(mesh, save=True) as bm:\n            for i, face in enumerate(bm.faces):\n                for vi, vert in enumerate(face.verts):\n                    vert.normal = normal_list[vert.index]\n\n        uv_texture = mesh.uv_textures.new(texname.decode(\"ascii\", \"ignore\"))\n        texture = None\n        if has_texture:\n            texture = bpy.data.images.load(texpath)\n            uv_texture.data[0].image = texture\n\n        with to_bmesh(mesh, save=True) as bm:\n            uv_layer = bm.loops.layers.uv.verify()\n            tex_layer = bm.faces.layers.tex.verify()\n            for i, face in enumerate(bm.faces):\n                for vi, vert in enumerate(face.verts):\n                    uv = uv_list[vert.index]\n                    uv = (uv[0], 1 - uv[1])\n                    face.loops[vi][uv_layer].uv = uv\n                    if texture:\n                        face[tex_layer].image = texture\n\n        mesh.validate()\n        mesh.update()\n\n        return mesh\n\n\nclass Verts:\n    def __init__(self):\n        self._verts = []\n        self._by_blender_index = defaultdict(list)\n\n    def get(self, coords, normal, blender_index, uv=None):\n        instance = Vert(coords=coords, normal=normal, uv=uv)\n        for other in self._by_blender_index[blender_index]:\n            if instance.similar(other):\n                return other\n        self._by_blender_index[blender_index].append(instance)\n        instance.index = len(self._verts)\n        self._verts.append(instance)\n        return instance\n\n    def __len__(self):\n        return len(self._verts)\n\n    def __iter__(self):\n        return iter(self._verts)\n\n\ndef vec_similar(v1, v2):\n    return (v1 - v2).length < 0.01\n\n\nclass Vert:\n    def __init__(self, coords, normal, uv):\n        self.coords = coords\n        self.normal = normal\n        self.uv = uv\n\n    def similar(self, other):\n        is_similar = vec_similar(self.coords, other.coords)\n        is_similar = is_similar and vec_similar(self.normal, other.normal)\n        if self.uv and other.uv:\n            is_similar = is_similar and vec_similar(self.uv, other.uv)\n        return is_similar\n\n\ndef save(operator, context, filepath, triangulate, check_existing):\n    print(\"exporting\", filepath)\n    global_matrix = axis_conversion(to_forward='-Z', to_up='Y').to_4x4()\n    scene = context.scene\n    obj = scene.objects.active\n    mesh = obj.to_mesh(scene, True, 'PREVIEW')\n    mesh.transform(global_matrix * obj.matrix_world)  # inverse transformation\n\n    with to_bmesh(mesh) as bm:\n        triangulate = triangulate or any([len(face.verts) != 3 for face in bm.faces])\n    if triangulate or any([len(face.vertices) != 3 for face in mesh.tessfaces]):\n        print(\"triangulating...\")\n        with to_bmesh(mesh, save=True) as bm:\n            bmesh.ops.triangulate(bm, faces=bm.faces)\n        mesh.update(calc_edges=True, calc_tessface=True)\n\n    filepath = os.fsencode(filepath)\n\n    with open(filepath, 'wb') as file:\n\n        def writestruct(s, *args):\n            file.write(struct.pack(s, *args))\n\n        writestruct('I', BOB_FILE_ID)\n        writestruct('I', 1)  # MESH_FORMAT_UV16_N8_INDEX16\n\n        verts = Verts()\n        faces = []\n        with to_bmesh(mesh) as bm:\n            uv_layer = None\n            if len(bm.loops.layers.uv) > 0:\n                uv_layer = bm.loops.layers.uv[0]\n            for i, face in enumerate(bm.faces):\n                faceverts = []\n                for vi, vert in enumerate(face.verts):\n                    uv = face.loops[vi][uv_layer].uv if uv_layer else None\n                    v = verts.get(coords=vert.co, normal=vert.normal, uv=uv, blender_index=vert.index)\n                    faceverts.append(v)\n                faces.append(faceverts)\n\n            print(\"verts: {} [best: {}, worst: {}]\".format(len(verts), len(mesh.vertices), len(faces) * 3))\n            print(\"faces:\", len(faces))\n            writestruct('I', len(verts))\n            writestruct('I', len(faces))\n\n            for vert in verts:\n                writestruct('fff', *vert.coords)\n                if vert.uv:\n                    uv = vert.uv\n                    writestruct('HH', int(clamp(uv[0]) * 65535), int((1 - clamp(uv[1])) * 65535))\n                else:\n                    writestruct('HH', 0, 0)\n                normal = tuple(map(lambda n: int(clamp(n, -1, 1) * 32767), vert.normal))\n                writestruct('hhh', *normal)\n                writestruct('xx')\n\n            for face in faces:\n                assert len(face) == 3\n                for vert in face:\n                    writestruct('H', vert.index)\n\n    return {'FINISHED'}\n\n\ndef loadcob(operator, context, filepath):\n    with open(os.fsencode(filepath), 'rb') as file:\n        def readstruct(s):\n            tup = struct.unpack(s, file.read(struct.calcsize(s)))\n            return tup[0] if len(tup) == 1 else tup\n\n        assert readstruct(\"I\") == COB_FILE_ID\n\n        vertexCount = readstruct(\"I\")\n        faceCount = readstruct(\"I\")\n\n        verts = []\n        faces = []\n        edges = []\n        indices = []\n\n        for i in range(vertexCount):\n            vertexObj = readstruct(\"fff\")\n            position = (vertexObj[0], vertexObj[1], vertexObj[2])\n            verts.append(position)\n\n        for i in range(faceCount * 3):\n            indices.append(readstruct(\"I\"))\n\n        for i in range(faceCount):\n            faces.append((indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2]))\n\n        bob_name = bpy.path.display_name_from_filepath(filepath)\n        mesh = bpy.data.meshes.new(name=bob_name)\n        mesh.from_pydata(verts, edges, faces)\n\n        mesh.validate()\n        mesh.update()\n\n        return mesh\n\n\ndef savecob(operator, context, filepath, triangulate, check_existing):\n    print(\"exporting\", filepath)\n    global_matrix = axis_conversion(to_forward='-Z', to_up='Y').to_4x4()\n    scene = context.scene\n    obj = scene.objects.active\n    mesh = obj.to_mesh(scene, True, 'PREVIEW')\n    mesh.transform(global_matrix * obj.matrix_world)  # inverse transformation\n\n    if triangulate or any([len(face.vertices) != 3 for face in mesh.tessfaces]):\n        print(\"triangulating...\")\n        with to_bmesh(mesh, save=True) as bm:\n            bmesh.ops.triangulate(bm, faces=bm.faces)\n        mesh.update(calc_edges=True, calc_tessface=True)\n\n    with open(os.fsencode(filepath), 'wb') as file:\n\n        def writestruct(s, *args):\n            file.write(struct.pack(s, *args))\n\n        writestruct('I', COB_FILE_ID)\n        writestruct('I', len(mesh.vertices))\n        writestruct('I', len(mesh.tessfaces))\n\n        for i, vert in enumerate(mesh.vertices):\n            writestruct('fff', *vert.co)\n\n        for face in mesh.tessfaces:\n            assert len(face.vertices) == 3\n            for vertid in face.vertices:\n                writestruct('I', vertid)\n\n        for face in mesh.tessfaces:\n            writestruct('fff', *face.normal)\n\n    return {'FINISHED'}\n\n\ndef flpV(vector):\n    vector = vector.copy()\n    vector.y = -vector.y\n    return vector.xzy\n\n\nclass ImportLevelDefs(bpy.types.Operator, ImportHelper):\n    \"\"\"Load Bombsquad Level Defs\"\"\"\n    bl_idname = \"import_bombsquad.leveldefs\"\n    bl_label = \"Import Bombsquad Level Definitions\"\n    filename_ext = \".py\"\n    filter_glob = StringProperty(\n        default=\"*.py\",\n        options={'HIDDEN'},\n    )\n\n    def execute(self, context):\n        keywords = self.as_keywords(ignore=('filter_glob',))\n        print(\"executing\", keywords[\"filepath\"])\n        data = {}\n        with open(os.fsencode(keywords[\"filepath\"]), \"r\") as file:\n            exec(file.read(), data)\n        del data[\"__builtins__\"]\n        if \"points\" not in data or \"boxes\" not in data:\n            return {'CANCELLED'}\n\n        scene = bpy.context.scene\n\n        points_obj = bpy.data.objects.new(\"points\", None)\n        points_obj.matrix_world = axis_conversion(from_forward='-Z', from_up='Y').to_4x4()\n        scene.objects.link(points_obj)\n        scene.update()\n        points_obj.layers = tuple([i == 1 for i in range(20)])\n\n        boxes_obj = bpy.data.objects.new(\"boxes\", None)\n        boxes_obj.matrix_world = axis_conversion(from_forward='-Z', from_up='Y').to_4x4()\n        scene.objects.link(boxes_obj)\n        scene.update()\n        boxes_obj.layers = tuple([i == 1 for i in range(20)])\n\n        def makeBox(middle, scale):\n            bpy.ops.mesh.primitive_cube_add(location=middle)\n            cube = scene.objects.active\n            cube.scale = scale\n            cube.show_name = True\n            cube.show_wire = True\n            cube.draw_type = 'WIRE'\n            return cube\n\n        for key, pos in data[\"points\"].items():\n            if len(pos) == 6:  # spawn points with random variance\n                middle, size = Vector(pos[:3]), Vector(pos[3:])\n                if \"spawn\" in key.lower():\n                    size.y = 0.05\n                cube = makeBox(middle, size)\n                cube.parent = points_obj\n                cube.name = key\n            else:\n                empty = bpy.data.objects.new(key, None)\n                empty.location = pos[:3]\n                empty.empty_draw_size = 0.45\n                empty.parent = points_obj\n                empty.show_name = True\n                scene.objects.link(empty)\n\n        for key, pos in data[\"boxes\"].items():\n            middle, size = Vector(pos[:3]), flpV(Vector(pos[6:9]))\n            cube = makeBox(middle, size)\n            cube.parent = boxes_obj\n            cube.name = key\n\n        scene.update()\n        return {'FINISHED'}\n\n\nclass ExportLevelDefs(bpy.types.Operator, ImportHelper):\n    \"\"\"Export Bombsquad Level Defs\"\"\"\n    bl_idname = \"export_bombsquad.leveldefs\"\n    bl_label = \"Export Bombsquad Level Definitions\"\n    filename_ext = \".py\"\n    filter_glob = StringProperty(\n        default=\"*.py\",\n        options={'HIDDEN'},\n    )\n\n    def execute(self, context):\n        keywords = self.as_keywords(ignore=('filter_glob',))\n        filepath = keywords[\"filepath\"]\n        print(\"writing level defs\", filepath)\n\n        scene = bpy.context.scene\n        if \"points\" not in scene.objects or \"boxes\" not in scene.objects:\n            return {'CANCELLED'}\n\n        def v_to_str(v, flip=True, isScale=False):\n            if flip:\n                v = flpV(v)\n            if isScale:\n                v = Vector([abs(n) for n in v])\n            return repr(tuple([round(n, 5) for n in tuple(v)]))\n\n        with open(os.fsencode(filepath), \"w\") as file:\n            file.write(\"# This file generated from '{}'\\n\".format(os.path.basename(bpy.data.filepath)))\n            file.write(\"points, boxes = {}, {}\\n\")\n\n            for point in scene.objects[\"points\"].children:\n                pos = point.matrix_world.to_translation()\n                if point.type == 'MESH':  # spawn point with random variance\n                    scale = point.scale * point.rotation_euler.to_matrix()\n                    file.write(\"points['{}'] = {}\".format(point.name, v_to_str(pos)))\n                    file.write(\" + {}\\n\".format(v_to_str(scale, False, isScale=True)))\n                else:\n                    file.write(\"points['{}'] = {}\\n\".format(point.name, v_to_str(pos)))\n\n            for box in scene.objects[\"boxes\"].children:\n                pos = box.matrix_world.to_translation()\n                scale = box.scale * box.rotation_euler.to_matrix()\n                file.write(\"boxes['{}'] = {}\".format(box.name, v_to_str(pos)))\n                file.write(\" + (0, 0, 0) + {}\\n\".format(v_to_str(scale, isScale=True)))\n\n        return {'FINISHED'}\n\n\nif __name__ == \"__main__\":\n    register()\n"
  },
  {
    "path": "utils/inject_mod.py",
    "content": "import telnetlib\nimport click\nimport json\n\n# TODO: support for multiple files\n\n@click.command()\n@click.option('--host', default=\"localhost\", prompt=\"Host\")\n@click.option('--file', type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True))\ndef inject(host, file):\n\tpath = file\n\twith open(path, \"r\") as f:\n\t\tfile = f.read()\n\tfilename = path.split(\"/\")[-1]\n\ttn = telnetlib.Telnet(host, 43250)\n\ttn.read_until(b\"bombsquad>\")\n\tprint(\"host available\")\n\twhile 1:\n\t\ttry:\n\t\t\ttn.write(b\"42\\n\")\n\t\t\td = tn.read_until(b\"42\", timeout=5).decode(\"utf-8\")\n\t\t\tprint(d)\n\t\t\tif \"42\" in d:\n\t\t\t\tbreak\n\t\texcept EOFError:\n\t\t\tprint(\"telnet not allowed\")\n\tprint(\"telnet allowed\")\n\n\tdef send_line(line):\n\t\tprint(line)\n\t\ttn.write(bytes(line, encoding=\"ascii\"))\n\t\td = tn.read_until(b\"bombsquad>\", timeout=1).decode(\"utf-8\")\n\t\tif len(d.replace(\"bombsquad>\", \"\")) > 1:\n\t\t\tprint(\">\", d)\n\n\tsetup = \"\"\"\nimport bs, json, os\nfrom md5 import md5\nmods_folder = bs.getEnvironment()['userScriptsDirectory'] + \"/\"\nbs.playSound(bs.getSound(\"gunCocking\"))\nbs.screenMessage(\"sending data\")\nd = \"\"\n\"\"\"[1:]\n\tfor line in setup.split(\"\\n\"):\n\t\tsend_line(line)\n\tdata = json.dumps(file)[1:-1]\n\tchunksize = 250\n\tpos = 0\n\tbuf = \"\"\n\twhile pos < len(data):\n\t\tbuf += data[pos]\n\t\tpos += 1\n\t\tif len(buf) > chunksize or pos >= len(data):\n\t\t\tsend_line('d += {}'.format(repr(buf)))\n\t\t\tbuf = \"\"\n\n\tsend_line(\"bs.screenMessage('data sent')\")\n\tsend_line(\"md5(d).hexdigest()\") # FIXME: actually check md5\n\n\tif click.confirm('Save to mods folder?', default=True):\n\t\tsend_line(\"path = mods_folder + '{}'\".format(filename))\n\t\t#send_line(\"os.rename(path, path + '.bak')\")\n\t\tsend_line(\"f = open(path, 'w')\")\n\t\tsend_line(\"f.write(d)\")\n\t\tsend_line(\"f.close()\")\n\tif click.confirm('Exec?'):\n\t\tsend_line(\"exec(d)\")\n\tif click.confirm(\"Quit?\", default=True):\n\t\tsend_line(\"bs.quit()\")\n\n\t#tn.interact()\n\n\ttn.close()\n\nif __name__ == '__main__':\n\tinject()\n"
  },
  {
    "path": "utils/installer.py",
    "content": "import bs\nimport bsInternal\nimport threading\nimport json\nimport urllib2\nimport weakref\nimport os\nimport os.path\n\nimport httplib\nSUPPORTS_HTTPS = hasattr(httplib, 'HTTPS')\n\nmodPath = bs.getEnvironment()['userScriptsDirectory'] + \"/\"\nBRANCH = \"master\"\nUSER_REPO = \"Mrmaxmeier/BombSquad-Community-Mod-Manager\"\nENTRY_MOD = \"modManager\"\n\n\ndef index_url():\n    if SUPPORTS_HTTPS:\n        yield \"https://raw.githubusercontent.com/{}/{}/index.json\".format(USER_REPO, BRANCH)\n        yield \"https://rawgit.com/{}/{}/index.json\".format(USER_REPO, BRANCH)\n    yield \"http://raw.githack.com/{}/{}/index.json\".format(USER_REPO, BRANCH)\n    yield \"http://rawgit.com/{}/{}/index.json\".format(USER_REPO, BRANCH)\n\n\ndef mod_url(data):\n    if \"commit_sha\" in data and \"filename\" in data:\n        commit_hexsha = data[\"commit_sha\"]\n        filename = data[\"filename\"]\n        if SUPPORTS_HTTPS:\n            yield \"https://cdn.rawgit.com/{}/{}/mods/{}\".format(USER_REPO, commit_hexsha, filename)\n        yield \"http://rawcdn.githack.com/{}/{}/mods/{}\".format(USER_REPO, commit_hexsha, filename)\n    if \"url\" in data:\n        if SUPPORTS_HTTPS:\n            yield data[\"url\"]\n        yield data[\"url\"].replace(\"https\", \"http\")\n\n\ndef try_fetch_cb(generator, callback):\n    def f(data):\n        if data:\n            callback(data)\n        else:\n            try:\n                SimpleGetThread(next(generator), f).start()\n            except StopIteration:\n                callback(None)\n    SimpleGetThread(next(generator), f).start()\n\n\nclass SimpleGetThread(threading.Thread):\n    def __init__(self, url, callback=None):\n        threading.Thread.__init__(self)\n        self._url = url.encode(\"ascii\")  # embedded python2.7 has weird encoding issues\n        self._callback = callback or (lambda d: None)\n        self._context = bs.Context('current')\n        # save and restore the context we were created from\n        activity = bs.getActivity(exceptionOnNone=False)\n        self._activity = weakref.ref(activity) if activity is not None else None\n\n    def _runCallback(self, arg):\n        # if we were created in an activity context and that activity has since died, do nothing\n        # (hmm should we be using a context-call instead of doing this manually?)\n        if self._activity is not None and (self._activity() is None or self._activity().isFinalized()):\n            return\n        # (technically we could do the same check for session contexts, but not gonna worry about it for now)\n        with self._context:\n            self._callback(arg)\n\n    def run(self):\n        try:\n            bsInternal._setThreadName(\"SimpleGetThread\")\n            response = urllib2.urlopen(self._url)\n            bs.callInGameThread(bs.Call(self._runCallback, response.read()))\n        except:\n            bs.printException()\n            bs.callInGameThread(bs.Call(self._runCallback, None))\n\n\ninstalled = []\ninstalling = []\n\n\ndef check_finished():\n    if any([m not in installed for m in installing]):\n        return\n    bs.screenMessage(\"installed everything.\")\n    if os.path.isfile(modPath + __name__ + \".pyc\"):\n        os.remove(modPath + __name__ + \".pyc\")\n    if os.path.isfile(modPath + __name__ + \".py\"):\n        os.remove(modPath + __name__ + \".py\")\n        bs.screenMessage(\"deleted self\")\n    bs.screenMessage(\"activating modManager\")\n    __import__(ENTRY_MOD)\n\n\ndef install(data, mod):\n    installing.append(mod)\n    bs.screenMessage(\"installing \" + str(mod))\n    print(\"installing\", mod)\n    for dep in data[mod].get(\"requires\", []):\n        install(data, dep)\n    filename = data[mod][\"filename\"]\n\n    def f(data):\n        if not data:\n            bs.screenMessage(\"failed to download mod '{}'\".format(filename))\n        print(\"writing\", filename)\n        with open(modPath + filename, \"w\") as f:\n            f.write(data)\n        installed.append(mod)\n        check_finished()\n\n    try_fetch_cb(mod_url(data[mod]), f)\n\n\ndef onIndex(data):\n    if not data:\n        bs.screenMessage(\"network error :(\")\n        return\n    data = json.loads(data)\n    install(data[\"mods\"], ENTRY_MOD)\n\ntry_fetch_cb(index_url(), onIndex)\n"
  }
]