Repository: Mrmaxmeier/BombSquad-Community-Mod-Manager Branch: master Commit: aeba3f685833 Files: 100 Total size: 747.4 KB Directory structure: gitextract_8htf1a4p/ ├── .gitignore ├── LICENSE ├── README.md ├── index.json ├── mods/ │ ├── AroundTheWorld.py │ ├── BackToYou.json │ ├── BackToYou.py │ ├── Basketball.json │ ├── Basketball.py │ ├── BuddyBunny.json │ ├── BuddyBunny.py │ ├── Collector.json │ ├── Collector.py │ ├── FillErUp.json │ ├── FillErUp.py │ ├── FlagDay.py │ ├── GravityFalls.py │ ├── Greed.json │ ├── Greed.py │ ├── GuessTheBomb.json │ ├── GuessTheBomb.py │ ├── HazardousCargo.json │ ├── HazardousCargo.py │ ├── Infection.json │ ├── Infection.py │ ├── JumpingContest.py │ ├── LandGrab.json │ ├── LandGrab.py │ ├── Paint.py │ ├── Portal.json │ ├── Portal.py │ ├── Protection.json │ ├── Protection.py │ ├── SharksAndMinnows.py │ ├── Siege.py │ ├── SimonSays.py │ ├── SnoBallz.json │ ├── SnoBallz.py │ ├── SnowBallFight.json │ ├── SnowBallFight.py │ ├── WizardWar.json │ ├── WizardWar.py │ ├── ZombieHorde.json │ ├── ZombieHorde.py │ ├── airStrike.json │ ├── airStrike.py │ ├── arms_race.json │ ├── arms_race.py │ ├── auto_reloader.json │ ├── auto_reloader.py │ ├── bomb_on_my_head.json │ ├── bomb_on_my_head.py │ ├── bomberman.json │ ├── bomberman.py │ ├── boxing.json │ ├── boxing.py │ ├── brainFreeze.json │ ├── brainFreeze.py │ ├── bsBoxingOfTheHill.json │ ├── bsBoxingOfTheHill.py │ ├── bsKillZone.json │ ├── bsKillZone.py │ ├── catch_to_live.json │ ├── catch_to_live.py │ ├── fightOfFaith.json │ ├── fightOfFaith.py │ ├── frozenone.json │ ├── frozenone.py │ ├── iceDeathmatch.json │ ├── iceDeathmatch.py │ ├── magic_box.json │ ├── magic_box.py │ ├── modManager.json │ ├── modManager.py │ ├── puckDeathmatch.json │ ├── puckDeathmatch.py │ ├── quickGameButton.json │ ├── quickGameButton.py │ ├── settings_patcher.json │ ├── settings_patcher.py │ ├── smash.json │ ├── smash.py │ ├── snake.json │ ├── snake.py │ ├── snowyPowerup.json │ ├── snowyPowerup.py │ ├── surviveCurse.json │ ├── surviveCurse.py │ ├── ui_wrappers.json │ └── ui_wrappers.py ├── requirements.txt ├── server/ │ ├── .gitignore │ ├── Cargo.toml │ └── src/ │ ├── main.rs │ └── redis_middleware.rs ├── update_index.py └── utils/ ├── blender/ │ ├── README.md │ └── bob_plugin.py ├── inject_mod.py └── installer.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ 1.3.8 audioCache replays .idea .bsac .bsac2 .bsuuid *.py.bak *.pyc config.json config.json.prev node_modules ================================================ FILE: LICENSE ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================

Note: This repository is not maintained and is not compatible with current BombSquad versions (>v1.5). See https://github.com/bombsquad-community/plugin-manager for a modern alternative.

BombSquad Community Mod Manager

What is it?

This is a mod for the game BombSquad by Eric Froemling that aims to improve the management of community created content for BombSquad. It's build using the modding api so it can be used on all platforms and should be compatible with all recent versions of BombSquad. A 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)).

Installation

Put installer.py in your mods folder. This file will download and install the Mod-Manager and its dependencies. You can find your mods folder in Settings > Advanced > Show Mods Folder.
Note:
On 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. | Platform | Path | | --------- | ---------- | | OS X | ~/Library/Containers/net.froemling.bombsquad/Data/Library/Application Support/BombSquad/mods | | Android | *<*sdcard*>*/BombSquad/mods | | Windows | %appdata%/BombSquad/mods | | Linux | ~/.bombsquad/mods |

One-Liners

OSX
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
Linux (wget)
wget -P ~/.bombsquad/mods https://raw.githubusercontent.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/master/utils/installer.py
Linux (curl)
cd ~/.bombsquad/mods && curl -O https://raw.githubusercontent.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/master/utils/installer.py
Windows (PowerShell)
wget https://raw.githubusercontent.com/Mrmaxmeier/BombSquad-ModManager-and-Mods/master/utils/installer.py -OutFile $env:APPDATA/BombSquad/mods/installer.py

Usage

After restarting BombSquad there should be a new button in the settings window. ![Settings Window](screenshots/SettingsWindow.png) Upon clicking this button a new window pops up. ![ModManager Window](screenshots/ModManagerWindow.png) You can download, install or delete mods here.

Tabs

The mods are grouped in three categories:
Minigames Installing these will add games to the game select screen.
Utilities These are mods that add UI elements or other non game related things
Libraries These are mods that can be used as libraries by other mods.
You can also view all mods using the 'all' tab.

Settings

There is a settings button in the mod manager window. | Setting | More infos | | ---------- | ---------- | | Branch | A List of all available branches can be found [here](https://api.github.com/repos/Mrmaxmeier/BombSquad-Community-Mod-Manager/branches) | | Auto check for updates | This will check for updates while BombSquad is starting | | Auto-update old mods | This will update mods with versions that are known to be old.
Mods you are developing won't get updated by this. |

Contributing

Want to contribute? Great! 1. Fork it 2. Create a new file in the mods folder 3. Add a json file with additional infos (optional) 5. Open a Pull Request 6. Profit

License

``` This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ``` ================================================ FILE: index.json ================================================ { "mods": { "AroundTheWorld": { "changelog": [ "Create AroundTheWorld.pyThis mod allows players to race around the Happy Thoughts map, touching platforms as they go. First one around the world wins!" ], "commit_sha": "0673756fe8376878d81f0a6d1e4c163c6750c9cb", "filename": "AroundTheWorld.py", "md5": "58bbe46603794fb20e018cf015387674", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/0673756fe8376878d81f0a6d1e4c163c6750c9cb/mods/AroundTheWorld.py" }, "BackToYou": { "author": "joshville79", "category": "minigames", "changelog": [ "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.", "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." ], "commit_sha": "f0771dc63724a5b92c95a52d6ee74d4cab35eaf0", "filename": "BackToYou.py", "md5": "8b672f5a8d2876391bddbecdfed9b970", "name": "Back To You", "old_md5s": [ "ec65e09ff91f1535252718d3580ac927" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/BackToYou.py" }, "Basketball": { "author": "MattZ45986", "category": "minigames", "changelog": [ "Add files via uploadAdd Basketball.py" ], "commit_sha": "de5422b29a3e8b7c2c5e63539698d3ecc8e8c0c0", "filename": "Basketball.py", "md5": "5b77f61eae97b0c379eff4b6ed0b242c", "name": "Basketball", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/de5422b29a3e8b7c2c5e63539698d3ecc8e8c0c0/mods/Basketball.py" }, "BuddyBunny": { "author": "joshville79", "category": "libraries", "changelog": [ "convert a bunch of files to linux line feeds (#25)", "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." ], "commit_sha": "a483c74a4b6a9c540ae58904bce5164dcfd5f4d4", "filename": "BuddyBunny.py", "md5": "794110f3e9c9cecb6e3294cf1c829b67", "name": "Buddy Bunny powerup", "old_md5s": [ "2e1bd9ed5b9dab8c2fb4eb21a88689ec" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/BuddyBunny.py" }, "Collector": { "author": "TheMikirog", "category": "minigames", "changelog": [ "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", "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." ], "commit_sha": "0ebd80ae04e67629b2cc1be3e76fa96b68e2a42f", "filename": "Collector.py", "md5": "da460fcf6c5cc622709141a3aa133e48", "name": "Collector", "old_md5s": [ "9f38dfeff4590bd1073d2397867d2fc9", "6fd8ec35e9ced9d961e2efbd7f96b990", "571ec87186cd1b74a0ba196b2a263648", "9db6ae29f084aaa7afdd81953b6f5937" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/0ebd80ae04e67629b2cc1be3e76fa96b68e2a42f/mods/Collector.py" }, "FillErUp": { "author": "joshville79", "category": "minigames", "changelog": [ "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." ], "commit_sha": "f0771dc63724a5b92c95a52d6ee74d4cab35eaf0", "filename": "FillErUp.py", "md5": "e0f7e8c5b259c090269d269eca139106", "name": "Fill 'Er Up", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/FillErUp.py" }, "FlagDay": { "changelog": [ "Create FlagDay.pyThis game lets players take turns choosing their fate, picking flags and facing challenges to earn points. But beware..." ], "commit_sha": "55a13e781e5aca80752f59c5bb7f5ccb2a7b4042", "filename": "FlagDay.py", "md5": "0a15acdc7ff94ac6e3dff79b088338e0", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/55a13e781e5aca80752f59c5bb7f5ccb2a7b4042/mods/FlagDay.py" }, "GravityFalls": { "changelog": [ "Create GravityFalls.pyStay on the ground for as long as you can. Don't fly away in this crazy mod." ], "commit_sha": "729dc2dc599b17a889d1ebfcfef02bc52a0b6cef", "filename": "GravityFalls.py", "md5": "a7d7f9c4e898e18a758979bd7430b2b7", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/729dc2dc599b17a889d1ebfcfef02bc52a0b6cef/mods/GravityFalls.py" }, "Greed": { "author": "joshville79", "category": "minigames", "changelog": [ "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.", "move new mods to mods folder" ], "commit_sha": "f0771dc63724a5b92c95a52d6ee74d4cab35eaf0", "filename": "Greed.py", "md5": "c997ab2e55fc0f52a015a0109e0dd6c2", "name": "Greed", "old_md5s": [ "716fc4ab7c3c5f2562540dd2aa4b9531" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/Greed.py" }, "GuessTheBomb": { "author": "Paolo Valerdi", "category": "minigames", "changelog": [ "convert a bunch of files to linux line feeds (#25)", "New minigameThis is just another meteor shower game but the bombs fall randomly" ], "commit_sha": "a483c74a4b6a9c540ae58904bce5164dcfd5f4d4", "filename": "GuessTheBomb.py", "md5": "0c56aef73be562024b896e3ceef05cce", "name": "Guess The Bomb", "old_md5s": [ "069221cf6a656d3efe1ea69dabc12e8f" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/GuessTheBomb.py" }, "HazardousCargo": { "author": "joshville79", "category": "minigames", "changelog": [ "convert a bunch of files to linux line feeds (#25)", "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." ], "commit_sha": "a483c74a4b6a9c540ae58904bce5164dcfd5f4d4", "filename": "HazardousCargo.py", "md5": "1cb82c35c92a821a19d635854a1b7183", "name": "Hazardous Cargo", "old_md5s": [ "266a2b0a26d9a8c19fb8ab1afcaead83" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/HazardousCargo.py" }, "Infection": { "author": "joshville79", "category": "minigames", "changelog": [ "convert a bunch of files to linux line feeds (#25)", "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." ], "commit_sha": "a483c74a4b6a9c540ae58904bce5164dcfd5f4d4", "filename": "Infection.py", "md5": "b15d9384ea106b69b93c403d7a185f8f", "name": "Infection", "old_md5s": [ "a72d024cee252ae53039f2ad0936b198" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/Infection.py" }, "JumpingContest": { "changelog": [ "Create JumpingContest.pyThis mod tests an underappreciated aspect of the game: jumping." ], "commit_sha": "163ead5ffd2592f11c6ae2600f3f138baabfb018", "filename": "JumpingContest.py", "md5": "9f78c4bec42990c2504f2014914e7c24", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/163ead5ffd2592f11c6ae2600f3f138baabfb018/mods/JumpingContest.py" }, "LandGrab": { "author": "joshville79", "category": "minigames", "changelog": [ "rename \"Land Grab.py\" to \"LandGrab.py\" (#25)" ], "commit_sha": "8f28b96d4ae9df51bbc8718a932f5d17938c5ce4", "filename": "LandGrab.py", "md5": "95526447f0031c1fcb1e2488a50e0db9", "name": "Land Grab", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8f28b96d4ae9df51bbc8718a932f5d17938c5ce4/mods/LandGrab.py" }, "Paint": { "changelog": [ "Create Paint.pyPaint is a Co-op game where artists can create true masterpieces ... Bombsquad style." ], "commit_sha": "4ea62c8752eca7006fd7f0ecfed50b600b10b84a", "filename": "Paint.py", "md5": "12bc6efea06a65e10d571595e4824d8e", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/4ea62c8752eca7006fd7f0ecfed50b600b10b84a/mods/Paint.py" }, "Protection": { "author": "joshville79", "category": "minigames", "changelog": [ "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.", "move new mods to mods folder" ], "commit_sha": "f0771dc63724a5b92c95a52d6ee74d4cab35eaf0", "filename": "Protection.py", "md5": "82d3509f2d8a99381a6b82db8a7ef716", "name": "Protection", "old_md5s": [ "a8fee2500811ab273f0db546d7dceba8" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/Protection.py" }, "SharksAndMinnows": { "changelog": [ "Create SharksAndMinnows.pyThis classic game allows teams to play a sharks-and-minnows spin-off, compatible with Bombsquad rules and regulations." ], "commit_sha": "a3fe6d87b1e2cc1ed77cb44a0213b45eef4decb0", "filename": "SharksAndMinnows.py", "md5": "57ce4125981997c9367d0da439c56f55", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a3fe6d87b1e2cc1ed77cb44a0213b45eef4decb0/mods/SharksAndMinnows.py" }, "Siege": { "changelog": [ "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!" ], "commit_sha": "e1439dc9eca46d99470c09b59fdc8028c6f3c550", "filename": "Siege.py", "md5": "7e4b7063579f43402df08a2d6afebf8a", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/e1439dc9eca46d99470c09b59fdc8028c6f3c550/mods/Siege.py" }, "SimonSays": { "changelog": [ "Create SimonSays.pyThis is a classic game of Simon Says ... Bombsquad style. Follow the commands, but only when Simon says to." ], "commit_sha": "025f68c78c76ec390cae327a1679a7da0d9e9b2b", "filename": "SimonSays.py", "md5": "3872e52eab13983507591b48ecf0a2b3", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/025f68c78c76ec390cae327a1679a7da0d9e9b2b/mods/SimonSays.py" }, "SnoBallz": { "author": "joshville79", "category": "libraries", "changelog": [ "convert a bunch of files to linux line feeds (#25)", "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." ], "commit_sha": "a483c74a4b6a9c540ae58904bce5164dcfd5f4d4", "filename": "SnoBallz.py", "md5": "ad1b04613718179b8e901b7ceafd8384", "name": "SnoBallz powerup", "old_md5s": [ "a05749ae364883a4f23d05d6788c9edb" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/SnoBallz.py" }, "SnowBallFight": { "author": "joshville79", "category": "minigames", "changelog": [ "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." ], "commit_sha": "f0771dc63724a5b92c95a52d6ee74d4cab35eaf0", "filename": "SnowBallFight.py", "md5": "aaaaa9c177f0726d761eb18782dbee9f", "name": "Snowball Fight (requires SnoBallz.py)", "old_md5s": [], "requires": [ "SnoBallz" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/SnowBallFight.py" }, "WizardWar": { "author": "MattZ45986", "category": "minigames", "changelog": [ "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.", "Rename Wizard War.py to WizardWar.py" ], "commit_sha": "33e6d75ee504241c4206c6bd3e986e52ffa1b0d7", "filename": "WizardWar.py", "md5": "dd76a8d19ca21e33332153a0ce454ee1", "name": "WizardWar", "old_md5s": [ "46315134786af3bf46396918d663137d" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/33e6d75ee504241c4206c6bd3e986e52ffa1b0d7/mods/WizardWar.py" }, "ZombieHorde": { "author": "joshville79", "category": "minigames", "changelog": [ "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.", "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." ], "commit_sha": "f0771dc63724a5b92c95a52d6ee74d4cab35eaf0", "filename": "ZombieHorde.py", "md5": "2117baf2a313305199b39ce75970aba4", "name": "Zombie Horde", "old_md5s": [ "716d5b347b42f18156e7cc85a4a175ee" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f0771dc63724a5b92c95a52d6ee74d4cab35eaf0/mods/ZombieHorde.py" }, "airStrike": { "author": "SoKpl", "category": "minigames", "changelog": [ "bump all mod api versions", "add Air Strike, see #4" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "airStrike.py", "md5": "6cfa8b25079832edec4862958fc223f2", "name": "Air Strike", "old_md5s": [ "23a8cefb018aefae51442b0554fa6e64" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/airStrike.py" }, "arms_race": { "author": "Mrmaxmeier", "category": "minigames", "changelog": [ "prevent arms race upgrade on team kill (fixes #22)", "bump all mod api versions" ], "commit_sha": "4044dd3363efc75421db36cef3f770d499811d47", "filename": "arms_race.py", "md5": "6ef1cf7e90801851caf145ec1cb24f06", "name": "Arms Race", "old_md5s": [ "812023fcef83769a5a937cb8e8b90d44", "e23a86e0b95291192c1d3463df3b0605", "6ff1810f9d73a2b698d19216b36a4413", "b725e48f91fa04197aea4cc1eefc1cbf" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/4044dd3363efc75421db36cef3f770d499811d47/mods/arms_race.py" }, "auto_reloader": { "author": "Mrmaxmeier", "category": "utilities", "changelog": [ "bump all mod api versions", "rating ui" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "auto_reloader.py", "md5": "c80cb4f40bf8f9bd9b7a4f75b0eb0a9d", "name": "Auto Reloader", "old_md5s": [ "d7924cf4c9e684120f376d0710114391", "48611852f3386839093cd9115c2d6152", "115932c45f0443290c3798e8762b6e9e", "969de56c489c774d116eed0a40632b11", "41588189e303988a9151523e71e689b0" ], "supports": [ "config_editor" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/auto_reloader.py" }, "bomb_on_my_head": { "author": "Mrmaxmeier", "category": "minigames", "changelog": [ "bump all mod api versions", "refactoring" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "bomb_on_my_head.py", "md5": "0a2d23af4260764799268cf5bc3bdb43", "name": "Bomb on my Head", "old_md5s": [ "c16286fe16dd5f2594e0836953fdfd68", "6963c68439371d19fb52458e064dc930" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/bomb_on_my_head.py" }, "bomberman": { "author": "Mrmaxmeier", "category": "minigames", "changelog": [ "bump all mod api versions", "refactoring" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "bomberman.py", "md5": "b683a0481e1ef2f06525d6e3ff754fce", "name": "Bomberman", "old_md5s": [ "5019f3cb21e319b175bbeba4378e0926", "1eafb570a01dcb157f737ff391424cdf" ], "tag": "experimental", "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/bomberman.py" }, "boxing": { "author": "TheMikirog", "category": "minigames", "changelog": [ "bump all mod api versions", "Merge pull request #6 from TheMikirog/masterBoxing: Gameplay changes" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "boxing.py", "md5": "119d3ea4808d79d6c92942821565aec5", "name": "Boxing", "old_md5s": [ "f60cfb347f93e1fa25f56cad057ab0d5", "9485eac414e791f8eb06b7e93e0897e4" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/boxing.py" }, "brainFreeze": { "author": "TheMikirog", "category": "minigames", "changelog": [ "bump all mod api versions", "Rename brainFreeze to brainFreeze.py" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "brainFreeze.py", "md5": "bc7b703a365c67c5b37d6c6cb2c94f02", "name": "Brain Freeze", "old_md5s": [ "3a9b5bbdb96deacdb4504a752b3618ee" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/brainFreeze.py" }, "bsBoxingOfTheHill": { "author": "joshville79", "category": "minigames", "changelog": [ "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." ], "commit_sha": "09be1d940c0db5f01587911930cc212982a7b020", "filename": "bsBoxingOfTheHill.py", "md5": "dd81f8f2ccb148a6e88dd96d9e91f248", "name": "Boxing Of The Hill", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/09be1d940c0db5f01587911930cc212982a7b020/mods/bsBoxingOfTheHill.py" }, "bsKillZone": { "author": "joshville79", "category": "minigames", "changelog": [ "convert a bunch of files to linux line feeds (#25)", "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." ], "commit_sha": "a483c74a4b6a9c540ae58904bce5164dcfd5f4d4", "filename": "bsKillZone.py", "md5": "15040077256ea55a394cb28badd7ee06", "name": "Kill Zone", "old_md5s": [ "1b5c3d11a408f1cb78a09f0758b6e980" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/a483c74a4b6a9c540ae58904bce5164dcfd5f4d4/mods/bsKillZone.py" }, "catch_to_live": { "author": "Deva", "category": "minigames", "changelog": [ "Add more settings and change color to normal", "rename game" ], "commit_sha": "352e1a82a46c431459842c2a302a31385aa1e872", "filename": "catch_to_live.py", "md5": "23530bf4ea77e546dd99212203864409", "name": "CatchToLive", "old_md5s": [ "d279afd2952852bafb99d04a63d92e60" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/352e1a82a46c431459842c2a302a31385aa1e872/mods/catch_to_live.py" }, "fightOfFaith": { "author": "SoKpl", "category": "minigames", "changelog": [ "bump all mod api versions", "added 'Fight of Faith', see #1" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "fightOfFaith.py", "md5": "8acf4285dee7fb154290b2709006521d", "name": "Fight of Faith", "old_md5s": [ "c7f6864575253c2d4f594cb08daf2769" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/fightOfFaith.py" }, "frozenone": { "author": "Mrmaxmeier", "category": "minigames", "changelog": [ "bump all mod api versions", "refactoring" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "frozenone.py", "md5": "e37c20cad5f7e6e54c9961e15d30ce70", "name": "The Frozen One", "old_md5s": [ "c99d65d6b3051cb60d48c565c6b2b66e", "483fe2ea852fe51525748f472d7d787b", "0de2033b65a83260413a5a49607bb4c5", "0a6a3366c7103712a7119e5efc4d4a15" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/frozenone.py" }, "iceDeathmatch": { "author": "SoKpl", "category": "minigames", "changelog": [ "bump all mod api versions", "add \"Ice Deathmatch\" by SoKpl#7" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "iceDeathmatch.py", "md5": "052236109d110187da641b2dd5964ae1", "name": "Ice Deathmatch", "old_md5s": [ "9796fed0dd67c68a9e7eef95d4a51da1" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/iceDeathmatch.py" }, "magic_box": { "author": "Mrmaxmeier", "category": "minigames", "changelog": [ "bump all mod api versions", "cleanup magic_box" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "magic_box.py", "md5": "15f629062771aa87e538dc697c5eb525", "name": "Magic Box", "old_md5s": [ "7fae4c59cb8bfe02936722c1ac17a4bc", "244b4fcbdc2c74904254d47272bc4b9e", "3aac96b22db7dbc1e6cf2306d3454228", "2340f5eee0252ac43a2e2fb96e73700e" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/magic_box.py" }, "modManager": { "author": "Mrmaxmeier", "category": "utilities", "changelog": [ "only generate uuid if the stat server is enabled", "end smash game if team leaves completly" ], "commit_sha": "3672c8fdd3a6a26aa7321e264f33a9e9a83232e8", "filename": "modManager.py", "md5": "536b069e5b60bbeee052b03464991ce9", "name": "Mod Manager (this thingy)", "old_md5s": [ "79fad6241b448f45be7a97b7c5aa3dd9", "95137096a9c415267e4bfb4ee75c9b73", "5c0fac4ce24659ae410a56faced81d25", "72efc28be645667697116e6191ababfe", "d0619dbfde6337d656482312f38d4574" ], "requires": [ "ui_wrappers", "settings_patcher" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/3672c8fdd3a6a26aa7321e264f33a9e9a83232e8/mods/modManager.py" }, "puckDeathmatch": { "author": "Mrmaxmeier", "category": "minigames", "changelog": [ "bump all mod api versions", "puck timeout" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "puckDeathmatch.py", "md5": "4ee530ed550d4adcd1f5b2600688c066", "name": "Puck Deathmatch", "old_md5s": [ "8c4f61495cd1cb5221be02cfa8a58e45", "034af0481765c2502dc184e5657dc435" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/puckDeathmatch.py" }, "quickGameButton": { "author": "Mrmaxmeier", "category": "utilities", "changelog": [ "fix quick-game-button", "update quickGameButton to API v4" ], "commit_sha": "1d1bb169d32004924d6457a5f8ae4109cfece58c", "filename": "quickGameButton.py", "md5": "da1b86df0af645733d050ee5a5831747", "name": "Quick-Game Button", "old_md5s": [ "090df7b52669e73b3a898788d73a6bf8", "210c7ea410f847b8d4146801f7108471", "5b22370f25e872746283d45790af7e67", "9dbbc871cc8457bbced9420c11681c72", "a9d97c3630a84baafc34e68968d86eb7" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/1d1bb169d32004924d6457a5f8ae4109cfece58c/mods/quickGameButton.py" }, "settings_patcher": { "author": "Mrmaxmeier", "category": "libraries", "changelog": [ "replace bs.getResource calls", "even more flake8" ], "commit_sha": "724587f96a6011b863903b389ef7babce70976a7", "filename": "settings_patcher.py", "md5": "2a39de960bfcc989876e6f843df456a2", "name": "settings_patcher", "old_md5s": [ "b47906864b52626ccde30eba89491674", "fcfa6832da8c59e08afd1eef0d8139b5", "85217ec90f53b02dc3646030796f3dd5", "474dbf37a8b1f36e532e0e936f9c0f4b" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/724587f96a6011b863903b389ef7babce70976a7/mods/settings_patcher.py" }, "smash": { "author": "Mrmaxmeier", "category": "minigames", "changelog": [ "end smash game if team leaves completly", "bump all mod api versions" ], "commit_sha": "86072bfdadbd9205f897396825a8104fe2cbb630", "filename": "smash.py", "md5": "1210d0a95ee6fd3c9e05066dbc8a7c0b", "name": "Super Smash", "old_md5s": [ "44452c0419bb398be64dbac6379ba560", "339ab2df890ae78b57507224bab11985", "5916f2a3a15b623802e454dba70fb7f2", "5dd773cb8407f92618b29da5134cd6d2" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/86072bfdadbd9205f897396825a8104fe2cbb630/mods/smash.py" }, "snake": { "author": "Mrmaxmeier", "category": "minigames", "changelog": [ "bump all mod api versions", "refactoring" ], "commit_sha": "8d599cb0829b4f28d03db30e61ac618e8f1e0779", "filename": "snake.py", "md5": "49d878c10214d4ccec5df70b17e3793a", "name": "Snake", "old_md5s": [ "b7ed1f0b4f5ad311b2f35406ff07588f", "76032fe956ce6b9805bcb2453a1decf5" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/8d599cb0829b4f28d03db30e61ac618e8f1e0779/mods/snake.py" }, "snowyPowerup": { "author": "joshville79", "category": "libraries", "changelog": [ "fix for bsFootball's powerup handling", "Renabe bsPowerup to snowyPowerup, utilize monkey patching (#25)" ], "commit_sha": "017a13dbe85c78220175fb795bd81e1b5e66c0e9", "filename": "snowyPowerup.py", "md5": "ce805e9d11cef2002d24b9dab6654595", "name": "Modified bsPowerup.py", "old_md5s": [ "9a5abe159a10e15f5219dce1ac7e6176" ], "requires": [ "BuddyBunny", "SnoBallz" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/017a13dbe85c78220175fb795bd81e1b5e66c0e9/mods/snowyPowerup.py" }, "surviveCurse": { "author": "joshville79", "category": "minigames", "changelog": [ "move new mods to mods folder" ], "commit_sha": "22846a813dc5a550a49a74307d86c3601d58cf4e", "filename": "surviveCurse.py", "md5": "29e21b16f3d7e4003532f0155a175ac3", "name": "Survive the Curse!", "old_md5s": [], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/22846a813dc5a550a49a74307d86c3601d58cf4e/mods/surviveCurse.py" }, "ui_wrappers": { "author": "Mrmaxmeier", "category": "libraries", "changelog": [ "add note about textWidget.set(text=...)", "more flake8" ], "commit_sha": "f2c1140ba899c0a5dbbe683fd767343449258942", "filename": "ui_wrappers.py", "md5": "421c7d87f4d832e2d2e42c462fc3d63a", "name": "ui_wrappers", "old_md5s": [ "9f813943333a21f139b45487c0579224", "069d4605c92528211e0a8c8bb67a3c22", "cbfce8f368e32a21159b1ed94f2d55b1", "7df3a7c699903f8a0c8b8d6c847b0840", "8d1e6e55ae9a953de95278d5317c3476" ], "url": "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/f2c1140ba899c0a5dbbe683fd767343449258942/mods/ui_wrappers.py" } }, "version": 1.1 } ================================================ FILE: mods/AroundTheWorld.py ================================================ import bs import random import bsUtils def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [AroundTheWorld] class AroundTheWorld(bs.TeamGameActivity): @classmethod def getName(cls): return 'Around The World' @classmethod def getDescription(cls,sessionType): return 'Race around the world.' @classmethod def getScoreInfo(cls): return {'scoreName':'Time', 'lowerIsBetter':True, 'scoreType':'milliseconds'} @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return ['Happy Thoughts'] @classmethod def getSettings(cls,sessionType): settings = [("Laps",{'minValue':1,"default":3,"increment":1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Epic Mode",{'default':False})] if issubclass(sessionType,bs.TeamsSession): settings.append(("Entire Team Must Finish",{'default':False})) return settings def __init__(self,settings): self._raceStarted = False bs.TeamGameActivity.__init__(self,settings) for player in self.players: player.gameData['lastPoint'] = 0 self._scoreBoard = bs.ScoreBoard() if self.settings['Epic Mode']: self._isSlowMotion = True self._scoreSound = bs.getSound("score") self._swipSound = bs.getSound("swip") self._lastTeamTime = None self._frontRaceRegion = None self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) def getInstanceDescription(self): if isinstance(self.getSession(),bs.TeamsSession) and self.settings.get('Entire Team Must Finish', False): tStr = ' Your entire team has to finish.' else: tStr = '' if self.settings['Laps'] > 1: s = ('${ARG1} laps.'+tStr,self.settings['Laps']) else: s = 'Fly 1 lap.'+tStr return s def getInstanceScoreBoardDescription(self): if self.settings['Laps'] > 1: s = ('fly ${ARG1} laps',self.settings['Laps']) else: s = 'fly 1 lap' return s def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None): posList = ((0,5,0),(9,11,0),(0,12,0),(-11,11,0)) try: pos = posList[player.gameData['lastPoint']] except: pos = (0,5,0) position = (pos[0]+random.random()*2 -1 ,pos[1],pos[2]) name = player.getName() color = player.color highlight = player.highlight lightColor = bsUtils.getNormalizedColor(color) displayColor = bs.getSafeColor(color,targetIntensity=0.75) spaz = bs.PlayerSpaz(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor if self._raceStarted: spaz.connectControlsToPlayer() spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360))) t = bs.getGameTime() bs.playSound(self._spawnSound,1,position=spaz.node.position) light = bs.newNode('light',attrs={'color':lightColor}) spaz.node.connectAttr('position',light,'position') bsUtils.animate(light,'intensity',{0:0,250:1,500:0}) bs.gameTimer(500,light.delete) if not self._raceStarted: player.gameData['lastPoint'] = 0 bs.gameTimer(250,bs.Call(self.checkPt,player)) return spaz def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic Race' if self.settings['Epic Mode'] else 'Race') self._nubTex = bs.getTexture('nub') self._beep1Sound = bs.getSound('raceBeep1') self._beep2Sound = bs.getSound('raceBeep2') def _flashPlayer(self,player,scale): pos = player.actor.node.position light = bs.newNode('light', attrs={'position':pos, 'color':(1,1,0), 'heightAttenuated':False, 'radius':0.4}) bs.gameTimer(500,light.delete) bs.animate(light,'intensity',{0:0,100:1.0*scale,500:0}) def onTeamJoin(self,team): team.gameData['time'] = None team.gameData['lap'] = 0 team.gameData['finished'] = False self._updateScoreBoard() def onPlayerJoin(self,player): player.gameData['lastRegion'] = 0 player.gameData['lap'] = 0 player.gameData['distance'] = 0.0 player.gameData['finished'] = False player.gameData['rank'] = None bs.TeamGameActivity.onPlayerJoin(self,player) def onPlayerLeave(self,player): bs.TeamGameActivity.onPlayerLeave(self,player) if isinstance(self.getSession(),bs.TeamsSession) and self.settings.get('Entire Team Must Finish'): bs.screenMessage(bs.Lstr(translate=('statements', '${TEAM} is disqualified because ${PLAYER} left'), subs=[('${TEAM}',player.getTeam().name), ('${PLAYER}',player.getName(full=True))]),color=(1,1,0)) player.getTeam().gameData['finished'] = True player.getTeam().gameData['time'] = None player.getTeam().gameData['lap'] = 0 bs.playSound(bs.getSound("boo")) for player in player.getTeam().players: player.gameData['lap'] = 0 player.gameData['finished'] = True try: player.actor.handleMessage(bs.DieMessage()) except Exception: pass bs.gameTimer(1,self._checkEndGame) def _updateScoreBoard(self): for team in self.teams: distances = [player.gameData['distance'] for player in team.players] if len(distances) == 0: teamDist = 0 else: if isinstance(self.getSession(),bs.TeamsSession) and self.settings.get('Entire Team Must Finish'): teamDist = min(distances) else: teamDist = max(distances) self._scoreBoard.setTeamValue(team,teamDist,self.settings['Laps'],flash=(teamDist >= float(self.settings['Laps'])),showValue=False) if (teamDist >= float(self.settings['Laps'])): self.checkEnd() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops() self._teamFinishPts = 100 # throw a timer up on-screen self._timeText = bs.NodeActor(bs.newNode('text', attrs={'vAttach':'top','hAttach':'center','hAlign':'right', 'color':(1,1,1,.5),'flatness':0.5,'shadow':0.5, 'position':(600,-500),'scale':1.4,'text':'Touch\nthe\nright,\ntop,\nleft,\nand\nbottom\nplatforms\nin\norder.'})) self._timer = bs.OnScreenTimer() self._scoreBoardTimer = bs.Timer(250,self._updateScoreBoard,repeat=True) if self._isSlowMotion: tScale = 0.4 lightY = 50 else: tScale = 1.0 lightY = 150 lStart = int(7100*tScale) inc = int(1250*tScale) bs.gameTimer(lStart,self._doLight1) bs.gameTimer(lStart+inc,self._doLight2) bs.gameTimer(lStart+2*inc,self._doLight3) bs.gameTimer(lStart+3*inc,self._startRace) self._startLights = [] for i in range(4): l = bs.newNode('image', attrs={'texture':bs.getTexture('nub'), 'opacity':1.0, 'absoluteScale':True, 'position':(-75+i*50,lightY), 'scale':(50,50), 'attach':'center'}) bs.animate(l,'opacity',{4000*tScale:0,5000*tScale:1.0,12000*tScale:1.0,12500*tScale:0.0}) bs.gameTimer(int(13000*tScale),l.delete) self._startLights.append(l) self._startLights[0].color = (0.2,0,0) self._startLights[1].color = (0.2,0,0) self._startLights[2].color = (0.2,0.05,0) self._startLights[3].color = (0.0,0.3,0) def _doLight1(self): self._startLights[0].color = (1.0,0,0) bs.playSound(self._beep1Sound) def _doLight2(self): self._startLights[1].color = (1.0,0,0) bs.playSound(self._beep1Sound) def _doLight3(self): self._startLights[2].color = (1.0,0.3,0) bs.playSound(self._beep1Sound) def _startRace(self): self._startLights[3].color = (0.0,1.0,0) bs.playSound(self._beep2Sound) for player in self.players: if player.actor is not None: try:player.actor.connectControlsToPlayer() except Exception,e: print 'Exception in race player connects:',e self._timer.start() self._raceStarted = True def checkPt(self,player): if not player.isAlive(): return pos = player.actor.node.positionCenter if 8 < pos[0] < 11 and 10.5 < pos[1] < 13: if player.gameData['lastPoint'] in (2,3): self.killPlayer(player) return elif player.gameData['lastPoint'] == 0: player.gameData['distance'] += .25 player.gameData['lastPoint'] = 1 if -1 < pos[0] < 1 and 11.5 < pos[1] < 15: if player.gameData['lastPoint'] in (3,0): self.killPlayer(player) return elif player.gameData['lastPoint'] == 1: player.gameData['distance'] += .25 player.gameData['lastPoint'] = 2 if -12.5 < pos[0] < -10 and 10.5 < pos[1] < 13: if player.gameData['lastPoint'] in (0,1): self.killPlayer(player) return elif player.gameData['lastPoint'] == 2: player.gameData['distance'] += .25 player.gameData['lastPoint'] = 3 if -2 < pos[0] < 2 and 4.5 < pos[1] < 6.5: if player.gameData['lastPoint'] in (1,2): self.killPlayer(player) return elif player.gameData['lastPoint'] == 3: player.gameData['distance'] += .25 player.gameData['lastPoint'] = 0 bs.gameTimer(250,bs.Call(self.checkPt,player)) def checkEnd(self): for player in self.players: if player.gameData['distance'] >= self.settings['Laps']: player.getTeam().gameData['time'] = (bs.getGameTime() - self._timer.getStartTime()) player.actor.node.delete() self.endGame() def killPlayer(self,player): player.actor.handleMessage(bs.DieMessage()) bs.screenMessage("Killing " + player.getName() + " for skipping part of the track.", (1,0,0)) def endGame(self): if self._timer.hasStarted(): self._timer.stop(endTime=None if self._lastTeamTime is None else (self._timer.getStartTime()+self._lastTeamTime)) results = bs.TeamGameResults() for t in self.teams: results.setTeamScore(t,t.gameData['time']) self.end(results=results,announceWinningTeam=True) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # augment default try: player = m.spaz.getPlayer() if player is None: bs.printError('FIXME: getPlayer() should no longer ever be returning None') else: if not player.exists(): raise Exception() team = player.getTeam() except Exception: return if not player.gameData['finished']: self.respawnPlayer(player,respawnTime=1000) else: bs.TeamGameActivity.handleMessage(self,m) ================================================ FILE: mods/BackToYou.json ================================================ { "name": "Back To You", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/BackToYou.py ================================================ import bs import random import bsUtils #import PlayerSpaz def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [BackToYou] class Icon(bs.Actor): def __init__(self,player,position,scale,showLives=True,showDeath=True, nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0): bs.Actor.__init__(self) self._player = player self._showLives = showLives self._showDeath = showDeath self._nameScale = nameScale self._outlineTex = bs.getTexture('characterIconMask') icon = player.getIcon() self.node = bs.newNode('image', owner=self, attrs={'texture':icon['texture'], 'tintTexture':icon['tintTexture'], 'tintColor':icon['tintColor'], 'vrDepth':400, 'tint2Color':icon['tint2Color'], 'maskTexture':self._outlineTex, 'opacity':1.0, 'absoluteScale':True, 'attach':'bottomCenter'}) self._nameText = bs.newNode('text', owner=self.node, attrs={'text':player.getName(), 'color':bs.getSafeColor(player.getTeam().color), 'hAlign':'center', 'vAlign':'center', 'vrDepth':410, 'maxWidth':nameMaxWidth, 'shadow':shadow, 'flatness':flatness, 'hAttach':'center', 'vAttach':'bottom'}) if self._showLives: self._livesText = bs.newNode('text', owner=self.node, attrs={'text':'x0', 'color':(1,1,0.5), 'hAlign':'left', 'vrDepth':430, 'shadow':1.0, 'flatness':1.0, 'hAttach':'center', 'vAttach':'bottom'}) self.setPositionAndScale(position,scale) def setPositionAndScale(self,position,scale): self.node.position = position self.node.scale = [70.0*scale] self._nameText.position = (position[0],position[1]+scale*52.0) self._nameText.scale = 1.0*scale*self._nameScale if self._showLives: self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0) self._livesText.scale = 1.0*scale def updateForLives(self): if self._player.exists(): lives = self._player.gameData['lives'] else: lives = 0 if self._showLives: if lives > 0: self._livesText.text = 'x'+str(lives-1) else: self._livesText.text = '' if lives == 0: myAct = self._player.actor.getActivity() if self._player in myAct.winners: if myAct.winners[0] == self._player: self._livesText.text = "1st" elif myAct.winners[1] == self._player: self._livesText.text = "2nd" elif myAct.winners[2] == self._player: self._livesText.text = "3rd" else: self._nameText.opacity = 0.2 self.node.color = (0.7,0.3,0.3) self.node.opacity = 0.2 def handlePlayerSpawned(self): if not self.node.exists(): return self.node.opacity = 1.0 self.updateForLives() def handlePlayerDied(self): if not self.node.exists(): return if self._showDeath: bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0, 300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2}) lives = self._player.gameData['lives'] if lives == 0: bs.gameTimer(600,self.updateForLives) class PlayerSpaz_BTY(bs.PlayerSpaz): def handleMessage(self, m): if isinstance(m, bs.HitMessage): if not self.node.exists(): return if not self.isAlive(): return #We don't want to be hitting corpses! srcSpaz = None theGame = self.getActivity() for theSpaz in theGame.spazList: if theSpaz.getPlayer() == m.sourcePlayer: srcSpaz = theSpaz break #print(["HitSrc", srcSpaz]) #print(["hitSpaz", self]) if not srcSpaz == self: if not srcSpaz == None: #We need to calculate new position for hit. Otherwise it won't #actually hit the source spaz if he's across the screen p1 = m.pos p2 = self.node.position p3 = srcSpaz.node.position hit2spaz = [p2[0]-p1[0],p2[1]-p1[1], p2[2]-p1[2]] m.pos = [p3[0]-hit2spaz[0], p3[1]-hit2spaz[1], p3[2]-hit2spaz[2]] m.sourcePlayer = self.getPlayer() #print(['sroucenode', m.srcNode]) #print(['pos', m.pos]) #print(['velocity', m.velocity]) #print(['magnitude',m.magnitude]) #print(['vMag', m.velocityMagnitude]) #print(['radisu', m.radius]) #print([m.sourcePlayer]) #print(['kickback', m.kickBack]) #print(['flat', m.flatDamage]) #print(['hittype', m.hitType]) #print(['forcedir', m.forceDirection]) #print(['Hitsubtype', m.hitSubType]) super(srcSpaz.__class__, srcSpaz).handleMessage(m) #if isinstance(m, bs.ImpactDamageMessage): #print(["impact", m.intensity]) #super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) class BackToYou(bs.TeamGameActivity): @classmethod def getName(cls): return 'Back To You!' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'seconds', 'noneIsWinner':False, 'lowerIsBetter':True} @classmethod def getDescription(cls,sessionType): return 'Damage others to kill yourself! First one out wins!' @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): settings = [("Lives Per Player",{'default':1,'minValue':1,'maxValue':10,'increment':1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Epic Mode",{'default':False})] if issubclass(sessionType,bs.TeamsSession): settings.append(("Solo Mode",{'default':False})) settings.append(("Balance Total Lives",{'default':False})) return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # show messages when players die since it's meaningful here self.announcePlayerDeaths = True try: self._soloMode = settings['Solo Mode'] except Exception: self._soloMode = False self._scoreBoard = bs.ScoreBoard() self.spazList = [] self.winners = [] def getInstanceDescription(self): return 'First team out wins.' if isinstance(self.getSession(),bs.TeamsSession) else 'Damage others to kill yourself! First one out wins!' def getInstanceScoreBoardDescription(self): return 'first team out wins' if isinstance(self.getSession(),bs.TeamsSession) else 'Damage others to kill yourself! First one out wins!' def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onTeamJoin(self,team): team.gameData['survivalSeconds'] = None team.gameData['spawnOrder'] = [] def onPlayerJoin(self, player): player.gameData['lives'] = self.settings['Lives Per Player'] if self._soloMode: player.gameData['icons'] = [] player.getTeam().gameData['spawnOrder'].append(player) self._updateSoloMode() else: # create our icon and spawn player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)] if player.gameData['lives'] > 0: self.spawnPlayer(player) # dont waste time doing this until begin if self.hasBegun(): self._updateIcons() def _updateSoloMode(self): # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) break def _updateIcons(self): # in free-for-all mode, everyone is just lined up along the bottom if isinstance(self.getSession(),bs.FreeForAllSession): count = len(self.teams) xOffs = 85 x = xOffs*(count-1) * -0.5 for i,team in enumerate(self.teams): if len(team.players) == 1: player = team.players[0] for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs # in teams mode we split up teams else: if self._soloMode: # first off, clear out all icons for player in self.players: player.gameData['icons'] = [] # now for each team, cycle through our available players adding icons for team in self.teams: if team.getID() == 0: x = -60 xOffs = -78 else: x = 60 xOffs = 78 isFirst = True testLives = 1 while True: playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives] if len(playersWithLives) == 0: break for player in playersWithLives: player.gameData['icons'].append(Icon(player, position=(x,(40 if isFirst else 25)), scale=1.0 if isFirst else 0.5, nameMaxWidth=130 if isFirst else 75, nameScale=0.8 if isFirst else 1.0, flatness=0.0 if isFirst else 1.0, shadow=0.5 if isFirst else 1.0, showDeath=True if isFirst else False, showLives=False)) x += xOffs * (0.8 if isFirst else 0.56) isFirst = False testLives += 1 # non-solo mode else: for team in self.teams: if team.getID() == 0: x = -50 xOffs = -85 else: x = 50 xOffs = 85 for player in team.players: for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs def _getSpawnPoint(self,player): # in solo-mode, if there's an existing live player on the map, spawn at whichever # spot is farthest from them (keeps the action spread out) if self._soloMode: livingPlayer = None for team in self.teams: for player in team.players: if player.isAlive(): p = player.actor.node.position livingPlayer = player livingPlayerPos = p break if livingPlayer: playerPos = bs.Vector(*livingPlayerPos) points = [] for team in self.teams: startPos = bs.Vector(*self.getMap().getStartPosition(team.getID())) points.append([(startPos-playerPos).length(),startPos]) points.sort() return points[-1][1] else: return None else: return None def spawnPlayer(self,player): """This next line is the default spawn line. But we need to spawn our special guy""" #self.spawnPlayerSpaz(player,self._getSpawnPoint(player)) #position = self._getSpawnPoint(player) #if isinstance(self.getSession(), bs.TeamsSession): # position = self.getMap().getStartPosition(player.getTeam().getID()) #else: # # otherwise do free-for-all spawn locations position = self.getMap().getFFAStartPosition(self.players) angle = 20 #spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up #spaz.connectControlsToPlayer(enablePunch=False, # enableBomb=False, # enablePickUp=False) # also lets have them make some noise when they die.. #spaz.playBigDeathSound = True name = player.getName() lightColor = bsUtils.getNormalizedColor(player.color) displayColor = bs.getSafeColor(player.color, targetIntensity=0.75) spaz = PlayerSpaz_BTY(color=player.color, highlight=player.highlight, character=player.character, player=player) player.setActor(spaz) #For some reason, I can't figure out how to get a list of all spaz. #Therefore, I am making the list here so I can get which spaz belongs #to the player supplied by HitMessage. self.spazList.append(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() self.scoreSet.playerGotNewSpaz(player, spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360))) t = bs.getGameTime() bs.playSound(self._spawnSound, 1, position=spaz.node.position) light = bs.newNode('light', attrs={'color': lightColor}) spaz.node.connectAttr('position', light, 'position') bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0}) bs.gameTimer(500, light.delete) #Start code to spawn special guy: #End of code to spawn special guy if not self._soloMode: bs.gameTimer(300,bs.Call(self._printLives,player)) # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerSpawned() def _printLives(self,player): if not player.exists() or not player.isAlive(): return try: pos = player.actor.node.position except Exception,e: print 'EXC getting player pos in bsElim',e return bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1), offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain() def onPlayerLeave(self,player): bs.TeamGameActivity.onPlayerLeave(self,player) player.gameData['icons'] = None if player in self.winners: self.winners.remove(player) # remove us from spawn-order if self._soloMode: if player in player.getTeam().gameData['spawnOrder']: player.getTeam().gameData['spawnOrder'].remove(player) # update icons in a moment since our team will be gone from the list then bs.gameTimer(0, self._updateIcons) def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops() if self._soloMode: self._vsText = bs.NodeActor(bs.newNode("text", attrs={'position':(0,105), 'hAttach':"center", 'hAlign':'center', 'maxWidth':200, 'shadow':0.5, 'vrDepth':390, 'scale':0.6, 'vAttach':"bottom", 'color':(0.8,0.8,0.3,1.0), 'text':bs.Lstr(resource='vsText')})) # if balance-team-lives is on, add lives to the smaller team until total lives match if (isinstance(self.getSession(),bs.TeamsSession) and self.settings['Balance Total Lives'] and len(self.teams[0].players) > 0 and len(self.teams[1].players) > 0): if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]): lesserTeam = self.teams[0] greaterTeam = self.teams[1] else: lesserTeam = self.teams[1] greaterTeam = self.teams[0] addIndex = 0 while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam): lesserTeam.players[addIndex].gameData['lives'] += 1 addIndex = (addIndex + 1) % len(lesserTeam.players) self._updateIcons() # we could check game-over conditions at explicit trigger points, # but lets just do the simple thing and poll it... bs.gameTimer(1000, self._update, repeat=True) def _getTotalTeamLives(self,team): return sum(player.gameData['lives'] for player in team.players) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() respawnPoints = None print([player, m.spaz.hitPoints, "killed by", m.killerPlayer]) if m.killerPlayer is None: pass #Don't take away a life for non-violent death elif m.killerPlayer == m.spaz.getPlayer(): pass #No credit for suicide! elif m.spaz.hitPoints > 0: #Spaz died, but had positive hit points. Probably fell. Take points from player. #tossing or knocking off a player respawns them w/o taking life. print([player, "died from fall.", m.spaz.hitPoints]) pass else: player.gameData['lives'] -= 1 #Remove this spaz from the list of active spazzes if m.spaz in self.spazList: self.spazList.remove(m.spaz) if player.gameData['lives'] < 0: bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode)) player.gameData['lives'] = 0 # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerDied() # play big death sound on our last death or for every one in solo mode if self._soloMode or player.gameData['lives'] == 0: bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound) # if we hit zero lives, we're dead (and our team might be too) if player.gameData['lives'] == 0: # if the whole team is now dead, mark their survival time.. #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players): if self._getTotalTeamLives(player.getTeam()) == 0: player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000 self.winners.append(player) else: # otherwise, in regular mode, respawn.. if not self._soloMode: self.respawnPlayer(player) # in solo, put ourself at the back of the spawn order if self._soloMode: player.getTeam().gameData['spawnOrder'].remove(player) player.getTeam().gameData['spawnOrder'].append(player) else: bs.TeamGameActivity.handleMessage(self, m) def _update(self): if self._soloMode: # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) self._updateIcons() break # if we're down to 1 or fewer living teams, start a timer to end the game # (allows the dust to settle and draws to occur if deaths are close enough) if (len(self._getLivingTeams()) < 2) or len(self.winners) > 2: self._roundEndTimer = bs.Timer(500,self.endGame) def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)] def endGame(self): if self.hasEnded(): return results = bs.TeamGameResults() self._vsText = None # kill our 'vs' if its there for team in self.teams: results.setTeamScore(team, team.gameData['survivalSeconds']) self.end(results=results) ================================================ FILE: mods/Basketball.json ================================================ { "name": "Basketball", "author": "MattZ45986", "category": "minigames" } ================================================ FILE: mods/Basketball.py ================================================ #Basketball import bs import bsUtils import math import random # This game is played with the same rules as the classic American sport, Bombsquad style! # Featuring: a hoop, fouls, foul shots, jump-balls, three-pointers, a referee, and two teams ready to duke it out. # Dedicated to - David def bsGetAPIVersion(): return 4 def bsGetGames(): return [Basketball] class ImpactMessage(object): pass class Referee(bs.SpazBot): character = 'Bernard' chargeDistMax = 9999 throwDistMin = 9999 throwDistMax = 9999 color=(0,0,0) highlight=(1,1,1) punchiness = 0.0 chargeSpeedMin = 0.0 chargeSpeedMax = 0.0 class Hoop(bs.Actor): def __init__(self,position=(0,5,-8),color=(1,1,1)): self._r1 = 0.7 self._rFudge = 0.15 bs.Actor.__init__(self) self._position = bs.Vector(*position) self.color = color p1 = position p2 = (position[0]+1,position[1],position[2]) p3 = (position[0]-1,position[1],position[2]) showInSpace = False self._hit = False n1 = bs.newNode('locator',attrs={'shape':'circle','position':p1, 'color':self.color,'opacity':0.5, 'drawBeauty':showInSpace,'additive':True}) n2 = bs.newNode('locator',attrs={'shape':'circle','position':p2, 'color':self.color,'opacity':0.5, 'drawBeauty':showInSpace,'additive':True}) n3 = bs.newNode('locator',attrs={'shape':'circle','position':p3, 'color':self.color,'opacity':0.5, 'drawBeauty':showInSpace,'additive':True}) n4 = bs.newNode('light',attrs={'color':self.color,'position':p1,'intensity':.5}) bs.animateArray(n1,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n2,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n3,'size',1,{0:[0.0],200:[self._r1*2.0]}) self._nodes = [n1,n2,n3,n4] class ThreePointLine(bs.Actor): def __init__(self): bs.Actor.__init__(self) r1 = 6 n1 = bs.newNode('locator',attrs={'shape':'circleOutline','position':(0,4,-8),'color':(1,1,1),'opacity':.3,'drawBeauty':False,'additive':True}) self._nodes = [n1] bs.animateArray(n1,'size',1,{50:[0.0],250:[r1*2.0]}) class BasketBallFactory(bs.BombFactory): def __init__(self): self.basketBallMaterial = bs.Material() self.basketBallMaterial.addActions(conditions=(('weAreOlderThan',200), 'and',('theyAreOlderThan',200), 'and',('evalColliding',), 'and',(('theyHaveMaterial',bs.getSharedObject('footingMaterial')), 'or',('theyHaveMaterial',bs.getSharedObject('objectMaterial')))), actions=(('message','ourNode','atConnect',ImpactMessage()))) bs.BombFactory.__init__(self) class Baller(bs.PlayerSpaz): def onBombPress(self): pass def onPickUpPress(self): bs.PlayerSpaz.onPickUpPress(self) self.node.getDelegate()._pos = self.node.positionCenter class BasketBomb(bs.Bomb): def __init__(self,position=(0,1,0),velocity=(0,0,0),bombType='normal',blastRadius=2.0,sourcePlayer=None,owner=None): bs.Actor.__init__(self) self.up = False factory = BasketBallFactory() self.bombType = 'basketball' self._exploded = False self.blastRadius = blastRadius self._explodeCallbacks = [] self.sourcePlayer = sourcePlayer self.hitType = 'impact' self.hitSubType = 'basketball' owner = bs.Node(None) self.owner = owner materials = (factory.bombMaterial, bs.getSharedObject('objectMaterial')) materials = materials + (factory.normalSoundMaterial,) materials = materials + (factory.basketBallMaterial,) self.node = bs.newNode('prop', delegate=self, attrs={'position':position, 'velocity':velocity, 'body':'sphere', 'model':factory.bombModel, 'shadowSize':0.3, 'colorTexture':bs.getTexture('bonesColorMask'), 'reflection':'soft', 'reflectionScale':[1.5], 'materials':materials}) bsUtils.animate(self.node,"modelScale",{0:0, 200:1.3, 260:1}) def handleMessage(self, m): if isinstance(m, bs.OutOfBoundsMessage): self.getActivity().respawnBall((not self.getActivity().possession)) bs.Bomb.handleMessage(self, m) elif isinstance(m, bs.PickedUpMessage): self.heldLast = m.node.getDelegate().getPlayer() self.getActivity().heldLast = self.heldLast if self.heldLast in self.getActivity().teams[0].players: self.getActivity().possession = True else: self.getActivity().possession = False bs.Bomb.handleMessage(self, m) if self.up == True: activity = self.getActivity() bs.gameTimer(3000,bs.WeakCall(activity.jumpBall)) self.up = True elif isinstance(m, ImpactMessage): self.getActivity().handleShot(self) elif isinstance(m, bs.DroppedMessage): self.up = False else: bs.Bomb.handleMessage(self, m) class Basketball(bs.TeamGameActivity): @classmethod def getName(cls): return "Basketball" @classmethod def getDescription(cls, sessionType): return "A classic sport, Bombsquad style!" @classmethod def getScoreInfo(cls): return{'scoreType':'points'} @classmethod def getSettings(cls, sessionType): return [("Epic Mode", {'default': False}), ("Enable Running", {'default': True}), ("Enable Jumping", {'default': True}), ("Play To: ", { 'choices': [ ('1 point', 1), ('11 points', 11), ('21 points', 21), ('45 points', 45), ('100 points', 100) ], 'default': 21})] @classmethod def getSupportedMaps(cls, sessionType): return ['Courtyard'] @classmethod def supportsSessionType(cls, sessionType): return True if issubclass(sessionType, bs.TeamsSession) else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) self.possession = True self.heldLast = None self.fouled = False self.firstFoul = False self.jb = True self.blueBench = bs.newNode('light', attrs={ 'color':(0,0,1),'intensity':1,'position':(-6.5,0,-2)}) self.redBench = bs.newNode('light', attrs={ 'color':(1,0,0),'intensity':1,'position':(6.5,0,-2)}) self._bots = bs.BotSet() self.hoop = Hoop((0,5,-8), (1,1,1)) self.threePointLine = ThreePointLine().autoRetain() self._scoredis = bs.ScoreBoard() self.referee = Referee bs.gameTimer(10,bs.Call(self._bots.spawnBot,self.referee,pos=(-6,3,-6),spawnTime=1)) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self,music='Sports') def onBegin(self): bs.TeamGameActivity.onBegin(self) s = self.settings for player in self.players: player.actor.connectControlsToPlayer(enableBomb=False, enableRun = s["Enable Running"], enableJump = s["Enable Jumping"]) player.sessionData['fouls'] = 0 self.respawnBall(None) self.teams[0].gameData['score'] = 0 self.teams[1].gameData['score'] = 0 self._scoredis.setTeamValue(self.teams[0],self.teams[1].gameData['score']) self._scoredis.setTeamValue(self.teams[1],self.teams[1].gameData['score']) self.updateScore() self.checkEnd() def spawnPlayerSpaz(self,player,position=(0,5,-3),angle=None, killedDuringFoulShots = False): name = player.getName() color = player.color highlight = player.highlight spaz = Baller(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) if player in self.teams[0].players: position = (-6.5,3.2,(random.random()*5)-4.5) else: position = (6.5,3.2,(random.random()*5)-4.5) if self.fouled == True and killedDuringFoulShots == False: position = (0,3.2,-3) s = self.settings player.actor.connectControlsToPlayer(enableBomb=False, enableRun = s["Enable Running"], enableJump = s["Enable Jumping"]) spaz.handleMessage(bs.StandMessage(position,90)) def respawnBall(self, owner): if owner == True: self.basketball = BasketBomb(position=(-6,5,-3)).autoRetain() elif owner == False: self.basketball = BasketBomb(position=(6,5,-3)).autoRetain() else: self.basketball = BasketBomb(position=(0,5,-2.5)).autoRetain() def handleMessage(self, m): if isinstance(m, bs.SpazBotDeathMessage): if m.killerPlayer in self.teams[0].players: results = bs.TeamGameResults() results.setTeamScore(self.teams[0],0) results.setTeamScore(self.teams[1],100) self.end(results=results) bs.screenMessage("Don't take it out on the ref!", color=(1,0,0)) elif m.killerPlayer in self.teams[1].players: results = bs.TeamGameResults() results.setTeamScore(self.teams[1],0) results.setTeamScore(self.teams[0],100) self.end(results=results) bs.screenMessage("Don't take it out on the ref!", color=(0,0,1)) elif isinstance(m, bs.PlayerSpazDeathMessage): if m.killed: if m.spaz.getPlayer() in self.teams[0].players: team = self.teams[0] elif m.spaz.getPlayer() in self.teams[1].players: team = self.teams[1] if m.killerPlayer not in team.players: m.killerPlayer.sessionData['fouls'] += 1 m.killerPlayer.actor.setScoreText("FOUL " + str(m.killerPlayer.sessionData['fouls'])) bs.playSound(bs.getSound('bearDeath')) if m.killerPlayer.sessionData['fouls'] == 3: self.foulOut(m.killerPlayer) if self.fouled == True: self.spawnPlayerSpaz(player=m.spaz.getPlayer(),killedDuringFoulShots=True) return self.fouled = True self.giveFoulShots(m.spaz) elif m.spaz.getPlayer().sessionData['fouls'] < 3: self.respawnPlayer(m.spaz.getPlayer()) elif m.spaz.getPlayer().sessionData['fouls'] < 3: self.respawnPlayer(m.spaz.getPlayer()) s = self.settings else: bs.TeamGameActivity.handleMessage(self, m) def giveFoulShots(self, player): for p in self.players: p.actor.disconnectControlsFromPlayer() 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) 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) self.spawnPlayerSpaz(player.getPlayer()) name = player.getPlayer().getName() for p in self.players: if p.getName() == name: player = p.actor s = self.settings player.connectControlsToPlayer(enableBomb=False, enableRun = s["Enable Running"], enableJump = s["Enable Jumping"],enablePunch = False) self.firstFoul = True self.basketball.node.delete() self.respawnBall(None) sound = bs.getSound('announceTwo') bs.gameTimer(1000, bs.Call(bs.playSound,sound)) player.node.handleMessage('stand',0,3.2,-3, 0) player.onPickUpPress() player.onPickUpRelease() player.setScoreText("5 seconds to shoot") bs.gameTimer(6000, bs.Call(self.continueFoulShots,player)) def continueFoulShots(self, player): if self.basketball.node.exists(): self.basketball.node.delete() self.firstFoul = False player.node.handleMessage('stand',0,3.2,-3, 0) self.respawnBall(None) bs.playSound(bs.getSound('announceOne')) player.onPickUpPress() player.onPickUpRelease() bs.playSound(bs.getSound('bear1')) player.setScoreText("5 seconds to shoot") bs.gameTimer(6000, bs.Call(self.continuePlay)) def continuePlay(self): self.fouled = False if self.basketball.node.exists(): self.basketball.node.delete() self.respawnBall(not self.possession) s = self.settings for player in self.players: player.actor.connectControlsToPlayer(enableBomb=False, enableRun = s["Enable Running"], enableJump = s["Enable Jumping"], enablePunch = True) if player in self.teams[0].players: player.actor.node.handleMessage('stand',-6.5,3.2,(random.random()*5)-4.5, 0) elif player in self.teams[1].players: player.actor.node.handleMessage('stand',6.5,3.2,(random.random()*5)-4.5, 0) def foulOut(self, player): player.actor.shatter() player.actor.setScoreText("FOULED OUT") def jumpBall(self): ball = self.basketball if ball.up == True: self.basketball.heldLast.actor.setScoreText("Jump Ball") for player in self.teams[0].players: player.actor.node.handleMessage('stand',-6.5,3.2,(random.random()*5)-4.5, 0) for player in self.teams[1].players: player.actor.node.handleMessage('stand',6.5,3.2,(random.random()*5)-4.5, 0) ball.node.delete() self.respawnBall(not self.jb) self.jb = not self.jb def handleShot(self, ball): if ball.node.position[0] > -1.5 and ball.node.position[0] < 1.5: if ball.node.position[1] > 4 and ball.node.position[1] < 5: if ball.node.position[2] > -9 and ball.node.position[2] < -8: if self.isTendingGoal(ball): ball.node.delete() self.respawnBall(not self.possession) bs.playSound(bs.getSound('bearDeath')) ball.heldLast.actor.shatter() return bs.playSound(bs.getSound('bear' +str(random.randint(1,4)))) for node in self.hoop._nodes: node.delete() self.hoop = None if not self.fouled: if self.possession: pts = self.checkThreePoint(ball) self.teams[0].gameData['score'] += pts ball.heldLast.actor.setScoreText(str(pts) + " Points") self.hoop = Hoop((0,5,-8),(0,0,1)) for player in self.teams[0].players: player.actor.node.handleMessage('stand',-6.5,3.2,(random.random()*5)-4.5, 0) else: pts = self.checkThreePoint(ball) self.teams[1].gameData['score'] += pts ball.heldLast.actor.setScoreText(str(pts) + " Points") self.hoop = Hoop((0,5,-8),(1,0,0)) for player in self.teams[1].players: player.actor.node.handleMessage('stand',6.5,3.2,(random.random()*5)-4.5, 0) self.updateScore() ball.node.delete() self.respawnBall(not self.possession) else: if self.possession: self.hoop = Hoop((0,5,-8),(0,0,1)) self.teams[0].gameData['score'] += 1 else: self.hoop = Hoop((0,5,-8),(1,0,0)) self.teams[1].gameData['score'] += 1 ball.heldLast.actor.setScoreText("1 Point") self.updateScore() ball.node.delete() def checkThreePoint(self, ball): pos = ball.heldLast.actor._pos if pos[0]*pos[0] + (pos[2]+8)*(pos[2]+8) >= 36: return 3 else: return 2 def isTendingGoal(self,ball): pos = ball.heldLast.actor._pos if pos[0] > -1.5 and pos[0] < 1.5: if pos[2] > -9 and pos[2] < -8: return True return False def updateScore(self): for team in self.teams: self._scoredis.setTeamValue(team,team.gameData['score']) self.checkEnd() def checkEnd(self): for team in self.teams: i = 0 if team.gameData['score'] >= self.settings['Play To: ']: self.endGame() for player in team.players: if player.isAlive(): i = 1 if i == 0: self.endGame() def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team, team.gameData['score']) i = 0 for player in team.players: if player.isAlive(): i = 1 if i == 0: results.setTeamScore(team, 0) self.end(results=results) ================================================ FILE: mods/BuddyBunny.json ================================================ { "name": "Buddy Bunny powerup", "author": "joshville79", "category": "libraries" } ================================================ FILE: mods/BuddyBunny.py ================================================ import bsSpaz import bs import bsUtils import weakref import random class BunnyBuddyBot(bsSpaz.SpazBot): """ category: Bot Classes A speedy attacking melee bot. """ color=(1,1,1) highlight=(1.0,0.5,0.5) character = 'Easter Bunny' punchiness = 1.0 run = True bouncy = True defaultBoxingGloves = True chargeDistMin = 1.0 chargeDistMax = 9999.0 chargeSpeedMin = 1.0 chargeSpeedMax = 1.0 throwDistMin = 3 throwDistMax = 6 pointsMult = 2 def __init__(self,player): """ Instantiate a spaz-bot. """ self.color = player.color self.highlight = player.highlight bsSpaz.Spaz.__init__(self,color=self.color,highlight=self.highlight,character=self.character, sourcePlayer=None,startInvincible=False,canAcceptPowerups=False) # if you need to add custom behavior to a bot, set this to a callable which takes one # arg (the bot) and returns False if the bot's normal update should be run and True if not self.updateCallback = None self._map = weakref.ref(bs.getActivity().getMap()) self.lastPlayerAttackedBy = None # FIXME - should use empty player-refs self.lastAttackedTime = 0 self.lastAttackedType = None self.targetPointDefault = None self.heldCount = 0 self.lastPlayerHeldBy = None # FIXME - should use empty player-refs here self.targetFlag = None self._chargeSpeed = 0.5*(self.chargeSpeedMin+self.chargeSpeedMax) self._leadAmount = 0.5 self._mode = 'wait' self._chargeClosingIn = False self._lastChargeDist = 0.0 self._running = False self._lastJumpTime = 0 class BunnyBotSet(bsSpaz.BotSet): """ category: Bot Classes A container/controller for one or more bs.SpazBots. """ def __init__(self, sourcePlayer): """ Create a bot-set. """ # we spread our bots out over a few lists so we can update them in a staggered fashion self._botListCount = 5 self._botAddList = 0 self._botUpdateList = 0 self._botLists = [[] for i in range(self._botListCount)] self._spawnSound = bs.getSound('spawn') self._spawningCount = 0 self.startMovingBunnies() self.sourcePlayer = sourcePlayer def doBunny(self): self.spawnBot(BunnyBuddyBot, self.sourcePlayer.actor.node.position, 2000, self.setupBunny) def startMovingBunnies(self): self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self._bUpdate),repeat=True) def _spawnBot(self,botType,pos,onSpawnCall): spaz = botType(self.sourcePlayer) bs.playSound(self._spawnSound,position=pos) spaz.node.handleMessage("flash") spaz.node.isAreaOfInterest = 0 spaz.handleMessage(bs.StandMessage(pos,random.uniform(0,360))) self.addBot(spaz) self._spawningCount -= 1 if onSpawnCall is not None: onSpawnCall(spaz) def _bUpdate(self): # update one of our bot lists each time through.. # first off, remove dead bots from the list # (we check exists() here instead of dead.. we want to keep them around even if they're just a corpse) try: botList = self._botLists[self._botUpdateList] = [b for b in self._botLists[self._botUpdateList] if b.exists()] except Exception: bs.printException("error updating bot list: "+str(self._botLists[self._botUpdateList])) self._botUpdateList = (self._botUpdateList+1)%self._botListCount # update our list of player points for the bots to use playerPts = [] try: #if player.isAlive() and not (player is self.sourcePlayer): # playerPts.append((bs.Vector(*player.actor.node.position), # bs.Vector(*player.actor.node.velocity))) for n in bs.getNodes(): if n.getNodeType() == 'spaz': s = n.getDelegate() if isinstance(s,bsSpaz.SpazBot): if not s in self.getLivingBots(): if hasattr(s, 'sourcePlayer'): if not s.sourcePlayer is self.sourcePlayer: playerPts.append((bs.Vector(*n.position), bs.Vector(*n.velocity))) else: playerPts.append((bs.Vector(*n.position), bs.Vector(*n.velocity))) elif isinstance(s, bsSpaz.PlayerSpaz): if not (s.getPlayer() is self.sourcePlayer): playerPts.append((bs.Vector(*n.position), bs.Vector(*n.velocity))) except Exception: bs.printException('error on bot-set _update') for b in botList: b._setPlayerPts(playerPts) b._updateAI() def setupBunny(self, spaz): spaz.sourcePlayer = self.sourcePlayer spaz.color = self.sourcePlayer.color spaz.highlight = self.sourcePlayer.highlight self.setBunnyText(spaz) def setBunnyText(self, spaz): m = bs.newNode('math', owner=spaz.node, attrs={'input1': (0, 0.7, 0), 'operation': 'add'}) spaz.node.connectAttr('position', m, 'input2') spaz._bunnyText = bs.newNode('text', owner=spaz.node, attrs={'text':self.sourcePlayer.getName(), 'inWorld':True, 'shadow':1.0, 'flatness':1.0, 'color':self.sourcePlayer.color, 'scale':0.0, 'hAlign':'center'}) m.connectAttr('output', spaz._bunnyText, 'position') bs.animate(spaz._bunnyText, 'scale', {0: 0.0, 1000: 0.01}) ================================================ FILE: mods/Collector.json ================================================ { "name": "Collector", "author": "TheMikirog", "category": "minigames" } ================================================ FILE: mods/Collector.py ================================================ import bs import random import bsUtils import weakref ''' Gamemode: Collector Creator: TheMikirog Website: https://bombsquadjoyride.blogspot.com/ This is a gamemode purely made by me just to spite unchallenged modders out there that put out crap to the market. We don't want gamemodes that are just the existing ones with some novelties! Gamers deserve more! 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. After you kill an enemy that carries some of them, they drop a respective amount of Capsules they carried + two more. 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. The first player or team to get to the required ammount wins. 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. ''' # scripts specify an API-version they were written against # so the game knows to ignore out-of-date ones. def bsGetAPIVersion(): return 4 # how BombSquad asks us what games we provide def bsGetGames(): return [CollectorGame] class CollectorGame(bs.TeamGameActivity): tips = ['Making you opponent fall down the pit makes his Capsules wasted!\nTry not to kill enemies by throwing them off the cliff.', 'Don\'t be too reckless. You can lose your loot quite quickly!', 'Don\'t let the leading player score his Capsules at the Deposit Point!\nTry to catch him if you can!', 'Lucky Capsules give 4 to your inventory and they have 8% chance of spawning after kill!', 'Don\t camp in one place! Make your move first, so hopefully you get some dough!'] FLAG_NEW = 0 FLAG_UNCONTESTED = 1 FLAG_CONTESTED = 2 FLAG_HELD = 3 @classmethod def getName(cls): return 'Collector' @classmethod def getDescription(cls,sessionType): return ('Kill your opponents to steal their Capsules.\n' 'Collect them and score at the Deposit point!') def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops() if len(self.teams) > 0: self._scoreToWin = self.settings['Capsules to Collect'] * max(1,max(len(t.players) for t in self.teams)) else: self._scoreToWin = self.settings['Capsules to Collect'] self._updateScoreBoard() self._dingSound = bs.getSound('dingSmall') if isinstance(bs.getActivity().getSession(),bs.FreeForAllSession): self._flagNumber = random.randint(0,1) self._flagPos = self.getMap().getFlagPosition(self._flagNumber) else: self._flagPos = self.getMap().getFlagPosition(None) bs.gameTimer(1000,self._tick,repeat=True) self._flagState = self.FLAG_NEW self.projectFlagStand(self._flagPos) self._flag = bs.Flag(position=self._flagPos, touchable=False, color=(1,1,1)) self._flagLight = bs.newNode('light', attrs={'position':self._flagPos, 'intensity':0.2, 'heightAttenuated':False, 'radius':0.4, 'color':(0.2,0.2,0.2)}) # flag region bs.newNode('region', attrs={'position':self._flagPos, 'scale': (1.8,1.8,1.8), 'type': 'sphere', 'materials':[self._flagRegionMaterial,bs.getSharedObject('regionMaterial')]}) self._updateFlagState() @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("keepAway") @classmethod def getSettings(cls,sessionType): settings = [("Capsules to Collect",{'minValue':1,'default':10,'increment':1}), ("Capsules on Death",{'minValue':1,'maxValue':10,'default':2,'increment':1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Allow Lucky Capsules",{'default':True}), ("Epic Mode",{'default':False})] return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die since it matters here.. self.announcePlayerDeaths = True self._scoreBoard = bs.ScoreBoard() self._swipSound = bs.getSound("swip") self._tickSound = bs.getSound('tick') self._scoreToWin = self.settings['Capsules to Collect'] self._capsuleModel = bs.getModel('bomb') self._capsuleTex = bs.getTexture('bombColor') self._capsuleLuckyTex = bs.getTexture('bombStickyColor') self._collectSound = bs.getSound('powerup01') self._luckyCollectSound = bs.getSound('cashRegister2') self._capsuleMaterial = bs.Material() self._capsuleMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('playerMaterial')), actions=(("call","atConnect",self._onCapsulePlayerCollide),)) self._capsules = [] self._flagRegionMaterial = bs.Material() self._flagRegionMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('playerMaterial')), actions=(("modifyPartCollision","collide",True), ("modifyPartCollision","physical",False), ("call","atConnect",bs.Call(self._handlePlayerFlagRegionCollide,1)), ("call","atDisconnect",bs.Call(self._handlePlayerFlagRegionCollide,0)))) def getInstanceDescription(self): return ('Score ${ARG1} capsules from your enemies.',self._scoreToWin) def getInstanceScoreBoardDescription(self): return ('collect ${ARG1} capsules',self._scoreToWin) def onTeamJoin(self,team): team.gameData['capsules'] = 0 self._updateScoreBoard() def onPlayerJoin(self,player): bs.TeamGameActivity.onPlayerJoin(self,player) player.gameData['atFlag'] = 0 player.gameData['capsules'] = 0 def spawnPlayer(self,player): spaz = self.spawnPlayerSpaz(player) spaz.connectControlsToPlayer() player.gameData['capsules'] = 0 def _tick(self): self._updateFlagState() scoringTeam = None if self._scoringTeam is None else self._scoringTeam() # give holding players points for player in self.players: if player.gameData['atFlag'] > 0 and player.gameData['capsules'] > 0 and self._flagState == self.FLAG_HELD and scoringTeam.gameData['capsules'] < self._scoreToWin: player.gameData['capsules'] -= 1 self._handleCapsuleStorage((self._flagPos[0],self._flagPos[1]+1,self._flagPos[2]),player) self.scoreSet.playerScored(player,3,screenMessage=False,display=False) if scoringTeam: if scoringTeam.gameData['capsules'] < self._scoreToWin: bs.playSound(self._tickSound) scoringTeam.gameData['capsules'] = max(0,scoringTeam.gameData['capsules']+1) self._updateScoreBoard() if scoringTeam.gameData['capsules'] > 0: self._flag.setScoreText(str(self._scoreToWin-scoringTeam.gameData['capsules'])) # winner if scoringTeam.gameData['capsules'] >= self._scoreToWin: self.endGame() def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team,team.gameData['capsules']) self.end(results=results,announceDelay=0) def _updateFlagState(self): holdingTeams = set(player.getTeam() for player in self.players if player.gameData['atFlag']) prevState = self._flagState if len(holdingTeams) > 1: self._flagState = self.FLAG_CONTESTED self._scoringTeam = None self._flagLight.color = (0.6,0.6,0.1) self._flag.node.color = (1.0,1.0,0.4) elif len(holdingTeams) == 1: holdingTeam = list(holdingTeams)[0] self._flagState = self.FLAG_HELD self._scoringTeam = weakref.ref(holdingTeam) self._flagLight.color = bs.getNormalizedColor(holdingTeam.color) self._flag.node.color = holdingTeam.color else: self._flagState = self.FLAG_UNCONTESTED self._scoringTeam = None self._flagLight.color = (0.2,0.2,0.2) self._flag.node.color = (1,1,1) if self._flagState != prevState: bs.playSound(self._swipSound) def _handlePlayerFlagRegionCollide(self,colliding): flagNode,playerNode = bs.getCollisionInfo("sourceNode","opposingNode") try: player = playerNode.getDelegate().getPlayer() except Exception: return # different parts of us can collide so a single value isn't enough # also don't count it if we're dead (flying heads shouldnt be able to win the game :-) if colliding and player.isAlive(): player.gameData['atFlag'] += 1 else: player.gameData['atFlag'] = max(0,player.gameData['atFlag'] - 1) self._updateFlagState() def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team,team.gameData['capsules'],self.settings['Capsules to Collect'],countdown=True) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Scary') def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team,team.gameData['capsules'],self._scoreToWin) def _onCapsulePlayerCollide(self): if not self.hasEnded(): capsuleNode, playerNode = bs.getCollisionInfo('sourceNode','opposingNode') if capsuleNode is not None and playerNode is not None: capsule = capsuleNode.getDelegate() spaz = playerNode.getDelegate() player = spaz.getPlayer() if hasattr(spaz,'getPlayer') else None if player is not None and player.exists() and capsule is not None: if player.isAlive(): if capsuleNode.colorTexture == self._capsuleLuckyTex: player.gameData['capsules'] += 4 bsUtils.PopupText('BONUS!', color=(1,1,0), scale=1.5, position=(capsuleNode.position)).autoRetain() bs.playSound(self._luckyCollectSound,1.0,position=capsuleNode.position) bs.emitBGDynamics(position=capsuleNode.position,velocity=(0,0,0),count=int(6.4+random.random()*24),scale=1.2, spread=2.0,chunkType='spark'); bs.emitBGDynamics(position=capsuleNode.position,velocity=(0,0,0),count=int(4.0+random.random()*6),emitType='tendrils'); else: player.gameData['capsules'] += 1 bs.playSound(self._collectSound,0.6,position=capsuleNode.position) # create a flash light = bs.newNode('light', attrs={'position': capsuleNode.position, 'heightAttenuated':False, 'radius':0.1, 'color':(1,1,0)}) # Create a short text informing about your inventory self._handleCapsuleStorage(playerNode.position,player) bs.animate(light,'intensity',{0:0,100:0.5,200:0},loop=False) bs.gameTimer(200,light.delete) capsule.handleMessage(bs.DieMessage()) def _handleCapsuleStorage(self,pos,player): self.capsules = player.gameData['capsules'] if player.gameData['capsules'] > 10: player.gameData['capsules'] = 10 self.capsules = 10 bsUtils.PopupText('Full Capacity!', color=(1,0.85,0), scale=1.75, position=(pos[0],pos[1]-1,pos[2])).autoRetain() # Make a different color and size depending on the storage if self.capsules > 7: self.color = (1,0,0) self.size = 2.4 elif self.capsules > 7: self.color = (1,0.4,0.4) self.size = 2.1 elif self.capsules > 4: self.color = (1,1,0.4) self.size = 2.0 else: self.color = (1,1,1) self.size = 1.9 if self.capsules < 10: bsUtils.PopupText((str(player.gameData['capsules'])), color=self.color, scale=self.size+(0.02*self.capsules), position=(pos[0],pos[1]-1,pos[2])).autoRetain() # various high-level game events come through this method def handleMessage(self,m): # respawn dead players if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior player = m.spaz.getPlayer() self.respawnPlayer(player) # Respawn the player pt = m.spaz.node.position 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 w = 0.6 # How far from each other these capsules should spawn s = 0.005 - (player.gameData['capsules']*0.01) # How much these capsules should fly after spawning self._capsules.append(Capsule(position=(pt[0]+random.uniform(-w,w), pt[1]+0.75+random.uniform(-w,w), pt[2]), velocity=(random.uniform(-s,s), random.uniform(-s,s), random.uniform(-s,s)), lucky=False)) if random.randint(1,12) == 1 and self.settings['Allow Lucky Capsules']: w = 0.6 # How far from each other these capsules should spawn s = 0.005 # How much these capsules should fly after spawning self._capsules.append(Capsule(position=(pt[0]+random.uniform(-w,w), pt[1]+0.75+random.uniform(-w,w), pt[2]), velocity=(random.uniform(-s,s), random.uniform(-s,s), random.uniform(-s,s)), lucky=True)) player.gameData['atFlag'] = 0 else: # default handler: bs.TeamGameActivity.handleMessage(self,m) class Capsule(bs.Actor): def __init__(self, position=(0,1,0), velocity=(0,0.5,0),lucky=False): bs.Actor.__init__(self) self._luckyAppearSound = bs.getSound('ding') activity = self.getActivity() # spawn just above the provided point self._spawnPos = (position[0], position[1], position[2]) if lucky: bs.playSound(self._luckyAppearSound,1.0,self._spawnPos) self.capsule = bs.newNode("prop", attrs={'model': activity._capsuleModel, 'colorTexture': activity._capsuleLuckyTex, 'body':'crate', 'reflection':'powerup', 'modelScale':0.8, 'bodyScale':0.65, 'density':6.0, 'reflectionScale':[0.15], 'shadowSize': 0.65, 'position':self._spawnPos, 'velocity':velocity, 'materials': [bs.getSharedObject('objectMaterial'),activity._capsuleMaterial] }, delegate=self) bs.animate(self.capsule,"modelScale",{0:0, 100:0.9, 160:0.8}) self.lightCapsule = bs.newNode('light', attrs={'position':self._spawnPos, 'heightAttenuated':False, 'radius':0.5, 'color':(0.2,0.2,0)}) else: self.capsule = bs.newNode("prop", attrs={'model': activity._capsuleModel, 'colorTexture': activity._capsuleTex, 'body':'capsule', 'reflection':'soft', 'modelScale':0.6, 'bodyScale':0.3, 'density':4.0, 'reflectionScale':[0.15], 'shadowSize': 0.6, 'position':self._spawnPos, 'velocity':velocity, 'materials': [bs.getSharedObject('objectMaterial'),activity._capsuleMaterial] }, delegate=self) bs.animate(self.capsule,"modelScale",{0:0, 100:0.6, 160:0.5}) self.lightCapsule = bs.newNode('light', attrs={'position':self._spawnPos, 'heightAttenuated':False, 'radius':0.1, 'color':(0.2,1,0.2)}) self.capsule.connectAttr('position',self.lightCapsule,'position') def handleMessage(self,m): if isinstance(m,bs.DieMessage): self.capsule.delete() try: bs.animate(self.lightCapsule,'intensity',{0:1.0,50:0.0},loop=False) bs.gameTimer(50,self.lightCapsule.delete) except AttributeError: pass elif isinstance(m,bs.OutOfBoundsMessage): self.handleMessage(bs.DieMessage()) elif isinstance(m,bs.HitMessage): self.capsule.handleMessage("impulse",m.pos[0],m.pos[1],m.pos[2], m.velocity[0]/8,m.velocity[1]/8,m.velocity[2]/8, 1.0*m.magnitude,1.0*m.velocityMagnitude,m.radius,0, m.forceDirection[0],m.forceDirection[1],m.forceDirection[2]) else: bs.Actor.handleMessage(self,m) ================================================ FILE: mods/FillErUp.json ================================================ { "name": "Fill 'Er Up", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/FillErUp.py ================================================ import bs import bsSpaz import random import bsUtils import bsPowerup def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [FillErUp] class cargoBox(bs.Bomb): def __init__(self,pos): bs.Bomb.__init__(self,position=pos,bombType='tnt') #self = box self.node.maxSpeed = 0 self.node.damping = 100 #self.node.density = 10 pam = bs.Powerup.getFactory().powerupAcceptMaterial nPum = self.getActivity().noPickMat materials = getattr(self.node,'materials') if not pam in materials: setattr(self.node,'materials',materials + (pam,)) materials = getattr(self.node,'materials') if not nPum in materials: setattr(self.node,'materials',materials + (nPum,)) def handleMessage(self,m): if isinstance(m, bs.HitMessage): #We don't want crates taking damage. return True if isinstance(m, bs.PowerupMessage): #Give or take points, depending on powerup received. for player in self.getActivity().players: if player.gameData['crate'] == self: if m.powerupType == 'health': player.getTeam().gameData['score'] += 1 else: player.getTeam().gameData['score'] += self.getActivity().settings['Curse Box Points'] self.getActivity()._updateScoreBoard() self.setboxScoreText(str(player.getTeam().gameData['score']), player.color) if m.sourceNode.exists(): m.sourceNode.handleMessage(bs.PowerupAcceptMessage()) else: super(self.__class__, self).handleMessage(m) def setboxScoreText(self,t,color=(1,1,0.4),flash=False): """ Utility func to show a message momentarily over our spaz that follows him around; Handy for score updates and things. """ colorFin = bs.getSafeColor(color)[:3] if not self.node.exists(): return try: exists = self._scoreText.exists() except Exception: exists = False if not exists: startScale = 0.0 m = bs.newNode('math',owner=self.node,attrs={'input1':(0,1.4,0),'operation':'add'}) self.node.connectAttr('position',m,'input2') self._scoreText = bs.newNode('text', owner=self.node, attrs={'text':t, 'inWorld':True, 'shadow':1.0, 'flatness':1.0, 'color':colorFin, 'scale':0.02, 'hAlign':'center'}) m.connectAttr('output',self._scoreText,'position') else: self._scoreText.color = colorFin startScale = self._scoreText.scale self._scoreText.text = t if flash: combine = bs.newNode("combine",owner=self._scoreText,attrs={'size':3}) sc = 1.8 offs = 0.5 t = 300 for i in range(3): c1 = offs+sc*colorFin[i] c2 = colorFin[i] bs.animate(combine,'input'+str(i),{0.5*t:c2, 0.75*t:c1, 1.0*t:c2}) combine.connectAttr('output',self._scoreText,'color') bs.animate(self._scoreText,'scale',{0:startScale,200:0.02}) #self._scoreTextHideTimer = bs.Timer(1000,bs.WeakCall(self._hideScoreText)) def setMovingText(self, theActor, theText, color): m = bs.newNode('math', owner=theActor.node, attrs={'input1': (0, 0.7, 0), 'operation': 'add'}) theActor.node.connectAttr('position', m, 'input2') theActor._movingText = bs.newNode('text', owner=theActor.node, attrs={'text':theText, 'inWorld':True, 'shadow':1.0, 'flatness':1.0, 'color':color, 'scale':0.0, 'hAlign':'center'}) m.connectAttr('output', theActor._movingText, 'position') bs.animate(theActor._movingText, 'scale', {0: 0.0, 1000: 0.01}) class FillErUp(bs.TeamGameActivity): @classmethod def getName(cls): return 'Fill \'Er Up' @classmethod def getScoreInfo(cls): return {'scoreName':'score', 'scoreType':'points', 'noneIsWinner':False, 'lowerIsBetter':False} @classmethod def getDescription(cls,sessionType): return 'Fill your crate with boxes' @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return ['Doom Shroom','Courtyard'] @classmethod def getSettings(cls,sessionType): settings = [("Time Limit",{'choices':[('30 Seconds',30),('1 Minute',60), ('90 Seconds',90),('2 Minutes',120), ('3 Minutes',180),('5 Minutes',300)],'default':60}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Curse Box Chance (lower = more chance)",{'default':10,'minValue':5,'maxValue':15,'increment':1}), ("Curse Box Points",{'default':-2,'minValue':-10,'maxValue':-1,'increment':1}), ("Boxes Per Player",{'default':1.0,'minValue':0.5,'maxValue':3.0,'increment':0.5}), ("Epic Mode",{'default':False})] if issubclass(sessionType,bs.TeamsSession): settings.append(("Solo Mode",{'default':False})) settings.append(("Balance Total Lives",{'default':False})) return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # show messages when players die since it's meaningful here self.announcePlayerDeaths = True try: self._soloMode = settings['Solo Mode'] except Exception: self._soloMode = False self._scoreBoard = bs.ScoreBoard() self.totBoxes = [] #Create a special powerup material for our boxes that allows pickup. self.fpowerupMaterial = bs.Material() # pass a powerup-touched message to applicable stuff pam = bs.Powerup.getFactory().powerupAcceptMaterial self.fpowerupMaterial.addActions( conditions=(("theyHaveMaterial",pam)), actions=(("modifyPartCollision","collide",True), ("modifyPartCollision","physical",False), ("message","ourNode","atConnect",bsPowerup._TouchedMessage()))) # we DO wanna be picked up #self.powerupMaterial.addActions( # conditions=("theyHaveMaterial",bs.getSharedObject('pickupMaterial')), # actions=( ("modifyPartCollision","collide",False))) self.fpowerupMaterial.addActions( conditions=("theyHaveMaterial",bs.getSharedObject('footingMaterial')), actions=(("impactSound",bs.Powerup.getFactory().dropSound,0.5,0.1))) #Create a material to prevent TNT box pickup self.noPickMat = bs.Material() self.noPickMat.addActions( conditions=("theyHaveMaterial",bs.getSharedObject('pickupMaterial')), actions=( ("modifyPartCollision","collide",False))) def getInstanceDescription(self): return 'Steal all the health boxes for yourself' if isinstance(self.getSession(),bs.TeamsSession) else 'Fill your crate with boxes' def getInstanceScoreBoardDescription(self): return 'Steal all the health boxes for yourself' if isinstance(self.getSession(),bs.TeamsSession) else 'Fill your crate with boxes' def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onTeamJoin(self,team): team.gameData['survivalSeconds'] = None team.gameData['spawnOrder'] = [] team.gameData['score'] = 0 def onPlayerJoin(self, player): #player.gameData['lives'] = 1 player.getTeam().gameData['score'] = 0 player.gameData['home'] = None player.gameData['crate'] = None self._updateScoreBoard() if self._soloMode: #player.gameData['icons'] = [] player.getTeam().gameData['spawnOrder'].append(player) self._updateSoloMode() else: # create our icon and spawn #player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)] self.spawnPlayer(player) # dont waste time doing this until begin if self.hasBegun(): pass#self._updateIcons() def _updateSoloMode(self): # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) break def _getSpawnPoint(self,player): # in solo-mode, if there's an existing live player on the map, spawn at whichever # spot is farthest from them (keeps the action spread out) if self._soloMode: livingPlayer = None for team in self.teams: for player in team.players: if player.isAlive(): p = player.actor.node.position livingPlayer = player livingPlayerPos = p break if livingPlayer: playerPos = bs.Vector(*livingPlayerPos) points = [] for team in self.teams: startPos = bs.Vector(*self.getMap().getStartPosition(team.getID())) points.append([(startPos-playerPos).length(),startPos]) points.sort() return points[-1][1] else: return None else: return None def spawnPlayer(self,player): """ Spawn *something* for the provided bs.Player. The default implementation simply calls spawnPlayerSpaz(). """ #Overloaded for this game to respawn at home instead of random FFA spots if not player.exists(): bs.printError('spawnPlayer() called for nonexistant player') return if player.gameData['home'] is None: pos = self.getMap().getFFAStartPosition(self.players) if player.gameData['crate'] is None: box = cargoBox(pos) box.setMovingText(box,player.getName(),player.color) player.gameData['crate'] = box position = [pos[0],pos[1]+1.0,pos[2]] player.gameData['home'] = position else: position = player.gameData['home'] spaz = self.spawnPlayerSpaz(player, position) #Need to prevent accepting powerups: pam = bs.Powerup.getFactory().powerupAcceptMaterial for attr in ['materials','rollerMaterials','extrasMaterials']: materials = getattr(spaz.node,attr) if pam in materials: setattr(spaz.node,attr,tuple(m for m in materials if m != pam)) return spaz def _printLives(self,player): if not player.exists() or not player.isAlive(): return try: pos = player.actor.node.position except Exception,e: print 'EXC getting player pos in bsElim',e return bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1), offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain() def onPlayerLeave(self,player): bs.TeamGameActivity.onPlayerLeave(self,player) #player.gameData['icons'] = None player.gameData['score'] = 0 if player.gameData['crate'].exists(): player.gameData['crate'].handleMessage(bs.DieMessage(immediate=True)) # remove us from spawn-order if self._soloMode: if player in player.getTeam().gameData['spawnOrder']: player.getTeam().gameData['spawnOrder'].remove(player) def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) #self.setupStandardPowerupDrops() if self._soloMode: self._vsText = bs.NodeActor(bs.newNode("text", attrs={'position':(0,105), 'hAttach':"center", 'hAlign':'center', 'maxWidth':200, 'shadow':0.5, 'vrDepth':390, 'scale':0.6, 'vAttach':"bottom", 'color':(0.8,0.8,0.3,1.0), 'text':bs.Lstr(resource='vsText')})) # if balance-team-lives is on, add lives to the smaller team until total lives match if (isinstance(self.getSession(),bs.TeamsSession) and self.settings['Balance Total Lives'] and len(self.teams[0].players) > 0 and len(self.teams[1].players) > 0): if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]): lesserTeam = self.teams[0] greaterTeam = self.teams[1] else: lesserTeam = self.teams[1] greaterTeam = self.teams[0] addIndex = 0 while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam): lesserTeam.players[addIndex].gameData['lives'] += 1 addIndex = (addIndex + 1) % len(lesserTeam.players) #self._updateIcons() # we could check game-over conditions at explicit trigger points, # but lets just do the simple thing and poll it... bs.gameTimer(1000, self._update, repeat=True) def _getTotalTeamLives(self,team): return sum(player.gameData['lives'] for player in team.players) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() self.respawnPlayer(player) # in solo, put ourself at the back of the spawn order if self._soloMode: player.getTeam().gameData['spawnOrder'].remove(player) player.getTeam().gameData['spawnOrder'].append(player) else: bs.TeamGameActivity.handleMessage(self, m) def _update(self): if self._soloMode: # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) #self._updateIcons() break # if we're down to 1 or fewer living teams, start a timer to end the game # (allows the dust to settle and draws to occur if deaths are close enough) self.boxSpawn() def boxSpawn(self): Plyrs = 0 for team in self.teams: for player in team.players: Plyrs += 1 maxBoxes = int(Plyrs * self.settings["Boxes Per Player"]) if maxBoxes > 16: maxBoxes = 16 elif maxBoxes < 1: maxBoxes = 1 for box in self.totBoxes: if not box.exists(): self.totBoxes.remove(box) while len(self.totBoxes) < maxBoxes: #print([Plyrs, self.boxMult,len(self.totBoxes), maxBoxes]) if random.randint(1,self.settings["Curse Box Chance (lower = more chance)"]) == 1: type = 'curse' else: type = 'health' box = bsPowerup.Powerup(position=self.getRandomPowerupPoint(), powerupType=type,expire=False).autoRetain() #we have to remove the default powerup material because it doesn't allow for pickups. #Then we add our own powerup material. pm = box.getFactory().powerupMaterial materials = getattr(box.node,'materials') if pm in materials: setattr(box.node,'materials',tuple(m for m in materials if m != pm)) materials = getattr(box.node,'materials') if not self.fpowerupMaterial in materials: setattr(box.node,'materials',materials + (self.fpowerupMaterial,)) self.totBoxes.append(box) #self.boxMult -= self.settings["Box Reduction Rate"] def getRandomPowerupPoint(self): myMap = self.getMap().getName() #print(myMap) if myMap == 'Doom Shroom': while True: x = random.uniform(-1.0,1.0) y = random.uniform(-1.0,1.0) if x*x+y*y < 1.0: break return ((5.0*x,4.0,-3.5+3.0*y)) elif myMap == 'Courtyard': x = random.uniform(-3.3,3.3) y = random.uniform(-3.9,-0.2) return ((x, 4.0, y)) else: x = random.uniform(-5.0,5.0) y = random.uniform(-6.0,0.0) return ((x, 8.0, y)) def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)] def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team, team.gameData['score']) def endGame(self): if self.hasEnded(): return results = bs.TeamGameResults() self._vsText = None # kill our 'vs' if its there for team in self.teams: results.setTeamScore(team, team.gameData['score']) self.end(results=results) ================================================ FILE: mods/FlagDay.py ================================================ #FlagDay import bs import random import bsUtils import math # http://www.froemling.net/docs/bombsquad-python-api #if you really want in-depth explanations of specific terms, go here ^ # fixing random generation of players in setupNextRound class FlagBearer(bs.PlayerSpaz): def handleMessage(self, m): bs.PlayerSpaz.handleMessage(self, m) if isinstance(m, bs.PowerupMessage): if self.getActivity().lastPrize == 'curse': self.getPlayer().getTeam().gameData['score'] += 25 self.getActivity().updateScore() elif self.getActivity().lastPrize == 'landmines': self.getPlayer().getTeam().gameData['score'] += 15 self.getActivity().updateScore() self.connectControlsToPlayer() elif self.getActivity().lastPrize == 'climb': self.getPlayer().getTeam().gameData['score'] += 50 self.getActivity().updateScore() #This gives the API version to the game to make sure that we are using the right vocabulary def bsGetAPIVersion(): return 4 #This tells the game what kind of program this is def bsGetGames(): return [FlagDay] #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 def bsGetLevels(): return [bs.Level('FlagDay45986', displayName='${GAME}', gameType=FlagDay, settings={}, previewTexName='courtyardPreview')] #this is the class that will actually be saved to the game as a mini-game class FlagDay(bs.TeamGameActivity): def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) #gives it a name @classmethod def getName(cls): return 'Flag Day' #Gives it how things are scored @classmethod def getScoreInfo(cls): return {'scoreType':'points'} #Gives a description of the game @classmethod def getDescription(cls,sessionType): return 'Pick up flags to receive a prize.\nBut beware...' #Gives which maps are supported, in this case only courtyard though you could probably try it with others, too @classmethod def getSupportedMaps(cls,sessionType): return ['Courtyard'] #Tells the game what kinds of seesions are supported by this mini-game @classmethod def supportsSessionType(cls,sessionType): return True if issubclass(sessionType,bs.FreeForAllSession) or issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.CoopSession) else False #Tells the game what to do on the transition in def onTransitionIn(self): #Sets the music to "To the Death" bs.TeamGameActivity.onTransitionIn(self,music='ToTheDeath') def onPlayerJoin(self, player): player.getTeam().gameData['score'] = 0 if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText', subs=[('${PLAYER}', player.getName(full=True))]), color=(0, 1, 0)) def onPlayerLeave(self, player): if player is self.currentPlayer: self.setupNextRound() self.checkEnd() bs.TeamGameActivity.onPlayerLeave(self,player) self.queueLine.remove(player) def onBegin(self): self.bombSurvivor = None self.light = None self.set = False #Do normal stuff: calls to the main class to operate everything that usually would be done bs.TeamGameActivity.onBegin(self) self.b = [] self.queueLine = [] self.playerIndex = 0 for player in self.players: player.gameData['dead'] = False if player.actor is not None: player.actor.handleMessage(bs.DieMessage()) player.actor.node.delete() self.queueLine.append(player) self.spawnPlayerSpaz(self.queueLine[self.playerIndex%len(self.queueLine)],(0,3,-2)) self.lastPrize = 'none' self.currentPlayer = self.queueLine[0] #Declare a set of bots (enemies) that we will use later self._bots = bs.BotSet() #make another scoreboard? IDK why I did this, probably to make it easier to refer to in the future self._scoredis = bs.ScoreBoard() #for each team in the game's directory, give them a score of zero for team in self.teams: team.gameData['score'] = 0 #Now we go ahead and put that on the scoreboard for player in self.queueLine: self._scoredis.setTeamValue(player.getTeam(),player.getTeam().gameData['score']) self.resetFlags() #This handles all the messages that the game throws at us def handleMessage(self,m): #If it's a flag picked up... if isinstance(m,bs.FlagPickedUpMessage): #Get the last player to hold that flag m.flag._lastPlayerToHold = m.node.getDelegate().getPlayer() #Get the last actor to hold that flag (If you are a player, then your body is the actor, think of it like that) self._player = m.node.getDelegate() #The person to last hold a flag gets the prize, not the person to hold that flag, note. self._prizeRecipient = m.node.getDelegate().getPlayer() #Call a method to kill the flags self.killFlags() self.givePrize(random.randint(1,8)) self.currentPlayer = self._prizeRecipient #If a player died... if isinstance(m,bs.PlayerSpazDeathMessage): #give them a nice farewell if bs.getGameTime() < 500: return if m.how == 'game': return guy = m.spaz.getPlayer() bs.screenMessage(str(guy.getName()) + " died!",color=guy.color) guy.gameData['dead'] = True if guy is self.currentPlayer: self.setupNextRound() #check to see if we can end the game self.checkEnd() #If a bot died... if isinstance(m,bs.SpazBotDeathMessage): #find out which team the last person to hold a flag was on team = self._prizeRecipient.getTeam() #give them their points team.gameData['score'] += self._badGuyCost #update the scores for team in self.teams: self._scoredis.setTeamValue(team,team.gameData['score']) bs.gameTimer(300,self.checkBots) def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None): name = player.getName() color = player.color highlight = player.highlight lightColor = bsUtils.getNormalizedColor(color) displayColor = bs.getSafeColor(color,targetIntensity=0.75) spaz = FlagBearer(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360))) t = bs.getGameTime() bs.playSound(self._spawnSound,1,position=spaz.node.position) light = bs.newNode('light',attrs={'color':lightColor}) spaz.node.connectAttr('position',light,'position') bsUtils.animate(light,'intensity',{0:0,250:1,500:0}) bs.gameTimer(500,light.delete) return spaz #a method to remake the flags def resetFlags(self): #remake the flags self._flag1 = bs.Flag(position=(0,3,1),touchable=True,color=(0,0,1)) self._flag2 = bs.Flag(position=(0,3,-5),touchable=True,color=(1,0,0)) self._flag3 = bs.Flag(position=(3,3,-2),touchable=True,color=(0,1,0)) self._flag4 = bs.Flag(position=(-3,3,-2),touchable=True,color=(1,1,1)) self._flag5 = bs.Flag(position=(1.8,3,.2),touchable=True,color=(0,1,1)) self._flag6 = bs.Flag(position=(-1.8,3,.2),touchable=True,color=(1,0,1)) self._flag7 = bs.Flag(position=(1.8,3,-3.8),touchable=True,color=(1,1,0)) self._flag8 = bs.Flag(position=(-1.8,3,-3.8),touchable=True,color=(0,0,0)) #a method to kill the flags def killFlags(self): #destroy all the flags by erasing all references to them, indicated by None similar to null self._flag1 = None self._flag2 = None self._flag3 = None self._flag4 = None self._flag5 = None # 132, 210 ,12 self._flag6 = None self._flag7 = None self._flag8 = None def setupNextRound(self): if self.light is not None: self.light.delete() for bomb in self.b: bomb.handleMessage(bs.DieMessage()) self.killFlags() self._bots.clear() self.resetFlags() self.currentPlayer.actor.handleMessage(bs.DieMessage(how='game')) self.currentPlayer.actor.node.delete() c = 0 self.playerIndex += 1 self.playerIndex %= len(self.queueLine) if len(self.queueLine) > 0: while self.queueLine[self.playerIndex].gameData['dead']: if c > len(self.queueLine): return self.playerIndex += 1 self.playerIndex %= len(self.queueLine) c += 1 self.spawnPlayerSpaz(self.queueLine[self.playerIndex],(0,3,-2)) self.currentPlayer = self.queueLine[self.playerIndex] self.lastPrize = 'none' #a method to give the prize recipient a prize depending on what flag he took (not really). def givePrize(self, prize): if prize == 1: #Curse him aka make him blow up in 5 seconds #give them a nice message bs.screenMessage("You were", color=(1,0,0)) bs.screenMessage("CURSED", color=(.1,.1,.1)) self.makeHealthBox((0,0,0)) self.lastPrize = 'curse' self._prizeRecipient.actor.curse() bs.gameTimer(5500,self.setupNextRound) if prize == 2: self.setupROF() bs.screenMessage("RUN", color=(1,.2,.1)) self.lastPrize = 'ringoffire' if prize == 3: self.lastPrize = 'climb' self.light =bs.newNode('locator',attrs={'shape':'circle','position':(0,3,-9), 'color':(1,1,1),'opacity':1, 'drawBeauty':True,'additive':True}) bs.screenMessage("Climb to the top",color=(.5,.5,.5)) bs.gameTimer(3000, bs.Call(self.makeHealthBox,(0,6,-9))) bs.gameTimer(10000, self.setupNextRound) if prize == 4: self.lastPrize = 'landmines' self.makeHealthBox((6,5,-2)) self.makeLandMines() self._prizeRecipient.actor.node.getDelegate().connectControlsToPlayer(enableBomb=False) self._prizeRecipient.actor.node.handleMessage(bs.StandMessage(position=(-6,3,-2))) bs.gameTimer(7000,self.setupNextRound) if prize == 5: #Make it rain bombs self.bombSurvivor = self._prizeRecipient bs.screenMessage("BOMB RAIN!", color=(1,.5,.16)) #Set positions for the bombs to drop for bzz in range(-5,6): for azz in range(-5,2): #for each position make a bomb drop there self.makeBomb(bzz,azz) bs.gameTimer(3300,self.givePoints) self.lastPrize = 'bombrain' if prize == 6: self.setupBR() self.bombSurvivor = self._prizeRecipient bs.gameTimer(7000,self.givePoints) self.lastPrize = 'bombroad' if prize == 7: #makes killing a bad guy worth ten points self._badGuyCost = 2 bs.screenMessage("Lame Guys", color=(1,.5,.16)) #makes a set of nine positions for a in range(-1,2): for b in range(-3,0): #and spawns one in each position self._bots.spawnBot(bs.ToughGuyBotLame,pos=(a,2.5,b)) #and we give our player boxing gloves and a shield self._player.equipBoxingGloves() self._player.equipShields() self.lastPrize = 'lameguys' if prize == 8: bs.screenMessage("!JACKPOT!", color=(1,0,0)) bs.screenMessage("!JACKPOT!", color=(0,1,0)) bs.screenMessage("!JACKPOT!", color=(0,0,1)) team = self._prizeRecipient.getTeam() #GIVE THEM A WHOPPING 50 POINTS!!! team.gameData['score'] += 50 # and update the scores self.updateScore() self.lastPrize = 'jackpot' bs.gameTimer(2000,self.setupNextRound) def updateScore(self): for player in self.queueLine: self._scoredis.setTeamValue(player.getTeam(),player.getTeam().gameData['score']) def checkBots(self): if not self._bots.haveLivingBots(): self.setupNextRound() def makeLandMines(self): self.b = [] for i in range(-11,7): self.b.append(bs.Bomb(position=(0, 6, i/2.0), bombType='landMine', blastRadius=2.0)) self.b[i+10].arm() def givePoints(self): if self.bombSurvivor is not None and self.bombSurvivor.isAlive(): self.bombSurvivor.getTeam().gameData['score'] += 20 self.updateScore() def makeHealthBox(self, position=(0,3,0)): if position == (0,3,0): position = (random.randint(-6,6),6,random.randint(-6,4)) elif position == (0,0,0): position = random.choice(((-7,6,-5),(7,6,-5),(-7,6,1),(7,6,1))) self.healthBox = bs.Powerup(position=position,powerupType='health').autoRetain() #called in prize #5 def makeBomb(self,xpos,zpos): #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 b=bs.Bomb(position=(xpos, 12, zpos)).autoRetain() def setupBR(self): self.makeBombRow(6) self._prizeRecipient.actor.handleMessage(bs.StandMessage(position=(6,3,-2))) def makeBombRow(self, num): if num == 0: bs.gameTimer(1000, self.setupNextRound) return for i in range(-11,7): self.b.append(bs.Bomb(position=(-3, 3, i/2.0), velocity=(12,0,0),bombType='normal', blastRadius=1.2)) if self._prizeRecipient.isAlive(): bs.gameTimer(1000,bs.Call(self.makeBombRow,num-1)) else: self.setupNextRound() def setupROF(self): self.makeBlastRing(10) self._prizeRecipient.actor.handleMessage(bs.StandMessage(position=(0,3,-2))) def makeBlastRing(self,length): if length == 0: self.setupNextRound() self._prizeRecipient.getTeam().gameData['score'] += 50 self.updateScore() return for angle in range(0,360,45): angle += random.randint(0,45) angle %= 360 x = length * math.cos(math.radians(angle)) z = length * math.sin(math.radians(angle)) blast = bs.Blast(position=(x,2.2,z-2),blastRadius=3.5) if self._prizeRecipient.isAlive(): bs.gameTimer(750,bs.Call(self.makeBlastRing,length-1)) else: self.setupNextRound() #checks to see if we should end the game def checkEnd(self): for player in self.queueLine: if not player.gameData['dead']: return self.endGame() #called when ready to end the game def endGame(self): if self.set == True: return self.set = True results = bs.TeamGameResults() #Set the results for the game to display at the end of the game for team in self.teams: results.setTeamScore(team, team.gameData['score']) self.end(results=results) ================================================ FILE: mods/GravityFalls.py ================================================ import bs import bsUtils import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [GravityFalls] class FlyMessage(object): pass class Icon(bs.Actor): def __init__(self,player,position,scale,showLives=True,showDeath=True, nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0): bs.Actor.__init__(self) self._player = player self._showLives = showLives self._showDeath = showDeath self._nameScale = nameScale self._outlineTex = bs.getTexture('characterIconMask') icon = player.getIcon() self.node = bs.newNode('image', owner=self, attrs={'texture':icon['texture'], 'tintTexture':icon['tintTexture'], 'tintColor':icon['tintColor'], 'vrDepth':400, 'tint2Color':icon['tint2Color'], 'maskTexture':self._outlineTex, 'opacity':1.0, 'absoluteScale':True, 'attach':'bottomCenter'}) self._nameText = bs.newNode('text', owner=self.node, attrs={'text':bs.Lstr(value=player.getName()), 'color':bs.getSafeColor(player.getTeam().color), 'hAlign':'center', 'vAlign':'center', 'vrDepth':410, 'maxWidth':nameMaxWidth, 'shadow':shadow, 'flatness':flatness, 'hAttach':'center', 'vAttach':'bottom'}) if self._showLives: self._livesText = bs.newNode('text', owner=self.node, attrs={'text':'x0', 'color':(1,1,0.5), 'hAlign':'left', 'vrDepth':430, 'shadow':1.0, 'flatness':1.0, 'hAttach':'center', 'vAttach':'bottom'}) self.setPositionAndScale(position,scale) def setPositionAndScale(self,position,scale): self.node.position = position self.node.scale = [70.0*scale] self._nameText.position = (position[0],position[1]+scale*52.0) self._nameText.scale = 1.0*scale*self._nameScale if self._showLives: self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0) self._livesText.scale = 1.0*scale def updateForLives(self): if self._player.exists(): lives = self._player.gameData['lives'] else: lives = 0 if self._showLives: if lives > 0: self._livesText.text = 'x'+str(lives-1) else: self._livesText.text = '' if lives == 0: self._nameText.opacity = 0.2 self.node.color = (0.7,0.3,0.3) self.node.opacity = 0.2 def handlePlayerSpawned(self): if not self.node.exists(): return self.node.opacity = 1.0 self.updateForLives() def handlePlayerDied(self): if not self.node.exists(): return if self._showDeath: bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0, 300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2}) lives = self._player.gameData['lives'] if lives == 0: bs.gameTimer(600,self.updateForLives) class AntiGravityPlayerSpaz(bs.PlayerSpaz): def handleMessage(self,m): if isinstance(m, FlyMessage): try: self.node.handleMessage("impulse",self.node.position[0],self.node.position[1]+.5,self.node.position[2],0,5,0, 3,10,0,0, 0,5,0) except Exception: pass else: bs.PlayerSpaz.handleMessage(self,m) class GravityFalls(bs.TeamGameActivity): @classmethod def getName(cls): return 'Gravity Falls' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'seconds', 'noneIsWinner':True} @classmethod def getDescription(cls,sessionType): return 'Last remaining alive wins.' @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): settings = [("Lives Per Player",{'default':1,'minValue':1,'maxValue':10,'increment':1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Epic Mode",{'default':False})] if issubclass(sessionType,bs.TeamsSession): settings.append(("Solo Mode",{'default':False})) settings.append(("Balance Total Lives",{'default':False})) return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) self.announcePlayerDeaths = True try: self._soloMode = settings['Solo Mode'] except Exception: self._soloMode = False self._scoreBoard = bs.ScoreBoard() def getInstanceDescription(self): return 'Last team standing wins.' if isinstance(self.getSession(),bs.TeamsSession) else 'Last one standing wins.' def getInstanceScoreBoardDescription(self): return 'last team standing wins' if isinstance(self.getSession(),bs.TeamsSession) else 'last one standing wins' def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onTeamJoin(self,team): team.gameData['survivalSeconds'] = None team.gameData['spawnOrder'] = [] def _updateSoloMode(self): # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) break def _updateIcons(self): # in free-for-all mode, everyone is just lined up along the bottom if isinstance(self.getSession(),bs.FreeForAllSession): count = len(self.teams) xOffs = 85 x = xOffs*(count-1) * -0.5 for i,team in enumerate(self.teams): if len(team.players) == 1: player = team.players[0] for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs # in teams mode we split up teams else: if self._soloMode: # first off, clear out all icons for player in self.players: player.gameData['icons'] = [] # now for each team, cycle through our available players adding icons for team in self.teams: if team.getID() == 0: x = -60 xOffs = -78 else: x = 60 xOffs = 78 isFirst = True testLives = 1 while True: playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives] if len(playersWithLives) == 0: break for player in playersWithLives: player.gameData['icons'].append(Icon(player, position=(x,(40 if isFirst else 25)), scale=1.0 if isFirst else 0.5, nameMaxWidth=130 if isFirst else 75, nameScale=0.8 if isFirst else 1.0, flatness=0.0 if isFirst else 1.0, shadow=0.5 if isFirst else 1.0, showDeath=True if isFirst else False, showLives=False)) x += xOffs * (0.8 if isFirst else 0.56) isFirst = False testLives += 1 # non-solo mode else: for team in self.teams: if team.getID() == 0: x = -50 xOffs = -85 else: x = 50 xOffs = 85 for player in team.players: for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs def _getSpawnPoint(self,player): # in solo-mode, if there's an existing live player on the map, spawn at whichever # spot is farthest from them (keeps the action spread out) if self._soloMode: livingPlayer = None for team in self.teams: for player in team.players: if player.isAlive(): p = player.actor.node.position livingPlayer = player livingPlayerPos = p break if livingPlayer: playerPos = bs.Vector(*livingPlayerPos) points = [] for team in self.teams: startPos = bs.Vector(*self.getMap().getStartPosition(team.getID())) points.append([(startPos-playerPos).length(),startPos]) points.sort() return points[-1][1] else: return None else: return None def spawnPlayer(self,player): self.spawnPlayerSpaz(player,(0,5,0)) if not self._soloMode: bs.gameTimer(300,bs.Call(self._printLives,player)) # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerSpawned() def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None): name = player.getName() color = player.color highlight = player.highlight lightColor = bsUtils.getNormalizedColor(color) displayColor = bs.getSafeColor(color,targetIntensity=0.75) spaz = AntiGravityPlayerSpaz(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360))) t = bs.getGameTime() bs.playSound(self._spawnSound,1,position=spaz.node.position) light = bs.newNode('light',attrs={'color':lightColor}) spaz.node.connectAttr('position',light,'position') bsUtils.animate(light,'intensity',{0:0,250:1,500:0}) bs.gameTimer(500,light.delete) bs.gameTimer(1000,bs.Call(self.raisePlayer, player)) return spaz def _printLives(self,player): if not player.exists() or not player.isAlive(): return try: pos = player.actor.node.position except Exception,e: print 'EXC getting player pos in bsElim',e return bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1), offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain() def onPlayerJoin(self, player): # no longer allowing mid-game joiners here... too easy to exploit if self.hasBegun(): player.gameData['lives'] = 0 player.gameData['icons'] = [] # make sure our team has survival seconds set if they're all dead # (otherwise blocked new ffa players would be considered 'still alive' in score tallying) if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None: player.getTeam().gameData['survivalSeconds'] = 0 bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) return player.gameData['lives'] = self.settings['Lives Per Player'] if self._soloMode: player.gameData['icons'] = [] player.getTeam().gameData['spawnOrder'].append(player) self._updateSoloMode() else: # create our icon and spawn player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)] if player.gameData['lives'] > 0: self.spawnPlayer(player) # dont waste time doing this until begin if self.hasBegun(): self._updateIcons() def onPlayerLeave(self,player): bs.TeamGameActivity.onPlayerLeave(self,player) player.gameData['icons'] = None # remove us from spawn-order if self._soloMode: if player in player.getTeam().gameData['spawnOrder']: player.getTeam().gameData['spawnOrder'].remove(player) # update icons in a moment since our team will be gone from the list then bs.gameTimer(0, self._updateIcons) def raisePlayer(self, player): player.actor.handleMessage(FlyMessage()) if player.isAlive(): bs.gameTimer(50,bs.Call(self.raisePlayer,player)) """spaz.node.handleMessage("impulse",spaz.node.position[0],spaz.node.position[1],spaz.node.position[2], 0,8,0, 2,6,0,0,0,8,0)""" def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops() if self._soloMode: self._vsText = bs.NodeActor(bs.newNode("text", attrs={'position':(0,105), 'hAttach':"center", 'hAlign':'center', 'maxWidth':200, 'shadow':0.5, 'vrDepth':390, 'scale':0.6, 'vAttach':"bottom", 'color':(0.8,0.8,0.3,1.0), 'text':bs.Lstr(resource='vsText')})) # if balance-team-lives is on, add lives to the smaller team until total lives match if (isinstance(self.getSession(),bs.TeamsSession) and self.settings['Balance Total Lives'] and len(self.teams[0].players) > 0 and len(self.teams[1].players) > 0): if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]): lesserTeam = self.teams[0] greaterTeam = self.teams[1] else: lesserTeam = self.teams[1] greaterTeam = self.teams[0] addIndex = 0 while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam): lesserTeam.players[addIndex].gameData['lives'] += 1 addIndex = (addIndex + 1) % len(lesserTeam.players) self._updateIcons() # we could check game-over conditions at explicit trigger points, # but lets just do the simple thing and poll it... bs.gameTimer(1000, self._update, repeat=True) def _getTotalTeamLives(self,team): return sum(player.gameData['lives'] for player in team.players) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() player.gameData['lives'] -= 1 if player.gameData['lives'] < 0: bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode)) player.gameData['lives'] = 0 # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerDied() # play big death sound on our last death or for every one in solo mode if self._soloMode or player.gameData['lives'] == 0: bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound) # if we hit zero lives, we're dead (and our team might be too) if player.gameData['lives'] == 0: # if the whole team is now dead, mark their survival time.. #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players): if self._getTotalTeamLives(player.getTeam()) == 0: player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000 else: # otherwise, in regular mode, respawn.. if not self._soloMode: self.respawnPlayer(player) # in solo, put ourself at the back of the spawn order if self._soloMode: player.getTeam().gameData['spawnOrder'].remove(player) player.getTeam().gameData['spawnOrder'].append(player) def _update(self): if self._soloMode: # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) self._updateIcons() break # if we're down to 1 or fewer living teams, start a timer to end the game # (allows the dust to settle and draws to occur if deaths are close enough) if len(self._getLivingTeams()) < 2: self._roundEndTimer = bs.Timer(500,self.endGame) def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)] def endGame(self): if self.hasEnded(): return results = bs.TeamGameResults() self._vsText = None # kill our 'vs' if its there for team in self.teams: results.setTeamScore(team, team.gameData['survivalSeconds']) self.end(results=results) ================================================ FILE: mods/Greed.json ================================================ { "name": "Greed", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/Greed.py ================================================ import bs import random import bsUtils import bsPowerup from bsSpaz import PlayerSpazHurtMessage def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [Greed] class PlayerSpaz_Greed(bs.PlayerSpaz): def handleMessage(self, m): #print(m) #First we copy handling from PlayerSpaz, then almost the whole HitMessage handling from bsSpaz, but just to calculate the "damage". if isinstance(m,bs.HitMessage): #Damage is calculated in the bsSpaz message handling, which happens #after the PlayerSpaz message handling. Here we have to pull the code #from PlayerSpaz in order to give kill credit (and avoid suicide), then the code from Spaz. if m.sourcePlayer is not None and m.sourcePlayer.exists(): self.lastPlayerAttackedBy = m.sourcePlayer self.lastAttackedTime = bs.getGameTime() self.lastAttackedType = (m.hitType,m.hitSubType) #self.__superHandleMessage(m) # augment standard behavior #super is not needed anymore due to being copied here below activity = self._activity() if activity is not None: activity.handleMessage(PlayerSpazHurtMessage(self)) #End of code from bsPlayerSpaz #Now the code from bsSpaz boxDamage = 0 if not self.node.exists(): return if self.node.invincible == True: bs.playSound(self.getFactory().blockSound,1.0,position=self.node.position) return True # if we were recently hit, don't count this as another # (so punch flurries and bomb pileups essentially count as 1 hit) gameTime = bs.getGameTime() if self._lastHitTime is None or gameTime-self._lastHitTime > 1000: self._numTimesHit += 1 self._lastHitTime = gameTime mag = m.magnitude * self._impactScale velocityMag = m.velocityMagnitude * self._impactScale damageScale = 0.22 # if they've got a shield, deliver it to that instead.. if self.shield is not None: if m.flatDamage: damage = m.flatDamage * self._impactScale else: # hit our spaz with an impulse but tell it to only return theoretical damage; not apply the impulse.. self.node.handleMessage("impulse",m.pos[0],m.pos[1],m.pos[2], m.velocity[0],m.velocity[1],m.velocity[2], mag,velocityMag,m.radius,1,m.forceDirection[0],m.forceDirection[1],m.forceDirection[2]) damage = damageScale * self.node.damage self.shieldHitPoints -= damage self.shield.hurt = 1.0 - float(self.shieldHitPoints)/self.shieldHitPointsMax # its a cleaner event if a hit just kills the shield without damaging the player.. # however, massive damage events should still be able to damage the player.. # this hopefully gives us a happy medium. # maxSpillover = 500 maxSpillover = self.getFactory().maxShieldSpilloverDamage if self.shieldHitPoints <= 0: # fixme - transition out perhaps?.. self.shield.delete() self.shield = None bs.playSound(self.getFactory().shieldDownSound,1.0,position=self.node.position) # emit some cool lookin sparks when the shield dies t = self.node.position bs.emitBGDynamics(position=(t[0],t[1]+0.9,t[2]), velocity=self.node.velocity, count=random.randrange(20,30),scale=1.0,spread=0.6,chunkType='spark') else: bs.playSound(self.getFactory().shieldHitSound,0.5,position=self.node.position) # emit some cool lookin sparks on shield hit bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*1.0, m.forceDirection[1]*1.0, m.forceDirection[2]*1.0), count=min(30,5+int(damage*0.005)),scale=0.5,spread=0.3,chunkType='spark') # if they passed our spillover threshold, pass damage along to spaz if self.shieldHitPoints <= -maxSpillover: leftoverDamage = -maxSpillover-self.shieldHitPoints shieldLeftoverRatio = leftoverDamage/damage # scale down the magnitudes applied to spaz accordingly.. mag *= shieldLeftoverRatio velocityMag *= shieldLeftoverRatio else: return True # good job shield! else: shieldLeftoverRatio = 1.0 if m.flatDamage: damage = m.flatDamage * self._impactScale * shieldLeftoverRatio else: # hit it with an impulse and get the resulting damage self.node.handleMessage("impulse",m.pos[0],m.pos[1],m.pos[2], m.velocity[0],m.velocity[1],m.velocity[2], mag,velocityMag,m.radius,0,m.forceDirection[0],m.forceDirection[1],m.forceDirection[2]) damage = damageScale * self.node.damage self.node.handleMessage("hurtSound") # play punch impact sound based on damage if it was a punch if m.hitType == 'punch': self.onPunched(damage) # if damage was significant, lets show it if damage > 350: bsUtils.showDamageCount('-'+str(int(damage/10))+"%",m.pos,m.forceDirection) # lets always add in a super-punch sound with boxing gloves just to differentiate them if m.hitSubType == 'superPunch': bs.playSound(self.getFactory().punchSoundStronger,1.0, position=self.node.position) if damage > 500: sounds = self.getFactory().punchSoundsStrong sound = sounds[random.randrange(len(sounds))] else: sound = self.getFactory().punchSound bs.playSound(sound,1.0,position=self.node.position) # throw up some chunks bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*0.5, m.forceDirection[1]*0.5, m.forceDirection[2]*0.5), count=min(10,1+int(damage*0.0025)),scale=0.3,spread=0.03); bs.emitBGDynamics(position=m.pos, chunkType='sweat', velocity=(m.forceDirection[0]*1.3, m.forceDirection[1]*1.3+5.0, m.forceDirection[2]*1.3), count=min(30,1+int(damage*0.04)), scale=0.9, spread=0.28); # momentary flash hurtiness = damage*0.003 punchPos = (m.pos[0]+m.forceDirection[0]*0.02, m.pos[1]+m.forceDirection[1]*0.02, m.pos[2]+m.forceDirection[2]*0.02) flashColor = (1.0,0.8,0.4) light = bs.newNode("light", attrs={'position':punchPos, 'radius':0.12+hurtiness*0.12, 'intensity':0.3*(1.0+1.0*hurtiness), 'heightAttenuated':False, 'color':flashColor}) bs.gameTimer(60,light.delete) flash = bs.newNode("flash", attrs={'position':punchPos, 'size':0.17+0.17*hurtiness, 'color':flashColor}) bs.gameTimer(60,flash.delete) if m.hitType == 'impact': bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*2.0, m.forceDirection[1]*2.0, m.forceDirection[2]*2.0), count=min(10,1+int(damage*0.01)),scale=0.4,spread=0.1); if self.hitPoints > 0: # its kinda crappy to die from impacts, so lets reduce impact damage # by a reasonable amount if it'll keep us alive if m.hitType == 'impact' and damage > self.hitPoints: # drop damage to whatever puts us at 10 hit points, or 200 less than it used to be # whichever is greater (so it *can* still kill us if its high enough) newDamage = max(damage-200,self.hitPoints-10) damage = newDamage self.node.handleMessage("flash") # if we're holding something, drop it if damage > 0.0 and self.node.holdNode.exists(): self.node.holdNode = bs.Node(None) ################################# boxDamage = damage if bs.getActivity().settings['Hits damage players']: if damage > self.hitPoints: boxDamage = self.hitPoints #Only take boxes for the remaining health if boxDamage < 0: boxDamage = 0 #might be hitting a corpse self.hitPoints -= damage #Only take hit points for damage if settings allow self.node.hurt = 1.0 - float(self.hitPoints)/self.hitPointsMax # if we're cursed, *any* damage blows us up if self._cursed and damage > 0: bs.gameTimer(50,bs.WeakCall(self.curseExplode,m.sourcePlayer)) # if we're frozen, shatter.. otherwise die if we hit zero if self.frozen and (damage > 200 or self.hitPoints <= 0): self.shatter() elif self.hitPoints <= 0: self.node.handleMessage(bs.DieMessage(how='impact')) # if we're dead, take a look at the smoothed damage val # (which gives us a smoothed average of recent damage) and shatter # us if its grown high enough if self.hitPoints <= 0: damageAvg = self.node.damageSmoothed * damageScale if damageAvg > 1000: self.shatter() #Now calculate the number of boxes to spew. Idea is to spew half of boxes if damage is 1000 (basically one0shot kill) player = self.getPlayer() if player.exists(): #Don't spew boxes for hitting a corpse! ptot = 0 for p2 in player.getTeam().players: if p2.exists(): ptot +=1 if (player.getTeam().gameData['score']/ptot <= 2) and boxDamage >= 800: boxes = 1 else: boxes = int(player.getTeam().gameData['score']/ptot * boxDamage / 2000) if boxes >= player.getTeam().gameData['score']: #somehow the team loses more than all theri boxes? boxes = player.getTeam().gameData['score'] #print([damage, boxes]) if boxes > 0: player.getTeam().gameData['score'] -= boxes self.setScoreText(str(player.getTeam().gameData['score']), player.color) bs.gameTimer(50,bs.WeakCall(self.getActivity().spewBoxes,self.node.position, boxes)) if player.getTeam().gameData['score'] == 0: self.handleMessage(bs.DieMessage()) self.getActivity()._updateScoreBoard() return if isinstance(m,bs.PowerupMessage): #Have to handle health powerups ourselves if m.powerupType == 'health': if self._dead: return True if self.pickUpPowerupCallback is not None: self.pickUpPowerupCallback(self) player = self.getPlayer() if player.exists(): player.getTeam().gameData['score'] += 1 self.getActivity()._updateScoreBoard() self.setScoreText(str(player.getTeam().gameData['score']), player.color) self.node.handleMessage("flash") if m.sourceNode.exists(): m.sourceNode.handleMessage(bs.PowerupAcceptMessage()) return True else: super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) class Greed(bs.TeamGameActivity): @classmethod def getName(cls): return 'Greed' @classmethod def getScoreInfo(cls): return {'scoreName':'score', 'scoreType':'points', 'noneIsWinner':False, 'lowerIsBetter':False} @classmethod def getDescription(cls,sessionType): return 'Steal all the health boxes for yourself' @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): settings = [("Time Limit",{'choices':[('30 Seconds',30),('1 Minute',60), ('90 Seconds',90),('2 Minutes',120), ('3 Minutes',180),('5 Minutes',300)],'default':60}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Starting Points", {'default':10,'minValue':5, 'maxValue':50,'increment':5}), ("Gloves:",{'choices':[('Everyone',1),('Spawn',2),('None',3)],'default':3}), ("Hits damage players",{'default':True}), ("Epic Mode",{'default':False})] if issubclass(sessionType,bs.TeamsSession): settings.append(("Solo Mode",{'default':False})) settings.append(("Balance Total Lives",{'default':False})) return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # show messages when players die since it's meaningful here self.announcePlayerDeaths = True try: self._soloMode = settings['Solo Mode'] except Exception: self._soloMode = False self._scoreBoard = bs.ScoreBoard() def getInstanceDescription(self): return 'Steal all the health boxes for yourself' if isinstance(self.getSession(),bs.TeamsSession) else 'Steal all the health boxes for yourself' def getInstanceScoreBoardDescription(self): return 'Steal all the health boxes for yourself' if isinstance(self.getSession(),bs.TeamsSession) else 'Steal all the health boxes for yourself' def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onTeamJoin(self,team): team.gameData['survivalSeconds'] = None team.gameData['spawnOrder'] = [] team.gameData['score'] = 0 def onPlayerJoin(self, player): # no longer allowing mid-game joiners here... too easy to exploit if self.hasBegun(): player.gameData['lives'] = 0 player.gameData['icons'] = [] # make sure our team has survival seconds set if they're all dead # (otherwise blocked new ffa players would be considered 'still alive' in score tallying) if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None: player.getTeam().gameData['survivalSeconds'] = 1000 bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) return player.gameData['lives'] = 1 player.getTeam().gameData['score'] = self.settings['Starting Points'] self._updateScoreBoard() if self._soloMode: #player.gameData['icons'] = [] player.getTeam().gameData['spawnOrder'].append(player) self._updateSoloMode() else: # create our icon and spawn #player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)] if player.gameData['lives'] > 0: self.spawnPlayer(player) # dont waste time doing this until begin if self.hasBegun(): pass#self._updateIcons() def _updateSoloMode(self): # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) break def _updateIcons(self): # in free-for-all mode, everyone is just lined up along the bottom if isinstance(self.getSession(),bs.FreeForAllSession): count = len(self.teams) xOffs = 85 x = xOffs*(count-1) * -0.5 for i,team in enumerate(self.teams): if len(team.players) == 1: player = team.players[0] for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs # in teams mode we split up teams else: if self._soloMode: # first off, clear out all icons for player in self.players: player.gameData['icons'] = [] # now for each team, cycle through our available players adding icons for team in self.teams: if team.getID() == 0: x = -60 xOffs = -78 else: x = 60 xOffs = 78 isFirst = True testLives = 1 while True: playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives] if len(playersWithLives) == 0: break for player in playersWithLives: player.gameData['icons'].append(Icon(player, position=(x,(40 if isFirst else 25)), scale=1.0 if isFirst else 0.5, nameMaxWidth=130 if isFirst else 75, nameScale=0.8 if isFirst else 1.0, flatness=0.0 if isFirst else 1.0, shadow=0.5 if isFirst else 1.0, showDeath=True if isFirst else False, showLives=False)) x += xOffs * (0.8 if isFirst else 0.56) isFirst = False testLives += 1 # non-solo mode else: for team in self.teams: if team.getID() == 0: x = -50 xOffs = -85 else: x = 50 xOffs = 85 for player in team.players: for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs def _getSpawnPoint(self,player): # in solo-mode, if there's an existing live player on the map, spawn at whichever # spot is farthest from them (keeps the action spread out) if self._soloMode: livingPlayer = None for team in self.teams: for player in team.players: if player.isAlive(): p = player.actor.node.position livingPlayer = player livingPlayerPos = p break if livingPlayer: playerPos = bs.Vector(*livingPlayerPos) points = [] for team in self.teams: startPos = bs.Vector(*self.getMap().getStartPosition(team.getID())) points.append([(startPos-playerPos).length(),startPos]) points.sort() return points[-1][1] else: return None else: return None def spawnPlayer(self,player): """This next line is the default spawn line. But we need to spawn our special guy""" #self.spawnPlayerSpaz(player,self._getSpawnPoint(player)) #position = self._getSpawnPoint(player) #if isinstance(self.getSession(), bs.TeamsSession): # position = self.getMap().getStartPosition(player.getTeam().getID()) #else: # # otherwise do free-for-all spawn locations position = self.getMap().getFFAStartPosition(self.players) angle = 20 #spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up #spaz.connectControlsToPlayer(enablePunch=False, # enableBomb=False, # enablePickUp=False) # also lets have them make some noise when they die.. #spaz.playBigDeathSound = True name = player.getName() lightColor = bsUtils.getNormalizedColor(player.color) displayColor = bs.getSafeColor(player.color, targetIntensity=0.75) spaz = PlayerSpaz_Greed(color=player.color, highlight=player.highlight, character=player.character, player=player) player.setActor(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() self.scoreSet.playerGotNewSpaz(player, spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360))) t = bs.getGameTime() bs.playSound(self._spawnSound, 1, position=spaz.node.position) light = bs.newNode('light', attrs={'color': lightColor}) spaz.node.connectAttr('position', light, 'position') bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0}) bs.gameTimer(500, light.delete) #Start code to spawn special guy: #End of code to spawn special guy if not self._soloMode: bs.gameTimer(1000,bs.WeakCall(self.updateSpazScore, player,spaz), repeat=True) #spaz.setScoreText(str(player.getTeam().gameData['score']), player.color) if self.settings['Gloves:'] == 1: spaz.equipBoxingGloves() # if we have any icons, update their state #for icon in player.gameData['icons']: # icon.handlePlayerSpawned() def updateSpazScore(self,player,spaz): if player.isAlive(): if spaz.isAlive(): spaz.setScoreText(str(player.getTeam().gameData['score']), player.color) def _printLives(self,player): if not player.exists() or not player.isAlive(): return try: pos = player.actor.node.position except Exception,e: print 'EXC getting player pos in bsElim',e return bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1), offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain() def onPlayerLeave(self,player): bs.TeamGameActivity.onPlayerLeave(self,player) #player.gameData['icons'] = None player.gameData['score'] = 0 # remove us from spawn-order if self._soloMode: if player in player.getTeam().gameData['spawnOrder']: player.getTeam().gameData['spawnOrder'].remove(player) # update icons in a moment since our team will be gone from the list then #bs.gameTimer(0, self._updateIcons) def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops() if self._soloMode: self._vsText = bs.NodeActor(bs.newNode("text", attrs={'position':(0,105), 'hAttach':"center", 'hAlign':'center', 'maxWidth':200, 'shadow':0.5, 'vrDepth':390, 'scale':0.6, 'vAttach':"bottom", 'color':(0.8,0.8,0.3,1.0), 'text':bs.Lstr(resource='vsText')})) # if balance-team-lives is on, add lives to the smaller team until total lives match if (isinstance(self.getSession(),bs.TeamsSession) and self.settings['Balance Total Lives'] and len(self.teams[0].players) > 0 and len(self.teams[1].players) > 0): if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]): lesserTeam = self.teams[0] greaterTeam = self.teams[1] else: lesserTeam = self.teams[1] greaterTeam = self.teams[0] addIndex = 0 while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam): lesserTeam.players[addIndex].gameData['lives'] += 1 addIndex = (addIndex + 1) % len(lesserTeam.players) self.excludePowerups = ['health','iceBombs','curse'] if self.settings['Gloves:'] == 3: self.excludePowerups.append('punch') #self._updateIcons() # we could check game-over conditions at explicit trigger points, # but lets just do the simple thing and poll it... bs.gameTimer(1000, self._update, repeat=True) def _getTotalTeamLives(self,team): return sum(player.gameData['lives'] for player in team.players) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() if m.killerPlayer == player: player.getTeam().gameData['score'] /= 2 self._updateScoreBoard() #print([player, m.spaz.hitPoints, "killed by", m.killerPlayer]) if player.getTeam().gameData['score'] == 0: #Only permadeath if they lost all their boxes. player.gameData['lives'] -= 1 if player.gameData['lives'] < 0: bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode)) player.gameData['lives'] = 0 # if we have any icons, update their state #for icon in player.gameData['icons']: # icon.handlePlayerDied() # play big death sound on our last death or for every one in solo mode if self._soloMode or player.gameData['lives'] == 0: bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound) # if we hit zero lives, we're dead (and our whole team is too, since 0 boxes left) if player.gameData['lives'] == 0: pass # if the whole team is now dead, mark their survival time.. #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players): #if self._getTotalTeamLives(player.getTeam()) == 0: # player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000 #for p2 in player.getTeam().players: # p2.gameData['lives'] = 0 # self.scoreSet._players[player.getName()].getSpaz().handleMessage(bs.DieMessage()) else: # otherwise, in regular mode, respawn.. if not self._soloMode: self.respawnPlayer(player) # in solo, put ourself at the back of the spawn order if self._soloMode: player.getTeam().gameData['spawnOrder'].remove(player) player.getTeam().gameData['spawnOrder'].append(player) else: bs.TeamGameActivity.handleMessage(self, m) def _update(self): if self._soloMode: # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) #self._updateIcons() break # if we're down to 1 or fewer living teams, start a timer to end the game # (allows the dust to settle and draws to occur if deaths are close enough) if (len(self._getLivingTeams()) < 2): self._roundEndTimer = bs.Timer(500,self.endGame) def spewBoxes(self, pos, boxes): p2 = list(pos) p2[1] += 1 for n in range(0,boxes): #print(["spewbox", [p2[0], p2[1] + 1.5 + (x*0.4), p2[2]]]) #Let's create a spiral, just for fun gap = 0.1 x=gap*float(n+2)*((-1.0)**float(int((n % 4)/2))) #x = (-1.0)^float(3) y=gap*float(n+2)*((-1.0)**float(int((n + 1 % 4)/2))) bs.gameTimer(int((1+n)*25), bs.Call(self.spewWithTimer, [p2[0]+x, p2[1] + 1.0 + (n*gap), p2[2]+y])) def spewWithTimer(self,pos): bsPowerup.Powerup(position=pos, powerupType='health',expire=False).autoRetain() def _standardDropPowerup(self,index,expire=True): #Overloaded from bsGame in order to randomize without curse, health, or freeze. bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[index], powerupType=bs.Powerup.getFactory().getRandomPowerupType(forceType=None, excludeTypes=self.excludePowerups),expire=expire).autoRetain() def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)] def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team, team.gameData['score']) def endGame(self): if self.hasEnded(): return results = bs.TeamGameResults() self._vsText = None # kill our 'vs' if its there for team in self.teams: results.setTeamScore(team, team.gameData['score']) self.end(results=results) ================================================ FILE: mods/GuessTheBomb.json ================================================ { "name": "Guess The Bomb", "author": "Paolo Valerdi", "category": "minigames" } ================================================ FILE: mods/GuessTheBomb.py ================================================ #Made by Paolo Valerdi import bs import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [GuessTheBombGame] def bsGetLevels(): return [bs.Level('Guess The Bomb',displayName='${GAME}',gameType=GuessTheBombGame,settings={},previewTexName='rampagePreview'), bs.Level('Epic Guess The Bomb',displayName='${GAME}',gameType=GuessTheBombGame,settings={'Epic Mode':True},previewTexName='rampagePreview')] class GuessTheBombGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Guess The Bomb' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'milliseconds', 'scoreVersion':'B'} @classmethod def getDescription(cls,sessionType): return 'Dodge the falling bombs.' @classmethod def getSupportedMaps(cls,sessionType): return ['Rampage'] @classmethod def getSettings(cls,sessionType): return [("Epic Mode",{'default':False})] @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession) or issubclass(sessionType,bs.CoopSession)) else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True self.announcePlayerDeaths = True self._lastPlayerDeathTime = None def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') def onBegin(self): bs.TeamGameActivity.onBegin(self) self._meteorTime = 3000 t = 7500 if len(self.players) > 2 else 4000 if self.settings['Epic Mode']: t /= 4 bs.gameTimer(t,self._decrementMeteorTime,repeat=True) t = 3000 if self.settings['Epic Mode']: t /= 4 bs.gameTimer(t,self._setMeteorTimer) self._timer = bs.OnScreenTimer() self._timer.start() def spawnPlayer(self,player): spaz = self.spawnPlayerSpaz(player) spaz.connectControlsToPlayer(enablePunch=False, enableBomb=False, enablePickUp=False) spaz.playBigDeathSound = True def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) deathTime = bs.getGameTime() m.spaz.getPlayer().gameData['deathTime'] = deathTime if isinstance(self.getSession(),bs.CoopSession): bs.pushCall(self._checkEndGame) self._lastPlayerDeathTime = deathTime else: bs.gameTimer(1000,self._checkEndGame) else: bs.TeamGameActivity.handleMessage(self,m) def _checkEndGame(self): livingTeamCount = 0 for team in self.teams: for player in team.players: if player.isAlive(): livingTeamCount += 1 break if isinstance(self.getSession(),bs.CoopSession): if livingTeamCount <= 0: self.endGame() else: if livingTeamCount <= 1: self.endGame() def _setMeteorTimer(self): bs.gameTimer(int((1.0+0.2*random.random())*self._meteorTime),self._dropBombCluster) def _dropBombCluster(self): if False: bs.newNode('locator',attrs={'position':(8,6,-5.5)}) bs.newNode('locator',attrs={'position':(8,6,-2.3)}) bs.newNode('locator',attrs={'position':(-7.3,6,-5.5)}) bs.newNode('locator',attrs={'position':(-7.3,6,-2.3)}) delay = 0 for i in range(random.randrange(1,3)): types = ["normal", "ice", "sticky", "impact"] magic = random.choice(types) bt = magic pos = (-7.3+15.3*random.random(),11,-5.5+2.1*random.random()) vel = ((-5.0+random.random()*30.0) * (-1.0 if pos[0] > 0 else 1.0), -4.0,0) bs.gameTimer(delay,bs.Call(self._dropBomb,pos,vel,bt)) delay += 100 self._setMeteorTimer() def _dropBomb(self,position,velocity,bombType): b = bs.Bomb(position=position,velocity=velocity,bombType=bombType).autoRetain() def _decrementMeteorTime(self): self._meteorTime = max(10,int(self._meteorTime*0.9)) def endGame(self): curTime = bs.getGameTime() for team in self.teams: for player in team.players: if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime+1 score = (player.gameData['deathTime']-self._timer.getStartTime())/1000 if 'deathTime' not in player.gameData: score += 50 self.scoreSet.playerScored(player,score,screenMessage=False) self._timer.stop(endTime=self._lastPlayerDeathTime) results = bs.TeamGameResults() for team in self.teams: longestLife = 0 for player in team.players: longestLife = max(longestLife,(player.gameData['deathTime'] - self._timer.getStartTime())) results.setTeamScore(team,longestLife) self.end(results=results) ================================================ FILE: mods/HazardousCargo.json ================================================ { "name": "Hazardous Cargo", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/HazardousCargo.py ================================================ import bs import random import bsUtils import bsBomb def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [HazardousCargo] class boxCrossMessage(object): pass class myMine(bs.Bomb): #reason for the mine class is so we can get the HitMessage. #We need the HitMessage to prevent chain reactions from pretty #much blowing the whole map. def __init__(self,pos): bs.Bomb.__init__(self,position=pos,bombType='landMine') self.hitSubType = 1 #Startt SubType at 1 def handleMessage(self,m): #print(m) if isinstance(m, bs.HitMessage): #print(['hit',m.hitSubType]) #hitSubType comes from the thing doing the hitting. #In our case, all the mines start with 1. We want to increment #hitSubType with each successive hitter so that we can limit #chain reactions that pretty much clear the whole map. if m.hitSubType < 4: self.hitSubType = m.hitSubType + 1 m.hitSubType = self.hitSubType super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) class cargoBox(bs.Bomb): def __init__(self,pos): bs.Bomb.__init__(self,position=pos,bombType='tnt') self.claimedBy = None self.scored = False #self = box fm = bs.Flag.getFactory().flagMaterial materials = getattr(self.node,'materials') if not fm in materials: setattr(self.node,'materials',materials + (fm,)) def handleMessage(self,m): if isinstance(m,bs.HitMessage): if m.hitSubType == 'tnt': return True else: super(self.__class__, self).handleMessage(m) if isinstance(m, boxCrossMessage): self.getActivity().checkForScore(self) if isinstance(m, bs.PickedUpMessage): self._updateBoxState() if isinstance(m, bs.DieMessage): act = self.getActivity() act.updateBoxTimer() super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) def _updateBoxState(self): claimerHold = False userperHold = False for player in self.getActivity().players: try: if player.actor.isAlive() and player.actor.node.holdNode.exists(): holdingBox = (player.actor.node.holdNode == self.node) else: holdingBox = False except Exception: bs.printException("exception checking hold flag") if holdingBox: if self.claimedBy is None: self.claimedBy = player claimerHold = True else: if self.claimedBy == player: claimerHold = True else: userperHold = True #release claim on any other existing boxes for box in self.getActivity().boxes: if box <> self and box.claimedBy == self.claimedBy: box.claimedBy = None #Blow up this box if it belongs to someone else if (not claimerHold) and userperHold: self.handleMessage(bs.HitMessage()) class HazardousCargo(bs.TeamGameActivity): @classmethod def getName(cls): return 'Hazardous Cargo' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'seconds', 'noneIsWinner':False, 'lowerIsBetter':True} @classmethod def supportsSessionType(cls,sessionType): # we support teams, free-for-all return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getDescription(cls,sessionType): return 'Go get a TNT box and return to the end zone.' @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType('football') @classmethod def getSettings(cls,sessionType): return [("Score to Win",{'minValue':1,'default':1,'increment':1}), ("Max Mines",{'minValue':20,'default':80,'increment':10}), ("Start Mines",{'minValue':20,'default':40,'increment':10}), ("Mines per Second",{'minValue':1,'default':5,'increment':1}), ("Enable Bombs",{'default':False}), ("One-Way Trip",{'default':False}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':120}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ] def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self._scoreBoard = bs.ScoreBoard() self.boxes = [] self.mines = [] # load some media we need self._cheerSound = bs.getSound("cheer") self._chantSound = bs.getSound("crowdChant") self._scoreSound = bs.getSound("score") self._swipSound = bs.getSound("swip") self._whistleSound = bs.getSound("refWhistle") self.scoreRegionMaterial = bs.Material() self.scoreRegionMaterial.addActions( conditions=("theyHaveMaterial",bs.Flag.getFactory().flagMaterial), actions=(("modifyPartCollision","collide",True), ("modifyPartCollision","physical",False), ("message",'theirNode','atConnect',boxCrossMessage()))) def getInstanceDescription(self): return ('Go get a TNT box and carry it to the end zone.') def getInstanceScoreBoardDescription(self): tds = self.settings['Score to Win'] if tds > 1: return ('Bring back ${ARG1} TNT boxes. Better get your own!',tds) else: return ('Bring back a TNT box. Better get your own!') def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Football') self._startGameTime = bs.getGameTime() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.winners = [] self.setupStandardTimeLimit(self.settings['Time Limit']) # set up the score region self._scoreRegions = [] defs = self.getMap().defs self._scoreRegions.append(bs.NodeActor(bs.newNode('region', attrs={'position':defs.boxes['goal1'][0:3], 'scale':defs.boxes['goal1'][6:9], 'type': 'box', 'materials':(self.scoreRegionMaterial,)}))) self._updateScoreBoard() self.updateBoxes() #Preload the play area with mines while len(self.mines) < self.settings['Start Mines']: x = random.uniform(-11.3, 11.3) y = random.uniform(-5.2,5.7) pos = [x,0.32,y] self._makeMine(pos) bs.playSound(self._chantSound) #Set up the timer for mine spawning bs.gameTimer(int(1000/self.settings['Mines per Second']), bs.WeakCall(self.mineUpdate), repeat=True) bs.gameTimer(1000, self._update, repeat=True) def updateBoxTimer(self): bs.gameTimer(50, self.updateBoxes) def updateBoxes(self): for box in self.boxes: if not box.exists(): self.boxes.remove(box) while len(self.boxes) < len(self.teams): #x = random.uniform(-12.5,-11.3) y = random.uniform(-5,5.5) self.boxes.append(cargoBox([-12.3,0.4,y])) def onTeamJoin(self,team): team.gameData['score'] = 0 team.gameData['survivalSeconds'] = None self._updateScoreBoard() self.updateBoxes() def checkForScore(self, box): if box.scored: return player = box.claimedBy if player.actor.isAlive() and player.actor.node.holdNode.exists(): if player.actor.node.holdNode == box.node: box.scored = True player.getTeam().gameData['score'] +=1 bsUtils.animate(box.node, "modelScale", {0:1.0, 150:0.5, 300:0.0}) bs.gameTimer(300, bs.WeakCall(box.handleMessage, bs.DieMessage())) self._updateScoreBoard() for playa in player.getTeam().players: try: playa.actor.node.handleMessage('celebrate',2000) except Exception: pass if player.getTeam().gameData['score'] == self.settings['Score to Win']: player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000 self.winners.append(player.getTeam()) for playa in player.getTeam().players: self._flashPlayer(playa,1.0) playa.actor.handleMessage(bs.DieMessage(immediate=True)) bs.playSound(self._scoreSound) bs.playSound(self._cheerSound) else: #print('nobody scored') box.handleMessage(bs.HitMessage()) else: box.handleMessage(bs.HitMessage()) def _update(self): # if we're down to 1 or fewer living teams, start a timer to end the game # (allows the dust to settle and draws to occur if deaths are close enough) if (len([team for team in self.teams if team.gameData['score'] < self.settings['Score to Win'] ]) < 2) or len(self.winners) > 2: self._roundEndTimer = bs.Timer(500,self.endGame) def mineUpdate(self): #purge dead mines for m in self.mines: if not m.exists(): self.mines.remove(m) #Remove an old mine (if needed) and make a new mine if len(self.mines) > self.settings['Max Mines']: self.mines[0].handleMessage(bs.DieMessage(immediate=True)) del self.mines[0] x = random.uniform(-10.3, 11.3) y = random.uniform(-5.2,5.7) pos = [x,0.32,y] self._flashMine(pos) bs.gameTimer(950,bs.Call(self._makeMine,pos)) def _makeMine(self,posn): m = myMine(pos=posn) m.arm() self.mines.append(m) def _flashMine(self,pos): light = bs.newNode("light", attrs={'position':pos, 'color':(1,0.2,0.2), 'radius':0.1, 'heightAttenuated':False}) bs.animate(light,"intensity",{0:0,100:1.0,200:0},loop=True) bs.gameTimer(1000,light.delete) def endGame(self): results = bs.TeamGameResults() for t in self.teams: results.setTeamScore(t,t.gameData['survivalSeconds']) self.end(results=results,announceDelay=800) def _flashPlayer(self,player,scale): pos = player.actor.node.position light = bs.newNode('light', attrs={'position':pos, 'color':(1,1,0), 'heightAttenuated':False, 'radius':0.4}) bs.gameTimer(500,light.delete) bs.animate(light,'intensity',{0:0,100:1.0*scale,500:0}) def _updateScoreBoard(self): winScore = self.settings['Score to Win'] for team in self.teams: self._scoreBoard.setTeamValue(team,team.gameData['score'],winScore) def handleMessage(self,m): # respawn dead players if they're still in the game if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior self.respawnPlayer(m.spaz.getPlayer()) else: bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior def spawnPlayer(self,player): """ Spawn *something* for the provided bs.Player. The default implementation simply calls spawnPlayerSpaz(). """ #Overloaded for this game to spawn players in the end zone instead of FFA spots if not player.exists(): bs.printError('spawnPlayer() called for nonexistant player') return y = random.uniform(-5,5.5) if self.settings['One-Way Trip'] == True: x = -11.8 else: x = 12.0 spz = self.spawnPlayerSpaz(player, position=[x,0.35,y]) spz.connectControlsToPlayer(enablePunch=True, enableBomb=self.settings['Enable Bombs'], enablePickUp=True) return spz ================================================ FILE: mods/Infection.json ================================================ { "name": "Infection", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/Infection.py ================================================ import bs import random import bsUtils import bsBomb import bsVector def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [Infection] def bsGetLevels(): return [ bs.Level('Infection', displayName='${GAME}', gameType=Infection, settings={}, previewTexName='footballStadiumPreview') ] class PlayerSpaz_Infection(bs.PlayerSpaz): def handleMessage(self, m): if isinstance(m, bs.HitMessage): if not self.node.exists(): return True if m.sourcePlayer != self.getPlayer(): return True else: super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) class myMine(bs.Bomb): #reason for the mine class is so we can add the death zone def __init__(self,pos): bs.Bomb.__init__(self,position=pos,bombType='landMine') showInSpace = False self.died = False self.rad = 0.3 self.zone = bs.newNode('locator',attrs={'shape':'circle','position':self.node.position,'color':(1,0,0),'opacity':0.5,'drawBeauty':showInSpace,'additive':True}) bs.animateArray(self.zone,'size',1,{0:[0.0],50:[2*self.rad]}) def handleMessage(self,m): if isinstance(m,bs.DieMessage): if not self.died: self.getActivity().mineCount -= 1 self.died = True bs.animateArray(self.zone,'size',1,{0:[2*self.rad],50:[0]}) self.zone = None super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) class Infection(bs.TeamGameActivity): @classmethod def getName(cls): return 'Infection' @classmethod def getScoreInfo(cls): return { 'scoreName':'Survived', 'scoreType':'milliseconds', 'scoreVersion':'B' } @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession) or issubclass(sessionType,bs.CoopSession)) else False @classmethod def getDescription(cls,sessionType): return "It's spreading!" @classmethod def getSupportedMaps(cls,sessionType): return ['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Crag Castle', 'Big G', 'Football Stadium'] @classmethod def getSettings(cls,sessionType): return [("Mines",{'minValue':5,'default':10,'increment':5}), ("Enable Bombs",{'default':True}), ("Sec/Extra Mine",{'minValue':1,'default':10,'increment':1}), ("Max Infected Size",{'minValue':4,'default':6,'increment':1}), ("Max Size Increases Every",{'choices':[('10s',10),('20s',20), ('30s',30),('Minute',60)],'default':20}), ("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}), ] def __init__(self,settings): bs.TeamGameActivity.__init__(self, settings) # print messages when players die (since its meaningful in this game) self.announcePlayerDeaths = True self._lastPlayerDeathTime = None self.mines = [] self.maxMines = settings['Mines'] self.updateRate = 100 #update the mine radii etc every this many milliseconds self.growthRate = self.settings['Infection Spread Rate'] #grow the radius of each death zone by this much every update self.maxSize = self.settings['Max Infected Size'] #We'll set a timer later to increase this def getInstanceDescription(self): return ('Avoid the spread!') def getInstanceScoreBoardDescription(self): return ('Avoid the spread!') def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Survival') self._startGameTime = bs.getGameTime() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.mineCount = 0 bs.gameTimer(self.updateRate, bs.WeakCall(self.mineUpdate), repeat=True) bs.gameTimer(self.settings['Max Size Increases Every']*1000, bs.WeakCall(self.maxSizeUpdate), repeat=True) bs.gameTimer(self.settings['Sec/Extra Mine']*1000, bs.WeakCall(self.maxMineUpdate), repeat=True) self._timer = bs.OnScreenTimer() self._timer.start() # check for immediate end (if we've only got 1 player, etc) bs.gameTimer(5000, self._checkEndGame) def onPlayerJoin(self, player): # don't allow joining after we start # (would enable leave/rejoin tomfoolery) if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) # for score purposes, mark them as having died right as the game started player.gameData['deathTime'] = self._timer.getStartTime() return self.spawnPlayer(player) def onPlayerLeave(self, player): # augment default behavior... bs.TeamGameActivity.onPlayerLeave(self, player) # a departing player may trigger game-over self._checkEndGame() def maxMineUpdate(self): self.maxMines += 1 def maxSizeUpdate(self): self.maxSize += 1 def mineUpdate(self): #print self.mineCount #purge dead mines, update their animantion, check if players died for m in self.mines: if not m.exists(): self.mines.remove(m) else: #First, check if any player is within the current death zone for player in self.players: if not player.actor is None: if player.actor.isAlive(): p1 = player.actor.node.position p2 = m.node.position diff = (bs.Vector(p1[0]-p2[0],0.0,p1[2]-p2[2])) dist = (diff.length()) if dist < m.rad: player.actor.handleMessage(bs.DieMessage()) #Now tell the circle to grow to the new size if m.rad < self.maxSize: bs.animateArray(m.zone,'size',1,{0:[m.rad*2],self.updateRate:[(m.rad+self.growthRate)*2]}) #Tell the circle to be the new size. This will be the new check radius next time. m.rad +=self.growthRate #make a new mine if needed. if self.mineCount < self.maxMines: pos = self.getRandomPowerupPoint() self.mineCount += 1 self._flashMine(pos) bs.gameTimer(950,bs.Call(self._makeMine,pos)) def _makeMine(self,posn): m = myMine(pos=posn) m.arm() self.mines.append(m) def _flashMine(self,pos): light = bs.newNode("light", attrs={'position':pos, 'color':(1,0.2,0.2), 'radius':0.1, 'heightAttenuated':False}) bs.animate(light,"intensity",{0:0,100:1.0,200:0},loop=True) bs.gameTimer(1000,light.delete) def endGame(self): results = bs.TeamGameResults() for t in self.teams: results.setTeamScore(t,t.gameData['survivalSeconds']) self.end(results=results,announceDelay=800) def _flashPlayer(self,player,scale): pos = player.actor.node.position light = bs.newNode('light', attrs={'position':pos, 'color':(1,1,0), 'heightAttenuated':False, 'radius':0.4}) bs.gameTimer(500,light.delete) bs.animate(light,'intensity',{0:0,100:1.0*scale,500:0}) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # (augment standard behavior) deathTime = bs.getGameTime() # record the player's moment of death m.spaz.getPlayer().gameData['deathTime'] = deathTime # in co-op mode, end the game the instant everyone dies (more accurate looking) # in teams/ffa, allow a one-second fudge-factor so we can get more draws if isinstance(self.getSession(),bs.CoopSession): # teams will still show up if we check now.. check in the next cycle bs.pushCall(self._checkEndGame) self._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock.. else: bs.gameTimer(1000, self._checkEndGame) else: # default handler: bs.TeamGameActivity.handleMessage(self,m) def _checkEndGame(self): livingTeamCount = 0 for team in self.teams: for player in team.players: if player.isAlive(): livingTeamCount += 1 break # in co-op, we go till everyone is dead.. otherwise we go until one team remains if isinstance(self.getSession(),bs.CoopSession): if livingTeamCount <= 0: self.endGame() else: if livingTeamCount <= 1: self.endGame() def spawnPlayer(self, player): spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up spaz.connectControlsToPlayer(enablePunch=False, enableBomb=self.settings['Enable Bombs'], enablePickUp=False) # also lets have them make some noise when they die.. spaz.playBigDeathSound = True def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None): """ Create and wire up a bs.PlayerSpaz for the provide bs.Player. """ position = self.getMap().getFFAStartPosition(self.players) name = player.getName() color = player.color highlight = player.highlight lightColor = bsUtils.getNormalizedColor(color) displayColor = bs.getSafeColor(color,targetIntensity=0.75) spaz = PlayerSpaz_Infection(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() self.scoreSet.playerGotNewSpaz(player,spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360))) t = bs.getGameTime() bs.playSound(self._spawnSound,1,position=spaz.node.position) light = bs.newNode('light',attrs={'color':lightColor}) spaz.node.connectAttr('position',light,'position') bsUtils.animate(light,'intensity',{0:0,250:1,500:0}) bs.gameTimer(500,light.delete) return spaz def getRandomPowerupPoint(self): #So far, randomized points only figured out for mostly rectangular maps. #Boxes will still fall through holes, but shouldn't be terrible problem (hopefully) #If you add stuff here, need to add to "supported maps" above. #['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium'] myMap = self.getMap().getName() #print(myMap) if myMap == 'Doom Shroom': while True: x = random.uniform(-1.0,1.0) y = random.uniform(-1.0,1.0) if x*x+y*y < 1.0: break return ((8.0*x,2.5,-3.5+5.0*y)) elif myMap == 'Rampage': x = random.uniform(-6.0,7.0) y = random.uniform(-6.0,-2.5) return ((x, 5.2, y)) elif myMap == 'Hockey Stadium': x = random.uniform(-11.5,11.5) y = random.uniform(-4.5,4.5) return ((x, 0.2, y)) elif myMap == 'Courtyard': x = random.uniform(-4.3,4.3) y = random.uniform(-4.4,0.3) return ((x, 3.0, y)) elif myMap == 'Crag Castle': x = random.uniform(-6.7,8.0) y = random.uniform(-6.0,0.0) return ((x, 10.0, y)) elif myMap == 'Big G': x = random.uniform(-8.7,8.0) y = random.uniform(-7.5,6.5) return ((x, 3.5, y)) elif myMap == 'Football Stadium': x = random.uniform(-12.5,12.5) y = random.uniform(-5.0,5.5) return ((x, 0.32, y)) else: x = random.uniform(-5.0,5.0) y = random.uniform(-6.0,0.0) return ((x, 8.0, y)) def endGame(self): curTime = bs.getGameTime() # mark 'death-time' as now for any still-living players # and award players points for how long they lasted. # (these per-player scores are only meaningful in team-games) for team in self.teams: for player in team.players: # throw an extra fudge factor +1 in so teams that # didn't die come out ahead of teams that did if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime+1 # award a per-player score depending on how many seconds they lasted # (per-player scores only affect teams mode; everywhere else just looks at the per-team score) score = (player.gameData['deathTime']-self._timer.getStartTime())/1000 if 'deathTime' not in player.gameData: score += 50 # a bit extra for survivors self.scoreSet.playerScored(player,score,screenMessage=False) # stop updating our time text, and set the final time to match # exactly when our last guy died. self._timer.stop(endTime=self._lastPlayerDeathTime) # ok now calc game results: set a score for each team and then tell the game to end results = bs.TeamGameResults() # remember that 'free-for-all' mode is simply a special form of 'teams' mode # where each player gets their own team, so we can just always deal in teams # and have all cases covered for team in self.teams: # set the team score to the max time survived by any player on that team longestLife = 0 for player in team.players: longestLife = max(longestLife,(player.gameData['deathTime'] - self._timer.getStartTime())) results.setTeamScore(team,longestLife) self.end(results=results) ================================================ FILE: mods/JumpingContest.py ================================================ # Jumping Contest # This simple game tests each player's ability in an underappreciated aspect of the game: jumping. import bs import bsUtils import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [JumpingContest] def bsGetLevels(): return [bs.Level('Jumping Contest', displayName='${GAME}', gameType=JumpingContest, settings={}, previewTexName='courtyardPreview')] class RaceTimer: # the race timer to start things off def __init__(self, incTime=1000): lightY = 150 self.pos = 0 self._beep1Sound = bs.getSound('raceBeep1') self._beep2Sound = bs.getSound('raceBeep2') self.lights = [] for i in range(4): l = bs.newNode('image', attrs={'texture':bs.getTexture('nub'), 'opacity':1.0, 'absoluteScale':True, 'position':(-75+i*50, lightY), 'scale':(50, 50), 'attach':'center'}) bs.animate(l, 'opacity', {10:0, 1000:1.0}) self.lights.append(l) self.lights[0].color = (0.2, 0, 0) self.lights[1].color = (0.2, 0, 0) self.lights[2].color = (0.2, 0.05, 0) self.lights[3].color = (0.0, 0.3, 0) self.cases = {1: self._doLight1, 2: self._doLight2, 3: self._doLight3, 4: self._doLight4} self.incTimer = None self.incTime = incTime def start(self): self.incTimer = bs.Timer(self.incTime, bs.WeakCall(self.increment), timeType="game", repeat=True) def _doLight1(self): self.lights[0].color = (1.0, 0, 0) bs.playSound(self._beep1Sound) def _doLight2(self): self.lights[1].color = (1.0, 0, 0) bs.playSound(self._beep1Sound) def _doLight3(self): self.lights[2].color = (1.0, 0.3, 0) bs.playSound(self._beep1Sound) def _doLight4(self): self.lights[3].color = (0.0, 1.0, 0) bs.playSound(self._beep2Sound) for l in self.lights: bs.animate(l, 'opacity', {0: 1.0, 1000: 0.0}) bs.gameTimer(1000, l.delete) self.incTimer = None self.onFinish() del self def onFinish(self): pass def onIncrement(self): pass def increment(self): self.pos += 1 if self.pos in self.cases: self.cases[self.pos]() self.onIncrement() class JumpSpaz(bs.PlayerSpaz): def onMove(self,x,y): pass def onMoveLeftRight(self,value): pass def onMoveUpDown(self,value): pass def onPunchPress(self): self.getActivity().setEndHeight(self) def onJumpPress(self): self.getActivity().setStartHeight(self) bs.PlayerSpaz.onJumpPress(self) class JumpingContest(bs.TeamGameActivity): @classmethod def getName(cls): return "Jumping Contest" @classmethod def getDescription(cls, sessionType): return "Jump as high as you can." @classmethod def getScoreInfo(cls): return{'scoreType':'points'} @classmethod def getSettings(cls, sessionType): return [("Epic Mode", {'default': False})] @classmethod def getSupportedMaps(cls, sessionType): listy = bs.getMapsSupportingPlayType('melee') listy.remove("Happy Thoughts") return listy @classmethod def supportsSessionType(cls, sessionType): return True if issubclass(sessionType, bs.FreeForAllSession) or issubclass(sessionType, bs.TeamsSession) else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self.called = False if self.settings['Epic Mode']: self._isSlowMotion = True self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) self._scoredis = bs.ScoreBoard() self.timer = bs.OnScreenCountdown(30,self.endGame) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self,music='FlagCatcher') def getInstanceScoreBoardDescription(self): return ('Punch to lock in your score') def onBegin(self): bs.TeamGameActivity.onBegin(self) s = self.settings bs.gameTimer(3500, bs.Call(self.doRaceTimer)) for team in self.teams: team.gameData['score'] = 0 self.updateScore() def onPlayerJoin(self, player): if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) self.checkEnd() return else: self.spawnPlayerSpaz(player) def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None): name = player.getName() color = player.color highlight = player.highlight lightColor = bsUtils.getNormalizedColor(color) displayColor = bs.getSafeColor(color,targetIntensity=0.75) position = self.getMap().getFFAStartPosition(self.players) spaz = JumpSpaz(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360))) def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) m.spaz.getPlayer().actor.disconnectControlsFromPlayer() m.spaz.getPlayer().sessionData['score'] = 0 def updateScore(self): for team in self.teams: self._scoredis.setTeamValue(team,round(team.gameData['score'],2)) def startJump(self): for player in self.players: player.actor.connectControlsToPlayer(enableBomb=False, enablePunch=True, enablePickUp=False, enableRun=False, enableFly = False) player.sessionData['jumped'] = False self.timer.start() self.backupTimer = bs.gameTimer(30000,self.backupEnd) def doRaceTimer(self): self.raceTimer = RaceTimer() bs.gameTimer(1000, bs.Call(self.raceTimer.start)) self.raceTimer.onFinish = bs.WeakCall(self.startJump) def setStartHeight(self, player): player = player.getPlayer() player.sessionData['height'] = player.actor.node.positionCenter[1] player.sessionData['jumped'] = True def setEndHeight(self,player): player = player.getPlayer() if not player.sessionData['jumped']: return player.sessionData['score'] = (player.actor.node.positionCenter[1] - player.sessionData['height']) * 10 if player.sessionData['score'] > player.getTeam().gameData['score']: player.getTeam().gameData['score'] = player.sessionData['score'] self.updateScore() def backupEnd(self): if not self.called: self.endGame() def endGame(self): self.called = True results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team, round(team.gameData['score'],2)) self.end(results=results) ================================================ FILE: mods/LandGrab.json ================================================ { "name": "Land Grab", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/LandGrab.py ================================================ import bs import random import math import bsUtils import bsBomb import bsVector import bsSpaz def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [LandGrab] class PlayerSpaz_Grab(bs.PlayerSpaz): def dropBomb(self): """ Tell the spaz to drop one of his bombs, and returns the resulting bomb object. If the spaz has no bombs or is otherwise unable to drop a bomb, returns None. Overridden for Land Grab: -Add condition for mineTimeout, -make it create myMine instead of regular mine -set this spaz's last mine time to current time -Don't decrement LandMineCount. We'll set to 0 when spaz double-punches. """ t = bs.getGameTime() if ((self.landMineCount <= 0 or t-self.lastMine < self.mineTimeout) and self.bombCount <= 0) or self.frozen: return p = self.node.positionForward v = self.node.velocity if self.landMineCount > 0: droppingBomb = False #self.setLandMineCount(self.landMineCount-1) #Don't decrement mine count. Unlimited mines. if t - self.lastMine < self.mineTimeout: return #Last time we dropped mine was too short ago. Don't drop another one. else: self.lastMine = t self.node.billboardCrossOut = True bs.gameTimer(self.mineTimeout,bs.WeakCall(self.unCrossBillboard)) bomb = myMine(pos=(p[0],p[1] - 0.0,p[2]), vel=(v[0],v[1],v[2]), bRad=self.blastRadius, sPlay=self.sourcePlayer, own=self.node).autoRetain() self.getPlayer().gameData['mines'].append(bomb) elif self.dropEggs: if len(self.getPlayer().gameData['bots']) > 0 : return #Only allow one snowman at a time. droppingBomb = True bomb = Egg(position=(p[0],p[1] - 0.0,p[2]), sourcePlayer=self.sourcePlayer,owner=self.node).autoRetain() else: droppingBomb = True bombType = self.bombType bomb = bs.Bomb(position=(p[0],p[1] - 0.0,p[2]), velocity=(v[0],v[1],v[2]), bombType=bombType, blastRadius=self.blastRadius, sourcePlayer=self.sourcePlayer, owner=self.node).autoRetain() if droppingBomb: self.bombCount -= 1 bomb.node.addDeathAction(bs.WeakCall(self.handleMessage,bsSpaz._BombDiedMessage())) if not self.eggsHatch: bomb.hatch = False else: bomb.hatch = True self._pickUp(bomb.node) for c in self._droppedBombCallbacks: c(self,bomb) return bomb def unCrossBillboard(self): if self.node.exists(): self.node.billboardCrossOut = False def onPunchPress(self): """ Called to 'press punch' on this spaz; used for player or AI connections. Override for land grab: catch double-punch to switch bombs! """ if not self.node.exists() or self.frozen or self.node.knockout > 0.0: return if self.punchCallback is not None: self.punchCallback(self) t = bs.getGameTime() self._punchedNodes = set() # reset this.. ########This catches punches and switches between bombs and mines #if t - self.lastPunchTime < 500: if self.landMineCount < 1: self.landMineCount = 1 bs.animate(self.node,"billboardOpacity",{0:0.0,100:1.0,400:1.0}) else: self.landMineCount = 0 bs.animate(self.node,"billboardOpacity",{0:1.0,400:0.0}) if t - self.lastPunchTime > self._punchCooldown: self.lastPunchTime = t self.node.punchPressed = True if not self.node.holdNode.exists(): bs.gameTimer(100,bs.WeakCall(self._safePlaySound,self.getFactory().swishSound,0.8)) def handleMessage(self, m): #print m.sourcePlayer if isinstance(m, bs.HitMessage): #print m.sourcePlayer.getName() if not self.node.exists(): return True if m.sourcePlayer != self.getPlayer(): return True else: super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) class myMine(bs.Bomb): #reason for the mine class is so we can intercept messages. def __init__(self,pos,vel,bRad,sPlay,own): bs.Bomb.__init__(self,position=pos,velocity=vel,bombType='landMine',blastRadius=bRad,sourcePlayer=sPlay,owner=own) self.isHome = False self.died = False self.activated = False self.defRad = self.getActivity().claimRad self.rad = 0.0# Will set to self.getActivity().settings['Claim Size'] when arming #Don't do this until mine arms self.zone = None fm = bs.getSharedObject('footingMaterial') materials = getattr(self.node,'materials') if not fm in materials: setattr(self.node,'materials',materials + (fm,)) def handleMessage(self,m): if isinstance(m,bsBomb.ArmMessage): self.arm()#This is all the main bs.Bomb does. All below is extra self.activateArea() elif isinstance(m, bs.HitMessage): #print m.hitType, m.hitSubType if self.isHome: return True if m.sourcePlayer == self.sourcePlayer: return True #I think this should stop mines from exploding due to self activity or chain reactions?. if not self.activated: return True else: super(self.__class__, self).handleMessage(m) elif isinstance(m,bsBomb.ImpactMessage): if self.isHome: return True #Never explode the home bomb. super(self.__class__, self).handleMessage(m) elif isinstance(m,bs.DieMessage): if self.isHome: return True #Home never dies (even if player leaves, I guess...) if self.exists() and not self.died: self.died = True self.rad = 0.0 if self.zone.exists(): bs.animateArray(self.zone,'size',1,{0:[2*self.rad],1:[0]}) self.zone = None super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) def activateArea(self): mineOK = False if self.exists(): r = self.defRad fudge = self.getActivity().minOverlap #This is the minimum overlap to join owner's territory (not used to check enemy overlap) p1 = self.node.position self.node.maxSpeed = 0.0 #We don't want mines moving around. They could leave their zone. self.damping = 100 #First, confirm that this mine "touches" owner's mines if self.sourcePlayer.exists(): for m in self.sourcePlayer.gameData['mines']: if m.exists() and not m.died: if m.rad != 0: #Don't check un-activated mines p2 = m.node.position diff = (bs.Vector(p1[0]-p2[0],0.0,p1[2]-p2[2])) dist = (diff.length()) 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. mineOK = True #mine adjoins owner's territory. Will set to false if it also adjoin's enemy though. break #Get out of the loop takeovers = [] if mineOK: for p in self.getActivity().players: if not p is self.sourcePlayer: if p.exists(): for m in p.gameData['mines']: if m.rad != 0.0: #Don't check un-activated mines p2 = m.node.position diff = (bs.Vector(p1[0]-p2[0],0.0,p1[2]-p2[2])) dist = (diff.length()) 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. mineOK = False takeovers = [] break #If we made it to here and mineOK is true, we can activate. Otherwise, we'll flash red and die. self.zone = bs.newNode('locator',attrs={'shape':'circle','position':self.node.position,'color':self.sourcePlayer.color,'opacity':0.5,'drawBeauty':False,'additive':True}) 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 if mineOK or self.isHome: self.activated = True self.rad = r #Immediately set this mine's radius else: #mine was not OK keys = {0:(1,0,0),49:(1,0,0),50:(1,1,1),100:(0,1,0)} bs.animateArray(self.zone,'color',3,keys,loop=True) bs.gameTimer(800, bs.WeakCall(self.handleMessage, bs.DieMessage()), repeat=False) #Takeovers didn't work so well. Very confusing. #if len(takeovers) > 0: # #Flash it red and kill it # for m in takeovers: # if m.exists(): # if not m._exploded: # if not m.died: # keys = {0:(1,0,0),49:(1,0,0),50:(1,1,1),100:(0,1,0)} # if m.zone.exists(): # bs.animateArray(m.zone,'color',3,keys,loop=True) # bs.gameTimer(800, bs.WeakCall(m.handleMessage, bs.DieMessage()), repeat=False) def _handleHit(self,m): #This one is overloaded to prevent chaining of explosions isPunch = (m.srcNode.exists() and m.srcNode.getNodeType() == 'spaz') # normal bombs are triggered by non-punch impacts.. impact-bombs by all impacts if not self._exploded and not isPunch or self.bombType in ['impact','landMine']: # also lets change the owner of the bomb to whoever is setting us off.. # (this way points for big chain reactions go to the person causing them) if m.sourcePlayer not in [None]: #self.sourcePlayer = m.sourcePlayer # also inherit the hit type (if a landmine sets off by a bomb, the credit should go to the mine) # the exception is TNT. TNT always gets credit. #if self.bombType != 'tnt': # self.hitType = m.hitType # self.hitSubType = m.hitSubType pass bs.gameTimer(100+int(random.random()*100),bs.WeakCall(self.handleMessage,bsBomb.ExplodeMessage())) self.node.handleMessage("impulse",m.pos[0],m.pos[1],m.pos[2], m.velocity[0],m.velocity[1],m.velocity[2], m.magnitude,m.velocityMagnitude,m.radius,0,m.velocity[0],m.velocity[1],m.velocity[2]) if m.srcNode.exists(): pass #print 'FIXME HANDLE KICKBACK ON BOMB IMPACT' # bs.nodeMessage(m.srcNode,"impulse",m.srcBody,m.pos[0],m.pos[1],m.pos[2], # -0.5*m.force[0],-0.75*m.force[1],-0.5*m.force[2]) def _handleImpact(self,m): #This is overridden so that we can keep from exploding due to own player's activity. node,body = bs.getCollisionInfo("opposingNode","opposingBody") # if we're an impact bomb and we came from this node, don't explode... # alternately if we're hitting another impact-bomb from the same source, don't explode... try: nodeDelegate = node.getDelegate() #This could be a bomb or a spaz (or none) except Exception: nodeDelegate = None if node is not None and node.exists(): if isinstance(nodeDelegate, PlayerSpaz_Grab): if nodeDelegate.getPlayer() is self.sourcePlayer: #print("Hit by own self, don't blow") return True 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): #print("Hit by owr own bomb") return else: #print 'exploded handling impact' self.handleMessage(bsBomb.ExplodeMessage()) class Egg(bs.Actor): def __init__(self, position=(0,1,0), sourcePlayer=None, owner=None): bs.Actor.__init__(self) activity = self.getActivity() # spawn just above the provided point self._spawnPos = (position[0], position[1]+1.0, position[2]) #This line was replaced by 'color' belwo: 'colorTexture': bsBomb.BombFactory().impactTex, self.node = bs.newNode("prop", attrs={'model': activity._ballModel, 'body':'sphere', 'colorTexture': bs.getTexture("frostyColor"), 'reflection':'soft', 'modelScale':2.0, 'bodyScale':2.0, 'density':0.08, 'reflectionScale':[0.15], 'shadowSize': 0.6, 'position':self._spawnPos, 'materials': [bs.getSharedObject('objectMaterial'),activity._bombMat] }, delegate=self) self.sourcePlayer = sourcePlayer self.owner = owner def handleMessage(self,m): if isinstance(m,bs.DieMessage): self.node.delete() elif isinstance(m,bs.DroppedMessage): self._handleDropped(m) elif isinstance(m,bs.OutOfBoundsMessage): self.handleMessage(bs.DieMessage()) elif isinstance(m,bs.HitMessage): self.node.handleMessage("impulse",m.pos[0],m.pos[1],m.pos[2], m.velocity[0],m.velocity[1],m.velocity[2], 1.0*m.magnitude,1.0*m.velocityMagnitude,m.radius,0, m.forceDirection[0],m.forceDirection[1],m.forceDirection[2]) else: bs.Actor.handleMessage(self,m) def _handleDropped(self,m): if self.exists(): bs.gameTimer(int(self.getActivity().settings['Egg Lifetime']*1000),self._disappear) def _disappear(self): if self.node.exists(): scl = self.node.modelScale bsUtils.animate(self.node,"modelScale",{0:scl*1.0, 300:scl*0.5, 500:0.0}) self.maxSpeed = 0 if self.hatch and self.sourcePlayer.exists(): if len(self.sourcePlayer.gameData['bots']) < 3: self.materials = [] p = self.node.position #self.getActivity()._bots.spawnBot(ToughGuyFrostBot,pos=(p[0],p[1]-0.8,p[2]),spawnTime=0, onSpawnCall=self.setupFrosty) self.sourcePlayer.gameData['bset'].spawnBot(ToughGuyFrostBot,pos=(p[0],p[1]-0.8,p[2]),spawnTime=0, onSpawnCall=self.setupFrosty) bs.gameTimer(550,bs.WeakCall(self.handleMessage,bs.DieMessage())) def setupFrosty(self,spaz): spaz.sourcePlayer = self.sourcePlayer spaz.sourcePlayer.gameData['bots'].append(spaz) bs.gameTimer(5000,bs.WeakCall(spaz.handleMessage,bs.DieMessage())) #Kill spaz after 5 seconds #bsUtils.animate(spaz.node, "modelScale",{0:0.1, 500:0.3, 800:1.2, 1000:1.0}) class zBotSet(bs.BotSet): #the botset is overloaded to prevent adding players to the bots' targets if they are zombies too. def startMoving(self): #here we overload the default startMoving, which normally calls _update. #self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self._update),repeat=True) self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self.zUpdate),repeat=True) def zUpdate(self): # update one of our bot lists each time through.. # first off, remove dead bots from the list # (we check exists() here instead of dead.. we want to keep them around even if they're just a corpse) #####This is overloaded from bsSpaz to walk over other players' mines, but not source player. try: botList = self._botLists[self._botUpdateList] = [b for b in self._botLists[self._botUpdateList] if b.exists()] except Exception: bs.printException("error updating bot list: "+str(self._botLists[self._botUpdateList])) self._botUpdateList = (self._botUpdateList+1)%self._botListCount # update our list of player points for the bots to use playerPts = [] for player in bs.getActivity().players: try: if player.exists(): if not player is self.sourcePlayer: #If the player has lives, add to attack points for m in player.gameData['mines']: if not m.isHome and m.exists(): playerPts.append((bs.Vector(*m.node.position), bs.Vector(0,0,0))) except Exception: bs.printException('error on bot-set _update') for b in botList: b._setPlayerPts(playerPts) b._updateAI() class ToughGuyFrostBot(bsSpaz.SpazBot): """ category: Bot Classes A manly bot who walks and punches things. """ character = 'Frosty' color = (1,1,1) highlight = (1,1,1) punchiness = 0.0 chargeDistMax = 9999.0 chargeSpeedMin = 1.0 chargeSpeedMax = 1.0 throwDistMin = 9999 throwDistMax = 9999 def handleMessage(self,m): if isinstance(m, bs.PickedUpMessage): self.handleMessage(bs.DieMessage()) super(self.__class__, self).handleMessage(m) class LandGrab(bs.TeamGameActivity): @classmethod def getName(cls): return 'Land Grab' @classmethod def getScoreInfo(cls): return {'scoreName':'score', 'scoreType':'points', 'noneIsWinner':False, 'lowerIsBetter':False} @classmethod def supportsSessionType(cls, sessionType): return True if issubclass(sessionType,bs.FreeForAllSession) else False @classmethod def getDescription(cls,sessionType): return 'Grow your territory' @classmethod def getSupportedMaps(cls,sessionType): return ['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Crag Castle', 'Big G', 'Football Stadium'] @classmethod def getSettings(cls,sessionType): return [("Claim Size",{'minValue':2,'default':5,'increment':1}), ("Min Sec btw Claims",{'minValue':1,'default':3,'increment':1}), ("Eggs Not Bombs",{'default':True}), ("Snowman Eggs",{'default':True}), ("Egg Lifetime",{'minValue':0.5,'default':2.0,'increment':0.5}), ("Time Limit",{'choices':[('30 Seconds',30),('1 Minute',60), ('90 Seconds',90),('2 Minutes',120), ('3 Minutes',180),('5 Minutes',300)],'default':60}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Epic Mode",{'default':False})] def __init__(self,settings): bs.TeamGameActivity.__init__(self, settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die (since its meaningful in this game) self.announcePlayerDeaths = True self._scoreBoard = bs.ScoreBoard() #self._lastPlayerDeathTime = None 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 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 self.updateRate = 200 #update the mine radii etc every this many milliseconds #This game's score calculation is very processor intensive. #Score only updated 2x per second during game, at lower resolution self.scoreUpdateRate = 1000 self.inGameScoreRes = 40 self.finalScoreRes = 300 self._eggModel = bs.getModel('egg') try: myFactory = self._sharedSpazFactory except Exception: myFactory = self._sharedSpazFactory = bsSpaz.SpazFactory() m=myFactory._getMedia('Frosty') self._ballModel = m['pelvisModel'] self._bombMat = bsBomb.BombFactory().bombMaterial self._mineIconTex=bs.Powerup.getFactory().texLandMines def getInstanceDescription(self): return ('Control territory with mines') def getInstanceScoreBoardDescription(self): return ('Control the most territory with mines\nDouble punch to switch between mines and bombs\n') def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) bs.gameTimer(self.scoreUpdateRate, bs.WeakCall(self._updateScoreBoard), repeat=True) bs.gameTimer(1000, bs.WeakCall(self.startUpdating), repeat=False)#Delay to allow for home mine to spawn #self._bots = bs.BotSet() # check for immediate end (if we've only got 1 player, etc) #bs.gameTimer(5000, self._checkEndGame) def onTeamJoin(self,team): team.gameData['spawnOrder'] = [] team.gameData['score'] = 0 def onPlayerJoin(self, player): # don't allow joining after we start # (would enable leave/rejoin tomfoolery) player.gameData['mines'] = [] if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) # for score purposes, mark them as having died right as the game started #player.gameData['deathTime'] = self._timer.getStartTime() return player.gameData['home'] = None player.gameData['bots'] = [] player.gameData['bset'] = zBotSet() player.gameData['bset'].sourcePlayer = player self.spawnPlayer(player) def onPlayerLeave(self, player): # augment default behavior... for m in player.gameData['mines']: m.handleMessage(bs.DieMessage()) player.gameData['mines'] = [] bs.TeamGameActivity.onPlayerLeave(self, player) # a departing player may trigger game-over self._checkEndGame() def startUpdating(self): bs.gameTimer(self.updateRate, bs.WeakCall(self.mineUpdate), repeat=True) def _updateScoreBoard(self): for team in self.teams: team.gameData['score'] = self.areaCalc(team,self.inGameScoreRes) self._scoreBoard.setTeamValue(team,team.gameData['score']) def mineUpdate(self): for player in self.players: #Need to purge mines, whether or not player is living for m in player.gameData['mines']: if not m.exists(): player.gameData['mines'].remove(m) if not player.actor is None: if player.actor.isAlive(): pSafe = False p1 = player.actor.node.position for teamP in player.getTeam().players: for m in teamP.gameData['mines']: if m.exists(): if not m._exploded: p2 = m.node.position diff = (bs.Vector(p1[0]-p2[0],0.0,p1[2]-p2[2])) dist = (diff.length()) if dist < m.rad: pSafe = True break if not pSafe: #print player.getName(), "died with mines:", len(player.gameData['mines']) player.actor.handleMessage(bs.DieMessage()) def endGame(self): results = bs.TeamGameResults() for t in self.teams: results.setTeamScore(t,t.gameData['score']) self.end(results=results,announceDelay=800) def _flashPlayer(self,player,scale): pos = player.actor.node.position light = bs.newNode('light', attrs={'position':pos, 'color':(1,1,0), 'heightAttenuated':False, 'radius':0.4}) bs.gameTimer(500,light.delete) bs.animate(light,'intensity',{0:0,100:1.0*scale,500:0}) def handleMessage(self,m): if isinstance(m, bs.SpazBotDeathMessage): if m.badGuy.sourcePlayer.exists(): m.badGuy.sourcePlayer.gameData['bots'].remove(m.badGuy) elif isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # (augment standard behavior) self.respawnPlayer(m.spaz.getPlayer()) #deathTime = bs.getGameTime() # record the player's moment of death #m.spaz.getPlayer().gameData['deathTime'] = deathTime # in co-op mode, end the game the instant everyone dies (more accurate looking) # in teams/ffa, allow a one-second fudge-factor so we can get more draws #if isinstance(self.getSession(),bs.CoopSession): # teams will still show up if we check now.. check in the next cycle # bs.pushCall(self._checkEndGame) # self._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock.. #else: #bs.gameTimer(1000, self._checkEndGame) else: # default handler: bs.TeamGameActivity.handleMessage(self,m) def _checkEndGame(self): livingTeamCount = 0 for team in self.teams: for player in team.players: if player.isAlive(): livingTeamCount += 1 break # in co-op, we go till everyone is dead.. otherwise we go until one team remains if isinstance(self.getSession(),bs.CoopSession): if livingTeamCount <= 0: self.endGame() else: if livingTeamCount <= 1: self.endGame() def spawnPlayer(self, player): #Overloaded for this game to respawn at home instead of random FFA spots if not player.exists(): bs.printError('spawnPlayer() called for nonexistant player') return if player.gameData['home'] is None: pos = self.getMap().getFFAStartPosition(self.players) bomb = myMine(pos, (0.0,0.0,0.0), 0.0, player, None).autoRetain() bomb.isHome = True bomb.handleMessage(bsBomb.ArmMessage()) position = [pos[0],pos[1]+0.3,pos[2]] player.gameData['home'] = position player.gameData['mines'].append(bomb) else: position = player.gameData['home'] spaz = self.spawnPlayerSpaz(player, position) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up spaz.connectControlsToPlayer(enablePunch=True, enableBomb=True, enablePickUp=True) #Wire up the spaz with mines spaz.landMineCount = 1 spaz.node.billboardTexture = self._mineIconTex bs.animate(spaz.node,"billboardOpacity",{0:0.0,100:1.0,400:1.0}) t = bs.getGameTime() if t - spaz.lastMine < spaz.mineTimeout: spaz.node.billboardCrossOut = True bs.gameTimer((spaz.mineTimeout-t+spaz.lastMine),bs.WeakCall(spaz.unCrossBillboard)) spaz.dropEggs = self.settings['Eggs Not Bombs'] spaz.eggsHatch = self.settings['Snowman Eggs'] # also lets have them make some noise when they die.. spaz.playBigDeathSound = True def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None): """ Create and wire up a bs.PlayerSpaz for the provide bs.Player. """ #position = self.getMap().getFFAStartPosition(self.players) name = player.getName() color = player.color highlight = player.highlight lightColor = bsUtils.getNormalizedColor(color) displayColor = bs.getSafeColor(color,targetIntensity=0.75) spaz = PlayerSpaz_Grab(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() ###These special attributes are for Land Grab: spaz.lastMine = 0 spaz.mineTimeout = self.settings['Min Sec btw Claims'] * 1000 self.scoreSet.playerGotNewSpaz(player,spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360))) t = bs.getGameTime() bs.playSound(self._spawnSound,1,position=spaz.node.position) light = bs.newNode('light',attrs={'color':lightColor}) spaz.node.connectAttr('position',light,'position') bsUtils.animate(light,'intensity',{0:0,250:1,500:0}) bs.gameTimer(500,light.delete) return spaz def getRandomPowerupPoint(self): #So far, randomized points only figured out for mostly rectangular maps. #Boxes will still fall through holes, but shouldn't be terrible problem (hopefully) #If you add stuff here, need to add to "supported maps" above. #['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium'] myMap = self.getMap().getName() #print(myMap) if myMap == 'Doom Shroom': while True: x = random.uniform(-1.0,1.0) y = random.uniform(-1.0,1.0) if x*x+y*y < 1.0: break return ((8.0*x,2.5,-3.5+5.0*y)) elif myMap == 'Rampage': x = random.uniform(-6.0,7.0) y = random.uniform(-6.0,-2.5) return ((x, 5.2, y)) elif myMap == 'Hockey Stadium': x = random.uniform(-11.5,11.5) y = random.uniform(-4.5,4.5) return ((x, 0.2, y)) elif myMap == 'Courtyard': x = random.uniform(-4.3,4.3) y = random.uniform(-4.4,0.3) return ((x, 3.0, y)) elif myMap == 'Crag Castle': x = random.uniform(-6.7,8.0) y = random.uniform(-6.0,0.0) return ((x, 10.0, y)) elif myMap == 'Big G': x = random.uniform(-8.7,8.0) y = random.uniform(-7.5,6.5) return ((x, 3.5, y)) elif myMap == 'Football Stadium': x = random.uniform(-12.5,12.5) y = random.uniform(-5.0,5.5) return ((x, 0.32, y)) else: x = random.uniform(-5.0,5.0) y = random.uniform(-6.0,0.0) return ((x, 8.0, y)) def areaCalc(self,team,res): ##This routine calculates (well, approximates) the area covered by a team ##and returns their score. the "res" argument is the resolution. Higher res, ##better approximation. ##Most of this code was stolen from rosettacode.org/wiki/Total_circles_area circles = () for p in team.players: for m in p.gameData['mines']: if m.exists(): if m.rad != 0: if not m._exploded: circles += ((m.node.position[0],m.node.position[2], m.rad),) # compute the bounding box of the circles if len(circles) == 0: return 0 x_min = min(c[0] - c[2] for c in circles) x_max = max(c[0] + c[2] for c in circles) y_min = min(c[1] - c[2] for c in circles) y_max = max(c[1] + c[2] for c in circles) box_side = res dx = (x_max - x_min) / box_side dy = (y_max - y_min) / box_side count = 0 for r in xrange(box_side): y = y_min + r * dy for c in xrange(box_side): x = x_min + c * dx if any((x-circle[0])**2 + (y-circle[1])**2 <= (circle[2] ** 2) for circle in circles): count += 1 return int(count * dx * dy *10) def endGame(self): if self.hasEnded(): return #sorryTxt = bsUtils.Text('Calculating final scores!...') for team in self.teams: team.gameData['score'] = str(round(self.areaCalc(team,self.finalScoreRes),2)) #sorryTxt.handleMessage(bs.DieMessage()) #print 'calc time:', (bs.getRealTime() - t) bs.gameTimer(300, bs.Call(self.waitForScores)) def waitForScores(self): results = bs.TeamGameResults() self._vsText = None # kill our 'vs' if its there for team in self.teams: results.setTeamScore(team, team.gameData['score']) self.end(results=results) ================================================ FILE: mods/Paint.py ================================================ #Canvas import bs import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [Paint] def bsGetLevels(): return [bs.Level('Paint', displayName='${GAME}', gameType=Paint, settings={}, previewTexName='courtyardPreview')] class Dot(bs.Actor): def __init__(self, position=(0,0,0), color=(0,0,0), radius=(.5)): bs.Actor.__init__(self) self._r1 = radius if radius < 0: self._r1 = 0 self.position = position self.color = color n1 = bs.newNode('locator',attrs={'shape':'circle','position':position, 'color':self.color,'opacity':1, 'drawBeauty':True,'additive':True}) bs.animateArray(n1,'size',1,{0:[0.0],200:[self._r1*2.0]}) self._node = [n1] class Artist(bs.PlayerSpaz): def __init__(self, color=(1,1,1), highlight=(0.5,0.5,0.5), character="Spaz", sourcePlayer=None, startInvincible=True, canAcceptPowerups=True, powerupsExpire=False, demoMode=False): self._player = sourcePlayer self.mode = 'Draw' self.dotRadius = .5 self.red = True self.blue = True self.green = True self.value = 1 self.color = [1.0,0.0,0.0] bs.PlayerSpaz.__init__(self, color, highlight, character, sourcePlayer, powerupsExpire) def onBombPress(self): if self.mode == 'Draw': self.dotRadius += .1 self.setScoreText("Radius: " + str(self.dotRadius), (1,1,1)) elif self.mode == "Color": if self.color[0] >= 1: if self.color[2] == 0: self.color[1] += .1 else: self.color[2] -= .1 if self.color[1] >= 1: if self.color[0] == 0: self.color[2] += .1 else: self.color[0] -= .1 if self.color[2] >= 1: if self.color[1] == 0: self.color[0] += .1 else: self.color[1] -= .1 for i in range(len(self.color)): if self.color[i] < 0: self.color[i] = 0 if self.color[i] > 1: self.color[i] = 1 color = (self.color[0]*self.value, self.color[1]*self.value, self.color[2]*self.value) self.setScoreText("COLOR", color) def onPunchPress(self): if self.mode == 'Draw': self.dotRadius -= .1 if self.dotRadius < .05: self.dotRadius = 0 self.setScoreText("Radius: " + str(self.dotRadius), (1,1,1)) elif self.mode == "Color": if self.color[0] >= 1: if self.color[1] == 0: self.color[2] += .1 else: self.color[1] -= .1 if self.color[1] >= 1: if self.color[2] == 0: self.color[0] += .1 else: self.color[2] -= .1 if self.color[2] >= 1: if self.color[0] == 0: self.color[1] += .1 else: self.color[0] -= .1 for i in range(len(self.color)): if self.color[i] < 0: self.color[i] = 0 if self.color[i] > 1: self.color[i] = 1 color = (self.color[0]*self.value, self.color[1]*self.value, self.color[2]*self.value) self.setScoreText("COLOR", color) def onJumpPress(self): if self.mode == 'Draw': color = (self.color[0]*self.value, self.color[1]*self.value, self.color[2]*self.value) pos = (self.node.positionCenter[0], self.node.positionCenter[1]-2, self.node.positionCenter[2]) dot = Dot(position=pos, color = color, radius=self.dotRadius) elif self.mode == "Color": self.value += .1 if self.value > 1 : self.value = 0 self.setScoreText("Value: " + str(round(self.value,2)), (self.color[0]*self.value, self.color[1]*self.value, self.color[2]*self.value)) def onPickUpPress(self): if self.mode == 'Draw': self.mode = 'Color' elif self.mode == "Color": self.mode = "Draw" self.setScoreText(self.mode + " Mode", (1,1,1)) class Paint(bs.CoopGameActivity): @classmethod def getName(cls): return 'Paint' @classmethod def getScoreInfo(cls): return {'scoreType':'points'} @classmethod def getDescription(cls,sessionType): return 'Create a masterpiece.' @classmethod def getSupportedMaps(cls,sessionType): return ['Doom Shroom'] @classmethod def supportsSessionType(cls,sessionType): return True if issubclass(sessionType,bs.CoopSession) else False def __init__(self, settings): bs.CoopGameActivity.__init__(self, settings) self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) def onTransitionIn(self): bs.CoopGameActivity.onTransitionIn(self,music='ForwardMarch') def onBegin(self): bs.CoopGameActivity.onBegin(self) def spawnPlayerSpaz(self,player,position=(0,5,-3),angle=None): name = player.getName() color = player.color highlight = player.highlight spaz = Artist(color=color, highlight=highlight, character=player.character, sourcePlayer=player) player.setActor(spaz) player.actor.connectControlsToPlayer() spaz.handleMessage(bs.StandMessage((0,3,0),90)) ================================================ FILE: mods/Portal.json ================================================ { "name": "Portal powerup", "author": "The Great", "category": "libraries" } ================================================ FILE: mods/Portal.py ================================================ import bs import random import bsUtils import copy import types # Following are necessary variables for portal maxportals = 3 currentnum = 0 lastpos = [(0,0,0), (1, 0, 2)] # initial values for test defi = [(0, 1, 2), (1, 0, 2)] # initial values for test class Portal(bs.Actor): def __init__(self,position1 = (0,1,0),color = (random.random(),random.random(),random.random()),r = 1.0,activity = None): bs.Actor.__init__(self) self.radius = r if position1 is None: self.position1 = self.getRandomPosition(activity) else : self.position1 = position1 self.position2 = self.getRandomPosition(activity) self.portal1Material = bs.Material() self.portal1Material.addActions(conditions=(('theyHaveMaterial', bs.getSharedObject('playerMaterial'))),actions=(("modifyPartCollision","collide",True), ("modifyPartCollision","physical",False), ("call","atConnect", self.Portal1))) self.portal2Material = bs.Material() self.portal2Material.addActions(conditions=(('theyHaveMaterial', bs.getSharedObject('playerMaterial'))),actions=(("modifyPartCollision","collide",True), ("modifyPartCollision","physical",False), ("call","atConnect", self.Portal2))) # uncomment the following lines to teleport objects also # self.portal1Material.addActions(conditions=(('theyHaveMaterial', bs.getSharedObject('objectMaterial')),'and',('theyDontHaveMaterial', bs.getSharedObject('playerMaterial'))),actions=(("modifyPartCollision","collide",True), # ("modifyPartCollision","physical",False), # ("call","atConnect", self.objPortal1))) # self.portal2Material.addActions(conditions=(('theyHaveMaterial', bs.getSharedObject('objectMaterial')),'and',('theyDontHaveMaterial', bs.getSharedObject('playerMaterial'))),actions=(("modifyPartCollision","collide",True), # ("modifyPartCollision","physical",False), # ("call","atConnect", self.objPortal2))) self.node1 = bs.newNode('region', attrs={'position':(self.position1[0],self.position1[1],self.position1[2]), 'scale':(self.radius,self.radius,self.radius), 'type':'sphere', 'materials':[self.portal1Material]}) self.visualRadius = bs.newNode('shield',attrs={'position':self.position1,'color':color,'radius':0.1}) bsUtils.animate(self.visualRadius,"radius",{0:0,500:self.radius*2}) bsUtils.animateArray(self.node1,"scale",3,{0:(0,0,0),500:(self.radius,self.radius,self.radius)}) self.node2 = bs.newNode('region', attrs={'position':(self.position2[0],self.position2[1],self.position2[2]), 'scale':(self.radius,self.radius,self.radius), 'type':'sphere', 'materials':[self.portal2Material]}) self.visualRadius2 = bs.newNode('shield',attrs={'position':self.position2,'color':color,'radius':0.1}) bsUtils.animate(self.visualRadius2,"radius",{0:0,500:self.radius*2}) bsUtils.animateArray(self.node2,"scale",3,{0:(0,0,0),500:(self.radius,self.radius,self.radius)}) def Portal1(self): node = bs.getCollisionInfo('opposingNode') node.handleMessage(bs.StandMessage(position = self.node2.position)) def Portal2(self): node = bs.getCollisionInfo('opposingNode') node.handleMessage(bs.StandMessage(position = self.node1.position)) def objPortal1(self): node = bs.getCollisionInfo('opposingNode') node.position = self.position2 def objPortal2(self): node = bs.getCollisionInfo('opposingNode') node.position = self.position1 def delete(self): if self.node1.exists() and self.node2.exists(): self.node1.delete() self.node2.delete() self.visualRadius.delete() self.visualRadius2.delete() if self.position1 in lastpos: lastpos.remove(self.position1) defi.remove(self.node2.position) def posn(self, s , act): ru = random.uniform rc = random.choice 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])]) if f in defi or f in lastpos : return self.getRandomPosition(act) else : defi.append(f) return f def getRandomPosition(self, activity): pts = copy.copy(activity.getMap().ffaSpawnPoints) pts2 = activity.getMap().powerupSpawnPoints for i in pts2: pts.append(i) pos = [[999, -999], [999, -999], [999, -999]] for pt in pts: for i in range(3): pos[i][0] = min(pos[i][0], pt[i]) pos[i][1] = max(pos[i][1], pt[i]) # The credit of this random position finder goes to Deva but I did some changes too. ru = random.uniform ps = pos 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) s = (t[0],t[1]-ru(1.0,1.3),t[2]) if s in defi or s in lastpos : return self.posn(s, activity) else : defi.append(s) return s ================================================ FILE: mods/Protection.json ================================================ { "name": "Protection", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/Protection.py ================================================ import bs import random import bsUtils def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [ProtectionGame] class SpazClone(bs.SpazBot): def __init__(self, player): self.character = player.character self.color = player.color self.highlight = player.highlight bs.SpazBot.__init__(self) self.setMovingText(self, player.getName(), player.color) self.sourcePlayer = player def setMovingText(self, theActor, theText, color): m = bs.newNode('math', owner=theActor.node, attrs={'input1': (0, 0.7, 0), 'operation': 'add'}) theActor.node.connectAttr('position', m, 'input2') theActor._movingText = bs.newNode('text', owner=theActor.node, attrs={'text':theText, 'inWorld':True, 'shadow':1.0, 'flatness':1.0, 'color':color, 'scale':0.0, 'hAlign':'center'}) m.connectAttr('output', theActor._movingText, 'position') bs.animate(theActor._movingText, 'scale', {0: 0.0, 1000: 0.01}) class myBots(bs.BotSet): def spawnCBot(self,player,botType,pos,spawnTime=3000,onSpawnCall=None): bsUtils.Spawner(pt=pos, spawnTime=spawnTime, sendSpawnMessage=False, spawnCallback=bs.Call(self._spawnCBot,player,botType,pos,onSpawnCall)) self._spawningCount += 1 def _spawnCBot(self,player,botType,pos,onSpawnCall): spaz = botType(player) bs.playSound(self._spawnSound,position=pos) spaz.node.handleMessage("flash") spaz.node.isAreaOfInterest = 0 spaz.handleMessage(bs.StandMessage(pos,random.uniform(0,360))) self.addBot(spaz) self._spawningCount -= 1 if onSpawnCall is not None: onSpawnCall(spaz) class Icon(bs.Actor): def __init__(self,player,position,scale,showLives=True,showDeath=True, nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0): bs.Actor.__init__(self) self._player = player self._showLives = showLives self._showDeath = showDeath self._nameScale = nameScale self._outlineTex = bs.getTexture('characterIconMask') icon = player.getIcon() self.node = bs.newNode('image', owner=self, attrs={'texture':icon['texture'], 'tintTexture':icon['tintTexture'], 'tintColor':icon['tintColor'], 'vrDepth':400, 'tint2Color':icon['tint2Color'], 'maskTexture':self._outlineTex, 'opacity':1.0, 'absoluteScale':True, 'attach':'bottomCenter'}) self._nameText = bs.newNode('text', owner=self.node, attrs={'text':player.getName(), 'color':bs.getSafeColor(player.getTeam().color), 'hAlign':'center', 'vAlign':'center', 'vrDepth':410, 'maxWidth':nameMaxWidth, 'shadow':shadow, 'flatness':flatness, 'hAttach':'center', 'vAttach':'bottom'}) if self._showLives: self._livesText = bs.newNode('text', owner=self.node, attrs={'text':'x0', 'color':(1,1,0.5), 'hAlign':'left', 'vrDepth':430, 'shadow':1.0, 'flatness':1.0, 'hAttach':'center', 'vAttach':'bottom'}) self.setPositionAndScale(position,scale) def setPositionAndScale(self,position,scale): self.node.position = position self.node.scale = [70.0*scale] self._nameText.position = (position[0],position[1]+scale*52.0) self._nameText.scale = 1.0*scale*self._nameScale if self._showLives: self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0) self._livesText.scale = 1.0*scale def updateForLives(self): if self._player.exists(): lives = self._player.gameData['lives'] else: lives = 0 if self._showLives: if lives > 0: self._livesText.text = 'x'+str(lives-1) else: self._livesText.text = '' if lives == 0: self._nameText.opacity = 0.2 self.node.color = (0.7,0.3,0.3) self.node.opacity = 0.2 def handlePlayerSpawned(self): if not self.node.exists(): return self.node.opacity = 1.0 self.updateForLives() def handlePlayerDied(self): if not self.node.exists(): return if self._showDeath: bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0, 300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2}) lives = self._player.gameData['lives'] if lives == 0: bs.gameTimer(600,self.updateForLives) class ProtectionGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Protection' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'seconds', 'noneIsWinner':True} @classmethod def getDescription(cls,sessionType): return 'Protect your twin.' @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): settings = [("Lives Per Player",{'default':1,'minValue':1,'maxValue':10,'increment':1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Epic Mode",{'default':False})] if issubclass(sessionType,bs.TeamsSession): settings.append(("Solo Mode",{'default':False})) settings.append(("Balance Total Lives",{'default':False})) return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # show messages when players die since it's meaningful here self.announcePlayerDeaths = True try: self._soloMode = settings['Solo Mode'] except Exception: self._soloMode = False self._scoreBoard = bs.ScoreBoard() self._bots = myBots() self.spazClones = [] def getInstanceDescription(self): return 'Last team standing wins.' if isinstance(self.getSession(),bs.TeamsSession) else 'Protect your twin!' def getInstanceScoreBoardDescription(self): return 'last team standing wins' if isinstance(self.getSession(),bs.TeamsSession) else 'Protect your twin!' def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onTeamJoin(self,team): team.gameData['survivalSeconds'] = None team.gameData['spawnOrder'] = [] def onPlayerJoin(self, player): # no longer allowing mid-game joiners here... too easy to exploit if self.hasBegun(): player.gameData['lives'] = 0 player.gameData['icons'] = [] # make sure our team has survival seconds set if they're all dead # (otherwise blocked new ffa players would be considered 'still alive' in score tallying) if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None: player.getTeam().gameData['survivalSeconds'] = 0 bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) return player.gameData['lives'] = self.settings['Lives Per Player'] player.gameData['cloneSpawnTime'] = 0 if self._soloMode: player.gameData['icons'] = [] player.getTeam().gameData['spawnOrder'].append(player) self._updateSoloMode() else: # create our icon and spawn player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)] if player.gameData['lives'] > 0: self.spawnPlayer(player) # dont waste time doing this until begin if self.hasBegun(): self._updateIcons() def _updateSoloMode(self): # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) break def _updateIcons(self): # in free-for-all mode, everyone is just lined up along the bottom if isinstance(self.getSession(),bs.FreeForAllSession): count = len(self.teams) xOffs = 85 x = xOffs*(count-1) * -0.5 for i,team in enumerate(self.teams): if len(team.players) == 1: player = team.players[0] for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs # in teams mode we split up teams else: if self._soloMode: # first off, clear out all icons for player in self.players: player.gameData['icons'] = [] # now for each team, cycle through our available players adding icons for team in self.teams: if team.getID() == 0: x = -60 xOffs = -78 else: x = 60 xOffs = 78 isFirst = True testLives = 1 while True: playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives] if len(playersWithLives) == 0: break for player in playersWithLives: player.gameData['icons'].append(Icon(player, position=(x,(40 if isFirst else 25)), scale=1.0 if isFirst else 0.5, nameMaxWidth=130 if isFirst else 75, nameScale=0.8 if isFirst else 1.0, flatness=0.0 if isFirst else 1.0, shadow=0.5 if isFirst else 1.0, showDeath=True if isFirst else False, showLives=False)) x += xOffs * (0.8 if isFirst else 0.56) isFirst = False testLives += 1 # non-solo mode else: for team in self.teams: if team.getID() == 0: x = -50 xOffs = -85 else: x = 50 xOffs = 85 for player in team.players: for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs def _getSpawnPoint(self,player): # in solo-mode, if there's an existing live player on the map, spawn at whichever # spot is farthest from them (keeps the action spread out) if self._soloMode: livingPlayer = None for team in self.teams: for player in team.players: if player.isAlive(): p = player.actor.node.position livingPlayer = player livingPlayerPos = p break if livingPlayer: playerPos = bs.Vector(*livingPlayerPos) points = [] for team in self.teams: startPos = bs.Vector(*self.getMap().getStartPosition(team.getID())) points.append([(startPos-playerPos).length(),startPos]) points.sort() return points[-1][1] else: return None else: return None def spawnPlayer(self,player): self.spawnPlayerSpaz(player,self._getSpawnPoint(player)) if not self._soloMode: bs.gameTimer(300,bs.Call(self._printLives,player)) # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerSpawned() def spawnPlayerByDummy(self,player): if player.gameData['lives'] > 0: #only try to spawn if player still has lives. Might have died during recheck delay... found = False for bot in self._bots.getLivingBots(): #Look for bot to spawn by if bot.sourcePlayer == player: found = True #print("Found player") self.spawnPlayerSpaz(player,bot.node.position) if not self._soloMode: bs.gameTimer(300,bs.Call(self._printLives,player)) # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerSpawned() if not found: #Didn't find clone. This can happen if player dies btw clone death and clone spawn #print('did not find player.') bs.gameTimer(1000,bs.Call(self.spawnPlayerByDummy, player)) #wait one second and try again for spawn def spawnDummy(self,player, myClone): #playerNode = bs.getActivity()._getPlayerNode(player) spz = self.scoreSet._players[player.getName()].getSpaz() t = bs.getGameTime() if t - player.gameData['cloneSpawnTime'] > self.minLife or player.gameData['cloneSpawnTime']==0: if not spz is None: pos = spz.node.position player.gameData['lastSpawn']= pos else: pos = player.gameData['lastSpawn'] else: player.gameData['lastSpawn'] = player.gameData['safeSpawn'] pos = player.gameData['safeSpawn'] bs.gameTimer(1000,bs.Call(self._bots.spawnCBot, player,myClone,pos=pos,spawnTime=1000, onSpawnCall=self.setSpawnTime)) def setSpawnTime(self,spaz): if spaz.sourcePlayer.exists(): spaz.sourcePlayer.gameData['cloneSpawnTime'] = bs.getGameTime() bs.gameTimer(self.minLife, bs.WeakCall(self.setSafeSpawn, spaz)) else: spaz.handleMessage(bs.DieMessage()) def setSafeSpawn(self, spaz): if spaz.isAlive(): if spaz.sourcePlayer.exists(): spaz.sourcePlayer.gameData['safeSpawn'] = spaz.sourcePlayer.gameData['lastSpawn'] def _printLives(self,player): if not player.exists() or not player.isAlive(): return try: pos = player.actor.node.position except Exception,e: print 'EXC getting player pos in bsElim',e return bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1), offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain() def onPlayerLeave(self,player): bs.TeamGameActivity.onPlayerLeave(self,player) player.gameData['icons'] = None # remove us from spawn-order if self._soloMode: if player in player.getTeam().gameData['spawnOrder']: player.getTeam().gameData['spawnOrder'].remove(player) # update icons in a moment since our team will be gone from the list then bs.gameTimer(0, self._updateIcons) def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops() if self._soloMode: self._vsText = bs.NodeActor(bs.newNode("text", attrs={'position':(0,105), 'hAttach':"center", 'hAlign':'center', 'maxWidth':200, 'shadow':0.5, 'vrDepth':390, 'scale':0.6, 'vAttach':"bottom", 'color':(0.8,0.8,0.3,1.0), 'text':bs.Lstr(resource='vsText')})) # if balance-team-lives is on, add lives to the smaller team until total lives match if (isinstance(self.getSession(),bs.TeamsSession) and self.settings['Balance Total Lives'] and len(self.teams[0].players) > 0 and len(self.teams[1].players) > 0): if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]): lesserTeam = self.teams[0] greaterTeam = self.teams[1] else: lesserTeam = self.teams[1] greaterTeam = self.teams[0] addIndex = 0 while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam): lesserTeam.players[addIndex].gameData['lives'] += 1 addIndex = (addIndex + 1) % len(lesserTeam.players) self._updateIcons() self._bots.finalCelebrate() activity = bs.getActivity() try: myFactory = activity._sharedSpazFactory except Exception: myFactory = activity._sharedSpazFactory = bsSpaz.SpazFactory() # we could check game-over conditions at explicit trigger points, # but lets just do the simple thing and poll it... self.minLife = 1000 #A clone's life must be at least this long for death to count. for player in self.players: self.spawnDummy(player,SpazClone) bs.gameTimer(1000, self._update, repeat=True) def _getTotalTeamLives(self,team): return sum(player.gameData['lives'] for player in team.players) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() #player.gameData['lives'] -= 1 #only drop a life if dummy was kilt if player.gameData['lives'] < 0: bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode)) player.gameData['lives'] = 0 # play big death sound on our last death or for every one in solo mode if self._soloMode or player.gameData['lives'] == 0: bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound) else: # otherwise, in regular mode, respawn.. if not self._soloMode: self.spawnPlayerByDummy(player) # in solo, put ourself at the back of the spawn order if self._soloMode: player.getTeam().gameData['spawnOrder'].remove(player) player.getTeam().gameData['spawnOrder'].append(player) if isinstance(m,bs.SpazBotDeathMessage): if isinstance(m.badGuy, SpazClone): player = m.badGuy.sourcePlayer if player in self.players: t = bs.getGameTime() #only take away a life if clone lifed longer than minimum length if t - player.gameData['cloneSpawnTime'] > self.minLife: player.gameData['lives'] -=1 # if we hit zero lives, we're dead (and our team might be too) if player.gameData['lives'] < 1: # if the whole team is now dead, mark their survival time.. #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players): if self._getTotalTeamLives(player.getTeam()) == 0: player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000 playerNode = bs.getActivity()._getPlayerNode(player) spz = self.scoreSet._players[player.getName()].getSpaz() if not spz is None: spz.handleMessage(bs.DieMessage()) for icon in player.gameData['icons']: icon.handlePlayerDied() else: self.spawnDummy(player, SpazClone) # if we have any icons, update their state for icon in player.gameData['icons']: icon.updateForLives() bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior def _update(self): if self._soloMode: # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) self._updateIcons() break # if we're down to 1 or fewer living teams, start a timer to end the game # (allows the dust to settle and draws to occur if deaths are close enough) if len(self._getLivingTeams()) < 2: self._roundEndTimer = bs.Timer(500,self.endGame) def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)] def endGame(self): if self.hasEnded(): return results = bs.TeamGameResults() self._vsText = None # kill our 'vs' if its there for team in self.teams: results.setTeamScore(team, team.gameData['survivalSeconds']) self.end(results=results) ================================================ FILE: mods/SharksAndMinnows.py ================================================ #SharksAndMinnows import bs import bsUtils import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [SharksAndMinnows] class Minnow(bs.PlayerSpaz): isShark = False nextZone = 2 def handleMessage(self, m): if isinstance(m, bs.PickedUpMessage): self.disconnectControlsFromPlayer() self.node.delete() self.getActivity().sharkify(self.getPlayer()) bs.PlayerSpaz.handleMessage(self, m) class Shark(bs.PlayerSpaz): isShark = True def handleMessage(self, m): if isinstance(m, bs.PickUpMessage): if not m.node.getDelegate().isShark: if self.getPlayer().getTeam() is m.node.getDelegate().getPlayer().getTeam(): points = 5 else: points = 20 self.getPlayer().getTeam().gameData['score'] += points self.getActivity().scoreSet.playerScored(self.getPlayer(),20,screenMessage=False,display=False) self.getActivity().updateScore() bs.PlayerSpaz.handleMessage(self, m) class SharksAndMinnows(bs.TeamGameActivity): @classmethod def getName(cls): return "Sharks and Minnows" @classmethod def getDescription(cls, sessionType): return "Eat or be eaten." @classmethod def getScoreInfo(cls): return{'scoreType':'points'} @classmethod def getSupportedMaps(cls, sessionType): return ['Football Stadium'] @classmethod def supportsSessionType(cls, sessionType): return True if issubclass(sessionType, bs.FreeForAllSession) or issubclass(sessionType, bs.TeamsSession)else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) self._safeZoneMaterial = bs.Material() self._scoredis = bs.ScoreBoard() self._safeZoneMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('playerMaterial')), actions=(("modifyPartCollision","collide",True), ("modifyPartCollision","physical",False), ("call","atConnect",bs.Call(self.handleSafeZoneEnter)))) self.safeZone1 = bs.newNode('region', attrs={'position':(-11,0,0), 'scale': (1.8,1.8,1.8), 'type': 'sphere', 'materials':[self._safeZoneMaterial,bs.getSharedObject('regionMaterial')]}) self.safeZone2 = bs.newNode('region', attrs={'position':(11,0,0), 'scale': (1.8,1.8,1.8), 'type': 'sphere', 'materials':[self._safeZoneMaterial,bs.getSharedObject('regionMaterial')]}) self.zoneLocator1 = bs.newNode('locator',attrs={'shape':'circle','position':(-11,0,0), 'color':(1,1,1),'opacity':1, 'drawBeauty':True,'additive':True}) bs.animateArray(self.zoneLocator1,'size',1,{0:[0.0],200:[1.8*2.0]}) self.zoneLocator2 = bs.newNode('locator',attrs={'shape':'circle','position':(11,0,0), 'color':(1,1,1),'opacity':1, 'drawBeauty':True,'additive':True}) bs.animateArray(self.zoneLocator2,'size',1,{0:[0.0],200:[1.8*2.0]}) def getInstanceScoreBoardDescription(self): return ('Sharks pick up minnows to score') def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self,music='FlagCatcher') def onBegin(self): bs.TeamGameActivity.onBegin(self) for team in self.teams: team.gameData['score'] = 0 teamNum = random.randint(0,len(self.teams)-1) for player in self.players: if player in self.teams[teamNum].players: bs.gameTimer(100, bs.Call(self.setupSharks, player=player)) else: self.spawnPlayerSpaz(player) self.setupStandardPowerupDrops(True) for team in self.teams: self._scoredis.setTeamValue(team,team.gameData['score']) def setupSharks(self, player): self.sharkify(player) def respawnPlayer(self, player): if player.actor.node.getDelegate().isShark: self.sharkify(player) return else: bs.TeamGameActivity.respawnPlayer(self,player) def spawnPlayerSpaz(self,player,position=(-10,1,0),angle=None): try: if player.actor.node.getDelegate().isShark: self.sharkify(player) return except: pass name = player.getName() color = player.color highlight = player.highlight spaz = Minnow(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) pos = [0,1,0] pos[0] = position[0] + random.random() * 2 * (-1)**random.randint(0,1) pos[2] = position[2] + random.random() * 2 * (-1)**random.randint(0,1) player.actor.connectControlsToPlayer(enableBomb=True, enableRun = True, enableJump = True, enablePickUp = False, enablePunch=True) spaz.handleMessage(bs.StandMessage(pos,90)) def sharkify(self, player): name = player.getName() color = (0,0,0) highlight = player.getTeam().color spaz = Shark(color=color, highlight=highlight, character=player.character, player=player) if player.actor is not None: player.actor.node.delete() player.setActor(spaz) pos = [0,.5,-5+(random.random()*10)] player.actor.connectControlsToPlayer(enableBomb=False, enableRun = True, enableJump = False, enablePickUp = True,enablePunch = False) spaz.handleMessage(bs.StandMessage(pos,90)) self.checkEnd() def onPlayerJoin(self, player): if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) return def updateScore(self): for team in self.teams: self._scoredis.setTeamValue(team,team.gameData['score']) def handleSafeZoneEnter(self): self.checkEnd() zoneNode,playerNode = bs.getCollisionInfo("sourceNode","opposingNode") try: player = playerNode.getDelegate().getPlayer() except Exception: return if player.isAlive() and player.actor.node.getDelegate().isShark: player.actor.handleMessage(bs.DieMessage()) self.sharkify(player) elif player.isAlive() and not player.actor.node.getDelegate().isShark: if player.actor.node.positionCenter[0] < 0 and player.actor.node.getDelegate().nextZone == 1: player.getTeam().gameData['score'] += 10 self.scoreSet.playerScored(player,10,screenMessage=False,display=False) self.updateScore() player.actor.node.getDelegate().nextZone = 2 elif player.actor.node.positionCenter[0] > 0 and player.actor.node.getDelegate().nextZone == 2: player.getTeam().gameData['score'] += 10 self.scoreSet.playerScored(player,10,screenMessage=False,display=False) self.updateScore() player.actor.node.getDelegate().nextZone = 1 def handleMessage(self,m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) bs.gameTimer(3100, self.checkEnd) if not m.spaz.isShark: self.respawnPlayer(m.spaz.getPlayer()) else: bs.TeamGameActivity.handleMessage(self, m) def checkEnd(self): if bs.getGameTime() < 5000: return count = 0 for player in self.players: if player.isAlive() and player.actor.node.getDelegate().isShark: count += 1 if count < 1: self.endGame() count = 0 for player in self.players: if player.isAlive(): count += 1 if count < 2: self.endGame() count = 0 for player in self.players: if player.isAlive() and player.actor.node.getDelegate().isShark == False: count += 1 if count < 1: self.endGame() for team in self.teams: if team.gameData['score'] >= 300: self.endGame() def endGame(self): results = bs.TeamGameResults() for team in self.teams: score = team.gameData['score'] results.setTeamScore(team,score) self.end(results=results,announceDelay=10) ================================================ FILE: mods/Siege.py ================================================ #Siege import bs import bsUtils import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [Siege] class SiegePowerupFactory(bs.PowerupFactory): def getRandomPowerupType(self,forceType=None,excludeTypes=['tripleBombs','iceBombs','impactBombs','shield','health','curse','snoball','bunny']): while True: t = self._powerupDist[random.randint(0,len(self._powerupDist)-1)] if t not in excludeTypes: break self._lastPowerupType = t return t class Puck(bs.Actor): # Borrowed from the hockey game def __init__(self, position=(0,1,0)): bs.Actor.__init__(self) self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) activity = self.getActivity() self._spawnPos = (position[0], position[1]+1.0, position[2]) self.lastPlayersToTouch = {} self.node = bs.newNode("prop", attrs={'model': bs.getModel('puck'), 'colorTexture': bs.getTexture('puckColor'), 'body':'puck', 'reflection':'soft', 'reflectionScale':[0.2], 'shadowSize': 1.0, 'gravityScale':2.5, 'isAreaOfInterest':True, 'position':self._spawnPos, 'materials': [bs.getSharedObject('objectMaterial'),activity._puckMaterial] }, delegate=self) class Siege(bs.TeamGameActivity): @classmethod def getName(cls): return "Siege" @classmethod def getDescription(cls, sessionType): return "Get the flag from the castle!" @classmethod def getScoreInfo(cls): return{'scoreType':'points'} @classmethod def getSupportedMaps(cls, sessionType): return ['Football Stadium'] @classmethod def supportsSessionType(cls, sessionType): return True if issubclass(sessionType, bs.FreeForAllSession) or issubclass(sessionType, bs.TeamsSession) else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self._puckMaterial = bs.Material() self._puckMaterial.addActions(actions=( ("modifyPartCollision","friction",100000))) self._puckMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('pickupMaterial')), actions=( ("modifyPartCollision","collide",False))) self._puckMaterial.addActions(conditions=( ("weAreYoungerThan",100),'and', ("theyHaveMaterial",bs.getSharedObject('objectMaterial')) ), actions=( ("modifyNodeCollision","collide",False))) self.pucks = [] self.flag = bs.Flag(color=(1,1,1), position=(0,1,-2), touchable=True) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self,music='FlagCatcher') def _standardDropPowerup(self,index,expire=True): import bsPowerup bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[index], powerupType=SiegePowerupFactory().getRandomPowerupType(),expire=expire).autoRetain() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardPowerupDrops(True) for j in range(0,12,3): for i in range(-6,4,3): self.pucks.append(Puck((3,j/4.0,i/2.0))) self.pucks.append(Puck((-3,j/4.0,i/2.0))) for i in range(-3,4,2): self.pucks.append(Puck((i/2.0,j/4.0,-3))) self.pucks.append(Puck((i/2.0,j/4.0,1.75))) def handleMessage(self,m): if isinstance(m,bs.FlagPickedUpMessage): winner = m.node.getDelegate() self.endGame(winner) elif isinstance(m,bs.PlayerSpazDeathMessage): self.respawnPlayer(m.spaz.getPlayer()) else: bs.TeamGameActivity.handleMessage(self,m) def endGame(self, winner): results = bs.TeamGameResults() for team in self.teams: if winner.getPlayer() in team.players: score = 50 else: score = 0 results.setTeamScore(team,score) self.end(results=results,announceDelay=10) ================================================ FILE: mods/SimonSays.py ================================================ #SimonSays # you had really better do what Simon says... import bs import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [SimonSays] class SimonSays(bs.TeamGameActivity): @classmethod def getName(cls): return "Simon Says" @classmethod def getDescription(cls, sessionType): return "You had better do what Simon says..." @classmethod def getScoreInfo(cls): return{'scoreType':'points'} @classmethod def getSettings(cls, sessionType): return [("Epic Mode", {'default': False}), ("Enable Jumping", {'default': False}), ("Enable Punching", {'default': False}), ("Enable Picking Up", {'default': False})] @classmethod def getSupportedMaps(cls, sessionType): return ["Courtyard"] @classmethod def supportsSessionType(cls, sessionType): return True if issubclass(sessionType, bs.FreeForAllSession) else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) self.roundNum = 0 self.simon = False self.time = 5000 self._r1 = 2 n1 = bs.newNode('locator',attrs={'shape':'circle','position':(-4,0,-6), 'color':(1,0,0),'opacity':0.5, 'drawBeauty':True,'additive':True}) n2 = bs.newNode('locator',attrs={'shape':'circle','position':(0,0,-6), 'color':(0,1,0),'opacity':0.5, 'drawBeauty':True,'additive':True}) n3 = bs.newNode('locator',attrs={'shape':'circle','position':(4,0,-6), 'color':(0,0,1),'opacity':0.5, 'drawBeauty':True,'additive':True}) n4 = bs.newNode('locator',attrs={'shape':'circle','position':(-4,0,-2), 'color':(1,1,0),'opacity':0.5, 'drawBeauty':True,'additive':True}) n5 = bs.newNode('locator',attrs={'shape':'circle','position':(0,0,-2), 'color':(0,1,1),'opacity':0.5, 'drawBeauty':True,'additive':True}) n6 = bs.newNode('locator',attrs={'shape':'circle','position':(4,0,-2), 'color':(1,0,1),'opacity':0.5, 'drawBeauty':True,'additive':True}) n7 = bs.newNode('locator',attrs={'shape':'circle','position':(-4,0,2), 'color':(.5,.5,.5),'opacity':0.5, 'drawBeauty':True,'additive':True}) n8 = bs.newNode('locator',attrs={'shape':'circle','position':(0,0,2), 'color':(.5,.325,0),'opacity':0.5, 'drawBeauty':True,'additive':True}) n9 = bs.newNode('locator',attrs={'shape':'circle','position':(4,0,2), 'color':(1,1,1),'opacity':0.5, 'drawBeauty':True,'additive':True}) bs.animateArray(n1,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n2,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n3,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n4,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n5,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n6,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n7,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n8,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n9,'size',1,{0:[0.0],200:[self._r1*2.0]}) self.options = ["red", "green", "blue", "yellow", "teal", "purple", "gray", "orange", "white", "top", "bottom", "middle row", "left", "right", "center column", "outside"] def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self,music='FlagCatcher') def onPlayerJoin(self, player): if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText', subs=[('${PLAYER}', player.getName(full=True))]), color=(0, 1, 0)) def onBegin(self): s = self.settings bs.TeamGameActivity.onBegin(self) for team in self.teams: team.gameData['score'] = 0 for player in self.players: player.gameData['score'] = 0 self.spawnPlayerSpaz(player,self.getMap().getFFAStartPosition(self.players)) player.actor.connectControlsToPlayer(enableBomb=False, enablePunch = s["Enable Punching"], enablePickUp = s["Enable Picking Up"], enableRun = True, enableJump = s["Enable Jumping"]) self.explainGame() def explainGame(self): bs.screenMessage("Follow the commands...") bs.screenMessage("but only when Simon says!") bs.gameTimer(5000, self.callRound) def callRound(self): self.roundNum += 1 self.num = random.randint(0, 15) num = self.num self.simon = random.choice([True, False]) if num < 9: line = "Run to the " + self.options[num] + " circle!" elif num < 15: line = "Run to the " + self.options[num] + "!" else: line = "Run outside of the circles!" if self.simon: line = "Simon says " + line[0].lower() + line[1:] self.text = bs.PopupText(line, position=(0, 5, -4), color=(1, 1, 1), randomOffset=0.5, offset=(0, 0, 0), scale=2.0).autoRetain() self.time -= 100 bs.gameTimer(self.time, self.checkRound) def checkRound(self): for player in self.players: if player.isAlive(): safe = True if self.options[self.num] in self.inCircle(player.actor.node.positionCenter) else False if ((self.simon and safe == False) or ((not self.simon) and safe == True)): player.getTeam().gameData["score"] = self.roundNum player.actor.handleMessage(bs.DieMessage()) self.callRound() def inCircle(self, pos): circles = [] x = pos[0] z = pos[2] if (x + 4) ** 2 + (z + 6) ** 2 < 4: circles.append("red") elif (x) ** 2 + (z + 6) ** 2 < 4: circles.append("green") elif (x - 4) ** 2 + (z + 6) ** 2 < 4: circles.append("blue") elif (x + 4) ** 2 + (z + 2) ** 2 < 4: circles.append("yellow") elif (x) ** 2 + (z + 2) ** 2 < 4: circles.append("teal") elif (x - 4) ** 2 + (z + 2) ** 2 < 4: circles.append("purple") elif (x + 4) ** 2 + (z - 2) ** 2 < 4: circles.append("gray") elif (x) ** 2 + (z - 2) ** 2 < 4: circles.append("orange") elif (x - 4) ** 2 + (z - 2) ** 2 < 4: circles.append("white") else: circles.append("outside") if x < -2: circles.append("left") if x > 2: circles.append("right") if x > -2 and x < 2: circles.append("center column") if z > 0: circles.append("bottom") if z < -4: circles.append("top") if z < 0 and z > -4: circles.append("middle row") return circles def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): self.checkEnd() else: bs.TeamGameActivity.handleMessage(self, m) def onPlayerJoin(self, player): if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) return def checkEnd(self): i = 0 for player in self.players: if player.isAlive(): i += 1 if i < 2: self.endGame() def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team, team.gameData['score']) self.end(results=results) ================================================ FILE: mods/SnoBallz.json ================================================ { "name": "SnoBallz powerup", "author": "joshville79", "category": "libraries" } ================================================ FILE: mods/SnoBallz.py ================================================ #This is not a minigame mod script. It's a powerup. import bs import bsUtils import bsVector import bsBomb from math import cos from random import randrange import weakref class snoMessage(object): #Message passed to a snoBall by collision with a spaz pass class otherHitMessage(object): #Message passed when we hit some other object. Lets us poof into thin air. pass class snoBall(bs.Actor): def __init__(self, position=(0,1,0), velocity=(5,0,5), sourcePlayer=None, owner=None, explode=False): bs.Actor.__init__(self) activity = bs.getActivity() factory = self.getFactory() # spawn at the provided point self._spawnPos = (position[0], position[1]+0.1, position[2]) self.node = bs.newNode("prop", attrs={'model': factory.snoModel, 'body':'sphere', 'colorTexture': factory.texSno, 'reflection':'soft', 'modelScale':0.8, 'bodyScale':0.8, 'density':1, 'reflectionScale':[0.15], 'shadowSize': 0.6, 'position':self._spawnPos, 'velocity':velocity, 'materials': [bs.getSharedObject('objectMaterial'), factory.ballMaterial] }, delegate=self) self.sourcePlayer = sourcePlayer self.owner = owner if factory._ballsMelt: #defaults to True. #Snowballs should melt after some time bs.gameTimer(1500, bs.WeakCall(self._disappear)) self._hitNodes = set() self._exploded = False if factory._ballsBust: self.shouldBust = True else: self.shouldBust = False if explode: self.shouldExplode = True else: self.shouldExplode = False def handleMessage(self,m): super(self.__class__, self).handleMessage(m) if isinstance(m, otherHitMessage): if self._exploded: return #Don't bother with calcs if we've done blowed up or busted. if self.shouldBust: myVel = self.node.velocity #Get the velocity at the instant of impact. We'll check it after 20ms (after bounce). If it has changed a lot, bust. bs.gameTimer(10, bs.WeakCall(self.calcBust,myVel)) else: return if isinstance(m,bs.DieMessage): self.node.delete() elif isinstance(m,bs.OutOfBoundsMessage): self.handleMessage(bs.DieMessage()) elif isinstance(m,bs.HitMessage): self.node.handleMessage("impulse",m.pos[0],m.pos[1],m.pos[2], m.velocity[0],m.velocity[1],m.velocity[2], 1.0*m.magnitude,1.0*m.velocityMagnitude,m.radius,0, m.forceDirection[0],m.forceDirection[1],m.forceDirection[2]) elif isinstance(m, bs.ImpactDamageMessage): print [dir(m), m.intensity] elif isinstance(m,snoMessage): #We should get a snoMessage any time a snowball hits a spaz. #We'll either explode (if we're exploding) or calculate like a punch. #We have to do this the hard way because the ImpactMessage won't do it for us (no source) #Below is modified pretty much from bsSpaz handling of a _punchHitMessage #print bsVector.Vector(*self.node.velocity).length() if self._exploded: return #Don't do anything if we've already done our damage, or if we've busted already if self.shouldExplode: """ Blows up the ball if it has not yet done so. """ if self._exploded: return self._exploded = True activity = self.getActivity() if activity is not None and self.node.exists(): blast = bsBomb.Blast(position=self.node.position,velocity=self.node.velocity, blastRadius=0.7,blastType='impact',sourcePlayer=self.sourcePlayer,hitType='snoBall',hitSubType='explode').autoRetain() # we blew up so we need to go away bs.gameTimer(1,bs.WeakCall(self.handleMessage,bs.DieMessage())) else: v = self.node.velocity #Only try to damage if the ball is moving at some reasonable rate of speed if bs.Vector(*v).length() > 5.0: node = bs.getCollisionInfo("opposingNode") # only allow one hit per node per ball if node is not None and node.exists() and not node in self._hitNodes: t = self.node.position #was punchPosition hitDir = self.node.velocity self._hitNodes.add(node) node.handleMessage(bs.HitMessage(pos=t, velocity=v, magnitude=bsVector.Vector(*v).length()*0.5, velocityMagnitude=bsVector.Vector(*v).length()*0.5, radius=0, srcNode=self.node, sourcePlayer=self.sourcePlayer, forceDirection = hitDir, hitType='snoBall', hitSubType='default')) if self.shouldBust: #Since we hit someone, let's bust: #Add a very short timer to allow one ball to hit more than one spaz if almost simultaneous. bs.gameTimer(50, bs.WeakCall(self.doBust)) else: bs.Actor.handleMessage(self,m) def doBust(self): if self.exists(): if not self._exploded: self._exploded = True 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') #Do a muffled punch sound sound = self.getFactory().impactSound bs.playSound(sound,1.0,position=self.node.position) scl = self.node.modelScale bsUtils.animate(self.node,"modelScale",{0:scl*1.0, 20:scl*0.5, 50:0.0}) bs.gameTimer(80,bs.WeakCall(self.handleMessage,bs.DieMessage())) def calcBust(self, oVel): #Original speed (magnitude of velocity) oSpd = bs.Vector(*oVel).length() #Now get the dot product of the original velocity and current velocity. dot = sum(x*y for x,y in zip(oVel,self.node.velocity)) if oSpd*oSpd - dot > 50.0: #Basically, the more different the dot product from the square of the original #velocity vector, the more the ball trajectory changed when it it something. #This is the best way I could figure out how "hard" the ball hit. #A difference value was a pretty arbitrary choice. #Add a very short timer to allow just a couple of hits. bs.gameTimer(50, bs.WeakCall(self.doBust)) def _disappear(self): self._exploded = True #don't try to damage stuff anymore because we should be melting. if self.exists(): scl = self.node.modelScale bsUtils.animate(self.node,"modelScale",{0:scl*1.0, 300:scl*0.5, 500:0.0}) bs.gameTimer(550,bs.WeakCall(self.handleMessage,bs.DieMessage())) def getFactory(cls): """ Returns a shared SnoBallz.SnoBallFactory object, creating it if necessary. """ activity = bs.getActivity() if activity is None: raise Exception("no current activity") try: return activity._sharedSnoBallFactory except Exception: f = activity._sharedSnoBallFactory = SnoBallFactory() return f class SnoBallFactory(object): def __init__(self): """ Instantiate a SnoBallFactory. You shouldn't need to do this; call snoBallz.snoBall.getFactory() to get a shared instance. """ self.texSno = bs.getTexture("bunnyColor") self.snoModel = bs.getModel("frostyPelvis") self.ballMaterial = bs.Material() self.impactSound = bs.getSound('impactMedium') #First condition keeps balls from damaging originating player by preventing collisions immediately after they appear. #Time is very short due to balls move fast. self.ballMaterial.addActions( conditions=((('weAreYoungerThan',5),'or',('theyAreYoungerThan',100)), 'and',('theyHaveMaterial',bs.getSharedObject('objectMaterial'))), actions=(('modifyNodeCollision','collide',False))) # we want pickup materials to always hit us even if we're currently not # colliding with their node (generally due to the above rule) self.ballMaterial.addActions( conditions=('theyHaveMaterial',bs.getSharedObject('pickupMaterial')), actions=(('modifyPartCollision','useNodeCollide',False))) self.ballMaterial.addActions(actions=('modifyPartCollision','friction',0.3)) #This action disables default physics when the ball hits a spaz. Sends a snoMessage to #itself so that it can try to damage spazzes. self.ballMaterial.addActions(conditions=('theyHaveMaterial', bs.getSharedObject('playerMaterial')), actions=(('modifyPartCollision','physical',False),('message', 'ourNode', 'atConnect', snoMessage()))) #This message sends a different message to our ball just to see if it should bust or not self.ballMaterial.addActions(conditions=( ('theyDontHaveMaterial', bs.getSharedObject('playerMaterial')), 'and', ('theyHaveMaterial', bs.getSharedObject('objectMaterial')), 'or', ('theyHaveMaterial', bs.getSharedObject('footingMaterial'))), actions=('message', 'ourNode', 'atConnect', otherHitMessage())) #The below can be changed after the factory is created self.defaultBallTimeout = 300 self._ballsMelt = True self._ballsBust = True self._powerExpire = True self._powerLife = 20000 def giveBallz(self,spaz): spaz.punchCallback = self.throwBall spaz.lastBallTime = bs.getGameTime() if self._powerExpire: weakSpaz = weakref.ref(spaz) spaz.snoExpireTimer = bs.Timer(self._powerLife, bs.WeakCall(self.takeBallz, weakSpaz)) def takeBallz(self,weakSpaz): if not weakSpaz() is None: weakSpaz().punchCallback = None def throwBall(self, spaz): t = bs.getGameTime() #Figure bomb timeout based on other owned powerups: bTime = self.defaultBallTimeout if spaz.bombType == 'impact': bTime *= 2 if spaz.bombCount > 1: bTime /= 2 if t - spaz.lastBallTime > bTime: spaz.lastBallTime = t #Figure out which way spaz is facing p1 = spaz.node.positionCenter p2 = spaz.node.positionForward direction = [p1[0]-p2[0],p2[1]-p1[1],p1[2]-p2[2]] direction[1] = 0.03 #This is the upward throw angle #Make a velocity vector for the snowball mag = 20.0/bsVector.Vector(*direction).length() vel = [v * mag for v in direction] #print vel if spaz.bombType == 'impact': explodeIt = True else: explodeIt = False snoBall(spaz.node.position,vel,spaz.getPlayer(),spaz.getPlayer(),explodeIt).autoRetain() ================================================ FILE: mods/SnowBallFight.json ================================================ { "name": "Snowball Fight (requires SnoBallz.py)", "author": "joshville79", "category": "minigames", "requires": ["SnoBallz"] } ================================================ FILE: mods/SnowBallFight.py ================================================ import bs import bsVector import bsSpaz import bsBomb import bsUtils import random import SnoBallz #please note that this Minigame requires the separate file SnoBallz.py. #It's a separate file to allow for snowballs as a powerup in any game. def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [SnowBallFightGame] class PlayerSpaz_Sno(bs.PlayerSpaz): def handleMessage(self,m): #print m, self.hitPoints if isinstance(m,bsSpaz._PunchHitMessage): return True #Nullify punches super(self.__class__, self).handleMessage(m) class SnowBallFightGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Snowball Fight' @classmethod def getDescription(cls,sessionType): return 'Kill a set number of enemies to win.' @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): settings = [("Kills to Win Per Player",{'minValue':1,'default':5,'increment':1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Snowball Rate",{'choices':[('Slowest',500),('Slow',400),('Normal',300),('Fast',200),('Lag City',100)],'default':300}), ("Snowballs Melt",{'default':True}), ("Snowballs Bust",{'default':True}), ("Epic Mode",{'default':False})] # In teams mode, a suicide gives a point to the other team, but in free-for-all it # subtracts from your own score. By default we clamp this at zero to benefit new players, # but pro players might like to be able to go negative. (to avoid a strategy of just # suiciding until you get a good drop) if issubclass(sessionType, bs.FreeForAllSession): settings.append(("Allow Negative Scores",{'default':False})) return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die since it matters here.. self.announcePlayerDeaths = True self._scoreBoard = bs.ScoreBoard() #Initiate the SnoBall factory self.snoFact = SnoBallz.snoBall().getFactory() self.snoFact.defaultBallTimeout = self.settings['Snowball Rate'] self.snoFact._ballsMelt = self.settings['Snowballs Melt'] self.snoFact._ballsBust = self.settings['Snowballs Bust'] self.snoFact._powerExpire = False def getInstanceDescription(self): return ('Crush ${ARG1} of your enemies.',self._scoreToWin) def getInstanceScoreBoardDescription(self): return ('kill ${ARG1} enemies',self._scoreToWin) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'ToTheDeath') def onTeamJoin(self,team): team.gameData['score'] = 0 if self.hasBegun(): self._updateScoreBoard() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops() if len(self.teams) > 0: self._scoreToWin = self.settings['Kills to Win Per Player'] * max(1,max(len(t.players) for t in self.teams)) else: self._scoreToWin = self.settings['Kills to Win Per Player'] self._updateScoreBoard() self._dingSound = bs.getSound('dingSmall') def _standardDropPowerup(self,index,expire=True): import bsPowerup forbidded = ['iceBombs','punch','stickyBombs','landMines', 'snoball'] bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[index], powerupType=bs.Powerup.getFactory().getRandomPowerupType(None,forbidded),expire=expire).autoRetain() def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None): """ Create and wire up a bs.PlayerSpaz for the provide bs.Player. """ position = self.getMap().getFFAStartPosition(self.players) name = player.getName() color = player.color highlight = player.highlight lightColor = bsUtils.getNormalizedColor(color) displayColor = bs.getSafeColor(color,targetIntensity=0.75) spaz = PlayerSpaz_Sno(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer(enableBomb=False, enablePickUp=False) self.snoFact.giveBallz(spaz) self.scoreSet.playerGotNewSpaz(player,spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360))) t = bs.getGameTime() bs.playSound(self._spawnSound,1,position=spaz.node.position) light = bs.newNode('light',attrs={'color':lightColor}) spaz.node.connectAttr('position',light,'position') bsUtils.animate(light,'intensity',{0:0,250:1,500:0}) bs.gameTimer(500,light.delete) return spaz def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior player = m.spaz.getPlayer() self.respawnPlayer(player) killer = m.killerPlayer if killer is None: return # handle team-kills if killer.getTeam() is player.getTeam(): # in free-for-all, killing yourself loses you a point if isinstance(self.getSession(),bs.FreeForAllSession): newScore = player.getTeam().gameData['score'] - 1 if not self.settings['Allow Negative Scores']: newScore = max(0, newScore) player.getTeam().gameData['score'] = newScore # in teams-mode it gives a point to the other team else: bs.playSound(self._dingSound) for team in self.teams: if team is not killer.getTeam(): team.gameData['score'] += 1 # killing someone on another team nets a kill else: killer.getTeam().gameData['score'] += 1 bs.playSound(self._dingSound) # in FFA show our score since its hard to find on the scoreboard try: killer.actor.setScoreText(str(killer.getTeam().gameData['score'])+'/'+str(self._scoreToWin),color=killer.getTeam().color,flash=True) except Exception: pass self._updateScoreBoard() # if someone has won, set a timer to end shortly # (allows the dust to clear and draws to occur if deaths are close enough) if any(team.gameData['score'] >= self._scoreToWin for team in self.teams): bs.gameTimer(500,self.endGame) else: bs.TeamGameActivity.handleMessage(self,m) def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team,team.gameData['score'],self._scoreToWin) def endGame(self): results = bs.TeamGameResults() for t in self.teams: results.setTeamScore(t,t.gameData['score']) self.end(results=results) ================================================ FILE: mods/WizardWar.json ================================================ { "name": "WizardWar", "author": "MattZ45986", "category": "minigames" } ================================================ FILE: mods/WizardWar.py ================================================ # Wizard War # In light of the new "Grumbledorf" character # I have made a war just for him. import bs import random import math import bsVector import bsUtils def bsGetAPIVersion(): return 4 def bsGetGames(): return [WizardWar] def bsGetLevels(): return [bs.Level('Wizard War', displayName='${GAME}', gameType=WizardWar, settings={}, previewTexName='courtyardPreview')] class ExplodeMessage(object): pass class ArmMessage(object): pass class WarnMessage(object): pass class DieMessage(object): pass class _BombDiedMessage(object): pass # A special type of bomb that is cast from the player's body. # It is shaped like an orb and is colored according to the player's team class WWBomb(bs.Bomb): def __init__(self,position=(0,1,0),velocity=(0,0,0),bombType='normal',blastRadius=2,sourcePlayer=None,owner=None): if not sourcePlayer.isAlive(): return bs.Actor.__init__(self) factory = self.getFactory() if not bombType in ('ice','impact','landMine','normal','sticky','tnt'): raise Exception("invalid bomb type: " + bombType) self.bombType = bombType self._exploded = False self.blastRadius = blastRadius self._explodeCallbacks = [] self.sourcePlayer = sourcePlayer self.hitType = 'explosion' self.hitSubType = self.bombType if owner is None: owner = bs.Node(None) self.owner = owner materials = (factory.bombMaterial, bs.getSharedObject('objectMaterial')) materials = materials + (factory.impactBlastMaterial,) players = self.getActivity().players i = 0 # This gives each player a unique orb color, made possible by the powerup textures within the game. while players[i] != sourcePlayer: i+=1 color = ("powerupIceBombs","powerupPunch","powerupStickyBombs","powerupBomb","powerupCurse","powerupHealth","powerupShield","powerupLandMines")[i] if isinstance(self.getActivity().getSession(), bs.TeamsSession): # unless we're on teams, so we'll overide the color to be the team's color if sourcePlayer in self.getActivity().teams[0].players: color = "powerupIceBombs" # for blue else: color = "powerupPunch" # for red self.node = bs.newNode('prop', delegate=self, attrs={'position':position, 'velocity':velocity, 'body':'sphere', 'model':bs.getModel("shield"), 'shadowSize':0.3, 'density':1, 'bodyScale':3, 'colorTexture':bs.getTexture(color), 'reflection':'soft', 'reflectionScale':[1.5], 'materials':materials}) self.armTimer = bs.Timer(200,bs.WeakCall(self.handleMessage,ArmMessage())) self.node.addDeathAction(bs.WeakCall(self.handleMessage,_BombDiedMessage())) bsUtils.animate(self.node,"modelScale",{0:0, 200:1.3, 260:1}) def _handleImpact(self,m): node,body = bs.getCollisionInfo("opposingNode","opposingBody") if node is None: return try: player = node.getDelegate().getPlayer() except Exception: player = None if self.getActivity().settings["Orbs Explode Other Orbs"]: if isinstance(node.getDelegate(),WWBomb) and node.getNodeType() == 'prop' and (node.getDelegate().sourcePlayer.getTeam() is not self.sourcePlayer.getTeam()): self.handleMessage(ExplodeMessage()) if (player is not None) and (player.getTeam() is not self.sourcePlayer.getTeam()): self.handleMessage(ExplodeMessage()) def handleMessage(self, m): if isinstance(m, _BombDiedMessage): self.sourcePlayer.actor._orbNum -= 1 elif isinstance(m, ExplodeMessage): self.explode() else: bs.Bomb.handleMessage(self,m) class WWSpaz(bs.PlayerSpaz): def onPunchPress(self): self.getActivity().shootBomb(self) class WizardWar(bs.TeamGameActivity): @classmethod def getName(cls): return "Wizard War" @classmethod def getDescription(cls, sessionType): return "Punch to summon magic orbs\nLast mage standing wins" @classmethod def getScoreInfo(cls): return{'scoreType':'points'} @classmethod def getSettings(cls, sessionType): return [("Epic Mode", {'default': False}), ("Enable Running", {'default': False}), ("Enable Jumping", {'default': False}), ("Enable Punching", {'default': False}), ("Enable Picking Up", {'default': False}), ("Orbs Explode Other Orbs", {'default':True}), ("Orb Limit", { 'choices': [ ('1 Orb', 1), ('2 Orbs', 2), ('3 Orbs', 3), ('5 Orbs', 5), ('Infinite', 300) ], 'default': 3})] @classmethod def getSupportedMaps(cls, sessionType): return bs.getMapsSupportingPlayType('melee') @classmethod def supportsSessionType(cls, sessionType): return True if issubclass(sessionType, bs.FreeForAllSession) or issubclass(sessionType, bs.TeamsSession) else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True self.info = bs.NodeActor(bs.newNode('text', attrs={'vAttach': 'bottom', 'hAlign': 'center', 'vrDepth': 0, 'color': (0,.2,0), 'shadow': 1.0, 'flatness': 1.0, 'position': (0,0), 'scale': 0.8, 'text': "Created by MattZ45986 on Github", })) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self,music='ToTheDeath') def onBegin(self): bs.TeamGameActivity.onBegin(self) s = self.settings for player in self.players: player.actor.connectControlsToPlayer(enableBomb=False, enablePickUp = s["Enable Picking Up"], enableRun = s["Enable Running"], enableJump = s["Enable Jumping"]) player.gameData['score'] = 0 pos = player.actor.node.positionCenter t = bs.newNode('text', owner=player.actor.node, attrs={'text':player.getName(), 'inWorld':True, 'color':player.color, 'scale':0.015, 'hAlign':'center', 'position':(pos)}) l = bs.newNode('light', owner=player.actor.node, attrs={'color':player.color, 'position':(pos), 'intensity':0.5}) bs.gameTimer(2000,l.delete) bs.gameTimer(2000,t.delete) def onPlayerJoin(self, player): if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) self.checkEnd() return else: self.spawnPlayerSpaz(player) def spawnPlayerSpaz(self,player,position=(0,5,-3),angle=None): name = player.getName() color = player.color highlight = player.highlight players = self.players i = 0 position = self.getMap().getFFAStartPosition(self.players) angle = 0 spaz = WWSpaz(color=color, highlight=highlight, character="Grumbledorf", player=player) player.setActor(spaz) spaz.handleMessage(bs.StandMessage(position,angle)) spaz._orbNum = 0 def shootBomb(self, spaz): if spaz._orbNum >= self.settings["Orb Limit"]: return spaz._orbNum += 1 try: spaz.node.handleMessage('celebrate',100) except Exception: pass cen = spaz.node.positionCenter frw = spaz.node.positionForward direction = [cen[0]-frw[0],frw[1]-cen[1],cen[2]-frw[2]] direction[1] *= .03 mag = 20.0/bsVector.Vector(*direction).length() vel = [v * mag for v in direction] WWBomb(position=(spaz.node.position[0],spaz.node.position[1]+1,spaz.node.position[2]), velocity=vel, sourcePlayer = spaz.getPlayer(), bombType = 'impact').autoRetain() def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) bs.gameTimer(1000,bs.Call(self.checkEnd)) m.spaz.getPlayer().actor.disconnectControlsFromPlayer() if m.how == "fall": pts = 10 elif m.how == "impact": pts = 50 else: pts = 0 self.scoreSet.playerScored(m.killerPlayer,pts,screenMessage=False) bs.screenMessage(str(m.spaz.getPlayer().getName()) + " died!", m.spaz.getPlayer().color, top=True) def checkEnd(self): if isinstance(self.getSession(), bs.FreeForAllSession): i = 0 for player in self.players: if player.isAlive(): i += 1 if i <= 1: self.endGame() if isinstance(self.getSession(), bs.TeamsSession): for team in self.teams: i=0 team.gameData['score'] = 1 for player in team.players: if player.isAlive(): i += 1 if i == 0: team.gameData['score'] -= 1 for team in self.teams: if team.gameData['score'] == 0: self.endGame() def endGame(self): if isinstance(self.getSession(), bs.FreeForAllSession): for player in self.players: if player.isAlive(): player.gameData['score'] = 1 else: player.gameData['score'] = 0 results = bs.TeamGameResults() for team in self.teams: for player in team.players: if player.isAlive(): results.setTeamScore(team, 5) break else: results.setTeamScore(team, 0) else: results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team, 0) for player in team.players: if player.isAlive(): team.gameData['score'] = 1 results.setTeamScore(team, 10) self.end(results=results) ================================================ FILE: mods/ZombieHorde.json ================================================ { "name": "Zombie Horde", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/ZombieHorde.py ================================================ import bs import random import bsUtils import bsSpaz import copy #import PlayerSpaz def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [ZombieHorde] class Icon(bs.Actor): def __init__(self,player,position,scale,showLives=True,showDeath=True, nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0): bs.Actor.__init__(self) self._player = player self._showLives = showLives self._showDeath = showDeath self._nameScale = nameScale self._outlineTex = bs.getTexture('characterIconMask') icon = player.getIcon() self.node = bs.newNode('image', owner=self, attrs={'texture':icon['texture'], 'tintTexture':icon['tintTexture'], 'tintColor':icon['tintColor'], 'vrDepth':400, 'tint2Color':icon['tint2Color'], 'maskTexture':self._outlineTex, 'opacity':1.0, 'absoluteScale':True, 'attach':'bottomCenter'}) self._nameText = bs.newNode('text', owner=self.node, attrs={'text':player.getName(), 'color':bs.getSafeColor(player.getTeam().color), 'hAlign':'center', 'vAlign':'center', 'vrDepth':410, 'maxWidth':nameMaxWidth, 'shadow':shadow, 'flatness':flatness, 'hAttach':'center', 'vAttach':'bottom'}) if self._showLives: self._livesText = bs.newNode('text', owner=self.node, attrs={'text':'x0', 'color':(1,1,0.5), 'hAlign':'left', 'vrDepth':430, 'shadow':1.0, 'flatness':1.0, 'hAttach':'center', 'vAttach':'bottom'}) self.setPositionAndScale(position,scale) def setPositionAndScale(self,position,scale): self.node.position = position self.node.scale = [70.0*scale] self._nameText.position = (position[0],position[1]+scale*52.0) self._nameText.scale = 1.0*scale*self._nameScale if self._showLives: self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0) self._livesText.scale = 1.0*scale def updateForLives(self): if self._player.exists(): lives = self._player.gameData['lives'] else: lives = 0 if self._showLives: if lives > 0: self._livesText.text = 'x'+str(lives-1) else: self._livesText.text = '' if lives == 0: self._nameText.opacity = 0.2 self.node.color = (0.7,0.3,0.3) self.node.opacity = 0.2 def handlePlayerSpawned(self): if not self.node.exists(): return self.node.opacity = 1.0 self.updateForLives() def handlePlayerDied(self): if not self.node.exists(): return if self._showDeath: bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0, 300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2}) lives = self._player.gameData['lives'] if lives == 0: bs.gameTimer(600,self.updateForLives) class PlayerSpaz_Zom(bs.PlayerSpaz): def handleMessage(self, m): if isinstance(m, bs.HitMessage): if not self.node.exists(): return if not m.sourcePlayer is None: #it seems as though spazBots are actually players, but with invalid names... Do a try for invalid name? try: playa = m.sourcePlayer.getName(True, False) # Long name, no icons if not playa is None: #Player had a name. Hit by a person. No damage unless player is also zombie (zero lives). if m.sourcePlayer.gameData['lives'] < 1: super(self.__class__, self).handleMessage(m) except: super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) elif isinstance(m,bs.FreezeMessage): pass #Can't be frozen. Would allow self-freeze, but can't prevent others from freezing. elif isinstance(m,bsSpaz._PickupMessage): #Complete copy from bsSpaz except for added section to prevent picking players opposingNode,opposingBody = bs.getCollisionInfo('opposingNode','opposingBody') if opposingNode is None or not opposingNode.exists(): return True # dont allow picking up of invincible dudes try: if opposingNode.invincible == True: return True except Exception: pass ####ADDED SECTION - Don't allow picking up of non-Zombie dudes try: playa = opposingNode.sourcePlayer.getName(True, False) # Long name, no icons if not playa is None: #Player had a name. Prevent pickup unless player is also zombie (zero lives). if opposingNode.sourcePlayer.gameData['lives'] > 0: return True except Exception: pass ##### # if we're grabbing the pelvis of a non-shattered spaz, we wanna grab the torso instead if opposingNode.getNodeType() == 'spaz' and not opposingNode.shattered and opposingBody == 4: opposingBody = 1 # special case - if we're holding a flag, dont replace it # ( hmm - should make this customizable or more low level ) held = self.node.holdNode if held is not None and held.exists() and held.getNodeType() == 'flag': return True self.node.holdBody = opposingBody # needs to be set before holdNode self.node.holdNode = opposingNode else: super(self.__class__, self).handleMessage(m) class PlayerZombie(bs.PlayerSpaz): def handleMessage(self, m): if isinstance(m, bs.HitMessage): if not self.node.exists(): return if not m.sourcePlayer is None: #it seems as though spazBots are actually players, but with invalid names... Do a try for invalid name? #print(['hit by]', m.sourcePlayer.getName(True,False)]) try: playa = m.sourcePlayer.getName(True, False) # Long name, no icons if playa is None: #Player had no name. Hit by a Zombie. No damage. pass else: super(self.__class__, self).handleMessage(m) except: super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) class zBotSet(bs.BotSet): #the botset is overloaded to prevent adding players to the bots' targets if they are zombies too. def startMoving(self): #here we overload the default startMoving, which normally calls _update. #self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self._update),repeat=True) self._botUpdateTimer = bs.Timer(50,bs.WeakCall(self.zUpdate),repeat=True) def zUpdate(self): # update one of our bot lists each time through.. # first off, remove dead bots from the list # (we check exists() here instead of dead.. we want to keep them around even if they're just a corpse) #####This is overloaded from bsSpaz to prevent zombies from attacking player Zombies. try: botList = self._botLists[self._botUpdateList] = [b for b in self._botLists[self._botUpdateList] if b.exists()] except Exception: bs.printException("error updating bot list: "+str(self._botLists[self._botUpdateList])) self._botUpdateList = (self._botUpdateList+1)%self._botListCount # update our list of player points for the bots to use playerPts = [] for player in bs.getActivity().players: try: if player.isAlive(): if player.gameData['lives'] > 0: #If the player has lives, add to attack points playerPts.append((bs.Vector(*player.actor.node.position), bs.Vector(*player.actor.node.velocity))) except Exception: bs.printException('error on bot-set _update') for b in botList: b._setPlayerPts(playerPts) b._updateAI() class ZombieHorde(bs.TeamGameActivity): @classmethod def getName(cls): return 'Zombie Horde' @classmethod def getScoreInfo(cls): return {'scoreName':'score', 'scoreType':'points', 'noneIsWinner':False, 'lowerIsBetter':False} @classmethod def getDescription(cls,sessionType): return 'Kill walkers for points!' @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): settings = [("Lives Per Player",{'default':1,'minValue':1,'maxValue':10,'increment':1}), ("Max Zombies", {'default':10,'minValue':5, 'maxValue':50,'increment':5}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':120}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Epic Mode",{'default':False})] if issubclass(sessionType,bs.TeamsSession): settings.append(("Solo Mode",{'default':False})) settings.append(("Balance Total Lives",{'default':False})) return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # show messages when players die since it's meaningful here self.announcePlayerDeaths = True #Need to create our Zombie character. It's a composite of several. #We'll name it 'Kronk2' try: self._soloMode = settings['Solo Mode'] except Exception: self._soloMode = False self._scoreBoard = bs.ScoreBoard() self.spazList = [] self.zombieQ = 0 activity = bs.getActivity() try: myFactory = activity._sharedSpazFactory except Exception: myFactory = activity._sharedSpazFactory = bsSpaz.SpazFactory() #Load up resources for our composite model appears=['Kronk','Zoe','Pixel','Agent Johnson','Bones','Frosty','Kronk2'] myAppear = copy.copy(bsSpaz.appearances['Kronk']) myAppear.name = 'Kronk2' bsSpaz.appearances['Kronk2'] = myAppear for appear in appears: myFactory._getMedia(appear) #Now all the media is loaded up for the spazzes we are pulling from. med = myFactory.spazMedia med['Kronk2']['headModel'] = med['Zoe']['headModel'] med['Kronk2']['colorTexture']=med['Agent Johnson']['colorTexture'] med['Kronk2']['colorMaskTexture']=med['Pixel']['colorMaskTexture'] med['Kronk2']['torsoModel'] = med['Bones']['torsoModel'] med['Kronk2']['pelvisModel'] = med['Pixel']['pelvisModel'] med['Kronk2']['upperArmModel'] = med['Frosty']['upperArmModel'] med['Kronk2']['foreArmModel'] = med['Frosty']['foreArmModel'] med['Kronk2']['handModel'] = med['Bones']['handModel'] med['Kronk2']['upperLegModel'] = med['Bones']['upperLegModel'] med['Kronk2']['lowerLegModel'] = med['Pixel']['lowerLegModel'] med['Kronk2']['toesModel'] = med['Bones']['toesModel'] def getInstanceDescription(self): 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!' def getInstanceScoreBoardDescription(self): 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!' def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onTeamJoin(self,team): team.gameData['score'] = 0 team.gameData['spawnOrder'] = [] self._updateScoreBoard() def onPlayerJoin(self, player): # no longer allowing mid-game joiners here... too easy to exploit if self.hasBegun(): player.gameData['lives'] = 0 player.gameData['icons'] = [] # make sure our team has survival seconds set if they're all dead # (otherwise blocked new ffa players would be considered 'still alive' in score tallying) #if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None: # player.getTeam().gameData['survivalSeconds'] = 1000 bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) return player.gameData['lives'] = self.settings['Lives Per Player'] if self._soloMode: player.gameData['icons'] = [] player.getTeam().gameData['spawnOrder'].append(player) self._updateSoloMode() else: # create our icon and spawn player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)] if player.gameData['lives'] > 0: self.spawnPlayer(player) # dont waste time doing this until begin if self.hasBegun(): self._updateIcons() def _updateSoloMode(self): # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) break def _updateIcons(self): # in free-for-all mode, everyone is just lined up along the bottom if isinstance(self.getSession(),bs.FreeForAllSession): count = len(self.teams) xOffs = 85 x = xOffs*(count-1) * -0.5 for i,team in enumerate(self.teams): if len(team.players) == 1: player = team.players[0] for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs # in teams mode we split up teams else: if self._soloMode: # first off, clear out all icons for player in self.players: player.gameData['icons'] = [] # now for each team, cycle through our available players adding icons for team in self.teams: if team.getID() == 0: x = -60 xOffs = -78 else: x = 60 xOffs = 78 isFirst = True testLives = 1 while True: playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives] if len(playersWithLives) == 0: break for player in playersWithLives: player.gameData['icons'].append(Icon(player, position=(x,(40 if isFirst else 25)), scale=1.0 if isFirst else 0.5, nameMaxWidth=130 if isFirst else 75, nameScale=0.8 if isFirst else 1.0, flatness=0.0 if isFirst else 1.0, shadow=0.5 if isFirst else 1.0, showDeath=True if isFirst else False, showLives=False)) x += xOffs * (0.8 if isFirst else 0.56) isFirst = False testLives += 1 # non-solo mode else: for team in self.teams: if team.getID() == 0: x = -50 xOffs = -85 else: x = 50 xOffs = 85 for player in team.players: for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs def _getSpawnPoint(self,player): # in solo-mode, if there's an existing live player on the map, spawn at whichever # spot is farthest from them (keeps the action spread out) if self._soloMode: livingPlayer = None for team in self.teams: for player in team.players: if player.isAlive(): p = player.actor.node.position livingPlayer = player livingPlayerPos = p break if livingPlayer: playerPos = bs.Vector(*livingPlayerPos) points = [] for team in self.teams: startPos = bs.Vector(*self.getMap().getStartPosition(team.getID())) points.append([(startPos-playerPos).length(),startPos]) points.sort() return points[-1][1] else: return None else: return None def spawnPlayer(self,player): """This next line is the default spawn line. But we need to spawn our special guy""" #self.spawnPlayerSpaz(player,self._getSpawnPoint(player)) #position = self._getSpawnPoint(player) #if isinstance(self.getSession(), bs.TeamsSession): # position = self.getMap().getStartPosition(player.getTeam().getID()) #else: # # otherwise do free-for-all spawn locations position = self.getMap().getFFAStartPosition(self.players) angle = 20 #spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up #spaz.connectControlsToPlayer(enablePunch=False, # enableBomb=False, # enablePickUp=False) # also lets have them make some noise when they die.. #spaz.playBigDeathSound = True name = player.getName() lightColor = bsUtils.getNormalizedColor(player.color) displayColor = bs.getSafeColor(player.color, targetIntensity=0.75) spaz = PlayerSpaz_Zom(color=player.color, highlight=player.highlight, character=player.character, player=player) player.setActor(spaz) #For some reason, I can't figure out how to get a list of all spaz. #Therefore, I am making the list here so I can get which spaz belongs #to the player supplied by HitMessage. self.spazList.append(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() #Unfortunately, I can't figure out how to prevent picking up other player but allow other pickup. factory = spaz.getFactory() self.scoreSet.playerGotNewSpaz(player, spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360))) t = bs.getGameTime() bs.playSound(self._spawnSound, 1, position=spaz.node.position) light = bs.newNode('light', attrs={'color': lightColor}) spaz.node.connectAttr('position', light, 'position') bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0}) bs.gameTimer(500, light.delete) #Start code to spawn special guy: #End of code to spawn special guy if not self._soloMode: bs.gameTimer(300,bs.Call(self._printLives,player)) # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerSpawned() def respawnPlayerZombie(self,player,respawnTime=None): """ Given a bs.Player, sets up a standard respawn timer, along with the standard counter display, etc. At the end of the respawn period spawnPlayer() will be called if the Player still exists. An explicit 'respawnTime' can optionally be provided (in milliseconds). """ if player is None or not player.exists(): if player is None: bs.printError('None passed as player to respawnPlayer()') else: bs.printError('Nonexistant bs.Player passed to respawnPlayer(); call player.exists() to make sure a player is still there.') return if player.getTeam() is None: bs.printError('player has no team in respawnPlayer()') return if respawnTime is None: if len(player.getTeam().players) == 1: respawnTime = 3000 elif len(player.getTeam().players) == 2: respawnTime = 5000 elif len(player.getTeam().players) == 3: respawnTime = 6000 else: respawnTime = 7000 # if this standard setting is present, factor it in if 'Respawn Times' in self.settings: respawnTime *= self.settings['Respawn Times'] respawnTime = int(max(1000,respawnTime)) if respawnTime%1000 != 0: respawnTime -= respawnTime%1000 # we want whole seconds if player.actor and not self.hasEnded(): import bsSpaz player.gameData['respawnTimer'] = bs.Timer(respawnTime,bs.WeakCall(self.spawnPlayerIfExistsAsZombie,player)) player.gameData['respawnIcon'] = bsSpaz.RespawnIcon(player,respawnTime) def spawnPlayerIfExistsAsZombie(self,player): """ A utility method which calls self.spawnPlayer() *only* if the bs.Player provided still exists; handy for use in timers and whatnot. There is no need to override this; just override spawnPlayer(). """ if player.exists(): self.spawnPlayerZombie(player) def spawnPlayerZombie(self,player): position = self.getMap().getFFAStartPosition(self.players) angle = 20 name = player.getName() lightColor = bsUtils.getNormalizedColor(player.color) displayColor = bs.getSafeColor(player.color, targetIntensity=0.75) spaz = PlayerZombie(color=player.color, highlight=player.highlight, character='Kronk2', player=player) player.setActor(spaz) #For some reason, I can't figure out how to get a list of all spaz. #Therefore, I am making the list here so I can get which spaz belongs #to the player supplied by HitMessage. self.spazList.append(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) #Need to prevent picking up powerups: pam = bs.Powerup.getFactory().powerupAcceptMaterial for attr in ['materials','rollerMaterials','extrasMaterials']: materials = getattr(spaz.node,attr) if pam in materials: setattr(spaz.node,attr,tuple(m for m in materials if m != pam)) #spaz.node.materials.remove(pam) #spaz.node.rollerMaterials.remove(pam) #spaz.node.extrasMaterials.remove(pam) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer(enablePunch=True, enableBomb=False, enablePickUp=False) #Unfortunately, I can't figure out how to prevent picking up other player but allow other pickup. self.scoreSet.playerGotNewSpaz(player, spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360))) t = bs.getGameTime() bs.playSound(self._spawnSound, 1, position=spaz.node.position) light = bs.newNode('light', attrs={'color': lightColor}) spaz.node.connectAttr('position', light, 'position') bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0}) bs.gameTimer(500, light.delete) #Start code to spawn special guy: #End of code to spawn special guy if not self._soloMode: bs.gameTimer(300,bs.Call(self._printLives,player)) # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerSpawned() def _printLives(self,player): if not player.exists() or not player.isAlive(): return try: pos = player.actor.node.position except Exception,e: print 'EXC getting player pos in bsElim',e return if player.gameData['lives'] > 0: bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1), offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain() else: bs.PopupText('Dead!',color=(1,1,0,1), offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain() def onPlayerLeave(self,player): bs.TeamGameActivity.onPlayerLeave(self,player) player.gameData['icons'] = None # remove us from spawn-order if self._soloMode: if player in player.getTeam().gameData['spawnOrder']: player.getTeam().gameData['spawnOrder'].remove(player) # update icons in a moment since our team will be gone from the list then bs.gameTimer(0, self._updateIcons) def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops() self.zombieQ = 1 # queue of zombies to spawn. this will increment/decrement if self._soloMode: self._vsText = bs.NodeActor(bs.newNode("text", attrs={'position':(0,105), 'hAttach':"center", 'hAlign':'center', 'maxWidth':200, 'shadow':0.5, 'vrDepth':390, 'scale':0.6, 'vAttach':"bottom", 'color':(0.8,0.8,0.3,1.0), 'text':bs.Lstr(resource='vsText')})) # if balance-team-lives is on, add lives to the smaller team until total lives match if (isinstance(self.getSession(),bs.TeamsSession) and self.settings['Balance Total Lives'] and len(self.teams[0].players) > 0 and len(self.teams[1].players) > 0): if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]): lesserTeam = self.teams[0] greaterTeam = self.teams[1] else: lesserTeam = self.teams[1] greaterTeam = self.teams[0] addIndex = 0 while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam): lesserTeam.players[addIndex].gameData['lives'] += 1 addIndex = (addIndex + 1) % len(lesserTeam.players) #Let's add a couple of bots # this wrangles our bots self._bots = zBotSet() #Set colors and character for ToughGuyBot to be zombie setattr(bs.ToughGuyBot, 'color', (0.4,0.1,0.05)) setattr(bs.ToughGuyBot, 'highlight', (0.2,0.4,0.3)) setattr(bs.ToughGuyBot, 'character', 'Kronk2') # start some timers to spawn bots thePt = self.getMap().getFFAStartPosition(self.players) #bs.gameTimer(1000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=thePt,spawnTime=3000)) self._updateIcons() self._updateScoreBoard # we could check game-over conditions at explicit trigger points, # but lets just do the simple thing and poll it... bs.gameTimer(1000, self._update, repeat=True) def _getTotalTeamLives(self,team): return sum(player.gameData['lives'] for player in team.players) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() #print([player, m.spaz.hitPoints, "killed by", m.killerPlayer]) if player.gameData['lives'] > 0: #Dying player was not zombie. Remove a life player.gameData['lives'] -= 1 else: #Dying player was a zombie. Give points to killer if m.killerPlayer.exists(): if m.killerPlayer.gameData['lives'] > 0: m.killerPlayer.getTeam().gameData['score'] += 2 self._updateScoreBoard() #Remove this spaz from the list of active spazzes if m.spaz in self.spazList: self.spazList.remove(m.spaz) if player.gameData['lives'] < 0: bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode)) player.gameData['lives'] = 0 # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerDied() # play big death sound on our last death or for every one in solo mode if self._soloMode or player.gameData['lives'] == 0: bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound) # if we hit zero lives, we're dead. Become a zombie. if player.gameData['lives'] == 0: self.respawnPlayerZombie(player) else: # otherwise, in regular mode, respawn.. if not self._soloMode: self.respawnPlayer(player) # in solo, put ourself at the back of the spawn order if self._soloMode: player.getTeam().gameData['spawnOrder'].remove(player) player.getTeam().gameData['spawnOrder'].append(player) elif isinstance(m,bs.SpazBotDeathMessage): self._onSpazBotDied(m) bs.TeamGameActivity.handleMessage(self,m) #bs.PopupText("died",position=self._position,color=popupColor,scale=popupScale).autoRetain() else: bs.TeamGameActivity.handleMessage(self,m) def _update(self): #self.randZombie() #Check if we neeed more zombies if self.zombieQ > 0: self.zombieQ -= 1 self.spawnZombie() if self._soloMode: # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) self._updateIcons() break # if we're down to 1 or fewer living teams, start a timer to end the game # (allows the dust to settle and draws to occur if deaths are close enough) #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. teamsRemain = self._getLivingTeams() if len(teamsRemain) < 2: if len(teamsRemain) == 1: theScores = [] for team in self.teams: theScores.append(team.gameData['score']) if teamsRemain[0].gameData['score']< max(theScores): pass # the last guy doesn't have the best score elif teamsRemain[0].gameData['score'] == max(theScores) and theScores.count(max(theScores)) > 1: pass #The last guy left is tied for the lead! Can he get one more? else: self._roundEndTimer = bs.Timer(500,self.endGame) else: self._roundEndTimer = bs.Timer(500,self.endGame) def spawnZombie(self): #We need a Z height... thePt = list(self.getRandomPointInPlay()) thePt2 = self.getMap().getFFAStartPosition(self.players) thePt[1] = thePt2[1] bs.gameTimer(100,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=thePt,spawnTime=1000)) def _onSpazBotDied(self,DeathMsg): #Just in case we are over max... if len(self._bots.getLivingBots()) < self.settings['Max Zombies']: #Go ahead and replace dead zombie, no matter how it died self.zombieQ +=1 if DeathMsg.killerPlayer is None: pass else: player = DeathMsg.killerPlayer #print(player) if not player.exists(): return # could happen if they leave after throwing a bomb.. if player.gameData['lives'] < 1: return #You only get a point if you have lives player.getTeam().gameData['score'] += 1 #if kill was legit, spawn additional zombie! self.zombieQ += 1 self._updateScoreBoard() def getRandomPointInPlay(self): #So far, randomized points only figured out for mostly rectangular maps. #Boxes will still fall through holes, but shouldn't be terrible problem (hopefully) #If you add stuff here, need to add to "supported maps" above. #['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium'] myMap = self.getMap().getName() #print(myMap) if myMap == 'Doom Shroom': while True: x = random.uniform(-1.0,1.0) y = random.uniform(-1.0,1.0) if x*x+y*y < 1.0: break return ((8.0*x,8.0,-3.5+5.0*y)) elif myMap == 'Rampage': x = random.uniform(-6.0,7.0) y = random.uniform(-6.0,-2.5) return ((x, 8.0, y)) elif myMap == 'Hockey Stadium': x = random.uniform(-11.5,11.5) y = random.uniform(-4.5,4.5) return ((x, 5.0, y)) elif myMap == 'Courtyard': x = random.uniform(-4.3,4.3) y = random.uniform(-4.4,0.3) return ((x, 8.0, y)) elif myMap == 'Crag Castle': x = random.uniform(-6.7,8.0) y = random.uniform(-6.0,0.0) return ((x, 12.0, y)) elif myMap == 'Big G': x = random.uniform(-8.7,8.0) y = random.uniform(-7.5,6.5) return ((x, 8.0, y)) elif myMap == 'Football Stadium': x = random.uniform(-12.5,12.5) y = random.uniform(-5.0,5.5) return ((x, 8.0, y)) else: x = random.uniform(-5.0,5.0) y = random.uniform(-6.0,0.0) return ((x, 8.0, y)) def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team, team.gameData['score']) def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)] def endGame(self): if self.hasEnded(): return #Reset the default color for the ToughGuyBot setattr(bs.ToughGuyBot, 'color', (0.6,0.6,0.6)) setattr(bs.ToughGuyBot, 'highlight', (0.6,0.6,0.6)) setattr(bs.ToughGuyBot, 'character', 'Kronk') results = bs.TeamGameResults() self._vsText = None # kill our 'vs' if its there for team in self.teams: results.setTeamScore(team, team.gameData['score']) self.end(results=results) ================================================ FILE: mods/airStrike.json ================================================ { "name": "Air Strike", "author": "SoKpl", "category": "minigames" } ================================================ FILE: mods/airStrike.py ================================================ import bs import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [AirStrikeGame] def bsGetLevels(): # Levels are unique named instances of a particular game with particular settings. # They show up as buttons in the co-op section, get high-score lists associated with them, etc. return [bs.Level('Air Strike', # globally-unique name for this level (not seen by user) displayName='${GAME}', # ${GAME} will be replaced by the results of the game's getName() call gameType=AirStrikeGame, settings={}, # we currently dont have any settings; we'd specify them here if we did. previewTexName='courtyardPreview')] class AirStrikeGame(bs.TeamGameActivity): # name seen by the user @classmethod def getName(cls): return 'Air Strike' @classmethod def getScoreInfo(cls): return {'scoreType':'milliseconds', 'lowerIsBetter':True, 'scoreName':'Time'} @classmethod def getDescription(cls,sessionType): return 'Enemies are coming from air! Kill them all!' @classmethod def getSupportedMaps(cls,sessionType): # for now we're hard-coding spawn positions and whatnot # so we need to be sure to specity that we only support # a specific map.. return ['Courtyard'] @classmethod def supportsSessionType(cls,sessionType): # we currently support Co-Op only return True if issubclass(sessionType,bs.CoopSession) else False # in the constructor we should load any media we need/etc. # but not actually create anything yet. def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self._winSound = bs.getSound("score") # called when our game is transitioning in but not ready to start.. # ..we can go ahead and start creating stuff, playing music, etc. def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='ToTheDeath') # called when our game actually starts def onBegin(self): bs.TeamGameActivity.onBegin(self) self._won = False self.setupStandardPowerupDrops() # make our on-screen timer and start it roughly when our bots appear self._timer = bs.OnScreenTimer() bs.gameTimer(4000,self._timer.start) # this wrangles our bots self._bots = bs.BotSet() # spawn some baddies self._bots = bs.BotSet() bs.gameTimer(1000,bs.Call(self._bots.spawnBot,bs.MelBotStatic,pos=(6,7,-6),spawnTime=3000)) bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.ToughGuyBotProShielded,pos=(-3,10,-2),spawnTime=3000)) bs.gameTimer(3000,bs.Call(self._bots.spawnBot,bs.NinjaBotProShielded,pos=(5,6,-2),spawnTime=3000)) bs.gameTimer(4000,bs.Call(self._bots.spawnBot,bs.BomberBotProShielded,pos=(-5,6,-2),spawnTime=3000)) bs.gameTimer(5000,bs.Call(self._bots.spawnBot,bs.ChickBotStatic,pos=(-6,7,-6),spawnTime=3000)) # note: if spawns were spread out more we'd probably want to set some sort of flag on the # last spawn to ensure we don't inadvertantly allow a 'win' before every bot is spawned. # (ie: if bot 1, 2, and 3 got killed but 4 hadn't spawned yet, the game might end because # it sees no remaining bots. # called for each spawning player def spawnPlayer(self,player): # lets spawn close to the center spawnCenter = (0,3,-2) pos = (spawnCenter[0]+random.uniform(-1.5,1.5),spawnCenter[1],spawnCenter[2]+random.uniform(-1.5,1.5)) self.spawnPlayerSpaz(player,position=pos) def _checkIfWon(self): # simply end the game if there's no living bots.. if not self._bots.haveLivingBots(): self._won = True self.endGame() # called for miscellaneous events def handleMessage(self,m): # a player has died if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # do standard stuff self.respawnPlayer(m.spaz.getPlayer()) # kick off a respawn # a spaz-bot has died elif isinstance(m,bs.SpazBotDeathMessage): # unfortunately the bot-set will always tell us there are living # bots if we ask here (the currently-dying bot isn't officially marked dead yet) # ..so lets push a call into the event loop to check once this guy has finished dying. bs.pushCall(self._checkIfWon) else: # let the base class handle anything we don't.. bs.TeamGameActivity.handleMessage(self,m) # when this is called, we should fill out results and end the game # *regardless* of whether is has been won. (this may be called due # to a tournament ending or other external reason) def endGame(self): # stop our on-screen timer so players can see what they got self._timer.stop() results = bs.TeamGameResults() # if we won, set our score to the elapsed time # (there should just be 1 team here since this is co-op) # ..if we didn't win, leave scores as default (None) which means we lost if self._won: elapsedTime = bs.getGameTime()-self._timer.getStartTime() self.cameraFlash() bs.playSound(self._winSound) for team in self.teams: team.celebrate() # woooo! par-tay! results.setTeamScore(team,elapsedTime) # ends this activity.. self.end(results) ================================================ FILE: mods/arms_race.json ================================================ { "name": "Arms Race", "author": "Mrmaxmeier", "category": "minigames" } ================================================ FILE: mods/arms_race.py ================================================ import bs import bsUtils import random class State: def __init__(self, bomb=None, grab=False, punch=False, curse=False, required=False, final=False, name=""): self.bomb = bomb self.grab = grab self.punch = punch self.pickup = False self.curse = curse self.required = required or final self.final = final self.name = name self.next = None self.index = None def apply(self, spaz): spaz.disconnectControlsFromPlayer() spaz.connectControlsToPlayer(enablePunch=self.punch, enableBomb=bool(self.bomb), enablePickUp=self.grab) if self.curse: spaz.curseTime = -1 spaz.curse() if self.bomb: spaz.bombType = self.bomb spaz.setScoreText(self.name) def getSetting(self): return (self.name, {'default': True}) class ArmsRace(bs.TeamGameActivity): states = [ State(bomb='normal', name='Basic Bombs'), State(bomb='ice', name='Frozen Bombs'), State(bomb='sticky', name='Sticky Bombs'), State(bomb='impact', name='Impact Bombs'), State(grab=True, name='Grabbing only'), State(punch=True, name='Punching only'), State(curse=True, name='Cursed', final=True) ] @classmethod def getName(cls): return 'Arms Race' @classmethod def getScoreInfo(cls): return {'scoreType': 'points', 'lowerIsBetter': False, 'scoreName': 'Score'} @classmethod def getDescription(cls, sessionType): return "Upgrade your weapon by eliminating enemies.\nWin the match by being the first player\nto get a kill while cursed." def getInstanceDescription(self): return 'Upgrade your weapon by eliminating enemies.' def getInstanceScoreBoardDescription(self): return 'Kill {} Players to win'.format(len(self.states)) @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.TeamsSession) or issubclass(sessionType, bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls, sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls, sessionType): settings = [("Epic Mode", {'default': False}), ("Time Limit", {'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120), ('5 Minutes', 300)], 'default': 0})] for state in cls.states: if not state.required: settings.append(state.getSetting()) return settings def __init__(self, settings): self.states = [s for s in self.states if settings.get(s.name, True)] for i, state in enumerate(self.states): if i < len(self.states) and not state.final: state.next = self.states[i + 1] state.index = i bs.TeamGameActivity.__init__(self, settings) self.announcePlayerDeaths = True if self.settings['Epic Mode']: self._isSlowMotion = True def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) #self.setupStandardPowerupDrops(enableTNT=False) def onPlayerJoin(self, player): if 'state' not in player.gameData: player.gameData['state'] = self.states[0] self.spawnPlayer(player) # overriding the default character spawning.. def spawnPlayer(self, player): state = player.gameData['state'] super(self.__class__, self).spawnPlayer(player) state.apply(player.actor) def isValidKill(self, m): return all([ m.killed, m.spaz.getPlayer() is not m.killerPlayer, m.killerPlayer is not None, m.spaz.getPlayer().color is not m.killerPlayer.color ]) # various high-level game events come through this method def handleMessage(self,m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior if self.isValidKill(m): if not m.killerPlayer.gameData["state"].final: m.killerPlayer.gameData["state"] = m.killerPlayer.gameData["state"].next m.killerPlayer.gameData["state"].apply(m.killerPlayer.actor) else: self.endGame() self.respawnPlayer(m.spaz.getPlayer()) else: super(self.__class__, self).handleMessage(m) def endGame(self): results = bs.TeamGameResults() for team in self.teams: score = max([player.gameData["state"].index for player in team.players]) results.setTeamScore(team, score) self.end(results=results) def bsGetAPIVersion(): return 4 def bsGetGames(): return [ArmsRace] ================================================ FILE: mods/auto_reloader.json ================================================ { "name": "Auto Reloader", "author": "Mrmaxmeier", "category": "utilities", "supports": ["config_editor"] } ================================================ FILE: mods/auto_reloader.py ================================================ import bs import os import os.path from md5 import md5 import weakref import imp import sys default = { "_version": 1, "_name": "Auto Reload", "enabled": dict(_default=True, _name="Enable"), "check_interval": dict(_default=2.5, _min=1, _inc=0.5, _max=10, _name="Check interval"), "folder": dict(_default="auto_reloader_mods", _name="Folder") } if bs.getConfig().get("auto_reloader", default)["_version"] != default["_version"]: bs.getConfig()["auto_reloader"] = default bs.getConfig()["auto_reloader"] = bs.getConfig().get("auto_reloader", default) def cfg(key): return bs.getConfig()["auto_reloader"][key].get("_value", bs.getConfig()["auto_reloader"][key]["_default"]) CHECK_INTERVAL = int(cfg("check_interval") * 1000) IMPORT_FOLDER = bs.getEnvironment()['userScriptsDirectory'] + "/" + cfg("folder") + "/" sys.path.append(IMPORT_FOLDER) # FIXME class GameWrapper(object): _game = None _type = None _instances = weakref.WeakSet() def __init__(self, filename): self._filename = filename with open(IMPORT_FOLDER + self._filename, "r") as f: self._module_md5 = md5(f.read()).hexdigest() self._did_print_error = False self._import_module() if self._is_available() and self._type == "game": self._game = self._module.bsGetGames()[0] else: self._game = None def _import_module(self): try: data = imp.find_module(self._filename[:-3]) self._module = imp.load_module(self._filename[:-3], *data) except Exception, e: import traceback traceback.print_exc() self._module = None self._game = None self._module_error(str(e)) def _module_error(self, *args): print(self._filename + ": " + " ".join(args)) if not self._did_print_error: bs.screenMessage(self._filename + ": " + " ".join(args), color=(1, 0, 0)) self._did_print_error = True def _is_available(self): if not self._module: return False if not hasattr(self._module, '_supports_auto_reloading'): self._module_error('missing _supports_auto_reloading') return False if not hasattr(self._module, '_auto_reloader_type'): self._module_error('missing _auto_reloader_type') return False self._type = self._module._auto_reloader_type if not hasattr(self._module, '_prepare_reload'): self._module_error('missing _prepare_reload') return False if not hasattr(self._module, 'bsGetAPIVersion'): self._module_error('missing bsGetAPIVersion') return False if self._type == "game" and not hasattr(self._module, 'bsGetGames'): self._module_error('missing bsGetGames') return False if not self._module._supports_auto_reloading: self._module_error('doesnt support auto reloading') return False if not self._module.bsGetAPIVersion() == 3: self._module_error('missing wrong API Version', self._module.bsGetAPIVersion()) return False if self._type == "game" and len(self._module.bsGetGames()) != 1: self._module_error("more than 1 game isnt supported") # FIXME return False return True def _prepare_reload(self): try: if hasattr(self._module, "_prepare_reload"): self._module._prepare_reload() for instance in self._instances: if instance and hasattr(instance, "_prepare_reload"): instance._prepare_reload() except Exception, e: print(e) self._module_error("_prepare_reload failed") def _reload_module(self): bs.screenMessage("reloading " + self._filename) self._prepare_reload() self._import_module() #self._module = import_module(self._filename[:-3], package=IMPORT_FOLDER.split("/")[-2]) with open(IMPORT_FOLDER + self._filename, "r") as f: self._module_md5 = md5(f.read()).hexdigest() self._did_print_error = False if self._is_available() and self._type == "game": self._game = self._module.bsGetGames()[0] else: self._game = None bs.playSound(bs.getSound('swish')) def _check_update(self): with open(IMPORT_FOLDER + self._filename, "r") as f: data = f.read() if self._module_md5 != md5(data).hexdigest(): self._reload_module() def __call__(self, *args, **kwargs): if not self._type == "game": self._module_error("non games can't be called") instance = self._game(*args, **kwargs) self._instances.add(instance) return instance def __getattr__(self, key): "pass static methods" return getattr(self._game, key) wrappers = [] if cfg("enabled"): if not os.path.isdir(IMPORT_FOLDER): os.mkdir(IMPORT_FOLDER) for file in os.listdir(IMPORT_FOLDER): if not file.endswith(".py"): continue if file.startswith("."): continue wrappers.append(GameWrapper(file)) wrappers = [w for w in wrappers if w._is_available()] # print("tracking mods:", [wrapper._filename for wrapper in wrappers]) def check_wrappers(): for wrapper in wrappers: wrapper._check_update() bs.realTimer(CHECK_INTERVAL, check_wrappers) if CHECK_INTERVAL: bs.realTimer(CHECK_INTERVAL, check_wrappers) def bsGetAPIVersion(): return 4 def bsGetGames(): return [wrapper for wrapper in wrappers if wrapper._type == "game"] ================================================ FILE: mods/bomb_on_my_head.json ================================================ { "name": "Bomb on my Head", "author": "Mrmaxmeier", "category": "minigames" } ================================================ FILE: mods/bomb_on_my_head.py ================================================ import random import bs import bsUtils from bsSpaz import _BombDiedMessage class PlayerSpazBombOnMyHead(bs.PlayerSpaz): def handleMessage(self, m): if isinstance(m, _BombDiedMessage): #bs.screenMessage('recyceling') self.bombCount += 1 self.checkAvalibleBombs() else: super(self.__class__, self).handleMessage(m) def checkAvalibleBombs(self): if self.exists(): if self.bombCount >= 1: if not self.node.holdNode.exists(): self.onBombPress() self.onBombRelease() def startBombChecking(self): self.checkAvalibleBombs() self._bombCheckTimer = bs.gameTimer(500, bs.WeakCall(self.checkAvalibleBombs), repeat=True) def dropBomb(self): lifespan = 3000 if (self.bombCount <= 0) or self.frozen: return p = self.node.positionForward v = self.node.velocity bombType = "normal" bomb = bs.Bomb(position=(p[0], p[1] - 0.0, p[2]), velocity=(v[0], v[1], v[2]), bombType=bombType, blastRadius=self.blastRadius, sourcePlayer=self.sourcePlayer, owner=self.node).autoRetain() bsUtils.animate(bomb.node, 'modelScale', {0:0.0, lifespan*0.1:1.5, lifespan*0.5:1.0}) self.bombCount -= 1 bomb.node.addDeathAction(bs.WeakCall(self.handleMessage, _BombDiedMessage())) self._pickUp(bomb.node) for meth in self._droppedBombCallbacks: meth(self, bomb) return bomb def bsGetAPIVersion(): return 4 def bsGetGames(): return [BombOnMyHead] class BombOnMyHead(bs.TeamGameActivity): @classmethod def getName(cls): return 'Bomb on my Head' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'milliseconds', 'scoreVersion':'B'} @classmethod def getDescription(cls, sessionType): return "You'll always have a bomb on your head. \n Survive as long as you can!" def getInstanceDescription(self): return 'Survive as long as you can' @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.TeamsSession) or issubclass(sessionType, bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls, sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): return [("Time Limit", {'choices':[('None', 0), ('1 Minute', 60), ('2 Minutes', 120), ('5 Minutes', 300), ('10 Minutes', 600), ('20 Minutes', 1200)], 'default':0}), ("Max Bomb Limit", {'choices':[('Normal', 1.0), ('Two', 2.0), ('Three', 3.0), ('Four', 4.0)], 'default':1.0}), ("Epic Mode", {'default':False})] def __init__(self, settings): bs.TeamGameActivity.__init__(self, settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die (since its meaningful in this game) self.announcePlayerDeaths = True self._lastPlayerDeathTime = None self.startTime = 1000 def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Chosen One') def onBegin(self): bs.TeamGameActivity.onBegin(self) # overriding the default character spawning.. def spawnPlayer(self, player): if isinstance(self.getSession(), bs.TeamsSession): position = self.getMap().getStartPosition(player.getTeam().getID()) else: # otherwise do free-for-all spawn locations position = self.getMap().getFFAStartPosition(self.players) angle = None #spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up #spaz.connectControlsToPlayer(enablePunch=False, # enableBomb=False, # enablePickUp=False) # also lets have them make some noise when they die.. #spaz.playBigDeathSound = True name = player.getName() lightColor = bsUtils.getNormalizedColor(player.color) displayColor = bs.getSafeColor(player.color, targetIntensity=0.75) spaz = PlayerSpazBombOnMyHead(color=player.color, highlight=player.highlight, character=player.character, player=player) player.setActor(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() self.scoreSet.playerGotNewSpaz(player, spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360))) t = bs.getGameTime() bs.playSound(self._spawnSound, 1, position=spaz.node.position) light = bs.newNode('light', attrs={'color':lightColor}) spaz.node.connectAttr('position', light, 'position') bsUtils.animate(light, 'intensity', {0:0, 250:1, 500:0}) bs.gameTimer(500, light.delete) #bs.gameTimer(1000, bs.WeakCall(spaz.onBombPress)) bs.gameTimer(self.startTime, bs.WeakCall(spaz.startBombChecking)) spaz.setBombCount(self.settings['Max Bomb Limit']) # various high-level game events come through this method def handleMessage(self,m): if isinstance(m, bs.PlayerSpazDeathMessage): super(self.__class__, self).handleMessage(m)#bs.TeamGameActivity.handleMessage(self,m) # (augment standard behavior) deathTime = bs.getGameTime() # record the player's moment of death m.spaz.getPlayer().gameData['deathTime'] = deathTime # in co-op mode, end the game the instant everyone dies (more accurate looking) # in teams/ffa, allow a one-second fudge-factor so we can get more draws if isinstance(self.getSession(), bs.CoopSession): # teams will still show up if we check now.. check in the next cycle bs.pushCall(self._checkEndGame) self._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock.. else: bs.gameTimer(1000, self._checkEndGame) else: # default handler: super(self.__class__, self).handleMessage(m)#bs.TeamGameActivity.handleMessage(self,m) def _checkEndGame(self): livingTeamCount = 0 for team in self.teams: for player in team.players: if player.isAlive(): livingTeamCount += 1 break # in co-op, we go till everyone is dead.. otherwise we go until one team remains if isinstance(self.getSession(), bs.CoopSession): if livingTeamCount <= 0: self.endGame() else: if livingTeamCount <= 1: self.endGame() def endGame(self): curTime = bs.getGameTime() # mark 'death-time' as now for any still-living players # and award players points for how long they lasted. # (these per-player scores are only meaningful in team-games) for team in self.teams: for player in team.players: # throw an extra fudge factor +1 in so teams that # didn't die come out ahead of teams that did if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime+1 - self.startTime # award a per-player score depending on how many seconds they lasted # (per-player scores only affect teams mode; everywhere else just looks at the per-team score) score = (player.gameData['deathTime']) if 'deathTime' not in player.gameData: score += 50 # a bit extra for survivors self.scoreSet.playerScored(player, score, screenMessage=False) # ok now calc game results: set a score for each team and then tell the game to end results = bs.TeamGameResults() # remember that 'free-for-all' mode is simply a special form of 'teams' mode # where each player gets their own team, so we can just always deal in teams # and have all cases covered for team in self.teams: # set the team score to the max time survived by any player on that team longestLife = 0 for player in team.players: longestLife = max(longestLife, (player.gameData['deathTime'] - self.startTime)) results.setTeamScore(team, longestLife) self.end(results=results) ================================================ FILE: mods/bomberman.json ================================================ { "name": "Bomberman", "author": "Mrmaxmeier", "category": "minigames", "tag": "experimental" } ================================================ FILE: mods/bomberman.py ================================================ import bs import bsUtils import bsElimination import bsBomb import bsSpaz import random import math class Map: center = (0, 3, -4) radius = 8 @classmethod def inBounds(cls, pos): dx, dy, dz = pos[0] - cls.center[0], pos[1] - cls.center[1], pos[2] - cls.center[2], return cls.radius >= math.sqrt(dx**2 + dy**2 + dz**2) class Crate(bsBomb.Bomb): def __init__(self, position=(0, 1, 0), velocity=(0, 0, 0)): self.position = position bsBomb.Bomb.__init__(self, position, velocity, bombType='tnt', blastRadius=0.0, sourcePlayer=None, owner=None) self.node.extraAcceleration = (0, -50, 0) def handleMessage(self, m): #if isinstance(m, bs.PickedUpMessage): # self._heldBy = m.node #elif isinstance(m, bs.DroppedMessage): # bs.animate(self._powText, 'scale', {0:0.01, 500: 0.03}) # bs.gameTimer(500, bs.WeakCall(self.pow)) bsBomb.Bomb.handleMessage(self, m) def explode(self): pos = self.position bs.gameTimer(100, bs.WeakCall(bs.getActivity().dropPowerup, pos)) bs.gameTimer(1, bs.WeakCall(self.handleMessage, bs.DieMessage())) class Bomb(bsBomb.Bomb): def explode(self): if self._exploded: return self._exploded = True size = int(self.blastRadius) for mod in range(-size, size+1): pos = self.node.position posX = (pos[0] + mod*1.0, pos[1], pos[2]) posY = (pos[0], pos[1], pos[2] + mod*1.0) if Map.inBounds(posX): bs.gameTimer(abs(mod)*150, bs.Call(blast, posX, self.bombType, self.sourcePlayer, self.hitType, self.hitSubType)) if Map.inBounds(posY): bs.gameTimer(abs(mod)*150, bs.Call(blast, posY, self.bombType, self.sourcePlayer, self.hitType, self.hitSubType)) bs.gameTimer(1, bs.WeakCall(self.handleMessage, bs.DieMessage())) class Blast(bsBomb.Blast): # all that code to reduce the camera shake effect def __init__(self,position=(0,1,0),velocity=(0,0,0),blastRadius=2.0,blastType="normal",sourcePlayer=None,hitType='explosion',hitSubType='normal'): """ Instantiate with given values. """ bs.Actor.__init__(self) factory = Bomb.getFactory() self.blastType = blastType self.sourcePlayer = sourcePlayer self.hitType = hitType; self.hitSubType = hitSubType; # blast radius self.radius = blastRadius self.node = bs.newNode('region', attrs={'position':(position[0],position[1]-0.1,position[2]), # move down a bit so we throw more stuff upward 'scale':(self.radius,self.radius,self.radius), 'type':'sphere', 'materials':(factory.blastMaterial,bs.getSharedObject('attackMaterial'))}, delegate=self) bs.gameTimer(50,self.node.delete) # throw in an explosion and flash explosion = bs.newNode("explosion", attrs={'position':position, 'velocity':(velocity[0],max(-1.0,velocity[1]),velocity[2]), 'radius':self.radius, 'big':(self.blastType == 'tnt')}) if self.blastType == "ice": explosion.color = (0,0.05,0.4) bs.gameTimer(1000,explosion.delete) if self.blastType != 'ice': bs.emitBGDynamics(position=position,velocity=velocity,count=int(1.0+random.random()*4),emitType='tendrils',tendrilType='thinSmoke') bs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*4),emitType='tendrils',tendrilType='ice' if self.blastType == 'ice' else 'smoke') bs.emitBGDynamics(position=position,emitType='distortion',spread=1.0 if self.blastType == 'tnt' else 2.0) # and emit some shrapnel.. if self.blastType == 'ice': def _doEmit(): bs.emitBGDynamics(position=position,velocity=velocity,count=30,spread=2.0,scale=0.4,chunkType='ice',emitType='stickers'); bs.gameTimer(50,_doEmit) # looks better if we delay a bit elif self.blastType == 'sticky': def _doEmit(): bs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),spread=0.7,chunkType='slime'); bs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),scale=0.5, spread=0.7,chunkType='slime'); bs.emitBGDynamics(position=position,velocity=velocity,count=15,scale=0.6,chunkType='slime',emitType='stickers'); bs.emitBGDynamics(position=position,velocity=velocity,count=20,scale=0.7,chunkType='spark',emitType='stickers'); bs.emitBGDynamics(position=position,velocity=velocity,count=int(6.0+random.random()*12),scale=0.8,spread=1.5,chunkType='spark'); bs.gameTimer(50,_doEmit) # looks better if we delay a bit elif self.blastType == 'impact': # regular bomb shrapnel def _doEmit(): bs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),scale=0.8,chunkType='metal'); bs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),scale=0.4,chunkType='metal'); bs.emitBGDynamics(position=position,velocity=velocity,count=20,scale=0.7,chunkType='spark',emitType='stickers'); bs.emitBGDynamics(position=position,velocity=velocity,count=int(8.0+random.random()*15),scale=0.8,spread=1.5,chunkType='spark'); bs.gameTimer(50,_doEmit) # looks better if we delay a bit else: # regular or land mine bomb shrapnel def _doEmit(): if self.blastType != 'tnt': bs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),chunkType='rock'); bs.emitBGDynamics(position=position,velocity=velocity,count=int(4.0+random.random()*8),scale=0.5,chunkType='rock'); bs.emitBGDynamics(position=position,velocity=velocity,count=30,scale=1.0 if self.blastType=='tnt' else 0.7,chunkType='spark',emitType='stickers'); bs.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'); # tnt throws splintery chunks if self.blastType == 'tnt': def _emitSplinters(): bs.emitBGDynamics(position=position,velocity=velocity,count=int(20.0+random.random()*25),scale=0.8,spread=1.0,chunkType='splinter'); bs.gameTimer(10,_emitSplinters) # every now and then do a sparky one if self.blastType == 'tnt' or random.random() < 0.1: def _emitExtraSparks(): bs.emitBGDynamics(position=position,velocity=velocity,count=int(10.0+random.random()*20),scale=0.8,spread=1.5,chunkType='spark'); bs.gameTimer(20,_emitExtraSparks) bs.gameTimer(50,_doEmit) # looks better if we delay a bit light = bs.newNode('light', attrs={'position':position, 'color': (0.6,0.6,1.0) if self.blastType == 'ice' else (1,0.3,0.1), 'volumeIntensityScale': 10.0}) s = random.uniform(0.6,0.9) scorchRadius = lightRadius = self.radius if self.blastType == 'tnt': lightRadius *= 1.4 scorchRadius *= 1.15 s *= 3.0 iScale = 1.6 bsUtils.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}) bsUtils.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}) bs.gameTimer(int(s*3000),light.delete) # make a scorch that fades over time scorch = bs.newNode('scorch', attrs={'position':position,'size':scorchRadius*0.5,'big':(self.blastType == 'tnt')}) if self.blastType == 'ice': scorch.color = (1,1,1.5) bsUtils.animate(scorch,"presence",{3000:1, 13000:0}) bs.gameTimer(13000,scorch.delete) if self.blastType == 'ice': bs.playSound(factory.hissSound,position=light.position) p = light.position bs.playSound(factory.getRandomExplodeSound(),position=p) bs.playSound(factory.debrisFallSound,position=p) ######## bs.shakeCamera(intensity=5.0 if self.blastType == 'tnt' else 0.05) ######## # tnt is more epic.. if self.blastType == 'tnt': bs.playSound(factory.getRandomExplodeSound(),position=p) def _extraBoom(): bs.playSound(factory.getRandomExplodeSound(),position=p) bs.gameTimer(250,_extraBoom) def _extraDebrisSound(): bs.playSound(factory.debrisFallSound,position=p) bs.playSound(factory.woodDebrisFallSound,position=p) bs.gameTimer(400,_extraDebrisSound) def blast(pos, blastType, sourcePlayer, hitType, hitSubType): Blast(position=pos, velocity=(0, 1, 0), blastRadius=0.5,blastType=blastType, sourcePlayer=sourcePlayer,hitType=hitType, hitSubType=hitSubType).autoRetain() class Player(bs.PlayerSpaz): isDead = False #def __init__(self, *args, **kwargs): # super(self.__class__, self).init(*args, **kwargs) # self.multiplyer = 0 def handleMessage(self, m): if False: pass elif isinstance(m, bs.PowerupMessage): if m.powerupType == 'punch': self.blastRadius += 1.0 self.setScoreText("range up") super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) def dropBomb(self): """ Tell the spaz to drop one of his bombs, and returns the resulting bomb object. If the spaz has no bombs or is otherwise unable to drop a bomb, returns None. """ if (self.landMineCount <= 0 and self.bombCount <= 0) or self.frozen: return p = self.node.positionForward v = self.node.velocity if self.landMineCount > 0: droppingBomb = False self.setLandMineCount(self.landMineCount-1) bombType = 'landMine' else: droppingBomb = True bombType = self.bombType bomb = Bomb(position=(p[0],p[1] - 0.0,p[2]), velocity=(v[0],v[1],v[2]), bombType=bombType, blastRadius=self.blastRadius, sourcePlayer=self.sourcePlayer, owner=self.node).autoRetain() if droppingBomb: self.bombCount -= 1 bomb.node.addDeathAction(bs.WeakCall(self.handleMessage,bsSpaz._BombDiedMessage())) self._pickUp(bomb.node) for c in self._droppedBombCallbacks: c(self,bomb) return bomb def bsGetAPIVersion(): return 4 def bsGetGames(): return [Bomberman] class Bomberman(bs.TeamGameActivity): @classmethod def getName(cls): return 'Bomberman' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'seconds', 'scoreVersion':'B', 'noneIsWinner':True} @classmethod def getDescription(cls, sessionType): return "Destroy crates and collect powerups" def getInstanceDescription(self): return 'Destroy crates and collect powerups' @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.TeamsSession) or issubclass(sessionType, bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls, sessionType): return ["Doom Shroom"] @classmethod def getSettings(cls, sessionType): return [("Time Limit",{'choices':[('None',0),('1 Minute',60),('2 Minutes',120), ('5 Minutes',300)],'default':0}), ("Lives (0 = Unlimited)",{'minValue':0,'default':3,'increment':1}), ("Epic Mode",{'default':False})] def __init__(self, settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die (since its meaningful in this game) self.announcePlayerDeaths = True self._lastPlayerDeathTime = None self._startGameTime = 1000 self.gridsize = (1.0, 1.0) self.gridnum = (18, 18) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) for x in range(self.gridnum[0]): for y in range(self.gridnum[1]): self.dropCrate(x, y) def dropCrate(self, gridX, gridY): pos = (Map.center[0] + self.gridsize[0]*gridX - self.gridnum[0]*self.gridsize[0]*0.5, Map.center[1], Map.center[2] + self.gridsize[1]*gridY - self.gridnum[1]*self.gridsize[1]*0.5) #print('dropped crate @', pos) if Map.inBounds(pos): Crate(position=pos).autoRetain() def dropPowerup(self, position): powerupType = random.choice(["punch", "tripleBombs", "health"]) bs.Powerup(position=position, powerupType=powerupType, expire=False).autoRetain() def onPlayerJoin(self, player): self.spawnPlayer(player) def onPlayerLeave(self, player): bs.TeamGameActivity.onPlayerLeave(self, player) # overriding the default character spawning.. def spawnPlayer(self, player): if isinstance(self.getSession(), bs.TeamsSession): position = self.getMap().getStartPosition(player.getTeam().getID()) else: # otherwise do free-for-all spawn locations position = self.getMap().getFFAStartPosition(self.players) angle = None #spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up #spaz.connectControlsToPlayer(enablePunch=False, # enableBomb=False, # enablePickUp=False) # also lets have them make some noise when they die.. #spaz.playBigDeathSound = True name = player.getName() lightColor = bsUtils.getNormalizedColor(player.color) displayColor = bs.getSafeColor(player.color, targetIntensity=0.75) spaz = Player(color=player.color, highlight=player.highlight, character=player.character, player=player) player.setActor(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer( enableJump=True, enablePunch=True, enablePickUp=False, enableBomb=True, enableRun=True, enableFly=False) self.scoreSet.playerGotNewSpaz(player,spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0, 360))) t = bs.getGameTime() bs.playSound(self._spawnSound, 1, position=spaz.node.position) light = bs.newNode('light', attrs={'color': lightColor}) spaz.node.connectAttr('position', light, 'position') bsUtils.animate(light, 'intensity', {0:0, 250:1, 500:0}) bs.gameTimer(500, light.delete) # various high-level game events come through this method def handleMessage(self,m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() player.gameData["survivalSeconds"] = bs.getGameTime() if len(self._getLivingTeams()) < 2: self._roundEndTimer = bs.Timer(1000, self.endGame) else: # default handler: super(self.__class__, self).handleMessage(m)#bs.TeamGameActivity.handleMessage(self,m) def endGame(self): curTime = bs.getGameTime() # mark 'death-time' as now for any still-living players # and award players points for how long they lasted. # (these per-player scores are only meaningful in team-games) for team in self.teams: for player in team.players: # throw an extra fudge factor +1 in so teams that # didn't die come out ahead of teams that did if 'survivalSeconds' in player.gameData: score = player.gameData['survivalSeconds'] elif 'survivalSeconds' in team.gameData: score = team.gameData['survivalSeconds'] else: score = (curTime - self._startGameTime)/1000 + 1 #if 'survivalSeconds' not in player.gameData: # player.gameData['survivalSeconds'] = (curTime - self._startGameTime)/1000 + 1 # print('extraBonusSwag for player') # award a per-player score depending on how many seconds they lasted # (per-player scores only affect teams mode; everywhere else just looks at the per-team score) #score = (player.gameData['survivalSeconds']) self.scoreSet.playerScored(player, score, screenMessage=False) # ok now calc game results: set a score for each team and then tell the game to end results = bs.TeamGameResults() # remember that 'free-for-all' mode is simply a special form of 'teams' mode # where each player gets their own team, so we can just always deal in teams # and have all cases covered for team in self.teams: # set the team score to the max time survived by any player on that team longestLife = 0 for player in team.players: if 'survivalSeconds' in player.gameData: time = player.gameData['survivalSeconds'] elif 'survivalSeconds' in team.gameData: time = team.gameData['survivalSeconds'] else: time = (curTime - self._startGameTime)/1000 + 1 longestLife = max(longestLife, time) results.setTeamScore(team, longestLife) self.end(results=results) def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any('survivalSeconds' not in player.gameData for player in team.players)] ================================================ FILE: mods/boxing.json ================================================ { "name": "Boxing", "author": "TheMikirog", "category": "minigames" } ================================================ FILE: mods/boxing.py ================================================ import bs def bsGetAPIVersion(): return 4 def bsGetGames(): return [DeathMatchGame] class DeathMatchGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Boxing' @classmethod def getDescription(cls,sessionType): return ('No bombs!\n' 'Knock out your enemies using your bare hands!\n' 'Powerups not included.') @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): return [("KOs to Win Per Player",{'minValue':1,'default':5,'increment':1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Epic Mode",{'default':False})] def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die since it matters here.. self.announcePlayerDeaths = True self._scoreBoard = bs.ScoreBoard() def getInstanceDescription(self): return ('KO ${ARG1} of your enemies.',self._scoreToWin) def getInstanceScoreBoardDescription(self): return ('KO ${ARG1} enemies',self._scoreToWin) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'GrandRomp') def onTeamJoin(self,team): team.gameData['score'] = 0 if self.hasBegun(): self._updateScoreBoard() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) if len(self.teams) > 0: self._scoreToWin = self.settings['KOs to Win Per Player'] * max(1,max(len(t.players) for t in self.teams)) else: self._scoreToWin = self.settings['KOs to Win Per Player'] self._updateScoreBoard() self._dingSound = bs.getSound('dingSmall') def spawnPlayer(self,player): spaz = self.spawnPlayerSpaz(player) spaz.connectControlsToPlayer(enablePunch=True, enableBomb=False, enablePickUp=True) spaz.equipBoxingGloves() def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # augment standard behavior player = m.spaz.getPlayer() self.respawnPlayer(player) killer = m.killerPlayer if killer is None: return # handle team-kills if killer.getTeam() is player.getTeam(): # in free-for-all, killing yourself loses you a point if isinstance(self.getSession(),bs.FreeForAllSession): player.getTeam().gameData['score'] = max(0,player.getTeam().gameData['score']-1) # in teams-mode it gives a point to the other team else: bs.playSound(self._dingSound) for team in self.teams: if team is not killer.getTeam(): team.gameData['score'] += 1 # killing someone on another team nets a kill else: killer.getTeam().gameData['score'] += 1 bs.playSound(self._dingSound) # in FFA show our score since its hard to find on the scoreboard try: killer.actor.setScoreText(str(killer.getTeam().gameData['score'])+'/'+str(self._scoreToWin),color=killer.getTeam().color,flash=True) except Exception: pass self._updateScoreBoard() # if someone has won, set a timer to end shortly # (allows the dust to clear and draws to occur if deaths are close enough) if any(team.gameData['score'] >= self._scoreToWin for team in self.teams): bs.gameTimer(500,self.endGame) else: bs.TeamGameActivity.handleMessage(self,m) def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team,team.gameData['score'],self._scoreToWin) def endGame(self): results = bs.TeamGameResults() for t in self.teams: results.setTeamScore(t,t.gameData['score']) self.end(results=results) ================================================ FILE: mods/brainFreeze.json ================================================ { "name": "Brain Freeze", "author": "TheMikirog", "category": "minigames" } ================================================ FILE: mods/brainFreeze.py ================================================ import bs import random def bsGetAPIVersion(): return 4 def bsGetGames(): return [BrainFreezeGame] def bsGetLevels(): return [bs.Level('Brain Freeze',displayName='${GAME}',gameType=BrainFreezeGame,settings={},previewTexName='rampagePreview'), bs.Level('Epic Brain Freeze',displayName='${GAME}',gameType=BrainFreezeGame,settings={'Epic Mode':True},previewTexName='rampagePreview')] class BrainFreezeGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Brain Freeze' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'milliseconds', 'scoreVersion':'B'} @classmethod def getDescription(cls,sessionType): return 'Dodge the falling ice bombs.' # we're currently hard-coded for one map.. @classmethod def getSupportedMaps(cls,sessionType): return ['Rampage'] @classmethod def getSettings(cls,sessionType): return [("Epic Mode",{'default':False})] # we support teams, free-for-all, and co-op sessions @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession) or issubclass(sessionType,bs.CoopSession)) else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die (since its meaningful in this game) self.announcePlayerDeaths = True self._lastPlayerDeathTime = None # called when our game is transitioning in but not ready to start.. # ..we can go ahead and set our music and whatnot def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') # called when our game actually starts def onBegin(self): bs.TeamGameActivity.onBegin(self) # drop a wave every few seconds.. and every so often drop the time between waves # ..lets have things increase faster if we have fewer players self._meteorTime = 3000 t = 7500 if len(self.players) > 2 else 4000 if self.settings['Epic Mode']: t /= 4 bs.gameTimer(t,self._decrementMeteorTime,repeat=True) # kick off the first wave in a few seconds t = 3000 if self.settings['Epic Mode']: t /= 4 bs.gameTimer(t,self._setMeteorTimer) self._timer = bs.OnScreenTimer() self._timer.start() # overriding the default character spawning.. def spawnPlayer(self,player): spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up spaz.connectControlsToPlayer(enablePunch=False, enableBomb=False, enablePickUp=False) # also lets have them make some noise when they die.. spaz.playBigDeathSound = True # various high-level game events come through this method def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # (augment standard behavior) deathTime = bs.getGameTime() # record the player's moment of death m.spaz.getPlayer().gameData['deathTime'] = deathTime # in co-op mode, end the game the instant everyone dies (more accurate looking) # in teams/ffa, allow a one-second fudge-factor so we can get more draws if isinstance(self.getSession(),bs.CoopSession): # teams will still show up if we check now.. check in the next cycle bs.pushCall(self._checkEndGame) self._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock.. else: bs.gameTimer(1000,self._checkEndGame) else: # default handler: bs.TeamGameActivity.handleMessage(self,m) def _checkEndGame(self): livingTeamCount = 0 for team in self.teams: for player in team.players: if player.isAlive(): livingTeamCount += 1 break # in co-op, we go till everyone is dead.. otherwise we go until one team remains if isinstance(self.getSession(),bs.CoopSession): if livingTeamCount <= 0: self.endGame() else: if livingTeamCount <= 1: self.endGame() def _setMeteorTimer(self): bs.gameTimer(int((1.0+0.2*random.random())*self._meteorTime),self._dropBombCluster) def _dropBombCluster(self): # random note: code like this is a handy way to plot out extents and debug things if False: bs.newNode('locator',attrs={'position':(8,6,-5.5)}) bs.newNode('locator',attrs={'position':(8,6,-2.3)}) bs.newNode('locator',attrs={'position':(-7.3,6,-5.5)}) bs.newNode('locator',attrs={'position':(-7.3,6,-2.3)}) # drop several bombs in series.. delay = 0 for i in range(random.randrange(1,3)): # drop them somewhere within our bounds with velocity pointing toward the opposite side pos = (-7.3+15.3*random.random(),11,-5.5+2.1*random.random()) vel = ((-5.0+random.random()*30.0) * (-1.0 if pos[0] > 0 else 1.0), -4.0,0) bs.gameTimer(delay,bs.Call(self._dropBomb,pos,vel)) delay += 100 self._setMeteorTimer() def _dropBomb(self,position,velocity): b = bs.Bomb(position=position,velocity=velocity,bombType='ice').autoRetain() def _decrementMeteorTime(self): self._meteorTime = max(10,int(self._meteorTime*0.9)) def endGame(self): curTime = bs.getGameTime() # mark 'death-time' as now for any still-living players # and award players points for how long they lasted. # (these per-player scores are only meaningful in team-games) for team in self.teams: for player in team.players: # throw an extra fudge factor +1 in so teams that # didn't die come out ahead of teams that did if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime+1 # award a per-player score depending on how many seconds they lasted # (per-player scores only affect teams mode; everywhere else just looks at the per-team score) score = (player.gameData['deathTime']-self._timer.getStartTime())/1000 if 'deathTime' not in player.gameData: score += 50 # a bit extra for survivors self.scoreSet.playerScored(player,score,screenMessage=False) # stop updating our time text, and set the final time to match # exactly when our last guy died. self._timer.stop(endTime=self._lastPlayerDeathTime) # ok now calc game results: set a score for each team and then tell the game to end results = bs.TeamGameResults() # remember that 'free-for-all' mode is simply a special form of 'teams' mode # where each player gets their own team, so we can just always deal in teams # and have all cases covered for team in self.teams: # set the team score to the max time survived by any player on that team longestLife = 0 for player in team.players: longestLife = max(longestLife,(player.gameData['deathTime'] - self._timer.getStartTime())) results.setTeamScore(team,longestLife) self.end(results=results) ================================================ FILE: mods/bsBoxingOfTheHill.json ================================================ { "name": "Boxing Of The Hill", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/bsBoxingOfTheHill.py ================================================ import bs import weakref def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [BoxingOfTheHillGame] class BoxingOfTheHillGame(bs.TeamGameActivity): FLAG_NEW = 0 FLAG_UNCONTESTED = 1 FLAG_CONTESTED = 2 FLAG_HELD = 3 @classmethod def getName(cls): return 'Boxing of the Hill' @classmethod def getDescription(cls,sessionType): return 'Secure the flag for a set length of time. Gloves only!' @classmethod def getScoreInfo(cls): return {'scoreName':'Time Held'} @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("kingOfTheHill") @classmethod def getSettings(cls,sessionType): return [("Hold Time",{'minValue':10,'default':30,'increment':10}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0})] def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self._scoreBoard = bs.ScoreBoard() self._swipSound = bs.getSound("swip") self._tickSound = bs.getSound('tick') self._countDownSounds = {10:bs.getSound('announceTen'), 9:bs.getSound('announceNine'), 8:bs.getSound('announceEight'), 7:bs.getSound('announceSeven'), 6:bs.getSound('announceSix'), 5:bs.getSound('announceFive'), 4:bs.getSound('announceFour'), 3:bs.getSound('announceThree'), 2:bs.getSound('announceTwo'), 1:bs.getSound('announceOne')} self._flagRegionMaterial = bs.Material() self._flagRegionMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('playerMaterial')), actions=(("modifyPartCollision","collide",True), ("modifyPartCollision","physical",False), ("call","atConnect",bs.Call(self._handlePlayerFlagRegionCollide,1)), ("call","atDisconnect",bs.Call(self._handlePlayerFlagRegionCollide,0)))) def getInstanceDescription(self): return ('Secure the flag for ${ARG1} seconds.',self.settings['Hold Time']) def getInstanceScoreBoardDescription(self): return ('secure the flag for ${ARG1} seconds',self.settings['Hold Time']) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Scary') def onTeamJoin(self,team): team.gameData['timeRemaining'] = self.settings["Hold Time"] self._updateScoreBoard() def onPlayerJoin(self,player): bs.TeamGameActivity.onPlayerJoin(self,player) player.gameData['atFlag'] = 0 def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) # self.setupStandardPowerupDrops() #no powerups due to boxing self._flagPos = self.getMap().getFlagPosition(None) bs.gameTimer(1000,self._tick,repeat=True) self._flagState = self.FLAG_NEW self.projectFlagStand(self._flagPos) self._flag = bs.Flag(position=self._flagPos, touchable=False, color=(1,1,1)) self._flagLight = bs.newNode('light', attrs={'position':self._flagPos, 'intensity':0.2, 'heightAttenuated':False, 'radius':0.4, 'color':(0.2,0.2,0.2)}) # flag region bs.newNode('region', attrs={'position':self._flagPos, 'scale': (1.8,1.8,1.8), 'type': 'sphere', 'materials':[self._flagRegionMaterial,bs.getSharedObject('regionMaterial')]}) self._updateFlagState() def spawnPlayer(self,player): spaz = self.spawnPlayerSpaz(player) spaz.connectControlsToPlayer(enablePunch=True, enableBomb=False, enablePickUp=True) spaz.equipBoxingGloves() def _tick(self): self._updateFlagState() # give holding players points for player in self.players: if player.gameData['atFlag'] > 0: self.scoreSet.playerScored(player,3,screenMessage=False,display=False) scoringTeam = None if self._scoringTeam is None else self._scoringTeam() if scoringTeam: if scoringTeam.gameData['timeRemaining'] > 0: bs.playSound(self._tickSound) scoringTeam.gameData['timeRemaining'] = max(0,scoringTeam.gameData['timeRemaining']-1) self._updateScoreBoard() if scoringTeam.gameData['timeRemaining'] > 0: self._flag.setScoreText(str(scoringTeam.gameData['timeRemaining'])) # announce numbers we have sounds for try: bs.playSound(self._countDownSounds[scoringTeam.gameData['timeRemaining']]) except Exception: pass # winner if scoringTeam.gameData['timeRemaining'] <= 0: self.endGame() def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team,self.settings['Hold Time'] - team.gameData['timeRemaining']) self.end(results=results,announceDelay=0) def _updateFlagState(self): holdingTeams = set(player.getTeam() for player in self.players if player.gameData['atFlag']) prevState = self._flagState if len(holdingTeams) > 1: self._flagState = self.FLAG_CONTESTED self._scoringTeam = None self._flagLight.color = (0.6,0.6,0.1) self._flag.node.color = (1.0,1.0,0.4) elif len(holdingTeams) == 1: holdingTeam = list(holdingTeams)[0] self._flagState = self.FLAG_HELD self._scoringTeam = weakref.ref(holdingTeam) self._flagLight.color = bs.getNormalizedColor(holdingTeam.color) self._flag.node.color = holdingTeam.color else: self._flagState = self.FLAG_UNCONTESTED self._scoringTeam = None self._flagLight.color = (0.2,0.2,0.2) self._flag.node.color = (1,1,1) if self._flagState != prevState: bs.playSound(self._swipSound) def _handlePlayerFlagRegionCollide(self,colliding): flagNode,playerNode = bs.getCollisionInfo("sourceNode","opposingNode") try: player = playerNode.getDelegate().getPlayer() except Exception: return # different parts of us can collide so a single value isn't enough # also don't count it if we're dead (flying heads shouldnt be able to win the game :-) if colliding and player.isAlive(): player.gameData['atFlag'] += 1 else: player.gameData['atFlag'] = max(0,player.gameData['atFlag'] - 1) self._updateFlagState() def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team,team.gameData['timeRemaining'],self.settings['Hold Time'],countdown=True) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # augment default # no longer can count as atFlag once dead player = m.spaz.getPlayer() player.gameData['atFlag'] = 0 self._updateFlagState() self.respawnPlayer(player) ================================================ FILE: mods/bsKillZone.json ================================================ { "name": "Kill Zone", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/bsKillZone.py ================================================ import bs import random import math def bsGetAPIVersion(): return 4 def bsGetGames(): return [KillZoneGame] def bsGetLevels(): return [bs.Level('Kill Zone',displayName='${GAME}',gameType=KillZoneGame,settings={},previewTexName='doomShroomPreview')] class KillZoneGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Kill Zone - Kill no-shirts on targets' @classmethod def getDescription(cls,sessionType): return 'Get points for killing enemies within the targets.' @classmethod def getSupportedMaps(cls,sessionType): return ['Doom Shroom'] @classmethod def supportsSessionType(cls,sessionType): # we support teams, co-op, and free-for-all return True if (issubclass(sessionType,bs.CoopSession) or issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self._scoreBoard = bs.ScoreBoard() def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='ForwardMarch') def onTeamJoin(self,team): team.gameData['score'] = 0 if self.hasBegun(): self._updateScoreBoard() def onBegin(self): bs.TeamGameActivity.onBegin(self) self._updateScoreBoard() self._targets = [] # number of targets is based on player count numTargets = min(5,len(self.initialPlayerInfo)+2) for i in range(numTargets): bs.gameTimer(5000+i*1000,self._spawnTarget) # this wrangles our bots self._bots = bs.BotSet() # start some timers to spawn bots bs.gameTimer(1000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=(3,3,-2),spawnTime=3000)) #bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=(-3,3,-2),spawnTime=3000)) #bs.gameTimer(3000,bs.Call(self._bots.spawnBot,bs.NinjaBot,pos=(5,3,-2),spawnTime=3000)) #bs.gameTimer(4000,bs.Call(self._bots.spawnBot,bs.NinjaBot,pos=(-5,3,-2),spawnTime=3000)) # add a few extras for multiplayer if len(self.initialPlayerInfo) > 2: bs.gameTimer(5000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=(0,3,-5),spawnTime=3000)) if len(self.initialPlayerInfo) > 3: bs.gameTimer(6000,bs.Call(self._bots.spawnBot,bs.ToughGuyBot,pos=(0,3,1),spawnTime=3000)) # note: if spawns were spread out more we'd probably want to set some sort of flag on the # last spawn to ensure we don't inadvertantly allow a 'win' before every bot is spawned. # (ie: if bot 1, 2, and 3 got killed but 4 hadn't spawned yet, the game might end because # it sees no remaining bots. self._updateTimer = bs.Timer(1000,self._update,repeat=True) self._countdown = bs.OnScreenCountdown(150,endCall=self.endGame) bs.gameTimer(4000,self._countdown.start) def spawnPlayer(self,player): spawnCenter = (0,3,-5) pos = (spawnCenter[0]+random.uniform(-1.5,1.5),spawnCenter[1],spawnCenter[2]+random.uniform(-1.5,1.5)) # reset their streak player.gameData['streak'] = 0 spaz = self.spawnPlayerSpaz(player,position=pos) spaz.equipBoxingGloves() spaz.connectControlsToPlayer(enablePunch=True, enableBomb=False, enablePickUp=True) # give players permanent triple impact bombs and wire them up # to tell us when they drop a bomb spaz.bombType = 'impact' spaz.setBombCount(3) spaz.addDroppedBombCallback(self._onSpazDroppedBomb) def _spawnTarget(self): # gen a few random points; we'll use whichever one is farthest from # our existing targets. (dont want overlapping targets) points = [] for i in range(4): # calc a random point within a circle while True: x = random.uniform(-1.0,1.0) y = random.uniform(-1.0,1.0) if x*x+y*y < 1.0: break points.append((8.0*x,2.2,-3.5+5.0*y)) def getMinDistFromTarget(point): return min((t.getDistFromPoint(point) for t in self._targets)) # if we have existing targets, use the point with the highest min-distance-from-targets if self._targets: point = max(points,key=getMinDistFromTarget) else: point = points[0] self._targets.append(Target(position=point)) def _onSpazDroppedBomb(self,spaz,bomb): # wire up this bomb to inform us when it blows up pass #bomb.addExplodeCallback(self._onBombExploded)#Commented out to prevent bomb wiring. Get info from spazbot death message instead def _onSpazBotDied(self,DeathMsg): x = random.uniform(-1.0,1.0) y = random.uniform(-1.0,1.0) self._bots.spawnBot(bs.ToughGuyBot,pos=(8.0*x,3,5.0*y),spawnTime=1000) pos = DeathMsg.badGuy.node.position # debugging: throw a locator down where we landed.. #bs.newNode('locator',attrs={'position':blast.node.position}) # feed the explosion point to all our targets and get points in return.. # note: we operate on a copy of self._targets since the list may change # under us if we hit stuff (dont wanna get points for new targets) print(DeathMsg.how) if DeathMsg.killerPlayer is None: #print("No killer") pass else: player = DeathMsg.killerPlayer #print(player) if not player.exists(): return # could happen if they leave after throwing a bomb.. #print("got here") bullsEye = any(target.doHitAtPosition(pos,player) for target in list(self._targets)) if bullsEye: player.gameData['streak'] += 1 else: player.gameData['streak'] = 0 def _update(self): # misc. periodic updating.. # clear out targets that have died self._targets = [t for t in self._targets if t.exists()] def handleMessage(self,m): # when players die, respawn them if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # do standard stuff self.respawnPlayer(m.spaz.getPlayer()) # kick off a respawn elif isinstance(m,Target.TargetHitMessage): # a target is telling us it was hit and will die soon.. # ..so make another one. self._spawnTarget() elif isinstance(m,bs.SpazBotDeathMessage): self._onSpazBotDied(m) bs.TeamGameActivity.handleMessage(self,m) #bs.PopupText("died",position=self._position,color=popupColor,scale=popupScale).autoRetain() else: bs.TeamGameActivity.handleMessage(self,m) def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team,team.gameData['score']) def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team,team.gameData['score']) self.end(results) class Target (bs.Actor): class TargetHitMessage(object): pass def __init__(self,position): self._r1 = 0.45 self._r2 = 1.1 self._r3 = 2.0 self._rFudge = 0.15 bs.Actor.__init__(self) self._position = bs.Vector(*position) self._hit = False 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.. n1 = bs.newNode('locator',attrs={'shape':'circle','position':position,'color':(0,1,0),'opacity':0.5,'drawBeauty':showInSpace,'additive':True}) n2 = bs.newNode('locator',attrs={'shape':'circleOutline','position':position,'color':(0,1,0),'opacity':0.3,'drawBeauty':False,'additive':True}) n3 = bs.newNode('locator',attrs={'shape':'circleOutline','position':position,'color':(0,1,0),'opacity':0.1,'drawBeauty':False,'additive':True}) self._nodes = [n1,n2,n3] bs.animateArray(n1,'size',1,{0:[0.0],200:[self._r1*2.0]}) bs.animateArray(n2,'size',1,{50:[0.0],250:[self._r2*2.0]}) bs.animateArray(n3,'size',1,{100:[0.0],300:[self._r3*2.0]}) bs.playSound(bs.getSound('laserReverse')) def exists(self): return True if self._nodes else False def handleMessage(self,m): if isinstance(m,bs.DieMessage): for node in self._nodes: node.delete() self._nodes = [] else: bs.Actor.handleMessage(self,m) def getDistFromPoint(self,pos): 'Given a point, returns distance squared from it' return (bs.Vector(*pos)-self._position).length() def doHitAtPosition(self,pos,player): activity = self.getActivity() #print("Hit target?") # ignore hits if the game is over or if we've already been hit if activity.hasEnded() or self._hit or not self._nodes: return 0 diff = (bs.Vector(*pos)-self._position) diff[1] = 0.0 # disregard y difference (our target point probably isnt exactly on the ground anyway) dist = diff.length() bullsEye = False points = 0 if dist <= self._r3+self._rFudge: # inform our activity that we were hit self._hit = True self.getActivity().handleMessage(self.TargetHitMessage()) keys = {0:(1,0,0),49:(1,0,0),50:(1,1,1),100:(0,1,0)} cDull = (0.3,0.3,0.3) if dist <= self._r1+self._rFudge: bullsEye = True self._nodes[1].color = cDull self._nodes[2].color = cDull bs.animateArray(self._nodes[0],'color',3,keys,loop=True) popupScale = 1.8 popupColor = (1,1,0,1) streak = player.gameData['streak'] points = 10 + min(20,streak * 2) bs.playSound(bs.getSound('bellHigh')) if streak > 0: bs.playSound(bs.getSound('orchestraHit4' if streak > 3 else 'orchestraHit3' if streak > 2 else 'orchestraHit2' if streak > 1 else 'orchestraHit')) elif dist <= self._r2+self._rFudge: self._nodes[0].color = cDull self._nodes[2].color = cDull bs.animateArray(self._nodes[1],'color',3,keys,loop=True) popupScale = 1.25 popupColor = (1,0.5,0.2,1) points = 4 bs.playSound(bs.getSound('bellMed')) else: self._nodes[0].color = cDull self._nodes[1].color = cDull bs.animateArray(self._nodes[2],'color',3,keys,loop=True) popupScale= 1.0 popupColor = (0.8,0.3,0.3,1) points = 2 bs.playSound(bs.getSound('bellLow')) # award points/etc.. (technically should probably leave this up to the activity) popupStr = "+"+str(points) # if there's more than 1 player in the game, include their names and colors # so they know who got the hit if len(activity.players) > 1: popupColor = bs.getSafeColor(player.color,targetIntensity=0.75) popupStr += ' '+player.getName() bs.PopupText(popupStr,position=self._position,color=popupColor,scale=popupScale).autoRetain() # give this player's team points and update the score-board player.getTeam().gameData['score'] += points activity._updateScoreBoard() # also give this individual player points (only applies in teams mode) activity.scoreSet.playerScored(player,points,showPoints=False,screenMessage=False) bs.animateArray(self._nodes[0],'size',1,{800:self._nodes[0].size,1000:[0.0]}) bs.animateArray(self._nodes[1],'size',1,{850:self._nodes[1].size,1050:[0.0]}) bs.animateArray(self._nodes[2],'size',1,{900:self._nodes[2].size,1100:[0.0]}) bs.gameTimer(1100,bs.Call(self.handleMessage,bs.DieMessage())) return bullsEye ================================================ FILE: mods/catch_to_live.json ================================================ { "name": "CatchToLive", "author": "Deva", "category": "minigames" } ================================================ FILE: mods/catch_to_live.py ================================================ # coding=utf-8 # coding=utf8 import bs import bsUtils import random class CheckNeedNewMadMessage(object): def __init__(self, spaz=None): self.spaz = spaz class ClearProtectMessage(object): def __init__(self): pass class grimPlayer(bs.PlayerSpaz): def __init__(self, color, highlight, character, player, gameProtectionTime=3, hitPoints=5): bs.PlayerSpaz.__init__(self, color=color, highlight=highlight, character=character, player=player) self._inmad = False # 默认不是处于疯狂状态 self._madProtect = False # 默认处于无保护状态 self.hitPoints = hitPoints * 1000 self.hitPointsMax = self.hitPoints self.gameProtectionTime = gameProtectionTime self._startMadTime = None self._allMadTime = None self._startProtectTime = None self.normalColor = color self.madColor = (1, 0, 0) self.protectColor = (0, 0, 1) def handleMessage(self, m): if isinstance(m, bs.PickedUpMessage): if not self.getPlayer().isAlive(): return oppoSpaz = m.node.getDelegate() if not oppoSpaz.getPlayer().isAlive(): return # 让对方放手 oppoSpaz.onPickUpRelease() oppoSpaz.onPickUpPress() oppoSpaz.onPickUpRelease() if self._madProtect: bs.PlayerSpaz.handleMessage(self, m) return if oppoSpaz._inmad: oppoSpaz.stopMad() oppoSpaz.protectAdd() leftTime = (oppoSpaz._allMadTime - bs.getGameTime() + oppoSpaz._startMadTime) self.onMad(leftTime) bs.PlayerSpaz.handleMessage(self, m) elif isinstance(m, bs.DieMessage): self._inmad = False if not self._dead and not m.immediate: self._activity().handleMessage(CheckNeedNewMadMessage(self)) bs.PlayerSpaz.handleMessage(self, m) else: bs.PlayerSpaz.handleMessage(self, m) def protectAdd(self): # self.setScoreText(str(self.gameProtectionTime) + 's Crazy Protection') self.setScoreText('Anti-Crazy') self.node.color = self.protectColor self._madProtect = True self._startProtectTime = bs.getGameTime() bs.gameTimer(self.gameProtectionTime * 1000, bs.Call(self.protectClear, self._startProtectTime)) # add hockey self.node.hockey = True def protectClear(self, checkProtectStartTime): if self._madProtect and self._startProtectTime == checkProtectStartTime: self._madProtect = False if self._inmad: # 躲不了系统给的MAD return self.node.color = self.normalColor # add hockey self.node.hockey = False def onMad(self, madTime=10000): # 10秒后炸掉 if self._inmad: return self._inmad = True self.getPlayer().assignInputCall('pickUpPress', self.onPickUpPress) self.getPlayer().assignInputCall('pickUpRelease', self.onPickUpRelease) self.node.hockey = True self.node.color = self.madColor self._startMadTime = bs.getGameTime() self._allMadTime = madTime bs.gameTimer(madTime, bs.WeakCall(self.madExplode, self._startMadTime)) def stopMad(self): self._inmad = False self.getPlayer().assignInputCall('pickUpPress', lambda: None) self.getPlayer().assignInputCall('pickUpRelease', lambda: None) self.node.hockey = False self.node.color = self.normalColor def madExplode(self, checkStartTime): if self._inmad and self._startMadTime == checkStartTime: self.shatter(extreme=True) self.handleMessage(bs.DieMessage()) def bsGetAPIVersion(): return 4 def bsGetGames(): return [CatchToLiveGame] class CatchToLiveGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Catch To Live' @classmethod def getScoreInfo(cls): return {'scoreName': 'Survived', 'scoreType': 'milliseconds', 'scoreVersion': 'B'} @classmethod def getDescription(cls, sessionType): return 'If you\'re CRAZY and don\'t wanna die\nThen PICKUP others!' @classmethod def getSupportedMaps(cls, sessionType): # return ['Rampage'] return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls, sessionType): return [("Mad Time To Die (Approximate)", {'minValue': 5, 'default': 10, 'increment': 1}), ("Protection Time After Catching", {'minValue': 1, 'default': 3, 'increment': 1}), ("Player HP", { 'choices': [('normal', 1), ('2 times', 2), ('3 times', 3), ('5 times', 5), ('7 times', 7), ('10 times', 10)], 'default': 5}), ("Epic Mode", {'default': False}), ("Allow Landmine", {'default': True})] # we support teams, free-for-all, and co-op sessions @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.FreeForAllSession)) else False def __init__(self, settings): bs.TeamGameActivity.__init__(self, settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die (since its meaningful in this game) self.announcePlayerDeaths = True self._lastPlayerDeathTime = None # called when our game is transitioning in but not ready to start.. # ..we can go ahead and set our music and whatnot def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') # called when our game actually starts def onBegin(self): # self.playerList = [] bs.TeamGameActivity.onBegin(self) self._madTime = self.settings['Mad Time To Die (Approximate)'] * 1000 # bs.gameTimer(t,self._decrementMeteorTime,repeat=True) # kick off the first wave in a few seconds t = 3000 if self.settings['Epic Mode']: t /= 4 # bs.gameTimer(t,self._setMeteorTimer) self._timer = bs.OnScreenTimer() self._timer.start() bs.gameTimer(10, bs.WeakCall(self.updateSpazText), repeat=True) bs.gameTimer(t, bs.WeakCall(self.handleMessage, CheckNeedNewMadMessage()), repeat=False) bs.gameTimer(1000, self._checkNeedMad, repeat=True) # bs.gameTimer(5000, self._checkEndGame) # 4秒之后检测一波 def updateSpazText(self): for team in self.teams: for player in team.players: try: if player.actor._inmad: leftTime = (player.actor._allMadTime - bs.getGameTime() + player.actor._startMadTime) / 1000.0 if leftTime > 0.0: player.actor.setScoreText('Crazy') # player.actor.setScoreText('%.2f' % (leftTime), color=(1, 1, 1)) else: player.actor.setScoreText('') elif player.actor._madProtect: player.actor.setScoreText('Anti-Crazy') else: player.actor.setScoreText('') except: pass # overriding the default character spawning.. def spawnPlayer(self, player): position = self.getMap().getFFAStartPosition(self.players) angle = 20 name = player.getName() lightColor = bsUtils.getNormalizedColor(player.color) displayColor = bs.getSafeColor(player.color, targetIntensity=0.75) spaz = grimPlayer(color=player.color, highlight=player.highlight, character=player.character, player=player, gameProtectionTime=self.settings['Protection Time After Catching'], hitPoints=self.settings['Player HP']) player.setActor(spaz) # For some reason, I can't figure out how to get a list of all spaz. # Therefore, I am making the list here so I can get which spaz belongs # to the player supplied by HitMessage. # self.playerList.append(spaz) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() self.scoreSet.playerGotNewSpaz(player, spaz) # add landmine spaz.bombTypeDefault = 'landMine' # random.choice(['ice', 'impact', 'landMine', 'normal', 'sticky', 'tnt']) spaz.bombType = spaz.bombTypeDefault # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360))) t = bs.getGameTime() bs.playSound(self._spawnSound, 1, position=spaz.node.position) light = bs.newNode('light', attrs={'color': lightColor}) spaz.node.connectAttr('position', light, 'position') bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0}) bs.gameTimer(500, light.delete) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up spaz.connectControlsToPlayer(enablePunch=False, enableBomb=self.settings['Allow Landmine'], enablePickUp=False) # player.assignInputCall('pickUpPress', lambda: None) # player.assignInputCall('pickUpRelease', lambda: None) # also lets have them make some noise when they die.. spaz.playBigDeathSound = True return spaz def onPlayerJoin(self, player): # don't allow joining after we start # (would enable leave/rejoin tomfoolery) if self.hasBegun(): bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText', subs=[('${PLAYER}', player.getName(full=True))]), color=(0, 1, 0)) # for score purposes, mark them as having died right as the game started player.gameData['noScore'] = True return self.spawnPlayer(player) def onPlayerLeave(self, player): # augment default behavior... bs.TeamGameActivity.onPlayerLeave(self, player) # a departing player may trigger game-over bs.gameTimer(100, bs.Call(self._checkEndGame)) # various high-level game events come through this method def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # (augment standard behavior) deathTime = bs.getGameTime() # record the player's moment of death m.spaz.getPlayer().gameData['deathTime'] = deathTime # in co-op mode, end the game the instant everyone dies (more accurate looking) # in teams/ffa, allow a one-second fudge-factor so we can get more draws if isinstance(self.getSession(), bs.CoopSession): # teams will still show up if we check now.. check in the next cycle bs.pushCall(self._checkEndGame) self._lastPlayerDeathTime = deathTime # also record this for a final setting of the clock.. else: bs.gameTimer(1000, self._checkEndGame) elif isinstance(m, CheckNeedNewMadMessage): self._checkNeedMad() else: # default handler: bs.TeamGameActivity.handleMessage(self, m) def _checkNeedMad(self): # print('check if we need a new mad') alivePlayers = [] for team in self.teams: for player in team.players: if player.isAlive(): alivePlayers.append(player) if player.actor._inmad: # print('no need for new mad') return if len(alivePlayers) == 0: return selectedPlayer = random.choice(alivePlayers) selectedPlayer.actor.onMad(random.randint(self._madTime - 2500, self._madTime + 2500)) def _checkEndGame(self): livingTeamCount = 0 for team in self.teams: for player in team.players: if player.isAlive(): livingTeamCount += 1 break # in co-op, we go till everyone is dead.. otherwise we go until one team remains if isinstance(self.getSession(), bs.CoopSession): if livingTeamCount <= 0: self.endGame() else: if livingTeamCount <= 1: self.endGame() def endGame(self): curTime = bs.getGameTime() # mark 'death-time' as now for any still-living players # and award players points for how long they lasted. # (these per-player scores are only meaningful in team-games) for team in self.teams: for player in team.players: # throw an extra fudge factor +1 in so teams that # didn't die come out ahead of teams that did if 'deathTime' not in player.gameData: player.gameData['deathTime'] = curTime + 1 if 'noScore' in player.gameData: player.gameData['deathTime'] = self._timer.getStartTime() # award a per-player score depending on how many seconds they lasted # (per-player scores only affect teams mode; everywhere else just looks at the per-team score) score = (player.gameData['deathTime'] - self._timer.getStartTime()) / 1000 if 'deathTime' not in player.gameData: score += 50 # a bit extra for survivors self.scoreSet.playerScored(player, score, screenMessage=False) # stop updating our time text, and set the final time to match # exactly when our last guy died. self._timer.stop(endTime=self._lastPlayerDeathTime) # ok now calc game results: set a score for each team and then tell the game to end results = bs.TeamGameResults() # remember that 'free-for-all' mode is simply a special form of 'teams' mode # where each player gets their own team, so we can just always deal in teams # and have all cases covered for team in self.teams: # set the team score to the max time survived by any player on that team longestLife = 0 for player in team.players: longestLife = max(longestLife, (player.gameData['deathTime'] - self._timer.getStartTime())) results.setTeamScore(team, longestLife) self.end(results=results) ================================================ FILE: mods/fightOfFaith.json ================================================ { "name": "Fight of Faith", "author": "SoKpl", "category": "minigames" } ================================================ FILE: mods/fightOfFaith.py ================================================ import bs import random def bsGetAPIVersion(): # return the api-version this script expects. # this prevents it from attempting to run in newer versions of the game # where changes have been made to the modding APIs return 4 def bsGetGames(): return [FightOfFaithGame] def bsGetLevels(): # Levels are unique named instances of a particular game with particular settings. # They show up as buttons in the co-op section, get high-score lists associated with them, etc. return [bs.Level('Fight of Faith', # globally-unique name for this level (not seen by user) displayName='${GAME}', # ${GAME} will be replaced by the results of the game's getName() call gameType=FightOfFaithGame, settings={}, # we currently dont have any settings; we'd specify them here if we did. previewTexName='courtyardPreview')] class FightOfFaithGame(bs.TeamGameActivity): # name seen by the user @classmethod def getName(cls): return 'Fight of Faith' @classmethod def getScoreInfo(cls): return {'scoreType':'milliseconds', 'lowerIsBetter':True, 'scoreName':'Time'} @classmethod def getDescription(cls,sessionType): return 'How quickly you kill THEM?' @classmethod def getSupportedMaps(cls,sessionType): # for now we're hard-coding spawn positions and whatnot # so we need to be sure to specity that we only support # a specific map.. return ['Courtyard'] @classmethod def supportsSessionType(cls,sessionType): # we currently support Co-Op only return True if issubclass(sessionType,bs.CoopSession) else False # in the constructor we should load any media we need/etc. # but not actually create anything yet. def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) self._winSound = bs.getSound("score") # called when our game is transitioning in but not ready to start.. # ..we can go ahead and start creating stuff, playing music, etc. def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='ToTheDeath') # called when our game actually starts def onBegin(self): bs.TeamGameActivity.onBegin(self) self._won = False self.setupStandardPowerupDrops() # make our on-screen timer and start it roughly when our bots appear self._timer = bs.OnScreenTimer() bs.gameTimer(4000,self._timer.start) # this wrangles our bots self._bots = bs.BotSet() # start some timers to spawn bots bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.MelBot,pos=(3,3,-2),spawnTime=3000)) bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.ChickBot,pos=(-3,3,-2),spawnTime=3000)) bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.ToughGuyBotPro,pos=(5,3,-2),spawnTime=3000)) bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.BomberBotPro,pos=(-5,3,-2),spawnTime=3000)) bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.BomberBot,pos=(0,3,-5),spawnTime=3000)) bs.gameTimer(2000,bs.Call(self._bots.spawnBot,bs.PirateBotNoTimeLimit,pos=(0,3,1),spawnTime=10000)) # note: if spawns were spread out more we'd probably want to set some sort of flag on the # last spawn to ensure we don't inadvertantly allow a 'win' before every bot is spawned. # (ie: if bot 1, 2, and 3 got killed but 4 hadn't spawned yet, the game might end because # it sees no remaining bots. # called for each spawning player def spawnPlayer(self,player): # lets spawn close to the center spawnCenter = (0,3,-2) pos = (spawnCenter[0]+random.uniform(-1.5,1.5),spawnCenter[1],spawnCenter[2]+random.uniform(-1.5,1.5)) self.spawnPlayerSpaz(player,position=pos) def _checkIfWon(self): # simply end the game if there's no living bots.. if not self._bots.haveLivingBots(): self._won = True self.endGame() # called for miscellaneous events def handleMessage(self,m): # a player has died if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self,m) # do standard stuff self.respawnPlayer(m.spaz.getPlayer()) # kick off a respawn # a spaz-bot has died elif isinstance(m,bs.SpazBotDeathMessage): # unfortunately the bot-set will always tell us there are living # bots if we ask here (the currently-dying bot isn't officially marked dead yet) # ..so lets push a call into the event loop to check once this guy has finished dying. bs.pushCall(self._checkIfWon) else: # let the base class handle anything we don't.. bs.TeamGameActivity.handleMessage(self,m) # when this is called, we should fill out results and end the game # *regardless* of whether is has been won. (this may be called due # to a tournament ending or other external reason) def endGame(self): # stop our on-screen timer so players can see what they got self._timer.stop() results = bs.TeamGameResults() # if we won, set our score to the elapsed time # (there should just be 1 team here since this is co-op) # ..if we didn't win, leave scores as default (None) which means we lost if self._won: elapsedTime = bs.getGameTime()-self._timer.getStartTime() self.cameraFlash() bs.playSound(self._winSound) for team in self.teams: team.celebrate() # woooo! par-tay! results.setTeamScore(team,elapsedTime) # ends this activity.. self.end(results) ================================================ FILE: mods/frozenone.json ================================================ { "name": "The Frozen One", "author": "Mrmaxmeier", "category": "minigames" } ================================================ FILE: mods/frozenone.py ================================================ import bs from bsChosenOne import ChosenOneGame def bsGetAPIVersion(): return 4 def bsGetGames(): return [FrozenOneGame] class FrozenOneGame(ChosenOneGame): @classmethod def getName(cls): return 'Frozen One' @classmethod def getDescription(cls,sessionType): return ('Be the Frozen one for a length of time to win.\n' 'Kill the Frozen one to become it.') @classmethod def getSettings(cls, sessionType): return [('Frozen One Time', {'default': 30, 'increment': 10, 'minValue': 10}), ('Frozen One Gets Gloves', {'default': True}), ('Time Limit', {'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120), ('5 Minutes', 300), ('10 Minutes', 600), ('20 Minutes', 1200)], 'default': 0}), ('Respawn Times', {'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0), ('Long', 2.0), ('Longer', 4.0)], 'default': 1.0}), ('Epic Mode', {'default': False})] def onTeamJoin(self,team): team.gameData['timeRemaining'] = self.settings["Frozen One Time"] self._updateScoreBoard() def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team,self.settings['Frozen One Time'] - team.gameData['timeRemaining']) self.end(results=results,announceDelay=0) def _setChosenOnePlayer(self, player): try: for p in self.players: p.gameData['FrozenLight'] = None bs.playSound(self._swipSound) if player is None or not player.exists(): self._flag = bs.Flag(color=(1,0.9,0.2), position=self._flagSpawnPos, touchable=False) self._chosenOnePlayer = None l = bs.newNode('light', owner=self._flag.node, attrs={'position': self._flagSpawnPos, 'intensity':0.6, 'heightAttenuated':False, 'volumeIntensityScale':0.1, 'radius':0.1, 'color': (1.2,1.2,0.4)}) self._flashFlagSpawn() else: if player.actor is not None: self._flag = None self._chosenOnePlayer = player if player.actor.node.exists(): if self.settings['Frozen One Gets Gloves']: player.actor.handleMessage(bs.PowerupMessage('punch')) player.actor.frozen = True player.actor.node.frozen = 1 # use a color that's partway between their team color and white color = [0.3+c*0.7 for c in bs.getNormalizedColor(player.getTeam().color)] l = player.gameData['FrozenLight'] = bs.NodeActor(bs.newNode('light', attrs={"intensity":0.6, "heightAttenuated":False, "volumeIntensityScale":0.1, "radius":0.13, "color": color})) bs.animate(l.node, 'intensity', {0:1.0, 200:0.4, 400:1.0}, loop=True) player.actor.node.connectAttr('position',l.node,'position') except Exception, e: import traceback print 'EXC in _setChosenOnePlayer' traceback.print_exc(e) traceback.print_stack() def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team,team.gameData['timeRemaining'],self.settings['Frozen One Time'], countdown=True) ================================================ FILE: mods/iceDeathmatch.json ================================================ { "name": "Ice Deathmatch", "author": "SoKpl", "category": "minigames" } ================================================ FILE: mods/iceDeathmatch.py ================================================ import bs def bsGetAPIVersion(): return 4 def bsGetGames(): return [IceDeathMatchGame] class IceDeathMatchGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Ice Deathmatch' @classmethod def getDescription(cls, sessionType): return 'Freeze enemies and dump them off the map' @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.TeamsSession) or issubclass(sessionType, bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def getSettings(cls,sessionType): return [("Kills to Win Per Player",{'minValue':1,'default':5,'increment':1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Epic Mode",{'default':False})] def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die since it matters here.. self.announcePlayerDeaths = True self._scoreBoard = bs.ScoreBoard() def getInstanceDescription(self): return ('Crush ${ARG1} of your enemies.',self._scoreToWin) def getInstanceScoreBoardDescription(self): return ('kill ${ARG1} enemies',self._scoreToWin) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Onslaught') def onTeamJoin(self, team): team.gameData['score'] = 0 if self.hasBegun(): self._updateScoreBoard() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) if len(self.teams) > 0: self._scoreToWin = self.settings['Kills to Win Per Player'] * max(1,max(len(t.players) for t in self.teams)) else: self._scoreToWin = self.settings['Kills to Win Per Player'] self._updateScoreBoard() self._dingSound = bs.getSound('dingSmall') def spawnPlayer(self, player): spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to pick stuff up spaz.connectControlsToPlayer(enablePunch=False, enableBomb=True, enablePickUp=True) # also lets have them make some noise when they die.. spaz.playBigDeathSound = True # give players permanent triple impact bombs and wire them up # to tell us when they drop a bomb spaz.bombType = 'ice' spaz.setBombCount(2) spaz.addDroppedBombCallback(self._onSpazDroppedBomb) def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() self.respawnPlayer(player) killer = m.killerPlayer if killer is None: return # handle team-kills if killer.getTeam() is player.getTeam(): # in free-for-all, killing yourself loses you a point if isinstance(self.getSession(), bs.FreeForAllSession): player.getTeam().gameData['score'] = max(0, player.getTeam().gameData['score']-1) # in teams-mode it gives a point to the other team else: bs.playSound(self._dingSound) for team in self.teams: if team is not killer.getTeam(): team.gameData['score'] += 1 # killing someone on another team nets a kill else: killer.getTeam().gameData['score'] += 1 bs.playSound(self._dingSound) # in FFA show our score since its hard to find on the scoreboard try: killer.actor.setScoreText(str(killer.getTeam().gameData['score'])+'/'+str(self._scoreToWin),color=killer.getTeam().color,flash=True) except Exception: pass self._updateScoreBoard() # if someone has won, set a timer to end shortly # (allows the dust to clear and draws to occur if deaths are close enough) if any(team.gameData['score'] >= self._scoreToWin for team in self.teams): bs.gameTimer(500, self.endGame) else: bs.TeamGameActivity.handleMessage(self, m) def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team, team.gameData['score'], self._scoreToWin) def endGame(self): results = bs.TeamGameResults() for t in self.teams: results.setTeamScore(t, t.gameData['score']) self.end(results=results) ================================================ FILE: mods/magic_box.json ================================================ { "name": "Magic Box", "author": "Mrmaxmeier", "category": "minigames" } ================================================ FILE: mods/magic_box.py ================================================ import bs import bsUtils class MagicBox(bs.Bomb): def __init__(self, position=(0, 1, 0), velocity=(0, 0, 0), bombType='tnt', blastRadius=2.0, sourcePlayer=None, owner=None): """ Create a new Bomb. bombType can be 'ice','impact','landMine','normal','sticky', or 'tnt'. Note that for impact or landMine bombs you have to call arm() before they will go off. """ bs.Actor.__init__(self) factory = self.getFactory() self.bombType = 'tnt' self._exploded = False self.blastRadius = blastRadius # TNT self.blastRadius *= 1.45 self._explodeCallbacks = [] # the player this came from self.sourcePlayer = sourcePlayer # by default our hit type/subtype is our own, but we pick up types of whoever # sets us off so we know what caused a chain reaction self.hitType = 'explosion' self.hitSubType = self.bombType # if no owner was provided, use an unconnected node ref if owner is None: owner = bs.Node(None) # the node this came from self.owner = owner # TNT materials = (factory.bombMaterial, bs.getSharedObject('footingMaterial'), bs.getSharedObject('objectMaterial')) materials = materials + (factory.normalSoundMaterial,) self.node = bs.newNode('prop', delegate=self, attrs={'position':position, 'velocity':velocity, 'model':factory.tntModel, 'lightModel':factory.tntModel, 'body':'crate', 'shadowSize':0.5, 'colorTexture':factory.tntTex, 'reflection':'soft', 'reflectionScale':[0.23], 'materials':materials}) #self.node.extraAcceleration = (0, 40, 0) self.heldBy = 0 self._isDead = False bsUtils.animate(self.node, "modelScale", {0:0, 200:1.3, 260:1}) def handleMessage(self, m): if isinstance(m, bs.PickedUpMessage): bs.getActivity()._updateBoxState() elif isinstance(m, bs.DroppedMessage): self.heldBy -= 1 self.updateFloatyness() bs.gameTimer(200, bs.getActivity()._updateBoxState) elif isinstance(m, bs.DieMessage): if not self._isDead: bs.gameTimer(1000, bs.getActivity()._spawnBox) self._isDead = True super(self.__class__, self).handleMessage(m) def updateFloatyness(self): oldY = self.node.extraAcceleration[1] newY = {0: 0, 1: 39, 2: 19 + 20 * 2, 3: 19 + 20 * 3}.get(self.heldBy, 0) # needs more science time = 300 if (oldY >= newY) else 1000 keys = {0: (0, oldY, 0), time: (0, newY, 0)} bs.animateArray(self.node, 'extraAcceleration', 3, keys) def _hideScoreText(self): try: exists = self._scoreText.exists() except Exception: exists = False if exists: bs.animate(self._scoreText, 'scale', {0: self._scoreText.scale, 200: 0}) def setScoreText(self, text): """ Utility func to show a message over the flag; handy for scores. """ if not self.node.exists(): return try: exists = self._scoreText.exists() except Exception: exists = False if not exists: startScale = 0.0 math = bs.newNode('math', owner=self.node, attrs={'input1': (0, 0.6, 0), 'operation': 'add'}) self.node.connectAttr('position', math, 'input2') self._scoreText = bs.newNode('text', owner=self.node, attrs={'text':text, 'inWorld':True, 'scale':0.02, 'shadow':0.5, 'flatness':1.0, 'hAlign':'center'}) math.connectAttr('output', self._scoreText, 'position') else: startScale = self._scoreText.scale self._scoreText.text = text self._scoreText.color = bs.getSafeColor((1.0, 1.0, 0.4)) bs.animate(self._scoreText, 'scale', {0: startScale, 200: 0.02}) self._scoreTextHideTimer = bs.Timer(1000, bs.WeakCall(self._hideScoreText)) def bsGetAPIVersion(): return 4 def bsGetGames(): return [MagicBoxGame] class MagicBoxGame(bs.TeamGameActivity): BOX_NEW = 0 BOX_UNCONTESTED = 1 BOX_CONTESTED = 2 BOX_HELD = 3 @classmethod def getName(cls): return 'Magic Box' @classmethod def getDescription(cls, sessionType): return 'Grab the box and start flying.' @classmethod def getScoreInfo(cls): return {'scoreName': 'Time Held'} @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.TeamsSession) or issubclass(sessionType, bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls, sessionType): return bs.getMapsSupportingPlayType("keepAway") @classmethod def getSettings(cls, sessionType): return [("Hold Time", {'minValue': 30, 'default': 60, 'increment': 10}), ("Time Limit", {'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120), ('5 Minutes', 300)], 'default': 0}), ("Respawn Times", {'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0), ('Long', 2.0), ('Longer', 4.0)], 'default': 1.0})] def __init__(self, settings): bs.TeamGameActivity.__init__(self, settings) self._scoreBoard = bs.ScoreBoard() self._swipSound = bs.getSound("swip") self._tickSound = bs.getSound('tick') self._countDownSounds = {10:bs.getSound('announceTen'), 9:bs.getSound('announceNine'), 8:bs.getSound('announceEight'), 7:bs.getSound('announceSeven'), 6:bs.getSound('announceSix'), 5:bs.getSound('announceFive'), 4:bs.getSound('announceFour'), 3:bs.getSound('announceThree'), 2:bs.getSound('announceTwo'), 1:bs.getSound('announceOne')} def getInstanceDescription(self): return ('Hold the magic box for ${ARG1} seconds.', self.settings['Hold Time']) def getInstanceScoreBoardDescription(self): return ('Hold the magic box for ${ARG1} seconds', self.settings['Hold Time']) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Keep Away') def onTeamJoin(self, team): team.gameData['timeRemaining'] = self.settings["Hold Time"] self._updateScoreBoard() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops(enableTNT=False) self._boxSpawnPos = self.getMap().getFlagPosition(None) self._spawnBox() self._updateTimer = bs.Timer(1000, call=self._tick, repeat=True) self._updateBoxState() def _tick(self): self._updateBoxState() # award points to all living players holding the flag for player in self._holdingPlayers: if player.exists(): self.scoreSet.playerScored(player, 3, screenMessage=False, display=False) scoringTeam = self._scoringTeam if scoringTeam is not None: if scoringTeam.gameData['timeRemaining'] > 0: bs.playSound(self._tickSound) scoringTeam.gameData['timeRemaining'] = max(0, scoringTeam.gameData['timeRemaining'] - 1) self._updateScoreBoard() if scoringTeam.gameData['timeRemaining'] > 0: self._box.setScoreText(str(scoringTeam.gameData['timeRemaining'])) # announce numbers we have sounds for try: bs.playSound(self._countDownSounds[scoringTeam.gameData['timeRemaining']]) except Exception: pass # winner if scoringTeam.gameData['timeRemaining'] <= 0: self.endGame() def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team, self.settings['Hold Time'] - team.gameData['timeRemaining']) self.end(results=results, announceDelay=0) def _updateBoxState(self): for team in self.teams: team.gameData['holdingBox'] = False self._holdingPlayers = [] for player in self.players: try: if player.actor.isAlive() and player.actor.node.holdNode.exists(): holdingBox = (player.actor.node.holdNode == self._box.node) else: holdingBox = False except Exception: bs.printException("exception checking hold flag") if holdingBox: self._holdingPlayers.append(player) player.getTeam().gameData['holdingBox'] = True if self._box is not None and self._box.exists(): self._box.heldBy = len(self._holdingPlayers) self._box.updateFloatyness() holdingTeams = set(t for t in self.teams if t.gameData['holdingBox']) prevState = self._boxState if len(holdingTeams) > 1: self._boxState = self.BOX_CONTESTED self._scoringTeam = None elif len(holdingTeams) == 1: holdingTeam = list(holdingTeams)[0] self._boxState = self.BOX_HELD self._scoringTeam = holdingTeam else: self._boxState = self.BOX_UNCONTESTED self._scoringTeam = None if self._boxState != prevState: bs.playSound(self._swipSound) def _spawnBox(self): bs.playSound(self._swipSound) self._flashBoxSpawn() self._box = MagicBox(position=self._boxSpawnPos) self._boxState = self.BOX_NEW self._box.light = bs.newNode('light', owner=self._box.node, attrs={'intensity':0.2, 'radius':0.3, 'color': (0.2, 0.2, 0.2)}) self._box.node.connectAttr('position', self._box.light, 'position') self._updateBoxState() def _flashBoxSpawn(self): light = bs.newNode('light', attrs={'position':self._boxSpawnPos, 'color':(1, 1, 1), 'radius':0.3, 'heightAttenuated':False}) bs.animate(light, 'intensity', {0:0, 250:0.5, 500:0}, loop=True) bs.gameTimer(1000, light.delete) def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team, team.gameData['timeRemaining'], self.settings['Hold Time'], countdown=True) def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment default self.respawnPlayer(m.spaz.getPlayer()) else: bs.TeamGameActivity.handleMessage(self, m) ================================================ FILE: mods/modManager.json ================================================ { "name": "Mod Manager (this thingy)", "author": "Mrmaxmeier", "category": "utilities", "requires": ["ui_wrappers", "settings_patcher"] } ================================================ FILE: mods/modManager.py ================================================ from __future__ import print_function import bs import bsInternal import os import urllib import urllib2 import httplib import json import random import time import threading import weakref from md5 import md5 from bsUI import gSmallUI, gMedUI, gHeadingColor, uiGlobals, ConfirmWindow, StoreWindow, MainMenuWindow, Window from functools import partial try: from settings_patcher import SettingsButton except ImportError: bs.screenMessage("library settings_patcher missing", color=(1, 0, 0)) raise try: from ui_wrappers import TextWidget, ContainerWidget, ButtonWidget, CheckBoxWidget, ScrollWidget, ColumnWidget, Widget except ImportError: bs.screenMessage("library ui_wrappers missing", color=(1, 0, 0)) raise # roll own uuid4 implementation because uuid module might not be available # this is broken on android/1.4.216 due to 16**8 == 0 o.O def uuid4(): components = [8, 4, 4, 4, 12] return "-".join([('%012x' % random.randrange(16**a))[12 - a:] for a in components]) PROTOCOL_VERSION = 1.1 STAT_SERVER_URI = None # "http://bsmm.thuermchen.com" SUPPORTS_HTTPS = hasattr(httplib, 'HTTPS') USER_REPO = "Mrmaxmeier/BombSquad-Community-Mod-Manager" _supports_auto_reloading = True _auto_reloader_type = "patching" StoreWindow_setTab = StoreWindow._setTab MainMenuWindow__init__ = MainMenuWindow.__init__ def _prepare_reload(): settingsButton.remove() MainMenuWindow.__init__ = MainMenuWindow__init__ del MainMenuWindow._cb_checkUpdateData StoreWindow._setTab = StoreWindow_setTab del StoreWindow._onGetMoreGamesPress def bsGetAPIVersion(): return 4 quittoapply = None checkedMainMenu = False if 'mod_manager_config' not in bs.getConfig(): bs.getConfig()['mod_manager_config'] = {} bs.writeConfig() config = bs.getConfig()['mod_manager_config'] def index_url(branch=None): if not branch: branch = config.get("branch", "master") if SUPPORTS_HTTPS: yield "https://raw.githubusercontent.com/{}/{}/index.json".format(USER_REPO, branch) yield "https://rawgit.com/{}/{}/index.json".format(USER_REPO, branch) yield "http://raw.githack.com/{}/{}/index.json".format(USER_REPO, branch) yield "http://rawgit.com/{}/{}/index.json".format(USER_REPO, branch) def mod_url(data): if "commit_sha" in data and "filename" in data: commit_hexsha = data["commit_sha"] filename = data["filename"] if SUPPORTS_HTTPS: yield "https://cdn.rawgit.com/{}/{}/mods/{}".format(USER_REPO, commit_hexsha, filename) yield "http://rawcdn.githack.com/{}/{}/mods/{}".format(USER_REPO, commit_hexsha, filename) if "url" in data: if SUPPORTS_HTTPS: yield data["url"] yield data["url"].replace("https", "http") def try_fetch_cb(generator, callback, **kwargs): def f(data, status_code): if data: callback(data, status_code) else: try: get_cached(next(generator), f, **kwargs) except StopIteration: callback(None, None) get_cached(next(generator), f, **kwargs) web_cache = config.get("web_cache", {}) config["web_cache"] = web_cache if STAT_SERVER_URI and 'uuid' not in config: config['uuid'] = uuid4() bs.writeConfig() def get_cached(url, callback, force_fresh=False, fallback_to_outdated=True): def cache(data, status_code): if data: web_cache[url] = (data, time.time()) bs.writeConfig() def f(data, status_code): # TODO: cancel prev fetchs callback(data, status_code) cache(data, status_code) if force_fresh: mm_serverGet(url, {}, f) return if url in web_cache: data, timestamp = web_cache[url] if timestamp + 10 * 30 > time.time(): mm_serverGet(url, {}, cache) if fallback_to_outdated or timestamp + 10 * 60 > time.time(): callback(data, None) return mm_serverGet(url, {}, f) def get_index(callback, branch=None, **kwargs): try_fetch_cb(index_url(branch), callback, **kwargs) def fetch_stats(callback, **kwargs): if STAT_SERVER_URI: url = STAT_SERVER_URI + "/stats?uuid=" + config['uuid'] get_cached(url, callback, **kwargs) def stats_cached(): if not STAT_SERVER_URI: return False url = STAT_SERVER_URI + "/stats?uuid=" + config['uuid'] return url in web_cache def submit_mod_rating(mod, rating, callback): if not STAT_SERVER_URI: return bs.screenMessage('rating submission disabled') url = STAT_SERVER_URI + "/submit_rating" data = { "uuid": config['uuid'], "mod_str": mod.base, "rating": rating, } def cb(data, status_code): if status_code == 200: bs.screenMessage("rating submitted") callback() else: bs.screenMessage("failed to submit rating") mm_serverPost(url, data, cb, eval_data=False) def submit_download(mod): if not config.get('submit-download-statistics', True) or not STAT_SERVER_URI: return url = STAT_SERVER_URI + "/submit_download" data = { "uuid": config.get('uuid'), "mod_str": mod.base, } def cb(data, status_code): if status_code != 200: print("failed to submit download stats") mm_serverPost(url, data, cb, eval_data=False) def fetch_mod(data, callback): generator = mod_url(data) def f(data, status_code): if data: callback(data, status_code) else: try: mm_serverGet(next(generator), {}, f, eval_data=False) except StopIteration: callback(None, None) mm_serverGet(next(generator), {}, f, eval_data=False) def process_server_data(data): mods = data["mods"] version = data["version"] if version - 0.5 > PROTOCOL_VERSION: print("version diff:", version, PROTOCOL_VERSION) bs.screenMessage("please manually update the mod manager") return mods, version def _cb_checkUpdateData(self, data, status_code): try: if data: m, v = process_server_data(data) mods = [Mod(d) for d in m.values()] for mod in mods: mod._mods = {m.base: m for m in mods} if mod.is_installed() and mod.is_outdated(): if config.get("auto-update-old-mods", True): bs.screenMessage("updating mod '{}'...".format(mod.name)) def cb(mod, success): if success: bs.screenMessage("updated mod '{}'.".format(mod.name)) mod.install(cb) else: bs.screenMessage("an update for mod '{}' is available!".format(mod.name)) except: bs.printException() bs.screenMessage("failed to check for mod updates") oldMainInit = MainMenuWindow.__init__ def newMainInit(self, transition='inRight'): global checkedMainMenu oldMainInit(self, transition) if checkedMainMenu: return checkedMainMenu = True if config.get("auto-check-updates", True): get_index(self._cb_checkUpdateData, force_fresh=True) MainMenuWindow.__init__ = newMainInit MainMenuWindow._cb_checkUpdateData = _cb_checkUpdateData def _doModManager(swinstance): swinstance._saveState() bs.containerWidget(edit=swinstance._rootWidget, transition='outLeft') mm_window = ModManagerWindow(backLocationCls=swinstance.__class__) uiGlobals['mainMenuWindow'] = mm_window.getRootWidget() settingsButton = SettingsButton(id="ModManager", icon="heart", sorting_position=6) \ .setCallback(_doModManager) \ .setText("Mod Manager") \ .add() class ModManager_ServerCallThread(threading.Thread): def __init__(self, request, requestType, data, callback, eval_data=True): threading.Thread.__init__(self) self._request = request.encode("ascii") # embedded python2.7 has weird encoding issues self._requestType = requestType self._data = {} if data is None else data self._eval_data = eval_data self._callback = callback self._context = bs.Context('current') # save and restore the context we were created from activity = bs.getActivity(exceptionOnNone=False) self._activity = weakref.ref(activity) if activity is not None else None def _runCallback(self, *args): # if we were created in an activity context and that activity has since died, do nothing # (hmm should we be using a context-call instead of doing this manually?) if self._activity is not None and (self._activity() is None or self._activity().isFinalized()): return # (technically we could do the same check for session contexts, but not gonna worry about it for now) with self._context: self._callback(*args) def run(self): try: bsInternal._setThreadName("ModManager_ServerCallThread") # FIXME: using protected apis env = {'User-Agent': bs.getEnvironment()['userAgentString']} if self._requestType != "get" or self._data: if self._requestType == 'get': if self._data: request = urllib2.Request(self._request + '?' + urllib.urlencode(self._data), None, env) else: request = urllib2.Request(self._request, None, env) elif self._requestType == 'post': request = urllib2.Request(self._request, json.dumps(self._data), env) else: raise RuntimeError("Invalid requestType: " + self._requestType) response = urllib2.urlopen(request) else: response = urllib2.urlopen(self._request) if self._eval_data: responseData = json.loads(response.read()) else: responseData = response.read() if self._callback is not None: bs.callInGameThread(bs.Call(self._runCallback, responseData, response.getcode())) except: bs.printException() if self._callback is not None: bs.callInGameThread(bs.Call(self._runCallback, None, None)) def mm_serverGet(request, data, callback=None, eval_data=True): ModManager_ServerCallThread(request, 'get', data, callback, eval_data=eval_data).start() def mm_serverPost(request, data, callback=None, eval_data=True): ModManager_ServerCallThread(request, 'post', data, callback, eval_data=eval_data).start() class ModManagerWindow(Window): _selectedMod, _selectedModIndex = None, None categories = set(["all"]) tabs = [] tabheight = 35 mods = [] _modWidgets = [] currently_fetching = False timers = {} def __init__(self, transition='inRight', modal=False, showTab="all", onCloseCall=None, backLocationCls=None, originWidget=None): # if they provided an origin-widget, scale up from that if originWidget is not None: self._transitionOut = 'outScale' transition = 'inScale' else: self._transitionOut = 'outRight' self._backLocationCls = backLocationCls self._onCloseCall = onCloseCall self._showTab = showTab self._selectedTab = {'label': showTab} if showTab != "all": def check_tab_available(): if not self._rootWidget.exists(): return if any([mod.category == showTab for mod in self.mods]): return if "button" in self._selectedTab: return self._selectedTab = {"label": "all"} self._refresh() self.timers["check_tab_available"] = bs.Timer(300, check_tab_available, timeType='real') self._modal = modal self._windowTitleName = "Community Mod Manager" def sort_rating(mods): mods = sorted(mods, key=lambda mod: mod.rating_submissions, reverse=True) return sorted(mods, key=lambda mod: mod.rating, reverse=True) def sort_downloads(mods): return sorted(mods, key=lambda mod: mod.downloads, reverse=True) def sort_alphabetical(mods): return sorted(mods, key=lambda mod: mod.name.lower()) _sortModes = [ ('Rating', sort_rating, lambda m: stats_cached()), ('Downloads', sort_downloads, lambda m: stats_cached()), ('Alphabetical', sort_alphabetical), ] self.sortModes = {} for i, sortMode in enumerate(_sortModes): name, func = sortMode[:2] next_sortMode = _sortModes[(i + 1) % len(_sortModes)] condition = sortMode[2] if len(sortMode) > 2 else (lambda mods: True) self.sortModes[name] = { 'func': func, 'condition': condition, 'next': next_sortMode[0], 'name': name, 'index': i, } sortMode = config.get('sortMode') if not sortMode or sortMode not in self.sortModes: sortMode = _sortModes[0][0] self.sortMode = self.sortModes[sortMode] self._width = 650 self._height = 380 if gSmallUI else 420 if gMedUI else 500 topExtra = 20 if gSmallUI else 0 self._rootWidget = ContainerWidget(size=(self._width, self._height + topExtra), transition=transition, scale=2.05 if gSmallUI else 1.5 if gMedUI else 1.0, stackOffset=(0, -10) if gSmallUI else (0, 0)) self._backButton = backButton = ButtonWidget(parent=self._rootWidget, position=(self._width - 160, self._height - 60), size=(160, 68), scale=0.77, autoSelect=True, textScale=1.3, label=bs.Lstr(resource='doneText' if self._modal else 'backText'), onActivateCall=self._back) self._rootWidget.cancelButton = backButton TextWidget(parent=self._rootWidget, position=(0, self._height - 47), size=(self._width, 25), text=self._windowTitleName, color=gHeadingColor, maxWidth=290, hAlign="center", vAlign="center") v = self._height - 59 h = 41 bColor = (0.6, 0.53, 0.63) bTextColor = (0.75, 0.7, 0.8) s = 1.1 if gSmallUI else 1.27 if gMedUI else 1.57 v -= 63.0 * s self.refreshButton = ButtonWidget(parent=self._rootWidget, position=(h, v), size=(90, 58.0 * s), onActivateCall=bs.Call(self._cb_refresh, force_fresh=True), color=bColor, autoSelect=True, buttonType='square', textColor=bTextColor, textScale=0.7, label="Reload List") v -= 63.0 * s self.modInfoButton = ButtonWidget(parent=self._rootWidget, position=(h, v), size=(90, 58.0 * s), onActivateCall=bs.Call(self._cb_info), color=bColor, autoSelect=True, textColor=bTextColor, buttonType='square', textScale=0.7, label="Mod Info") v -= 63.0 * s self.sortButtonData = {"s": s, "h": h, "v": v, "bColor": bColor, "bTextColor": bTextColor} self.sortButton = ButtonWidget(parent=self._rootWidget, position=(h, v), size=(90, 58.0 * s), onActivateCall=bs.Call(self._cb_sorting), color=bColor, autoSelect=True, textColor=bTextColor, buttonType='square', textScale=0.7, label="Sorting:\n" + self.sortMode['name']) v -= 63.0 * s self.settingsButton = ButtonWidget(parent=self._rootWidget, position=(h, v), size=(90, 58.0 * s), onActivateCall=bs.Call(self._cb_settings), color=bColor, autoSelect=True, textColor=bTextColor, buttonType='square', textScale=0.7, label="Settings") v = self._height - 75 self.columnPosY = self._height - 75 - self.tabheight self._scrollHeight = self._height - 119 - self.tabheight scrollWidget = ScrollWidget(parent=self._rootWidget, position=(140, self.columnPosY - self._scrollHeight), size=(self._width - 180, self._scrollHeight + 10)) backButton.set(downWidget=scrollWidget, leftWidget=scrollWidget) self._columnWidget = ColumnWidget(parent=scrollWidget) for b in [self.refreshButton, self.modInfoButton, self.settingsButton]: b.rightWidget = scrollWidget scrollWidget.leftWidget = self.refreshButton self._cb_refresh() backButton.onActivateCall = self._back self._rootWidget.startButton = backButton self._rootWidget.onCancelCall = backButton.activate self._rootWidget.selectedChild = scrollWidget def _refresh(self, refreshTabs=True): while len(self._modWidgets) > 0: self._modWidgets.pop().delete() for mod in self.mods: if mod.category: self.categories.add(mod.category) if refreshTabs: self._refreshTabs() while not self.sortMode['condition'](self.mods): self.sortMode = self.sortModes[self.sortMode['next']] self.sortButton.label = "Sorting:\n" + self.sortMode['name'] self.mods = self.sortMode["func"](self.mods) visible = self.mods[:] if self._selectedTab["label"] != "all": visible = [m for m in visible if m.category == self._selectedTab["label"]] for index, mod in enumerate(visible): color = (0.6, 0.6, 0.7, 1.0) if mod.is_installed(): color = (0.85, 0.85, 0.85, 1) if mod.checkUpdate(): if mod.is_outdated(): color = (0.85, 0.3, 0.3, 1) else: color = (1, 0.84, 0, 1) w = TextWidget(parent=self._columnWidget, size=(self._width - 40, 24), maxWidth=self._width - 110, text=mod.name, hAlign='left', vAlign='center', color=color, alwaysHighlight=True, onSelectCall=bs.Call(self._cb_select, index, mod), onActivateCall=bs.Call(self._cb_info, True), selectable=True) w.showBufferTop = 50 w.showBufferBottom = 50 # hitting up from top widget shoud jump to 'back; if index == 0: tab_button = self.tabs[int((len(self.tabs) - 1) / 2)]["button"] w.upWidget = tab_button if self._selectedMod and mod.filename == self._selectedMod.filename: self._columnWidget.set(selectedChild=w, visibleChild=w) self._modWidgets.append(w) def _refreshTabs(self): if not self._rootWidget.exists(): return for t in self.tabs: for widget in t.values(): if isinstance(widget, bs.Widget) or isinstance(widget, Widget): widget.delete() self.tabs = [] total = len(self.categories) columnWidth = self._width - 180 tabWidth = 100 tabSpacing = 12 # _______/-minigames-\_/-utilities-\_______ for i, tab in enumerate(sorted(list(self.categories))): px = 140 + columnWidth / 2 - tabWidth * total / 2 + tabWidth * i pos = (px, self.columnPosY + 5) size = (tabWidth - tabSpacing, self.tabheight + 10) rad = 10 center = (pos[0] + 0.1 * size[0], pos[1] + 0.9 * size[1]) txt = TextWidget(parent=self._rootWidget, position=center, size=(0, 0), hAlign='center', vAlign='center', maxWidth=1.4 * rad, scale=0.6, shadow=1.0, flatness=1.0) button = ButtonWidget(parent=self._rootWidget, position=pos, autoSelect=True, buttonType='tab', size=size, label=tab, enableSound=False, onActivateCall=bs.Call(self._cb_select_tab, i), color=(0.52, 0.48, 0.63), textColor=(0.65, 0.6, 0.7)) self.tabs.append({'text': txt, 'button': button, 'label': tab}) for i, tab in enumerate(self.tabs): if self._selectedTab["label"] == tab["label"]: self._cb_select_tab(i, refresh=False) def _cb_select_tab(self, index, refresh=True): bs.playSound(bs.getSound('click01')) self._selectedTab = self.tabs[index] for i, tab in enumerate(self.tabs): button = tab["button"] if i == index: button.set(color=(0.5, 0.4, 0.93), textColor=(0.85, 0.75, 0.95)) # lit else: button.set(color=(0.52, 0.48, 0.63), textColor=(0.65, 0.6, 0.7)) # unlit if refresh: self._refresh(refreshTabs=False) def _cb_select(self, index, mod): self._selectedModIndex = index self._selectedMod = mod def _cb_refresh(self, force_fresh=False): self.mods = [] localfiles = os.listdir(bs.getEnvironment()['userScriptsDirectory'] + "/") for file in localfiles: if file.endswith(".py"): self.mods.append(LocalMod(file)) # if CHECK_FOR_UPDATES: # for mod in self.mods: # if mod.checkUpdate(): # bs.screenMessage('Update available for ' + mod.filename) # UpdateModWindow(mod, self._cb_refresh) self._refresh() self.currently_fetching = True def f(*args, **kwargs): kwargs["force_fresh"] = force_fresh self._cb_serverdata(*args, **kwargs) get_index(f, force_fresh=force_fresh) self.timers["showFetchingIndicator"] = bs.Timer(500, bs.WeakCall(self._showFetchingIndicator), timeType='real') def _cb_serverdata(self, data, status_code, force_fresh=False): if not self._rootWidget.exists(): return self.currently_fetching = False if data: m, v = process_server_data(data) # when we got network add the network mods localMods = self.mods[:] netMods = [Mod(d) for d in m.values()] self.mods = netMods netFilenames = [m.filename for m in netMods] for localmod in localMods: if localmod.filename not in netFilenames: self.mods.append(localmod) for mod in self.mods: mod._mods = {m.base: m for m in self.mods} self._refresh() else: bs.screenMessage('network error :(') fetch_stats(self._cb_stats, force_fresh=force_fresh) def _cb_stats(self, data, status_code): if not self._rootWidget.exists() or not data: return def fill_mods_with(d, attr): for mod_id, value in d.items(): for mod in self.mods: if mod.base == mod_id: setattr(mod, attr, value) fill_mods_with(data.get('average_ratings', {}), 'rating') fill_mods_with(data.get('own_ratings', {}), 'own_rating') fill_mods_with(data.get('amount_ratings', {}), 'rating_submissions') fill_mods_with(data.get('downloads', {}), 'downloads') self._refresh() def _showFetchingIndicator(self): if self.currently_fetching: bs.screenMessage("loading...") def _cb_info(self, withSound=False): if withSound: bs.playSound(bs.getSound('swish')) ModInfoWindow(self._selectedMod, self, originWidget=self.modInfoButton) def _cb_settings(self): SettingsWindow(self._selectedMod, self, originWidget=self.settingsButton) def _cb_sorting(self): self.sortMode = self.sortModes[self.sortMode['next']] while not self.sortMode['condition'](self.mods): self.sortMode = self.sortModes[self.sortMode['next']] config['sortMode'] = self.sortMode['name'] bs.writeConfig() self.sortButton.label = "Sorting:\n" + self.sortMode['name'] self._cb_refresh() def _back(self): self._rootWidget.doTransition(self._transitionOut) if not self._modal: uiGlobals['mainMenuWindow'] = self._backLocationCls(transition='inLeft').getRootWidget() if self._onCloseCall is not None: self._onCloseCall() class UpdateModWindow(Window): def __init__(self, mod, onok, swish=True, back=False): self._back = back self.mod = mod self.onok = bs.WeakCall(onok) if swish: bs.playSound(bs.getSound('swish')) text = "Do you want to update %s?" if mod.is_installed() else "Do you want to install %s?" text = text % (mod.filename) if mod.changelog and mod.is_installed(): text += "\n\nChangelog:" for change in mod.changelog: text += "\n" + change height = 100 * (1 + len(mod.changelog) * 0.3) if mod.is_installed() else 100 width = 360 * (1 + len(mod.changelog) * 0.15) if mod.is_installed() else 360 self._rootWidget = ConfirmWindow(text, self.ok, height=height, width=width).getRootWidget() def ok(self): self.mod.install(lambda mod, success: self.onok()) class DeleteModWindow(Window): def __init__(self, mod, onok, swish=True, back=False): self._back = back self.mod = mod self.onok = bs.WeakCall(onok) if swish: bs.playSound(bs.getSound('swish')) self._rootWidget = ConfirmWindow("Are you sure you want to delete " + mod.filename, self.ok).getRootWidget() def ok(self): self.mod.delete(self.onok) QuitToApplyWindow() class RateModWindow(Window): levels = ["Poor", "Below Average", "Average", "Above Average", "Excellent"] icons = ["trophy0b", "trophy1", "trophy2", "trophy3", "trophy4"] def __init__(self, mod, onok, swish=True, back=False): self._back = back self.mod = mod self.onok = onok if swish: bs.playSound(bs.getSound('swish')) text = "How do you want to rate {}?".format(mod.name) okText = bs.Lstr(resource='okText') cancelText = bs.Lstr(resource='cancelText') width = 360 height = 330 self._rootWidget = ContainerWidget(size=(width, height), transition='inRight', scale=2.1 if gSmallUI else 1.5 if gMedUI else 1.0) TextWidget(parent=self._rootWidget, position=(width * 0.5, height - 30), size=(0, 0), hAlign="center", vAlign="center", text=text, maxWidth=width * 0.9, maxHeight=height - 75) b = ButtonWidget(parent=self._rootWidget, autoSelect=True, position=(20, 20), size=(150, 50), label=cancelText, onActivateCall=self._cancel) self._rootWidget.set(cancelButton=b) okButtonH = width - 175 b = ButtonWidget(parent=self._rootWidget, autoSelect=True, position=(okButtonH, 20), size=(150, 50), label=okText, onActivateCall=self._ok) self._rootWidget.set(selectedChild=b, startButton=b) columnPosY = height - 75 _scrollHeight = height - 150 scrollWidget = ScrollWidget(parent=self._rootWidget, position=(20, columnPosY - _scrollHeight), size=(width - 40, _scrollHeight + 10)) columnWidget = ColumnWidget(parent=scrollWidget) self._rootWidget.set(selectedChild=columnWidget) self.selected = self.mod.own_rating or 2 for num, name in enumerate(self.levels): s = bs.getSpecialChar(self.icons[num]) + name w = TextWidget(parent=columnWidget, size=(width - 40, 24 + 8), maxWidth=width - 110, text=s, scale=0.85, hAlign='left', vAlign='center', alwaysHighlight=True, onSelectCall=bs.Call(self._select, num), onActivateCall=bs.Call(self._ok), selectable=True) w.showBufferTop = 50 w.showBufferBottom = 50 if num == self.selected: columnWidget.set(selectedChild=w, visibleChild=w) self._rootWidget.set(selectedChild=w) elif num == 4: w.downWidget = b def _select(self, index): self.selected = index def _cancel(self): self._rootWidget.doTransition('outRight') def _ok(self): if not self._rootWidget.exists(): return self._rootWidget.doTransition('outLeft') self.onok(self.selected) class QuitToApplyWindow(Window): def __init__(self): global quittoapply if quittoapply is not None: quittoapply.delete() quittoapply = None bs.playSound(bs.getSound('swish')) text = "Quit BS to reload mods?" if bs.getEnvironment()["platform"] == "android": text += "\n(On Android you have to close the activity)" self._rootWidget = quittoapply = ConfirmWindow(text, self._doFadeAndQuit).getRootWidget() def _doFadeAndQuit(self): # FIXME: using protected apis bsInternal._fadeScreen(False, time=200, endCall=bs.Call(bs.quit, soft=True)) bsInternal._lockAllInput() # unlock and fade back in shortly.. just in case something goes wrong # (or on android where quit just backs out of our activity and we may come back) bs.realTimer(300, bsInternal._unlockAllInput) # bs.realTimer(300, bs.Call(bsInternal._fadeScreen,True)) class ModInfoWindow(Window): def __init__(self, mod, modManagerWindow, originWidget=None): # TODO: cleanup self.modManagerWindow = modManagerWindow self.mod = mod s = 1.1 if gSmallUI else 1.27 if gMedUI else 1.57 bColor = (0.6, 0.53, 0.63) bTextColor = (0.75, 0.7, 0.8) width = 360 * s height = 40 + 100 * s if mod.author: height += 25 if not mod.isLocal: height += 50 if mod.rating is not None: height += 50 if mod.downloads: height += 50 buttons = sum([(mod.checkUpdate() or not mod.is_installed()), mod.is_installed(), mod.is_installed(), True]) color = (1, 1, 1) textScale = 0.7 * s # if they provided an origin-widget, scale up from that if originWidget is not None: self._transitionOut = 'outScale' scaleOrigin = originWidget.getScreenSpaceCenter() transition = 'inScale' else: self._transitionOut = None scaleOrigin = None transition = 'inRight' self._rootWidget = ContainerWidget(size=(width, height), transition=transition, scale=2.1 if gSmallUI else 1.5 if gMedUI else 1.0, scaleOriginStackOffset=scaleOrigin) pos = height * 0.9 labelspacing = height / (7.0 if (mod.rating is None and not mod.downloads) else 7.5) if mod.tag: TextWidget(parent=self._rootWidget, position=(width * 0.49, pos), size=(0, 0), hAlign="right", vAlign="center", text=mod.name, scale=textScale * 1.5, color=color, maxWidth=width * 0.9, maxHeight=height - 75) TextWidget(parent=self._rootWidget, position=(width * 0.51, pos - labelspacing * 0.1), hAlign="left", vAlign="center", text=mod.tag, scale=textScale * 0.9, color=(1, 0.3, 0.3), big=True, size=(0, 0)) else: TextWidget(parent=self._rootWidget, position=(width * 0.5, pos), size=(0, 0), hAlign="center", vAlign="center", text=mod.name, scale=textScale * 1.5, color=color, maxWidth=width * 0.9, maxHeight=height - 75) pos -= labelspacing if mod.author: TextWidget(parent=self._rootWidget, position=(width * 0.5, pos), size=(0, 0), hAlign="center", vAlign="center", text="by " + mod.author, scale=textScale, color=color, maxWidth=width * 0.9, maxHeight=height - 75) pos -= labelspacing if not mod.isLocal: if mod.checkUpdate(): if mod.is_outdated(): status = "update available" else: status = "unrecognized version" else: status = "installed" if not mod.is_installed(): status = "not installed" TextWidget(parent=self._rootWidget, position=(width * 0.45, pos), size=(0, 0), hAlign="right", vAlign="center", text="Status:", scale=textScale, color=color, maxWidth=width * 0.9, maxHeight=height - 75) status = TextWidget(parent=self._rootWidget, position=(width * 0.55, pos), size=(0, 0), hAlign="left", vAlign="center", text=status, scale=textScale, color=color, maxWidth=width * 0.9, maxHeight=height - 75) pos -= labelspacing * 0.775 if mod.downloads: TextWidget(parent=self._rootWidget, position=(width * 0.45, pos), size=(0, 0), hAlign="right", vAlign="center", text="Downloads:", scale=textScale, color=color, maxWidth=width * 0.9, maxHeight=height - 75) TextWidget(parent=self._rootWidget, position=(width * 0.55, pos), size=(0, 0), hAlign="left", vAlign="center", text=str(mod.downloads), scale=textScale, color=color, maxWidth=width * 0.9, maxHeight=height - 75) pos -= labelspacing * 0.775 if mod.rating is not None: TextWidget(parent=self._rootWidget, position=(width * 0.45, pos), size=(0, 0), hAlign="right", vAlign="center", text="Rating:", scale=textScale, color=color, maxWidth=width * 0.9, maxHeight=height - 75) rating_str = bs.getSpecialChar(RateModWindow.icons[mod.rating]) + RateModWindow.levels[mod.rating] TextWidget(parent=self._rootWidget, position=(width * 0.4725, pos), size=(0, 0), hAlign="left", vAlign="center", text=rating_str, scale=textScale, color=color, maxWidth=width * 0.9, maxHeight=height - 75) pos -= labelspacing * 0.775 submissions = "({} {})".format(mod.rating_submissions, "submission" if mod.rating_submissions < 2 else "submissions") TextWidget(parent=self._rootWidget, position=(width * 0.4725, pos), size=(0, 0), hAlign="left", vAlign="center", text=submissions, scale=textScale, color=color, maxWidth=width * 0.9, maxHeight=height - 75) pos += labelspacing * 0.3 if not mod.author and mod.isLocal: pos -= labelspacing if not (gSmallUI or gMedUI): pos -= labelspacing * 0.25 pos -= labelspacing * 2.55 self.button_index = -1 def button_pos(): self.button_index += 1 d = { 1: [0.5], 2: [0.3, 0.7], 3: [0.2, 0.45, 0.8], 4: [0.17, 0.390, 0.61, 0.825], } x = width * d[buttons][self.button_index] y = pos sx, sy = button_size() x -= sx / 2 y += sy / 2 return x, y def button_size(): sx = {1: 100, 2: 80, 3: 80, 4: 75}[buttons] * s sy = 40 * s return sx, sy def button_text_size(): return {1: 0.8, 2: 1.0, 3: 1.2, 4: 1.2}[buttons] if mod.checkUpdate() or not mod.is_installed(): text = "Download Mod" if mod.is_outdated(): text = "Update Mod" elif mod.checkUpdate(): text = "Reset Mod" self.downloadButton = ButtonWidget(parent=self._rootWidget, position=button_pos(), size=button_size(), onActivateCall=bs.Call(self._download,), color=bColor, autoSelect=True, textColor=bTextColor, buttonType='square', textScale=button_text_size(), label=text) if mod.is_installed(): self.deleteButton = ButtonWidget(parent=self._rootWidget, position=button_pos(), size=button_size(), onActivateCall=bs.Call(self._delete), color=bColor, autoSelect=True, textColor=bTextColor, buttonType='square', textScale=button_text_size(), label="Delete Mod") self.rateButton = ButtonWidget(parent=self._rootWidget, position=button_pos(), size=button_size(), onActivateCall=bs.Call(self._rate), color=bColor, autoSelect=True, textColor=bTextColor, buttonType='square', textScale=button_text_size(), label="Rate Mod" if mod.own_rating is None else "Change Rating") okButtonSize = button_size() okButtonPos = button_pos() okText = bs.Lstr(resource='okText') b = ButtonWidget(parent=self._rootWidget, autoSelect=True, position=okButtonPos, size=okButtonSize, label=okText, onActivateCall=self._ok) self._rootWidget.onCancelCall = b.activate self._rootWidget.selectedChild = b self._rootWidget.startButton = b def _ok(self): self._rootWidget.doTransition('outLeft' if self._transitionOut is None else self._transitionOut) def _delete(self): DeleteModWindow(self.mod, self.modManagerWindow._cb_refresh) self._ok() def _download(self): UpdateModWindow(self.mod, self.modManagerWindow._cb_refresh) self._ok() def _rate(self): def submit_cb(): self.modManagerWindow._cb_refresh(force_fresh=True) def cb(rating): submit_mod_rating(self.mod, rating, submit_cb) RateModWindow(self.mod, cb) self._ok() class SettingsWindow(Window): def __init__(self, mod, modManagerWindow, originWidget=None): self.modManagerWindow = modManagerWindow self.mod = mod s = 1.1 if gSmallUI else 1.27 if gMedUI else 1.57 bTextColor = (0.75, 0.7, 0.8) width = 380 * s height = 240 * s textScale = 0.7 * s # if they provided an origin-widget, scale up from that if originWidget is not None: self._transitionOut = 'outScale' scaleOrigin = originWidget.getScreenSpaceCenter() transition = 'inScale' else: self._transitionOut = None scaleOrigin = None transition = 'inRight' self._rootWidget = ContainerWidget(size=(width, height), transition=transition, scale=2.1 if gSmallUI else 1.5 if gMedUI else 1.0, scaleOriginStackOffset=scaleOrigin) self._titleText = TextWidget(parent=self._rootWidget, position=(0, height - 52), size=(width, 30), text="ModManager Settings", color=(1.0, 1.0, 1.0), hAlign="center", vAlign="top", scale=1.5 * textScale) pos = height * 0.65 TextWidget(parent=self._rootWidget, position=(width * 0.35, pos), size=(0, 40), hAlign="right", vAlign="center", text="Branch:", scale=textScale, color=bTextColor, maxWidth=width * 0.9, maxHeight=(height - 75)) self.branch = TextWidget(parent=self._rootWidget, position=(width * 0.4, pos), size=(width * 0.4, 40), text=config.get("branch", "master"), hAlign="left", vAlign="center", editable=True, padding=4, onReturnPressCall=self.setBranch) pos -= height * 0.125 checkUpdatesValue = config.get("submit-download-statistics", True) self.downloadStats = CheckBoxWidget(parent=self._rootWidget, text="submit download statistics", position=(width * 0.2, pos), size=(170, 30), textColor=(0.8, 0.8, 0.8), value=checkUpdatesValue, onValueChangeCall=self.setDownloadStats) pos -= height * 0.125 checkUpdatesValue = config.get("auto-check-updates", True) self.checkUpdates = CheckBoxWidget(parent=self._rootWidget, text="automatically check for updates", position=(width * 0.2, pos), size=(170, 30), textColor=(0.8, 0.8, 0.8), value=checkUpdatesValue, onValueChangeCall=self.setCheckUpdate) pos -= height * 0.125 autoUpdatesValue = config.get("auto-update-old-mods", True) self.autoUpdates = CheckBoxWidget(parent=self._rootWidget, text="auto-update outdated mods", position=(width * 0.2, pos), size=(170, 30), textColor=(0.8, 0.8, 0.8), value=autoUpdatesValue, onValueChangeCall=self.setAutoUpdate) self.checkAutoUpdateState() okButtonSize = (150, 50) okButtonPos = (width * 0.5 - okButtonSize[0] / 2, 20) okText = bs.Lstr(resource='okText') okButton = ButtonWidget(parent=self._rootWidget, position=okButtonPos, size=okButtonSize, label=okText, onActivateCall=self._ok) self._rootWidget.set(onCancelCall=okButton.activate, selectedChild=okButton, startButton=okButton) def _ok(self): if self.branch.text() != config.get("branch", "master"): self.setBranch() self._rootWidget.doTransition('outLeft' if self._transitionOut is None else self._transitionOut) def setBranch(self): branch = self.branch.text() if branch == '': branch = "master" bs.screenMessage("fetching branch '" + branch + "'") def cb(data, status_code): newBranch = branch if data: bs.screenMessage('ok') else: bs.screenMessage('failed to fetch branch') newBranch = "master" bs.screenMessage("set branch to " + newBranch) config["branch"] = newBranch bs.writeConfig() self.modManagerWindow._cb_refresh() get_index(cb, branch=branch) def setCheckUpdate(self, val): config["auto-check-updates"] = bool(val) bs.writeConfig() self.checkAutoUpdateState() def checkAutoUpdateState(self): if not self.checkUpdates.value: # FIXME: properly disable checkbox self.autoUpdates.set(value=False, color=(0.65, 0.65, 0.65), textColor=(0.65, 0.65, 0.65)) else: # FIXME: match original color autoUpdatesValue = config.get("auto-update-old-mods", True) self.autoUpdates.set(value=autoUpdatesValue, color=(0.475, 0.6, 0.2), textColor=(0.8, 0.8, 0.8)) def setAutoUpdate(self, val): # FIXME: properly disable checkbox if not self.checkUpdates.value: bs.playSound(bs.getSound("error")) self.autoUpdates.value = False return config["auto-update-old-mods"] = bool(val) bs.writeConfig() def setDownloadStats(self, val): config["submit-download-statistics"] = bool(val) bs.writeConfig() class Mod: name = False author = None filename = None base = None changelog = [] old_md5s = [] url = False isLocal = False category = None requires = [] supports = [] rating = None rating_submissions = 0 own_rating = None downloads = None tag = None data = None def __init__(self, d): self.data = d self.author = d.get('author') if 'filename' in d: self.filename = d['filename'] self.base = self.filename[:-3] else: raise RuntimeError('mod without filename') if 'name' in d: self.name = d['name'] else: self.name = self.filename if 'md5' in d: self.md5 = d['md5'] else: raise RuntimeError('mod without md5') self.changelog = d.get('changelog', []) self.old_md5s = d.get('old_md5s', []) self.category = d.get('category', None) self.requires = d.get('requires', []) self.supports = d.get('supports', []) self.tag = d.get('tag', None) def writeData(self, callback, doQuitWindow, data, status_code): path = bs.getEnvironment()['userScriptsDirectory'] + "/" + self.filename if data: if self.is_installed(): os.rename(path, path + ".bak") # rename the old file to be able to recover it if something goes wrong with open(path, 'w') as f: f.write(data) else: bs.screenMessage("Failed to write mod") if callback: callback(self, data is not None) if doQuitWindow: QuitToApplyWindow() submit_download(self) def install(self, callback, doQuitWindow=True): def check_deps_and_install(mod=None, succeded=True): if any([dep not in self._mods for dep in self.requires]): raise Exception("dependency inconsistencies") if not all([self._mods[dep].up_to_date() for dep in self.requires]) or not succeded: return fetch_mod(self.data, partial(self.writeData, callback, doQuitWindow)) if len(self.requires) < 1: check_deps_and_install() else: for dep in self.requires: bs.screenMessage(self.name + " requires " + dep + "; installing...") if not self._mods: raise Exception("missing mod._mods") if dep not in self._mods: raise Exception("dependency inconsistencies (missing " + dep + ")") self._mods[dep].install(check_deps_and_install, False) @property def ownData(self): path = bs.getEnvironment()['userScriptsDirectory'] + "/" + self.filename if os.path.exists(path): with open(path, "r") as ownFile: return ownFile.read() def delete(self, cb=None): path = bs.getEnvironment()['userScriptsDirectory'] + "/" + self.filename os.rename(path, path + ".bak") # rename the old file to be able to recover it if something goes wrong if os.path.exists(path + "c"): # check for python bytecode os.remove(path + "c") # remove python bytecode because importing still works without .py file if cb: cb() def checkUpdate(self): if not self.is_installed(): return False if self.local_md5() != self.md5: return True return False def up_to_date(self): return self.is_installed() and self.local_md5() == self.md5 def is_installed(self): return os.path.exists(bs.getEnvironment()['userScriptsDirectory'] + "/" + self.filename) def local_md5(self): return md5(self.ownData).hexdigest() def is_outdated(self): if not self.old_md5s or not self.is_installed(): return False local_md5 = self.local_md5() for old_md5 in self.old_md5s: if local_md5.startswith(old_md5): return True return False class LocalMod(Mod): isLocal = True def __init__(self, filename): self.filename = filename self.base = self.filename[:-3] self.name = filename + " (Local Only)" with open(bs.getEnvironment()['userScriptsDirectory'] + "/" + filename, "r") as ownFile: self.ownData = ownFile.read() def checkUpdate(self): return False def is_installed(self): return True def up_to_date(self): return True def getData(self): return False def writeData(self, data=None): bs.screenMessage("Can't update local-only mod!") _setTabOld = StoreWindow._setTab def _setTab(self, tab): _setTabOld(self, tab) if hasattr(self, "_getMoreGamesButton"): if self._getMoreGamesButton.exists(): self._getMoreGamesButton.delete() if tab == "minigames": self._getMoreGamesButton = bs.buttonWidget(parent=self._rootWidget, autoSelect=True, label=bs.Lstr(resource='addGameWindow.getMoreGamesText'), color=(0.54, 0.52, 0.67), textColor=(0.7, 0.65, 0.7), onActivateCall=self._onGetMoreGamesPress, size=(178, 50), position=(70, 60)) # TODO: transitions def _onGetMoreGamesPress(self): if not self._modal: bs.containerWidget(edit=self._rootWidget, transition='outLeft') mm_window = ModManagerWindow(modal=self._modal, backLocationCls=self.__class__, showTab="minigames") if not self._modal: uiGlobals['mainMenuWindow'] = mm_window.getRootWidget() StoreWindow._setTab = _setTab StoreWindow._onGetMoreGamesPress = _onGetMoreGamesPress ================================================ FILE: mods/puckDeathmatch.json ================================================ { "name": "Puck Deathmatch", "author": "Mrmaxmeier", "category": "minigames" } ================================================ FILE: mods/puckDeathmatch.py ================================================ import bs import bsHockey import random class PuckTouchedMessage(object): pass class Puck(bsHockey.Puck): def __init__(self, position, team): bsHockey.Puck.__init__(self, position) self.team = team self.tickrate = 100 self._timeout = 5000 / self.tickrate self._count = self._timeout self._tickTimer = bs.Timer(self.tickrate, call=bs.WeakCall(self._tick), repeat=True) self._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'}) self.age = 0 self.scored = False self.lastHoldingPlayer = None self.light = None self.movedSinceSpawn = False def _tick(self): self.age += 1 if self.node.exists(): if sum([abs(v) for v in self.node.velocity]) > 0.5: if self.age > 3000 / self.tickrate: self.movedSinceSpawn = True self._count = self._timeout self._counter.text = '' else: self._count -= 1 if self._count <= 10 * self.tickrate and self.movedSinceSpawn: t = self.node.position self._counter.position = (t[0], t[1]+1.0, t[2]) self._counter.text = str(round(self._count * self.tickrate / 1000.0, 2)) if self._count < 1: self.handleMessage(bs.OutOfBoundsMessage()) else: self._counter.text = '' def handleMessage(self, m): if isinstance(m, PuckTouchedMessage): node = bs.getCollisionInfo("opposingNode") #bs.screenMessage(str(node.position)) #node.sourcePlayer if node.sourcePlayer.getTeam() == self.team: return #Score - isAlive to avoid multiple kills per death if 'notKilled' not in node.sourcePlayer.gameData: node.sourcePlayer.gameData['notKilled'] = True if node.sourcePlayer.gameData['notKilled']: #node.sourcePlayer.getTeam().gameData['timesKilled'] += 1 self.team.gameData['score'] += 1 bs.getActivity()._updateScoreBoard() node.sourcePlayer.gameData['notKilled'] = False x, y, z = node.position node.handleMessage("impulse", x, y, z, 0, 0, 0, #velocity 1000.0, 0, 3, 0, 0, 0, 0) # forceDirection node.frozen = True bs.gameTimer(1000, node.sourcePlayer.actor.shatter) if isinstance(m, bs.OutOfBoundsMessage): self.node.position = self._spawnPos self.movedSinceSpawn = False self.age = 0 else: bsHockey.Puck.handleMessage(self, m) def bsGetAPIVersion(): return 4 def bsGetGames(): return [PuckDeathMatch] class PuckDeathMatch(bs.TeamGameActivity): @classmethod def getName(cls): return 'Puck Deathmatch' @classmethod def getScoreInfo(cls): return {'scoreType':'points', 'lowerIsBetter':False, 'scoreName':'Score'} @classmethod def getDescription(cls, sessionType): return 'Kill everyone with your Puck' @classmethod def getSupportedMaps(cls, sessionType): return bs.getMapsSupportingPlayType("melee") @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.TeamsSession) or issubclass(sessionType, bs.FreeForAllSession)) else False @classmethod def getSettings(cls, sessionType): return [("Kills to Win", {'minValue': 1, 'default': 5, 'increment': 1})] # in the constructor we should load any media we need/etc. # but not actually create anything yet. def __init__(self, settings): bs.TeamGameActivity.__init__(self, settings) self._winSound = bs.getSound("score") self._cheerSound = bs.getSound("cheer") self._chantSound = bs.getSound("crowdChant") self._foghornSound = bs.getSound("foghorn") self._swipSound = bs.getSound("swip") self._whistleSound = bs.getSound("refWhistle") self._puckModel = bs.getModel("puck") self._puckTex = bs.getTexture("puckColor") self._puckSound = bs.getSound("metalHit") self._puckMaterial = bs.Material() self._puckMaterial.addActions(actions=( ("modifyPartCollision","friction",0.1))) self._puckMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('pickupMaterial')), actions=( ("modifyPartCollision","collide",False) ) ) self._puckMaterial.addActions(conditions=( ("weAreYoungerThan",100),'and', ("theyHaveMaterial",bs.getSharedObject('objectMaterial')) ), actions=( ("modifyNodeCollision","collide",False) ) ) self._puckMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('footingMaterial')), actions=(("impactSound",self._puckSound,0.2,5))) # keep track of which player last touched the puck self._puckMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('playerMaterial')), actions=(("call","atConnect",self._handlePuckPlayerCollide),)) # we want the puck to kill powerups; not get stopped by them self._puckMaterial.addActions(conditions=("theyHaveMaterial",bs.Powerup.getFactory().powerupMaterial), actions=(("modifyPartCollision","physical",False), ("message","theirNode","atConnect",bs.DieMessage()))) # dis is kill self._puckMaterial.addActions(conditions=("theyHaveMaterial",bs.getSharedObject('playerMaterial')), actions=(("modifyPartCollision","physical",False), ("message", "ourNode", "atConnect", PuckTouchedMessage()))) self._scoreBoard = bs.ScoreBoard() self._killsToWin = self.settings['Kills to Win'] self._scoreSound = bs.getSound("score") self.pucks = [] # called when our game is transitioning in but not ready to start.. # ..we can go ahead and start creating stuff, playing music, etc. def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='ToTheDeath') # called when our game actually starts def onBegin(self): bs.TeamGameActivity.onBegin(self) self._won = False #for team in self.teams: # team.gameData['timesKilled'] = 0 #self._updateScoreBoard() #for team in self.teams: # self._spawnPuck(team.getID()) self.setupStandardPowerupDrops() def onPlayerJoin(self, player): self._spawnPuck(player.getTeam()) self._updateScoreBoard() bs.TeamGameActivity.onPlayerJoin(self, player) def onTeamJoin(self, team): team.gameData['score'] = 0 bs.TeamGameActivity.onTeamJoin(self, team) # called for each spawning player def spawnPlayer(self, player): # lets spawn close to the center #spawnCenter = (1,4,0) #pos = (spawnCenter[0]+random.uniform(-1.5,1.5),spawnCenter[1],spawnCenter[2]+random.uniform(-1.5,1.5)) pos = self.getMap().getStartPosition(player.getTeam().getID()) spaz = self.spawnPlayerSpaz(player, position=pos) spaz.connectControlsToPlayer(enablePunch=True, enableBomb=False, enablePickUp=True) player.gameData['notKilled'] = True def _flashPuckSpawn(self, pos): light = bs.newNode('light', attrs={'position': pos, 'heightAttenuated':False, 'color': (1, 0, 0)}) bs.animate(light, 'intensity', {0: 0, 250: 1, 500: 0}, loop=True) bs.gameTimer(1000, light.delete) def _spawnPuck(self, team): puckPos = self.getMap().getStartPosition(team.getID()) lightcolor = team.color bs.playSound(self._swipSound) bs.playSound(self._whistleSound) self._flashPuckSpawn(puckPos) puck = Puck(position=puckPos, team=team) puck.light = bs.newNode('light', owner=puck.node, attrs={'intensity':0.3, 'heightAttenuated':False, 'radius':0.2, 'color': lightcolor}) puck.node.connectAttr('position', puck.light, 'position') self.pucks.append(puck) def _handlePuckPlayerCollide(self): try: puckNode, playerNode = bs.getCollisionInfo('sourceNode', 'opposingNode') puck = puckNode.getDelegate() player = playerNode.getDelegate().getPlayer() except Exception: player = puck = None if player is not None and player.exists() and puck is not None: puck.lastPlayersToTouch[player.getTeam().getID()] = player def _checkIfWon(self): # simply end the game if there's no living bots.. for team in self.teams: if team.gameData['score'] >= self._killsToWin: self._won = True self.endGame() def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team, team.gameData['score'], self._killsToWin) self._checkIfWon() # called for miscellaneous events def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # do standard stuff self.respawnPlayer(m.spaz.getPlayer()) # kick off a respawn else: # let the base class handle anything we don't.. bs.TeamGameActivity.handleMessage(self, m) # when this is called, we should fill out results and end the game # *regardless* of whether is has been won. (this may be called due # to a tournament ending or other external reason) def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team, team.gameData['score']) self.end(results=results) ================================================ FILE: mods/quickGameButton.json ================================================ { "name": "Quick-Game Button", "author": "Mrmaxmeier", "category": "utilities" } ================================================ FILE: mods/quickGameButton.py ================================================ import bs import bsInternal import bsTeamGame from bsUI import PlayWindow, AddGameWindow, gSmallUI, gMedUI, gTitleColor, uiGlobals, gWindowStates import bsUtils _supports_auto_reloading = True _auto_reloader_type = "patching" PlayWindow__init__ = PlayWindow.__init__ PlayWindow_save_state = PlayWindow._save_state PlayWindow_restore_state = PlayWindow._restore_state def _prepare_reload(): PlayWindow.__init__ = PlayWindow__init__ PlayWindow._save_state = PlayWindow_save_state PlayWindow._restore_state = PlayWindow_restore_state # TODO: support other gametypes than free-for-all if "quickGameButton" in bs.getConfig(): config = bs.getConfig()["quickGameButton"] else: config = {"selected": None, "config": None} bs.getConfig()["quickGameButton"] = config bs.writeConfig() def startGame(session, fadeout=True): def callback(): if fadeout: bsInternal._unlockAllInput() try: bsInternal._newHostSession(session) except Exception: import bsMainMenu bs.printException("exception running session", session) # drop back into a main menu session.. bsInternal._newHostSession(bsMainMenu.MainMenuSession) if fadeout: bsInternal._fadeScreen(False, time=250, endCall=callback) bsInternal._lockAllInput() else: callback() class SimplePlaylist(object): def __init__(self, settings, gameType): self.settings = settings self.gameType = gameType def pullNext(self): if "map" not in self.settings["settings"]: settings = dict(map=self.settings["map"], **self.settings["settings"]) else: settings = self.settings["settings"] return dict(resolvedType=self.gameType, settings=settings) class CustomSession(bsTeamGame.FreeForAllSession): def __init__(self, *args, **kwargs): self._useTeams = False self._tutorialActivityInstance = None bs.Session.__init__(self, teamNames=None, teamColors=None, useTeamColors=False, minPlayers=1, maxPlayers=self.getMaxPlayers()) self._haveShownControlsHelpOverlay = False self._seriesLength = 1 self._ffaSeriesLength = 1 # which game activity we're on self._gameNumber = 0 self._playlist = SimplePlaylist(self._config, self._gameType) config["selected"] = self._gameType.__name__ config["config"] = self._config bs.writeConfig() # get a game on deck ready to go self._currentGameSpec = None self._nextGameSpec = self._playlist.pullNext() self._nextGame = self._nextGameSpec["resolvedType"] # go ahead and instantiate the next game we'll use so it has lots of time to load self._instantiateNextGame() # start in our custom join screen self.setActivity(bs.newActivity(bsTeamGame.TeamJoiningActivity)) class SelectGameWindow(AddGameWindow): def __init__(self, transition='inRight'): class EditSession: _sessionType = bs.FreeForAllSession def getSessionType(self): return self._sessionType self._editSession = EditSession() self._width = 650 self._height = 346 if gSmallUI else 380 if gMedUI else 440 topExtra = 30 if gSmallUI else 20 self._scrollWidth = 210 self._rootWidget = bs.containerWidget(size=(self._width, self._height+topExtra), transition=transition, scale=2.17 if gSmallUI else 1.5 if gMedUI else 1.0, stackOffset=(0, 1) if gSmallUI else (0, 0)) self._backButton = bs.buttonWidget(parent=self._rootWidget, position=(58, self._height-53), size=(165, 70), scale=0.75, textScale=1.2, label=bs.Lstr(resource='backText'), autoSelect=True, buttonType='back', onActivateCall=self._back) self._selectButton = selectButton = bs.buttonWidget(parent=self._rootWidget, position=(self._width-172, self._height-50), autoSelect=True, size=(160, 60), scale=0.75, textScale=1.2, label=bs.Lstr(resource='selectText'), onActivateCall=self._add) bs.textWidget(parent=self._rootWidget, position=(self._width*0.5, self._height-28), size=(0, 0), scale=1.0, text="Select Game", hAlign='center', color=gTitleColor, maxWidth=250, vAlign='center') v = self._height - 64 self._selectedTitleText = bs.textWidget(parent=self._rootWidget, position=(self._scrollWidth+50+30, v-15), size=(0, 0), scale=1.0, color=(0.7, 1.0, 0.7, 1.0), maxWidth=self._width-self._scrollWidth-150, hAlign='left', vAlign='center') v -= 30 self._selectedDescriptionText = bs.textWidget(parent=self._rootWidget, position=(self._scrollWidth+50+30, v), size=(0, 0), scale=0.7, color=(0.5, 0.8, 0.5, 1.0), maxWidth=self._width-self._scrollWidth-150, hAlign='left') scrollHeight = self._height-100 v = self._height - 60 self._scrollWidget = bs.scrollWidget(parent=self._rootWidget, position=(61, v-scrollHeight), size=(self._scrollWidth, scrollHeight)) bs.widget(edit=self._scrollWidget, upWidget=self._backButton, leftWidget=self._backButton, rightWidget=selectButton) self._column = None v -= 35 bs.containerWidget(edit=self._rootWidget, cancelButton=self._backButton, startButton=selectButton) self._selectedGameType = None bs.containerWidget(edit=self._rootWidget, selectedChild=self._scrollWidget) self._refresh() if config["selected"]: for gt in bsUtils.getGameTypes(): if not gt.supportsSessionType(self._editSession._sessionType): continue if gt.__name__ == config["selected"]: self._refresh(selected=gt) self._setSelectedGameType(gt) def _refresh(self, selectGetMoreGamesButton=False, selected=None): if self._column is not None: self._column.delete() self._column = bs.columnWidget(parent=self._scrollWidget) gameTypes = [gt for gt in bsUtils.getGameTypes() if gt.supportsSessionType(self._editSession._sessionType)] # sort in this language gameTypes.sort(key=lambda g: g.getDisplayString()) for i, gameType in enumerate(gameTypes): t = bs.textWidget(parent=self._column, position=(0, 0), size=(self._width-88, 24), text=gameType.getDisplayString(), hAlign="left", vAlign="center", color=(0.8, 0.8, 0.8, 1.0), maxWidth=self._scrollWidth*0.8, onSelectCall=bs.Call(self._setSelectedGameType, gameType), alwaysHighlight=True, selectable=True, onActivateCall=bs.Call(bs.realTimer, 100, self._selectButton.activate)) if i == 0: bs.widget(edit=t, upWidget=self._backButton) if gameType == selected: bs.containerWidget(edit=self._column, selectedChild=t, visibleChild=t) self._getMoreGamesButton = bs.buttonWidget(parent=self._column, autoSelect=True, label=bs.Lstr(resource='addGameWindow.getMoreGamesText'), color=(0.54, 0.52, 0.67), textColor=(0.7, 0.65, 0.7), onActivateCall=self._onGetMoreGamesPress, size=(178, 50)) if selectGetMoreGamesButton: bs.containerWidget(edit=self._column, selectedChild=self._getMoreGamesButton, visibleChild=self._getMoreGamesButton) def _add(self): bsInternal._lockAllInput() # make sure no more commands happen bs.realTimer(100, bsInternal._unlockAllInput) gameconfig = {} if config["selected"] == self._selectedGameType.__name__: if config["config"]: gameconfig = config["config"] if "map" in gameconfig: gameconfig["settings"]["map"] = gameconfig.pop("map") self._selectedGameType.createConfigUI(self._editSession._sessionType, gameconfig, self.onEditGameDone) def onEditGameDone(self, config): if config: CustomSession._config = config CustomSession._gameType = self._selectedGameType startGame(CustomSession) else: bs.containerWidget(edit=uiGlobals["mainMenuWindow"], transition='outRight') uiGlobals["mainMenuWindow"] = SelectGameWindow(transition="inLeft").getRootWidget() def _back(self): bs.containerWidget(edit=self._rootWidget, transition='outRight') uiGlobals["mainMenuWindow"] = PlayWindow(transition="inLeft").getRootWidget() oldInit = PlayWindow.__init__ def newInit(self, *args, **kwargs): oldInit(self, *args, **kwargs) width = 800 height = 550 def doQuickGame(): self._save_state() uiGlobals["mainMenuWindow"] = SelectGameWindow().getRootWidget() bs.containerWidget(edit=self._rootWidget, transition='outLeft') self._quickGameButton = bs.buttonWidget(parent=self._rootWidget, autoSelect=True, position=(width - 55 - 120, height - 132), size=(120, 60), scale=1.1, textScale=1.2, label="custom...", onActivateCall=doQuickGame, color=(0.54, 0.52, 0.67), textColor=(0.7, 0.65, 0.7)) self._restore_state() PlayWindow.__init__ = newInit def states(self): return { "Team Games": self._teamsButton, "Co-op Games": self._coopButton, "Free-for-All Games": self._freeForAllButton, "Back": self._backButton, "Quick Game": self._quickGameButton } def _save_state(self): swapped = {v: k for k, v in states(self).items()} if self._rootWidget.getSelectedChild() in swapped: gWindowStates[self.__class__.__name__] = swapped[self._rootWidget.getSelectedChild()] else: print("error saving state for ", self.__class__, self._rootWidget.getSelectedChild()) PlayWindow._save_state = _save_state def _restore_state(self): if not hasattr(self, "_quickGameButton"): return # ensure that our monkey patched init ran if self.__class__.__name__ not in gWindowStates: bs.containerWidget(edit=self._rootWidget, selectedChild=self._coopButton) return sel = states(self).get(gWindowStates[self.__class__.__name__], None) if sel: bs.containerWidget(edit=self._rootWidget, selectedChild=sel) else: bs.containerWidget(edit=self._rootWidget, selectedChild=self._coopButton) print('error restoring state (', gWindowStates[self.__class__.__name__], ') for', self.__class__) PlayWindow._restore_state = _restore_state ================================================ FILE: mods/settings_patcher.json ================================================ { "name": "settings_patcher", "author": "Mrmaxmeier", "category": "libraries" } ================================================ FILE: mods/settings_patcher.py ================================================ import bs from bsUI import SettingsWindow, gSmallUI, gMedUI, gTitleColor, gDoAndroidNav, gWindowStates try: from ui_wrappers import TextWidget, ButtonWidget, ImageWidget except ImportError: bs.screenMessage("ui_wrappers missing", color=(1, 0, 0)) raise class SettingsButton: def __init__(self, id, text=None, icon=None, iconColor=None, sorting_position=None): self.id = id self.text = text self.icon = icon self.iconColor = iconColor self.sorting_position = sorting_position self.textOnly = icon is None self._cb = lambda x: None self._buttonInstance = None self.instanceLocals = {} def setText(self, text): self.text = text return self def setCallback(self, cb): self._cb = cb return self def add(self): buttons.append(self) return self def remove(self): buttons.remove(self) return self def setLocals(self, swinstance=None, **kwargs): self.instanceLocals.update(kwargs) if swinstance: if "button" in self.instanceLocals: setattr(swinstance, self.instanceLocals["button"], self._buttonInstance) return self def x(self, swinstance, index, bw, wmodsmallui=0.4, wmod=0.2): if self.icon: layout = iconbuttonlayouts[sum([b.icon is not None for b in buttons])] else: layout = textbuttonlayouts[sum([b.textOnly for b in buttons])] bw += wmodsmallui if gSmallUI else wmod for i in range(len(layout) + 1): if sum(layout[:i]) > index: row = i - 1 pos = index - sum(layout[:i - 1]) return swinstance._width / 2 + bw * (pos - layout[row] / 2.0) * (1.0 if self.icon else 1.05) def _create_icon_button(self, swinstance, index): width, height = swinstance._width, swinstance._gOnlyHeight layout = iconbuttonlayouts[sum([b.icon is not None for b in buttons])] bw = width / (max(layout) + (0.4 if gSmallUI else 0.2)) bwx = bw bh = height / (len(layout) + (0.5 if gSmallUI else 0.4)) # try to keep it squared if abs(1 - bw / bh) > 0.1: bwx *= (bh / bw - 1) / 2 + 1 bw = bh = min(bw, bh) for i in range(len(layout) + 1): if sum(layout[:i]) > index: row = i - 1 break x = self.x(swinstance, index, bwx) y = swinstance._height - 95 - (row + 0.8) * (bh - 10) button = ButtonWidget(parent=swinstance._rootWidget, autoSelect=True, position=(x, y), size=(bwx, bh), buttonType='square', label='') button.onActivateCall = lambda: self._cb(swinstance) x += (bwx - bw) / 2 TextWidget(parent=swinstance._rootWidget, text=self.text, position=(x + bw * 0.47, y + bh * 0.22), maxWidth=bw * 0.7, size=(0, 0), hAlign='center', vAlign='center', drawController=button, color=(0.7, 0.9, 0.7, 1.0)) iw, ih = bw * 0.65, bh * 0.65 i = ImageWidget(parent=swinstance._rootWidget, position=(x + bw * 0.49 - iw * 0.5, y + 43), size=(iw, ih), texture=bs.getTexture(self.icon), drawController=button) if self.iconColor: i.color = self.iconColor self._buttonInstance = button._instance return x, y def _create_text_button(self, swinstance, index, start_y): width, height = swinstance._width, swinstance._height - swinstance._gOnlyHeight layout = textbuttonlayouts[sum([b.textOnly for b in buttons])] bw = width / (max(layout) + (0.7 if gSmallUI else 0.4)) bh = height / len(layout) for i in range(len(layout) + 1): if sum(layout[:i]) > index: row = i - 1 break x = self.x(swinstance, index, bw, 0.7, 0.4) y = start_y - (row + 1) * bh button = ButtonWidget(parent=swinstance._rootWidget, autoSelect=True, position=(x, y), size=(bw, bh), label=self.text, color=ButtonWidget.COLOR_GREY, textColor=ButtonWidget.TEXTCOLOR_GREY) button.onActivateCall = lambda: self._cb(swinstance) self._buttonInstance = button._instance return x, y buttons = [] iconbuttonlayouts = { 0: [], 1: [1], 2: [2], 3: [2, 1], 4: [2, 2], 5: [3, 2], 6: [3, 3], 7: [4, 3], 8: [3, 3, 2], 9: [3, 3, 3] } textbuttonlayouts = { 1: [1], 2: [2], 3: [3], 4: [2, 2], 5: [3, 2], 6: [3, 3], 7: [4, 3] } if hasattr(SettingsWindow, "_doProfiles"): SettingsButton(id="Profiles", icon="cuteSpaz") \ .setCallback(lambda swinstance: swinstance._doProfiles()) \ .setText(bs.Lstr(resource='settingsWindow.playerProfilesText')) \ .add() if hasattr(SettingsWindow, "_doControllers"): SettingsButton(id="Controllers", icon="controllerIcon") \ .setCallback(lambda swinstance: swinstance._doControllers()) \ .setText(bs.Lstr(resource='settingsWindow.controllersText')) \ .setLocals(button="_controllersButton") \ .add() if hasattr(SettingsWindow, "_doGraphics"): SettingsButton(id="Graphics", icon="graphicsIcon") \ .setCallback(lambda swinstance: swinstance._doGraphics()) \ .setText(bs.Lstr(resource='settingsWindow.graphicsText')) \ .setLocals(button="_graphicsButton") \ .add() if hasattr(SettingsWindow, "_doAudio"): SettingsButton(id="Audio", icon="audioIcon", iconColor=(1, 1, 0)) \ .setCallback(lambda swinstance: swinstance._doAudio()) \ .setText(bs.Lstr(resource='settingsWindow.audioText')) \ .setLocals(button="_audioButton") \ .add() if hasattr(SettingsWindow, "_doAdvanced"): SettingsButton(id="Advanced", icon="advancedIcon", iconColor=(0.8, 0.95, 1)) \ .setCallback(lambda swinstance: swinstance._doAdvanced()) \ .setText(bs.Lstr(resource='settingsWindow.advancedText')) \ .setLocals(button="_advancedButton") \ .add() for i, button in enumerate(buttons): button.sorting_position = i def newInit(self, transition='inRight', originWidget=None): if originWidget is not None: self._transitionOut = 'outScale' scaleOrigin = originWidget.getScreenSpaceCenter() transition = 'inScale' else: self._transitionOut = 'outRight' scaleOrigin = None width = 600 if gSmallUI else 600 height = 360 if gSmallUI else 435 self._gOnlyHeight = height if any([b.textOnly for b in buttons]): if len(textbuttonlayouts[sum([b.textOnly for b in buttons])]) > 1: height += 80 if gSmallUI else 120 else: height += 60 if gSmallUI else 80 self._width, self._height = width, height R = bs.Lstr(resource='settingsWindow') topExtra = 20 if gSmallUI else 0 if originWidget is not None: self._rootWidget = bs.containerWidget(size=(width, height+topExtra), transition=transition, scaleOriginStackOffset=scaleOrigin, scale=1.75 if gSmallUI else 1.35 if gMedUI else 1.0, stackOffset=(0, -8) if gSmallUI else (0, 0)) else: self._rootWidget = bs.containerWidget(size=(width, height+topExtra), transition=transition, scale=1.75 if gSmallUI else 1.35 if gMedUI else 1.0, stackOffset=(0, -8) if gSmallUI else (0, 0)) self._backButton = b = bs.buttonWidget(parent=self._rootWidget, autoSelect=True, position=(40, height-55), size=(130, 60), scale=0.8, textScale=1.2, label=bs.Lstr(resource='backText'), buttonType='back', onActivateCall=self._doBack) bs.containerWidget(edit=self._rootWidget, cancelButton=b) t = bs.textWidget(parent=self._rootWidget, position=(0, height-44), size=(width, 25), text=bs.Lstr(resource='settingsWindow.titleText'), color=gTitleColor, hAlign="center", vAlign="center", maxWidth=130) if gDoAndroidNav: bs.buttonWidget(edit=b, buttonType='backSmall', size=(60, 60), label=bs.getSpecialChar('logoFlat')) bs.textWidget(edit=t, hAlign='left', position=(93, height-44)) icon_buttons = sorted([b for b in buttons if b.icon], key=lambda b: b.sorting_position) for i, button in enumerate(icon_buttons): x, y = button._create_icon_button(self, i) text_buttons = sorted([b for b in buttons if b.textOnly], key=lambda b: b.sorting_position) for i, button in enumerate(text_buttons): button._create_text_button(self, i, y) for button in buttons: button.setLocals(self) self._restoreState() SettingsWindow.__init__ = newInit def statedict(self): d = {button._buttonInstance: button.id for button in buttons} d.update({self._backButton: "Back"}) return d def _saveState(self): w = self._rootWidget.getSelectedChild() for k, v in statedict(self).items(): if w == k: gWindowStates[self.__class__.__name__] = {'selName': v} return bs.printError('error saving state for ' + str(self.__class__)) SettingsWindow._saveState = _saveState def _restoreState(self): sel = None if self.__class__.__name__ in gWindowStates and 'selName' in gWindowStates[self.__class__.__name__]: selName = gWindowStates[self.__class__.__name__]['selName'] for k, v in statedict(self).items(): if selName == v: sel = k sel = sel or buttons[0]._buttonInstance bs.containerWidget(edit=self._rootWidget, selectedChild=sel) SettingsWindow._restoreState = _restoreState ================================================ FILE: mods/smash.json ================================================ { "name": "Super Smash", "author": "Mrmaxmeier", "category": "minigames" } ================================================ FILE: mods/smash.py ================================================ import random import bs import bsUtils import bsElimination import bsBomb class Icon(bsElimination.Icon): def updateForLives(self): if self._player.exists(): lives = self._player.gameData['lives'] else: lives = 0 if self._showLives: if lives > 0: self._livesText.text = 'x' + str(lives - 1) elif lives < 0: self._livesText.text = str(lives) else: self._livesText.text = '' if lives == 0: if hasattr(bs.getActivity(), 'timeLimitOnly'): if not bs.getActivity().timeLimitOnly: self._nameText.opacity = 0.2 self.node.color = (0.7, 0.3, 0.3) self.node.opacity = 0.2 class PowBox(bsBomb.Bomb): def __init__(self, position=(0, 1, 0), velocity=(0, 0, 0)): bsBomb.Bomb.__init__(self, position, velocity, bombType='tnt', blastRadius=2.5, sourcePlayer=None, owner=None) self.setPowText() def setPowText(self, color=(1, 1, 0.4)): m = bs.newNode('math', owner=self.node, attrs={'input1': (0, 0.7, 0), 'operation': 'add'}) self.node.connectAttr('position', m, 'input2') self._powText = bs.newNode('text', owner=self.node, attrs={'text':'POW!', 'inWorld':True, 'shadow':1.0, 'flatness':1.0, 'color':color, 'scale':0.0, 'hAlign':'center'}) m.connectAttr('output', self._powText, 'position') bs.animate(self._powText, 'scale', {0: 0.0, 1000: 0.01}) def handleMessage(self, m): if isinstance(m, bs.PickedUpMessage): self._heldBy = m.node elif isinstance(m, bs.DroppedMessage): bs.animate(self._powText, 'scale', {0:0.01, 600: 0.03}) bs.gameTimer(600, bs.WeakCall(self.pow)) bsBomb.Bomb.handleMessage(self, m) def pow(self): self.explode() class PlayerSpaz_Smash(bs.PlayerSpaz): multiplyer = 1 isDead = False #def __init__(self, *args, **kwargs): # super(self.__class__, self).init(*args, **kwargs) # self.multiplyer = 0 def handleMessage(self, m): if isinstance(m, bs.HitMessage): if not self.node.exists(): return if self.node.invincible == True: bs.playSound(self.getFactory().blockSound, 1.0, position=self.node.position) return True # if we were recently hit, don't count this as another # (so punch flurries and bomb pileups essentially count as 1 hit) gameTime = bs.getGameTime() if self._lastHitTime is None or gameTime - self._lastHitTime > 1000: self._numTimesHit += 1 self._lastHitTime = gameTime mag = m.magnitude * self._impactScale velocityMag = m.velocityMagnitude * self._impactScale damageScale = 0.22 # if they've got a shield, deliver it to that instead.. if self.shield is not None: if m.flatDamage: damage = m.flatDamage * self._impactScale else: # hit our spaz with an impulse but tell it to only return theoretical damage; not apply the impulse.. self.node.handleMessage("impulse", m.pos[0], m.pos[1], m.pos[2], m.velocity[0], m.velocity[1], m.velocity[2], mag, velocityMag, m.radius, 1, m.forceDirection[0], m.forceDirection[1], m.forceDirection[2]) damage = damageScale * self.node.damage self.shieldHitPoints -= damage self.shield.hurt = 1.0 - self.shieldHitPoints / self.shieldHitPointsMax # its a cleaner event if a hit just kills the shield without damaging the player.. # however, massive damage events should still be able to damage the player.. # this hopefully gives us a happy medium. maxSpillover = 500 if self.shieldHitPoints <= 0: # fixme - transition out perhaps?.. self.shield.delete() self.shield = None bs.playSound(self.getFactory().shieldDownSound, 1.0, position=self.node.position) # emit some cool lookin sparks when the shield dies t = self.node.position bs.emitBGDynamics(position=(t[0], t[1]+0.9, t[2]), velocity=self.node.velocity, count=random.randrange(20, 30), scale=1.0, spread=0.6, chunkType='spark') else: bs.playSound(self.getFactory().shieldHitSound, 0.5, position=self.node.position) # emit some cool lookin sparks on shield hit bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*1.0, m.forceDirection[1]*1.0, m.forceDirection[2]*1.0), count=min(30, 5+int(damage*0.005)), scale=0.5, spread=0.3, chunkType='spark') # if they passed our spillover threshold, pass damage along to spaz if self.shieldHitPoints <= -maxSpillover: leftoverDamage = -maxSpillover - self.shieldHitPoints shieldLeftoverRatio = leftoverDamage / damage # scale down the magnitudes applied to spaz accordingly.. mag *= shieldLeftoverRatio velocityMag *= shieldLeftoverRatio else: return True # good job shield! else: shieldLeftoverRatio = 1.0 if m.flatDamage: damage = m.flatDamage * self._impactScale * shieldLeftoverRatio else: # hit it with an impulse and get the resulting damage #bs.screenMessage(str(velocityMag)) if self.multiplyer > 3.0: # at about 8.0 the physics glitch out velocityMag *= min((3.0 + (self.multiplyer-3.0)/4), 7.5) ** 1.9 else: velocityMag *= self.multiplyer ** 1.9 self.node.handleMessage("impulse", m.pos[0], m.pos[1], m.pos[2], m.velocity[0], m.velocity[1], m.velocity[2], mag, velocityMag, m.radius, 0, m.forceDirection[0], m.forceDirection[1], m.forceDirection[2]) damage = damageScale * self.node.damage self.node.handleMessage("hurtSound") # play punch impact sound based on damage if it was a punch if m.hitType == 'punch': self.onPunched(damage) # if damage was significant, lets show it #if damage > 350: bsUtils.showDamageCount('-'+str(int(damage/10))+"%",m.pos,m.forceDirection) # lets always add in a super-punch sound with boxing gloves just to differentiate them if m.hitSubType == 'superPunch': bs.playSound(self.getFactory().punchSoundStronger, 1.0, position=self.node.position) if damage > 500: sounds = self.getFactory().punchSoundsStrong sound = sounds[random.randrange(len(sounds))] else: sound = self.getFactory().punchSound bs.playSound(sound, 1.0, position=self.node.position) # throw up some chunks bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*0.5, m.forceDirection[1]*0.5, m.forceDirection[2]*0.5), count=min(10, 1+int(damage*0.0025)), scale=0.3, spread=0.03) bs.emitBGDynamics(position=m.pos, chunkType='sweat', velocity=(m.forceDirection[0]*1.3, m.forceDirection[1]*1.3+5.0, m.forceDirection[2]*1.3), count=min(30, 1 + int(damage * 0.04)), scale=0.9, spread=0.28) # momentary flash hurtiness = damage*0.003 hurtiness = min(hurtiness, 750 * 0.003) punchPos = (m.pos[0]+m.forceDirection[0]*0.02, m.pos[1]+m.forceDirection[1]*0.02, m.pos[2]+m.forceDirection[2]*0.02) flashColor = (1.0, 0.8, 0.4) light = bs.newNode("light", attrs={'position':punchPos, 'radius':0.12+hurtiness*0.12, 'intensity':0.3*(1.0+1.0*hurtiness), 'heightAttenuated':False, 'color':flashColor}) bs.gameTimer(60, light.delete) flash = bs.newNode("flash", attrs={'position':punchPos, 'size':0.17+0.17*hurtiness, 'color':flashColor}) bs.gameTimer(60, flash.delete) if m.hitType == 'impact': bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*2.0, m.forceDirection[1]*2.0, m.forceDirection[2]*2.0), count=min(10, 1 + int(damage * 0.01)), scale=0.4, spread=0.1) if self.hitPoints > 0: # its kinda crappy to die from impacts, so lets reduce impact damage # by a reasonable amount if it'll keep us alive if m.hitType == 'impact' and damage > self.hitPoints: # drop damage to whatever puts us at 10 hit points, or 200 less than it used to be # whichever is greater (so it *can* still kill us if its high enough) newDamage = max(damage-200, self.hitPoints-10) damage = newDamage self.node.handleMessage("flash") # if we're holding something, drop it if damage > 0.0 and self.node.holdNode.exists(): self.node.holdNode = bs.Node(None) #self.hitPoints -= damage self.multiplyer += min(damage / 2000, 0.15) if damage/2000 > 0.05: self.setScoreText(str(int((self.multiplyer-1)*100))+"%") #self.node.hurt = 1.0 - self.hitPoints/self.hitPointsMax self.node.hurt = 0.0 # if we're cursed, *any* damage blows us up if self._cursed and damage > 0: bs.gameTimer(50, bs.WeakCall(self.curseExplode, m.sourcePlayer)) # if we're frozen, shatter.. otherwise die if we hit zero #if self.frozen and (damage > 200 or self.hitPoints <= 0): # self.shatter() #elif self.hitPoints <= 0: # self.node.handleMessage(bs.DieMessage(how='impact')) # if we're dead, take a look at the smoothed damage val # (which gives us a smoothed average of recent damage) and shatter # us if its grown high enough #if self.hitPoints <= 0: # damageAvg = self.node.damageSmoothed * damageScale # if damageAvg > 1000: # self.shatter() elif isinstance(m, bs.DieMessage): self.oob_effect() super(self.__class__, self).handleMessage(m) elif isinstance(m, bs.PowerupMessage): if m.powerupType == 'health': if self.multiplyer > 2: self.multiplyer *= 0.5 else: self.multiplyer *= 0.75 self.multiplyer = max(1, self.multiplyer) self.setScoreText(str(int((self.multiplyer-1)*100))+"%") super(self.__class__, self).handleMessage(m) else: super(self.__class__, self).handleMessage(m) def oob_effect(self): if self.isDead: return self.isDead = True if self.multiplyer > 1.25: blastType = 'tnt' radius = min(self.multiplyer * 5, 20) else: # penalty for killing people with low multiplyer blastType = 'ice' radius = 7.5 bs.Blast(position=self.node.position, blastRadius=radius, blastType=blastType).autoRetain() def bsGetAPIVersion(): return 4 def bsGetGames(): return [SuperSmash] class SuperSmash(bs.TeamGameActivity): @classmethod def getName(cls): return 'Super Smash' @classmethod def getScoreInfo(cls): if cls == SuperSmash: if bs.getActivity().__class__ == SuperSmash and hasattr(bs.getActivity(), 'timeLimitOnly'): # some sanity checks that probably arent needed if bs.getActivity().timeLimitOnly: # if its timeonlymode return different scoreinfo return {'scoreName':'Deaths', 'scoreType':'points', 'scoreVersion':'B', 'noneIsWinner':True, 'lowerIsBetter': True} return {'scoreName':'Survived', 'scoreType':'seconds', 'scoreVersion':'B', 'noneIsWinner':True} @classmethod def getDescription(cls, sessionType): return "Kill everyone with your knockback." def getInstanceDescription(self): return 'Knock everyone off the map.' def getInstanceScoreBoardDescription(self): if self.timeLimitOnly: return 'Knock everyone off the map.' else: if self.settings['Lives'] > 1: return ('Knock the others off ${ARG1} times.', self.settings['Lives']) else: return 'Knock everyone off once.' @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.TeamsSession) or issubclass(sessionType, bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls, sessionType): maps = bs.getMapsSupportingPlayType("melee") for m in ['Lake Frigid', 'Hockey Stadium', 'Football Stadium']: # remove maps without bounds maps.remove(m) return maps @classmethod def getSettings(cls, sessionType): return [("Time Limit", {'choices':[('None', 0), ('1 Minute', 60), ('2 Minutes', 120), ('5 Minutes', 300)], 'default': 0}), ("Lives (0 = Unlimited)", {'minValue': 0, 'default': 3, 'increment': 1}), ("Epic Mode", {'default': False})] def __init__(self, settings): bs.TeamGameActivity.__init__(self, settings) self.settings['Lives'] = self.settings["Lives (0 = Unlimited)"] self.timeLimitOnly = (self.settings['Lives'] == 0) if self.timeLimitOnly: self.settings['Time Limit'] = max(60, self.settings['Time Limit']) if self.settings['Epic Mode']: self._isSlowMotion = True # print messages when players die (since its meaningful in this game) self.announcePlayerDeaths = True self._lastPlayerDeathTime = None self._startGameTime = 1000 def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) self.setupStandardPowerupDrops(enableTNT=False) self._pow = None self._tntDropTimer = bs.Timer(1000 * 30, bs.WeakCall(self._dropPowBox), repeat=True) self._updateIcons() def _dropPowBox(self): if self._pow is not None and self._pow.exists(): return if len(self.getMap().tntPoints) == 0: return pos = random.choice(self.getMap().tntPoints) pos = (pos[0], pos[1] + 1, pos[2]) self._pow = PowBox(position=pos, velocity=(0, 1, 0)) def onPlayerJoin(self, player): if 'lives' not in player.gameData: player.gameData['lives'] = self.settings['Lives'] # create our icon and spawn player.gameData['icons'] = [Icon(player, position=(0, 50), scale=0.8)] if player.gameData['lives'] > 0 or self.timeLimitOnly: self.spawnPlayer(player) # dont waste time doing this until begin if self.hasBegun(): self._updateIcons() def onPlayerLeave(self, player): bs.TeamGameActivity.onPlayerLeave(self, player) player.gameData['icons'] = None # update icons in a moment since our team will be gone from the list then bs.gameTimer(0, self._updateIcons) if sum([len(team.players) >= 1 for team in self.teams]) < 2: self.endGame() def _updateIcons(self): # in free-for-all mode, everyone is just lined up along the bottom if isinstance(self.getSession(), bs.FreeForAllSession): count = len(self.teams) xOffs = 85 x = xOffs*(count-1) * -0.5 for team in self.teams: if len(team.players) > 1: print('WTF have', len(team.players), 'players in ffa team') elif len(team.players) == 1: player = team.players[0] if len(player.gameData['icons']) != 1: print('WTF have', len(player.gameData['icons']), 'icons in non-solo elim') for icon in player.gameData['icons']: icon.setPositionAndScale((x, 30), 0.7) icon.updateForLives() x += xOffs # in teams mode we split up teams else: for team in self.teams: if team.getID() == 0: x = -50 xOffs = -85 else: x = 50 xOffs = 85 for player in team.players: if len(player.gameData['icons']) != 1: print('WTF have', len(player.gameData['icons']), 'icons in non-solo elim') for icon in player.gameData['icons']: icon.setPositionAndScale((x, 30), 0.7) icon.updateForLives() x += xOffs # overriding the default character spawning.. def spawnPlayer(self, player): if isinstance(self.getSession(), bs.TeamsSession): position = self.getMap().getStartPosition(player.getTeam().getID()) else: # otherwise do free-for-all spawn locations position = self.getMap().getFFAStartPosition(self.players) angle = None #spaz = self.spawnPlayerSpaz(player) # lets reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up #spaz.connectControlsToPlayer(enablePunch=False, # enableBomb=False, # enablePickUp=False) # also lets have them make some noise when they die.. #spaz.playBigDeathSound = True name = player.getName() lightColor = bsUtils.getNormalizedColor(player.color) displayColor = bs.getSafeColor(player.color, targetIntensity=0.75) spaz = PlayerSpaz_Smash(color=player.color, highlight=player.highlight, character=player.character, player=player) player.setActor(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(), bs.CoopSession) and self.getMap().getName() in ['Courtyard', 'Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() self.scoreSet.playerGotNewSpaz(player, spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position, angle if angle is not None else random.uniform(0, 360))) t = bs.getGameTime() bs.playSound(self._spawnSound, 1, position=spaz.node.position) light = bs.newNode('light', attrs={'color': lightColor}) spaz.node.connectAttr('position', light, 'position') bsUtils.animate(light, 'intensity', {0: 0, 250: 1, 500: 0}) bs.gameTimer(500, light.delete) # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerSpawned() # various high-level game events come through this method def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() player.gameData['lives'] -= 1 # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerDied() # play big death sound on our last death or for every one in solo mode if player.gameData['lives'] == 0: bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound) # if we hit zero lives we're dead and the game might be over if player.gameData['lives'] == 0 and not self.timeLimitOnly: # if the whole team is dead, make note of how long they lasted if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players): # log the team survival if we're the last player on the team player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000 # if someone has won, set a timer to end shortly # (allows the dust to settle and draws to occur if deaths are close enough) if len(self._getLivingTeams()) < 2: self._roundEndTimer = bs.Timer(1000, self.endGame) # we still have lives; yay! else: self.respawnPlayer(player) else: # default handler: super(self.__class__, self).handleMessage(m)#bs.TeamGameActivity.handleMessage(self,m) def endGame(self): curTime = bs.getGameTime() if not self.timeLimitOnly: # mark 'death-time' as now for any still-living players # and award players points for how long they lasted. # (these per-player scores are only meaningful in team-games) for team in self.teams: for player in team.players: # throw an extra fudge factor +1 in so teams that # didn't die come out ahead of teams that did if 'survivalSeconds' in player.gameData: score = player.gameData['survivalSeconds'] elif 'survivalSeconds' in team.gameData: score = team.gameData['survivalSeconds'] else: score = (curTime - self._startGameTime)/1000 + 1 #if 'survivalSeconds' not in player.gameData: # player.gameData['survivalSeconds'] = (curTime - self._startGameTime)/1000 + 1 # print('extraBonusSwag for player') # award a per-player score depending on how many seconds they lasted # (per-player scores only affect teams mode; everywhere else just looks at the per-team score) #score = (player.gameData['survivalSeconds']) self.scoreSet.playerScored(player, score, screenMessage=False) # ok now calc game results: set a score for each team and then tell the game to end results = bs.TeamGameResults() # remember that 'free-for-all' mode is simply a special form of 'teams' mode # where each player gets their own team, so we can just always deal in teams # and have all cases covered for team in self.teams: # set the team score to the max time survived by any player on that team longestLife = 0 for player in team.players: if 'survivalSeconds' in player.gameData: time = player.gameData['survivalSeconds'] elif 'survivalSeconds' in team.gameData: time = team.gameData['survivalSeconds'] else: time = (curTime - self._startGameTime)/1000 + 1 longestLife = max(longestLife, time) results.setTeamScore(team, longestLife) self.end(results=results) else: results = bs.TeamGameResults() for team in self.teams: deaths = sum([0 - player.gameData['lives'] for player in team.players]) results.setTeamScore(team, deaths) self.end(results=results) def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)] ================================================ FILE: mods/snake.json ================================================ { "name": "Snake", "author": "Mrmaxmeier", "category": "minigames" } ================================================ FILE: mods/snake.py ================================================ import bs def bsGetAPIVersion(): return 4 def bsGetGames(): return [SnakeGame] class RaceTimer: """Basicly the onscreen timer from bsRace...""" def __init__(self, incTime=1000): lightY = 150 self.pos = 0 self._beep1Sound = bs.getSound('raceBeep1') self._beep2Sound = bs.getSound('raceBeep2') self.lights = [] for i in range(4): l = bs.newNode('image', attrs={'texture':bs.getTexture('nub'), 'opacity':1.0, 'absoluteScale':True, 'position':(-75+i*50, lightY), 'scale':(50, 50), 'attach':'center'}) bs.animate(l, 'opacity', {10:0, 1000:1.0}) self.lights.append(l) self.lights[0].color = (0.2, 0, 0) self.lights[1].color = (0.2, 0, 0) self.lights[2].color = (0.2, 0.05, 0) self.lights[3].color = (0.0, 0.3, 0) self.cases = {1: self._doLight1, 2: self._doLight2, 3: self._doLight3, 4: self._doLight4} self.incTimer = None self.incTime = incTime def start(self): self.incTimer = bs.Timer(self.incTime, bs.WeakCall(self.increment), timeType="game", repeat=True) def _doLight1(self): self.lights[0].color = (1.0, 0, 0) bs.playSound(self._beep1Sound) def _doLight2(self): self.lights[1].color = (1.0, 0, 0) bs.playSound(self._beep1Sound) def _doLight3(self): self.lights[2].color = (1.0, 0.3, 0) bs.playSound(self._beep1Sound) def _doLight4(self): self.lights[3].color = (0.0, 1.0, 0) bs.playSound(self._beep2Sound) for l in self.lights: bs.animate(l, 'opacity', {0: 1.0, 1000: 0.0}) bs.gameTimer(1000, l.delete) self.incTimer = None self.onFinish() del self def onFinish(self): pass def onIncrement(self): pass def increment(self): self.pos += 1 if self.pos in self.cases: self.cases[self.pos]() self.onIncrement() class SnakeGame(bs.TeamGameActivity): tailIncrease = 0.2 maxTailLength = 30 mineDelay = 0.5 @classmethod def getName(cls): return 'Snake' @classmethod def getScoreInfo(cls): return {'scoreName':'Mines Planted'} @classmethod def getDescription(cls, sessionType): return 'Plant as many Mines as you can' @classmethod def supportsSessionType(cls, sessionType): return True if (issubclass(sessionType, bs.TeamsSession) or issubclass(sessionType, bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls, sessionType): return bs.getMapsSupportingPlayType("keepAway") @classmethod def getSettings(cls, sessionType): return [("Mines to win", {'choices':[('Few', 60), ('Some', 80), ('Some more', 120), ('Many much', 140), ('wow', 200)], 'default': 80}), ("Epic Mode", {'default':False})] def __init__(self, settings): bs.TeamGameActivity.__init__(self, settings) if self.settings['Epic Mode']: self._isSlowMotion = True self._scoreBoard = bs.ScoreBoard() self._swipSound = bs.getSound("swip") self._countDownSounds = {10:bs.getSound('announceTen'), 9:bs.getSound('announceNine'), 8:bs.getSound('announceEight'), 7:bs.getSound('announceSeven'), 6:bs.getSound('announceSix'), 5:bs.getSound('announceFive'), 4:bs.getSound('announceFour'), 3:bs.getSound('announceThree'), 2:bs.getSound('announceTwo'), 1:bs.getSound('announceOne')} self.maxTailLength = self.settings['Mines to win'] * self.tailIncrease self.isFinished = False self.hasStarted = False def getInstanceDescription(self): return 'Run around and don\'t get killed.' def getInstanceScoreBoardDescription(self): return ('Survive ${ARG1} mines', self.settings['Mines to win']) def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Chosen One') def onTeamJoin(self, team): team.gameData['tailLength'] = 0 team.gameData['minesPlanted'] = 0 self._updateScoreBoard() def onPlayerJoin(self, player): bs.TeamGameActivity.onPlayerJoin(self, player) player.gameData['mines'] = [] if self.hasStarted: call = bs.WeakCall(self._spawnMine, player) self.mineTimers.append(bs.Timer(int(self.mineDelay * 1000), call, repeat=True)) def onPlayerLeave(self, player): bs.TeamGameActivity.onPlayerLeave(self, player) def onBegin(self): self.mineTimers = [] bs.gameTimer(3500, bs.Call(self.doRaceTimer)) # test... if not all(player.exists() for player in self.players): bs.printError("Nonexistant player in onBegin: "+str([str(p) for p in self.players])) bs.TeamGameActivity.onBegin(self) def doRaceTimer(self): self.raceTimer = RaceTimer() self.raceTimer.onFinish = bs.WeakCall(self.timerCallback) bs.gameTimer(1000, bs.Call(self.raceTimer.start)) def timerCallback(self): for player in self.players: call = bs.WeakCall(self._spawnMine, player) self.mineTimers.append(bs.Timer(int(self.mineDelay * 1000), call, repeat=True)) self.hasStarted = True def _spawnMine(self, player): #Don't spawn mines if player is dead if not player.exists() or not player.isAlive(): return gameData = player.getTeam().gameData # no more mines for players who've already won # to get a working draw if gameData['minesPlanted'] >= self.settings['Mines to win']: return gameData['minesPlanted'] += 1 gameData['tailLength'] = gameData['minesPlanted'] * self.tailIncrease + 2 if gameData['minesPlanted'] >= self.settings['Mines to win'] - 10: num2win = self.settings['Mines to win'] - gameData['minesPlanted'] + 1 if num2win in self._countDownSounds: bs.playSound(self._countDownSounds[num2win]) self._updateScoreBoard() if player.getTeam().gameData['tailLength'] < 2: return pos = player.actor.node.position pos = (pos[0], pos[1] + 2, pos[2]) mine = bs.Bomb(position=pos, velocity=(0, 0, 0), bombType='landMine', blastRadius=2.0, sourcePlayer=player, owner=player).autoRetain() player.gameData['mines'].append(mine) bs.gameTimer(int(self.mineDelay * 1000), bs.WeakCall(mine.arm)) bs.gameTimer(int(int(player.getTeam().gameData['tailLength'] + 1) * self.mineDelay * 1000), bs.WeakCall(self._removeMine, player, mine)) def _removeMine(self, player, mine): #kill it with(out) fire if mine in player.gameData: player.gameData['mines'].remove(mine) mine.handleMessage(bs.DieMessage()) mine = None def endGame(self): results = bs.TeamGameResults() for team in self.teams: results.setTeamScore(team, min(int(team.gameData['minesPlanted']), self.settings['Mines to win'])) self.end(results=results, announceDelay=0) def handleMessage(self, m): if isinstance(m, bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() for mine in player.gameData['mines']: self._removeMine(player, mine) self.respawnPlayer(player) else: bs.TeamGameActivity.handleMessage(self, m) def _updateScoreBoard(self): for team in self.teams: self._scoreBoard.setTeamValue(team, min(int(team.gameData['minesPlanted']), self.settings['Mines to win']), self.settings['Mines to win'], countdown=False) if int(team.gameData['minesPlanted']) >= self.settings['Mines to win']: bs.gameTimer(500, bs.WeakCall(self.endGame)) self.isFinished = True ================================================ FILE: mods/snowyPowerup.json ================================================ { "name": "Modified bsPowerup.py", "author": "joshville79", "category": "libraries", "requires": ["BuddyBunny", "SnoBallz", "Portal"] } ================================================ FILE: mods/snowyPowerup.py ================================================ import bs import random #add for bunny buddy: import BuddyBunny import SnoBallz import bsPowerup import bsSpaz import Portal from bsPowerup import PowerupMessage, PowerupAcceptMessage, _TouchedMessage, PowerupFactory, Powerup defaultPowerupInterval = 8000 class NewPowerupFactory(PowerupFactory): def __init__(self): self._lastPowerupType = None self.model = bs.getModel("powerup") self.modelSimple = bs.getModel("powerupSimple") self.texBomb = bs.getTexture("powerupBomb") self.texPunch = bs.getTexture("powerupPunch") self.texIceBombs = bs.getTexture("powerupIceBombs") self.texStickyBombs = bs.getTexture("powerupStickyBombs") self.texShield = bs.getTexture("powerupShield") self.texImpactBombs = bs.getTexture("powerupImpactBombs") self.texHealth = bs.getTexture("powerupHealth") self.texLandMines = bs.getTexture("powerupLandMines") self.texCurse = bs.getTexture("powerupCurse") #Add for Bunnybot: self.eggModel = bs.getModel('egg') self.texEgg = bs.getTexture('eggTex1') #Add for snoBalls: self.texSno = bs.getTexture("bunnyColor") #Bunny is most uniform plain white color. self.snoModel = bs.getModel("frostyPelvis") #Frosty pelvis is very nice and round... self.healthPowerupSound = bs.getSound("healthPowerup") self.powerupSound = bs.getSound("powerup01") self.powerdownSound = bs.getSound("powerdown01") self.dropSound = bs.getSound("boxDrop") self.texPort = bs.getTexture("ouyaOButton") # material for powerups self.powerupMaterial = bs.Material() # material for anyone wanting to accept powerups self.powerupAcceptMaterial = bs.Material() # pass a powerup-touched message to applicable stuff self.powerupMaterial.addActions( conditions=(("theyHaveMaterial",self.powerupAcceptMaterial)), actions=(("modifyPartCollision","collide",True), ("modifyPartCollision","physical",False), ("message","ourNode","atConnect",_TouchedMessage()))) # we dont wanna be picked up self.powerupMaterial.addActions( conditions=("theyHaveMaterial",bs.getSharedObject('pickupMaterial')), actions=( ("modifyPartCollision","collide",False))) self.powerupMaterial.addActions( conditions=("theyHaveMaterial",bs.getSharedObject('footingMaterial')), actions=(("impactSound",self.dropSound,0.5,0.1))) self._powerupDist = [] for p,freq in getDefaultPowerupDistribution(): for i in range(int(freq)): self._powerupDist.append(p) def getRandomPowerupType(self, forceType=None, excludeTypes=None): if excludeTypes: # exclude custom powerups if there is some custom powerup logic # example: bsFootball.py:456 excludeTypes.append('snoball') excludeTypes.append('bunny') else: excludeTypes = [] return PowerupFactory.getRandomPowerupType(self, forceType, excludeTypes) def getDefaultPowerupDistribution(): return (('tripleBombs',3), ('iceBombs',3), ('punch',3), ('impactBombs',3), ('landMines',2), ('stickyBombs',3), ('shield',2), ('health',1), ('bunny',2), ('portal',2), ('curse',1), ('snoball',3)) class NewPowerup(Powerup): def __init__(self,position=(0,1,0),powerupType='tripleBombs',expire=True): """ Create a powerup-box of the requested type at the requested position. see bs.Powerup.powerupType for valid type strings. """ bs.Actor.__init__(self) factory = self.getFactory() self.powerupType = powerupType; self._powersGiven = False mod = factory.model mScl = 1 if powerupType == 'tripleBombs': tex = factory.texBomb elif powerupType == 'punch': tex = factory.texPunch elif powerupType == 'iceBombs': tex = factory.texIceBombs elif powerupType == 'impactBombs': tex = factory.texImpactBombs elif powerupType == 'landMines': tex = factory.texLandMines elif powerupType == 'stickyBombs': tex = factory.texStickyBombs elif powerupType == 'shield': tex = factory.texShield elif powerupType == 'health': tex = factory.texHealth elif powerupType == 'curse': tex = factory.texCurse elif powerupType == 'portal': tex = factory.texPort elif powerupType == 'bunny': tex = factory.texEgg mod = factory.eggModel mScl = 0.7 elif powerupType == 'snoball': tex = factory.texSno mod = factory.snoModel else: raise Exception("invalid powerupType: "+str(powerupType)) if len(position) != 3: raise Exception("expected 3 floats for position") self.node = bs.newNode('prop', delegate=self, attrs={'body':'box', 'position':position, 'model':mod, 'lightModel':factory.modelSimple, 'shadowSize':0.5, 'colorTexture':tex, 'reflection':'powerup', 'reflectionScale':[1.0], 'materials':(factory.powerupMaterial,bs.getSharedObject('objectMaterial'))}) # animate in.. curve = bs.animate(self.node,"modelScale",{0:0,140:1.6,200:mScl}) bs.gameTimer(200,curve.delete) if expire: bs.gameTimer(defaultPowerupInterval-2500,bs.WeakCall(self._startFlashing)) bs.gameTimer(defaultPowerupInterval-1000,bs.WeakCall(self.handleMessage,bs.DieMessage())) def delpor(self): Portal.currentnum -= 1 self.port.delete() def handleMessage(self,m): self._handleMessageSanityCheck() if isinstance(m,PowerupAcceptMessage): factory = self.getFactory() if self.powerupType == 'health': bs.playSound(factory.healthPowerupSound,3,position=self.node.position) bs.playSound(factory.powerupSound,3,position=self.node.position) self._powersGiven = True self.handleMessage(bs.DieMessage()) elif isinstance(m,_TouchedMessage): if not self._powersGiven: node = bs.getCollisionInfo("opposingNode") if node is not None and node.exists(): #We won't tell the spaz about the bunny. It'll just happen. if self.powerupType == 'bunny': p=node.getDelegate().getPlayer() if 'bunnies' not in p.gameData: p.gameData['bunnies'] = BuddyBunny.BunnyBotSet(p) p.gameData['bunnies'].doBunny() self._powersGiven = True self.handleMessage(bs.DieMessage()) #a Spaz doesn't know what to do with a snoball powerup. All the snowball functionality #is handled through SnoBallz.py to minimize modifications to the original game files elif self.powerupType == 'snoball': spaz=node.getDelegate() SnoBallz.snoBall().getFactory().giveBallz(spaz) self._powersGiven = True self.handleMessage(bs.DieMessage()) elif self.powerupType == 'portal': t = bsSpaz.gPowerupWearOffTime if Portal.currentnum < Portal.maxportals : Portal.currentnum += 1 if self.node.position in Portal.lastpos : self.port = Portal.Portal(position1 = None,r = 0.9,color = (random.random(),random.random(),random.random()),activity = bs.getActivity()) bs.gameTimer(t,bs.Call(self.delpor)) else : m = self.node.position Portal.lastpos.append(m) self.port = Portal.Portal(position1 = self.node.position,r = 0.9,color = (random.random(),random.random(),random.random()),activity = bs.getActivity()) bs.gameTimer(t,bs.Call(self.delpor)) self._powersGiven = True self.handleMessage(bs.DieMessage()) else: node.handleMessage(PowerupMessage(self.powerupType,sourceNode=self.node)) elif isinstance(m,bs.DieMessage): if self.node.exists(): if (m.immediate): self.node.delete() else: curve = bs.animate(self.node,"modelScale",{0:1,100:0}) bs.gameTimer(100,self.node.delete) elif isinstance(m,bs.OutOfBoundsMessage): self.handleMessage(bs.DieMessage()) elif isinstance(m,bs.HitMessage): # dont die on punches (thats annoying) if m.hitType != 'punch': self.handleMessage(bs.DieMessage()) else: bs.Actor.handleMessage(self,m) bsPowerup.PowerupFactory = NewPowerupFactory bsPowerup.Powerup = NewPowerup ================================================ FILE: mods/surviveCurse.json ================================================ { "name": "Survive the Curse!", "author": "joshville79", "category": "minigames" } ================================================ FILE: mods/surviveCurse.py ================================================ import bs import random import bsUtils import bsPowerup def bsGetAPIVersion(): # see bombsquadgame.com/apichanges return 4 def bsGetGames(): return [SurviveCurseGame] class Icon(bs.Actor): def __init__(self,player,position,scale,showLives=True,showDeath=True, nameScale=1.0,nameMaxWidth=115.0,flatness=1.0,shadow=1.0): bs.Actor.__init__(self) self._player = player self._showLives = showLives self._showDeath = showDeath self._nameScale = nameScale self._outlineTex = bs.getTexture('characterIconMask') icon = player.getIcon() self.node = bs.newNode('image', owner=self, attrs={'texture':icon['texture'], 'tintTexture':icon['tintTexture'], 'tintColor':icon['tintColor'], 'vrDepth':400, 'tint2Color':icon['tint2Color'], 'maskTexture':self._outlineTex, 'opacity':1.0, 'absoluteScale':True, 'attach':'bottomCenter'}) self._nameText = bs.newNode('text', owner=self.node, attrs={'text':player.getName(), 'color':bs.getSafeColor(player.getTeam().color), 'hAlign':'center', 'vAlign':'center', 'vrDepth':410, 'maxWidth':nameMaxWidth, 'shadow':shadow, 'flatness':flatness, 'hAttach':'center', 'vAttach':'bottom'}) if self._showLives: self._livesText = bs.newNode('text', owner=self.node, attrs={'text':'x0', 'color':(1,1,0.5), 'hAlign':'left', 'vrDepth':430, 'shadow':1.0, 'flatness':1.0, 'hAttach':'center', 'vAttach':'bottom'}) self.setPositionAndScale(position,scale) def setPositionAndScale(self,position,scale): self.node.position = position self.node.scale = [70.0*scale] self._nameText.position = (position[0],position[1]+scale*52.0) self._nameText.scale = 1.0*scale*self._nameScale if self._showLives: self._livesText.position = (position[0]+scale*10.0,position[1]-scale*43.0) self._livesText.scale = 1.0*scale def updateForLives(self): if self._player.exists(): lives = self._player.gameData['lives'] else: lives = 0 if self._showLives: if lives > 0: self._livesText.text = 'x'+str(lives-1) else: self._livesText.text = '' if lives == 0: self._nameText.opacity = 0.2 self.node.color = (0.7,0.3,0.3) self.node.opacity = 0.2 def handlePlayerSpawned(self): if not self.node.exists(): return self.node.opacity = 1.0 self.updateForLives() def handlePlayerDied(self): if not self.node.exists(): return if self._showDeath: bs.animate(self.node,'opacity',{0:1.0,50:0.0,100:1.0,150:0.0,200:1.0,250:0.0, 300:1.0,350:0.0,400:1.0,450:0.0,500:1.0,550:0.2}) lives = self._player.gameData['lives'] if lives == 0: bs.gameTimer(600,self.updateForLives) class PlayerSpaz_Curse(bs.PlayerSpaz): minExplodeTime = 0 curseDamageExplode = 350 #This is done to reduce likelihood of dying by damage. Normal curse is any damage at all. def onJumpPress(self): """ Called to 'press jump' on this spaz; used by player or AI connections. This was just overridden to provide an easy way to get map extents. """ if not self.node.exists(): return self.node.jumpPressed = True #print(self.node.position) def handleMessage(self,m): if isinstance(m,bs.PowerupMessage): #Have to handle powerups ourselves if self._dead: return True if self.pickUpPowerupCallback is not None: self.pickUpPowerupCallback(self) if (m.powerupType == 'health'): self.reCurse() #Just reset the curse timer elif (m.powerupType == 'curse'): self.curseExplodeNoShrapnel() self.node.handleMessage("flash") if m.sourceNode.exists(): m.sourceNode.handleMessage(bs.PowerupAcceptMessage()) return True elif isinstance(m,bs.HitMessage): #Have to override this whole message handling just to reduce chance of dying by damage while cursed if not self.node.exists(): return if self.node.invincible == True: bs.playSound(self.getFactory().blockSound,1.0,position=self.node.position) return True # if we were recently hit, don't count this as another # (so punch flurries and bomb pileups essentially count as 1 hit) gameTime = bs.getGameTime() if self._lastHitTime is None or gameTime-self._lastHitTime > 1000: self._numTimesHit += 1 self._lastHitTime = gameTime mag = m.magnitude * self._impactScale velocityMag = m.velocityMagnitude * self._impactScale damageScale = 0.22 # if they've got a shield, deliver it to that instead.. if self.shield is not None: if m.flatDamage: damage = m.flatDamage * self._impactScale else: # hit our spaz with an impulse but tell it to only return theoretical damage; not apply the impulse.. self.node.handleMessage("impulse",m.pos[0],m.pos[1],m.pos[2], m.velocity[0],m.velocity[1],m.velocity[2], mag,velocityMag,m.radius,1,m.forceDirection[0],m.forceDirection[1],m.forceDirection[2]) damage = damageScale * self.node.damage self.shieldHitPoints -= damage self.shield.hurt = 1.0 - float(self.shieldHitPoints)/self.shieldHitPointsMax # its a cleaner event if a hit just kills the shield without damaging the player.. # however, massive damage events should still be able to damage the player.. # this hopefully gives us a happy medium. # maxSpillover = 500 maxSpillover = self.getFactory().maxShieldSpilloverDamage if self.shieldHitPoints <= 0: # fixme - transition out perhaps?.. self.shield.delete() self.shield = None bs.playSound(self.getFactory().shieldDownSound,1.0,position=self.node.position) # emit some cool lookin sparks when the shield dies t = self.node.position bs.emitBGDynamics(position=(t[0],t[1]+0.9,t[2]), velocity=self.node.velocity, count=random.randrange(20,30),scale=1.0,spread=0.6,chunkType='spark') else: bs.playSound(self.getFactory().shieldHitSound,0.5,position=self.node.position) # emit some cool lookin sparks on shield hit bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*1.0, m.forceDirection[1]*1.0, m.forceDirection[2]*1.0), count=min(30,5+int(damage*0.005)),scale=0.5,spread=0.3,chunkType='spark') # if they passed our spillover threshold, pass damage along to spaz if self.shieldHitPoints <= -maxSpillover: leftoverDamage = -maxSpillover-self.shieldHitPoints shieldLeftoverRatio = leftoverDamage/damage # scale down the magnitudes applied to spaz accordingly.. mag *= shieldLeftoverRatio velocityMag *= shieldLeftoverRatio else: return True # good job shield! else: shieldLeftoverRatio = 1.0 if m.flatDamage: damage = m.flatDamage * self._impactScale * shieldLeftoverRatio else: # hit it with an impulse and get the resulting damage self.node.handleMessage("impulse",m.pos[0],m.pos[1],m.pos[2], m.velocity[0],m.velocity[1],m.velocity[2], mag,velocityMag,m.radius,0,m.forceDirection[0],m.forceDirection[1],m.forceDirection[2]) damage = damageScale * self.node.damage self.node.handleMessage("hurtSound") # play punch impact sound based on damage if it was a punch if m.hitType == 'punch': self.onPunched(damage) # if damage was significant, lets show it if damage > 350: bsUtils.showDamageCount('-'+str(int(damage/10))+"%",m.pos,m.forceDirection) # lets always add in a super-punch sound with boxing gloves just to differentiate them if m.hitSubType == 'superPunch': bs.playSound(self.getFactory().punchSoundStronger,1.0, position=self.node.position) if damage > 500: sounds = self.getFactory().punchSoundsStrong sound = sounds[random.randrange(len(sounds))] else: sound = self.getFactory().punchSound bs.playSound(sound,1.0,position=self.node.position) # throw up some chunks bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*0.5, m.forceDirection[1]*0.5, m.forceDirection[2]*0.5), count=min(10,1+int(damage*0.0025)),scale=0.3,spread=0.03); bs.emitBGDynamics(position=m.pos, chunkType='sweat', velocity=(m.forceDirection[0]*1.3, m.forceDirection[1]*1.3+5.0, m.forceDirection[2]*1.3), count=min(30,1+int(damage*0.04)), scale=0.9, spread=0.28); # momentary flash hurtiness = damage*0.003 punchPos = (m.pos[0]+m.forceDirection[0]*0.02, m.pos[1]+m.forceDirection[1]*0.02, m.pos[2]+m.forceDirection[2]*0.02) flashColor = (1.0,0.8,0.4) light = bs.newNode("light", attrs={'position':punchPos, 'radius':0.12+hurtiness*0.12, 'intensity':0.3*(1.0+1.0*hurtiness), 'heightAttenuated':False, 'color':flashColor}) bs.gameTimer(60,light.delete) flash = bs.newNode("flash", attrs={'position':punchPos, 'size':0.17+0.17*hurtiness, 'color':flashColor}) bs.gameTimer(60,flash.delete) if m.hitType == 'impact': bs.emitBGDynamics(position=m.pos, velocity=(m.forceDirection[0]*2.0, m.forceDirection[1]*2.0, m.forceDirection[2]*2.0), count=min(10,1+int(damage*0.01)),scale=0.4,spread=0.1); if self.hitPoints > 0: # its kinda crappy to die from impacts, so lets reduce impact damage # by a reasonable amount if it'll keep us alive if m.hitType == 'impact' and damage > self.hitPoints: # drop damage to whatever puts us at 10 hit points, or 200 less than it used to be # whichever is greater (so it *can* still kill us if its high enough) newDamage = max(damage-200,self.hitPoints-10) damage = newDamage self.node.handleMessage("flash") # if we're holding something, drop it if damage > 0.0 and self.node.holdNode.exists(): self.node.holdNode = bs.Node(None) self.hitPoints -= damage self.node.hurt = 1.0 - float(self.hitPoints)/self.hitPointsMax # if we're cursed, *any* damage blows us up if self._cursed and damage > self.curseDamageExplode: bs.gameTimer(50,bs.WeakCall(self.curseExplode,m.sourcePlayer)) # if we're frozen, shatter.. otherwise die if we hit zero if self.frozen and (damage > 200 or self.hitPoints <= 0): self.shatter() elif self.hitPoints <= 0: self.node.handleMessage(bs.DieMessage(how='impact')) # if we're dead, take a look at the smoothed damage val # (which gives us a smoothed average of recent damage) and shatter # us if its grown high enough if self.hitPoints <= 0: damageAvg = self.node.damageSmoothed * damageScale if damageAvg > 1000: self.shatter() else: super(self.__class__, self).handleMessage(m) def reCurse(self): self.node.curseDeathTime = bs.getGameTime()+5000 bs.gameTimer(5000,bs.WeakCall(self.curseExplodeIfNotReset)) def curse(self): """ Give this poor spaz a curse; he will explode in 5 seconds. We have to override this from the parent class to allow for resetting the curse timing. Changed the WeakCall at the end to curseExplodeIfNotReset instead of straight curseExplode. """ if not self._cursed: factory = self.getFactory() self._cursed = True # add the curse material.. for attr in ['materials','rollerMaterials']: materials = getattr(self.node,attr) if not factory.curseMaterial in materials: setattr(self.node,attr,materials + (factory.curseMaterial,)) # -1 specifies no time limit if self.curseTime == -1: self.node.curseDeathTime = -1 else: self.node.curseDeathTime = bs.getGameTime()+5000 bs.gameTimer(5000,bs.WeakCall(self.curseExplodeIfNotReset)) def curseExplodeIfNotReset(self): if self.node.exists(): if self.node.curseDeathTime <= bs.getGameTime(): self.curseExplodeNoShrapnel() def curseExplodeNoShrapnel(self,sourcePlayer=None): """ Explode the poor spaz as happens when a curse timer runs out. Less shrapnel for surviveCurse, just explode. Otherwise, shrapnel hits other players too much. Player shrapnel causes instant curse explosion of other players. Over too quickly. I could probably figure out how to prevent. However, too lazy. Making immediate=True in the DieMessage prevents shrapnel. However, spaz node disappears instantly and no cleanup happens. """ # convert None to an empty player-ref if sourcePlayer is None: sourcePlayer = bs.Player(None) if self._cursed and self.node.exists(): #self.shatter(extreme=True) self.handleMessage(bs.DieMessage(immediate=False)) activity = self._activity() if activity: bs.Blast(position=self.node.position, velocity=self.node.velocity, blastRadius=3.0,blastType='normal', sourcePlayer=sourcePlayer if sourcePlayer.exists() else self.sourcePlayer).autoRetain() self._cursed = False class SurviveCurseGame(bs.TeamGameActivity): @classmethod def getName(cls): return 'Survive the Curse!' @classmethod def getScoreInfo(cls): return {'scoreName':'Survived', 'scoreType':'seconds', 'noneIsWinner':True} @classmethod def getDescription(cls,sessionType): return 'Last remaining alive wins.' @classmethod def supportsSessionType(cls,sessionType): return True if (issubclass(sessionType,bs.TeamsSession) or issubclass(sessionType,bs.FreeForAllSession)) else False @classmethod def getSupportedMaps(cls,sessionType): return ['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium'] @classmethod def getSettings(cls,sessionType): settings = [("Lives Per Player",{'default':1,'minValue':1,'maxValue':1,'increment':1}), ("Time Limit",{'choices':[('None',0),('1 Minute',60), ('2 Minutes',120),('5 Minutes',300), ('10 Minutes',600),('20 Minutes',1200)],'default':0}), ("Respawn Times",{'choices':[('Shorter',0.25),('Short',0.5),('Normal',1.0),('Long',2.0),('Longer',4.0)],'default':1.0}), ("Box Reduction Rate",{'choices':[('Faster',0.1),('Fast',0.07),('Normal',0.05),('Slow',0.03),('Slower',0.01)],'default':0.05}), ("Curse Box Chance (lower = more chance)",{'default':10,'minValue':5,'maxValue':15,'increment':1}), ("Epic Mode",{'default':False})] if issubclass(sessionType,bs.TeamsSession): settings.append(("Solo Mode",{'default':False})) settings.append(("Balance Total Lives",{'default':False})) return settings def __init__(self,settings): bs.TeamGameActivity.__init__(self,settings) if self.settings['Epic Mode']: self._isSlowMotion = True # show messages when players die since it's meaningful here self.announcePlayerDeaths = True try: self._soloMode = settings['Solo Mode'] except Exception: self._soloMode = False self._scoreBoard = bs.ScoreBoard() def getInstanceDescription(self): return 'Last team standing wins.' if isinstance(self.getSession(),bs.TeamsSession) else 'Last one standing wins.' def getInstanceScoreBoardDescription(self): return 'last team standing wins' if isinstance(self.getSession(),bs.TeamsSession) else 'last one standing wins' def onTransitionIn(self): bs.TeamGameActivity.onTransitionIn(self, music='Epic' if self.settings['Epic Mode'] else 'Survival') self._startGameTime = bs.getGameTime() def onTeamJoin(self,team): team.gameData['survivalSeconds'] = None team.gameData['spawnOrder'] = [] def onPlayerJoin(self, player): # no longer allowing mid-game joiners here... too easy to exploit if self.hasBegun(): player.gameData['lives'] = 0 player.gameData['icons'] = [] # make sure our team has survival seconds set if they're all dead # (otherwise blocked new ffa players would be considered 'still alive' in score tallying) if self._getTotalTeamLives(player.getTeam()) == 0 and player.getTeam().gameData['survivalSeconds'] is None: player.getTeam().gameData['survivalSeconds'] = 0 bs.screenMessage(bs.Lstr(resource='playerDelayedJoinText',subs=[('${PLAYER}',player.getName(full=True))]),color=(0,1,0)) return player.gameData['lives'] = self.settings['Lives Per Player'] if self._soloMode: player.gameData['icons'] = [] player.getTeam().gameData['spawnOrder'].append(player) self._updateSoloMode() else: # create our icon and spawn player.gameData['icons'] = [Icon(player,position=(0,50),scale=0.8)] if player.gameData['lives'] > 0: self.spawnPlayer(player) # dont waste time doing this until begin if self.hasBegun(): self._updateIcons() def _updateSoloMode(self): # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) break def _updateIcons(self): # in free-for-all mode, everyone is just lined up along the bottom if isinstance(self.getSession(),bs.FreeForAllSession): count = len(self.teams) xOffs = 85 x = xOffs*(count-1) * -0.5 for i,team in enumerate(self.teams): if len(team.players) == 1: player = team.players[0] for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs # in teams mode we split up teams else: if self._soloMode: # first off, clear out all icons for player in self.players: player.gameData['icons'] = [] # now for each team, cycle through our available players adding icons for team in self.teams: if team.getID() == 0: x = -60 xOffs = -78 else: x = 60 xOffs = 78 isFirst = True testLives = 1 while True: playersWithLives = [p for p in team.gameData['spawnOrder'] if p.exists() and p.gameData['lives'] >= testLives] if len(playersWithLives) == 0: break for player in playersWithLives: player.gameData['icons'].append(Icon(player, position=(x,(40 if isFirst else 25)), scale=1.0 if isFirst else 0.5, nameMaxWidth=130 if isFirst else 75, nameScale=0.8 if isFirst else 1.0, flatness=0.0 if isFirst else 1.0, shadow=0.5 if isFirst else 1.0, showDeath=True if isFirst else False, showLives=False)) x += xOffs * (0.8 if isFirst else 0.56) isFirst = False testLives += 1 # non-solo mode else: for team in self.teams: if team.getID() == 0: x = -50 xOffs = -85 else: x = 50 xOffs = 85 for player in team.players: for icon in player.gameData['icons']: icon.setPositionAndScale((x,30),0.7) icon.updateForLives() x += xOffs def _getSpawnPoint(self,player): # in solo-mode, if there's an existing live player on the map, spawn at whichever # spot is farthest from them (keeps the action spread out) if self._soloMode: livingPlayer = None for team in self.teams: for player in team.players: if player.isAlive(): p = player.actor.node.position livingPlayer = player livingPlayerPos = p break if livingPlayer: playerPos = bs.Vector(*livingPlayerPos) points = [] for team in self.teams: startPos = bs.Vector(*self.getMap().getStartPosition(team.getID())) points.append([(startPos-playerPos).length(),startPos]) points.sort() return points[-1][1] else: return None else: return None def spawnPlayer(self,player): self.spawnPlayerSpaz(player,self._getSpawnPoint(player)) if not self._soloMode: bs.gameTimer(300,bs.Call(self._printLives,player)) # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerSpawned() def spawnPlayerSpaz(self,player,position=(0,0,0),angle=None): """ Create and wire up a bs.PlayerSpaz for the provide bs.Player. """ position = self.getMap().getFFAStartPosition(self.players) name = player.getName() color = player.color highlight = player.highlight lightColor = bsUtils.getNormalizedColor(color) displayColor = bs.getSafeColor(color,targetIntensity=0.75) spaz = PlayerSpaz_Curse(color=color, highlight=highlight, character=player.character, player=player) player.setActor(spaz) # we want a bigger area-of-interest in co-op mode # if isinstance(self.getSession(),bs.CoopSession): spaz.node.areaOfInterestRadius = 5.0 # else: spaz.node.areaOfInterestRadius = 5.0 # if this is co-op and we're on Courtyard or Runaround, add the material that allows us to # collide with the player-walls # FIXME; need to generalize this if isinstance(self.getSession(),bs.CoopSession) and self.getMap().getName() in ['Courtyard','Tower D']: mat = self.getMap().preloadData['collideWithWallMaterial'] spaz.node.materials += (mat,) spaz.node.rollerMaterials += (mat,) spaz.node.name = name spaz.node.nameColor = displayColor spaz.connectControlsToPlayer() self.scoreSet.playerGotNewSpaz(player,spaz) # move to the stand position and add a flash of light spaz.handleMessage(bs.StandMessage(position,angle if angle is not None else random.uniform(0,360))) t = bs.getGameTime() bs.playSound(self._spawnSound,1,position=spaz.node.position) light = bs.newNode('light',attrs={'color':lightColor}) spaz.node.connectAttr('position',light,'position') bsUtils.animate(light,'intensity',{0:0,250:1,500:0}) bs.gameTimer(500,light.delete) return spaz def _printLives(self,player): if not player.exists() or not player.isAlive(): return try: pos = player.actor.node.position except Exception,e: print 'EXC getting player pos in bsElim',e return bs.PopupText('x'+str(player.gameData['lives']-1),color=(1,1,0,1), offset=(0,-0.8,0),randomOffset=0.0,scale=1.8,position=pos).autoRetain() def onPlayerLeave(self,player): bs.TeamGameActivity.onPlayerLeave(self,player) player.gameData['icons'] = None # remove us from spawn-order if self._soloMode: if player in player.getTeam().gameData['spawnOrder']: player.getTeam().gameData['spawnOrder'].remove(player) # update icons in a moment since our team will be gone from the list then bs.gameTimer(0, self._updateIcons) def onBegin(self): bs.TeamGameActivity.onBegin(self) self.setupStandardTimeLimit(self.settings['Time Limit']) #self.setupStandardPowerupDrops() #No standard powerups. We'll drop 'em from the sky. if self._soloMode: self._vsText = bs.NodeActor(bs.newNode("text", attrs={'position':(0,105), 'hAttach':"center", 'hAlign':'center', 'maxWidth':200, 'shadow':0.5, 'vrDepth':390, 'scale':0.6, 'vAttach':"bottom", 'color':(0.8,0.8,0.3,1.0), 'text':bs.Lstr(resource='vsText')})) # if balance-team-lives is on, add lives to the smaller team until total lives match if (isinstance(self.getSession(),bs.TeamsSession) and self.settings['Balance Total Lives'] and len(self.teams[0].players) > 0 and len(self.teams[1].players) > 0): if self._getTotalTeamLives(self.teams[0]) < self._getTotalTeamLives(self.teams[1]): lesserTeam = self.teams[0] greaterTeam = self.teams[1] else: lesserTeam = self.teams[1] greaterTeam = self.teams[0] addIndex = 0 while self._getTotalTeamLives(lesserTeam) < self._getTotalTeamLives(greaterTeam): lesserTeam.players[addIndex].gameData['lives'] += 1 addIndex = (addIndex + 1) % len(lesserTeam.players) self._updateIcons() for pName in self.scoreSet._players: spz = self.scoreSet._players[pName].getSpaz() if not spz is None: bs.gameTimer(1500,bs.WeakCall(spz.curse)) #Curse you all! #bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[0], powerupType='health',expire=False).autoRetain() #bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[1], powerupType='curse',expire=False).autoRetain() #bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[3], powerupType='health',expire=False).autoRetain() #bsPowerup.Powerup(position=self.getMap().powerupSpawnPoints[2], powerupType='curse',expire=False).autoRetain() self.boxMult = 4.0 self.totBoxes = [] self.boxSpawn() # we could check game-over conditions at explicit trigger points, # but lets just do the simple thing and poll it... bs.gameTimer(1000, self._update, repeat=True) def _getTotalTeamLives(self,team): return sum(player.gameData['lives'] for player in team.players) def handleMessage(self,m): if isinstance(m,bs.PlayerSpazDeathMessage): bs.TeamGameActivity.handleMessage(self, m) # augment standard behavior player = m.spaz.getPlayer() player.gameData['lives'] -= 1 if player.gameData['lives'] < 0: bs.printError('Got lives < 0 in Elim; this shouldnt happen. solo:'+str(self._soloMode)) player.gameData['lives'] = 0 # if we have any icons, update their state for icon in player.gameData['icons']: icon.handlePlayerDied() # play big death sound on our last death or for every one in solo mode if self._soloMode or player.gameData['lives'] == 0: bs.playSound(bs.Spaz.getFactory().singlePlayerDeathSound) # if we hit zero lives, we're dead (and our team might be too) if player.gameData['lives'] == 0: # if the whole team is now dead, mark their survival time.. #if all(teammate.gameData['lives'] == 0 for teammate in player.getTeam().players): if self._getTotalTeamLives(player.getTeam()) == 0: player.getTeam().gameData['survivalSeconds'] = (bs.getGameTime()-self._startGameTime)/1000 else: # otherwise, in regular mode, respawn.. if not self._soloMode: self.respawnPlayer(player) # in solo, put ourself at the back of the spawn order if self._soloMode: player.getTeam().gameData['spawnOrder'].remove(player) player.getTeam().gameData['spawnOrder'].append(player) def boxSpawn(self): Plyrs = 0 for team in self.teams: for player in team.players: if player.gameData['lives'] > 0: Plyrs += 1 maxBoxes = Plyrs * self.boxMult if maxBoxes > 16: maxBoxes = 16 for box in self.totBoxes: if not box.exists(): self.totBoxes.remove(box) while len(self.totBoxes) < maxBoxes: #print([Plyrs, self.boxMult,len(self.totBoxes), maxBoxes]) if random.randint(1,self.settings["Curse Box Chance (lower = more chance)"]) == 1: self.totBoxes.append(bsPowerup.Powerup(position=self.getRandomPowerupPoint(), powerupType='curse',expire=False).autoRetain()) else: self.totBoxes.append(bsPowerup.Powerup(position=self.getRandomPowerupPoint(), powerupType='health',expire=False).autoRetain()) self.boxMult -= self.settings["Box Reduction Rate"] def getRandomPowerupPoint(self): #So far, randomized points only figured out for mostly rectangular maps. #Boxes will still fall through holes, but shouldn't be terrible problem (hopefully) #If you add stuff here, need to add to "supported maps" above. #['Doom Shroom', 'Rampage', 'Hockey Stadium', 'Courtyard', 'Crag Castle', 'Big G', 'Football Stadium'] myMap = self.getMap().getName() #print(myMap) if myMap == 'Doom Shroom': while True: x = random.uniform(-1.0,1.0) y = random.uniform(-1.0,1.0) if x*x+y*y < 1.0: break return ((8.0*x,8.0,-3.5+5.0*y)) elif myMap == 'Rampage': x = random.uniform(-6.0,7.0) y = random.uniform(-6.0,-2.5) return ((x, 8.0, y)) elif myMap == 'Hockey Stadium': x = random.uniform(-11.5,11.5) y = random.uniform(-4.5,4.5) return ((x, 5.0, y)) elif myMap == 'Courtyard': x = random.uniform(-4.3,4.3) y = random.uniform(-4.4,0.3) return ((x, 8.0, y)) elif myMap == 'Crag Castle': x = random.uniform(-6.7,8.0) y = random.uniform(-6.0,0.0) return ((x, 12.0, y)) elif myMap == 'Big G': x = random.uniform(-8.7,8.0) y = random.uniform(-7.5,6.5) return ((x, 8.0, y)) elif myMap == 'Football Stadium': x = random.uniform(-12.5,12.5) y = random.uniform(-5.0,5.5) return ((x, 8.0, y)) else: x = random.uniform(-5.0,5.0) y = random.uniform(-6.0,0.0) return ((x, 8.0, y)) def _update(self): if self._soloMode: # for both teams, find the first player on the spawn order list with lives remaining # and spawn them if they're not alive for team in self.teams: # prune dead players from the spawn order team.gameData['spawnOrder'] = [p for p in team.gameData['spawnOrder'] if p.exists()] for player in team.gameData['spawnOrder']: if player.gameData['lives'] > 0: if not player.isAlive(): self.spawnPlayer(player) self._updateIcons() break # if we're down to 1 or fewer living teams, start a timer to end the game # (allows the dust to settle and draws to occur if deaths are close enough) self.boxSpawn() if len(self._getLivingTeams()) < 2: self._roundEndTimer = bs.Timer(500,self.endGame) def _getLivingTeams(self): return [team for team in self.teams if len(team.players) > 0 and any(player.gameData['lives'] > 0 for player in team.players)] def endGame(self): if self.hasEnded(): return results = bs.TeamGameResults() self._vsText = None # kill our 'vs' if its there for team in self.teams: results.setTeamScore(team, team.gameData['survivalSeconds']) self.end(results=results) ================================================ FILE: mods/ui_wrappers.json ================================================ { "name": "ui_wrappers", "author": "Mrmaxmeier", "category": "libraries" } ================================================ FILE: mods/ui_wrappers.py ================================================ import bs DEBUG = False class Widget(bs.Widget): _instance = None _values = dict(upWidget=None, downWidget=None, leftWidget=None, rightWidget=None, showBufferTop=None, showBufferBottom=None, showBufferLeft=None, showBufferRight=None, autoSelect=None) _required = [] _func = bs.widget _can_create = False _values_funcs = {} def __init__(self, **kwargs): if not self._can_create: raise Exception("cant create widget of type " + str(self.__class__)) for key in self._required: if key not in kwargs: raise ValueError("expected " + key) self._instance = self._call_func(self._func, kwargs) self._values_funcs = {} self._values = {} for cls in [self.__class__] + list(self.__class__.__bases__): self._values_funcs[cls._func] = cls._values self._values.update(cls._values) self._values.update(kwargs) def _call_func(self, func, kwargs): d = {} for key, value in kwargs.items(): d[key] = value if isinstance(value, Widget): d[key] = value._instance if DEBUG: print("bs.{}(**{})".format(func.__name__, d)) return func(**d) def set(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) def reset_value(self, key): setattr(self, key, self.__class__._values[key]) def activate(self, *args, **kwargs): return self._instance.activate(*args, **kwargs) def delete(self, *args, **kwargs): return self._instance.delete(*args, **kwargs) def exists(self, *args, **kwargs): return self._instance.exists(*args, **kwargs) def getChildren(self, *args, **kwargs): return self._instance.getChildren(*args, **kwargs) def getScreenSpaceCenter(self, *args, **kwargs): return self._instance.getScreenSpaceCenter(*args, **kwargs) def getSelectedChild(self, *args, **kwargs): return self._instance.getSelectedChild(*args, **kwargs) def getWidgetType(self, *args, **kwargs): return self._instance.getWidgetType(*args, **kwargs) def __getattr__(self, key): if hasattr(self._instance, key): return getattr(self._instance, key) if key in self._values: return self._values[key] raise AttributeError("type object '{}' has no attribute '{}'".format(type(self), key)) def __setattr__(self, key, value): if DEBUG: print("__setattr__({}, {})".format(repr(key), value)) for func, values in self._values_funcs.items(): if key in values: self._call_func(func, {"edit": self._instance, key: value}) self._values[key] = value return self.__dict__[key] = value def __repr__(self): return object.__repr__(self) def __str__(self): return object.__str__(self) class TextWidget(Widget): _values = dict(parent=None, size=None, position=None, vAlign=None, hAlign=None, editable=False, padding=None, onReturnPressCall=None, selectable=None, onActivateCall=None, query=None, maxChars=None, color=None, clickActivate=None, scale=None, alwaysHighlight=None, drawController=None, description=None, transitionDelay=None, flatness=None, enabled=None, forceInternalEditing=False, alwaysShowCarat=None, maxWidth=None, maxHeight=None, big=False) # FIXME: check default values _required = ["parent"] _func = bs.textWidget _can_create = True # FIXME: textWidget.set(text=...) shadows instance method def text(self): return self._func(query=self._instance) class ButtonWidget(Widget): _values = dict(parent=None, size=None, position=None, onActivateCall=None, label=None, color=None, texture=None, textScale=None, enableSound=True, modelTransparent=None, modelOpaque=None, transitionDelay=None, onSelectCall=None, extraTouchBorderScale=None, buttonType=None, touchOnly=None, showBufferTop=None, icon=None, iconScale=None, iconTint=None, iconColor=None, autoSelect=None, repeat=None, maskTexture=None, tintTexture=None, tintColor=None) # FIXME: check default values _required = ["parent", "position", "size"] _func = bs.buttonWidget _can_create = True COLOR_GREY = (0.52, 0.48, 0.63) TEXTCOLOR_GREY = (0.65, 0.6, 0.7) class CheckBoxWidget(Widget): _values = dict(parent=None, size=None, position=None, value=None, clickSelect=None, onActivateCall=None, onValueChangeCall=None, onSelectCall=None, isRadioButton=False, scale=None, maxWidth=None, autoSelect=None, color=None) # FIXME: check default values _required = ["parent", "position"] _func = bs.checkBoxWidget _can_create = True def __init__(self, **kwargs): super(self.__class__, self).__init__(**kwargs) if not self.onValueChangeCall: def f(val): print(val) self._values["value"] = val self._func(edit=self._instance, onValueChangeCall=f) def _call_func(self, func, kwargs): d = {} for key, value in kwargs.items(): d[key] = value if isinstance(value, Widget): d[key] = value._instance if key == "onValueChangeCall": def w(value): def f(val): self._values["value"] = val value(val) return f d[key] = w(value) return func(**d) class ContainerWidget(Widget): _values = dict(parent=None, size=None, position=None, selectedChild=None, transition=None, cancelButton=None, startButton=None, rootSelectable=None, onActivateCall=None, claimsLeftRight=None, claimsTab=None, selectionLoops=None, selectionLoopToParent=None, scale=None, type=None, onOutsideClickCall=None, singleDepth=None, visibleChild=None, stackOffset=None, color=None, onCancelCall=None, printListExitInstructions=None, clickActivate=None, alwaysHighlight=None, selectable=None, scaleOriginStackOffset=None) # FIXME: check default values _required = ["size"] _func = bs.containerWidget _can_create = True def doTransition(self, transition): self.set(transition=transition) class ScrollWidget(Widget): _values = dict(parent=None, size=None, position=None, captureArrows=False, onSelectCall=None, centerSmallContent=None, color=None, highlight=None, borderOpacity=None) # FIXME: check default values _required = ["parent", "position", "size"] _func = bs.scrollWidget _can_create = True class ColumnWidget(Widget): _values = dict(parent=None, size=None, position=None, singleDepth=None, printListExitInstructions=None, leftBorder=None, selectedChild=None, visibleChild=None) # FIXME: check default values _required = ["parent"] _func = bs.columnWidget _can_create = True class HScrollWidget(Widget): _values = dict(parent=None, size=None, position=None, captureArrows=False, onSelectCall=None, centerSmallContent=None, color=None, highlight=None, borderOpacity=None) # FIXME: check default values _required = ["parent", "position", "size"] _func = bs.hScrollWidget _can_create = True class ImageWidget(Widget): _values = dict(parent=None, size=None, position=None, color=None, texture=None, model=None, modelTransparent=None, modelOpaque=None, hasAlphaChannel=True, tintTexture=None, tintColor=None, transitionDelay=None, drawController=None, tiltScale=None, maskTexture=None) # FIXME: check default values _required = ["parent", "size", "position"] _func = bs.imageWidget _can_create = True class RowWidget(Widget): _values = dict(parent=None, size=None, position=None, selectable=False) _required = ["parent", "size", "position"] _func = bs.rowWidget _can_create = True ================================================ FILE: requirements.txt ================================================ gitpython>=0.3.5 ================================================ FILE: server/.gitignore ================================================ target ================================================ FILE: server/Cargo.toml ================================================ [package] authors = ["Mrmaxmeier "] name = "server" version = "0.1.0" [dependencies] nickel = "^0.8.0" plugin = "^0.2.6" r2d2 = "^0.7.0" r2d2_redis = "0.4.0" redis = "^0.6.0" rustc-serialize = "^0.3.19" typemap = "^0.3.3" ================================================ FILE: server/src/main.rs ================================================ extern crate rustc_serialize; #[macro_use] extern crate nickel; extern crate redis; extern crate r2d2; extern crate r2d2_redis; extern crate plugin; extern crate typemap; extern crate core; use std::env; use std::collections::HashMap; use r2d2::NopErrorHandler; use nickel::{Nickel, HttpRouter, JsonBody, MediaType, QueryString}; use nickel::status::StatusCode; use core::ops::Deref; use redis::{Commands, Connection, RedisError}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use rustc_serialize::json; use redis_middleware::{RedisMiddleware, RedisRequestExtensions}; mod redis_middleware; #[derive(RustcDecodable, RustcEncodable)] struct RatingSubmission { uuid: String, mod_str: String, rating: Rating, } #[derive(RustcDecodable, RustcEncodable)] struct DownloadSubmission { uuid: String, mod_str: String, } #[derive(Clone, Copy, Debug)] enum Rating { Poor, BelowAverage, Average, AboveAverage, Excellent, } impl From for Rating { fn from(rating: usize) -> Self { match rating { 0 => Rating::Poor, 1 => Rating::BelowAverage, 2 => Rating::Average, 3 => Rating::AboveAverage, _ => Rating::Excellent, } } } impl Decodable for Rating { fn decode(d: &mut D) -> Result { let r = try!(d.read_usize()); Ok(Rating::from(r)) } } impl Encodable for Rating { fn encode(&self, s: &mut S) -> Result<(), S::Error> { let as_usize = *self as usize; s.emit_usize(as_usize) } } #[derive(RustcEncodable)] struct StatsResults { average_ratings: HashMap, amount_ratings: HashMap, own_ratings: Option>, downloads: HashMap, } fn incr_requests(conn: &Connection) { if let Err(_) = conn.incr::<_, _, usize>("requests", 1) { println!("failed to incr request counter.") } } fn get_mod_rating(conn: &Connection, mod_str: &str) -> Result<(Rating, usize), RedisError> { let ratings = try!(conn.hvals::<_, Vec>(format!("{}_ratings", mod_str))); let length = ratings.len(); let mut sum = 0; for rating in ratings { sum += rating; } match length { 0 => Ok((Rating::Poor, length)), _ => Ok((Rating::from(sum / length), length)), } } fn get_mod_downloads(conn: &Connection, mod_str: &str) -> Result { let downloads = try!(conn.hvals::<_, Vec>(format!("{}_downloads", mod_str))); Ok(downloads.iter().fold(0, |acc, &x| acc + x)) } const OK_RESP: (StatusCode, &'static str) = (StatusCode::Ok, "ok"); fn main() { let mut webserver = Nickel::new(); let redis_url = env::var("DATABASE_URL").unwrap_or("redis://localhost/3".to_owned()); println!("connecting to redis @ {}", redis_url); let redispool = RedisMiddleware::new(&*redis_url, 3, Box::new(NopErrorHandler)).unwrap(); webserver.utilize(redispool); webserver.post("/submit_rating", middleware! { |request, response| let rcn_ref = request.redis_conn(); let redis_conn = rcn_ref.deref(); incr_requests(redis_conn); let sbm = try_with!(response, { request.json_as::().map_err(|e| (StatusCode::BadRequest, e)) }); println!("{} rates {} as {:?}", sbm.uuid, sbm.mod_str, sbm.rating); let _: bool = try_with!(response, { redis_conn.hset("mods", &*sbm.mod_str, true).map_err(|e| (StatusCode::BadRequest, e)) }); let _: usize = try_with!(response, { let key = format!("{}_ratings", sbm.mod_str); redis_conn.hset(key, sbm.uuid, sbm.rating as usize).map_err(|e| (StatusCode::BadRequest, e) ) }); OK_RESP }); webserver.get("/rating/:mod", middleware! { |request, response| let rcn_ref = request.redis_conn(); let redis_conn = rcn_ref.deref(); incr_requests(redis_conn); let mod_str = request.param("mod").unwrap(); let result = try_with!(response, { get_mod_rating(redis_conn, mod_str).map_err(|e| (StatusCode::BadRequest, e)) }); match result { (_, 0) => (StatusCode::NotFound, "Not Found!".to_owned()), (rating, sbm) => (StatusCode::Ok, format!("{:?}, {} submissions", rating, sbm)), } }); webserver.get("/stats", middleware! { |request, mut response| let rcn_ref = request.redis_conn(); let redis_conn = rcn_ref.deref(); incr_requests(&redis_conn); let mods = try_with!(response, { redis_conn.hkeys::<_, Vec>("mods").map_err(|e| (StatusCode::BadRequest, e)) }); let mut own_ratings: HashMap = HashMap::new(); let mut amount_ratings: HashMap = HashMap::new(); let mut average_ratings: HashMap = HashMap::new(); let mut mod_downloads: HashMap = HashMap::new(); for mod_str in mods { let mod_str = mod_str.as_str(); let downloads = try_with!(response, { get_mod_downloads(redis_conn, mod_str).map_err(|e| (StatusCode::BadRequest, e)) }); mod_downloads.insert(mod_str.to_owned(), downloads); let (rating, sbm) = try_with!(response, { get_mod_rating(redis_conn, mod_str).map_err(|e| (StatusCode::BadRequest, e)) }); if sbm == 0 { continue; } average_ratings.insert(mod_str.to_owned(), rating); amount_ratings.insert(mod_str.to_owned(), sbm); if let Some(uuid) = request.query().get("uuid") { if let Ok(rating) = redis_conn.hget::<_, _, usize>(mod_str, uuid) { own_ratings.insert(mod_str.to_owned(), Rating::from(rating)); } } } let result = StatsResults { average_ratings: average_ratings, amount_ratings: amount_ratings, own_ratings: match request.query().get("uuid") { Some(_) => Some(own_ratings), None => None, }, downloads: mod_downloads, }; response.set(MediaType::Json); json::encode(&result).unwrap() }); webserver.post("/submit_download", middleware! { |request, response| let rcn_ref = request.redis_conn(); let redis_conn = rcn_ref.deref(); incr_requests(redis_conn); let sbm = try_with!(response, { request.json_as::().map_err(|e| (StatusCode::BadRequest, e)) }); println!("{} downloaded {}", sbm.uuid, sbm.mod_str); let _: bool = try_with!(response, { redis_conn.hset("mods", &*sbm.mod_str, true).map_err(|e| (StatusCode::BadRequest, e)) }); let _: usize = try_with!(response, { redis_conn.hincr(format!("{}_downloads", sbm.mod_str), sbm.uuid, 1).map_err(|e| (StatusCode::BadRequest, e) ) }); OK_RESP }); webserver.listen("127.0.0.1:7998") } ================================================ FILE: server/src/redis_middleware.rs ================================================ use std::sync::Arc; use std::error::Error as StdError; use nickel::{Request, Response, Middleware, Continue, MiddlewareResult}; use r2d2_redis::RedisConnectionManager; use r2d2::{Pool, HandleError, Config, PooledConnection}; use typemap::Key; use plugin::Extensible; pub struct RedisMiddleware { pub pool: Arc>, } impl RedisMiddleware { pub fn new(connect_str: &str, num_connections: u32, error_handler: Box>) -> Result> { let manager = try!(RedisConnectionManager::new(connect_str)); let config = Config::builder() .pool_size(num_connections) .error_handler(error_handler) .build(); let pool = try!(Pool::new(config, manager)); Ok(RedisMiddleware { pool: Arc::new(pool) }) } } impl Key for RedisMiddleware { type Value = Arc>; } impl Middleware for RedisMiddleware { fn invoke<'mw, 'conn>(&self, req: &mut Request<'mw, 'conn, D>, res: Response<'mw, D>) -> MiddlewareResult<'mw, D> { req.extensions_mut().insert::(self.pool.clone()); Ok(Continue(res)) } } pub trait RedisRequestExtensions { fn redis_conn(&self) -> PooledConnection; } impl<'a, 'b, D> RedisRequestExtensions for Request<'a, 'b, D> { fn redis_conn(&self) -> PooledConnection { self.extensions().get::().unwrap().get().unwrap() } } ================================================ FILE: update_index.py ================================================ import os import os.path import json import hashlib import git PROTOCOL_VERSION = 1.1 def normalize_path(p): # handles git renames: # a/{b.py => c.py} # b.py => a/c.py if "=>" not in p: return p if "/{" in p: p = p.split("/") p[-1] = p[-1].strip("{}").split(" => ")[1] return '/'.join(p) else: return p.split(" => ")[1] gitRepo = git.Repo("./") mods = {} current_commit = gitRepo.rev_parse("HEAD") url_base = "https://cdn.rawgit.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/" modurl = url_base + current_commit.hexsha + "/mods/" old_data = None for blob in gitRepo.head.object.tree.traverse(): if blob.path.startswith("mods/") and blob.path.endswith(".py"): filename = blob.path[5:] base = filename[:-3] data = blob.data_stream.read() md5 = hashlib.md5(data).hexdigest() mod = { "changelog": [], "md5": md5, "url": modurl + filename, # TODO: remove url field and bump version to 1.6 "filename": filename, "commit_sha": current_commit.hexsha, "old_md5s": [], } if os.path.isfile("mods/" + base + ".json"): with open("mods/" + base + ".json", "r") as json_file: try: data = json.load(json_file) except Exception as e: print(f"failed to read {base}.json") raise e mod.update(data) if mod.get("index", True): mods[base] = mod elif blob.path == "index.json": old_data = json.loads(blob.data_stream.read().decode("UTF-8")) specific_sha = set() for commit in gitRepo.iter_commits(max_count=1000, paths="mods/"): for filename in commit.stats.files: filename = normalize_path(filename) if not filename.startswith("mods/") or not filename.endswith(".py"): continue filename = filename[5:] if filename[:-3] not in mods: continue txt = commit.message.replace("\n", "") mod_slug = filename[:-3] mods[mod_slug]["changelog"].append(txt) if filename not in specific_sha: # TODO: remove url field and bump version to 1.6 mods[mod_slug]["url"] = url_base + commit.hexsha + "/mods/" + filename mods[mod_slug]["commit_sha"] = commit.hexsha specific_sha.add(filename) for blob in commit.tree["mods"].blobs: if not blob.path.endswith(".py"): continue name = blob.path[5:-3] if name in mods: data = blob.data_stream.read() md5 = hashlib.md5(data).hexdigest() if md5 not in mods[name]["old_md5s"] and md5 != mods[name]["md5"] and len(mods[name]['old_md5s']) < 5: mods[name]["old_md5s"].append(md5) for mod in mods: if not mod + ".py" in specific_sha: print("didnt find latest commit for", mod + ", head is used") for mod in mods.values(): mod["changelog"] = mod["changelog"][:2] # TODO: if the index.json gets too big # mod["old_md5s"] = [md5[:10] for md5 in mod["old_md5s"]] index_data = {"mods": mods, "version": PROTOCOL_VERSION} with open("index.json", "w") as f: json.dump(index_data, f, indent=4, sort_keys=True) if old_data: old_mods = old_data["mods"] text = "" def add(text, mod, spacer, *args): if spacer: text += " " * (spacer + 2) else: text += mod + ": " spacer = len(mod) text += " ".join(args) + "\n" return text, spacer spacer = None for mod in set(list(old_mods.keys()) + list(mods.keys())): if spacer: spacer = None text += "\n" if mod in mods and mod in old_mods: md, omd = mods[mod], old_mods[mod] for key in set(list(md.keys()) + list(omd.keys())): if key in md and key in omd: if md[key] != omd[key]: text, spacer = add(text, mod, spacer, 'updated', key) elif key not in md: text, spacer = add(text, mod, spacer, 'removed', key) else: text, spacer = add(text, mod, spacer, 'added', key) elif mod in mods: text, spacer = add(text, mod, spacer, 'added') else: text, spacer = add(text, mod, spacer, 'removed') if len(text) == 0: print("no changes.") else: text = "update index.json\n\n" + text print(text) if input("Do commit? [Yn]") in ["", "Y", "y"]: print("staging index.json") gitRepo.index.add(["index.json"]) print("committing") gitRepo.index.commit(text) else: print("didnt commit changes.") ================================================ FILE: utils/blender/README.md ================================================ 1. Download [this file](https://github.com/Mrmaxmeier/BombSquad-Community-Mod-Manager/blob/master/utils/blender/bob_plugin.py). 2. Open Blender. (tested using version 2.77) 3. Go to File > User Preferences... > Addons tab. 4. On the bottom, click `Install from File...` 5. Select the `bob_plugin.py` from this project. 6. Enable the plugin by checking the checkbox. 7. Now you should now have new import/export menu items for .bob files. To-Dos: - [x] Import - [x] Mesh - [x] UV-Maps - [x] fix material loading - [ ] allow specifying texture files - [ ] import normals? - [x] Export - [x] Mesh - [x] Normals - [x] UV-Maps - [x] Cob - [x] Import - [ ] import normals? - [x] Export - [x] Import Level-Defs - [x] Export Level-Defs ================================================ FILE: utils/blender/bob_plugin.py ================================================ import os import os.path import bpy import bmesh import struct from mathutils import Vector from bpy.props import StringProperty, BoolProperty from bpy_extras.io_utils import ImportHelper, ExportHelper, axis_conversion from contextlib import contextmanager from collections import defaultdict bl_info = { "name": "BOB format", "description": "Import-Export BombSquad .bob files.", "author": "Mrmaxmeier", "version": (0, 0), "blender": (2, 77, 0), "location": "File > Import-Export", "warning": "", "wiki_url": "", "category": "Import-Export" } BOB_FILE_ID = 45623 COB_FILE_ID = 13466 """ .BOB File Structure: MAGIC 45623 (I) meshFormat (I) vertexCount (I) faceCount (I) VertexObject x vertexCount (fff HH hhh xx) index x faceCount*3 (b / H) struct VertexObjectFull { float position[3]; bs_uint16 uv[2]; // normalized to 16 bit unsigned ints 0 - 65535 bs_sint16 normal[3]; // normalized to 16 bit signed ints -32768 - 32767 bs_uint8 _padding[2]; }; .COB File Structure: MAGIC 13466 (I) vertexCount (I) faceCount (I) vertexPos x vertexCount (fff) index x faceCount*3 (I) normal x faceCount (fff) """ @contextmanager def to_bmesh(mesh, save=False): try: bm = bmesh.new() bm.from_mesh(mesh) bm.faces.ensure_lookup_table() yield bm finally: if save: bm.to_mesh(mesh) bm.free() del bm def clamp(val, minimum=0, maximum=1): if max(min(val, maximum), minimum) != val: print("clamped", val, "to", max(min(val, maximum), minimum)) return max(min(val, maximum), minimum) class ImportBOB(bpy.types.Operator, ImportHelper): """Load an Bombsquad Mesh file""" bl_idname = "import_mesh.bob" bl_label = "Import Bombsquad Mesh" filename_ext = ".bob" filter_glob = StringProperty( default="*.bob", options={'HIDDEN'}, ) def execute(self, context): keywords = self.as_keywords(ignore=('filter_glob',)) mesh = load(self, context, **keywords) if not mesh: return {'CANCELLED'} scene = bpy.context.scene obj = bpy.data.objects.new(mesh.name, mesh) scene.objects.link(obj) scene.objects.active = obj obj.select = True obj.matrix_world = axis_conversion(from_forward='-Z', from_up='Y').to_4x4() scene.update() return {'FINISHED'} class ExportBOB(bpy.types.Operator, ExportHelper): """Save an Bombsquad Mesh file""" bl_idname = "export_mesh.bob" bl_label = "Export Bombsquad Mesh" filter_glob = StringProperty( default="*.bob", options={'HIDDEN'}, ) check_extension = True filename_ext = ".bob" triangulate = BoolProperty( name="Force Triangulation", description="force triangulation of .bob files", default=False, ) def execute(self, context): keywords = self.as_keywords(ignore=('filter_glob',)) return save(self, context, **keywords) def import_bob_menu(self, context): self.layout.operator(ImportBOB.bl_idname, text="Bombsquad Mesh (.bob)") def export_bob_menu(self, context): self.layout.operator(ExportBOB.bl_idname, text="Bombsquad Mesh (.bob)") class ImportCOB(bpy.types.Operator, ImportHelper): """Load an Bombsquad Collision Mesh""" bl_idname = "import_mesh.cob" bl_label = "Import Bombsquad Collision Mesh" filename_ext = ".cob" filter_glob = StringProperty( default="*.cob", options={'HIDDEN'}, ) def execute(self, context): keywords = self.as_keywords(ignore=('filter_glob',)) mesh = loadcob(self, context, **keywords) if not mesh: return {'CANCELLED'} scene = bpy.context.scene obj = bpy.data.objects.new(mesh.name, mesh) scene.objects.link(obj) scene.objects.active = obj obj.select = True obj.draw_type = "SOLID" obj.matrix_world = axis_conversion(from_forward='-Z', from_up='Y').to_4x4() scene.update() return {'FINISHED'} class ExportCOB(bpy.types.Operator, ExportHelper): """Save an Bombsquad Collision Mesh file""" bl_idname = "export_mesh.cob" bl_label = "Export Bombsquad Collision Mesh" filter_glob = StringProperty( default="*.cob", options={'HIDDEN'}, ) check_extension = True filename_ext = ".cob" triangulate = BoolProperty( name="Force Triangulation", description="force triangulation of .cob files", default=False, ) def execute(self, context): keywords = self.as_keywords(ignore=('filter_glob',)) return savecob(self, context, **keywords) def import_cob_menu(self, context): self.layout.operator(ImportCOB.bl_idname, text="Bombsquad Collision Mesh (.cob)") def export_cob_menu(self, context): self.layout.operator(ExportCOB.bl_idname, text="Bombsquad Collision Mesh (.cob)") def import_leveldefs(self, context): self.layout.operator(ImportLevelDefs.bl_idname, text="Bombsquad Level Definitions (.py)") def export_leveldefs(self, context): self.layout.operator(ExportLevelDefs.bl_idname, text="Bombsquad Level Definitions (.py)") def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_import.append(import_bob_menu) bpy.types.INFO_MT_file_export.append(export_bob_menu) bpy.types.INFO_MT_file_import.append(import_cob_menu) bpy.types.INFO_MT_file_export.append(export_cob_menu) bpy.types.INFO_MT_file_import.append(import_leveldefs) bpy.types.INFO_MT_file_export.append(export_leveldefs) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_import.remove(import_bob_menu) bpy.types.INFO_MT_file_export.remove(export_bob_menu) bpy.types.INFO_MT_file_import.remove(import_cob_menu) bpy.types.INFO_MT_file_export.remove(export_cob_menu) bpy.types.INFO_MT_file_import.remove(import_leveldefs) bpy.types.INFO_MT_file_export.remove(export_leveldefs) def load(operator, context, filepath): filepath = os.fsencode(filepath) bs_dir = os.path.dirname(os.path.dirname(filepath)) texname = os.path.basename(filepath).rstrip(b".bob") + b".dds" texpath = os.path.join(bs_dir, b"textures", texname) print(texpath) has_texture = os.path.isfile(texpath) print("texture file found:", has_texture) with open(filepath, 'rb') as file: def readstruct(s): tup = struct.unpack(s, file.read(struct.calcsize(s))) return tup[0] if len(tup) == 1 else tup assert readstruct("I") == BOB_FILE_ID meshFormat = readstruct("I") assert meshFormat in [0, 1] vertexCount = readstruct("I") faceCount = readstruct("I") verts = [] faces = [] edges = [] indices = [] uv_list = [] normal_list = [] for i in range(vertexCount): vertexObj = readstruct("fff HH hhh xx") position = (vertexObj[0], vertexObj[1], vertexObj[2]) uv = (vertexObj[3] / 65535, vertexObj[4] / 65535) normal = (vertexObj[5] / 32767, vertexObj[6] / 32767, vertexObj[7] / 32767) verts.append(position) uv_list.append(uv) normal_list.append(normal) for i in range(faceCount * 3): if meshFormat == 0: # MESH_FORMAT_UV16_N8_INDEX8 indices.append(readstruct("b")) elif meshFormat == 1: # MESH_FORMAT_UV16_N8_INDEX16 indices.append(readstruct("H")) for i in range(faceCount): faces.append((indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2])) bob_name = bpy.path.display_name_from_filepath(filepath) mesh = bpy.data.meshes.new(name=bob_name) mesh.from_pydata(verts, edges, faces) with to_bmesh(mesh, save=True) as bm: for i, face in enumerate(bm.faces): for vi, vert in enumerate(face.verts): vert.normal = normal_list[vert.index] uv_texture = mesh.uv_textures.new(texname.decode("ascii", "ignore")) texture = None if has_texture: texture = bpy.data.images.load(texpath) uv_texture.data[0].image = texture with to_bmesh(mesh, save=True) as bm: uv_layer = bm.loops.layers.uv.verify() tex_layer = bm.faces.layers.tex.verify() for i, face in enumerate(bm.faces): for vi, vert in enumerate(face.verts): uv = uv_list[vert.index] uv = (uv[0], 1 - uv[1]) face.loops[vi][uv_layer].uv = uv if texture: face[tex_layer].image = texture mesh.validate() mesh.update() return mesh class Verts: def __init__(self): self._verts = [] self._by_blender_index = defaultdict(list) def get(self, coords, normal, blender_index, uv=None): instance = Vert(coords=coords, normal=normal, uv=uv) for other in self._by_blender_index[blender_index]: if instance.similar(other): return other self._by_blender_index[blender_index].append(instance) instance.index = len(self._verts) self._verts.append(instance) return instance def __len__(self): return len(self._verts) def __iter__(self): return iter(self._verts) def vec_similar(v1, v2): return (v1 - v2).length < 0.01 class Vert: def __init__(self, coords, normal, uv): self.coords = coords self.normal = normal self.uv = uv def similar(self, other): is_similar = vec_similar(self.coords, other.coords) is_similar = is_similar and vec_similar(self.normal, other.normal) if self.uv and other.uv: is_similar = is_similar and vec_similar(self.uv, other.uv) return is_similar def save(operator, context, filepath, triangulate, check_existing): print("exporting", filepath) global_matrix = axis_conversion(to_forward='-Z', to_up='Y').to_4x4() scene = context.scene obj = scene.objects.active mesh = obj.to_mesh(scene, True, 'PREVIEW') mesh.transform(global_matrix * obj.matrix_world) # inverse transformation with to_bmesh(mesh) as bm: triangulate = triangulate or any([len(face.verts) != 3 for face in bm.faces]) if triangulate or any([len(face.vertices) != 3 for face in mesh.tessfaces]): print("triangulating...") with to_bmesh(mesh, save=True) as bm: bmesh.ops.triangulate(bm, faces=bm.faces) mesh.update(calc_edges=True, calc_tessface=True) filepath = os.fsencode(filepath) with open(filepath, 'wb') as file: def writestruct(s, *args): file.write(struct.pack(s, *args)) writestruct('I', BOB_FILE_ID) writestruct('I', 1) # MESH_FORMAT_UV16_N8_INDEX16 verts = Verts() faces = [] with to_bmesh(mesh) as bm: uv_layer = None if len(bm.loops.layers.uv) > 0: uv_layer = bm.loops.layers.uv[0] for i, face in enumerate(bm.faces): faceverts = [] for vi, vert in enumerate(face.verts): uv = face.loops[vi][uv_layer].uv if uv_layer else None v = verts.get(coords=vert.co, normal=vert.normal, uv=uv, blender_index=vert.index) faceverts.append(v) faces.append(faceverts) print("verts: {} [best: {}, worst: {}]".format(len(verts), len(mesh.vertices), len(faces) * 3)) print("faces:", len(faces)) writestruct('I', len(verts)) writestruct('I', len(faces)) for vert in verts: writestruct('fff', *vert.coords) if vert.uv: uv = vert.uv writestruct('HH', int(clamp(uv[0]) * 65535), int((1 - clamp(uv[1])) * 65535)) else: writestruct('HH', 0, 0) normal = tuple(map(lambda n: int(clamp(n, -1, 1) * 32767), vert.normal)) writestruct('hhh', *normal) writestruct('xx') for face in faces: assert len(face) == 3 for vert in face: writestruct('H', vert.index) return {'FINISHED'} def loadcob(operator, context, filepath): with open(os.fsencode(filepath), 'rb') as file: def readstruct(s): tup = struct.unpack(s, file.read(struct.calcsize(s))) return tup[0] if len(tup) == 1 else tup assert readstruct("I") == COB_FILE_ID vertexCount = readstruct("I") faceCount = readstruct("I") verts = [] faces = [] edges = [] indices = [] for i in range(vertexCount): vertexObj = readstruct("fff") position = (vertexObj[0], vertexObj[1], vertexObj[2]) verts.append(position) for i in range(faceCount * 3): indices.append(readstruct("I")) for i in range(faceCount): faces.append((indices[i * 3], indices[i * 3 + 1], indices[i * 3 + 2])) bob_name = bpy.path.display_name_from_filepath(filepath) mesh = bpy.data.meshes.new(name=bob_name) mesh.from_pydata(verts, edges, faces) mesh.validate() mesh.update() return mesh def savecob(operator, context, filepath, triangulate, check_existing): print("exporting", filepath) global_matrix = axis_conversion(to_forward='-Z', to_up='Y').to_4x4() scene = context.scene obj = scene.objects.active mesh = obj.to_mesh(scene, True, 'PREVIEW') mesh.transform(global_matrix * obj.matrix_world) # inverse transformation if triangulate or any([len(face.vertices) != 3 for face in mesh.tessfaces]): print("triangulating...") with to_bmesh(mesh, save=True) as bm: bmesh.ops.triangulate(bm, faces=bm.faces) mesh.update(calc_edges=True, calc_tessface=True) with open(os.fsencode(filepath), 'wb') as file: def writestruct(s, *args): file.write(struct.pack(s, *args)) writestruct('I', COB_FILE_ID) writestruct('I', len(mesh.vertices)) writestruct('I', len(mesh.tessfaces)) for i, vert in enumerate(mesh.vertices): writestruct('fff', *vert.co) for face in mesh.tessfaces: assert len(face.vertices) == 3 for vertid in face.vertices: writestruct('I', vertid) for face in mesh.tessfaces: writestruct('fff', *face.normal) return {'FINISHED'} def flpV(vector): vector = vector.copy() vector.y = -vector.y return vector.xzy class ImportLevelDefs(bpy.types.Operator, ImportHelper): """Load Bombsquad Level Defs""" bl_idname = "import_bombsquad.leveldefs" bl_label = "Import Bombsquad Level Definitions" filename_ext = ".py" filter_glob = StringProperty( default="*.py", options={'HIDDEN'}, ) def execute(self, context): keywords = self.as_keywords(ignore=('filter_glob',)) print("executing", keywords["filepath"]) data = {} with open(os.fsencode(keywords["filepath"]), "r") as file: exec(file.read(), data) del data["__builtins__"] if "points" not in data or "boxes" not in data: return {'CANCELLED'} scene = bpy.context.scene points_obj = bpy.data.objects.new("points", None) points_obj.matrix_world = axis_conversion(from_forward='-Z', from_up='Y').to_4x4() scene.objects.link(points_obj) scene.update() points_obj.layers = tuple([i == 1 for i in range(20)]) boxes_obj = bpy.data.objects.new("boxes", None) boxes_obj.matrix_world = axis_conversion(from_forward='-Z', from_up='Y').to_4x4() scene.objects.link(boxes_obj) scene.update() boxes_obj.layers = tuple([i == 1 for i in range(20)]) def makeBox(middle, scale): bpy.ops.mesh.primitive_cube_add(location=middle) cube = scene.objects.active cube.scale = scale cube.show_name = True cube.show_wire = True cube.draw_type = 'WIRE' return cube for key, pos in data["points"].items(): if len(pos) == 6: # spawn points with random variance middle, size = Vector(pos[:3]), Vector(pos[3:]) if "spawn" in key.lower(): size.y = 0.05 cube = makeBox(middle, size) cube.parent = points_obj cube.name = key else: empty = bpy.data.objects.new(key, None) empty.location = pos[:3] empty.empty_draw_size = 0.45 empty.parent = points_obj empty.show_name = True scene.objects.link(empty) for key, pos in data["boxes"].items(): middle, size = Vector(pos[:3]), flpV(Vector(pos[6:9])) cube = makeBox(middle, size) cube.parent = boxes_obj cube.name = key scene.update() return {'FINISHED'} class ExportLevelDefs(bpy.types.Operator, ImportHelper): """Export Bombsquad Level Defs""" bl_idname = "export_bombsquad.leveldefs" bl_label = "Export Bombsquad Level Definitions" filename_ext = ".py" filter_glob = StringProperty( default="*.py", options={'HIDDEN'}, ) def execute(self, context): keywords = self.as_keywords(ignore=('filter_glob',)) filepath = keywords["filepath"] print("writing level defs", filepath) scene = bpy.context.scene if "points" not in scene.objects or "boxes" not in scene.objects: return {'CANCELLED'} def v_to_str(v, flip=True, isScale=False): if flip: v = flpV(v) if isScale: v = Vector([abs(n) for n in v]) return repr(tuple([round(n, 5) for n in tuple(v)])) with open(os.fsencode(filepath), "w") as file: file.write("# This file generated from '{}'\n".format(os.path.basename(bpy.data.filepath))) file.write("points, boxes = {}, {}\n") for point in scene.objects["points"].children: pos = point.matrix_world.to_translation() if point.type == 'MESH': # spawn point with random variance scale = point.scale * point.rotation_euler.to_matrix() file.write("points['{}'] = {}".format(point.name, v_to_str(pos))) file.write(" + {}\n".format(v_to_str(scale, False, isScale=True))) else: file.write("points['{}'] = {}\n".format(point.name, v_to_str(pos))) for box in scene.objects["boxes"].children: pos = box.matrix_world.to_translation() scale = box.scale * box.rotation_euler.to_matrix() file.write("boxes['{}'] = {}".format(box.name, v_to_str(pos))) file.write(" + (0, 0, 0) + {}\n".format(v_to_str(scale, isScale=True))) return {'FINISHED'} if __name__ == "__main__": register() ================================================ FILE: utils/inject_mod.py ================================================ import telnetlib import click import json # TODO: support for multiple files @click.command() @click.option('--host', default="localhost", prompt="Host") @click.option('--file', type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True)) def inject(host, file): path = file with open(path, "r") as f: file = f.read() filename = path.split("/")[-1] tn = telnetlib.Telnet(host, 43250) tn.read_until(b"bombsquad>") print("host available") while 1: try: tn.write(b"42\n") d = tn.read_until(b"42", timeout=5).decode("utf-8") print(d) if "42" in d: break except EOFError: print("telnet not allowed") print("telnet allowed") def send_line(line): print(line) tn.write(bytes(line, encoding="ascii")) d = tn.read_until(b"bombsquad>", timeout=1).decode("utf-8") if len(d.replace("bombsquad>", "")) > 1: print(">", d) setup = """ import bs, json, os from md5 import md5 mods_folder = bs.getEnvironment()['userScriptsDirectory'] + "/" bs.playSound(bs.getSound("gunCocking")) bs.screenMessage("sending data") d = "" """[1:] for line in setup.split("\n"): send_line(line) data = json.dumps(file)[1:-1] chunksize = 250 pos = 0 buf = "" while pos < len(data): buf += data[pos] pos += 1 if len(buf) > chunksize or pos >= len(data): send_line('d += {}'.format(repr(buf))) buf = "" send_line("bs.screenMessage('data sent')") send_line("md5(d).hexdigest()") # FIXME: actually check md5 if click.confirm('Save to mods folder?', default=True): send_line("path = mods_folder + '{}'".format(filename)) #send_line("os.rename(path, path + '.bak')") send_line("f = open(path, 'w')") send_line("f.write(d)") send_line("f.close()") if click.confirm('Exec?'): send_line("exec(d)") if click.confirm("Quit?", default=True): send_line("bs.quit()") #tn.interact() tn.close() if __name__ == '__main__': inject() ================================================ FILE: utils/installer.py ================================================ import bs import bsInternal import threading import json import urllib2 import weakref import os import os.path import httplib SUPPORTS_HTTPS = hasattr(httplib, 'HTTPS') modPath = bs.getEnvironment()['userScriptsDirectory'] + "/" BRANCH = "master" USER_REPO = "Mrmaxmeier/BombSquad-Community-Mod-Manager" ENTRY_MOD = "modManager" def index_url(): if SUPPORTS_HTTPS: yield "https://raw.githubusercontent.com/{}/{}/index.json".format(USER_REPO, BRANCH) yield "https://rawgit.com/{}/{}/index.json".format(USER_REPO, BRANCH) yield "http://raw.githack.com/{}/{}/index.json".format(USER_REPO, BRANCH) yield "http://rawgit.com/{}/{}/index.json".format(USER_REPO, BRANCH) def mod_url(data): if "commit_sha" in data and "filename" in data: commit_hexsha = data["commit_sha"] filename = data["filename"] if SUPPORTS_HTTPS: yield "https://cdn.rawgit.com/{}/{}/mods/{}".format(USER_REPO, commit_hexsha, filename) yield "http://rawcdn.githack.com/{}/{}/mods/{}".format(USER_REPO, commit_hexsha, filename) if "url" in data: if SUPPORTS_HTTPS: yield data["url"] yield data["url"].replace("https", "http") def try_fetch_cb(generator, callback): def f(data): if data: callback(data) else: try: SimpleGetThread(next(generator), f).start() except StopIteration: callback(None) SimpleGetThread(next(generator), f).start() class SimpleGetThread(threading.Thread): def __init__(self, url, callback=None): threading.Thread.__init__(self) self._url = url.encode("ascii") # embedded python2.7 has weird encoding issues self._callback = callback or (lambda d: None) self._context = bs.Context('current') # save and restore the context we were created from activity = bs.getActivity(exceptionOnNone=False) self._activity = weakref.ref(activity) if activity is not None else None def _runCallback(self, arg): # if we were created in an activity context and that activity has since died, do nothing # (hmm should we be using a context-call instead of doing this manually?) if self._activity is not None and (self._activity() is None or self._activity().isFinalized()): return # (technically we could do the same check for session contexts, but not gonna worry about it for now) with self._context: self._callback(arg) def run(self): try: bsInternal._setThreadName("SimpleGetThread") response = urllib2.urlopen(self._url) bs.callInGameThread(bs.Call(self._runCallback, response.read())) except: bs.printException() bs.callInGameThread(bs.Call(self._runCallback, None)) installed = [] installing = [] def check_finished(): if any([m not in installed for m in installing]): return bs.screenMessage("installed everything.") if os.path.isfile(modPath + __name__ + ".pyc"): os.remove(modPath + __name__ + ".pyc") if os.path.isfile(modPath + __name__ + ".py"): os.remove(modPath + __name__ + ".py") bs.screenMessage("deleted self") bs.screenMessage("activating modManager") __import__(ENTRY_MOD) def install(data, mod): installing.append(mod) bs.screenMessage("installing " + str(mod)) print("installing", mod) for dep in data[mod].get("requires", []): install(data, dep) filename = data[mod]["filename"] def f(data): if not data: bs.screenMessage("failed to download mod '{}'".format(filename)) print("writing", filename) with open(modPath + filename, "w") as f: f.write(data) installed.append(mod) check_finished() try_fetch_cb(mod_url(data[mod]), f) def onIndex(data): if not data: bs.screenMessage("network error :(") return data = json.loads(data) install(data["mods"], ENTRY_MOD) try_fetch_cb(index_url(), onIndex)