mirror of
https://github.com/bombsquad-community/plugin-manager.git
synced 2026-08-15 13:03:08 +00:00
Compare commits
80 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d442c44ea8 | ||
|
|
7acd7a87ab | ||
|
|
a1baa5fca2 | ||
|
|
c2c2a04891 | ||
|
|
b6ccdb12ef | ||
|
|
f1c02ae85a | ||
|
|
5d7d22245b | ||
|
|
b8dee8f224 | ||
|
|
710dbebee4 | ||
|
|
643ac51a08 | ||
|
|
d0df37d764 | ||
|
|
3adfe38e55 | ||
|
|
2660a8d973 | ||
|
|
767fbe3a28 | ||
|
|
18dbcbbe3f | ||
|
|
0749053077 | ||
|
|
4804a55ca0 | ||
|
|
2e2ff3656a | ||
|
|
55c3fe5af4 | ||
|
|
7fcb9b4468 | ||
|
|
edaed70235 | ||
|
|
e091fb6983 | ||
|
|
d88d0e149d | ||
|
|
7e249c1181 | ||
|
|
99cf837498 | ||
|
|
1662980eea | ||
|
|
929ce3f26f | ||
|
|
d35b78326b | ||
|
|
a6d11fd8ce | ||
|
|
d517d47f3e | ||
|
|
811e151c81 | ||
|
|
a1b809bc80 | ||
|
|
7c718cf4a3 | ||
|
|
8fba8c8ab5 | ||
|
|
f6c0fd5fbe | ||
|
|
e9e1a29fea | ||
|
|
576a9c0304 | ||
|
|
9a74e81ec7 | ||
|
|
d0e936c582 | ||
|
|
e6d9c5d71c | ||
|
|
3a622edb51 | ||
|
|
23fbdf5e9d | ||
|
|
6a494a44fc | ||
|
|
7c5038263c | ||
|
|
bbf36dc4a9 | ||
|
|
6212108315 | ||
|
|
05c3f1d023 | ||
|
|
e131859fe6 | ||
|
|
023b733c3b | ||
|
|
6c8e898913 | ||
|
|
3876b25f47 | ||
|
|
06d11887a4 | ||
|
|
120a09f380 | ||
|
|
bbd1fe6632 | ||
|
|
1c728633ea | ||
|
|
de9ca9ddb2 | ||
|
|
e6c5130186 | ||
|
|
864e4efabf | ||
|
|
0417dc09e0 | ||
|
|
c0527f3635 | ||
|
|
a8999bd8e1 | ||
|
|
7a6d6e301f | ||
|
|
6312b16f49 | ||
|
|
3aa666ff65 | ||
|
|
8864fc650e | ||
|
|
564357affd | ||
|
|
f74738487f | ||
|
|
5ebbadb946 | ||
|
|
8fcb89492a | ||
|
|
0288823e6c | ||
|
|
16cb91a80b | ||
|
|
4f0cdaceff | ||
|
|
7a6fa42e0a | ||
|
|
56ad1aa843 | ||
|
|
38f9e35e53 | ||
|
|
0c22ef23b0 | ||
|
|
4baad2b813 | ||
|
|
963f7dfa87 | ||
|
|
e67d891716 | ||
|
|
ebbd198628 |
17 changed files with 8929 additions and 3142 deletions
|
|
@ -1,5 +1,13 @@
|
||||||
## Plugin Manager (dd-mm-yyyy)
|
## 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)
|
### 1.1.8 (23-02-2026)
|
||||||
|
|
||||||
- Fix for bs 1.7.61 build no 22714
|
- Fix for bs 1.7.61 build no 22714
|
||||||
|
|
|
||||||
12
index.json
12
index.json
|
|
@ -1,6 +1,18 @@
|
||||||
{
|
{
|
||||||
"plugin_manager_url": "https://github.com/bombsquad-community/plugin-manager/{content_type}/{tag}/plugin_manager.py",
|
"plugin_manager_url": "https://github.com/bombsquad-community/plugin-manager/{content_type}/{tag}/plugin_manager.py",
|
||||||
"versions": {
|
"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": {
|
"1.1.8": {
|
||||||
"api_version": 9,
|
"api_version": 9,
|
||||||
"commit_sha": "8222081",
|
"commit_sha": "8222081",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
import babase
|
import babase
|
||||||
import bauiv1 as bui
|
import bauiv1 as bui
|
||||||
from bauiv1lib import popup, confirm
|
from bauiv1lib import popup, confirm
|
||||||
from babase._meta import EXPORT_CLASS_NAME_SHORTCUTS
|
|
||||||
from bauiv1lib.settings.allsettings import AllSettingsWindow
|
from bauiv1lib.settings.allsettings import AllSettingsWindow
|
||||||
|
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
@ -26,7 +25,7 @@ from datetime import datetime
|
||||||
# Modules used for overriding AllSettingsWindow
|
# Modules used for overriding AllSettingsWindow
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
PLUGIN_MANAGER_VERSION = "1.1.8"
|
PLUGIN_MANAGER_VERSION = "1.1.10"
|
||||||
REPOSITORY_URL = "https://github.com/bombsquad-community/plugin-manager"
|
REPOSITORY_URL = "https://github.com/bombsquad-community/plugin-manager"
|
||||||
# Current tag can be changed to "staging" or any other branch in
|
# Current tag can be changed to "staging" or any other branch in
|
||||||
# plugin manager repo for testing purpose.
|
# plugin manager repo for testing purpose.
|
||||||
|
|
@ -82,7 +81,7 @@ REGEXP = {
|
||||||
"plugin_entry_points": re.compile(
|
"plugin_entry_points": re.compile(
|
||||||
bytes(
|
bytes(
|
||||||
"(ba_meta export (plugin|{})\n+class )(.*)\\(".format(
|
"(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"
|
"utf-8"
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1663,6 +1663,61 @@
|
||||||
"md5sum": "33e6b93420cf6115e992aca534d76bfd"
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
414
plugins/minigames/vanishing_tiles.py
Normal file
414
plugins/minigames/vanishing_tiles.py
Normal 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
|
||||||
1293
plugins/minigames/volley_punch.py
Normal file
1293
plugins/minigames/volley_punch.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1054,6 +1054,18 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": {
|
"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": {
|
"2.1.5": {
|
||||||
"api_version": 9,
|
"api_version": 9,
|
||||||
"commit_sha": "a36e3c2",
|
"commit_sha": "a36e3c2",
|
||||||
|
|
@ -2375,6 +2387,12 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": {
|
"versions": {
|
||||||
|
"1.0.1": {
|
||||||
|
"api_version": 9,
|
||||||
|
"commit_sha": "023b733",
|
||||||
|
"released_on": "01-05-2026",
|
||||||
|
"md5sum": "045eb589b2969398f7fcb171216303d1"
|
||||||
|
},
|
||||||
"1.0.0": {
|
"1.0.0": {
|
||||||
"api_version": 9,
|
"api_version": 9,
|
||||||
"commit_sha": "f38a7ac",
|
"commit_sha": "f38a7ac",
|
||||||
|
|
@ -2384,7 +2402,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"custom_hits": {
|
"custom_hits": {
|
||||||
"description": "when someone hited rainbow text whit effect will appear",
|
"description": "when someone hited text whit random effect will appear",
|
||||||
"external_url": "",
|
"external_url": "",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
|
@ -2394,6 +2412,12 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": {
|
"versions": {
|
||||||
|
"1.1.0": {
|
||||||
|
"api_version": 9,
|
||||||
|
"commit_sha": "023b733",
|
||||||
|
"released_on": "01-05-2026",
|
||||||
|
"md5sum": "bc39a552ab7f40a71ce2813f7860a62f"
|
||||||
|
},
|
||||||
"1.0.1": {
|
"1.0.1": {
|
||||||
"api_version": 9,
|
"api_version": 9,
|
||||||
"commit_sha": "b48a4bd",
|
"commit_sha": "b48a4bd",
|
||||||
|
|
@ -2426,6 +2450,139 @@
|
||||||
"md5sum": "d8002dfc93be74d71f8e6e4aacfe6fd1"
|
"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": [
|
||||||
|
{
|
||||||
|
"name": "ATD",
|
||||||
|
"email": "anasdhaoidi001@gmail.com",
|
||||||
|
"discord": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"versions": {
|
||||||
|
"1.0.0": {
|
||||||
|
"api_version": 9,
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
# added advanced ID revealer
|
||||||
# live ping
|
# live ping
|
||||||
|
|
||||||
# Made by Mr.Smoothy - Plasma Boson
|
# 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"
|
version_str = "7"
|
||||||
BCSSERVER = 'mods.ballistica.workers.dev'
|
BCSSERVER = 'mods.ballistica.workers.dev'
|
||||||
|
|
||||||
|
|
@ -461,6 +471,7 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow):
|
||||||
color=(0.55, 0.73, 0.25),
|
color=(0.55, 0.73, 0.25),
|
||||||
icon=bui.gettexture('menuButton'),
|
icon=bui.gettexture('menuButton'),
|
||||||
iconscale=1.2)
|
iconscale=1.2)
|
||||||
|
self._menu_popup: PopupMenuWindow | None = None
|
||||||
|
|
||||||
info = bs.get_connection_to_host_info_2()
|
info = bs.get_connection_to_host_info_2()
|
||||||
if info != None:
|
if info != None:
|
||||||
|
|
@ -864,7 +875,7 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow):
|
||||||
choices.append("hostInfo_Debug")
|
choices.append("hostInfo_Debug")
|
||||||
DisChoices.append(_getTransText("Debug_for_Host_Info", isBaLstr=True))
|
DisChoices.append(_getTransText("Debug_for_Host_Info", isBaLstr=True))
|
||||||
|
|
||||||
PopupMenuWindow(
|
self._menu_popup = PopupMenuWindow(
|
||||||
position=self._menu_button.get_screen_space_center(),
|
position=self._menu_button.get_screen_space_center(),
|
||||||
scale=_get_popup_window_scale(),
|
scale=_get_popup_window_scale(),
|
||||||
choices=choices,
|
choices=choices,
|
||||||
|
|
@ -891,12 +902,12 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow):
|
||||||
except:
|
except:
|
||||||
babase.print_exception()
|
babase.print_exception()
|
||||||
|
|
||||||
PopupMenuWindow(position=widget.get_screen_space_center(),
|
self._menu_popup = PopupMenuWindow(position=widget.get_screen_space_center(),
|
||||||
scale=_get_popup_window_scale(),
|
scale=_get_popup_window_scale(),
|
||||||
choices=choices,
|
choices=choices,
|
||||||
choices_display=choices_display,
|
choices_display=choices_display,
|
||||||
current_choice="@ this guy",
|
current_choice="@ this guy",
|
||||||
delegate=self)
|
delegate=self)
|
||||||
self._popup_party_member_client_id = client_id
|
self._popup_party_member_client_id = client_id
|
||||||
self._popup_party_member_is_host = is_host
|
self._popup_party_member_is_host = is_host
|
||||||
self._popup_type = "partyMemberPress"
|
self._popup_type = "partyMemberPress"
|
||||||
|
|
|
||||||
388
plugins/utilities/better_camera_shake.py
Normal file
388
plugins/utilities/better_camera_shake.py
Normal 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
|
||||||
599
plugins/utilities/chat_bubbles.py
Normal file
599
plugins/utilities/chat_bubbles.py
Normal 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)
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# ba_meta require api 9
|
# ba_meta require api 9
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import babase
|
import babase
|
||||||
|
|
@ -14,136 +13,81 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
plugman = dict(
|
plugman = dict(
|
||||||
plugin_name="custom_hits",
|
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="",
|
external_url="",
|
||||||
authors=[
|
authors=[
|
||||||
{"name": "ATD", "email": "anasdhaoidi001@gmail.com", "discord": ""},
|
{"name": "ATD", "email": "anasdhaoidi001@gmail.com", "discord": ""},
|
||||||
],
|
],
|
||||||
version="1.0.1",
|
version="1.1.0",
|
||||||
)
|
)
|
||||||
|
# ___________________________________________________
|
||||||
|
|
||||||
####################################
|
first_damage = (u'NAH NOOB?', (1, 1, 1), '6')
|
||||||
|
second_damage = (u'GOOD!', (0, 1, 0), '2')
|
||||||
first_damage = (u'\ue042TRY AGAIN?\ue042', (0, 1, 0), '6')
|
third_damage = (u'DAMN!', (0, 0, 0), '3')
|
||||||
second_damage = (u'\ue048GOOD!\ue048', (0, 1, 1), '2')
|
fourth_damage = (u'SRY NOOB', (1, 1, 0), '4')
|
||||||
third_damage = (u'\ue041NICE!\ue041', (0.8, 0.4, 1), '3')
|
five_damage = (u'FAH!!!', (1, 0, 0), '5')
|
||||||
fourth_damage = (u'\ue049UPPS\ue049', (1, 1, 0), '4')
|
|
||||||
five_damage = (u'\ue043DEATH!\ue043', (1, 0, 0), '5')
|
|
||||||
|
|
||||||
|
|
||||||
def custom_effects(pos: float, effect: str = None) -> None:
|
def custom_effects(pos: float, effect: str = None) -> None:
|
||||||
if effect == '1':
|
if effect == '1':
|
||||||
bs.emitfx(
|
bs.emitfx(position=pos, count=3, scale=0.1, spread=0.1, chunk_type='rock')
|
||||||
position=pos,
|
|
||||||
count=3,
|
|
||||||
scale=0.1,
|
|
||||||
spread=0.1,
|
|
||||||
chunk_type='rock')
|
|
||||||
elif effect == '2':
|
elif effect == '2':
|
||||||
bs.emitfx(
|
bs.emitfx(position=pos, count=70, scale=2, spread=0.8, chunk_type='spark')
|
||||||
position=pos,
|
|
||||||
count=70,
|
|
||||||
scale=2,
|
|
||||||
spread=0.8,
|
|
||||||
chunk_type='spark')
|
|
||||||
elif effect == '3':
|
elif effect == '3':
|
||||||
bs.emitfx(
|
bs.emitfx(position=pos, count=6, scale=0.3, spread=0.4, chunk_type='splinter')
|
||||||
position=pos,
|
|
||||||
count=6,
|
|
||||||
scale=0.3,
|
|
||||||
spread=0.4,
|
|
||||||
chunk_type='splinter')
|
|
||||||
elif effect == '4':
|
elif effect == '4':
|
||||||
bs.emitfx(
|
bs.emitfx(position=pos, count=50, scale=3.0, spread=0.9, chunk_type='ice')
|
||||||
position=pos,
|
|
||||||
count=50,
|
|
||||||
scale=3.0,
|
|
||||||
spread=0.9,
|
|
||||||
chunk_type='ice')
|
|
||||||
elif effect == '5':
|
elif effect == '5':
|
||||||
bs.emitfx(
|
bs.emitfx(position=pos, count=80, scale=3.0, spread=1.5, chunk_type='metal')
|
||||||
position=pos,
|
|
||||||
count=80,
|
|
||||||
scale=3.0,
|
|
||||||
spread=1.5,
|
|
||||||
chunk_type='metal')
|
|
||||||
elif effect == '6':
|
elif effect == '6':
|
||||||
bs.emitfx(
|
bs.emitfx(position=pos, count=30, scale=1.2, spread=0.8, chunk_type='slime')
|
||||||
position=pos,
|
|
||||||
count=30,
|
|
||||||
scale=1.2,
|
def on_punched(self, damage: int) -> None:
|
||||||
spread=0.8,
|
pos = self.node.position
|
||||||
chunk_type='slime')
|
|
||||||
|
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
|
# ba_meta export babase.Plugin
|
||||||
|
class byATD(babase.Plugin):
|
||||||
|
"""تفعيل ATD Hits — يُستدعى من bootstraping()."""
|
||||||
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])
|
|
||||||
Spaz.on_punched = on_punched
|
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))
|
||||||
|
|
|
||||||
305
plugins/utilities/enhanced_effects.py
Normal file
305
plugins/utilities/enhanced_effects.py
Normal 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
|
||||||
2122
plugins/utilities/fluffyplaylisteditor.py
Normal file
2122
plugins/utilities/fluffyplaylisteditor.py
Normal file
File diff suppressed because it is too large
Load diff
222
plugins/utilities/glowing_profiles.py
Normal file
222
plugins/utilities/glowing_profiles.py
Normal 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
142
plugins/utilities/rank_system.py
Normal file
142
plugins/utilities/rank_system.py
Normal 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
|
||||||
129
plugins/utilities/sleep_on_afk.py
Normal file
129
plugins/utilities/sleep_on_afk.py
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue