Compare commits

...

104 commits
v1.1.5 ... main

Author SHA1 Message Date
Loup
d442c44ea8
Merge pull request #469 from bombsquad-community/fix_119
Some checks failed
CI / build (push) Has been cancelled
Create Release / Create Release (push) Has been cancelled
fix for 1.1.9 breaking on older versions
2026-06-12 11:12:04 +05:30
Loup-Garou911XD
7acd7a87ab [ci] apply-version-metadata 2026-06-12 05:41:53 +00:00
Loup-Garou911XD
a1baa5fca2
fix for 1.1.9 breaking on older versions 2026-06-12 11:10:44 +05:30
Loup
c2c2a04891
Merge pull request #466 from GeoHnlol/patch-1
Updated export shortcuts
2026-06-12 01:49:59 +05:30
Loup
b6ccdb12ef
Merge pull request #468 from MrParadox1691/main
Minor bug fix for Vanishing tiles
2026-06-12 01:49:06 +05:30
MrParadox1691
f1c02ae85a [ci] apply-version-metadata 2026-06-11 15:16:45 +00:00
MrParadox1691
5d7d22245b [ci] apply-plugin-metadata-and-formatting 2026-06-11 15:16:42 +00:00
Mr.Paradox
b8dee8f224
Update vanishing_tiles.py 2026-06-11 20:43:39 +05:30
Mr.Paradox
710dbebee4
Merge branch 'bombsquad-community:main' into main 2026-06-11 20:25:43 +05:30
Mr.Paradox
643ac51a08
Update vanishing_tiles.py 2026-06-11 20:23:25 +05:30
GeoHnlol
d0df37d764 [ci] apply-version-metadata 2026-06-11 09:35:34 +00:00
GeoHnlol
3adfe38e55
Changed version number to 1.1.9 2026-06-11 12:35:04 +03:00
Loup
2660a8d973
Merge pull request #467 from MrParadox1691/main
Vanishing tiles minigame update
2026-06-10 22:05:13 +05:30
Loup
767fbe3a28
Delete version 1.0.2 from minigames.json
Removed version 1.0.2 details from minigames.json
2026-06-10 22:02:11 +05:30
MrParadox1691
18dbcbbe3f [ci] apply-version-metadata 2026-06-10 16:12:15 +00:00
MrParadox1691
0749053077 [ci] apply-plugin-metadata-and-formatting 2026-06-10 16:12:12 +00:00
Mr.Paradox
4804a55ca0
Update vanishing_tiles.py 2026-06-10 21:41:40 +05:30
Mr.Paradox
2e2ff3656a
Update utilities.json 2026-06-10 21:21:21 +05:30
Loup
55c3fe5af4
Merge pull request #465 from FluffyPal0/main
Added fluffyplaylisteditor
2026-06-10 20:33:54 +05:30
MrParadox1691
7fcb9b4468 [ci] apply-version-metadata 2026-06-10 07:05:09 +00:00
MrParadox1691
edaed70235 [ci] apply-plugin-metadata-and-formatting 2026-06-10 07:05:08 +00:00
Mr.Paradox
e091fb6983
Update and rename vanishing_tiles.py to vanishing_tiles.py 2026-06-10 09:18:14 +05:30
GeoHnlol
d88d0e149d
Updated export shortcuts
Replaced all instances of EXPORT_CLASS_NAME_SHORTCUTS with _DEPRECATED_EXPORT_SHORTCUTS for bs version 1.7.63
2026-06-08 14:27:28 +03:00
FluffyPal0
7e249c1181 [ci] apply-version-metadata 2026-06-08 05:14:51 +00:00
FluffyPal0
99cf837498 [ci] apply-plugin-metadata-and-formatting 2026-06-08 05:14:50 +00:00
FluffyPal
1662980eea
Adding fluffyplaylisteditor 2026-06-08 13:12:31 +08:00
Vishyyy
929ce3f26f
Merge pull request #464 from Scriptz1/main
Volley Punch
2026-06-02 11:55:34 +05:30
Scriptz1
d35b78326b [ci] apply-version-metadata 2026-06-02 00:27:00 +00:00
Scriptz1
a6d11fd8ce [ci] apply-plugin-metadata-and-formatting 2026-06-02 00:26:59 +00:00
Scriptz1
d517d47f3e
Update volley_punch.py 2026-06-01 20:26:09 -04:00
Scriptz1
811e151c81
Update volley_punch.py 2026-06-01 20:25:17 -04:00
Loup
a1b809bc80
Merge pull request #463 from bombsquad-community/revert-462-main
Revert "volley_punch"
2026-06-01 20:42:35 +05:30
Loup
7c718cf4a3
Revert "Volley Punch"
This reverts commit f6c0fd5fbe.
2026-06-01 08:21:09 +05:30
Scriptz1
8fba8c8ab5
Update volley_punch.py 2026-05-31 21:11:03 -04:00
Scriptz1
f6c0fd5fbe
Volley Punch 2026-06-01 06:32:13 +05:30
Scriptz1
e9e1a29fea [ci] apply-version-metadata 2026-06-01 00:58:05 +00:00
Scriptz1
576a9c0304 [ci] apply-plugin-metadata-and-formatting 2026-06-01 00:58:03 +00:00
Scriptz1
9a74e81ec7
Update volley_punch.py 2026-05-31 20:57:41 -04:00
Scriptz1
d0e936c582
Rename volleypunch.py to volley_punch.py 2026-05-31 20:55:10 -04:00
Scriptz1
e6d9c5d71c
Add files via upload 2026-05-31 20:43:43 -04:00
Loup
3a622edb51
Merge pull request #461 from andrejkuroglo8/main
Add glowing_profiles.py, which was merged since simpleredefiner.py
2026-05-15 11:47:54 +05:30
andrejkuroglo8
23fbdf5e9d [ci] apply-version-metadata 2026-05-10 11:24:45 +00:00
andrejkuroglo8
6a494a44fc [ci] apply-plugin-metadata-and-formatting 2026-05-10 11:24:43 +00:00
andrejkuroglo8
7c5038263c [ci] apply-plugin-metadata-and-formatting 2026-05-10 11:22:18 +00:00
andrejkuroglo8
bbf36dc4a9
Update glowing_profiles.py 2026-05-10 14:15:10 +03:00
andrejkuroglo8
6212108315
Add files via upload 2026-05-10 14:11:32 +03:00
Loup
05c3f1d023
Merge pull request #457 from anasdhaoidi/main
fix some plugins and add new plugin
2026-05-07 23:14:23 +05:30
anasdhaoidi
e131859fe6 [ci] apply-version-metadata 2026-05-01 14:28:17 +00:00
anasdhaoidi
023b733c3b [ci] apply-plugin-metadata-and-formatting 2026-05-01 14:28:15 +00:00
Anas
6c8e898913
Bump version from 1.0.0 to 1.0.1 2026-05-01 15:27:48 +01:00
Anas
3876b25f47
Update print statement from 'Hello' to 'Goodbye' 2026-05-01 15:22:43 +01:00
Anas
06d11887a4
Add rank system plugin for player scoring
Implement a rank system for players, including score tracking and rank display.
2026-05-01 15:15:01 +01:00
Anas
120a09f380
Add custom hits plugin with visual effects
This plugin triggers custom visual effects and messages based on the damage taken by a player. It defines various damage thresholds and corresponding effects, enhancing the gameplay experience.
2026-05-01 15:13:38 +01:00
Anas
bbd1fe6632
Delete plugins/custom_hits.py 2026-05-01 15:12:43 +01:00
Anas
1c728633ea
Add custom hit effects and messages
Implement custom hit effects and messages based on damage thresholds.
2026-05-01 15:12:04 +01:00
Anas
de9ca9ddb2
Delete plugins/utilities/custom_hits.py 2026-05-01 15:07:38 +01:00
Anas
e6c5130186
Delete plugins/utilities/powerup_manager.py 2026-05-01 15:07:09 +01:00
Vishyyy
864e4efabf
Merge pull request #455 from vishal332008/main
Update version to 2.1.7 in advanced_party_window.py
2026-04-06 01:18:49 +05:30
vishal332008
0417dc09e0 [ci] apply-version-metadata 2026-04-05 19:48:00 +00:00
Vishyyy
c0527f3635
Add version 2.1.7 to utilities.json 2026-04-06 01:17:38 +05:30
Vishyyy
a8999bd8e1
Update version to 2.1.7 in advanced_party_window.py 2026-04-06 01:14:45 +05:30
Vishyyy
7a6d6e301f
Merge pull request #453 from vishal332008/main
Fixing a Closing Window Bug
2026-04-06 00:39:39 +05:30
vishal332008
6312b16f49 [ci] apply-version-metadata 2026-04-05 19:08:41 +00:00
Vishyyy
3aa666ff65
Add version 2.1.6 to utilities.json 2026-04-06 00:38:19 +05:30
vishal332008
8864fc650e [ci] apply-version-metadata 2026-04-05 18:54:02 +00:00
vishal332008
564357affd [ci] apply-plugin-metadata-and-formatting 2026-04-05 18:54:01 +00:00
Vishyyy
f74738487f
Fix syntax for authors list in advanced_party_window.py 2026-04-06 00:23:37 +05:30
Vishyyy
5ebbadb946
Correct authors key syntax in plugin dict
Fix syntax error in plugin dictionary definition.
2026-04-06 00:21:15 +05:30
Vishyyy
8fcb89492a
Updating Advanced Party Window 2026-04-06 00:18:59 +05:30
Vishyyy
0288823e6c
Fixing it back 2026-04-06 00:17:54 +05:30
Vishyyy
16cb91a80b
Add version 2.1.6 to utilities.json 2026-04-05 23:26:23 +05:30
Vishyyy
4f0cdaceff
Fixing a Closing Window Bug 2026-04-05 23:22:03 +05:30
Loup
7a6fa42e0a
Merge pull request #446 from Loup-Garou911XD/add_vanishing_tiles_plugin
addded vanishing_tiles plugin
2026-03-12 20:46:30 +05:30
Loup-Garou911XD
56ad1aa843 [ci] apply-version-metadata 2026-03-09 19:18:20 +00:00
Loup-Garou911XD
38f9e35e53 [ci] apply-plugin-metadata-and-formatting 2026-03-09 19:18:19 +00:00
Loup-Garou911XD
0c22ef23b0
add vanishing_tiles plugin 2026-03-10 00:47:36 +05:30
Loup
4baad2b813
Merge pull request #444 from DinoWattz/main
Add better_camera_shake, chat_bubbles, enhanced_effects and sleep_on_afk
2026-03-09 23:14:05 +05:30
Loup
272bc0cb5c
Merge pull request #445 from Loup-Garou911XD/main
fix for bs build 22714
2026-02-23 17:37:03 +05:30
Loup-Garou911XD
ea78bf01db [ci] apply-version-metadata 2026-02-23 12:00:17 +00:00
Loup-Garou911XD
822208149c
fix for latest version 2026-02-23 17:29:39 +05:30
DinoWattz
963f7dfa87 [ci] apply-version-metadata 2026-02-23 08:18:31 +00:00
DinoWattz
e67d891716 [ci] apply-plugin-metadata-and-formatting 2026-02-23 08:18:30 +00:00
DinoWattz
ebbd198628
Upload plugins 2026-02-23 05:16:06 -03:00
Loup
3e5873b5c8
Merge pull request #442 from Freaku17/main
Update LICENSE & README
2026-02-16 21:12:50 +05:30
! Freaku
d07385de2d
Update README
Regarding installation of plugin manager
2026-02-16 12:38:39 +05:30
! Freaku
455967d4fd
Update LICENSE 2026-02-16 12:34:56 +05:30
Loup
3ea0449517
Merge pull request #440 from Loup-Garou911XD/main
bug report btn autofills form
2026-02-16 00:07:24 +05:30
Loup-Garou911XD
13a86bec22 [ci] apply-version-metadata 2026-02-15 18:32:23 +00:00
Loup-Garou911XD
af881afd9b
added plugin bug report form template 2026-02-16 00:01:50 +05:30
Loup
a55712ed50
Merge pull request #437 from DrapBS/main
add player list plugin
2026-02-14 16:18:02 +05:30
DrapBS
c7c0540134 [ci] apply-version-metadata 2026-02-14 09:07:31 +00:00
DrapBS
0d39f577f2 [ci] apply-plugin-metadata-and-formatting 2026-02-14 09:07:30 +00:00
DrapBS
abe91c4b0e
Upload The plugin on plugin manager
This plugin contains plugman dict
2026-02-14 10:07:07 +01:00
DrapBS
658677f057 [ci] apply-plugin-metadata-and-formatting 2026-02-07 16:26:10 +00:00
DrapBS
1ff3e6f790
Add files via upload 2026-02-07 13:20:52 +01:00
Loup
b0df2f3817
Merge pull request #436 from Loup-Garou911XD/improve_customhit_plugin
remove global screenmessage in custom_hits plugin
2026-02-07 17:38:39 +05:30
Loup-Garou911XD
fdfe0c284a [ci] apply-version-metadata 2026-02-06 23:49:16 +00:00
Loup-Garou911XD
b48a4bda31 [ci] apply-plugin-metadata-and-formatting 2026-02-06 23:49:15 +00:00
Loup-Garou911XD
e97c8dec37 remove global screenmessage in custom_hits plugin 2026-02-07 05:18:21 +05:30
Loup
7224a72e4e
Merge pull request #434 from bombsquad-community/fix_moreWindow_scale
fix_moreWindow_scale
2026-01-26 16:11:15 +05:30
Loup-Garou911XD
d516c5e2d4 [ci] apply-version-metadata 2026-01-26 10:40:59 +00:00
Loup-Garou911XD
b0c463e32c ci 2026-01-26 16:10:31 +05:30
Loup-Garou911XD
8197884864 [ci] apply-version-metadata 2026-01-26 10:36:37 +00:00
Loup-Garou911XD
f0b36c742b fix_moreWindow_scale 2026-01-26 16:05:30 +05:30
22 changed files with 10380 additions and 3215 deletions

View file

@ -1,46 +0,0 @@
---
name: Plugin bug report
about: Create a report about a plugin to help us improve
title: '[PLUGIN BUG]'
labels: Plugin Bug
assignees: ''
---
**Plugin name**
The name of the plugin having the bug
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- Device: [e.g. PC]
- OS: [e.g. Windows]
- OS Version (if applicable) [e.g. 11]
- Bombsquad Version [e.g. 1.7.37]
- Plugin Manager Version [e.g. 1.1.10]
- Plugin Version [e.g 2.0.1]
**Smartphone (please complete the following information):**
- Device: [e.g. Mobile]
- OS: [e.g. Android]
- OS Version (if applicable) [e.g. 11]
- Bombsquad Version [e.g. 1.7.37]
- Plugin Manager Version [e.g. 1.1.10]
- Plugin Version [e.g 2.0.1]
**Additional context**
Add any other context about the problem here.

View file

@ -0,0 +1,89 @@
name: Plugin Bug Report
description: Create a report about a plugin to help us improve.
title: '[PLUGIN BUG]: <plugin-name>'
labels: ['Plugin Bug']
body:
- type: input
id: plugin-name
attributes:
label: Plugin Name
description: The name of the plugin experiencing the bug.
placeholder: e.g. colorscheme
validations:
required: true
- type: textarea
id: bug-description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: console-log
attributes:
label: Console Log
description: Goto settings -> advanced -> Dev Tools -> enable "Show dev console button" and reproduce the bug. Then copy and paste the console log here.
- type: textarea
id: reproduction-steps
attributes:
label: To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
- type: input
id: os-version
attributes:
label: OS with Version
placeholder: e.g. Windows 11 / Android 13
- type: input
id: bombsquad-version
attributes:
label: BombSquad Version
placeholder: e.g. v1.7.60
validations:
required: true
- type: input
id: plugin-manager-version
attributes:
label: Plugin Manager Version
placeholder: e.g. v1.1.10
validations:
required: true
- type: input
id: plugin-version
attributes:
label: Plugin Version
placeholder: e.g. 2.0.1
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: Drag and drop screenshots here to help explain the problem.
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Add any other context about the problem here (logs, other plugins installed, etc).

View file

@ -1,5 +1,26 @@
## Plugin Manager (dd-mm-yyyy)
### 1.1.10 (12-06-2026)
- Fix for older bs versions using `EXPORT_CLASS_NAME_SHORTCUTS` import
### 1.1.9 (11-06-2026)
- Fix for bs 1.7.63
### 1.1.8 (23-02-2026)
- Fix for bs 1.7.61 build no 22714
### 1.1.7 (15-02-2026)
- Added function to fill form details for plugin bug report button
- Added a new plugin bug report yaml template
### 1.1.6 (26-01-2026)
- Fix UI scaling for more window
### 1.1.5 (26-01-2026)
- Various UI bugfixes and improvements
@ -27,15 +48,15 @@
### 1.1.1 (09-02-2025)
- Added bomb spinner widget for loading animation.
### 1.1.0 (23-01-2025)
- Updated to bombsquad api 9.
- Updated to bombsquad api 9.
### 1.0.23 (13-01-2025)
- Fixed a bug which wasn't letting older versions run Plugin Manager.
### 1.0.22 (07-08-2024)
- Fixed a few Errors and UI Bugs.
@ -189,13 +210,11 @@
- Added New Option in settings for Notifying new plugins.
- Added a Discord Button to join Bombsquad's Official Discord server.
### 0.1.6 (15-09-2022)
- Distinguish the settings button with a cyan color (previously was green) in plugin manager window.
- Clean up some internal code.
### 0.1.5 (08-09-2022)
- Plugin files that export classes besides plugin or game, now work.

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Members of bombsquad-community organization, and contributors
Copyright (c) 2022-2026 Members of bombsquad-community organization, and contributors
All source code in this repository is licensed under MIT, unless otherwise
explicitly stated in source code.

View file

@ -38,12 +38,13 @@ which makes further modding of your game more convenient by providing easier acc
## Installation
There are two different ways the plugin manager can be installed:
There are three different ways the plugin manager can be installed:
1. From dev console
- Enable "Show Dev Console Button" from advance BombSquad settings
- Make sure you're connected to the internet
- Paste the following code in dev console
```py
import urllib.request;import _babase;import os;url="https://github.com/bombsquad-community/plugin-manager/releases/latest/download/plugin_manager.py";plugin_path=os.path.join(_babase.env()["python_directory_user"],"plugin_manager.py");file=urllib.request.urlretrieve(url)[0];fl = open(file,'r');f=open(plugin_path, 'w+');f.write(fl.read());fl.close();f.close();print("SUCCESS")
@ -57,7 +58,7 @@ There are two different ways the plugin manager can be installed:
manually apply updates by copying the latest plugin manager's source code again to your workspace when using this method.
3. [Download plugin_manager.py][DownloadLink] to your mods directory (check it out by going into your game's
Settings -> Advanced -> Show Mods Folder). This is the recommended way (read next method to know why).
Settings -> Advanced -> Show Mods Folder).
If you're on a newer version of Android (11 or above) and not rooted, it probably won't be possible to copy
mods to game's mods folder. In this case, you can connect your Android phone to a computer and push `plugin_manager.py`
[using `adb`](https://www.xda-developers.com/install-adb-windows-macos-linux/):

View file

@ -1,6 +1,36 @@
{
"plugin_manager_url": "https://github.com/bombsquad-community/plugin-manager/{content_type}/{tag}/plugin_manager.py",
"versions": {
"1.1.10": {
"api_version": 9,
"commit_sha": "a1baa5f",
"released_on": "12-06-2026",
"md5sum": "ee4f7e3e88c2982bc8290e204b10b050"
},
"1.1.9": {
"api_version": 9,
"commit_sha": "3adfe38",
"released_on": "11-06-2026",
"md5sum": "d7e990ea4e5c530f780e617c9bdafb42"
},
"1.1.8": {
"api_version": 9,
"commit_sha": "8222081",
"released_on": "23-02-2026",
"md5sum": "8e8e9fc5b818883102c6081619404318"
},
"1.1.7": {
"api_version": 9,
"commit_sha": "af881af",
"released_on": "15-02-2026",
"md5sum": "454cbacd47c813154f2f9cb0d2736067"
},
"1.1.6": {
"api_version": 9,
"commit_sha": "b0c463e",
"released_on": "26-01-2026",
"md5sum": "fc42fef8804cbfece0280ecb9491983c"
},
"1.1.5": {
"api_version": 9,
"commit_sha": "dd1a605",

View file

@ -2,7 +2,6 @@
import babase
import bauiv1 as bui
from bauiv1lib import popup, confirm
from babase._meta import EXPORT_CLASS_NAME_SHORTCUTS
from bauiv1lib.settings.allsettings import AllSettingsWindow
import urllib.request
@ -26,7 +25,7 @@ from datetime import datetime
# Modules used for overriding AllSettingsWindow
import logging
PLUGIN_MANAGER_VERSION = "1.1.5"
PLUGIN_MANAGER_VERSION = "1.1.10"
REPOSITORY_URL = "https://github.com/bombsquad-community/plugin-manager"
# Current tag can be changed to "staging" or any other branch in
# plugin manager repo for testing purpose.
@ -35,15 +34,20 @@ CURRENT_TAG = "main"
_env = babase.env()
_app_api_version = babase.app.env.api_version
PLUGIN_ISSUE_TEMPLATE = "plugin-bug-report.md"
GITHUB_ISSUE_META = "{repository_url}/issues/new?template={issue_template}"
PLUGIN_ISSUE_TEMPLATE = "plugin-bug-report.yml"
GITHUB_PLUGIN_ISSUE_META = "{repository_url}/issues/new?template={issue_template}"
INDEX_META = "{repository_url}/{content_type}/{tag}/index.json"
CHANGELOG_META = "{repository_url}/{content_type}/{tag}/CHANGELOG.md"
HEADERS = {
"User-Agent": _env["legacy_user_agent_string"],
}
PLUGIN_DIRECTORY = _env["python_directory_user"]
loop = babase._asyncio._asyncio_event_loop
# compatibility for older API versions.
if _env.get("build_number", 0) < 22714:
babase._asyncio._g_asyncio_event_loop = babase._asyncio._asyncio_event_loop
loop = babase._asyncio._g_asyncio_event_loop
open_popups = []
@ -64,9 +68,10 @@ def _regexp_friendly_class_name_shortcut(string): return string.replace(".", "\\
def _by_scale(a, b, c):
u = _uiscale()
return (
a if _uiscale is babase.UIScale.SMALL else
b if _uiscale is babase.UIScale.MEDIUM else
a if u is babase.UIScale.SMALL else
b if u is babase.UIScale.MEDIUM else
c
)
@ -76,7 +81,7 @@ REGEXP = {
"plugin_entry_points": re.compile(
bytes(
"(ba_meta export (plugin|{})\n+class )(.*)\\(".format(
_regexp_friendly_class_name_shortcut(EXPORT_CLASS_NAME_SHORTCUTS["plugin"]),
_regexp_friendly_class_name_shortcut("babase.Plugin"),
),
"utf-8"
),
@ -1797,8 +1802,10 @@ class PluginWindow(popup.PopupWindow):
class MoreWindow:
def __init__(self, plugin, origin=None):
def __init__(self, plugin: Plugin, origin=None):
_add_popup(self)
self.plugin = plugin
# collect info
last_updated = None
for value in plugin.info['versions'].values():
@ -1911,13 +1918,7 @@ class MoreWindow:
button_type='square',
color=(0.6, 0.53, 0.63),
label='',
on_activate_call=bui.CallPartial(
bui.open_url,
GITHUB_ISSUE_META.format(
repository_url=REPOSITORY_URL,
issue_template=PLUGIN_ISSUE_TEMPLATE
)
)
on_activate_call=self._open_bug_report_url
)
bui.imagewidget(
parent=self._root_widget,
@ -1984,6 +1985,36 @@ class MoreWindow:
down_widget=source_button
)
def _open_bug_report_url(self):
import platform
import urllib.parse
import baenv
LOGS_LENGTH_LIMIT = 5000
_logs = ""
for entry in baenv._EnvGlobals.get().config.log_handler.get_cached().entries:
_logs += entry.message + "\n"
# reduce this more if it's too long
error_logs = f"""```py
{_logs[:LOGS_LENGTH_LIMIT]}
```"""
params = {
"title": "[PLUGIN BUG]: " + self.plugin.name,
"plugin-name": self.plugin.name,
"plugin-version": self.plugin.create_local().version if self.plugin.is_installed else 'Not Installed',
"plugin-manager-version": 'v' + PLUGIN_MANAGER_VERSION,
"bombsquad-version": 'v' + babase.app.env.engine_version + " (" + str(babase.app.env.engine_build_number) + ")",
"os-version": platform.platform() if platform else babase.app.env.platform.value + babase.app.env.os_version,
"console-log": error_logs
}
query_string = urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
base_url = GITHUB_PLUGIN_ISSUE_META.format(
repository_url=REPOSITORY_URL, issue_template=PLUGIN_ISSUE_TEMPLATE)
final_url = f"{base_url}&{query_string}"
bui.open_url(final_url)
def _back(self) -> None:
_remove_popup(self)
bui.getsound('swish').play()
@ -3388,7 +3419,7 @@ class EntryPoint(babase.Plugin):
from bauiv1lib.settings import allsettings
allsettings.AllSettingsWindow = NewAllSettingsWindow
DNSBlockWorkaround.apply()
asyncio.set_event_loop(babase._asyncio._asyncio_event_loop)
asyncio.set_event_loop(babase._asyncio._g_asyncio_event_loop)
startup_tasks = StartupTasks()
loop.create_task(startup_tasks.execute())

View file

@ -1663,6 +1663,61 @@
"md5sum": "33e6b93420cf6115e992aca534d76bfd"
}
}
},
"volley_punch": {
"description": "Have fun with your friends in this new volley game !",
"external_url": "https://github.com/Scriptz1/Learn.py-Installer/blob/main/volleypunch.py",
"authors": [
{
"name": "Learn.py",
"email": "phillipians36@gmail.com",
"discord": "Learning .py"
}
],
"versions": {
"1.1.0": {
"api_version": 9,
"commit_sha": "a6d11fd",
"released_on": "02-06-2026",
"md5sum": "a2d73ebe4cb84b723242c187ad5b5fbb"
},
"1.0.0": {
"api_version": 9,
"commit_sha": "576a9c0",
"released_on": "01-06-2026",
"md5sum": "be998b599896ed0e8dd5f2545fae6173"
}
}
},
"vanishing_tiles": {
"description": "Tiles disappear gradually. Survive each round to continue. Last player standing wins!",
"external_url": "https://discord.gg/sQGDsztQcy",
"authors": [
{
"name": "Mr.Paradox",
"email": "",
"discord": "gaurangbroyo"
},
{
"name": "senchx",
"email": "",
"discord": "senchx0"
}
],
"versions": {
"2.1.1": {
"api_version": 9,
"commit_sha": "5d7d222",
"released_on": "11-06-2026",
"md5sum": "d71b9615872ba3655a83166dd211ecd2"
},
"2.1.0": {
"api_version": 9,
"commit_sha": "0749053",
"released_on": "10-06-2026",
"md5sum": "152e2e149b2686619c44aa7880a7e460"
}
}
}
}
}

View file

@ -0,0 +1,414 @@
# ba_meta require api 9
from __future__ import annotations
from bascenev1lib.gameutils import SharedObjects
import bascenev1 as bs
import babase
import random
from typing import Optional, List, Dict, Any
# Vanishing Tiles - BombSquad Minigame
# Tiles disappear one by one. Survive all rounds to win.
# Last player/team standing takes the crown.
plugman = dict(
plugin_name="vanishing_tiles",
description="Tiles disappear gradually. Survive each round to continue. Last player standing wins!",
external_url="https://discord.gg/sQGDsztQcy",
authors=[
{"name": "Mr.Paradox", "email": "", "discord": "gaurangbroyo"},
{"name": "senchx", "email": "", "discord": "senchx0"},
],
version="2.1.1",
)
class Player(bs.Player['Team']):
def __init__(self) -> None:
super().__init__()
self.death_time: Optional[float] = None
class Team(bs.Team[Player]):
pass
# ba_meta export bascenev1.GameActivity
class VanishingTilesGame(bs.TeamGameActivity[Player, Team]):
name = 'Vanishing Tiles'
description = 'Tiles disappear one by one. Be the last one standing!'
scoreconfig = bs.ScoreConfig(label='Survived', scoretype=bs.ScoreType.MILLISECONDS)
announce_player_deaths = True
@classmethod
def get_available_settings(cls, sessiontype: type[bs.Session]) -> list[babase.Setting]:
return [
bs.BoolSetting('Epic Mode', default=False),
bs.BoolSetting('Show Credits', default=True),
]
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, (bs.FreeForAllSession, bs.DualTeamSession))
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return ['Vanishing Tiles Arena']
def __init__(self, settings: dict) -> None:
super().__init__(settings)
self._epic_mode: bool = bool(settings.get('Epic Mode', False))
self._show_credits: bool = bool(settings.get('Show Credits', True))
self.default_music = bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SURVIVAL
self._round: int = 1
self._removing: bool = False
self._game_start_time: Optional[float] = None
self._tile_nodes: Dict[int, bs.Node] = {}
self._region_nodes: Dict[int, bs.Node] = {}
self._present_tile_ids: set[int] = set()
self._remove_speed: float = 1.8
self._collide_mat = bs.Material()
self._collide_mat.add_actions(actions=(('modify_part_collision', 'collide', True),))
self._no_collide_mat = bs.Material()
self._no_collide_mat.add_actions(actions=(('modify_part_collision', 'collide', False),))
self._default_tex = bs.gettexture('powerupHealth')
self._warn_tex = bs.gettexture('powerupCurse')
self._final_tex = bs.gettexture('powerupPunch')
self._hud_round: Optional[bs.Node] = None
self._hud_players: Optional[bs.Node] = None
self._hud_tiles: Optional[bs.Node] = None
self._credit_node: Optional[bs.Node] = None
if self._epic_mode:
self.slow_motion = True
def spawn_player(self, player: Player) -> Any:
if isinstance(self.session, bs.FreeForAllSession):
spaz = self.spawn_player_spaz(player, position=VanishingTilesMapDefs.points['spawn1'])
else:
spaz = self.spawn_player_spaz(player)
spaz.connect_controls_to_player(enable_punch=False, enable_pickup=False, enable_bomb=False)
spaz.set_bomb_count(0)
return spaz
def on_begin(self) -> None:
super().on_begin()
self._game_start_time = bs.time()
if self._show_credits:
self._credit_node = bs.newnode('text', attrs={
'text': 'Made by Mr.Paradox',
'scale': 0.7, 'position': (0, 8), 'shadow': 0.8, 'flatness': 1.0,
'color': (1.0, 0.3, 0.8, 1.0), 'h_align': 'center', 'v_attach': 'bottom',
})
bs.animate_array(self._credit_node, 'color', 4, {
0.0: [1.0, 0.3, 0.8, 1.0], 1.0: [0.3, 0.8, 1.0, 1.0],
2.0: [0.4, 1.0, 0.4, 1.0], 3.0: [1.0, 1.0, 0.2, 1.0],
4.0: [1.0, 0.3, 0.8, 1.0],
}, loop=True)
self._build_hud()
self._cleanup_tiles()
self._spawn_all_tiles()
self._show_round_banner()
if not isinstance(self.session, bs.FreeForAllSession):
if not all(len(t.players) >= 1 for t in self.teams):
bs.broadcastmessage('Not enough players - draw!', color=(1, 1, 0))
bs.timer(1.0, lambda: self.end(bs.GameResults()))
return
bs.timer(1.0, self._check_initial_state)
bs.timer(3.0, self._start_removal)
def _build_hud(self) -> None:
for attr in ('_hud_round', '_hud_players', '_hud_tiles'):
node = getattr(self, attr, None)
if node is not None and node.exists():
node.delete()
setattr(self, attr, None)
self._hud_round = bs.newnode('text', attrs={
'text': '', 'scale': 0.85, 'position': (0, -40), 'maxwidth': 300,
'h_align': 'center', 'v_align': 'center', 'v_attach': 'top', 'h_attach': 'center',
'shadow': 1.0, 'flatness': 1.0, 'color': (1, 1, 1, 1), 'in_world': False,
})
self._hud_players = bs.newnode('text', attrs={
'text': '', 'scale': 0.8, 'position': (0, -58), 'maxwidth': 300,
'h_align': 'center', 'v_align': 'center', 'v_attach': 'top', 'h_attach': 'center',
'shadow': 1.0, 'flatness': 1.0, 'color': (1, 0.8, 0.2, 1), 'in_world': False,
})
self._hud_tiles = bs.newnode('text', attrs={
'text': '', 'scale': 0.8, 'position': (0, -74), 'maxwidth': 300,
'h_align': 'center', 'v_align': 'center', 'v_attach': 'top', 'h_attach': 'center',
'shadow': 1.0, 'flatness': 1.0, 'color': (0.5, 1.0, 0.5, 1), 'in_world': False,
})
self._refresh_hud()
def _refresh_hud(self) -> None:
alive = len([p for p in self.players if p.is_alive()])
tiles = len(self._present_tile_ids)
urgent = tiles <= 4
if self._hud_round:
self._hud_round.text = f'Round {self._round}'
if self._hud_players:
self._hud_players.text = f'Players alive: {alive}'
if self._hud_tiles:
self._hud_tiles.color = (1.0, 0.3, 0.3, 1) if urgent else (0.5, 1.0, 0.5, 1)
self._hud_tiles.text = f'Tiles left: {tiles}'
def _show_round_banner(self) -> None:
node = bs.newnode('text', attrs={
'text': f'Round {self._round}', 'scale': 1.3, 'position': (0, 60),
'shadow': 1.2, 'flatness': 0.7, 'color': (1, 1, 0, 1),
'h_align': 'center', 'v_attach': 'center', 'in_world': False,
})
bs.animate(node, 'scale', {0: 0.0, 0.15: 1.3, 2.0: 1.3, 2.5: 0.0})
bs.timer(2.6, node.delete)
def on_player_join(self, player: Player) -> None:
if self.has_begun():
player.death_time = bs.time()
bs.broadcastmessage(
f'{player.getname()} joined! Round {self._round} - {len(self._present_tile_ids)} tiles left.',
color=(0.5, 1.0, 1.0), transient=True,
)
return
self.spawn_player(player)
def _check_initial_state(self) -> None:
if not [p for p in self.players if p.is_alive()]:
bs.timer(1.0, self._check_end_game)
def _spawn_all_tiles(self) -> None:
positions = [
(4.5, 2, -9), (4.5, 2, -6), (4.5, 2, -3), (4.5, 2, 0),
(1.5, 2, -9), (1.5, 2, -6), (1.5, 2, -3), (1.5, 2, 0),
(-1.5, 2, -9), (-1.5, 2, -6), (-1.5, 2, -3), (-1.5, 2, 0),
(-4.5, 2, -9), (-4.5, 2, -6), (-4.5, 2, -3), (-4.5, 2, 0),
]
model = bs.getmesh('buttonSquareOpaque')
shared = SharedObjects.get()
for i, pos in enumerate(positions):
tile = bs.newnode('prop', attrs={
'body': 'puck', 'position': pos, 'mesh': model,
'mesh_scale': 3.73, 'body_scale': 3.73, 'gravity_scale': 0.0,
'color_texture': self._default_tex, 'reflection': 'soft',
'materials': [self._no_collide_mat],
})
region = bs.newnode('region', attrs={
'position': pos, 'scale': (3.5, 0.1, 3.5), 'type': 'box',
'materials': [self._collide_mat, shared.footing_material],
})
self._tile_nodes[i] = tile
self._region_nodes[i] = region
self._present_tile_ids.add(i)
self._refresh_hud()
def _start_removal(self) -> None:
if not self._removing:
self._removing = True
self._remove_next_tile()
def _make_final_tile(self) -> None:
try:
tile_id = list(self._present_tile_ids)[0]
tile = self._tile_nodes.get(tile_id)
if tile and tile.exists():
tile.color_texture = self._final_tex
except Exception:
pass
self.slow_motion = True
bs.broadcastmessage('LAST TILE!', color=(1, 0.4, 0.1))
for p in self.players:
if p.is_alive() and p.actor and p.actor.exists():
p.actor.node.handlemessage(bs.CelebrateMessage(3.0))
self._refresh_hud()
bs.timer(3.0, self._round_end_check)
def _remove_next_tile(self) -> None:
if len(self._present_tile_ids) <= 1:
self._make_final_tile()
return
if len(self._present_tile_ids) <= 4:
bs.getsound('shieldDown').play(0.4)
tile_id = random.choice(list(self._present_tile_ids))
self._remove_tile(tile_id)
bs.timer(self._remove_speed, self._remove_next_tile)
def _remove_tile(self, tile_id: int) -> None:
tile = self._tile_nodes.get(tile_id)
region = self._region_nodes.get(tile_id)
if tile is None or not tile.exists():
self._present_tile_ids.discard(tile_id)
return
tile.color_texture = self._warn_tex
def vanish() -> None:
if tile.exists():
bs.emitfx(position=tile.position, count=20, scale=0.9,
spread=0.5, chunk_type='spark')
tile.delete()
if region and region.exists():
region.delete()
self._present_tile_ids.discard(tile_id)
self._refresh_hud()
bs.timer(1.0, vanish)
def _round_end_check(self) -> None:
self.slow_motion = self._epic_mode
if not isinstance(self.session, bs.FreeForAllSession):
living_teams = [t for t in self.teams if any(p.is_alive() for p in t.players)]
if len(living_teams) <= 1:
self._removing = False
self.end_game()
return
else:
if len([p for p in self.players if p.is_alive()]) <= 1:
self._removing = False
self.end_game()
return
self._remove_speed = max(0.3, self._remove_speed * 0.75)
self._round += 1
survivors = [p for p in self.players if p.is_alive()]
bs.broadcastmessage(f'Round {self._round}! Speed increasing!', color=(0.5, 1.0, 1.0))
bs.timer(1.5, lambda s=survivors: self._next_round(s))
def _next_round(self, survivors: List[Player]) -> None:
safety = bs.newnode('region', attrs={
'position': (0, 1.5, -5), 'scale': (20, 0.2, 20), 'type': 'box',
'materials': [SharedObjects.get().footing_material],
})
self._cleanup_tiles()
self._spawn_all_tiles()
self._show_round_banner()
self._refresh_hud()
for p in survivors:
if p.actor and p.actor.exists():
p.actor.handlemessage(bs.StandMessage(VanishingTilesMapDefs.points['spawn1']))
bs.timer(2.5, safety.delete)
self._removing = False
bs.timer(3.0, self._start_removal)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg)
msg.getplayer(Player).death_time = bs.time()
self._refresh_hud()
bs.timer(0.5, self._check_end_game)
return None
return super().handlemessage(msg)
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
bs.timer(0.2, self._check_team_empty)
def _check_team_empty(self) -> None:
if self.has_ended():
return
if isinstance(self.session, bs.FreeForAllSession):
return
for team in self.teams:
if len(team.players) == 0:
bs.broadcastmessage('A team has no players - game over!', color=(1, 1, 0))
bs.timer(1.0, self.end_game)
return
def _check_end_game(self) -> None:
if not self.has_begun():
return
if not isinstance(self.session, bs.FreeForAllSession):
if len([t for t in self.teams if any(p.is_alive() for p in t.players)]) <= 1:
self.end_game()
else:
if len([p for p in self.players if p.is_alive()]) <= 1:
self.end_game()
def end_game(self) -> None:
if self.has_ended():
return
cur_time = bs.time()
start = self._game_start_time or cur_time
results = bs.GameResults()
for team in self.teams:
longest = 0.0
for p in team.players:
death = p.death_time or (cur_time + 1)
longest = max(longest, death - start)
results.set_team_score(team, int(longest * 1000))
for attr in ('_hud_round', '_hud_players', '_hud_tiles'):
node = getattr(self, attr, None)
if node is not None and node.exists():
node.delete()
if self._credit_node and self._credit_node.exists():
self._credit_node.delete()
self.end(results=results)
def _cleanup_tiles(self) -> None:
for n in list(self._tile_nodes.values()):
if n.exists():
n.delete()
for n in list(self._region_nodes.values()):
if n.exists():
n.delete()
self._tile_nodes.clear()
self._region_nodes.clear()
self._present_tile_ids.clear()
class VanishingTilesMapDefs:
points = {'spawn1': (0, 3, -5)}
boxes = {
'area_of_interest_bounds': (0, 4, -5, 0, 0, 0, 16, 8, 16),
'map_bounds': (0, 4, -5, 0, 0, 0, 30, 14, 30),
}
class VanishingTilesMap(bs.Map):
defs = VanishingTilesMapDefs()
name = 'Vanishing Tiles Arena'
@classmethod
def get_preview_texture_name(cls) -> str:
return 'powerupHealth'
@classmethod
def on_preload(cls) -> Any:
return {'bgtex': bs.gettexture('menuBG'), 'bgmesh': bs.getmesh('thePadBG')}
def __init__(self) -> None:
super().__init__()
self.node = bs.newnode('terrain', attrs={
'mesh': self.preloaddata['bgmesh'], 'lighting': False,
'background': True, 'color_texture': self.preloaddata['bgtex'],
})
try:
bs._map.register_map(VanishingTilesMap)
except Exception:
pass
# ba_meta export babase.Plugin
class Main(babase.Plugin):
pass

File diff suppressed because it is too large Load diff

View file

@ -1054,6 +1054,18 @@
}
],
"versions": {
"2.1.7": {
"api_version": 9,
"commit_sha": "c0527f3",
"released_on": "05-04-2026",
"md5sum": "442889926c17a0ffc70b924a808d8132"
},
"2.1.6": {
"api_version": 9,
"commit_sha": "3aa666f",
"released_on": "05-04-2026",
"md5sum": "d2b66f98b5a24aa14eb6b1ed9de2d5dc"
},
"2.1.5": {
"api_version": 9,
"commit_sha": "a36e3c2",
@ -2375,6 +2387,12 @@
}
],
"versions": {
"1.0.1": {
"api_version": 9,
"commit_sha": "023b733",
"released_on": "01-05-2026",
"md5sum": "045eb589b2969398f7fcb171216303d1"
},
"1.0.0": {
"api_version": 9,
"commit_sha": "f38a7ac",
@ -2384,7 +2402,133 @@
}
},
"custom_hits": {
"description": "when someone hited rainbow text whit effect will appear",
"description": "when someone hited text whit random effect will appear",
"external_url": "",
"authors": [
{
"name": "ATD",
"email": "anasdhaoidi001@gmail.com",
"discord": ""
}
],
"versions": {
"1.1.0": {
"api_version": 9,
"commit_sha": "023b733",
"released_on": "01-05-2026",
"md5sum": "bc39a552ab7f40a71ce2813f7860a62f"
},
"1.0.1": {
"api_version": 9,
"commit_sha": "b48a4bd",
"released_on": "06-02-2026",
"md5sum": "6ca4e2288f78e1800c1c6716477d8dd9"
},
"1.0.0": {
"api_version": 9,
"commit_sha": "603aa14",
"released_on": "22-01-2026",
"md5sum": "5082acb27f3dbe701d8bd42d8948b352"
}
}
},
"players_list": {
"description": "A plugin that shows you live players list",
"external_url": "https://m.youtube.com/drap_bs",
"authors": [
{
"name": "Drap",
"email": "mrunittest@gmail.com",
"discord": "drap_bs"
}
],
"versions": {
"1.7.0": {
"api_version": 9,
"commit_sha": "0d39f57",
"released_on": "14-02-2026",
"md5sum": "d8002dfc93be74d71f8e6e4aacfe6fd1"
}
}
},
"better_camera_shake": {
"description": "This plugin makes the camera only shake when an explosion hits a player character, begone excessive camera shaking!",
"external_url": "",
"authors": [
{
"name": "DinoWattz",
"email": "",
"discord": ""
}
],
"versions": {
"1.0.0": {
"api_version": 9,
"commit_sha": "e67d891",
"released_on": "23-02-2026",
"md5sum": "552b0ff0492bdab6cf69c1bc2d679e0a"
}
}
},
"chat_bubbles": {
"description": "Adds whatever the players say above their character, includes a few other features too!",
"external_url": "",
"authors": [
{
"name": "DinoWattz",
"email": "",
"discord": ""
}
],
"versions": {
"1.0.0": {
"api_version": 9,
"commit_sha": "e67d891",
"released_on": "23-02-2026",
"md5sum": "307175b18b80671a7984862bcf09f1ce"
}
}
},
"enhanced_effects": {
"description": "Explosions affect character colors, slow-mo tnt, fair colored shields, a trippy background screen and maybe more in the future!",
"external_url": "",
"authors": [
{
"name": "DinoWattz",
"email": "",
"discord": ""
}
],
"versions": {
"1.0.0": {
"api_version": 9,
"commit_sha": "e67d891",
"released_on": "23-02-2026",
"md5sum": "63c665905dd4619d3f99ca8977f8fbf3"
}
}
},
"sleep_on_afk": {
"description": "Staying idle for 40 seconds will make your character fall asleep, they need rest too..",
"external_url": "",
"authors": [
{
"name": "DinoWattz",
"email": "",
"discord": ""
}
],
"versions": {
"1.0.0": {
"api_version": 9,
"commit_sha": "e67d891",
"released_on": "23-02-2026",
"md5sum": "5de7a474c3c6b21e8ea1e1abff3c07a1"
}
}
},
"rank_system": {
"description": "ranks system for servers or for local players",
"external_url": "",
"authors": [
{
@ -2396,9 +2540,47 @@
"versions": {
"1.0.0": {
"api_version": 9,
"commit_sha": "603aa14",
"released_on": "22-01-2026",
"md5sum": "5082acb27f3dbe701d8bd42d8948b352"
"commit_sha": "023b733",
"released_on": "01-05-2026",
"md5sum": "487c0520dc833f15942941e0dbc82f1e"
}
}
},
"glowing_profiles": {
"description": "This plugin gives your profile glowlight, just like on some servers, but only offline",
"external_url": "https://m.youtube.com/watch?v=Jb_dKz99rhY",
"authors": [
{
"name": "andrejkuroglo8",
"email": "andrejkuroglo8@gmail.com",
"discord": "andrewku"
}
],
"versions": {
"1.0.0": {
"api_version": 9,
"commit_sha": "6a494a4",
"released_on": "10-05-2026",
"md5sum": "6346cd7146baf41eef8926dced23fdfe"
}
}
},
"fluffyplaylisteditor": {
"description": "A simple not-so advanced Playlist Editor",
"external_url": "https://discord.com/channels/1001896771347304639/1483463979450896445",
"authors": [
{
"name": "FluffyPal",
"email": "",
"discord": "fluffypal"
}
],
"versions": {
"1.1.0": {
"api_version": 9,
"commit_sha": "99cf837",
"released_on": "08-06-2026",
"md5sum": "3ead338c768fbdffe022aadcbdc8c1d8"
}
}
}

View file

@ -19,41 +19,51 @@ https://bombsquad-community.web.app/mods
'''
import threading
from _thread import start_new_thread
import urllib.parse
import urllib.request
from typing import TYPE_CHECKING, cast
import _babase
import bascenev1 as bs
import bauiv1 as bui
import babase
import time
import math
from dataclasses import dataclass
from bauiv1lib.confirm import ConfirmWindow
from bauiv1lib.colorpicker import ColorPickerExact
from typing import List, Sequence, Optional, Dict, Any, Union
import bauiv1lib.party as bascenev1lib_party
import ssl
import datetime
import base64
from babase._general import Call
from bauiv1lib.popup import PopupMenuWindow, PopupWindow
from bauiv1lib.account import viewer
import os
import urllib
import copy
import shutil
import sys
import re
import json
import codecs
import traceback
plugman = dict(
plugin_name="advanced_party_window",
description="Advanced your party window with lots of feature",
external_url="https://www.youtube.com/watch?v=QrES1jQGXF0",
authors=[
{"name": "Mr.Smoothy", "email": "", "discord": "mr.smoothy"},
],
version="2.1.7",
)
# added advanced ID revealer
# live ping
# Made by Mr.Smoothy - Plasma Boson
import traceback
import codecs
import json
import re
import sys
import shutil
import copy
import urllib
import os
from bauiv1lib.account import viewer
from bauiv1lib.popup import PopupMenuWindow, PopupWindow
from babase._general import Call
import base64
import datetime
import ssl
import bauiv1lib.party as bascenev1lib_party
from typing import List, Sequence, Optional, Dict, Any, Union
from bauiv1lib.colorpicker import ColorPickerExact
from bauiv1lib.confirm import ConfirmWindow
from dataclasses import dataclass
import math
import time
import babase
import bauiv1 as bui
import bascenev1 as bs
import _babase
from typing import TYPE_CHECKING, cast
import urllib.request
import urllib.parse
from _thread import start_new_thread
import threading
version_str = "7"
BCSSERVER = 'mods.ballistica.workers.dev'
@ -461,6 +471,7 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow):
color=(0.55, 0.73, 0.25),
icon=bui.gettexture('menuButton'),
iconscale=1.2)
self._menu_popup: PopupMenuWindow | None = None
info = bs.get_connection_to_host_info_2()
if info != None:
@ -864,7 +875,7 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow):
choices.append("hostInfo_Debug")
DisChoices.append(_getTransText("Debug_for_Host_Info", isBaLstr=True))
PopupMenuWindow(
self._menu_popup = PopupMenuWindow(
position=self._menu_button.get_screen_space_center(),
scale=_get_popup_window_scale(),
choices=choices,
@ -891,12 +902,12 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow):
except:
babase.print_exception()
PopupMenuWindow(position=widget.get_screen_space_center(),
scale=_get_popup_window_scale(),
choices=choices,
choices_display=choices_display,
current_choice="@ this guy",
delegate=self)
self._menu_popup = PopupMenuWindow(position=widget.get_screen_space_center(),
scale=_get_popup_window_scale(),
choices=choices,
choices_display=choices_display,
current_choice="@ this guy",
delegate=self)
self._popup_party_member_client_id = client_id
self._popup_party_member_is_host = is_host
self._popup_type = "partyMemberPress"

View file

@ -0,0 +1,388 @@
# ba_meta require api 9
from __future__ import annotations
import babase
import bascenev1 as bs
import bascenev1lib.actor.bomb as bomb
import random
from typing import TYPE_CHECKING
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.bomb import BombFactory, ExplodeHitMessage
from bascenev1lib.actor.playerspaz import PlayerSpaz
if TYPE_CHECKING:
from typing import Any, Sequence
plugman = dict(
plugin_name="better_camera_shake",
description="This plugin makes the camera only shake when an explosion hits a player character, begone excessive camera shaking!",
external_url="",
authors=[
{"name": "DinoWattz", "email": "", "discord": ""}
],
version="1.0.0",
)
# Camera shake only on player impact
def new__init__(
self,
*,
position: Sequence[float] = (0.0, 1.0, 0.0),
velocity: Sequence[float] = (0.0, 0.0, 0.0),
blast_radius: float = 2.0,
blast_type: str = 'normal',
source_player: bs.Player | None = None,
hit_type: str = 'explosion',
hit_subtype: str = 'normal',
):
"""Instantiate with given values."""
# bah; get off my lawn!
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
super(type(self), self).__init__()
shared = SharedObjects.get()
factory = BombFactory.get()
self.blast_type = blast_type
self._source_player = source_player
self.hit_type = hit_type
self.hit_subtype = hit_subtype
self.radius = blast_radius
# Set our position a bit lower so we throw more things upward.
rmats = (factory.blast_material, shared.attack_material)
self.node = bs.newnode(
'region',
delegate=self,
attrs={
'position': (position[0], position[1] - 0.1, position[2]),
'scale': (self.radius, self.radius, self.radius),
'type': 'sphere',
'materials': rmats,
},
)
bs.timer(0.05, self.node.delete)
# Throw in an explosion and flash.
evel = (velocity[0], max(-1.0, velocity[1]), velocity[2])
explosion = bs.newnode(
'explosion',
attrs={
'position': position,
'velocity': evel,
'radius': self.radius,
'big': (self.blast_type == 'tnt'),
},
)
if self.blast_type == 'ice':
explosion.color = (0, 0.05, 0.4)
bs.timer(1.0, explosion.delete)
if self.blast_type != 'ice':
bs.emitfx(
position=position,
velocity=velocity,
count=int(1.0 + random.random() * 4),
emit_type='tendrils',
tendril_type='thin_smoke',
)
bs.emitfx(
position=position,
velocity=velocity,
count=int(4.0 + random.random() * 4),
emit_type='tendrils',
tendril_type='ice' if self.blast_type == 'ice' else 'smoke',
)
bs.emitfx(
position=position,
emit_type='distortion',
spread=1.0 if self.blast_type == 'tnt' else 2.0,
)
# And emit some shrapnel.
if self.blast_type == 'ice':
def emit() -> None:
bs.emitfx(
position=position,
velocity=velocity,
count=30,
spread=2.0,
scale=0.4,
chunk_type='ice',
emit_type='stickers',
)
# It looks better if we delay a bit.
bs.timer(0.05, emit)
elif self.blast_type == 'sticky':
def emit() -> None:
bs.emitfx(
position=position,
velocity=velocity,
count=int(4.0 + random.random() * 8),
spread=0.7,
chunk_type='slime',
)
bs.emitfx(
position=position,
velocity=velocity,
count=int(4.0 + random.random() * 8),
scale=0.5,
spread=0.7,
chunk_type='slime',
)
bs.emitfx(
position=position,
velocity=velocity,
count=15,
scale=0.6,
chunk_type='slime',
emit_type='stickers',
)
bs.emitfx(
position=position,
velocity=velocity,
count=20,
scale=0.7,
chunk_type='spark',
emit_type='stickers',
)
bs.emitfx(
position=position,
velocity=velocity,
count=int(6.0 + random.random() * 12),
scale=0.8,
spread=1.5,
chunk_type='spark',
)
# It looks better if we delay a bit.
bs.timer(0.05, emit)
elif self.blast_type == 'impact':
def emit() -> None:
bs.emitfx(
position=position,
velocity=velocity,
count=int(4.0 + random.random() * 8),
scale=0.8,
chunk_type='metal',
)
bs.emitfx(
position=position,
velocity=velocity,
count=int(4.0 + random.random() * 8),
scale=0.4,
chunk_type='metal',
)
bs.emitfx(
position=position,
velocity=velocity,
count=20,
scale=0.7,
chunk_type='spark',
emit_type='stickers',
)
bs.emitfx(
position=position,
velocity=velocity,
count=int(8.0 + random.random() * 15),
scale=0.8,
spread=1.5,
chunk_type='spark',
)
# It looks better if we delay a bit.
bs.timer(0.05, emit)
else: # Regular or land mine bomb shrapnel.
def emit() -> None:
if self.blast_type != 'tnt':
bs.emitfx(
position=position,
velocity=velocity,
count=int(4.0 + random.random() * 8),
chunk_type='rock',
)
bs.emitfx(
position=position,
velocity=velocity,
count=int(4.0 + random.random() * 8),
scale=0.5,
chunk_type='rock',
)
bs.emitfx(
position=position,
velocity=velocity,
count=30,
scale=1.0 if self.blast_type == 'tnt' else 0.7,
chunk_type='spark',
emit_type='stickers',
)
bs.emitfx(
position=position,
velocity=velocity,
count=int(18.0 + random.random() * 20),
scale=1.0 if self.blast_type == 'tnt' else 0.8,
spread=1.5,
chunk_type='spark',
)
# TNT throws splintery chunks.
if self.blast_type == 'tnt':
def emit_splinters() -> None:
bs.emitfx(
position=position,
velocity=velocity,
count=int(20.0 + random.random() * 25),
scale=0.8,
spread=1.0,
chunk_type='splinter',
)
bs.timer(0.01, emit_splinters)
# Every now and then do a sparky one.
if self.blast_type == 'tnt' or random.random() < 0.1:
def emit_extra_sparks() -> None:
bs.emitfx(
position=position,
velocity=velocity,
count=int(10.0 + random.random() * 20),
scale=0.8,
spread=1.5,
chunk_type='spark',
)
bs.timer(0.02, emit_extra_sparks)
# It looks better if we delay a bit.
bs.timer(0.05, emit)
lcolor = (0.6, 0.6, 1.0) if self.blast_type == 'ice' else (1, 0.3, 0.1)
light = bs.newnode(
'light',
attrs={
'position': position,
'volume_intensity_scale': 10.0,
'color': lcolor,
},
)
scl = random.uniform(0.6, 0.9)
scorch_radius = light_radius = self.radius
if self.blast_type == 'tnt':
light_radius *= 1.4
scorch_radius *= 1.15
scl *= 3.0
iscale = 1.6
bs.animate(
light,
'intensity',
{
0: 2.0 * iscale,
scl * 0.02: 0.1 * iscale,
scl * 0.025: 0.2 * iscale,
scl * 0.05: 17.0 * iscale,
scl * 0.06: 5.0 * iscale,
scl * 0.08: 4.0 * iscale,
scl * 0.2: 0.6 * iscale,
scl * 2.0: 0.00 * iscale,
scl * 3.0: 0.0,
},
)
bs.animate(
light,
'radius',
{
0: light_radius * 0.2,
scl * 0.05: light_radius * 0.55,
scl * 0.1: light_radius * 0.3,
scl * 0.3: light_radius * 0.15,
scl * 1.0: light_radius * 0.05,
},
)
bs.timer(scl * 3.0, light.delete)
# Make a scorch that fades over time.
scorch = bs.newnode(
'scorch',
attrs={
'position': position,
'size': scorch_radius * 0.5,
'big': (self.blast_type == 'tnt'),
},
)
if self.blast_type == 'ice':
scorch.color = (1, 1, 1.5)
bs.animate(scorch, 'presence', {3.000: 1, 13.000: 0})
bs.timer(13.0, scorch.delete)
if self.blast_type == 'ice':
factory.hiss_sound.play(position=light.position)
lpos = light.position
factory.random_explode_sound().play(position=lpos)
factory.debris_fall_sound.play(position=lpos)
# ↓ We don't need any of this, we're fully trained professionals.
# bs.camerashake(intensity=5.0 if self.blast_type == 'tnt' else 1.0)
# TNT is more epic.
if self.blast_type == 'tnt':
factory.random_explode_sound().play(position=lpos)
def _extra_boom() -> None:
factory.random_explode_sound().play(position=lpos)
bs.timer(0.25, _extra_boom)
def _extra_debris_sound() -> None:
factory.debris_fall_sound.play(position=lpos)
factory.wood_debris_fall_sound.play(position=lpos)
bs.timer(0.4, _extra_debris_sound)
bomb.Blast.__init__ = new__init__
org_handlemessage = bomb.Blast.handlemessage
def new_handlemessage(self, msg: Any, *args, **kwargs) -> Any:
org_handlemessage(self, msg, *args, **kwargs)
if isinstance(msg, ExplodeHitMessage):
node = bs.getcollision().opposingnode
delegate = node.getdelegate(PlayerSpaz)
has_used_camerashake = getattr(self, 'has_used_camerashake', False)
if not has_used_camerashake and (delegate and not delegate.shield) and not node.invincible:
self.has_used_camerashake = True
bs.camerashake(intensity=5.0 if self.blast_type == 'tnt' else 1.0)
bomb.Blast.handlemessage = new_handlemessage
# ba_meta export babase.Plugin
class Plugin(babase.Plugin):
pass

View file

@ -0,0 +1,599 @@
# ba_meta require api 9
# This plugin only works with BombSquad 1.7.49+
from __future__ import annotations
from typing import TYPE_CHECKING, override
import babase
import bascenev1 as bs
import random
import string
import unicodedata
if TYPE_CHECKING:
from typing import Any
plugman = dict(
plugin_name="chat_bubbles",
description="Adds whatever the players say above their character, includes a few other features too!",
external_url="",
authors=[
{"name": "DinoWattz", "email": "", "discord": ""}
],
version="1.0.0",
)
# Prints chat messages to the console as "[Character's Name] Player 1/Player 2: Hello World!"
PRINT_CHAT_MESSAGES = True
BUBBLE_DURATION: float = 4.7 # 4.7 matches the default onscreen message duration
SPEAK_VOLUME = 1.5
SHOUT_VOLUME = 3.0
CELEBRATE_MESSAGE = [(bs.CelebrateMessage, {"duration": 1.0})]
# Easter egg triggers, sounds, and messages flag
# If adding new ones, make sure to test them in-game
EASTER_EGGS = [
{
"triggers": {
"hi", "hello", "hey", "hiya", "yo", "sup", "heya", "howdy",
"greetings", "salutations", "ahoy", "ahoyhoy", "bonjour",
"hola", "ciao", "ola", "oi", "eae", "salve",
"bye", "goodbye", "see ya", "see you", "cya",
"adios", "chau", "chao", "tchau",
"adeus", "ate mais", "falou", "flw"
},
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"aaa"},
"sound": "spazFall01",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"aaaa"},
"sound": "zoeFall01",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"aaaaa"},
"sound": "kronkFall",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"potato"},
"sound": "kronk2",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"dau", "tau", "d'oh", "doh"},
"sound": "kronk3",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"drau", "trau", "d'roh", "droh"},
"sound": "bunny2",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"hahaha", "hahahaha",
"hahahah", "hahahahah", "lol",
"nahahah", "kakaka", "kakakaka", "kkk", "kkkk",
"jajaja", "jajajaja", "jajajajaa"},
"sound": "mel05",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"ha", "hah", "ja", "ka"},
"sound": "mel06",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"merry christmas", "merry krismas", "merry xmas", "feliz natal", "feliz navidad"},
"sound": "santa02",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"hohoho", "ho ho ho", "ho-ho-ho"},
"sound": "santa05",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"gg", "good game", "que pro", "q pro"},
"sound": "achievement",
"messages": CELEBRATE_MESSAGE,
},
{
"triggers": {"boo", "bad game", "que noob", "q noob"},
"sound": "boo",
"messages": CELEBRATE_MESSAGE,
},
# ↓ Node messages are like this ↓
{
"triggers": {"0", "zero"},
"sound": "boxingBell",
"node_messages": [("celebrate_l", 1000)],
},
{
"triggers": {"1", "one"},
"sound": "announceOne",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"2", "two"},
"sound": "announceTwo",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"3", "three"},
"sound": "announceThree",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"4", "four"},
"sound": "announceFour",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"5", "five"},
"sound": "announceFive",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"6", "six"},
"sound": "announceSix",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"7", "seven"},
"sound": "announceSeven",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"8", "eight"},
"sound": "announceEight",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"9", "nine"},
"sound": "announceNine",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"10", "ten"},
"sound": "announceTen",
"node_messages": [("celebrate_r", 1000)],
},
{
"triggers": {"xd"},
"sound": "santaDeath",
"node_messages": [("knockout", 100)],
},
# ↓ Easter eggs without actor/node messages ↓
{
"triggers": {"huh", "huhh", "huhhh", "huhhhh", "humph", "hmph", "hum", "humm"},
"sound": "agent1",
},
{
"triggers": {"hoh", "hohh", "hohhh", "hohhhh", "ohh"},
"sound": "agent3",
},
{
"triggers": {"eff"},
"sound": "zoeEff",
},
{
"triggers": {"ow", "oww", "oow"},
"sound": "zoeOw",
},
{
"triggers": {"pipipi", "bibibi"},
"sound": "mel07",
},
{
"triggers": {"oh yeah", "oh yea", "oohh yeah", "oohh yea"},
"sound": "yeah",
},
{
"triggers": {"woo"},
"sound": "woo2",
},
{
"triggers": {"wooo"},
"sound": "woo",
},
{
"triggers": {"woo yeah", "woo yea"},
"sound": "woo3",
},
{
"triggers": {"ooh"},
"sound": "ooh",
},
{
"triggers": {"wow"},
"sound": "wow",
},
{
"triggers": {"gasp"},
"sound": "gasp",
},
{
"triggers": {"ahh", "aah"},
"sound": "aww",
},
{
"triggers": {"nice"},
"sound": "nice",
},
# ↑ Default easter egg list ends here ↑
]
# Chat bubble functionality
def create_bubble(player: bs.SessionPlayer, text: str, name: str):
msg = normalize_message(text)
is_caps = len(text) > 1 and text.isupper()
handled = False
bubble_created = False
activityplayer: bs.Player | None = getattr(player, 'activityplayer', None)
def _fallback_logic():
nonlocal handled, activityplayer
color = (1, 1, 0.4)
image_icon = {'texture': bs.gettexture("cuteSpaz"), 'tint_texture': bs.gettexture(
"cuteSpaz"), 'tint_color': (1.0, 1.0, 1.0), 'tint2_color': (1.0, 1.0, 1.0)}
if activityplayer:
normal_highlight = bs.normalized_color(
bs.safecolor(player.highlight, target_intensity=1.15))
saturated_highlight = bs.normalized_color(
bs.safecolor(player.highlight, target_intensity=0.75))
color = saturated_highlight if is_caps else normal_highlight
image_icon = activityplayer.get_icon()
bs.broadcastmessage(f"{name}: {text}",
color=color,
top=True,
image=image_icon)
for egg in EASTER_EGGS:
if msg in egg["triggers"]:
if egg.get("sound"):
sound = bs.getsound(egg["sound"])
sound.play(SHOUT_VOLUME if is_caps else SPEAK_VOLUME)
handled = True
break
if not handled and activityplayer:
assert bs.app.classic is not None
handled = True
character = activityplayer.character
char = bs.app.classic.spaz_appearances[character]
if is_caps:
attack_sounds = [bs.getsound(s) for s in char.attack_sounds]
if attack_sounds and isinstance(attack_sounds, (list, tuple)) and len(attack_sounds) > 0:
sound = random.choice(attack_sounds)
sound.play(SHOUT_VOLUME)
else:
pickup_sounds = [bs.getsound(s) for s in char.pickup_sounds]
if pickup_sounds and isinstance(pickup_sounds, (list, tuple)) and len(pickup_sounds) > 0:
sound = random.choice(pickup_sounds)
sound.play(SPEAK_VOLUME)
if activityplayer is None:
# No valid activityplayer: play sound if possible (lobby/non-in-game)
_fallback_logic()
return
bubbles = activityplayer.customdata.setdefault('chat_bubbles', [])
actor_list = []
# Support for both actor and ghost_actor (from the Ghost Players plugin)
if getattr(activityplayer, 'actor', None):
actor_list.append(activityplayer.actor)
if hasattr(activityplayer, 'customdata') and activityplayer.customdata.get('ghost_actor'):
actor_list.append(activityplayer.customdata['ghost_actor'])
if actor_list:
for actor in actor_list:
if actor and actor.is_alive() and actor.node:
assert actor is not None
char_node = actor.node
activity = actor.getactivity()
if activity is None or getattr(activity, 'expired', False):
continue
with activity.context:
# Calculate scale based on text length
base_scale = 0.015
min_scale = 0.009
scale = max(base_scale - 0.0003 * max(len(text) - 20, 0), min_scale)
y_offset = 1.2 + 0.25 * len(bubbles)
mnode = bs.newnode('math', owner=char_node, attrs={
'input1': (0, y_offset, 0),
'operation': 'add'
})
char_node.connectattr('torso_position', mnode, 'input2')
normal_highlight = bs.normalized_color(
bs.safecolor(player.highlight, target_intensity=1.15))
saturated_highlight = bs.normalized_color(
bs.safecolor(player.highlight, target_intensity=0.75))
bubble_color = saturated_highlight if is_caps else normal_highlight
textnode = bs.newnode('text',
owner=char_node,
attrs={
'text': text,
'color': bubble_color,
'shadow': 0.5,
'flatness': 0.5,
'scale': scale,
'h_align': 'center',
'v_align': 'bottom',
'in_world': True,
'opacity': 1.0,
})
mnode.connectattr('output', textnode, 'position')
focus = Plugin.Focus(owner=textnode).autoretain()
textnode.connectattr('position', focus.node, 'position')
# Adjust bubble duration for slow-motion activities
bubble_duration = BUBBLE_DURATION
if hasattr(activity, 'slow_motion') and activity.slow_motion:
bubble_duration = max(0.5, bubble_duration / 3)
# Play sound for all triggers (easter eggs and normal)
def _play_chat_sound(actor, sound, volume):
if actor.is_alive():
actor._safe_play_sound(sound, volume)
else:
sound.play(volume)
# Easter egg sound/message
for egg in EASTER_EGGS:
if msg in egg['triggers']:
# Sound selection logic
sound = None
if egg.get('sound'):
sound = bs.getsound(egg['sound'])
else:
# Default to character sounds if no specific sound is set
if is_caps:
attack_sounds = getattr(char_node, 'attack_sounds', None)
if attack_sounds and isinstance(attack_sounds, (list, tuple)) and len(attack_sounds) > 0:
sound = random.choice(attack_sounds)
else:
pickup_sounds = getattr(char_node, 'pickup_sounds', None)
if pickup_sounds and isinstance(pickup_sounds, (list, tuple)) and len(pickup_sounds) > 0:
sound = random.choice(pickup_sounds)
if sound:
_play_chat_sound(
actor, sound, SHOUT_VOLUME if is_caps else SPEAK_VOLUME)
# Animation logic
if egg.get('messages') and actor.is_alive() and hasattr(actor, 'handlemessage'):
for msg_class, msg_kwargs in egg.get('messages', []):
msg_kwargs = msg_kwargs or {}
actor.handlemessage(msg_class(**msg_kwargs))
if egg.get('node_messages') and actor.is_alive() and actor.node.exists():
for node_args in egg.get('node_messages', []):
actor.node.handlemessage(*node_args)
handled = True
break
if not handled:
if is_caps:
attack_sounds = getattr(char_node, 'attack_sounds', None)
if attack_sounds and isinstance(attack_sounds, (list, tuple)) and len(attack_sounds) > 0:
sound = random.choice(attack_sounds)
_play_chat_sound(actor, sound, SHOUT_VOLUME)
if actor.is_alive() and hasattr(actor, 'handlemessage'):
actor.handlemessage(bs.CelebrateMessage(duration=1.0))
else:
pickup_sounds = getattr(char_node, 'pickup_sounds', None)
if pickup_sounds and isinstance(pickup_sounds, (list, tuple)) and len(pickup_sounds) > 0:
sound = random.choice(pickup_sounds)
_play_chat_sound(actor, sound, SPEAK_VOLUME)
bubbles.append((textnode, mnode))
bubble_created = True
while len(bubbles) > 3:
old_textnode, old_mnode = bubbles.pop(0)
if old_textnode is not None and hasattr(old_textnode, 'exists') and old_textnode.exists():
old_textnode.delete()
if old_mnode is not None and hasattr(old_mnode, 'exists') and old_mnode.exists():
old_mnode.delete()
update_bubble_offsets(bubbles)
fade_time = max(0.1, bubble_duration - 0.5)
bs.animate(textnode, 'opacity', {fade_time: 1.0, bubble_duration: 0.0})
bs.timer(bubble_duration, textnode.delete)
bs.timer(bubble_duration, mnode.delete)
def cleanup():
if hasattr(activityplayer, 'customdata'):
if 'chat_bubbles' in activityplayer.customdata:
activityplayer.customdata['chat_bubbles'] = [
b for b in activityplayer.customdata['chat_bubbles'] if b[0] != textnode
]
bs.timer(bubble_duration, cleanup)
if not bubble_created:
# No valid actor found: play sound if possible
_fallback_logic()
return
def update_bubble_offsets(bubbles):
for i, (tnode, mnode) in enumerate(reversed(bubbles)):
y_offset = 1.2 + 0.25 * i
if mnode is not None and hasattr(mnode, 'exists') and mnode.exists():
mnode.input1 = (0, y_offset, 0)
# Tools
def normalize_message(text):
msg = text.strip().lower()
msg = msg.translate(str.maketrans('', '', string.punctuation))
msg = ''.join(
c for c in unicodedata.normalize('NFD', msg)
if unicodedata.category(c) != 'Mn'
)
return msg
# ba_meta export babase.Plugin
class Plugin(babase.Plugin):
def on_app_running(self) -> None:
# Hook into chat message filtering
# We're delaying until the app runs and using a timer so it's more likely to work with other chat plugins (please don't make a chat plugin/filter that is delayed like this).
bs.apptimer(0.001, bs.WeakCallStrict(self.apply_patch))
def apply_patch(self):
import bascenev1._hooks as _hooks
_org_filter_chat_message = _hooks.filter_chat_message
def _chat_bubbles_hook(msg: str, client_id: int, *args, **kwargs) -> str | None:
org_msg = _org_filter_chat_message(msg, client_id, *args, **kwargs)
if org_msg is None:
# The original message has been ignored, so we should ignore it as well
return org_msg
# Chat bubble sorcery
try:
roster = bs.get_game_roster()
session = bs.get_foreground_host_session()
if session is None:
# Probably couldn't find a session because we are a client
return org_msg
sessionplayers = session.sessionplayers
player_list = []
matched = False
dead_bubble = False
character: str | None = None
with session.context:
for player in sessionplayers:
if player.inputdevice.client_id == client_id and player.in_game:
player_list.append(player)
continue
if player_list:
# Get combined name for multiple players on same client
name = ''
created_bubble = False
for player in player_list:
if name == '':
name = player.getname()
else:
name = name + '/' + player.getname()
# Create chat bubble
for player in player_list:
activityplayer: bs.Player = player.activityplayer
# We have a session player but not an activity player
if not activityplayer:
if not dead_bubble:
create_bubble(player, org_msg, name)
created_bubble = dead_bubble = True
continue
# We have a dead activity player
if not activityplayer.is_alive() and not dead_bubble:
create_bubble(player, org_msg, name)
created_bubble = dead_bubble = True
character = activityplayer.character if character is None else character
# elif we have an alive activity player
elif activityplayer.is_alive():
create_bubble(player, org_msg, name)
created_bubble = True
character = activityplayer.character if character is None else character
if created_bubble:
matched = True
if PRINT_CHAT_MESSAGES:
print(f"[{character}] {name}: {org_msg}")
else:
bs.logging.warning(
"Failed to create a chat bubble, using non-session player method now.")
# Non-session players (in lobby or just spectating, this only works for servers)
if not matched and not dead_bubble:
for client in roster:
if client['client_id'] == client_id:
name = client.get('display_string')
create_bubble(None, org_msg, name)
matched = True
if PRINT_CHAT_MESSAGES:
print(f"[{character}] {name}: {org_msg}")
break
except Exception as e:
bs.logging.exception(f"Error in chat bubble hook: {e}")
return org_msg
_hooks.filter_chat_message = _chat_bubbles_hook
bs.reload_hooks()
print("[ChatBubbles] Plugin initialized.")
class Focus(bs.Actor):
def __init__(
self,
*,
owner: bs.Node | None = None,
):
super().__init__()
self._focusnode_material = bs.Material()
self._focusnode_material.add_actions(
actions=('modify_node_collision', 'collide', False),
)
if getattr(owner, 'owner', None):
assert owner is not None
area_of_interest = False if getattr(
owner.owner, 'is_area_of_interest', False) else True
else:
area_of_interest = False if getattr(owner, 'is_area_of_interest', False) else True
# I'm tired of this area_of_interest nonsense, it never works right
# area_of_interest = False
# actually it might work now that we don't spawn bubbles for dead players.
self.node = bs.newnode(
'prop',
delegate=self,
owner=owner,
attrs={
'body': 'sphere',
'body_scale': 0.0,
'shadow_size': 0.0,
'gravity_scale': 0.0,
'is_area_of_interest': area_of_interest,
'materials': (self._focusnode_material,),
},
)
@override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
if self.node:
self.node.delete()
else:
super().handlemessage(msg)

View file

@ -1,7 +1,6 @@
# ba_meta require api 9
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
@ -14,137 +13,81 @@ if TYPE_CHECKING:
plugman = dict(
plugin_name="custom_hits",
description="when someone hited rainbow text whit effect will appear",
description="when someone hited text whit random effect will appear",
external_url="",
authors=[
{"name": "ATD", "email": "anasdhaoidi001@gmail.com", "discord": ""},
],
version="1.0.0",
version="1.1.0",
)
# ___________________________________________________
####################################
first_damage = (u'\ue042TRY AGAIN?\ue042', (0, 1, 0), '6')
second_damage = (u'\ue048GOOD!\ue048', (0, 1, 1), '2')
third_damage = (u'\ue041NICE!\ue041', (0.8, 0.4, 1), '3')
fourth_damage = (u'\ue049UPPS\ue049', (1, 1, 0), '4')
five_damage = (u'\ue043DEATH!\ue043', (1, 0, 0), '5')
bs.broadcastmessage(u'\ue043 || \ue062WELCOME ATD\ue062 || \ue043', color=(0, 1, 0))
first_damage = (u'NAH NOOB?', (1, 1, 1), '6')
second_damage = (u'GOOD!', (0, 1, 0), '2')
third_damage = (u'DAMN!', (0, 0, 0), '3')
fourth_damage = (u'SRY NOOB', (1, 1, 0), '4')
five_damage = (u'FAH!!!', (1, 0, 0), '5')
def custom_effects(pos: float, effect: str = None) -> None:
if effect == '1':
bs.emitfx(
position=pos,
count=3,
scale=0.1,
spread=0.1,
chunk_type='rock')
bs.emitfx(position=pos, count=3, scale=0.1, spread=0.1, chunk_type='rock')
elif effect == '2':
bs.emitfx(
position=pos,
count=70,
scale=2,
spread=0.8,
chunk_type='spark')
bs.emitfx(position=pos, count=70, scale=2, spread=0.8, chunk_type='spark')
elif effect == '3':
bs.emitfx(
position=pos,
count=6,
scale=0.3,
spread=0.4,
chunk_type='splinter')
bs.emitfx(position=pos, count=6, scale=0.3, spread=0.4, chunk_type='splinter')
elif effect == '4':
bs.emitfx(
position=pos,
count=50,
scale=3.0,
spread=0.9,
chunk_type='ice')
bs.emitfx(position=pos, count=50, scale=3.0, spread=0.9, chunk_type='ice')
elif effect == '5':
bs.emitfx(
position=pos,
count=80,
scale=3.0,
spread=1.5,
chunk_type='metal')
bs.emitfx(position=pos, count=80, scale=3.0, spread=1.5, chunk_type='metal')
elif effect == '6':
bs.emitfx(
position=pos,
count=30,
scale=1.2,
spread=0.8,
chunk_type='slime')
bs.emitfx(position=pos, count=30, scale=1.2, spread=0.8, chunk_type='slime')
def on_punched(self, damage: int) -> None:
pos = self.node.position
def custom_text(msg: str, color: float) -> None:
text = bs.newnode('text', attrs={
'text': msg,
'color': color,
'in_world': True,
'h_align': 'center',
'shadow': 0.5,
'flatness': 1.0,
})
bs.animate_array(text, 'position', 3, {
0.0: (pos[0], pos[1] + 1.2, pos[2]),
2.0: (pos[0], pos[1] + 1.7, pos[2]),
})
bs.animate(text, 'opacity', {0.8: 1.0, 2.0: 0.0})
bs.animate(text, 'scale', {0: 0, 0.1: 0.017, 0.15: 0.014, 2.0: 0.016})
bs.timer(2.0, text.delete)
if damage < 200:
custom_text(first_damage[0], first_damage[1])
custom_effects(pos, first_damage[2])
elif damage < 500:
custom_text(second_damage[0], second_damage[1])
custom_effects(pos, second_damage[2])
elif damage < 800:
custom_text(third_damage[0], third_damage[1])
custom_effects(pos, third_damage[2])
elif damage < 1000:
custom_text(fourth_damage[0], fourth_damage[1])
custom_effects(pos, fourth_damage[2])
else:
custom_text(five_damage[0], five_damage[1])
custom_effects(pos, five_damage[2])
####################################
# ba_meta export babase.Plugin
class CustomHitsPlugin(babase.Plugin):
def on_punched(self, damage: int) -> None:
pos = self.node.position
def custom_text(msg: str, color: float) -> None:
text = bs.newnode(
'text',
attrs={
'text': msg,
'color': color,
'in_world': True,
'h_align': 'center',
'shadow': 0.5,
'flatness': 1.0})
bs.animate_array(text, 'position', 3, {
0.0: (pos[0], pos[1] + 1.2, pos[2]),
2.0: (pos[0], pos[1] + 1.7, pos[2])
})
bs.animate(text, 'opacity', {
0.8: 1.0,
2.0: 0.0
})
bs.animate(text, 'scale', {
0: 0,
0.1: 0.017,
0.15: 0.014,
2.0: 0.016
})
bs.animate_array(text, 'color', 3, {
0.0: (0, 0, 0), # Black
0.2: (2.55, 2.55, 2.55), # White
0.4: (2, 0, 0), # Red
0.6: (0, 2.55, 0), # Lime
0.8: (0, 0, 2.55), # Blue
1.0: (2.55, 2.55, 0), # Yellow
1.2: (0, 2.55, 2.55), # Cyan / Aqua
1.4: (2.55, 0, 2.55), # Magenta / Fuchsia
1.6: (1.92, 1.92, 1.92), # Silver
1.8: (1.28, 1.28, 1.28), # Gray
2.0: (1.28, 0, 0), # Maroon
2.2: (1.28, 1.28, 0), # Olive
2.4: (0, 1.28, 0), # Green
2.6: (1.28, 0, 1.28), # Purple
2.8: (0, 1.28, 1.28), # Teal
3.0: (0, 0, 1.28), # Navy
3.2: (1.5, 0.5, 0), # Orange
3.4: (1.8, 0.5, 1.6), # Pink
3.6: (0.5, 0.5, 0.5), # Gray
}, loop=True)
bs.timer(2.0, text.delete)
if damage < 200:
custom_text(first_damage[0], first_damage[1])
custom_effects(pos, first_damage[2])
elif damage < 500:
custom_text(second_damage[0], second_damage[1])
custom_effects(pos, second_damage[2])
elif damage < 800:
custom_text(third_damage[0], third_damage[1])
custom_effects(pos, third_damage[2])
elif damage < 1000:
custom_text(fourth_damage[0], fourth_damage[1])
custom_effects(pos, fourth_damage[2])
else:
custom_text(five_damage[0], five_damage[1])
custom_effects(pos, five_damage[2])
class byATD(babase.Plugin):
"""تفعيل ATD Hits — يُستدعى من bootstraping()."""
Spaz.on_punched = on_punched
try:
from bascenev1lib.actor.spazbot import SpazBot
SpazBot.on_punched = on_punched
except Exception:
pass
bs.broadcastmessage(u' || WELCOME || ', color=(0, 1, 0))

View file

@ -0,0 +1,305 @@
# ba_meta require api 9
from __future__ import annotations
from typing import TYPE_CHECKING
import random
import weakref
import babase
import bascenev1 as bs
from bascenev1lib.actor.background import Background
if TYPE_CHECKING:
from typing import Any
plugman = dict(
plugin_name="enhanced_effects",
description="Explosions affect character colors, slow-mo tnt, fair colored shields, a trippy background screen and maybe more in the future!",
external_url="",
authors=[
{"name": "DinoWattz", "email": "", "discord": ""}
],
version="1.0.0",
)
# Transparent Background (kinda hacky, works best on pc with high/higher quality 'visuals' setting)
BACKGROUND_OPACITY = 0.5
def __modified_background_init(
self,
fade_time: float = 0.5,
start_faded: bool = False,
show_logo: bool = False,
):
super(type(self), self).__init__()
self._dying = False
self.fade_time = fade_time
# We're special in that we create our node in the session
# scene instead of the activity scene.
# This way we can overlap multiple activities for fades
# and whatnot.
session = bs.getsession()
self._session = weakref.ref(session)
with session.context:
self.node = bs.newnode(
'image',
delegate=self,
attrs={
'fill_screen': True,
'texture': bs.gettexture('bg'),
'tilt_translate': -0.3,
'has_alpha_channel': False,
'color': (1, 1, 1),
'opacity': BACKGROUND_OPACITY,
},
)
if not start_faded:
bs.animate(
self.node,
'opacity',
{0.0: 0.0, self.fade_time: BACKGROUND_OPACITY},
loop=False,
)
if show_logo:
logo_texture = bs.gettexture('logo')
logo_mesh = bs.getmesh('logo')
logo_mesh_transparent = bs.getmesh('logoTransparent')
self.logo = bs.newnode(
'image',
owner=self.node,
attrs={
'texture': logo_texture,
'mesh_opaque': logo_mesh,
'mesh_transparent': logo_mesh_transparent,
'scale': (0.7, 0.7),
'vr_depth': -250,
'color': (0.15, 0.15, 0.15),
'position': (0, 0),
'tilt_translate': -0.05,
'absolute_scale': False,
},
)
self.node.connectattr('opacity', self.logo, 'opacity')
# add jitter/pulse for a stop-motion-y look unless we're in VR
# in which case stillness is better
if not bs.app.env.vr:
self.cmb = bs.newnode(
'combine', owner=self.node, attrs={'size': 2}
)
for attr in ['input0', 'input1']:
bs.animate(
self.cmb,
attr,
{0.0: 0.693, 0.05: 0.7, 0.5: 0.693},
loop=True,
)
self.cmb.connectattr('output', self.logo, 'scale')
cmb = bs.newnode(
'combine', owner=self.node, attrs={'size': 2}
)
cmb.connectattr('output', self.logo, 'position')
# Gen some random keys for that stop-motion-y look.
keys = {}
timeval = 0.0
for _i in range(10):
keys[timeval] = (random.random() - 0.5) * 0.0015
timeval += random.random() * 0.1
bs.animate(cmb, 'input0', keys, loop=True)
keys = {}
timeval = 0.0
for _i in range(10):
keys[timeval] = (random.random() - 0.5) * 0.0015 + 0.05
timeval += random.random() * 0.1
bs.animate(cmb, 'input1', keys, loop=True)
def _modified_background_die(self, immediate: bool = False) -> None:
session = self._session()
if session is None and self.node:
# If session is gone, our node should be too,
# since it was part of the session's scene.
# Let's make sure that's the case.
# (since otherwise we have no way to kill it)
bs.logging.exception(
'got None session on Background _die'
' (and node still exists!)'
)
elif session is not None:
with session.context:
if not self._dying and self.node:
self._dying = True
if immediate:
self.node.delete()
else:
bs.animate(
self.node,
'opacity',
{0.0: self.node.opacity, self.fade_time: 0.0},
loop=False,
)
bs.timer(self.fade_time + 0.1, self.node.delete)
Background.__init__ = __modified_background_init
Background._die = _modified_background_die
# Tools
def blend_toward(
rgb: tuple[float, float, float],
target: tuple[float, float, float],
amount: float = 1.0,
perceptual: bool = False
) -> tuple[float, ...]:
"""
Blend 'rgb' toward 'target' by 'amount' (0..1).
If perceptual=True, blend in linear-light space for smoother, more
natural-looking fades.
"""
def clamp01(x: float) -> float:
return max(0.0, min(1.0, x))
def srgb_to_linear(c: float) -> float:
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
def linear_to_srgb(c: float) -> float:
return 12.92 * c if c <= 0.0031308 else 1.055 * (c ** (1 / 2.4)) - 0.055
a = clamp01(amount)
if not perceptual:
return tuple(clamp01(c + (t - c) * a) for c, t in zip(rgb, target))
# Gamma-aware blend
rgb_lin = [srgb_to_linear(clamp01(c)) for c in rgb]
target_lin = [srgb_to_linear(clamp01(c)) for c in target]
blended_lin = [
c + (t - c) * a for c, t in zip(rgb_lin, target_lin)
]
return tuple(clamp01(linear_to_srgb(c)) for c in blended_lin)
# ba_meta export babase.Plugin
class Plugin(babase.Plugin):
def on_app_running(self) -> None:
from bascenev1lib.actor.bomb import Blast, ExplodeHitMessage
from bascenev1lib.actor.spaz import Spaz
from bascenev1lib.actor.spazbot import SpazBot
from bascenev1lib.actor.playerspaz import PlayerSpaz
# Colored Shields
org_equip_shields = Spaz.equip_shields
def equip_shields(self, decay: bool = False) -> None:
org_equip_shields(self, decay)
if self.shield is not None:
safe_highlight = bs.safecolor(
getattr(self, '_original_highlight', self.node.highlight), target_intensity=0.75)
self.shield.color = bs.normalized_color(safe_highlight)
Spaz.equip_shields = equip_shields
# Slow Motion TNT Explosion
org_blast_init = Blast.__init__
def new_init(self: Blast, blast_type='normal', hit_type='explosion', *args, **kwargs) -> None:
org_blast_init(self, blast_type=blast_type, hit_type=hit_type, *args, **kwargs)
if blast_type == 'tnt' and hit_type == 'explosion':
bs.camerashake(3.0)
activity = bs.getactivity()
gnode = activity.globalsnode
# Pause
gnode.paused = True
self._pause_timer = bs.DisplayTimer(
0.01, bs.CallPartial(setattr, gnode, 'paused', True), True)
delay = 0.06
bs.displaytimer(delay, bs.CallPartial(setattr, self, '_pause_timer', None))
bs.displaytimer(delay, bs.CallPartial(setattr, gnode, 'paused', False))
# Slow Motion
gnode.slow_motion = True
self._slow_motion_timer = bs.DisplayTimer(
0.01, bs.CallPartial(setattr, gnode, 'slow_motion', True), True)
delay += 0.14
bs.displaytimer(delay, bs.CallPartial(setattr, self, '_slow_motion_timer', None))
bs.displaytimer(delay, bs.CallPartial(
setattr, gnode, 'slow_motion', activity.slow_motion))
Blast.__init__ = new_init
# Explosion Spaz Coloring
org_handlemessage = Blast.handlemessage
def new_handlemessage(self: Blast, msg: Any, *args, **kwargs) -> Any:
org_handlemessage(self, msg, *args, **kwargs)
if isinstance(msg, ExplodeHitMessage):
node = bs.getcollision().opposingnode
delegate = node.getdelegate(PlayerSpaz) or node.getdelegate(SpazBot)
if (delegate and not delegate.shield) and not node.invincible:
if not hasattr(delegate, '_original_color'):
delegate._original_color = node.color
delegate._original_highlight = node.highlight
original_color = delegate._original_color
original_highlight = delegate._original_highlight
blend_time = 1.0
start_time = 6.0
end_time = 7.0
bs.emitfx(
position=node.position,
velocity=node.velocity,
count=int(4.0 + random.random() * 4),
emit_type='tendrils',
tendril_type='ice' if self.blast_type == 'ice' else 'smoke',
)
explosion_intensity = 0.70 if not self.blast_type == 'tnt' else 0.96
if self.blast_type == 'ice':
explosion_intensity = 0.91
explosion_color = (0.07, 0.6, 1.5)
elif self.blast_type == 'sticky':
explosion_intensity = 0.91
explosion_color = (0.07, 0.9, 0.07)
blend_time = 1.0
start_time = 1.2
end_time = 3.0
else:
explosion_color = (0.07, 0.03, 0.0)
blend_color = blend_toward(node.color, explosion_color,
explosion_intensity, True)
blend_highlight = blend_toward(
node.highlight, explosion_color, explosion_intensity-0.03, True)
bs.animate_array(node, 'color', 3, {
blend_time: blend_color,
start_time: blend_color,
end_time: original_color
})
bs.animate_array(node, 'highlight', 3, {
blend_time: blend_highlight,
start_time: blend_highlight,
end_time: original_highlight
})
delegate._color_timer = bs.Timer(
end_time, bs.CallPartial(delattr, delegate, '_original_color'))
delegate._highlight_timer = bs.Timer(
end_time, bs.CallPartial(delattr, delegate, '_original_highlight'))
Blast.handlemessage = new_handlemessage

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,222 @@
# ba_meta require api 9
from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import _babase
import bascenev1 as bs
from types import FunctionType
import enum
if TYPE_CHECKING:
from typing import Type, Callable, Any, Tuple
plugman = dict(
plugin_name="glowing_profiles",
description="This plugin gives your profile glowlight, just like on some servers, but only offline",
external_url="https://m.youtube.com/watch?v=Jb_dKz99rhY",
authors=[
{"name": "andrejkuroglo8", "email": "andrejkuroglo8@gmail.com", "discord": "andrewku"},
],
version="1.0.0",
)
def redefine_method(dst: Tuple[Any, str], src: Tuple[Any, str]) -> None:
if hasattr(getattr(*src), '__redefine_type') and getattr(*src).__redefine_type in (
RedefineFlag.DECORATE_PRE, RedefineFlag.DECORATE_AFTER, RedefineFlag.DECORATE_ADVANCED):
new = getattr(*src)
old = getattr(*dst)
func: Callable
if getattr(*src).__redefine_type == RedefineFlag.DECORATE_AFTER:
def func(*args, **kwargs):
returned = old(*args, **kwargs)
return new(*args, **kwargs, returned=returned)
elif getattr(*src).__redefine_type == RedefineFlag.DECORATE_PRE:
def func(*args, **kwargs):
new(*args, **kwargs)
return old(*args, **kwargs)
else:
def func(*args, **kwargs):
return new(*args, **kwargs, old_function=old)
setattr(*dst, func)
else:
setattr(*dst, getattr(*src))
# Fucking super()!
# dst.__code__ = CodeType(
# src.__code__.co_argcount,
# src.__code__.co_posonlyargcount,
# src.__code__.co_kwonlyargcount,
# src.__code__.co_nlocals,
# src.__code__.co_stacksize,
# src.__code__.co_flags,
# src.__code__.co_code,
# src.__code__.co_consts,
# src.__code__.co_names,
# src.__code__.co_varnames,
# src.__code__.co_filename,
# dst.__code__.co_name,
# src.__code__.co_firstlineno,
# src.__code__.co_lnotab,
# dst.__code__.co_freevars,
# dst.__code__.co_cellvars)
def redefine_class_methods(orig_cls: Type[object]) -> Callable[[Any], None]:
"""Returns decorator that redefines all class methods
Parameters:
orig_cls (Type[object]): class that will redefined"""
def decorator(cls) -> None:
# for method in filter(lambda x: isinstance(getattr(cls, x), FunctionType), dir(cls)):
for method in cls._redefine_methods:
if hasattr(orig_cls, method):
redefine_method((orig_cls, method), (cls, method))
else:
setattr(orig_cls, method, getattr(cls, method))
# setattr(getattr(orig_cls, 'self'), '__class__', getattr(cls, method)) # Fucking super()!!!!
return decorator
class RedefineFlag(enum.Enum):
REDEFINE = 0
DECORATE_AFTER = 1
DECORATE_PRE = 2
DECORATE_ADVANCED = 3
DECORATE = DECORATE_AFTER
def redefine_flag(*flags: RedefineFlag) -> Callable[[Callable], Callable]:
def decorator(func: Callable) -> Callable:
for flag in flags:
if flag in (RedefineFlag.DECORATE_AFTER, RedefineFlag.REDEFINE, RedefineFlag.DECORATE_PRE,
RedefineFlag.DECORATE_ADVANCED):
func.__redefine_type = flag
return func
return decorator
def get_locale(*args):
return "Error"
@redefine_class_methods(bs.Chooser)
class Chooser:
_redefine_methods = ('_gcinit', '_get_glowing_colors', 'update_from_profile',
'_getname')
def _gcinit(self):
if hasattr(self, '_gcinit_done'):
return
self.glow_dict = {}
self._markers = ('"', "'", '^', '%', ';', '`')
self._get_glowing_colors()
self._gcinit_done = True
@redefine_flag(RedefineFlag.REDEFINE)
def _get_glowing_colors(self):
"""Search glowing code among profiles."""
try:
should_del = []
for i in self._profilenames:
for m in self._markers:
if i.startswith(m + ','):
code = i.split(',')
self.glow_dict[code[0]] = (
float(code[1]),
float(code[2]),
int(code[3]),
int(code[4]))
# should_del.append(i)
for i in should_del:
self._profilenames.remove(i)
except Exception as err:
print(err)
ba.screenmessage(
get_locale('init_glowing_code_error'),
color=(1, 0, 0),
clients=[self._player.get_input_device().client_id],
transient=True)
@redefine_flag(RedefineFlag.DECORATE_ADVANCED)
def _getname(self, full=True, old_function=None):
name = old_function(self, full)
for m in self._markers:
name = name.replace(m, '')
return name
@redefine_flag(RedefineFlag.DECORATE_ADVANCED)
def update_from_profile(self, old_function):
self._gcinit()
from bascenev1 import _profile
try:
self._profilename = self._profilenames[self._profileindex]
character = self._profiles[self._profilename]['character']
if self._profilename[0] in self.glow_dict:
if (character not in self._character_names
and character in _ba.app.spaz_appearances):
self._character_names.append(character)
self._character_index = self._character_names.index(character)
player_glowing_dict = self.glow_dict[self._profilename[0]]
color_marker = player_glowing_dict[0]
color_marker = max(-999.0, min(color_marker, 50.0))
highlight_marker = float(player_glowing_dict[1])
highlight_marker = max(-999.0, min(highlight_marker, 50.0))
stabilize_color = int(player_glowing_dict[2]) > 0
stabilize_highlight = int(player_glowing_dict[3]) > 0
self._color, self._highlight = \
_profile.get_player_profile_colors(
self._profilename,
profiles=self._profiles)
if stabilize_color:
m = max(self._color)
self._color = list(self._color)
for i in (0, 1, 2):
if self._color[i] == m:
self._color[i] = self._color[i] * color_marker
self._color = tuple(self._color)
else:
self._color = (
self._color[0] * color_marker,
self._color[1] * color_marker,
self._color[2] * color_marker)
if not stabilize_highlight:
self._highlight = (
self._highlight[0] * highlight_marker,
self._highlight[1] * highlight_marker,
self._highlight[2] * highlight_marker)
else:
m = max(self._highlight)
self._highlight = list(self._highlight)
for i in (0, 1, 2):
if self._highlight[i] == m:
self._highlight[i] = \
self._highlight[i] * highlight_marker
self._highlight = tuple(self._highlight)
else:
old_function(self)
except KeyError:
self.character_index = self._random_character_index
self._color = self._random_color
self._highlight = self._random_highlight
self._update_icon()
self._update_text()
# ba_meta export babase.Plugin
class Glowing(babase.Plugin):
pass

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
# ba_meta require api 9
import os
import json
import babase
import bascenev1 as bs
plugman = dict(
plugin_name="rank_system",
description="ranks system for servers or for local players",
external_url="",
authors=[
{"name": "ATD", "email": "anasdhaoidi001@gmail.com", "discord": ""},
],
version="1.0.0",
)
MODS_DIR = os.path.dirname(__file__)
STATS_DIR = os.path.join(MODS_DIR, "stats")
STATS_FILE = os.path.join(STATS_DIR, "ranks.json")
class RankSystem:
def __init__(self):
self.data = {}
self.load()
def load(self):
if not os.path.exists(STATS_DIR):
os.makedirs(STATS_DIR)
if os.path.exists(STATS_FILE):
try:
with open(STATS_FILE, "r") as f:
self.data = json.load(f)
except:
self.data = {}
def save(self):
with open(STATS_FILE, "w") as f:
json.dump(self.data, f, indent=4)
def add_score(self, account_id, score=1):
if account_id not in self.data:
self.data[account_id] = {
"score": 0,
"rank": 0
}
self.data[account_id]["score"] += score
self.update_ranks()
self.save()
def update_ranks(self):
sorted_players = sorted(
self.data.items(),
key=lambda x: x[1]["score"],
reverse=True
)
for i, (aid, p) in enumerate(sorted_players):
p["rank"] = i + 1
def get_rank(self, account_id):
return self.data.get(account_id, {}).get("rank")
rank_sys = RankSystem()
class RankTag:
def __init__(self, node, rank):
m = bs.newnode(
"math",
owner=node,
attrs={
"input1": (0, 1.2, 0),
"operation": "add"
}
)
node.connectattr("torso_position", m, "input2")
if rank == 1:
text = "1"
color = (1, 1, 1)
elif rank == 2:
text = "2"
color = (1, 1, 1)
elif rank == 3:
text = "3"
color = (1, 1, 1)
else:
text = f"#{rank}"
color = (1, 1, 1)
t = bs.newnode(
"text",
owner=node,
attrs={
"text": text,
"in_world": True,
"color": color,
"scale": 0.01,
"h_align": "center"
}
)
m.connectattr("output", t, "position")
# 🔌 plugin
# ba_meta export babase.Plugin
class byATD(babase.Plugin):
def on_app_running(self):
print("Rank System Loaded ")
import bascenev1._gameactivity as ga
old_spawn = ga.GameActivity.spawn_player_spaz
def new_spawn(self, player, *args, **kwargs):
spaz = old_spawn(self, player, *args, **kwargs)
try:
aid = player.sessionplayer.get_account_id()
rank_sys.add_score(aid, 1)
rank = rank_sys.get_rank(aid)
if rank:
RankTag(spaz.node, rank)
except Exception as e:
print("Rank error:", e)
return spaz
ga.GameActivity.spawn_player_spaz = new_spawn

View file

@ -0,0 +1,129 @@
# ba_meta require api 9
from __future__ import annotations
import babase
import bascenev1 as bs
from bascenev1lib.actor.spaz import Spaz
plugman = dict(
plugin_name="sleep_on_afk",
description="Staying idle for 40 seconds will make your character fall asleep, they need rest too..",
external_url="",
authors=[
{"name": "DinoWattz", "email": "", "discord": ""}
],
version="1.0.0",
)
INGAME_TIME = 40 # (in seconds)
# Spaz Changes
def _afk_sleep_knockout(self, value: float) -> None:
if not self.node:
return
if not self.is_alive() and hasattr(self.getplayer(self), 'sessionplayer'):
current_activity = self.getactivity()
player = self.getplayer(self).sessionplayer
# Pop timer data if the player is dead
if current_activity.customdata.get(str(player.id) + '_knockoutTimer') is not None:
current_activity.customdata.pop(str(player.id) + '_knockoutTimer')
return
self.node.handlemessage('knockout', value)
Spaz._afk_sleep_knockout = _afk_sleep_knockout # type: ignore
# Idle Checker
def idle_start(activity: bs.Activity):
activity.customdata['afk_timer'] = bs.Timer(
0.5, bs.CallStrict(idle_check, activity), repeat=True)
def idle_check(current_activity: bs.Activity):
current_session = current_activity.session
wait_time = INGAME_TIME if not current_activity.slow_motion else round(INGAME_TIME / 3)
current = bs.time() * 1000
if not current_session:
return
for player in current_session.sessionplayers:
if (
not player.exists()
or not player.in_game
or not getattr(player, 'activityplayer', None)
or not getattr(player.activityplayer, 'actor', None)
or not getattr(player.activityplayer.actor, 'node', None)
or not player.activityplayer.is_alive()
or not player.activityplayer.actor.node
or not player.activityplayer.actor.node.exists()
and not getattr(player.activityplayer.actor.node, 'invincible', False)
):
continue
player_actor = player.activityplayer.actor
player_node = player_actor.node
player_data = player.activityplayer.customdata
player_turbo_times = player_actor._turbo_filter_times
if player_node.move_up_down != 0.0 or player_node.move_left_right != 0.0:
player_data['last_input'] = current
elif player_turbo_times:
highest_turbo_time = player_turbo_times.get(
max(player_turbo_times, key=player_turbo_times.get))
if highest_turbo_time > player_data.get('last_input', current):
player_data.update({'last_input': highest_turbo_time})
player_data['last_input'] = player_data.get('last_input', current)
last_input = max(0, player_data['last_input'])
afk_time = int((current - last_input) / 1000)
# print(player_data)
# print(last_input)
# print(current)
# print(player.getname() + ": " + str(afk_time))
# print(wait_time)
if afk_time >= wait_time:
current_activity.customdata[str(player.id) + '_knockoutTimer'] = bs.Timer(
0.1, bs.WeakCallStrict(player_actor._afk_sleep_knockout, 100.0), repeat=True)
# Make the player's node not an area of interest if it was one
if not getattr(player_actor, '_previous_is_area_of_interest', False):
player_actor._previous_is_area_of_interest = player_node.is_area_of_interest
if player_actor._previous_is_area_of_interest:
player_node.is_area_of_interest = False
elif current_activity.customdata.get(str(player.id) + '_knockoutTimer') is not None:
current_activity.customdata.pop(str(player.id) + '_knockoutTimer')
# Restore the player's node area of interest if necessary
if getattr(player_actor, '_previous_is_area_of_interest', False):
player_node.is_area_of_interest = True
if hasattr(player_actor, "_previous_is_area_of_interest"):
delattr(player_actor, "_previous_is_area_of_interest")
# Setup new activity
org_on_begin = bs.Activity.on_begin
def patched_on_begin(self, *args, **kwargs):
idle_start(self)
return org_on_begin(self, *args, **kwargs)
bs.Activity.on_begin = patched_on_begin
# ba_meta export babase.Plugin
class Plugin(babase.Plugin):
pass