diff --git a/CHANGELOG.md b/CHANGELOG.md index 9390c7f..45cca48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,5 @@ ## Plugin Manager (dd-mm-yyyy) -### 1.1.10 (12-06-2026) - -- Fix for older bs versions using `EXPORT_CLASS_NAME_SHORTCUTS` import - -### 1.1.9 (11-06-2026) - -- Fix for bs 1.7.63 - ### 1.1.8 (23-02-2026) - Fix for bs 1.7.61 build no 22714 diff --git a/index.json b/index.json index 120a1ba..f9bc733 100644 --- a/index.json +++ b/index.json @@ -1,18 +1,6 @@ { "plugin_manager_url": "https://github.com/bombsquad-community/plugin-manager/{content_type}/{tag}/plugin_manager.py", "versions": { - "1.1.10": { - "api_version": 9, - "commit_sha": "a1baa5f", - "released_on": "12-06-2026", - "md5sum": "ee4f7e3e88c2982bc8290e204b10b050" - }, - "1.1.9": { - "api_version": 9, - "commit_sha": "3adfe38", - "released_on": "11-06-2026", - "md5sum": "d7e990ea4e5c530f780e617c9bdafb42" - }, "1.1.8": { "api_version": 9, "commit_sha": "8222081", diff --git a/plugin_manager.py b/plugin_manager.py index 8d3300d..fce6017 100644 --- a/plugin_manager.py +++ b/plugin_manager.py @@ -2,6 +2,7 @@ import babase import bauiv1 as bui from bauiv1lib import popup, confirm +from babase._meta import EXPORT_CLASS_NAME_SHORTCUTS from bauiv1lib.settings.allsettings import AllSettingsWindow import urllib.request @@ -25,7 +26,7 @@ from datetime import datetime # Modules used for overriding AllSettingsWindow import logging -PLUGIN_MANAGER_VERSION = "1.1.10" +PLUGIN_MANAGER_VERSION = "1.1.8" REPOSITORY_URL = "https://github.com/bombsquad-community/plugin-manager" # Current tag can be changed to "staging" or any other branch in # plugin manager repo for testing purpose. @@ -81,7 +82,7 @@ REGEXP = { "plugin_entry_points": re.compile( bytes( "(ba_meta export (plugin|{})\n+class )(.*)\\(".format( - _regexp_friendly_class_name_shortcut("babase.Plugin"), + _regexp_friendly_class_name_shortcut(EXPORT_CLASS_NAME_SHORTCUTS["plugin"]), ), "utf-8" ), diff --git a/plugins/minigames.json b/plugins/minigames.json index 932471c..969670f 100644 --- a/plugins/minigames.json +++ b/plugins/minigames.json @@ -1663,61 +1663,6 @@ "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" - } - } } } } \ No newline at end of file diff --git a/plugins/minigames/vanishing_tiles.py b/plugins/minigames/vanishing_tiles.py deleted file mode 100644 index 5a75efe..0000000 --- a/plugins/minigames/vanishing_tiles.py +++ /dev/null @@ -1,414 +0,0 @@ -# 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 diff --git a/plugins/minigames/volley_punch.py b/plugins/minigames/volley_punch.py deleted file mode 100644 index f5260e4..0000000 --- a/plugins/minigames/volley_punch.py +++ /dev/null @@ -1,1293 +0,0 @@ -# VolleyPunch ? ig i'll name it like that - -# Made by Learn.py -# but this was def inspired by some freaky guy - - -# ba_meta require api 9 - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import babase -import _babase -import math -import random -import bascenev1 as bs -import bauiv1 as bui -import math -import time -import babase -from bascenev1lib.actor.playerspaz import PlayerSpaz -from bascenev1lib.actor.scoreboard import Scoreboard -from bascenev1lib.actor.powerupbox import PowerupBoxFactory -from bascenev1lib.actor.bomb import BombFactory -from bascenev1lib.gameutils import SharedObjects - -if TYPE_CHECKING: - from typing import Any, Sequence, Dict, Type, List, Optional, Union - - -plugman = dict( - plugin_name="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"}, - ], - version="1.1.0", -) - - -class PuckDiedMessage: - """Inform something that a puck has died.""" - - def __init__(self, puck: Puck): - self.puck = puck - - -class Puck(bs.Actor): - def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0)): - super().__init__() - shared = SharedObjects.get() - activity = self.getactivity() - - # Spawn just above the provided point. - self._spawn_pos = (position[0], position[1] + 1.05, position[2]) - self.last_players_to_touch: Dict[int, Player] = {} - self.scored = False - - self.touches = 0 - assert activity is not None - assert isinstance(activity, VolleyBallGame) - pmats = [shared.object_material, activity.puck_material] - - # so this is the volleyball we gonna mesh with today - self.node = bs.newnode('prop', - delegate=self, - attrs={ - 'mesh': activity.puck_mesh, - 'color_texture': activity.puck_tex, - 'body': 'sphere', - 'reflection': 'soft', - 'reflection_scale': [0.2], - 'shadow_size': 0.3, - 'mesh_scale': activity.ball_size, - 'body_scale': 1.07, - 'gravity_scale': 1, - 'is_area_of_interest': True, - 'position': self._spawn_pos, - 'materials': pmats - }) - - def handlemessage(self, msg: Any) -> Any: - if isinstance(msg, bs.DieMessage): - assert self.node - self.node.delete() - activity = self._activity() - if activity and not msg.immediate: - activity.handlemessage(PuckDiedMessage(self)) - - # If we go out of bounds, move back to where we started. - # copied that stuff from hockey game ngl - elif isinstance(msg, bs.OutOfBoundsMessage): - assert self.node - self.node.position = self._spawn_pos - - elif isinstance(msg, bs.HitMessage): - assert self.node - assert msg.force_direction is not None - self.node.handlemessage( - 'impulse', msg.pos[0], msg.pos[1], msg.pos[2], msg.velocity[0], - msg.velocity[1], msg.velocity[2], 1.0 * msg.magnitude, - 1.0 * msg.velocity_magnitude, msg.radius, 0, - msg.force_direction[0], msg.force_direction[1], - msg.force_direction[2]) - - # If this hit came from a player, log them as the last to touch us. - s_player = msg.get_source_player(Player) - if s_player is not None: - activity = self._activity() - if activity: - if s_player in activity.players: - self.last_players_to_touch[s_player.team.id] = s_player - else: - super().handlemessage(msg) - - -class Player(bs.Player['Team']): - """Our player type for this game.""" - - -class Team(bs.Team[Player]): - """Our team type for this game.""" - - def __init__(self) -> None: - self.score = 0 - - -# ba_meta export bascenev1.GameActivity -class VolleyBallGame(bs.TeamGameActivity[Player, Team]): - name = 'VolleyPunch' - description = 'Punch the Volleyball !' - available_settings = [ - bs.IntSetting( - 'Score to Win', - min_value=1, - default=5, - increment=1, - ), - bs.IntChoiceSetting( - 'Time Limit', - choices=[ - ('None', 0), - ('1 Minute', 60), - ('2 Minutes', 120), - ('5 Minutes', 300), - ('10 Minutes', 600), - ('20 Minutes', 1200), - ], - default=0, - ), - bs.FloatChoiceSetting( - 'Respawn Times', - choices=[ - ('Shorter', 0.25), - ('Short', 0.5), - ('Normal', 1.0), - ('Long', 2.0), - ('Longer', 4.0), - ], - default=1.0, - ), - bs.FloatSetting( - 'Ball size (m)', - min_value=0.15, - default=0.25, - max_value=0.35, - increment=0.05, - ), - bs.FloatChoiceSetting( - 'Ball Average Speed', - choices=[ - ('Slower', 0.9899), - ('Normal', 1.0), - ], - default=1.0, - ), - bs.BoolSetting('Epic Mode', False), - bs.BoolSetting('Enable Credits', True), - ] - default_music = bs.MusicType.HOCKEY - - @classmethod - def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool: - return issubclass(sessiontype, bs.DualTeamSession) - - @classmethod - def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]: - return ['Football Stadium'] - - def __init__(self, settings: dict): - super().__init__(settings) - shared = SharedObjects.get() - self._scoreboard = Scoreboard() - self._cheer_sound = bs.getsound('cheer') - self._chant_sound = bs.getsound('crowdChant') - self.one_sound = bs.getsound('announceOne') - self.two_sound = bs.getsound('announceTwo') - self.three_sound = bs.getsound('announceThree') # unused.. yet - self._swipsound = bs.getsound('swip') - self._whistle_sound = bs.getsound('refWhistle') - self.puck_mesh = bs.getmesh('shield') - self.puck_tex = bs.gettexture('ouyaUButton') - self._puck_sound = bs.getsound('metalHit') - self.puck_material = bs.Material() - self.puck_material.add_actions(actions=(('modify_part_collision', - 'friction', 4))) - self.puck_material.add_actions(conditions=('they_have_material', - shared.pickup_material), - actions=('modify_part_collision', - 'collide', True)) - self.puck_material.add_actions( - conditions=( - ('we_are_younger_than', 100), - 'and', - ('they_have_material', shared.object_material), - ), - actions=('modify_node_collision', 'collide', False), - ) - self.puck_material.add_actions(conditions=('they_have_material', - shared.footing_material), - actions=('impact_sound', - self._puck_sound, 0.2, 5)) - - # Keep track of which player last touched the puck - self.puck_material.add_actions( - conditions=('they_have_material', shared.player_material), - actions=(('call', 'at_connect', - self._handle_puck_player_collide), - ('modify_part_collision', 'physical', False))) - - # We want the puck to kill powerups; not get stopped by them - self.puck_material.add_actions( - conditions=('they_have_material', - PowerupBoxFactory.get().powerup_material), - actions=(('modify_part_collision', 'physical', False), - ('message', 'their_node', 'at_connect', bs.DieMessage()))) - self._score_region_material = bs.Material() - self._score_region_material.add_actions( - conditions=('they_have_material', self.puck_material), - actions=(('modify_part_collision', 'collide', - True), ('modify_part_collision', 'physical', False), - ('call', 'at_connect', self._handle_score))) - - self._fake_wall_material = bs.Material() # i'll fix this. trust - - self._net_wall_material = bs.Material() - self._net_material = bs.Material() - self._net_wall_material.add_actions( - conditions=('they_have_material', shared.player_material), - actions=( - ('modify_part_collision', 'collide', True), - ('modify_part_collision', 'physical', True) - - )) - self._net_material.add_actions( - conditions=('they_have_material', shared.object_material), - actions=( - ('modify_part_collision', 'collide', True), - ('modify_part_collision', 'physical', True), - ('modify_part_collision', - 'friction', -10), - ('call', 'at_connect', self.slow_down))) - - self._puck_spawn_pos: Optional[Sequence[float]] = None - self._score_regions: Optional[List[bs.NodeActor]] = None - self._puck: Optional[Puck] = None - self._score_to_win = int(settings['Score to Win']) - self._time_limit = float(settings['Time Limit']) - self.credit_text = bool(settings['Enable Credits']) # i did the freaku credit thing - self._epic_mode = bool(settings['Epic Mode']) - - # the ball is too fast for the players and the map is soo huge its a good poc - brostos - self.ball_size = float(settings['Ball size (m)']) - self.ball_speed = float(settings['Ball Average Speed']) - - # Base class overrides. - self.slow_motion = self._epic_mode - self.default_music = (bs.MusicType.EPIC if self._epic_mode else - bs.MusicType.TO_THE_DEATH) - self.s: list = [] - self.w: list = [] - self.n: list = [] - self.last_interaction_time = -9999 - self.smashed = -9999 - self.interactions_timer = None - self.curve = True - self.difficulty = 0.5 - self.smasher = None - self.commentary_text = None - self._text_task = None # Référence pour annuler l'animation en cours - self._clear_timer = None # Référence pour le timer de disparition - self.touches_indicator = None - self.guide = None - self.curving = False - - def get_instance_description(self) -> Union[str, Sequence]: - if self._score_to_win == 1: - return 'Score a goal.' - return 'Score ${ARG1} goals.', self._score_to_win - - def get_instance_description_short(self) -> Union[str, Sequence]: - if self._score_to_win == 1: - return 'score a goal' - return 'score ${ARG1} goals', self._score_to_win - - def on_begin(self) -> None: - super().on_begin() - - self.setup_standard_time_limit(self._time_limit) - self._puck_spawn_pos = (-4, 1, 0) if random.random() < 0.5 else (4, 1, 0) - self._spawn_puck() - - # Set up the two score regions. - self._score_regions = [] - self.commentary_text = bs.newnode( - 'text', - attrs=dict( - text='', - scale=0.012, - position=(0, 3.1, -5.8), - in_world=True, - flatness=1, - shadow=0, - color=(1, 1, 1, 0.5), - h_align='center', - v_align='top', - h_attach='center', - v_attach='top', - )) - self.touches_indicator = bs.newnode( - 'text', - attrs=dict( - text='', - scale=0.025, - position=(0, 3, -5.8), - in_world=True, - flatness=1, - shadow=0, - color=(1, 1, 1, 0.5), - h_align='center', - v_align='top', - h_attach='center', - v_attach='top', - )) - self.guide = bs.newnode( - 'text', - attrs=dict( - text='PUNCH TO TAP THE BALL \nPICKUP TO CUFF \nBOMB TO GLIDE / DIVE \nJUMP UNDER A MID AIR BALL WITH THE BAR FILLED TO SMASH \nTHE ULT BAR FILLS AS YOU PLAY', - scale=0.005, - position=(0, 2.7, -5.8), - in_world=True, - flatness=1, - shadow=0, - color=(1, 1, 1, 0.5), - h_align='center', - v_align='top', - h_attach='center', - v_attach='top', - )) - bs.animate_array(self.guide, 'color', 4, {0: (1, 1, 1, 0.5), 1: ( - 1, 1, 1, 1), 2: (1, 1, 1, 0.5)}, loop=True) - self.interactions_timer = bs.Timer( - 1 / 120, self.interactions, repeat=True) # time, function, repeat - self._score_regions.append( - bs.NodeActor( - bs.newnode('region', - attrs={ - 'position': (9, 0, 0), - 'scale': (17.3, 0.002, 12), - 'type': 'box', - 'materials': [self._score_region_material] - }))) - self._score_regions.append( - bs.NodeActor( - bs.newnode('region', - attrs={ - 'position': (-9, 0, 0), - 'scale': (17.3, 0.002, 20), - 'type': 'box', - 'materials': [self._score_region_material] - }))) - self._update_scoreboard() - self._chant_sound.play() - if self.credit_text: - t = bs.newnode('text', - attrs={'text': "Created by Learn.py", - 'scale': 0.7, - 'position': (0, 0), - 'shadow': 0.5, - 'flatness': 1.2, - 'color': (1, 1, 1), - 'h_align': 'center', - 'v_attach': 'bottom'}) - - # Create the net and regions. - mat = bs.Material() # spam this whenever u want a material THEN add some properties - mat.add_actions(actions=('modify_part_collision', - 'collide', False)) - net = bs.newnode( - 'prop', - attrs={ - 'body': 'puck', - 'body_scale': 0, - 'position': (0, 0, 0), - 'mesh_scale': 1, - 'mesh': bs.getmesh('volleyball_net'), - 'color_texture': bs.gettexture('black'), - 'reflection': 'soft', - 'reflection_scale': [1.5], - 'sticky': False, - 'shadow_size': 0.2, - 'gravity_scale': 0, - 'materials': [mat], - }, - ) - - self.s.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 2.4, 0), 'scale': ( - 0.8, 6, 20), 'type': 'box', 'materials': (self._fake_wall_material, )}))) - - self.w.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, 0), 'scale': ( - 0.6, 10, 20), 'type': 'box', 'materials': (self._net_wall_material, )}))) - self.n.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, 0), 'scale': ( - 0.6, 2.75, 20), 'type': 'box', 'materials': (self._net_material, )}))) - self.n.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, -5.9), 'scale': ( - 20, 20, 0.8), 'type': 'box', 'materials': (self._net_wall_material, self._net_material)}))) - self.n.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, 5.9), 'scale': ( - 20, 20, 0.8), 'type': 'box', 'materials': (self._net_wall_material, self._net_material)}))) - - def slow_down(self): - if not self._puck: - return - puck_node = self._puck.node - mult = 0.88 - v = puck_node.velocity - puck_node.velocity = (v[0] * mult, v[1] * mult, v[2] * mult) - - def write(self, text, color): - """types""" - if hasattr(self, '_timers'): - for t in self._timers: - t = None - self._timers = [] - - if not text: - return - - self.commentary_text.color = color - total_chars = len(text) - - interval = 0.9 / max(total_chars, 1) - - for i in range(1, total_chars + 1): - def show(index=i): - if self.commentary_text.exists(): - self.commentary_text.text = text[:index] - - self._timers.append(bs.Timer(interval * i, show, repeat=False)) - - def start_clear(): - for i in range(total_chars, -1, -1): - def hide(index=i): - if self.commentary_text.exists(): - self.commentary_text.text = text[:index] - - self._timers.append( - bs.Timer(0.9 + 3.0 + (0.015 * (total_chars - i)), hide, repeat=False)) - - start_clear() - - def vignette(self, final_color, longer): - """Aesthetic reasons.""" - glb = bs.getactivity().globalsnode - base_color = (0.57, 0.57, 0.57) - bs.animate_array(glb, 'vignette_outer', 3, {0: base_color, 0.1: ( - final_color[0] / 1.2, final_color[1] / 1.2, final_color[2] / 1.2), (1 if longer else 0.5): base_color}) - - def interactions(self): - """Some personalized stuff and my deepest secrets. - I don't know why, but I feel SoK may like this.""" - if self._puck is None: - return - - vel = self._puck.node.velocity - # apply the mult - self._puck.node.velocity = ( - vel[0], - vel[1] * self.ball_speed, - vel[2] - ) - - green = bs.gettexture('ouyaOButton') - yellow = bs.gettexture('ouyaYButton') - red = bs.gettexture('ouyaAButton') - blue = bs.gettexture('ouyaUButton') - - # If it passes the middle.. green. - puck_node = self._puck.node - pos = puck_node.position - x_diff = abs(pos[0]) - - if x_diff < 0.25 or self.curve: - puck_node.color_texture = blue - self._puck.touches = 0 - - if pos[1] < 2.6 and (bs.time() - self.last_interaction_time > 0.5): - mult = 0.935 - v = puck_node.velocity - puck_node.velocity = (v[0] * mult, v[1] * mult, v[2] * mult) - - self.save_text = random.choice([ - "dived to the rescue!", - "kept the ball alive!", - "flew in to save the day!", - "is too fast for the floor!", - "bounced it back just in time!", - "kept the dream alive!", - "barely missed the ground!", - "saved the rally!", - "made a clutch save!", - "refused to let it drop!", - "pulled a superman save!", - "made a desperate save!", - "proved the floor is not an option!", - "landed a miracle touch!", - "saved the point!", - "pulled off a heroic dive!", - "stretched to the limit!", - "barely kept it up!", - "showed reflexes of a cat!", - "denied the floor!", - "made a clutch save!", - "is staying in the game!", - "pulled an epic recovery!", - "reacted in the nick of time!", - "won't let the ball drop!", - "is pure hustle!", - "made an impossible save!", - "is defying gravity!", - "made sure the ground stays empty!", - "saved it!", - "pulled off last-second magic!", - "is floating to save it!", - "is too clutch to fail!", - "got the ball back in the air!", - "is denying the dirt!", - "saved it by a hair!", - "is fighting for every ball!", - "is pure energy!", - "never gives up!", - "keeps it in play!", - "saved it barely!", - "kept the floor clean!", - "is pure determination!", - "pulled a miraculous save!", - "denied the game over!", - "is still alive!", - "is keeping the rally going!", - "made an out-of-reach save!", - "made a ninja-like save!", - "bounced back from trouble!"]) - self.hit_text = random.choice([ - "sent it over!", - "cleared the net!", - "powered it across!", - "nailed a deep shot!", - "bounced it into their zone!", - "launched an attack!", - "sent a rocket over!", - "is dominating the net!", - "found the open spot!", - "placed it perfectly!", - "smashed it back!", - "is applying the pressure!", - "sent them scrambling!", - "over the net it goes!", - "hit a beauty!", - "is controlling the court!", - "forced a tough return!", - "keeps the pressure high!", - "landed a sweet strike!", - "sent it deep!", - "is playing aggressively!", - "hit a laser beam!", - "made a solid connection!", - "is taking charge!", - "sent a tricky shot over!", - "is firing back!", - "put it right on target!", - "sent the ball flying!", - "beat the defense!", - "is crushing it!", - "nailed that serve return!", - "showed off some power!", - "sent a high arc over!", - "is making moves!", - "kept them on their toes!", - "delivered a powerful hit!", - "is pushing the pace!", - "found the gap!", - "sent a curveball over!", - "is looking sharp!", - "hit it with precision!", - "made them work for it!", - "is owning the net!", - "sent a heavy strike!", - "is dictating the play!", - "put it out of reach!", - "landed the perfect hit!", - "is on fire!", - "sent a swift strike!", - "is forcing errors!" - ]) - self.control_text = random.choice([ - "is showing off sweet control!", - "has the ball on a string!", - "handles it with grace!", - "shows off some nice touch!", - "keeps it close and tight!", - "is demonstrating perfect poise!", - "has ice in their veins!", - "is keeping it under control!", - "shows a velvet touch!", - "is masterfully handling the ball!", - "keeps the ball glued to them!", - "is displaying total composure!", - "makes it look so easy!", - "is controlling the pace!", - "shows off pure finesse!", - "has the ball perfectly placed!", - "is staying cool under pressure!", - "has incredible ball handling!", - "is maneuvering with ease!", - "keeps the ball within reach!", - "shows off some fancy footwork!", - "is keeping the ball calm!", - "has the softest touch!", - "is dictating the rhythm!", - "keeps it balanced and steady!", - "is showing high-level control!", - "has the situation handled!", - "is playing with surgical precision!", - "keeps the ball in their zone!", - "shows off expert handling!", - "is keeping it smooth!", - "has the ball right where they want it!", - "demonstrates a gentle touch!", - "is keeping the game slow!", - "shows off calm concentration!", - "has everything under control!", - "is moving with grace!", - "keeps the ball softly hovering!", - "shows off true talent!", - "is maintaining perfect flow!", - "has a magical touch!", - "is playing it smart!", - "keeps the ball very close!", - "shows off professional control!", - "is handling the pressure well!", - "has a very steady hand!", - "is keeping it precise!", - "shows off masterful technique!", - "is keeping the ball in check!", - "has the perfect feel for it!" - ]) - if self.touches_indicator is not None and self.touches_indicator.exists(): - self.touches_indicator.position = (self._puck.node.position[0], self._puck.node.position[1] + 0.35, - self._puck.node.position[2]) - else: - self.touches_indicator = bs.newnode( - 'text', - attrs=dict( - text='', - scale=0.025, - position=(0, 3, -5.8), - in_world=True, - flatness=1, - shadow=0, - color=(1, 1, 1, 0.5), - h_align='center', - v_align='top', - h_attach='center', - v_attach='top', - )) - - everything = bs.getnodes() - everyone = [] - closest_distance = 9999 - closest_player = None - - # i won't explain what "curve" is... look by yourself - if not self.curve and not self._puck.scored: - if bs.time() < self.smashed: - random_pos = (random.uniform( - 0.1, 0.7) + self._puck.node.position[0], self._puck.node.position[1], random.uniform(0.1, 0.7) + self._puck.node.position[2]) - e = bs.newnode( - 'explosion', - attrs={ - 'position': random_pos, - 'color': self.smasher.color, - 'radius': random.uniform(0.1, 0.7), - 'big': False, - } - ) - bs.timer(1, e.delete) - self.smasher.getdelegate(object).percentage /= 1.1 - else: - ball_tex = self._puck.node.color_texture - - texture_to_name = { - green: 'green', - yellow: 'yellow', - red: 'red', - blue: 'blue' - } - - colors = { - 'green': (0.6, 1, 0.6), - 'yellow': (1, 0.3, 0), - 'red': (1, 0, 0), - 'blue': (0.6, 0.6, 1) - } - - color_name = texture_to_name.get(ball_tex) - color = colors.get(color_name) - - e = bs.newnode( - 'explosion', - attrs={ - 'position': self._puck.node.position, - 'color': color, - 'radius': 0.1, - 'big': False, - } - ) - indicator_color = (*color, 0.5) - bs.timer(1, e.delete) - self.touches_indicator.color = indicator_color - self.touches_indicator.text = str(3 - self._puck.touches) - - for human in everything: # doesn't make much sense, but I hope u understand I want players nodes. - if human.getnodetype() == "spaz": - everyone.append(human) - - for player_node in everyone: - if not player_node.exists(): # why wouldn't u exist at first - continue - - # not wanting to do that Euclidean thingy - distance = math.dist(player_node.position, self._puck.node.position) - player_node.hold_node = None - player_node.getdelegate(object).impact_scale = 0 - if distance < closest_distance: - closest_player = player_node # him, the closest one - closest_distance = distance # his distance with the volleyball - - good: bool = (self._puck.touches < 3 and - bs.time() - self.last_interaction_time > 0.4 and - not self._puck.scored) - # bs.time() is the current bombsquad time :) bs for bombsquad. - - delegate = closest_player.getdelegate(object) - now = bs.time() - can_jump = (delegate.last_jump_time_ms > - delegate._jump_cooldown and closest_player.position[1] < 1) - - if closest_player.jump_pressed and can_jump: - ppos = closest_player.position - delegate.last_jump_time_ms = int(bs.time() * 1000) - closest_player.handlemessage( - 'kick_back', - ppos[0], - ppos[1], - ppos[2], - 0, - 1, - 0, - 50, # small force - ) - - punched = now < delegate.punched - pickup = now < delegate.pickup - bombed = now < delegate.bombed - curved = now < delegate.curved - - # SO if broski is the nearest AND broski is near enough... - if closest_player and closest_distance < 4 and good: - player = spaz = delegate - ball = self._puck.node # ball node - you = closest_player.position # already a node - in_air = (you[1] > 0.8) - # Ball goes where you look but always tries to go to the other side. (tell me if u guys want it to go totally where you're looking at) - if closest_distance < (1.35 if not in_air else 1.6) and (closest_player.punch_pressed or punched): - self.difficulty += 0.6 / 75 - e = bs.newnode( - 'explosion', - attrs={ - 'position': self._puck.node.position, - 'color': closest_player.color, - 'radius': 0.8, - 'big': False, - } - ) - bs.timer(1, e.delete) - self.last_interaction_time = bs.time() - self._puck.touches += 1 - self.curve = False - if self._puck.touches == 2: - spaz.percentage = min(spaz.percentage + 8, 100) - ball.color_texture = yellow - self.two_sound.play() - elif self._puck.touches == 3: - spaz.percentage = min(spaz.percentage + 12, 100) - ball.color_texture = red - self.one_sound.play() - else: - ball.color_texture = green - # This is for the sound, you probably didn't know that! - bs.getsound("impactHard").play() - - sight = (bs.Vec3(you) - bs.Vec3(closest_player.position_forward) - ).normalized() * (12 if delegate.percentage < 95 else 30) # Sight factor - speed = 6 if delegate.percentage < 95 else 20 # bar full - - if curved: - start_pos = ball.position - end_pos = (sight[0] * 2, -1, -sight[2]) - duration = 1.1 - fps = 144 - total_steps = int(duration * fps) - curved_time = bs.time() + 0.15 - self.curving = True - - z_offset = -4.0 if sight[0] < 0 else 4.0 - - def move_to_point(step): - if not ball.exists(): - return - if ball.position[1] < 1 and bs.time() > curved_time: - return - if not self.curving: - return - - t = step / total_steps - - x = start_pos[0] + (end_pos[0] - start_pos[0]) * t - y = start_pos[1] + (end_pos[1] - start_pos[1]) * t - z = start_pos[2] + (end_pos[2] - start_pos[2]) * t - - y += math.sin(t * math.pi) * 3.0 - - # Ajout de la courbe latérale - z += math.sin(t * math.pi) * z_offset - - # --- LIMITE SUR L'AXE Z --- - # On force z à rester entre -5 et 5 - z = max(-5.0, min(5.0, z)) - # -------------------------- - - ball.position = (x, y, z) - - if step < total_steps: - bs.timer(1 / fps, lambda: move_to_point(step + 1)) - - move_to_point(0) - else: - # normal - self.curving = False - if in_air and delegate.percentage > 95: - if closest_player.position[0] < 0: - vel = (12 * 1.6, 0, sight[2]) * speed - else: - vel = (-12 * 1.6, 0, sight[2]) * speed - else: - if closest_player.position[0] < 0: - vel = (4.8 * 1.6, 0, sight[2]) * speed - else: - vel = (-4.8 * 1.6, 0, sight[2]) * speed - - # smth - dist_from_middle = math.dist(you, (0, 2, 0)) - height = (max(dist_from_middle * 2.75, 7) * 0.8) - if in_air and delegate.percentage > 95: - height = -4 * 2 / dist_from_middle - - ball.velocity = (vel[0], height, vel[2]) - if in_air and delegate.percentage > 95: - audio = random.choice(['explosion01', 'explosion02', - 'explosion03', 'explosion04', 'explosion05']) - bs.getsound(audio).play() - self.smashed = bs.time() + 1 - self.smasher = closest_player - self.vignette(closest_player.color, False) - delegate.on_punch_press() - delegate.on_punch_release() - self.write( - f'{player.name.upper()} SMASHED THE BALL !' if random.random() < 0.5 else f'{player.name} hit the ball with all his might !', closest_player.color) - else: - self.write( - f'{player.name} {self.hit_text}', closest_player.color) - - # prettiness here - ppos = you - f = (bs.Vec3(ball.position) - bs.Vec3(you)).normalized() - closest_player.handlemessage( - 'kick_back', - ppos[0], - ppos[1], - ppos[2], - f[0], - f[1], - f[2], - 400, # small force - ) - # Ball goes straight up in the air. idk what it is called. - if closest_distance < 1.35 and (closest_player.pickup_pressed or pickup): - self.write( - f'{player.name} {self.control_text}', closest_player.color) - y_diff = abs(self._puck.node.position[1] - closest_player.position[1]) - - if y_diff < 1: - self.difficulty += 0.6 / 75 - self.last_interaction_time = bs.time() - self.curve = False - - self._puck.touches += 1 - - ball = self._puck.node - if self._puck.touches == 2: - spaz.percentage = min(spaz.percentage + 4, 100) - ball.color_texture = yellow - self.two_sound.play() - elif self._puck.touches == 3: - spaz.percentage = min(spaz.percentage + 6, 100) - ball.color_texture = red - self.one_sound.play() - else: - ball.color_texture = green - - closest_player.handlemessage('celebrate', 400) - bs.getsound("impactMedium").play() - e = bs.newnode( - 'explosion', - attrs={ - 'position': self._puck.node.position, - 'color': closest_player.color, - 'radius': 0.5, - 'big': True, - } - ) - bs.timer(1, e.delete) - - direction = (bs.Vec3(you) - - bs.Vec3(closest_player.position_forward)).normalized() - ball.velocity = (-you[0] * 0.04, 12.8, direction[2] * 0.1) - if closest_player.bomb_pressed and not delegate.gliding or bombed and not delegate.gliding: # gliding.. variant coming soon - self.write( - f'{player.name} {self.save_text}', closest_player.color) - delegate.gliding = True - ppos = you - f = (bs.Vec3(ball.position) - bs.Vec3(you)).normalized() - distance = math.dist(ball.position, you) - closest_player.handlemessage( - 'kick_back', - ppos[0], - ppos[1], - ppos[2], - f[0], - 0, - f[2], - 1000 - (500/distance), # tiny force - ) - closest_player.handlemessage('knockout', 100) - print(1) - spaz.pickup = now + 0.8 - - bs.getsound("impactHard").play(volume=0.5) - e = bs.newnode( - 'explosion', - attrs={ - 'position': you, - 'color': (0, 0.3, 0.5), - 'radius': 1.1, - 'big': False, - } - ) - bs.timer(1, e.delete) - bs.timer(0.6, lambda: setattr(delegate, 'gliding', False)) - - # variant soon - - def on_team_join(self, team: Team) -> None: - self._update_scoreboard() - - def _handle_puck_player_collide(self) -> None: - collision = bs.getcollision() - try: - puck = collision.sourcenode.getdelegate(Puck, True) - player = collision.opposingnode.getdelegate(PlayerSpaz, - True).getplayer( - Player, True) - except bs.NotFoundError: - return - - puck.last_players_to_touch[player.team.id] = player - - def _kill_puck(self) -> None: - self._puck = None - self.curve = True - - def _handle_score(self) -> None: - assert self._puck is not None - assert self._score_regions is not None - - # Our puck might stick around for a second or two - # we don't want it to be able to score again - # to not be getting a corrupted score. - if self._puck.scored: - return - E = bs.newnode( - 'explosion', - attrs={ - 'position': self._puck.node.position, - 'color': (0, 0, 0), - 'radius': .1, - 'big': True, - } - ) - bs.timer(1, E.delete) - bs.animate(self.touches_indicator, 'scale', {0: 0, 2.65: 0, 3: 0.025}) - self.touches_indicator.text = '0' - if self.smashed > 0: - self._puck.node.sticky = True - self.smashed = -9999 - - region = bs.getcollision().sourcenode - index = 0 - for index in range(len(self._score_regions)): - if region == self._score_regions[index].node: - break - - for team in self.teams: - if team.id == index: - team.score += 1 - - # Puck Spawn - if team.id == 0: # left side scored - self._puck_spawn_pos = (5, 0.7, 0) - elif team.id == 1: # right side scored - self._puck_spawn_pos = (-5, 0.7, 0) - else: # what the heck - self._puck_spawn_pos = (0, 0.7, 0) - - for player in team.players: - if player.actor: - player.actor.handlemessage(bs.CelebrateMessage(2.0)) # yay we scored - player.actor.percentage = min(player.actor.percentage + 15, 100) - # dont mind this ;( - scorch = bs.newnode( - 'scorch', - attrs={ - 'position': self._puck.node.position, - 'size': 1.4, - 'color': player.actor.node.color, - 'big': True, - }, - ) - bs.animate(scorch, 'presence', {3.000: 1, 7.000: 0}) - bs.timer(7, scorch.delete) - self.vignette(player.actor.node.color, longer=True) - - # End game if we won. - if team.score >= self._score_to_win: - self.end_game() - - self._cheer_sound.play() - - self._puck.scored = True - - # Kill the puck (it'll respawn itself shortly). - # Animation ! 0 sec = 0.25 of size in our case - bs.animate(self._puck.node, 'mesh_scale', {0: 0.25, 0.4: 0}) - bs.timer(0.7, self._kill_puck) - - bs.cameraflash(duration=7.0) - self._update_scoreboard() - - def end_game(self) -> None: - results = bs.GameResults() - for team in self.teams: - results.set_team_score(team, team.score) - self.interactions_timer = None - self.end(results=results) - - def on_transition_in(self) -> None: - super().on_transition_in() - - def _update_scoreboard(self) -> None: - winscore = self._score_to_win - for team in self.teams: - self._scoreboard.set_team_value(team, team.score, winscore) - - # overriding the default character spawning.. - def spawn_player(self, player: Player) -> bs.Actor: - spaz = self.spawn_player_spaz(player) - spaz.bomb_count = 0 # No bombs for yall bahaha - # Now we want to make that your punches are useless too - # But we will need the button to work so let's put the damage to 0 and some custom stuff - spaz._punch_power_scale = 0 - spaz.impact_scale = 0 - spaz.check_timer = 0 - - spaz.pickup = 0 - spaz.punched = 0 - spaz.bombed = 0 - spaz.curved = 0 - - spaz.can_curve = False - - spaz.last_angle = 0 - spaz.total_rotation = 0.0 - spaz.rotation_direction = 0 # 0: neutre, 1: clockwise, -1: not clockwise - - spaz.gliding = False - spaz.smashed = False - spaz.percentage = 0 - spaz._jump_cooldown = 2000 - spaz.name = spaz.node.name - spaz.bar = bs.newnode( - 'shield', - owner=spaz.node, - attrs={ - 'position': spaz.node.position, - 'radius': .01, - 'color': (0, 0, 0), - } - ) - spaz.node.connectattr('position', spaz.bar, 'position') - spaz.bar.always_show_health_bar = True - spaz.node.name = '' # bahaha but you'll sspazuseful - - def check(): - if spaz.node.exists(): - # Controls part - now = bs.time() - - if spaz.node.punch_pressed: - spaz.punched = now + 0.25 - elif spaz.node.pickup_pressed: - spaz.pickup = now + 0.40 - elif spaz.node.bomb_pressed: - spaz.bombed = now + 0.20 - elif spaz.node.jump_pressed and spaz.percentage > 95: - spaz.punched = now + 0.50 - - if spaz.percentage > 95: - spaz.node.name = 'SMASH' - else: - spaz.node.name = '' - - spaz.bar.hurt = min(1.0 - float(spaz.percentage) / 100, 1) - - # Curved ball part - - pos = spaz.node.position - fwd = spaz.node.position_forward - - dx = fwd[0] - pos[0] - dz = fwd[2] - pos[2] - - length = math.sqrt(dx * dx + dz * dz) - if length > 0.0001: - dx /= length - dz /= length - - spaz.angle = math.degrees(math.atan2(-dx, -dz)) # trust - - # checks if u turn ! Not in the game yet ! - def check_270_rotation(): - now = bs.time() - current_angle = spaz.angle - - delta = current_angle - spaz.last_angle - if delta > 180: - delta -= 360 - if delta < -180: - delta += 360 - - current_dir = 1 if delta > 0 else -1 - - if spaz.rotation_direction != 0 and current_dir != spaz.rotation_direction: - spaz.total_rotation = 0 - - spaz.rotation_direction = current_dir - - spaz.total_rotation += abs(delta) - - if spaz.total_rotation >= 250: - spaz.curved = now + 0.40 - spaz.total_rotation = 0 - - spaz.last_angle = current_angle - - spaz.check_timer1 = bs.Timer(1/30, check, repeat=True) - # spaz.check_timer2 = bs.Timer(1/30, check_270_rotation, repeat=True) - - # spaz.node.hold_node = spaz.node ... nevermind - - # By the way when you have a node but want his player just do - # player = node.getdelegate(object) - # i dont know why you'll need it but here it is - - return spaz - - def handlemessage(self, msg: Any) -> Any: - - # Respawn dead players if they're still in the game. - # Because if we don't.. they won't like our game ;( - if isinstance(msg, bs.PlayerDiedMessage): - # Augment standard behavior... - super().handlemessage(msg) - self.respawn_player(msg.getplayer(Player)) - - # Respawn dead pucks. - elif isinstance(msg, PuckDiedMessage): - if not self.has_ended(): - bs.timer(2, self._spawn_puck) # 2 secs to breathe bahahaha - else: - super().handlemessage(msg) - - def _spawn_puck(self) -> None: - self._swipsound.play() - self._whistle_sound.play() - assert self._puck_spawn_pos is not None - self._puck = Puck(position=self._puck_spawn_pos) - self.timer = bs.Timer(1 / 144, self.lock_it, repeat=True) # 144 ?.. yes - - def lock_it(self): - if self._puck is None: - return - if self.curve: - self._puck.node.position = self._puck_spawn_pos - else: - self._puck.node.shadow_size = min(self._puck.node.position[1] * 0.3, 2) - - -# ba_meta export babase.Plugin -class justlearn(babase.Plugin): - def __init__(self): - self.installer = ModInstaller() - self.installer.run_full_install() - - -class ModInstaller: - def __init__(self) -> None: - self.python_user = _babase.env()["python_directory_user"] - self.files = self.python_user + '/VolleyBallMap/' - self.app_dir = _babase.env()["python_directory_app"] + '/' - self.data_dir = self.app_dir + '../' - self.meshes_dir = self.data_dir + 'meshes/' - self.platform = _babase.app.classic.platform - - def run_full_install(self) -> None: - import os - import urllib.request - import shutil - import babase - - target_path = os.path.join(self.files, 'volleyball_net.bob') - destination_path = os.path.join(self.meshes_dir, 'volleyball_net.bob') - - if os.path.exists(destination_path): - return - - def _do_install(): - try: - if not os.path.exists(self.files): - os.makedirs(self.files) - - url = "https://raw.githubusercontent.com/Scriptz1/Learn.py-Installer/main/volleyball_net.bob" - urllib.request.urlretrieve(url, target_path) - shutil.copy(target_path, destination_path) - - bui.screenmessage("Installation worked !", color=(0, 1, 0.3)) - bui.getsound('ding').play() - - except Exception as e: - print(f"DEBUG ERROR: {e}") - bui.screenmessage("Installation Failed!", color=(1, 0, 0)) - bui.getsound('kronk2').play() - - bs.apptimer(2.5, _do_install) diff --git a/plugins/utilities.json b/plugins/utilities.json index d9e3cd5..0c62bac 100644 --- a/plugins/utilities.json +++ b/plugins/utilities.json @@ -1054,18 +1054,6 @@ } ], "versions": { - "2.1.7": { - "api_version": 9, - "commit_sha": "c0527f3", - "released_on": "05-04-2026", - "md5sum": "442889926c17a0ffc70b924a808d8132" - }, - "2.1.6": { - "api_version": 9, - "commit_sha": "3aa666f", - "released_on": "05-04-2026", - "md5sum": "d2b66f98b5a24aa14eb6b1ed9de2d5dc" - }, "2.1.5": { "api_version": 9, "commit_sha": "a36e3c2", @@ -2387,12 +2375,6 @@ } ], "versions": { - "1.0.1": { - "api_version": 9, - "commit_sha": "023b733", - "released_on": "01-05-2026", - "md5sum": "045eb589b2969398f7fcb171216303d1" - }, "1.0.0": { "api_version": 9, "commit_sha": "f38a7ac", @@ -2402,7 +2384,7 @@ } }, "custom_hits": { - "description": "when someone hited text whit random effect will appear", + "description": "when someone hited rainbow text whit effect will appear", "external_url": "", "authors": [ { @@ -2412,12 +2394,6 @@ } ], "versions": { - "1.1.0": { - "api_version": 9, - "commit_sha": "023b733", - "released_on": "01-05-2026", - "md5sum": "bc39a552ab7f40a71ce2813f7860a62f" - }, "1.0.1": { "api_version": 9, "commit_sha": "b48a4bd", @@ -2450,139 +2426,6 @@ "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" - } - } } } } \ No newline at end of file diff --git a/plugins/utilities/advanced_party_window.py b/plugins/utilities/advanced_party_window.py index aca17e7..6bdfd2e 100644 --- a/plugins/utilities/advanced_party_window.py +++ b/plugins/utilities/advanced_party_window.py @@ -19,51 +19,41 @@ https://bombsquad-community.web.app/mods ''' -import threading -from _thread import start_new_thread -import urllib.parse -import urllib.request -from typing import TYPE_CHECKING, cast -import _babase -import bascenev1 as bs -import bauiv1 as bui -import babase -import time -import math -from dataclasses import dataclass -from bauiv1lib.confirm import ConfirmWindow -from bauiv1lib.colorpicker import ColorPickerExact -from typing import List, Sequence, Optional, Dict, Any, Union -import bauiv1lib.party as bascenev1lib_party -import ssl -import datetime -import base64 -from babase._general import Call -from bauiv1lib.popup import PopupMenuWindow, PopupWindow -from bauiv1lib.account import viewer -import os -import urllib -import copy -import shutil -import sys -import re -import json -import codecs -import traceback -plugman = dict( - plugin_name="advanced_party_window", - description="Advanced your party window with lots of feature", - external_url="https://www.youtube.com/watch?v=QrES1jQGXF0", - authors=[ - {"name": "Mr.Smoothy", "email": "", "discord": "mr.smoothy"}, - ], - version="2.1.7", -) - # added advanced ID revealer # live ping # Made by Mr.Smoothy - Plasma Boson +import traceback +import codecs +import json +import re +import sys +import shutil +import copy +import urllib +import os +from bauiv1lib.account import viewer +from bauiv1lib.popup import PopupMenuWindow, PopupWindow +from babase._general import Call +import base64 +import datetime +import ssl +import bauiv1lib.party as bascenev1lib_party +from typing import List, Sequence, Optional, Dict, Any, Union +from bauiv1lib.colorpicker import ColorPickerExact +from bauiv1lib.confirm import ConfirmWindow +from dataclasses import dataclass +import math +import time +import babase +import bauiv1 as bui +import bascenev1 as bs +import _babase +from typing import TYPE_CHECKING, cast +import urllib.request +import urllib.parse +from _thread import start_new_thread +import threading version_str = "7" BCSSERVER = 'mods.ballistica.workers.dev' @@ -471,7 +461,6 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow): color=(0.55, 0.73, 0.25), icon=bui.gettexture('menuButton'), iconscale=1.2) - self._menu_popup: PopupMenuWindow | None = None info = bs.get_connection_to_host_info_2() if info != None: @@ -875,7 +864,7 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow): choices.append("hostInfo_Debug") DisChoices.append(_getTransText("Debug_for_Host_Info", isBaLstr=True)) - self._menu_popup = PopupMenuWindow( + PopupMenuWindow( position=self._menu_button.get_screen_space_center(), scale=_get_popup_window_scale(), choices=choices, @@ -902,12 +891,12 @@ class ModifiedPartyWindow(bascenev1lib_party.PartyWindow): except: babase.print_exception() - self._menu_popup = PopupMenuWindow(position=widget.get_screen_space_center(), - scale=_get_popup_window_scale(), - choices=choices, - choices_display=choices_display, - current_choice="@ this guy", - delegate=self) + PopupMenuWindow(position=widget.get_screen_space_center(), + scale=_get_popup_window_scale(), + choices=choices, + choices_display=choices_display, + current_choice="@ this guy", + delegate=self) self._popup_party_member_client_id = client_id self._popup_party_member_is_host = is_host self._popup_type = "partyMemberPress" diff --git a/plugins/utilities/better_camera_shake.py b/plugins/utilities/better_camera_shake.py deleted file mode 100644 index d176f46..0000000 --- a/plugins/utilities/better_camera_shake.py +++ /dev/null @@ -1,388 +0,0 @@ -# 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 diff --git a/plugins/utilities/chat_bubbles.py b/plugins/utilities/chat_bubbles.py deleted file mode 100644 index af7eb31..0000000 --- a/plugins/utilities/chat_bubbles.py +++ /dev/null @@ -1,599 +0,0 @@ -# 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) diff --git a/plugins/utilities/custom_hits.py b/plugins/utilities/custom_hits.py index e6d3e78..afa7730 100644 --- a/plugins/utilities/custom_hits.py +++ b/plugins/utilities/custom_hits.py @@ -1,6 +1,7 @@ # ba_meta require api 9 from __future__ import annotations + from typing import TYPE_CHECKING import babase @@ -13,81 +14,136 @@ if TYPE_CHECKING: plugman = dict( plugin_name="custom_hits", - description="when someone hited text whit random effect will appear", + description="when someone hited rainbow text whit effect will appear", external_url="", authors=[ {"name": "ATD", "email": "anasdhaoidi001@gmail.com", "discord": ""}, ], - version="1.1.0", + version="1.0.1", ) -# ___________________________________________________ -first_damage = (u'NAH NOOB?', (1, 1, 1), '6') -second_damage = (u'GOOD!', (0, 1, 0), '2') -third_damage = (u'DAMN!', (0, 0, 0), '3') -fourth_damage = (u'SRY NOOB', (1, 1, 0), '4') -five_damage = (u'FAH!!!', (1, 0, 0), '5') +#################################### + +first_damage = (u'\ue042TRY AGAIN?\ue042', (0, 1, 0), '6') +second_damage = (u'\ue048GOOD!\ue048', (0, 1, 1), '2') +third_damage = (u'\ue041NICE!\ue041', (0.8, 0.4, 1), '3') +fourth_damage = (u'\ue049UPPS\ue049', (1, 1, 0), '4') +five_damage = (u'\ue043DEATH!\ue043', (1, 0, 0), '5') def custom_effects(pos: float, effect: str = None) -> None: if effect == '1': - bs.emitfx(position=pos, count=3, scale=0.1, spread=0.1, chunk_type='rock') + bs.emitfx( + position=pos, + count=3, + scale=0.1, + spread=0.1, + chunk_type='rock') elif effect == '2': - bs.emitfx(position=pos, count=70, scale=2, spread=0.8, chunk_type='spark') + bs.emitfx( + position=pos, + count=70, + scale=2, + spread=0.8, + chunk_type='spark') elif effect == '3': - bs.emitfx(position=pos, count=6, scale=0.3, spread=0.4, chunk_type='splinter') + bs.emitfx( + position=pos, + count=6, + scale=0.3, + spread=0.4, + chunk_type='splinter') elif effect == '4': - bs.emitfx(position=pos, count=50, scale=3.0, spread=0.9, chunk_type='ice') + bs.emitfx( + position=pos, + count=50, + scale=3.0, + spread=0.9, + chunk_type='ice') elif effect == '5': - bs.emitfx(position=pos, count=80, scale=3.0, spread=1.5, chunk_type='metal') + bs.emitfx( + position=pos, + count=80, + scale=3.0, + spread=1.5, + chunk_type='metal') elif effect == '6': - bs.emitfx(position=pos, count=30, scale=1.2, spread=0.8, chunk_type='slime') - - -def on_punched(self, damage: int) -> None: - pos = self.node.position - - def custom_text(msg: str, color: float) -> None: - text = bs.newnode('text', attrs={ - 'text': msg, - 'color': color, - 'in_world': True, - 'h_align': 'center', - 'shadow': 0.5, - 'flatness': 1.0, - }) - bs.animate_array(text, 'position', 3, { - 0.0: (pos[0], pos[1] + 1.2, pos[2]), - 2.0: (pos[0], pos[1] + 1.7, pos[2]), - }) - bs.animate(text, 'opacity', {0.8: 1.0, 2.0: 0.0}) - bs.animate(text, 'scale', {0: 0, 0.1: 0.017, 0.15: 0.014, 2.0: 0.016}) - bs.timer(2.0, text.delete) - - if damage < 200: - custom_text(first_damage[0], first_damage[1]) - custom_effects(pos, first_damage[2]) - elif damage < 500: - custom_text(second_damage[0], second_damage[1]) - custom_effects(pos, second_damage[2]) - elif damage < 800: - custom_text(third_damage[0], third_damage[1]) - custom_effects(pos, third_damage[2]) - elif damage < 1000: - custom_text(fourth_damage[0], fourth_damage[1]) - custom_effects(pos, fourth_damage[2]) - else: - custom_text(five_damage[0], five_damage[1]) - custom_effects(pos, five_damage[2]) + bs.emitfx( + position=pos, + count=30, + scale=1.2, + spread=0.8, + chunk_type='slime') +#################################### # 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 - try: - from bascenev1lib.actor.spazbot import SpazBot - SpazBot.on_punched = on_punched - except Exception: - pass - bs.broadcastmessage(u' || WELCOME || ', color=(0, 1, 0)) diff --git a/plugins/utilities/enhanced_effects.py b/plugins/utilities/enhanced_effects.py deleted file mode 100644 index d78f561..0000000 --- a/plugins/utilities/enhanced_effects.py +++ /dev/null @@ -1,305 +0,0 @@ -# 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 diff --git a/plugins/utilities/fluffyplaylisteditor.py b/plugins/utilities/fluffyplaylisteditor.py deleted file mode 100644 index 6f81fc8..0000000 --- a/plugins/utilities/fluffyplaylisteditor.py +++ /dev/null @@ -1,2122 +0,0 @@ -# ba_meta require api 9 - -from bascenev1._playlist import filter_playlist # , PlaylistType -from random import choice # , randint, randrange -from bascenev1 import ( - get_filtered_map_name, - get_map_class, - get_map_display_string, -) -from typing import Callable, Sequence, Literal, TypedDict, Any, cast -from bauiv1lib.confirm import ConfirmWindow -from bauiv1 import ( - # Window utils - Window, MainWindowState, - MainWindow, - UIScale, - - # Widget utils - Widget, widget, - containerwidget, - scrollwidget, - columnwidget, - buttonwidget as original_buttonwidget, - textwidget, - imagewidget, - checkboxwidget, - get_special_widget, - # Other - gettexture, - Mesh, Texture, Lstr, -) -from babase import ( - get_virtual_screen_size -) -from bauiv1lib.playlist.editcontroller import PlaylistEditController -import bauiv1lib.playlist.editcontroller -from bauiv1lib.playlist.editgame import PlaylistEditGameWindow -import bauiv1lib.playlist.editgame -from bauiv1lib.playlist.edit import PlaylistEditWindow -import bauiv1lib.playlist.edit -from bauiv1lib.playlist import PlaylistTypeVars -from babase import app, Plugin -import babase -import bascenev1 as bs -import bauiv1 as bui -from copy import deepcopy -import logging -plugman = dict( - plugin_name="fluffyplaylisteditor", - description="A simple not-so advanced Playlist Editor", - external_url="https://discord.com/channels/1001896771347304639/1483463979450896445", - authors=[ - {"name": "FluffyPal", "email": "", "discord": "fluffypal"} - ], - version="1.1.0", -) - -# Started: 12 Mar 2026 at night from scratch -# - Learning workflow -# - configuring packages -# - PlaylistEditWindow UI Polishing (I HATE UI POLISHING) - -# Continued: 13 Mar 2026 -# - PlaylistEditGameWindow UI Polishing (AAAA.. oh, found a bug) -# - Implemented GameEdit configs reset and restore -# - Implemented GameEdit map quick navigation buttons -# - Lazy TypedDict PlaylistType - -# Continued: 17 Mar 2026 -# - PlaylistEditController logics overwrite -# - Implemented PlaylistEdit duplicate game -# - Refactor filter_playlist -# - Implemented logics to show invalid games/maps in PlaylistEdit -# - Refine GameEdit reset and restore - -# Continued: 7 June 2026 -# - Fixed map select not recorded on MapSelect -# - Added batch game adding within supported maps - - -# ======= PLAYLIST PARENT =======# - -# ======= PLAYLIST BORWSER =======# -# Main Playlist Browser Window For Starting The Playlist -# Then goes to PlaylistCustomizeBrowserWindow if pressing "customize..." -# from bauiv1lib.playlist.browser import PlaylistBrowserWindow - -# import bauiv1lib.playlist.customizebrowser -# from bauiv1lib.playlist.customizebrowser import PlaylistCustomizeBrowserWindow - -# ======= PLAYLIST ADDING/EDITING =======# -# Main packages to modify -# - Game edit window - -# from bauiv1lib.playlist.addgame import PlaylistAddGameWindow - -# - Playlist games edit window - -# Window for adding registered games to the playlist and then goes to PlaylistEditController for editing -# The UI that shows games GameActivities names on the left and desc on the right -# And "Get More Games..." on the bottom -# After selecting the game, we goes to PlaylistEditGameWindow for configuring the game Settings and Map - -# - Game map select window -# import bauiv1lib.playlist.mapselect -# from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow - - -# ======= PLAYLIST PLAYING =======# -# This is used when we wanna "play" the playlist -# from bauiv1lib.play import PlayWindow, PlaylistSelectContext - -# from bauiv1lib.playoptions import PlayOptionsWindow - - -# ======= UI Packages =======# - -# ======= Type Hints =======# -# from enum import Enum - - -class UntotalOldTypedPlaylistDict(TypedDict, total=False): - map: str - level: str - resolved_type: type[bs.GameActivity] | None - is_unowned_map: bool - is_unowned_game: bool - is_map_invalid: bool - is_game_invalid: bool - - -class OldTypedPlaylistDict(TypedDict): - map: str - level: str - - -class TypedPlaylistDict(UntotalOldTypedPlaylistDict): - settings: OldTypedPlaylistDict - type: str - - -TypedPlaylistType = list[TypedPlaylistDict] - -# -------------------------- UI TOOLS --------------------------# - - -def buttonwidget( - *, - edit: Widget | None = None, - parent: Widget | None = None, - id: str | None = None, - size: Sequence[float] | None = None, - position: Sequence[float] | None = None, - on_activate_call: Callable | None = None, - label: str | Lstr | None = None, - color: Sequence[float] | None = None, - down_widget: Widget | None = None, - up_widget: Widget | None = None, - left_widget: Widget | None = None, - right_widget: Widget | None = None, - # texture: Texture | None = None, - text_scale: float | None = None, - textcolor: Sequence[float] | None = None, - enable_sound: bool | None = None, - mesh_transparent: Mesh | None = None, - mesh_opaque: Mesh | None = None, - repeat: bool | None = None, - scale: float | None = None, - transition_delay: float | None = None, - on_select_call: Callable | None = None, - button_type: str | None = None, - extra_touch_border_scale: float | None = None, - selectable: bool | None = None, - show_buffer_top: float | None = None, - icon: Texture | None = None, - iconscale: float | None = None, - icon_tint: float | None = None, - icon_color: Sequence[float] | None = None, - autoselect: bool | None = None, - mask_texture: Texture | None = None, - tint_texture: Texture | None = None, - tint_color: Sequence[float] | None = None, - tint2_color: Sequence[float] | None = None, - text_flatness: float | None = None, - text_res_scale: float | None = None, - text_literal: bool | None = None, - opacity: float | None = None, - better_bg_fit: bool | None = None, -) -> Widget: - return original_buttonwidget( - edit=edit, - parent=parent, - id=id, - size=size, - position=position, - on_activate_call=on_activate_call, - label=label, - color=color, - down_widget=down_widget, - up_widget=up_widget, - left_widget=left_widget, - right_widget=right_widget, - texture=gettexture('white'), - text_scale=text_scale, - textcolor=textcolor, - enable_sound=enable_sound, - mesh_transparent=mesh_transparent, - mesh_opaque=mesh_opaque, - repeat=repeat, - scale=scale, - transition_delay=transition_delay, - on_select_call=on_select_call, - button_type=button_type, - extra_touch_border_scale=extra_touch_border_scale, - selectable=selectable, - show_buffer_top=show_buffer_top, - icon=icon, - iconscale=iconscale, - icon_tint=icon_tint, - icon_color=icon_color, - autoselect=autoselect, - mask_texture=mask_texture, - tint_texture=tint_texture, - tint_color=tint_color, - tint2_color=tint2_color, - text_flatness=text_flatness, - text_res_scale=text_res_scale, - text_literal=text_literal, - opacity=opacity, - better_bg_fit=better_bg_fit - ) - - -def create_container(size: tuple[float, float], **kwargs) -> Widget: - """Create the main window with overlay.""" - # Container - p = containerwidget( - # parent=get_special_widget('overlay_stack'), - background=False, - # transition='in_scale', - size=size, - **kwargs - ) - x, y = get_virtual_screen_size() - - # Background - imagewidget( - parent=p, - texture=gettexture('white'), - size=(x*2, y*2), - position=(-x*0.5, 0-475), - opacity=0.55, - color=(0, 0, 0) - ) - - # Main Container View - border_size_range = 1.0075 - b = imagewidget( - texture=gettexture('white'), - parent=p, - size=(size[0]*border_size_range, size[1]*border_size_range), - tilt_scale=6, - color=tuple(col*1.5 for col in color_theme.get_color('primary')) - ) - f = imagewidget( # Foreground - texture=gettexture('white'), - parent=p, - size=size, - color=color_theme.get_color('bg') - ) - - border_size_offset = border_size_range-(border_size_range//border_size_range.real) - imagewidget( - edit=b, - position=tuple(pos*-border_size_offset for pos in f.center) - ) - - return p - - -def create_confirm(callback: Callable, text: str) -> Callable: - return lambda: ConfirmWindow( - text=f"{text}?", - color=color_theme.get_color('primary'), # pyright: ignore[reportArgumentType] - action=callback - ) - - -_SOUND_VOL = 1.2 - - -def play_error_sound(): - bui.getsound('error').play(_SOUND_VOL) - - -def play_guncocking_sound(): - bui.getsound('gunCocking').play(_SOUND_VOL) - - -def play_ding_sound(): - bui.getsound('ding').play(_SOUND_VOL) - - -def play_powerdown_sound(): - bui.getsound('powerdown01').play(_SOUND_VOL) - - -def play_shield_down_sound(): - bui.getsound('shieldDown').play(_SOUND_VOL) - - -def play_shield_up_sound(): - bui.getsound('shieldUp').play(_SOUND_VOL) - - -def play_deek_sound(): - bui.getsound(choice(['deek', 'deek2'])).play(_SOUND_VOL) - - -def play_click_sound(): - bui.getsound('click01').play(_SOUND_VOL) - - -class ScreenmessageColors: - RED = (1, 0, 0) - GREEN = (0, 1, 0) - BS_GREEN = (0.6, 1, 0.6) - BLUE = (0, 0, 1) - ORANGE = (1, 0.5, 0) - YELLOW = (1, 1, 0) - PURPLE = (0.6, 0, 1) - CYAN = (0, 1, 1) - MAGENTA = (1, 0, 1) - WHITE = (1, 1, 1) - BLACK = (0, 0, 0) - GRAY = (0.5, 0.5, 0.5) - PINK = (1, 0.4, 0.7) - - -_COLOR_TYPE = Literal[ - 'bg', 'primary', 'secondary', 'tertiary', 'unknown' -] - - -class ColorTheme: - main_color: Sequence[float] = (0.8, 0.8, 0.8) - colors: dict[_COLOR_TYPE, Sequence[float]] = { - 'bg': tuple(col*0.1 for col in main_color), - 'primary': main_color, - 'secondary': tuple(col*0.2 for col in main_color), - 'tertiary': tuple(col*0.4 for col in main_color), - 'unknown': (main_color[0]*1.2, main_color[1], main_color[2]) - } - - def get_color(self, color: _COLOR_TYPE) -> Sequence[float]: - return self.colors[color] - - -color_theme = ColorTheme() - - -# -------------------------- MAIN --------------------------# -class FluffyPlaylistEditController(PlaylistEditController): - def __init__( - self, - sessiontype: type[bs.Session], - from_window: MainWindow, - *, - existing_playlist_name: str | None = None, - playlist: TypedPlaylistType | None = None, - playlist_name: str | None = None, - ): - appconfig = bui.app.config - - # Since we may be showing our map list momentarily, - # lets go ahead and preload all map preview textures. - if app.classic is not None: - app.classic.preload_map_preview_media() - - self._sessiontype = sessiontype - - self._editing_game = False - self._editing_game_type: type[bs.GameActivity] | None = None - self._pvars = PlaylistTypeVars(sessiontype) - self._existing_playlist_name = existing_playlist_name - self._config_name_full = self._pvars.config_name + ' Playlists' - - self._is_batch_add = False - self._pre_game_add_state: MainWindowState | None = None - self._pre_game_edit_state: MainWindowState | None = None - - # Make sure config exists. - if self._config_name_full not in appconfig: - appconfig[self._config_name_full] = {} - - self._selected_index = 0 - if existing_playlist_name: - self._name = existing_playlist_name - - # Filter out invalid games. - self._playlist = new_filter_playlist( - appconfig[self._pvars.config_name + ' Playlists'][ - existing_playlist_name - ], - sessiontype=sessiontype, - remove_unowned=False, - # add_resolved_type=True, # HACK: This would cause json error as we save a Python class which aren't serializable - name=existing_playlist_name, - print_exc=False - ) - self._edit_ui_selection = None - else: - if playlist is not None: - self._playlist = playlist - else: - self._playlist = [] - if playlist_name is not None: - self._name = playlist_name - else: - # Find a good unused name. - i = 1 - while True: - self._name = ( - self._pvars.default_new_list_name.evaluate() - + ((' ' + str(i)) if i > 1 else '') - ) - if ( - self._name - not in appconfig[self._pvars.config_name + ' Playlists'] - ): - break - i += 1 - - # Also we want it to start with 'add' highlighted since its empty - # and that's all they can do. - self._edit_ui_selection = 'add_button' - - editwindow = from_window.main_window_replace( - lambda: FluffyPlaylistEditWindow(editcontroller=self) - ) - assert editwindow is not None - - # Once we've set our start window, store the back state. We'll - # skip back to there once we're fully done. - self._back_state = editwindow.main_window_back_state - - def duplicate_game_pressed(self) -> bool: - """Duplicate the currently selected game in the playlist and insert it just after.""" - if not self._playlist or self._selected_index is None: - bs.screenmessage("No game selected to duplicate", ScreenmessageColors.ORANGE) - play_error_sound() - return False - - base_entry = self._playlist[self._selected_index] - new_entry = deepcopy(base_entry) - - ins_index = self._selected_index + 1 - self._playlist.insert(ins_index, new_entry) - self._selected_index = ins_index - self._edit_ui_selection = None - - try: - cls = babase.getclass(new_entry['type'], bs.GameActivity) - name = cls.getname() - except (ImportError, AttributeError): - name = new_entry['type'] - - bs.screenmessage(f"Game '{name}' duplicated", ScreenmessageColors.BS_GREEN) - return True - - def toggle_epic_mode(self) -> bool: - if not self._playlist or self._selected_index is None: - bs.screenmessage("No game selected to toggle epic mode", ScreenmessageColors.ORANGE) - play_error_sound() - return False - - entry = self._playlist[self._selected_index] - if (epic_setting := 'Epic Mode') in entry['settings'] and isinstance(entry['settings'][epic_setting], bool): - # pyright: ignore[reportGeneralTypeIssues] - self._playlist[self._selected_index]['settings'][epic_setting] = not entry['settings'][epic_setting] - else: - play_error_sound() - return False - return True - - def toggle_solo_mode(self) -> bool: - if not self._playlist or self._selected_index is None: - bs.screenmessage("No game selected to toggle solo mode", ScreenmessageColors.ORANGE) - play_error_sound() - return False - - entry = self._playlist[self._selected_index] - if (solo_setting := 'Solo Mode') in entry['settings'] and isinstance(entry['settings'][solo_setting], bool): - # pyright: ignore[reportGeneralTypeIssues] - self._playlist[self._selected_index]['settings'][solo_setting] = not entry['settings'][solo_setting] - else: - play_error_sound() - return False - return True - - def reslove_default_settings_defs(self, settings: OldTypedPlaylistDict) -> list[bs.Setting]: - default_settings = list[bs.Setting]() - if (epic_mode := 'Epic Mode') in settings: - default_settings.append( - bs.BoolSetting(epic_mode, default=settings[epic_mode]) - ) - if (solo_mode := 'Solo Mode') in settings: - default_settings.append( - bs.BoolSetting(solo_mode, default=settings[solo_mode]) - ) - if (time_limit := 'Time Limit') in settings: - default_settings.append( - bs.IntChoiceSetting( - time_limit, - choices=[ - ('None', 0), - ('1 Minute', 60), - ('2 Minutes', 120), - ('5 Minutes', 300), - ('10 Minutes', 600), - ('20 Minutes', 1200), - ], - default=settings[time_limit], - ) - ) - if (respawn_times := 'Respawn Times') in settings: - default_settings.append( - bs.FloatChoiceSetting( - respawn_times, - choices=[ - ('Shorter', 0.25), - ('Short', 0.5), - ('Normal', 1.0), - ('Long', 2.0), - ('Longer', 4.0), - ], - default=settings[respawn_times], - ) - ) - return default_settings - - def edit_game_pressed(self, from_window: MainWindow) -> None: - if not self._playlist: - return - - playlist = self._playlist[self._selected_index] - try: - cls = babase.getclass( - playlist['type'], - subclassof=bs.GameActivity - ) - except AttributeError as e: - bs.screenmessage( - f'Can\'t edit game: {e}. Maybe try fix it?', ScreenmessageColors.YELLOW) - play_error_sound() - return - - except ModuleNotFoundError as e: - class FakeGameActivity(bs.GameActivity): - name = playlist['type'] - available_settings = self.reslove_default_settings_defs(playlist['settings']) - cls = FakeGameActivity - - self._show_edit_ui( - gametype=cls, - settings=playlist['settings'], # pyright: ignore[reportArgumentType] - from_window=from_window, - ) - - def _show_edit_ui( # pyright: ignore[reportIncompatibleMethodOverride] - self, - gametype: type[bs.GameActivity], - settings: TypedPlaylistDict | None, - from_window: bui.MainWindow, - ) -> None: - # pylint: disable=cyclic-import - if not from_window.main_window_has_control(): - return - - self._editing_game = settings is not None - self._editing_game_type = gametype - assert self._sessiontype is not None - - # Jump into an edit window. - editwindow = from_window.main_window_replace( - lambda: FluffyPlaylistEditGameWindow( - gametype, - self._sessiontype, - settings, - completion_call=self._edit_game_done, - ) - ) - assert editwindow is not None - - # Once we're there, store the back state. We'll use that to jump - # back out to our current location once the edit is done. - assert self._pre_game_edit_state is None - self._pre_game_edit_state = editwindow.main_window_back_state - - def _edit_game_done( # pyright: ignore[reportIncompatibleMethodOverride] - self, config: TypedPlaylistDict | None, from_window: bui.MainWindow - ) -> None: - """Called after finished editing/adding a game""" - # No-op if provided window isn't in charge. - if not from_window.main_window_has_control(): - return - - assert bui.app.classic is not None - if config is None: - play_powerdown_sound() - else: - # Make sure type is in there. - assert self._editing_game_type is not None - if self._editing_game: - playlist = self._playlist[self._selected_index] - config['type'] = playlist['type'] - else: - config['type'] = bui.get_type_name(self._editing_game_type) - - if self._editing_game: - self._playlist[self._selected_index] = deepcopy(config) - else: - # Add a new entry to the playlist. - insert_index = min( - len(self._playlist), self._selected_index + 1 - ) - - self._selected_index = insert_index - self._playlist.insert(insert_index, deepcopy(config)) - - if self._is_batch_add: - for _map in self._get_supported_maps_for_game(config, self._editing_game_type): - new_config = deepcopy(config) - new_config['map'] = _map - new_config['settings']['map'] = _map - self._playlist.insert(insert_index, new_config) - insert_index += 1 - - play_guncocking_sound() - - # If we're adding, jump to before the add started. - # Otherwise jump to before the edit started. - assert ( - self._pre_game_edit_state is not None - or self._pre_game_add_state is not None - ) - if self._pre_game_add_state is not None: - from_window.main_window_back_state = self._pre_game_add_state - elif self._pre_game_edit_state is not None: - from_window.main_window_back_state = self._pre_game_edit_state - - from_window.main_window_back() - self._pre_game_edit_state = None - self._pre_game_add_state = None - self._is_batch_add = False - - def _get_supported_maps_for_game(self, config: TypedPlaylistDict, gametype: type[bs.GameActivity]): - assert app.classic is not None - store = app.classic.store - - valid_maps = set(gametype.get_supported_maps(self._sessiontype)) - to_remove = set(store.get_unowned_maps()) - - if config_map := config.get('map') or (config.get('settings', {}) or {}).get('map'): - to_remove.add(config_map) - - valid_maps -= to_remove - - return sorted(valid_maps) - - def batch_add_game_pressed(self, from_window: MainWindow): - self._is_batch_add = True - self.add_game_pressed(from_window) - - -class FluffyPlaylistEditWindow(PlaylistEditWindow): - def __init__( - self, - editcontroller: FluffyPlaylistEditController, - transition: str | None = 'in_right', - origin_widget: Widget | None = None, - ): - self._list_widgets: list[Widget] = [] - self._editcontroller = editcontroller - editcontroller._is_batch_add = False # HACK: Couldn't think of any clean idea :p - - self._r = 'editGameListWindow' - prev_selection: str | None = self._editcontroller.get_edit_ui_selection() - - # Container Setup - uiscale = app.ui_v1.uiscale - is_small_ui = uiscale is UIScale.SMALL - # Keep a constant aspect ratio for all UIScale values. - # Let's use width:height (classic UI aspect) as a reference. - # We'll use a base width and scale it for each UI scale while maintaining the ratio. - base_width = 800 - aspect_ratio = 16 / 9 - self._width = base_width * 1.65 - - self._height = self._width / aspect_ratio - - # Adjust x_inset proportionally to width - self.x_inset = x_inset = int(self._width * (0.09 if is_small_ui else 0.02)) - self.yoffs = yoffs = -68 if is_small_ui else -15 - - # Round to integers for pixel positions - self._width = int(self._width) - self._height = int(self._height) - - MainWindow.__init__(self, # pyright: ignore[reportArgumentType] - root_widget=create_container( - size=(self._width, self._height), - scale=( - 1.20 if is_small_ui else - 0.80 if uiscale is UIScale.MEDIUM else - 0.60 - ), - toolbar_visibility=( - 'menu_minimal_no_back' - if is_small_ui - else 'menu_full' - ), - ), - transition=transition, - origin_widget=origin_widget, - ) - - # Widgets/children - # Buttons - self.b_color = b_color = color_theme.get_color('primary') - self.b_textcolor = b_textcolor = color_theme.get_color('secondary') - - cancel_button = buttonwidget( - parent=self._root_widget, - position=(35 + x_inset, self._height - 50 + yoffs), - scale=0.8, - size=(175, 60), - autoselect=True, - color=(b_color[0]*1.2, *b_color[1:]), - textcolor=b_textcolor, - label=bui.Lstr(resource='cancelText'), - text_scale=1.2, - ) - save_button = buttonwidget( - parent=self._root_widget, - position=( - self._width - ((225 if is_small_ui else 255) + x_inset), - self._height - 50 + yoffs - ), - scale=0.8, - size=(190, 60), - autoselect=True, - color=(b_color[0], b_color[1]*1.2, b_color[2]), - textcolor=b_textcolor, - left_widget=cancel_button, - label=bui.Lstr(resource='saveText'), - text_scale=1.2, - ) - - widget( - edit=save_button, - right_widget=bui.get_special_widget('squad_button'), - ) - - widget( - edit=cancel_button, - left_widget=cancel_button, - right_widget=save_button, - ) - - v_gap = 60 - h = 40 + x_inset - r_h = x_inset * (9.45 if is_small_ui else 45.25) - - left_button_v_size = 45 - right_button_v_size = 45 - - v = self._height - 172.0 + yoffs - v *= 1.1 - v -= 2.0 - v -= v_gap - buttonwidget( - parent=self._root_widget, - position=(r_h, v), - size=(110, right_button_v_size), - on_activate_call=self._toggle_epic_mode, - enable_sound=False, - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - text_scale=0.8, - label="Toggle\nEpic Mode", - ) - v -= v_gap - - buttonwidget( - parent=self._root_widget, - position=(r_h, v), - size=(110, right_button_v_size), - on_activate_call=self._toggle_solo_mode, - enable_sound=False, - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - text_scale=0.8, - label="Toggle\nSolo Mode", - ) - v -= v_gap - - v = self._height - 172.0 + yoffs - v *= 1.1 - v -= 2.0 - v -= v_gap - - add_game_button = buttonwidget( - parent=self._root_widget, - position=(h, v), - size=(110, left_button_v_size), - on_activate_call=self._add, - on_select_call=bui.CallPartial(self._set_ui_selection, 'add_button'), - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - text_scale=0.8, - label=bui.Lstr(resource=f'{self._r}.addGameText'), - ) - v -= v_gap - - batch_add_game_button = buttonwidget( - parent=self._root_widget, - position=(h, v), - size=(110, left_button_v_size), - on_activate_call=self._batch_add, - on_select_call=bui.CallPartial(self._set_ui_selection, 'batch_add_button'), - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - text_scale=0.8, - label=f"Batch {bui.Lstr(resource=f'{self._r}.addGameText').evaluate()}", - ) - v -= v_gap - - self._edit_button = edit_game_button = buttonwidget( - parent=self._root_widget, - position=(h, v), - size=(110, left_button_v_size), - on_activate_call=self._edit, - on_select_call=bui.CallPartial(self._set_ui_selection, 'editButton'), - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - text_scale=0.8, - label=bui.Lstr(resource=f'{self._r}.editGameText'), - ) - v -= v_gap - - remove_game_button = buttonwidget( - parent=self._root_widget, - position=(h, v), - size=(110, left_button_v_size), - text_scale=0.8, - on_activate_call=self._remove, - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - label=bui.Lstr(resource=f'{self._r}.removeGameText'), - ) - v -= v_gap - - # Customs - duplicate_button = buttonwidget( - parent=self._root_widget, - position=(h, v), - size=(110, left_button_v_size), - on_activate_call=self._duplicate_selected_game, - autoselect=True, - enable_sound=False, - button_type='square', - color=b_color, - textcolor=b_textcolor, - text_scale=0.8, - label="Duplicate\nGame", - ) - v -= v_gap - 10 - - h += 9 - buttonwidget( - parent=self._root_widget, - position=(h, v), - size=(42, 35), - on_activate_call=self._move_up, - label=bui.charstr(bui.SpecialChar.UP_ARROW), - button_type='square', - color=b_color, - textcolor=b_textcolor, - autoselect=True, - repeat=True, - ) - h += 52 - buttonwidget( - parent=self._root_widget, - position=(h, v), - size=(42, 35), - on_activate_call=self._move_down, - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - label=bui.charstr(bui.SpecialChar.DOWN_ARROW), - repeat=True, - ) - - # Scroller - v = self._height - 100 + yoffs - scroll_height = self._height - ( - 250 if is_small_ui else 155 - ) - self._scroll_width = self._width - (205 + (2.5 if is_small_ui else 5.5) * x_inset) - - scrollwidget = bui.scrollwidget( - parent=self._root_widget, - position=(160 + x_inset, v - scroll_height), - highlight=False, - on_select_call=bui.CallStrict(self._set_ui_selection, 'gameList'), - size=(self._scroll_width, (scroll_height - 15)), - border_opacity=0.4, - ) - widget( - edit=scrollwidget, - left_widget=add_game_button, - right_widget=scrollwidget, - ) - self._columnwidget = columnwidget( - parent=scrollwidget, border=2, margin=0 - ) - - for button in [ - add_game_button, batch_add_game_button, edit_game_button, remove_game_button, duplicate_button - ]: - widget( - edit=button, left_widget=button, right_widget=scrollwidget - ) - - buttonwidget(edit=cancel_button, on_activate_call=self._cancel) - containerwidget( - edit=self._root_widget, - cancel_button=cancel_button, - selected_child=scrollwidget, - ) - - buttonwidget(edit=save_button, on_activate_call=self._save_press) - containerwidget(edit=self._root_widget, start_button=save_button) - - # Texts - textwidget( - parent=self._root_widget, - position=(-10, self._height - 50 + yoffs), - size=(self._width, 25), - text=bui.Lstr(resource=f'{self._r}.titleText'), - color=app.ui_v1.title_color, - scale=1.05, - h_align='center', - v_align='center', - maxwidth=270, - ) - - v = self._height - 115.0 + yoffs - - textwidget( - parent=self._root_widget, - text=bui.Lstr(resource=f'{self._r}.listNameText'), - position=(196 + x_inset, v + 31), - maxwidth=150, - color=(0.8, 0.8, 0.8, 0.5), - size=(0, 0), - scale=0.75, - h_align='right', - v_align='center', - ) - - self._text_field = text_field = textwidget( - parent=self._root_widget, - position=(210 + x_inset, v + 7), - size=(self._scroll_width - 53, 43), - text=self._editcontroller.getname(), - h_align='left', - v_align='center', - max_chars=40, - maxwidth=380, - autoselect=True, - color=(0.9, 0.9, 0.9, 1.0), - description=bui.Lstr(resource=f'{self._r}.listNameText'), - editable=True, - padding=4, - on_return_press_call=self._save_press_with_sound, - ) - widget(edit=self._columnwidget, up_widget=text_field) - widget(edit=cancel_button, down_widget=text_field) - widget(edit=add_game_button, up_widget=text_field) - widget(edit=batch_add_game_button, up_widget=add_game_button) - - if prev_selection == 'add_button': - containerwidget( - edit=self._root_widget, selected_child=add_game_button - ) - elif prev_selection == 'batch_add_button': - containerwidget( - edit=self._root_widget, selected_child=batch_add_game_button - ) - elif prev_selection == 'editButton': - containerwidget( - edit=self._root_widget, selected_child=edit_game_button - ) - elif prev_selection == 'gameList': - containerwidget( - edit=self._root_widget, selected_child=scrollwidget - ) - - self._refresh() - - def _get_invalid_game_name(self, pentry: TypedPlaylistDict): - name = pentry['type'] - # A few substitutions for 'Epic', 'Solo' etc. modes. - # FIXME: Should provide a way for game types to define filters of - # their own and should not rely on hard-coded settings names. - if (solo_mode := 'Solo Mode') in pentry['settings'] and pentry['settings'][solo_mode]: - name = babase.Lstr( - resource='soloNameFilterText', subs=[('${NAME}', name)] - ) - if (epic_mode := 'Epic Mode') in pentry['settings'] and pentry['settings'][epic_mode]: - name = babase.Lstr( - resource='epicNameFilterText', subs=[('${NAME}', name)] - ) - - # Resolve map name - if 'map' in pentry['settings']: - sval = babase.Lstr( - value='${NAME} @ ${MAP}', - subs=[ - ('${NAME}', name), - ('${MAP}', bs.get_map_display_string( - bs.get_filtered_map_name(pentry['settings']['map'])), - ), - ], - ) - elif 'map' in pentry: - sval = babase.Lstr( - value='${NAME} @ ${MAP}', - subs=[ - ('${NAME}', name), - ('${MAP}', bs.get_map_display_string( - bs.get_filtered_map_name(pentry['map'])), - ), - ], - ) - else: - print('invalid game config - expected map entry under settings') - sval = babase.Lstr(value='???') - - return sval - - def _duplicate_selected_game(self) -> None: - if self._editcontroller.duplicate_game_pressed(): - play_guncocking_sound() - self._refresh() - - def _move_down(self) -> None: - if len(self._editcontroller.get_playlist()) > 1: - super()._move_down() - - def _move_up(self) -> None: - if len(self._editcontroller.get_playlist()) > 1: - super()._move_up() - - def _toggle_epic_mode(self): - if self._editcontroller.toggle_epic_mode(): - play_guncocking_sound() - self._refresh() - - def _toggle_solo_mode(self): - if self._editcontroller.toggle_solo_mode(): - play_guncocking_sound() - self._refresh() - - def _refresh(self) -> None: - # Need to grab this here as rebuilding the list will - # change it otherwise. - old_selection_index = self._editcontroller.get_selected_index() - - while self._list_widgets: - self._list_widgets.pop().delete() - # pyright: ignore[reportAssignmentType] - for index, pentry in enumerate(self._editcontroller.get_playlist()): - pentry: TypedPlaylistDict - try: - cls = babase.getclass(pentry['type'], subclassof=bs.GameActivity) - # pyright: ignore[reportArgumentType] - desc = cls.get_settings_display_string(pentry) - color = (0.8, 0.8, 0.8, 1.0) - except Exception: - # logging.exception('Error in playlist refresh.') - # desc = "(invalid: '" + pentry['type'] + "')" - desc = self._get_invalid_game_name(pentry) - color = color_theme.get_color('unknown') - - txtw = textwidget( - parent=self._columnwidget, - size=(self._width - 80, 30), - on_select_call=bui.CallStrict(self._select, index), - always_highlight=True, - color=color, - padding=0, - maxwidth=self._scroll_width * 0.93, - text=desc, - on_activate_call=self._edit_button.activate, - v_align='center', - selectable=True, - ) - widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50) - - # Wanna be able to jump up to the text field from the top one. - if index == 0: - widget(edit=txtw, up_widget=self._text_field) - self._list_widgets.append(txtw) - if old_selection_index == index: - columnwidget( - edit=self._columnwidget, - selected_child=txtw, - visible_child=txtw, - ) - - def _batch_add(self) -> None: - # Store list name then tell the session to perform an add. - self._editcontroller.setname( - cast(str, bui.textwidget(query=self._text_field)) - ) - self._editcontroller.batch_add_game_pressed(from_window=self) - - -class FluffyPlaylistEditGameWindow(PlaylistEditGameWindow): - - def __init__( - self, - gametype: type[bs.GameActivity], - sessiontype: type[bs.Session], - config: TypedPlaylistDict | None, - completion_call: Callable[[TypedPlaylistDict | None, bui.MainWindow], Any], - default_selection: str | None = None, - transition: str | None = 'in_right', - origin_widget: bui.Widget | None = None, - edit_info: dict[str, Any] | None = None - ): - - assert app.classic is not None - store = app.classic.store - - self._scrollwidget: Widget | None = None - self._subcontainer: Widget | None = None - - self._gametype = gametype - self._sessiontype = sessiontype - - # If we're within an editing session we get passed edit_info - # (returning from map selection window, etc). - if edit_info is not None: - self._edit_info = edit_info - - # ..otherwise determine whether we're adding or editing a game based - # on whether an existing config was passed to us. - else: - if config is None: - self._edit_info = {'editType': 'add'} - else: - self._edit_info = {'editType': 'edit'} - - self._r = 'gameSettingsWindow' - - self._valid_maps = valid_maps = gametype.get_supported_maps(sessiontype) - if not valid_maps: - bui.screenmessage(bui.Lstr(resource='noValidMapsErrorText')) - raise RuntimeError('No valid maps found.') - self._valid_maps_owned = [m for m in self._valid_maps if m not in store.get_unowned_maps()] - - self._config = config - - self._settings_defs = gametype.get_available_settings(sessiontype) - self._completion_call = completion_call - - # If there's a valid map name in the existing config, use that. - self._map: str | None = None - # To start with, pick a random map out of the ones we own. - unowned_maps = store.get_unowned_maps() - try: - if ( - config is not None - and 'map' in config - ): - filtered_map_name = get_filtered_map_name( - config['map'] - ) - if filtered_map_name not in unowned_maps: - self._map = filtered_map_name - elif ( - config is not None - and (settings := config.get('settings')) - and (raw_map := settings.get('map')) - ): - filtered_map_name = get_filtered_map_name(raw_map) - if filtered_map_name not in unowned_maps: - self._map = filtered_map_name - # else: - # raise Exception() - except Exception: - logging.exception('Error getting map for editor.') - - if not self._map: - if valid_maps_owned := [m for m in valid_maps if m not in unowned_maps]: - self._map = choice(valid_maps_owned) - # Hmmm.. we own none of these maps.. just pick a random un-owned one - # I guess.. should this ever happen? - else: - self._map = choice(valid_maps) - - if config is not None: - if 'settings' in config: - self._settings = config['settings'] - else: - self._settings = config - else: - self._settings: OldTypedPlaylistDict = {} # pyright: ignore[reportAttributeAccessIssue] - self._settings['map'] = self._map - - self._default_settings = deepcopy(self._settings) - - try: - self.map_tex_name = get_map_class(self._map).get_preview_texture_name() - except babase.NotFoundError: - self.map_tex_name = 'null' - - if self.map_tex_name is None: - raise RuntimeError(f'No map preview tex found for {self._map}.') - self._choice_selections: dict[str, int] = {} - - # Container Setup - uiscale = app.ui_v1.uiscale - is_small_ui = uiscale is UIScale.SMALL - # Keep a constant aspect ratio for all UIScale values. - # Let's use width:height (classic UI aspect) as a reference. - # We'll use a base width and scale it for each UI scale while maintaining the ratio. - base_width = 900 - aspect_ratio = 5 / 4 - self._width = width = int(base_width * 1.65) - self._height = height = int(base_width / aspect_ratio) - - y_extra2 = 50 # For topper widget elements - - # Adjust x_inset proportionally to width - self.x_inset = int(width * (0.09 if is_small_ui else 0.0225)) - self.yoffs = yoffs = -68 if is_small_ui else -30 - - MainWindow.__init__(self, - root_widget=containerwidget( - size=(width, height), - color=color_theme.get_color('bg'), - scale=( - 1.10 if is_small_ui else - 0.80 if uiscale is UIScale.MEDIUM else - 0.70 - ), - toolbar_visibility=( - 'menu_minimal_no_back' - if uiscale is UIScale.SMALL - else 'menu_full' - ), - ), - transition=transition, - origin_widget=origin_widget, - ) - - # Widgets/children - b_color = color_theme.get_color('primary') - b_textcolor = color_theme.get_color('secondary') - - is_add = self._edit_info['editType'] == 'add' - cancel_button = original_buttonwidget( - parent=self._root_widget, - position=(45 + self.x_inset, height - 82 + y_extra2 + yoffs), - size=(60, 48) if is_add else (180, 65), - label=( - bui.charstr(bui.SpecialChar.BACK) if is_add else - bui.Lstr(resource='cancelText') - ), - button_type='backSmall' if is_add else None, - autoselect=True, - scale=1.0 if is_add else 0.75, - text_scale=1.3, - color=(b_color[0]*1.2, *b_color[1:]), - textcolor=b_textcolor, - on_activate_call=bui.CallStrict(self._cancel), - ) - containerwidget(edit=self._root_widget, cancel_button=cancel_button) - - # Title - textwidget( - parent=self._root_widget, - position=((-20 if is_small_ui else -18), height - 70 + y_extra2 + yoffs), - size=(width, 25), - text=gametype.get_display_string(), - color=bui.app.ui_v1.title_color, - maxwidth=width*0.35, - scale=1.1, - h_align='center', - v_align='center', - ) - - self.add_button = add_button = original_buttonwidget( - parent=self._root_widget, - position=(width - ((255 if is_small_ui else 235) + self.x_inset), - height - 82 + y_extra2 + yoffs), - size=(200, 65), - scale=0.75, - text_scale=1.3, - color=(b_color[0], b_color[1]*1.2, b_color[2]), - textcolor=b_textcolor, - label=( - bui.Lstr(resource=f'{self._r}.addGameText') if is_add else - bui.Lstr(resource='applyText') - ), - ) - - base_h_pos = width - ((155 if is_small_ui else 135) + self.x_inset) - base_v_pos = height * 0.75 - right_buttons_gap = 75 - - reset_config_text = "Reset\nSettings" - original_buttonwidget( - parent=self._root_widget, - position=(base_h_pos, base_v_pos - right_buttons_gap), - size=(200, 100), - scale=0.75, - text_scale=1.3, - color=b_color, - textcolor=b_textcolor, - label=reset_config_text, - on_activate_call=create_confirm( - self._reset_settings, reset_config_text.replace('\n', ' ')), - icon=gettexture('replayIcon'), - iconscale=1.5 - ) - right_buttons_gap += right_buttons_gap - - restore_config_text = "Restore\nSettings" - original_buttonwidget( - parent=self._root_widget, - position=(base_h_pos, base_v_pos - right_buttons_gap), - size=(200, 100), - scale=0.75, - text_scale=1.3, - color=b_color, - textcolor=b_textcolor, - label=restore_config_text, - on_activate_call=( - create_confirm(self._restore_settings, restore_config_text.replace( - '\n', ' ')) if not is_add else play_error_sound - ), - icon=gettexture('leftButton'), - iconscale=1.5 - ) - - self._refresh_settings_items() - - original_buttonwidget( - edit=add_button, on_activate_call=bui.CallStrict(self._add) - ) - containerwidget( - edit=self._root_widget, - selected_child=add_button, - start_button=add_button, - ) - - if default_selection == 'map': - containerwidget( - edit=self._root_widget, selected_child=self._scrollwidget - ) - containerwidget( - edit=self._subcontainer, selected_child=self._map_buttonwidget - ) - - # Tools - def _reset_settings(self): - updated = False - if self._settings: - for setting in self._settings_defs: - if (value := setting.default) != (data := self._settings)[setting.name]: - data[setting.name] = value - updated = True - - if (def_map := self._default_settings['map']) != self._map: - self._map = def_map - try: - self.map_tex_name = get_map_class(def_map).get_preview_texture_name() - except babase.NotFoundError: - self.map_tex_name = 'null' - updated = True - - if updated: - self._refresh_settings_items() - play_shield_down_sound() - else: - play_error_sound() - - def _restore_settings(self): - updated = False - # Only update keys which are different, and track if anything actually changed - for key, value in self._default_settings.items(): - if key not in self._settings or self._settings[key] != value: - # print(f'key restored: {self._settings[key]} -> {key}') - self._settings[key] = value - updated = True - - # Check if map changed, update relevant attrs - restored_map = get_filtered_map_name(self._settings['map']) - if self._map != restored_map: - # print(f'map restored: {self._map} -> {restored_map}') - self._map = restored_map - try: - self.map_tex_name = get_map_class(self._map).get_preview_texture_name() - except babase.NotFoundError: - self.map_tex_name = 'null' - updated = True - - if updated: - self._refresh_settings_items() - self._update_map_widget() - play_ding_sound() - else: - play_error_sound() - - def _randomize_map(self): - valid_maps = self._valid_maps_owned - - # Hmmm.. we own none of these maps.. just pick a random un-owned one - # I guess.. should this ever happen? - cur_map = choice(valid_maps) - self._map = cur_map - self.map_tex_name = get_map_class(cur_map).get_preview_texture_name() - self._update_map_widget() - - play_deek_sound() - - def _update_map_widget(self): - assert self.map_tex_name and self._map - imagewidget( - edit=self._map_imagewidget, - texture=gettexture(self.map_tex_name) - ) - textwidget( - edit=self._map_textwidget, - text=get_map_display_string(self._map) - ) - - def _shift_selected_map(self, index: int): - """Shift selection in the valid maps list by target (wrap around)""" - valid_maps = self._valid_maps_owned - - cur_map = self._map - assert cur_map - try: - cur_map_index = valid_maps.index(cur_map) - except ValueError: - cur_map_index = 0 - new_index = (cur_map_index + index) % len(valid_maps) - - self._map = valid_maps[new_index] - self.map_tex_name = get_map_class(self._map).get_preview_texture_name() - - self._update_map_widget() - play_click_sound() - - def _refresh_settings_items(self): - uiscale = app.ui_v1.uiscale - is_small_ui = uiscale is UIScale.SMALL - - pbtn = get_special_widget('squad_button') - widget(edit=self.add_button, right_widget=pbtn, up_widget=pbtn) - - map_height = 100 - - scroll_width = self._width - (86 + (3 if is_small_ui else 5.5) * self.x_inset) - - spacing = 47 # Scroller playlist config items spacing - y_extra = 15 # For scroller widget - - # Calc our total height we'll need - scroll_height = map_height + 10 # map select and margin - scroll_height += spacing * len(self._settings_defs) - - if not self._scrollwidget: - self._scrollwidget = bui.scrollwidget( - parent=self._root_widget, - position=( - 44 + self.x_inset, - (95 if uiscale is UIScale.SMALL else 55) + y_extra + self.yoffs, - ), - size=( - scroll_width, - self._height - (166 if uiscale is UIScale.SMALL else 116), - ), - highlight=False, - claims_left_right=True, - selection_loops_to_parent=True, - border_opacity=0.4, - ) - if self._subcontainer: - for child in self._subcontainer.get_children(): - child.delete() - self._subcontainer.delete() - - self._subcontainer = containerwidget( - parent=self._scrollwidget, - size=(scroll_width, scroll_height), - background=False, - claims_left_right=True, - selection_loops_to_parent=True, - ) - - v = scroll_height - 5 - h = -40 - - # Keep track of all the selectable widgets we make so we can wire - # them up conveniently. - widget_column: list[list[bui.Widget]] = [] - - b_color = color_theme.get_color('primary') - b_textcolor = color_theme.get_color('secondary') - - textwidget( - parent=self._subcontainer, - position=(h + 49, v - 63), - size=(100, 30), - maxwidth=110, - text=bui.Lstr(resource='mapText'), - h_align='left', - color=(0.8, 0.8, 0.8, 1.0), - v_align='center', - ) - assert self.map_tex_name - map_tex = gettexture(self.map_tex_name) - - self._map_imagewidget = imagewidget( - parent=self._subcontainer, - size=(256 * 0.7, 125 * 0.7), - position=(h + scroll_width * 0.46, v - 90), - texture=map_tex, - mesh_opaque=bui.getmesh('level_select_button_opaque'), - mesh_transparent=bui.getmesh('level_select_button_transparent'), - mask_texture=gettexture('mapPreviewMask'), - ) - - self._map_buttonwidget = None - if len(self._valid_maps_owned) > 1: - original_buttonwidget( # map_prev - parent=self._subcontainer, - position=(h + scroll_width * 0.46 - 50 - 1, v - 63), - size=(35, 35), - label='<', - color=b_color, - textcolor=b_textcolor, - autoselect=True, - on_activate_call=bui.CallPartial(self._shift_selected_map, -1), - enable_sound=False, - repeat=True, - ) - original_buttonwidget( # map_next - parent=self._subcontainer, - position=(h + scroll_width * (0.655 if is_small_ui else 0.6225) + 5, v - 63), - size=(35, 35), - label='>', - color=b_color, - textcolor=b_textcolor, - autoselect=True, - on_activate_call=bui.CallPartial(self._shift_selected_map, 1), - enable_sound=False, - repeat=True, - ) - original_buttonwidget( - parent=self._subcontainer, - size=(140, 60), - position=(h + scroll_width * (0.775 if is_small_ui else 0.8), v - 72), - on_activate_call=bui.CallStrict(self._randomize_map), - enable_sound=False, - color=b_color, - textcolor=b_textcolor, - scale=0.7, - label="Randomize", - ) - # Map select button. - self._map_buttonwidget = original_buttonwidget( - parent=self._subcontainer, - size=(140, 60), - position=(h + scroll_width * 0.9, v - 72), - on_activate_call=bui.CallStrict(self._select_map), - scale=0.7, - color=b_color, - textcolor=b_textcolor, - label=bui.Lstr(resource='mapSelectText'), - ) - widget_column.append([self._map_buttonwidget]) - - assert self._map - self._map_textwidget = textwidget( - parent=self._subcontainer, - position=(h + scroll_width * 0.496, v - 114), - size=(100, 30), - flatness=1.0, - shadow=1.0, - scale=0.55, - maxwidth=256 * 0.7 * 0.8, - text=get_map_display_string(self._map), - h_align='center', - color=(0.6, 1.0, 0.6, 1.0), - v_align='center', - ) - v -= map_height - - config = self._settings - assert config - for setting in self._settings_defs: - value = setting.default - value_type = type(value) - - # Now, if there's an existing value for it in the config, - # override with that. - try: - if config is not None: - if ( - 'settings' in config - and (seeting_name := setting.name) in config['settings'] - ): - value = value_type(config['settings'][seeting_name]) - elif (seeting_name := setting.name) in config: - value = value_type(config[seeting_name]) - except Exception: - logging.exception('Error getting game setting.') - - # Shove the starting value in there to start. - self._settings[setting.name] = value - - name_translated = self._get_localized_setting_name(setting.name) - - mw1 = 280 - mw2 = 70 - - # Handle types with choices specially: - item_h_pos = h + scroll_width * 0.96 - if isinstance(setting, bs.ChoiceSetting): - invalid = False - for choice in setting.choices: - if len(choice) != 2: - raise ValueError( - "Expected 2-member tuples for 'choices'; got: " - + repr(choice) - ) - if not isinstance(choice[0], str): - raise TypeError( - 'First value for choice tuple must be a str; got: ' - + repr(choice) - ) - if not isinstance(choice[1], value_type): - invalid = True - # raise TypeError( - # 'Choice type does not match default value; choice:' - # + repr(choice) - # + '; setting:' - # + repr(setting) - # ) - if value_type not in (int, float): - raise TypeError( - 'Choice type setting must have int or float default; ' - 'got: ' + repr(setting) - ) - - # Start at the choice corresponding to the default if possible. - self._choice_selections[setting.name] = 0 - for index, choice in enumerate(setting.choices): - if choice[1] == value: - self._choice_selections[setting.name] = index - break - - v -= spacing - textwidget( - parent=self._subcontainer, - position=(h + 50, v), - size=(100, 30), - maxwidth=mw1, - text=name_translated, - h_align='left', - color=(0.8, 0.8, 0.8, 1.0), - v_align='center', - ) - txt = textwidget( - parent=self._subcontainer, - position=(item_h_pos - 95, v), - size=(0, 28), - text=self._get_localized_setting_name( - setting.choices[self._choice_selections[setting.name]][ - 0 - ] - ), - editable=False, - color=(0.6, 1.0, 0.6, 1.0), - maxwidth=mw2, - h_align='right', - v_align='center', - padding=2, - ) - btn1 = original_buttonwidget( - parent=self._subcontainer, - position=(item_h_pos - 50 - 1, v), - size=(28, 28), - label='<', - color=b_color, - textcolor=b_textcolor, - autoselect=True, - on_activate_call=bui.CallStrict( - self._choice_inc, setting.name, txt, setting, -1 - ) if not invalid else lambda: play_error_sound(), - repeat=True, - ) - btn2 = original_buttonwidget( - parent=self._subcontainer, - position=(item_h_pos + 5, v), - size=(28, 28), - label='>', - color=b_color, - textcolor=b_textcolor, - autoselect=True, - on_activate_call=bui.CallStrict( - self._choice_inc, setting.name, txt, setting, 1 - ) if not invalid else lambda: play_error_sound(), - repeat=True, - ) - widget_column.append([btn1, btn2]) - - elif isinstance(setting, (bs.IntSetting, bs.FloatSetting)): - v -= spacing - min_value = setting.min_value - max_value = setting.max_value - increment = setting.increment - textwidget( - parent=self._subcontainer, - position=(h + 50, v), - size=(100, 30), - text=name_translated, - h_align='left', - color=(0.8, 0.8, 0.8, 1.0), - v_align='center', - maxwidth=mw1, - ) - txt = textwidget( - parent=self._subcontainer, - position=(item_h_pos - 95, v), - size=(0, 28), - text=str(value), - editable=False, - color=(0.6, 1.0, 0.6, 1.0), - maxwidth=mw2, - h_align='right', - v_align='center', - padding=2, - ) - btn1 = original_buttonwidget( - parent=self._subcontainer, - position=(item_h_pos - 50 - 1, v), - size=(28, 28), - label='-', - color=b_color, - textcolor=b_textcolor, - autoselect=True, - on_activate_call=bui.CallStrict( - self._inc, - txt, - min_value, - max_value, - -increment, - value_type, - setting.name, - ), - repeat=True, - ) - btn2 = original_buttonwidget( - parent=self._subcontainer, - position=(item_h_pos + 5, v), - size=(28, 28), - label='+', - color=b_color, - textcolor=b_textcolor, - autoselect=True, - on_activate_call=bui.CallStrict( - self._inc, - txt, - min_value, - max_value, - increment, - value_type, - setting.name, - ), - repeat=True, - ) - widget_column.append([btn1, btn2]) - - elif value_type == bool: - v -= spacing - textwidget( - parent=self._subcontainer, - position=(h + 50, v), - size=(100, 30), - text=name_translated, - h_align='left', - color=(0.8, 0.8, 0.8, 1.0), - v_align='center', - maxwidth=mw1, - ) - txt = textwidget( - parent=self._subcontainer, - position=(item_h_pos - 95, v), - size=(0, 28), - text=( - bui.Lstr(resource='onText') - if value - else bui.Lstr(resource='offText') - ), - editable=False, - color=(0.6, 1.0, 0.6, 1.0), - maxwidth=mw2, - h_align='right', - v_align='center', - padding=2, - ) - cbw = checkboxwidget( - parent=self._subcontainer, - text='', - position=(item_h_pos - 50 - 5, v - 2), - size=(200, 30), - autoselect=True, - color=b_color, - textcolor=b_textcolor, - value=value, - on_value_change_call=bui.CallPartial( - self._check_value_change, setting.name, txt - ), - ) - widget_column.append([cbw]) - - else: - raise TypeError(f'Invalid value type: {value_type}.') - - # Ok now wire up the column. - try: - prev_widgets: list[Widget] | None = None - for cwdg in widget_column: - if prev_widgets is not None: - # Wire our rightmost to their rightmost. - widget(edit=prev_widgets[-1], down_widget=cwdg[-1]) - widget(edit=cwdg[-1], up_widget=prev_widgets[-1]) - - # Wire our leftmost to their leftmost. - widget(edit=prev_widgets[0], down_widget=cwdg[0]) - widget(edit=cwdg[0], up_widget=prev_widgets[0]) - prev_widgets = cwdg - except Exception: - logging.exception( - 'Error wiring up game-settings-select widget column.' - ) - - -original_filter_playlist = filter_playlist - - -def new_filter_playlist( - playlist: TypedPlaylistDict, - sessiontype: type[bs.Session], - *, - add_resolved_type: bool = False, - remove_unowned: bool = True, - mark_unowned: bool = False, - name: str = '?', - print_exc: bool = True -) -> TypedPlaylistType: - """Return a filtered version of a playlist. - - Strips out or replaces invalid or unowned game types, makes sure all - settings are present, and adds in a 'resolved_type' which is the actual - type. - """ - # pylint: disable=too-many-locals - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - from bascenev1 import get_filtered_map_name, GameActivity - - assert app.classic is not None - - goodlist = TypedPlaylistType() - available_maps: list[str] = list(app.classic.maps.keys()) - if (remove_unowned or mark_unowned) and app.classic is not None: - unowned_maps = app.classic.store.get_unowned_maps() - unowned_game_types = app.classic.store.get_unowned_game_types() - else: - unowned_maps = [] - unowned_game_types = set() - - for entry in deepcopy(playlist): # pyright: ignore[reportAssignmentType] - entry: TypedPlaylistDict - # 'map' used to be called 'level' here. - if 'level' in entry: - entry['map'] = entry['level'] - del entry['level'] - - # We now stuff map into settings instead of it being its own thing. - if 'map' in entry: - entry['settings']['map'] = entry['map'] - del entry['map'] - - # Update old map names to new ones. - entry['settings']['map'] = get_filtered_map_name( - entry['settings']['map'] - ) - if remove_unowned and entry['settings']['map'] in unowned_maps: - continue - - # Ok, for each game in our list, try to import the module and grab - # the actual game class. add successful ones to our initial list - # to present to the user. - if not isinstance(entry['type'], str): - raise TypeError('invalid entry format') - try: - # Do some type filters for backwards compat. - if entry['type'] in ( - 'Assault.AssaultGame', - 'Happy_Thoughts.HappyThoughtsGame', - 'bsAssault.AssaultGame', - 'bs_assault.AssaultGame', - 'bastd.game.assault.AssaultGame', - ): - entry['type'] = 'bascenev1lib.game.assault.AssaultGame' - if entry['type'] in ( - 'King_of_the_Hill.KingOfTheHillGame', - 'bsKingOfTheHill.KingOfTheHillGame', - 'bs_king_of_the_hill.KingOfTheHillGame', - 'bastd.game.kingofthehill.KingOfTheHillGame', - ): - entry['type'] = ( - 'bascenev1lib.game.kingofthehill.KingOfTheHillGame' - ) - if entry['type'] in ( - 'Capture_the_Flag.CTFGame', - 'bsCaptureTheFlag.CTFGame', - 'bs_capture_the_flag.CTFGame', - 'bastd.game.capturetheflag.CaptureTheFlagGame', - ): - entry['type'] = ( - 'bascenev1lib.game.capturetheflag.CaptureTheFlagGame' - ) - if entry['type'] in ( - 'Death_Match.DeathMatchGame', - 'bsDeathMatch.DeathMatchGame', - 'bs_death_match.DeathMatchGame', - 'bastd.game.deathmatch.DeathMatchGame', - ): - entry['type'] = 'bascenev1lib.game.deathmatch.DeathMatchGame' - if entry['type'] in ( - 'ChosenOne.ChosenOneGame', - 'bsChosenOne.ChosenOneGame', - 'bs_chosen_one.ChosenOneGame', - 'bastd.game.chosenone.ChosenOneGame', - ): - entry['type'] = 'bascenev1lib.game.chosenone.ChosenOneGame' - if entry['type'] in ( - 'Conquest.Conquest', - 'Conquest.ConquestGame', - 'bsConquest.ConquestGame', - 'bs_conquest.ConquestGame', - 'bastd.game.conquest.ConquestGame', - ): - entry['type'] = 'bascenev1lib.game.conquest.ConquestGame' - if entry['type'] in ( - 'Elimination.EliminationGame', - 'bsElimination.EliminationGame', - 'bs_elimination.EliminationGame', - 'bastd.game.elimination.EliminationGame', - ): - entry['type'] = 'bascenev1lib.game.elimination.EliminationGame' - if entry['type'] in ( - 'Football.FootballGame', - 'bsFootball.FootballTeamGame', - 'bs_football.FootballTeamGame', - 'bastd.game.football.FootballTeamGame', - ): - entry['type'] = 'bascenev1lib.game.football.FootballTeamGame' - if entry['type'] in ( - 'Hockey.HockeyGame', - 'bsHockey.HockeyGame', - 'bs_hockey.HockeyGame', - 'bastd.game.hockey.HockeyGame', - ): - entry['type'] = 'bascenev1lib.game.hockey.HockeyGame' - if entry['type'] in ( - 'Keep_Away.KeepAwayGame', - 'bsKeepAway.KeepAwayGame', - 'bs_keep_away.KeepAwayGame', - 'bastd.game.keepaway.KeepAwayGame', - ): - entry['type'] = 'bascenev1lib.game.keepaway.KeepAwayGame' - if entry['type'] in ( - 'Race.RaceGame', - 'bsRace.RaceGame', - 'bs_race.RaceGame', - 'bastd.game.race.RaceGame', - ): - entry['type'] = 'bascenev1lib.game.race.RaceGame' - if entry['type'] in ( - 'bsEasterEggHunt.EasterEggHuntGame', - 'bs_easter_egg_hunt.EasterEggHuntGame', - 'bastd.game.easteregghunt.EasterEggHuntGame', - ): - entry['type'] = ( - 'bascenev1lib.game.easteregghunt.EasterEggHuntGame' - ) - if entry['type'] in ( - 'bsMeteorShower.MeteorShowerGame', - 'bs_meteor_shower.MeteorShowerGame', - 'bastd.game.meteorshower.MeteorShowerGame', - ): - entry['type'] = ( - 'bascenev1lib.game.meteorshower.MeteorShowerGame' - ) - if entry['type'] in ( - 'bsTargetPractice.TargetPracticeGame', - 'bs_target_practice.TargetPracticeGame', - 'bastd.game.targetpractice.TargetPracticeGame', - ): - entry['type'] = ( - 'bascenev1lib.game.targetpractice.TargetPracticeGame' - ) - except Exception: - if print_exc: - logging.exception('Error in new_filter_playlist.') - - neededsettings = list[bs.Setting]() - gameclass = None - try: - gameclass = babase.getclass(entry['type'], GameActivity) - - if remove_unowned and gameclass in unowned_game_types: - continue - if add_resolved_type: - entry['resolved_type'] = gameclass - if mark_unowned and gameclass in unowned_game_types: - entry['is_unowned_game'] = True - neededsettings = gameclass.get_available_settings(sessiontype) - - except babase.MapNotFoundError: - if print_exc: - logging.warning( - 'Map \'%s\' not found while scanning playlist \'%s\'.', - entry['settings']['map'], - name, - ) - except ImportError as e: - if print_exc: - logging.warning( - 'Import failed while scanning playlist \'%s\': %s', name, e - ) - entry['is_game_invalid'] = True - # This exception usually happens when we could get the game 'module' - # but, we couldn't get game's GameActivity class name from `entry['type']` - except AttributeError as e: - logging.warning( - 'Get class failed while scanning playlist \'%s\': %s', name, e - ) - entry['is_game_invalid'] = True - - # We 'manually' add some of basic ba*.setting(s) to the filter - # if it exists in raw settings - if entry['settings']['map'] not in available_maps: - entry['is_map_invalid'] = True - - if mark_unowned and entry['settings']['map'] in unowned_maps: - entry['is_unowned_map'] = True - - # Make sure all settings the game defines are present. - for setting in neededsettings: - if setting.name not in entry['settings']: - entry['settings'][setting.name] = setting.default - - goodlist.append(entry) - - return goodlist - - -def apply_packages(): - # Playlist editor controller for PlaylistEditWindow - bauiv1lib.playlist.editcontroller.PlaylistEditController = FluffyPlaylistEditController - - bauiv1lib.playlist.editgame.PlaylistEditGameWindow = FluffyPlaylistEditGameWindow # Playlist Game Editer Window - bauiv1lib.playlist.edit.PlaylistEditWindow = FluffyPlaylistEditWindow # Playlist Editor Window - - bs.filter_playlist = new_filter_playlist - # pyright: ignore[reportAttributeAccessIssue] - bs._playlist.filter_playlist = new_filter_playlist - - -# ba_meta export babase.Plugin -class by_FluffyPal(Plugin): - def on_app_running(self) -> None: - apply_packages() diff --git a/plugins/utilities/glowing_profiles.py b/plugins/utilities/glowing_profiles.py deleted file mode 100644 index 799e7c8..0000000 --- a/plugins/utilities/glowing_profiles.py +++ /dev/null @@ -1,222 +0,0 @@ -# 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 diff --git a/plugins/utilities/powerup_manager.py b/plugins/utilities/powerup_manager.py index 2dad733..2a81577 100644 --- a/plugins/utilities/powerup_manager.py +++ b/plugins/utilities/powerup_manager.py @@ -1,2972 +1,2985 @@ -# ba_meta require api 9 -from __future__ import annotations - -import babase -import bauiv1 as bui -import bascenev1 as bs -import random -from bascenev1lib.actor import bomb -from bascenev1lib.actor import powerupbox as pupbox -from bascenev1lib.actor.spazbot import SpazBot -from bascenev1lib.actor.bomb import Bomb, Blast -from bauiv1lib.popup import PopupWindow, PopupMenuWindow, PopupMenu -from bascenev1lib.actor.spaz import ( - Spaz, - SpazFactory, - PickupMessage, - PunchHitMessage, - CurseExplodeMessage, - BombDiedMessage, -) -from bascenev1lib.mainmenu import MainMenuActivity, MainMenuSession -from bascenev1lib.gameutils import SharedObjects -from bascenev1lib.actor.powerupbox import PowerupBoxFactory -from bascenev1lib.actor.popuptext import PopupText -from bauiv1lib.confirm import ConfirmWindow -from bascenev1lib.actor.spaz import * - -from typing import TYPE_CHECKING - -plugman = dict( - plugin_name="powerup_manager", - description="This plugin add new modded powerups and features to manage them", - external_url="", - authors=[ - {"name": "ATD", "email": "anasdhaoidi001@gmail.com", "discord": ""}, - ], - version="1.0.1", -) - - -_sp_ = '\n' - -if TYPE_CHECKING: - pass - - -# === Mod updated by ATD and Less === - - -def getlanguage(text, subs: str = None, almacen: list = []): - if almacen == []: - almacen = list(range(1000)) - lang = bs.app.lang.language - translate = { - "Reset": {"Spanish": "Reiniciar", "English": "Reset", "Portuguese": "Reiniciar"}, - "Nothing": { - "Spanish": "Sin potenciadores", - "English": "No powerups", - "Portuguese": "Sem powerups", - }, - "Action 1": {"Spanish": "Potenciadores", "English": "Powerups", "Portuguese": "Powerups"}, - "Action 2": {"Spanish": "Configuración", "English": "Settings", "Portuguese": "Definições"}, - "Action 3": {"Spanish": "Extras", "English": "Extras", "Portuguese": "Extras"}, - "Action 4": {"Spanish": "Tienda", "English": "Store", "Portuguese": "Loja"}, - "Action 5": { - "Spanish": "Canjear código", - "English": "Enter Code", - "Portuguese": "Código promocional", - }, - "Custom": {"Spanish": "", "English": "Customize", "Portuguese": "Customizar"}, - "Impairment Bombs": { - "Spanish": "Bombas menoscabo", - "English": "Hyperactive bombs", - "Portuguese": "Bombas hiperativas", - }, - "Speed": {"Spanish": "Velocidad", "English": "Speed", "Portuguese": "Velocidade"}, - "Fire Bombs": { - "Spanish": "Bombas de fuego", - "English": "Fire Bombs", - "Portuguese": "Bombas de fogo", - }, - "Ice Man": { - "Spanish": "Hombre de hielo", - "English": "Ice man", - "Portuguese": "Homem de gelo", - }, - "Fly Bombs": { - "Spanish": "Bombas expansivas", - "English": "Expansive bombs", - "Portuguese": "Bombas expansivas", - }, - "Goodbye": {"Spanish": "¡Hasta luego!", "English": "Goodbye!", "Portuguese": "Adeus!"}, - "Healing Damage": { - "Spanish": "Auto-curación", - "English": "Healing Damage", - "Portuguese": "Auto-cura", - }, - "Tank Shield": { - "Spanish": "Súper blindaje", - "English": "Reinforced shield", - "Portuguese": "Escudo reforçado", - }, - "Tank Shield PTG": { - "Spanish": "Porcentaje de disminución", - "English": "Percentage decreased", - "Portuguese": "Percentual reduzido", - }, - "Healing Damage PTG": { - "Spanish": "Porcentaje de recuperación de salud", - "English": "Percentage of health recovered", - "Portuguese": "Porcentagem de recuperação de saúde", - }, - "SY: BALL": {"Spanish": "Esfera", "English": "Sphere", "Portuguese": "Esfera"}, - "SY: Impact": {"Spanish": "Especial", "English": "Special", "Portuguese": "Especial"}, - "SY: Egg": {"Spanish": "Huevito", "English": "Egg shape", "Portuguese": "Ovo"}, - "Powerup Scale": { - "Spanish": "Tamaño del potenciador", - "English": "Powerups size", - "Portuguese": "Tamanho de powerups", - }, - "Powerup With Shield": { - "Spanish": "Potenciadores con escudo", - "English": "Powerups with shield", - "Portuguese": "Powerups com escudo", - }, - "Powerup Time": { - "Spanish": "Mostrar Temporizador", - "English": "Show end time", - "Portuguese": "Mostrar cronômetro", - }, - "Powerup Style": { - "Spanish": "Forma de los potenciadores", - "English": "Shape of powerup", - "Portuguese": "Forma de powerup", - }, - "Powerup Name": { - "Spanish": "Mostrar nombre en los potenciadores", - "English": "Show name on powerups", - "Portuguese": "Mostrar nome em powerups", - }, - "Percentage": { - "Spanish": "Probabilidad", - "English": "Show percentage", - "Portuguese": "Mostrar porcentagem", - }, - "Only Items": { - "Spanish": "Sólo Accesorios", - "English": "Only utensils", - "Portuguese": "Apenas utensilios", - }, - "New": {"Spanish": "Nuevo", "English": "New", "Portuguese": "Novo"}, - "Only Bombs": { - "Spanish": "Sólo Bombas", - "English": "Only bombs", - "Portuguese": "Apenas bombas", - }, - "Coins 0": { - "Spanish": "Monedas Insuficientes", - "English": "Insufficient coins", - "Portuguese": "Moedas insuficientes", - }, - "Purchase": { - "Spanish": "Compra realizada correctamente", - "English": "Successful purchase", - "Portuguese": "Compra Bem Sucedida", - }, - "Double Product": { - "Spanish": "Ya has comprado este artículo", - "English": "You've already bought this", - "Portuguese": "Voce ja comprou isto", - }, - "Bought": {"Spanish": "Comprado", "English": "Bought", "Portuguese": "Comprou"}, - "Confirm Purchase": { - "Spanish": f'Tienes {subs} monedas. {_sp_} ¿Deseas comprar esto?', - "English": f'You have {subs} coins. {_sp_} Do you want to buy this?', - "Portuguese": f'Você tem {subs} moedas. {_sp_} Deseja comprar isto?', - }, - "FireBombs Store": { - "Spanish": 'Bombas de fuego', - "English": 'Fire bombs', - "Portuguese": 'Bombas de incêndio', - }, - "Timer Store": {"Spanish": 'Temporizador', "English": 'Timer', "Portuguese": 'Timer'}, - "Percentages Store": {"Spanish": 'Extras', "English": 'Extras', "Portuguese": 'Extras'}, - "Block Option Store": { - "Spanish": f"Uuups..{_sp_}Esta opción está bloqueada.{_sp_} Para acceder a ella puedes {_sp_} comprarla en la tienda.{_sp_} Gracias...", - "English": f"Oooops...{_sp_}This option is blocked. {_sp_} To access it you can buy {_sp_} it in the store.{_sp_} Thank you...", - "Portuguese": f"Ooops...{_sp_}Esta opção está bloqueada. {_sp_} Para acessá-lo, você pode {_sp_} comprá-lo na loja.{_sp_} Obrigado...", - }, - "True Code": { - "Spanish": "¡Código canjeado!", - "English": "Successful code!", - "Portuguese": "¡Código válido!", - }, - "False Code": { - "Spanish": "Código ya canjeado", - "English": "Expired code", - "Portuguese": "Código expirado", - }, - "Invalid Code": { - "Spanish": "Código inválido", - "English": "Invalid code", - "Portuguese": "Código inválido", - }, - "Reward Code": { - "Spanish": f"¡Felicitaciones! ¡Ganaste {subs} monedas!", - "English": f"Congratulations! You've {subs} coins", - "Portuguese": f"Parabéns! Você tem {subs} moedas", - }, - "Creator": { - "Spanish": "Mod edited by ATD", - "English": "Mod edited by ATD", - "Portuguese": "Mod edited by ATD", - }, - "Mod Info": { - "Spanish": f"Un mod genial que te permite gestionar {_sp_} los potenciadores a tu antojo. {_sp_} también incluye 8 potenciadores extra{_sp_} dejando 17 en total... ¡Guay!", - "English": f"A cool mod that allows you to manage {_sp_} powerups at your whims. {_sp_} also includes 8 extra powerups{_sp_} leaving 17 in total... Wow!", - "Portuguese": f"Um mod legal que permite que você gerencie os{_sp_} powerups de de acordo com seus caprichos. {_sp_} também inclui 8 powerups extras,{_sp_} deixando 17 no total... Uau!", - }, - "Coins Message": { - "Spanish": f"Recompensa: {subs} Monedas", - "English": f"Reward: {subs} Coins", - "Portuguese": f"Recompensa: {subs} Moedas", - }, - "Coins Limit Message": { - "Spanish": f"Ganaste {almacen[0]} Monedas.{_sp_} Pero has superado el límite de {almacen[1]}", - "English": f"You won {almacen[0]} Coins. {_sp_} But you have exceeded the limit of {almacen[1]}", - "Portuguese": f"Você ganhou {almacen[0]} Moedas. {_sp_} Mas você excedeu o limite de {almacen[1]}", - }, - } - languages = ['Spanish', 'Portuguese', 'English'] - if lang not in languages: - lang = 'English' - - if text not in translate: - return text - - return translate[text][lang] - - -def settings_distribution(): - return { - "Powers Gravity": False, - "Tank Shield PTG": 96, - "Healing Damage PTG": 72, - "Powerup Style": 'Auto', - "Powerup Scale": 1.0, - "Powerup Name": False, - "Powerup With Shield": False, - "Powerup Time": False, - } - - -apg = babase.app.config -if "PPM Settings" in apg: - old = apg['PPM Settings'] - for settings in settings_distribution(): - if settings not in old: - apg['PPM Settings'] = settings_distribution() -else: - apg['PPM Settings'] = settings_distribution() -apg.apply_and_commit() - -config = apg['PPM Settings'] - - -def default_powerups(): - return { - "Shield": 2, - "Punch": 3, - "Mine Bombs": 2, - "Impact Bombs": 3, - "Ice Bombs": 3, - "Triple": 3, - "Sticky Bombs": 3, - "Curse": 1, - "Health": 1, - "Speed": 2, - "Healing Damage": 1, - "Goodbye": 2, - "Ice Man": 1, - "Tank Shield": 1, - "Impairment Bombs": 2, - "Fire Bombs": 3, - "Fly Bombs": 3, - } - - -if "Powerups" in config: - p_old = config['Powerups'] - for powerups in default_powerups(): - if powerups not in p_old: - config['Powerups'] = default_powerups() -else: - config['Powerups'] = default_powerups() -apg.apply_and_commit() - -powerups = config['Powerups'] - -# === EXTRAS === - -GLOBAL = {"Tab": 'Action 1', "Cls Powerup": 0, "Coins Message": []} - - -# === STORE === -def promo_codes(): - return { - "G-Am54igO42Os": [True, 1100], - "P-tRo8nM8dZ": [True, 2800], - "Y-tU2B3S": [True, 500], - "B-0mB3RYT2z": [True, 910], - "B-Asd14mON9G0D": [True, 910], - "D-rAcK0cJ23": [True, 910], - "E-a27ZO6f3Y": [True, 600], - "E-Am54igO42Os": [True, 600], - "E-M4uN3K34XB": [True, 840], - "PM-731ClcAF": [True, 50000], - } - - -def store_items(): - return {"Buy Firebombs": True, "Buy Option": True, "Buy Percentage": True} - - -if apg.get('Bear Coin') is None: - apg['Bear Coin'] = 0 - apg.apply_and_commit() - -if apg.get('Bear Coin') is not None: - if apg['Bear Coin'] <= 0: - apg['Bear Coin'] = 0 - apg['Bear Coin'] = int(apg['Bear Coin']) - -if apg.get('Bear Store') is None: - apg['Bear Store'] = {} - -for i, j in store_items().items(): - store = apg['Bear Store'] - if i not in store: - if store.get(i) is None: - store[i] = j - apg.apply_and_commit() - -STORE = apg['Bear Store'] - -if STORE.get('Promo Code') is None: - STORE['Promo Code'] = promo_codes() - -for i, x in promo_codes().items(): - pmcode = STORE['Promo Code'] - if i not in pmcode: - if pmcode.get(i) is None: - pmcode[i] = x - -apg.apply_and_commit() - - -class BearStore: - def __init__(self, price: int = 1000, value: str = '', callback: call = None): - - self.price = price - self.value = value - self.store = STORE[value] - self.coins = apg['Bear Coin'] - self.callback = callback - - def buy(self): - if not self.store: - if self.coins >= (self.price): - - def confirm(): - STORE[self.value] = True - apg['Bear Coin'] -= int(self.price) - bs.broadcastmessage(getlanguage('Purchase'), (0, 1, 0)) - bui.getsound('cashRegister').play() - apg.apply_and_commit() - self.callback() - - ConfirmWindow( - getlanguage('Confirm Purchase', subs=self.coins), - width=400, - height=120, - action=confirm, - ok_text=babase.Lstr(resource='okText'), - ) - else: - bs.broadcastmessage(getlanguage('Coins 0'), (1, 0, 0)) - bui.getsound('error').play() - else: - bs.broadcastmessage(getlanguage('Double Product'), (1, 0, 0)) - bui.getsound('error').play() - - def __del__(self): - apg['Bear Coin'] = int(apg['Bear Coin']) - apg.apply_and_commit() - - -class PromoCode: - def __init__(self, code: str = ''): - self.code = code - self.codes_store = STORE['Promo Code'] - if self.code in self.codes_store: - self.code_type = STORE['Promo Code'][code] - self.promo_code_expire = self.code_type[0] - self.promo_code_amount = self.code_type[1] - - def __del__(self): - apg['Bear Coin'] = int(apg['Bear Coin']) - apg.apply_and_commit() - - def code_confirmation(self): - if self.code != "": - bs.broadcastmessage(babase.Lstr(resource='submittingPromoCodeText'), (0, 1, 0)) - try: - babase.pushcall(babase.CallPartial(self.validate_code), from_other_thread=True) - except: - pass - - def validate_code(self): - if self.code in self.codes_store: - if self.promo_code_expire: - with babase.ContextRef.empty(): - babase.pushcall( - babase.CallPartial(self.successful_code), from_other_thread=True - ) - bs.broadcastmessage(getlanguage('True Code'), (0, 1, 0)) - bui.getsound('cheer').play() - self.code_type[0] = False - else: - bs.broadcastmessage(getlanguage('False Code'), (1, 0, 0)) - bui.getsound('error').play() - else: - bs.broadcastmessage(getlanguage('Invalid Code'), (1, 0, 0)) - bui.getsound('error').play() - - def successful_code(self): - apg['Bear Coin'] += self.promo_code_amount - bs.broadcastmessage(getlanguage('Reward Code', subs=self.promo_code_amount), (0, 1, 0)) - bui.getsound('cashRegister2').play() - - -MainMenuActivity.super_transition_in = MainMenuActivity.on_transition_in - - -def new_on_transition_in(self): - self.super_transition_in() - limit = 8400 - bear_coin = apg['Bear Coin'] - coins_message = GLOBAL['Coins Message'] - try: - if not (STORE['Buy Firebombs'] and STORE['Buy Option'] and STORE['Buy Percentage']): - - if coins_message != []: - result = 0 - for i in coins_message: - result += i - - if not bear_coin >= (limit - 1): - bs.broadcastmessage(getlanguage('Coins Message', subs=result), (0, 1, 0)) - bui.getsound('cashRegister').play() - else: - bs.broadcastmessage( - getlanguage('Coins Limit Message', almacen=[result, limit]), (1, 0, 0) - ) - bui.getsound('error').play() - self.bear_coin_message = True - GLOBAL['Coins Message'] = [] - except: - pass - - -SpazBot.super_handlemessage = SpazBot.handlemessage - - -def bot_handlemessage(self, msg: Any): - self.super_handlemessage(msg) - if isinstance(msg, bs.DieMessage): - if not self.die: - self.die = True - self.limit = 8400 - self.free_coins = random.randint(1, 25) - self.bear_coins = apg['Bear Coin'] - - if not self.bear_coins >= (self.limit): - self.bear_coins += self.free_coins - GLOBAL['Coins Message'].append(self.free_coins) - - if self.bear_coins >= (self.limit): - self.bear_coins = self.limit - - apg['Bear Coin'] = int(self.bear_coins) - apg.apply_and_commit() - - else: - GLOBAL['Coins Message'].append(self.free_coins) - - -def cls_pow_color(): - return [ - (1, 0.1, 0.1), - (0.1, 0.5, 0.9), - (0.1, 0.9, 0.9), - (0.1, 0.9, 0.1), - (0.1, 1, 0.5), - (1, 1, 0.2), - (2, 0.5, 0.5), - (1, 0, 6), - ] - - -def random_color(): - a = random.random() * 3 - b = random.random() * 3 - c = random.random() * 3 - return (a, b, c) - - -def powerup_dist(): - return ( - ('triple_bombs', powerups['Triple']), - ('ice_bombs', powerups['Ice Bombs']), - ('punch', powerups['Punch']), - ('impact_bombs', powerups['Impact Bombs']), - ('land_mines', powerups['Mine Bombs']), - ('sticky_bombs', powerups['Sticky Bombs']), - ('shield', powerups['Shield']), - ('health', powerups['Health']), - ('curse', powerups['Curse']), - ('speed', powerups['Speed']), - ('health_damage', powerups['Healing Damage']), - ('goodbye', powerups['Goodbye']), - ('ice_man', powerups['Ice Man']), - ('tank_shield', powerups['Tank Shield']), - ('impairment_bombs', powerups['Impairment Bombs']), - ('fire_bombs', powerups['Fire Bombs']), - ('fly_bombs', powerups['Fly Bombs']), - ) - - -def percentage_tank_shield(): - percentage = config['Tank Shield PTG'] - percentage_text = ('0.') + str(percentage) - return float(percentage_text) - - -def percentage_health_damage(): - percentage = config['Healing Damage PTG'] - percentage_text = ('0.') + str(percentage) - return float(percentage_text) - - -# === Modify class === - - -class NewProfileBrowserWindow: - def __init__( - self, - transition: str = 'in_right', - in_main_menu: bool = True, - selected_profile: str = None, - origin_widget: bui.Widget = None, - ): - super().__init__(transition, in_main_menu, selected_profile, origin_widget) - - self.session = bs.get_foreground_host_session() - uiscale = bui.app.ui_v1.uiscale - width = 100 if uiscale is babase.UIScale.SMALL else -14 - size = 50 - position = (width * 1.65, 300) - - if isinstance(self.session, MainMenuSession): - self.button = bui.buttonwidget( - parent=self._root_widget, - autoselect=True, - position=position, - size=(size, size), - button_type='square', - label='', - on_activate_call=babase.CallPartial(self.powerupmanager_window), - ) - - size = size * 0.60 - self.image = bui.imagewidget( - parent=self._root_widget, - size=(size, size), - draw_controller=self.button, - position=(position[0] + 10.5, position[1] + 17), - texture=bui.gettexture('powerupSpeed'), - ) - - self.text = bui.textwidget( - parent=self._root_widget, - position=(position[0] + 25, position[1] + 10), - size=(0, 0), - scale=0.45, - color=(0.5, 0.5, 0.5, 0.5), - draw_controller=self.button, - maxwidth=60, - text=(f"powerups manager 1.1.1"), - h_align='center', - v_align='center', - ) - - def powerupmanager_window(self): - bui.containerwidget(edit=self._root_widget, transition='out_left') - PowerupManagerWindow() - - -class NewPowerupBoxFactory(pupbox.PowerupBoxFactory): - def __init__(self) -> None: - super().__init__() - self.tex_speed = bs.gettexture('powerupSpeed') - self.tex_health_damage = bs.gettexture('heart') - self.tex_goodbye = bs.gettexture('achievementOnslaught') - self.tex_ice_man = bs.gettexture('ouyaUButton') - self.tex_tank_shield = bs.gettexture('achievementSuperPunch') - self.tex_impairment_bombs = bs.gettexture('levelIcon') - self.tex_fire_bombs = bs.gettexture('ouyaOButton') - self.tex_fly_bombs = bs.gettexture('star') - - self._powerupdist = [] - for powerup, freq in powerup_dist(): - for _i in range(int(freq)): - self._powerupdist.append(powerup) - - def get_random_powerup_type(self, forcetype=None, excludetypes=None): - - try: - self.mapa = bs.getactivity()._map.getname() - except: - self.mapa = None - - speed_banned_maps = ['Hockey Stadium', 'Lake Frigid', 'Happy Thoughts'] - - if self.mapa in speed_banned_maps: - powerup_disable = ['speed'] - else: - powerup_disable = [] - - if excludetypes is None: - excludetypes = [] - if forcetype: - ptype = forcetype - else: - if self._lastpoweruptype == 'curse': - ptype = 'health' - else: - while True: - ptype = self._powerupdist[random.randint(0, len(self._powerupdist) - 1)] - if ptype not in excludetypes and ptype not in powerup_disable: - break - self._lastpoweruptype = ptype - return ptype - - -def fire_effect(self): - if self.node.exists(): - bs.emitfx( - position=self.node.position, scale=3, count=50 * 2, spread=0.3, chunk_type='sweat' - ) - else: - self.fire_effect_time = None - - -# BOMBS -Bomb._pm_old_bomb = Bomb.__init__ - - -def _bomb_init( - self, - position: Sequence[float] = (0.0, 1.0, 0.0), - velocity: Sequence[float] = (0.0, 0.0, 0.0), - bomb_type: str = 'normal', - blast_radius: float = 2.0, - bomb_scale: float = 1.0, - source_player: bs.Player = None, - owner: bs.Node = None, -): - - self.bm_type = bomb_type - new_bomb_type = 'ice' if bomb_type in ['ice_bubble', 'impairment', 'fire', 'fly'] else bomb_type - - # Call original __init__ - self._pm_old_bomb( - position=position, - velocity=velocity, - bomb_type=new_bomb_type, - blast_radius=blast_radius, - bomb_scale=bomb_scale, - source_player=source_player, - owner=owner, - ) - - tex = self.node.color_texture - - if self.bm_type == 'ice_bubble': - self.bomb_type = self.bm_type - self.node.mesh = None - self.shield_ice = bs.newnode( - 'shield', owner=self.node, attrs={'color': (0.5, 1.0, 7.0), 'radius': 0.6} - ) - self.node.connectattr('position', self.shield_ice, 'position') - - elif self.bm_type == 'fire': - self.bomb_type = self.bm_type - self.node.mesh = None - self.shield_fire = bs.newnode( - 'shield', owner=self.node, attrs={'color': (6.5, 6.5, 2.0), 'radius': 0.6} - ) - self.node.connectattr('position', self.shield_fire, 'position') - self.fire_effect_time = bs.Timer(0.1, bs.CallPartial(fire_effect, self), repeat=True) - elif self.bm_type == 'impairment': - self.bomb_type = self.bm_type - tex = bs.gettexture('eggTex3') - elif self.bm_type == 'fly': - self.bomb_type = self.bm_type - tex = bs.gettexture('eggTex1') - - if 'tex' in locals(): - self.node.color_texture = tex - self.hit_subtype = self.bomb_type - - if self.bomb_type == 'ice_bubble': - self.blast_radius *= 1.2 - elif self.bomb_type == 'fly': - self.blast_radius *= 2.2 - - -def bomb_handlemessage(self, msg: Any) -> Any: - assert not self.expired - - if isinstance(msg, bs.DieMessage): - if self.node: - self.node.delete() - - elif isinstance(msg, bomb.ExplodeHitMessage): - node = bs.getcollision().opposingnode - assert self.node - nodepos = self.node.position - mag = 2000.0 - if self.blast_type in ('ice', 'ice_bubble'): - mag *= 0.5 - elif self.blast_type == 'land_mine': - mag *= 2.5 - elif self.blast_type == 'tnt': - mag *= 2.0 - elif self.blast_type == 'fire': - mag *= 0.6 - elif self.blast_type == 'fly': - mag *= 5.5 - - node.handlemessage( - bs.HitMessage( - pos=nodepos, - velocity=(0, 0, 0), - magnitude=mag, - hit_type=self.hit_type, - hit_subtype=self.hit_subtype, - radius=self.radius, - source_player=babase.existing(self._source_player), - ) - ) - if self.blast_type in ('ice', 'ice_bubble'): - bomb.BombFactory.get().freeze_sound.play(10) - node.handlemessage(bs.FreezeMessage()) - - return None - - -def powerup_translated(self, type: str): - powerups_names = { - 'triple_bombs': babase.Lstr(resource='helpWindow.' + 'powerupBombNameText'), - 'ice_bombs': babase.Lstr(resource='helpWindow.' + 'powerupIceBombsNameText'), - 'punch': babase.Lstr(resource='helpWindow.' + 'powerupPunchNameText'), - 'impact_bombs': babase.Lstr(resource='helpWindow.' + 'powerupImpactBombsNameText'), - 'land_mines': babase.Lstr(resource='helpWindow.' + 'powerupLandMinesNameText'), - 'sticky_bombs': babase.Lstr(resource='helpWindow.' + 'powerupStickyBombsNameText'), - 'shield': babase.Lstr(resource='helpWindow.' + 'powerupShieldNameText'), - 'health': babase.Lstr(resource='helpWindow.' + 'powerupHealthNameText'), - 'curse': babase.Lstr(resource='helpWindow.' + 'powerupCurseNameText'), - 'speed': getlanguage('Speed'), - 'health_damage': getlanguage('Healing Damage'), - 'goodbye': getlanguage('Goodbye'), - 'ice_man': getlanguage('Ice Man'), - 'tank_shield': getlanguage('Tank Shield'), - 'impairment_bombs': getlanguage('Impairment Bombs'), - 'fire_bombs': getlanguage('Fire Bombs'), - 'fly_bombs': getlanguage('Fly Bombs'), - } - self.texts['Name'].text = powerups_names[type] - - -# POWERUP -pupbox.PowerupBox._old_pbx_ = pupbox.PowerupBox.__init__ - - -def _pbx_( - self, - position: Sequence[float] = (0.0, 1.0, 0.0), - poweruptype: str = 'triple_bombs', - expire: bool = True, -): - self.news: list = [] - for x, i in powerup_dist(): - self.news.append(x) - - self.box: list = [] - self.texts = {} - self.news = self.news[9:] - self.box.append(poweruptype) - self.npowerup = self.box[0] - factory = NewPowerupBoxFactory.get() - - if self.npowerup in self.news: - new_poweruptype = 'shield' - else: - new_poweruptype = poweruptype - self._old_pbx_(position, new_poweruptype, expire) - - type = new_poweruptype - tex = self.node.color_texture - mesh = self.node.mesh - - if self.npowerup == 'speed': - type = self.npowerup - tex = factory.tex_speed - elif self.npowerup == 'health_damage': - type = self.npowerup - tex = factory.tex_health_damage - elif self.npowerup == 'goodbye': - type = self.npowerup - tex = factory.tex_goodbye - elif self.npowerup == 'ice_man': - type = self.npowerup - tex = factory.tex_ice_man - elif self.npowerup == 'tank_shield': - type = self.npowerup - tex = factory.tex_tank_shield - elif self.npowerup == 'impairment_bombs': - type = self.npowerup - tex = factory.tex_impairment_bombs - elif self.npowerup == 'fire_bombs': - type = self.npowerup - tex = factory.tex_fire_bombs - elif self.npowerup == 'fly_bombs': - type = self.npowerup - tex = factory.tex_fly_bombs - - self.poweruptype = type - self.node.mesh = mesh - self.node.color_texture = tex - n_scale = config['Powerup Scale'] - style = config['Powerup Style'] - - curve = bs.animate(self.node, 'mesh_scale', {0: 0, 0.14: 1.6, 0.2: n_scale}) - bs.timer(0.2, curve.delete) - - def util_text( - type: str, - text: str, - scale: float = 1, - color: list = [1, 1, 1], - position: list = [0, 0.7, 0], - colors_name: bool = False, - ): - m = bs.newnode( - 'math', - owner=self.node, - attrs={'input1': (position[0], position[1], position[2]), 'operation': 'add'}, - ) - self.node.connectattr('position', m, 'input2') - self.texts[type] = bs.newnode( - 'text', - owner=self.node, - attrs={ - 'text': str(text), - 'in_world': True, - 'scale': 0.02, - 'shadow': 0.5, - 'flatness': 1.0, - 'color': (color[0], color[1], color[2]), - 'h_align': 'center', - }, - ) - m.connectattr('output', self.texts[type], 'position') - bs.animate(self.texts[type], 'scale', {0: 0.017, 0.4: 0.017, 0.5: 0.01 * scale}) - - if colors_name: - bs.animate_array( - self.texts[type], - 'color', - 3, - { - 0: (1, 0, 0), - 0.2: (1, 0.5, 0), - 0.4: (1, 1, 0), - 0.6: (0, 1, 0), - 0.8: (0, 1, 1), - 1.0: (1, 0, 1), - 1.2: (1, 0, 0), - }, - loop=True, - ) - - def update_time(time): - if hasattr(self, 'texts') and 'Time' in self.texts and self.texts['Time']: - self.texts['Time'].text = str(time) - - if config['Powerup Time']: - interval = int(pupbox.DEFAULT_POWERUP_INTERVAL) - time2 = interval - 1 - time = 1 - - util_text( - 'Time', time2, scale=1.5, color=(2, 2, 2), position=[0, 0.9, 0], colors_name=False - ) - - while interval + 3: - bs.timer(time - 1, babase.CallPartial(update_time, f'{time2}s')) - - if time2 == 0: - break - - time += 1 - time2 -= 1 - - if config['Powerup With Shield']: - scale = config['Powerup Scale'] - self.shield = bs.newnode( - 'shield', owner=self.node, attrs={'color': (1, 1, 0), 'radius': 1.3 * scale} - ) - self.node.connectattr('position', self.shield, 'position') - bs.animate_array( - self.shield, - 'color', - 3, - {0: (2, 0, 0), 0.5: (0, 2, 0), 1: (0, 1, 6), 1.5: (2, 0, 0)}, - loop=True, - ) - - if config['Powerup Name']: - util_text('Name', self.poweruptype, scale=1.2, position=[0, 0.4, 0], colors_name=True) - powerup_translated(self, self.poweruptype) - - if style == 'SY: BALL': - self.node.mesh = bs.getmesh('frostyPelvis') - elif style == 'SY: Impact': - self.node.mesh = bs.getmesh('impactBomb') - elif style == 'SY: Egg': - self.node.mesh = bs.getmesh('egg') - - -# SPAZ -def _speed_off_flash(self): - if self.node: - factory = NewPowerupBoxFactory.get() - self.node.billboard_texture = factory.tex_speed - self.node.billboard_opacity = 1.0 - self.node.billboard_cross_out = True - - -def _speed_wear_off(self): - if self.node: - self.node.hockey = False - self.node.billboard_opacity = 0.0 - bui.getsound('powerdown01').play() - - -def _ice_man_off_flash(self): - if self.node: - factory = NewPowerupBoxFactory.get() - self.node.billboard_texture = factory.tex_ice_man - self.node.billboard_opacity = 1.0 - self.node.billboard_cross_out = True - - -def _ice_man_wear_off(self): - if self.node: - f = self.color[0] - i = (0, 1, 4) - - bomb = self.bmb_color[0] - if bomb != 'ice_bubble': - self.bomb_type = bomb - else: - self.bomb_type = 'normal' - - self.freeze_punch = False - self.node.billboard_opacity = 0.0 - bs.animate_array(self.node, 'color', 3, {0: f, 0.3: i, 0.6: f}) - bui.getsound('powerdown01').play() - - -Spaz._pm2_spz_old = Spaz.__init__ - - -def _init_spaz_(self, *args, **kwargs): - self._pm2_spz_old(*args, **kwargs) - self.edg_eff = False - self.kill_eff = False - self.freeze_punch = False - self.die = False - self.color: list = [] - self.color.append(self.node.color) - - self.tankshield = {"Tank": False, "Reduction": False, "Shield": None} - - -Spaz._super_on_punch_press = Spaz.on_punch_press - - -def spaz_on_punch_press(self) -> None: - self._super_on_punch_press() - - if self.tankshield['Tank']: - try: - self.tankshield['Reduction'] = True - - shield = bs.newnode( - 'shield', owner=self.node, attrs={'color': (4, 1, 4), 'radius': 1.3} - ) - self.node.connectattr('position_center', shield, 'position') - - self.tankshield['Shield'] = shield - except: - pass - - -Spaz._super_on_punch_release = Spaz.on_punch_release - - -def spaz_on_punch_release(self) -> None: - self._super_on_punch_release() - try: - self.tankshield['Shield'].delete() - self.tankshield['Reduction'] = False - except: - pass - - -def new_get_bomb_type_tex(self) -> babase.Texture: - factory = NewPowerupBoxFactory.get() - if self.bomb_type == 'sticky': - return factory.tex_sticky_bombs - if self.bomb_type == 'ice': - return factory.tex_ice_bombs - if self.bomb_type == 'impact': - return factory.tex_impact_bombs - if self.bomb_type == 'impairment': - return factory.tex_impairment_bombs - if self.bomb_type == 'fire': - return factory.tex_fire_bombs - if self.bomb_type == 'fly': - return factory.tex_fly_bombs - return None - - -def new_handlemessage(self, msg: Any) -> Any: - assert not self.expired - - if isinstance(msg, bs.PickedUpMessage): - if self.node: - self.node.handlemessage('hurt_sound') - self.node.handlemessage('picked_up') - - self._num_times_hit += 1 - - elif isinstance(msg, bs.ShouldShatterMessage): - bs.timer(0.001, bs.WeakCallPartial(self.shatter)) - - elif isinstance(msg, bs.ImpactDamageMessage): - bs.timer(0.001, bs.WeakCallPartial(self._hit_self, msg.intensity)) - elif isinstance(msg, bs.PowerupMessage): - factory = NewPowerupBoxFactory.get() - if self._dead or not self.node: - return True - if self.pick_up_powerup_callback is not None: - self.pick_up_powerup_callback(self) - if msg.poweruptype == 'triple_bombs': - tex = PowerupBoxFactory.get().tex_bomb - self._flash_billboard(tex) - self.set_bomb_count(3) - if self.powerups_expire: - self.node.mini_billboard_1_texture = tex - t_ms = int(bs.time() * 1000) - assert isinstance(t_ms, int) - self.node.mini_billboard_1_start_time = t_ms - self.node.mini_billboard_1_end_time = t_ms + POWERUP_WEAR_OFF_TIME - self._multi_bomb_wear_off_timer = bs.Timer( - (POWERUP_WEAR_OFF_TIME - 2000), - babase.CallPartial(self._multi_bomb_wear_off_flash), - ) - self._multi_bomb_wear_off_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME, babase.CallPartial(self._multi_bomb_wear_off) - ) - elif msg.poweruptype == 'land_mines': - self.set_land_mine_count(min(self.land_mine_count + 3, 3)) - elif msg.poweruptype == 'impact_bombs': - self.bomb_type = 'impact' - tex = self._get_bomb_type_tex() - self._flash_billboard(tex) - if self.powerups_expire: - self.node.mini_billboard_2_texture = tex - t_ms = int(bs.time() * 1000) - assert isinstance(t_ms, int) - self.node.mini_billboard_2_start_time = t_ms - self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME - self._bomb_wear_off_flash_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME - 2000, babase.CallPartial(self._bomb_wear_off_flash) - ) - self._bomb_wear_off_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME, babase.CallPartial(self._bomb_wear_off) - ) - elif msg.poweruptype == 'sticky_bombs': - self.bomb_type = 'sticky' - tex = self._get_bomb_type_tex() - self._flash_billboard(tex) - if self.powerups_expire: - self.node.mini_billboard_2_texture = tex - t_ms = int(bs.time() * 1000) - assert isinstance(t_ms, int) - self.node.mini_billboard_2_start_time = t_ms - self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME - self._bomb_wear_off_flash_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME - 2000, babase.CallPartial(self._bomb_wear_off_flash) - ) - self._bomb_wear_off_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME, babase.CallPartial(self._bomb_wear_off) - ) - elif msg.poweruptype == 'punch': - self._has_boxing_gloves = True - tex = PowerupBoxFactory.get().tex_punch - self._flash_billboard(tex) - self.equip_boxing_gloves() - if self.powerups_expire: - self.node.boxing_gloves_flashing = False - self.node.mini_billboard_3_texture = tex - t_ms = int(bs.time() * 1000) - assert isinstance(t_ms, int) - self.node.mini_billboard_3_start_time = t_ms - self.node.mini_billboard_3_end_time = t_ms + POWERUP_WEAR_OFF_TIME - self._boxing_gloves_wear_off_flash_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME - 2000, bs.WeakCallPartial(self._gloves_wear_off_flash) - ) - self._boxing_gloves_wear_off_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME, - bs.WeakCallPartial(self._gloves_wear_off), - ) - elif msg.poweruptype == 'shield': - factory = SpazFactory.get() - self.equip_shields(decay=factory.shield_decay_rate > 0) - elif msg.poweruptype == 'curse': - self.curse() - elif msg.poweruptype == 'ice_bombs': - self.bomb_type = 'ice' - tex = self._get_bomb_type_tex() - self._flash_billboard(tex) - if self.powerups_expire: - self.node.mini_billboard_2_texture = tex - t_ms = bs.time() * 1000 - assert isinstance(t_ms, int) - self.node.mini_billboard_2_start_time = t_ms - self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME - self._bomb_wear_off_flash_timer = bs.Timer( - (POWERUP_WEAR_OFF_TIME - 2000) / 1000.0, - bs.WeakCallPartial(self._bomb_wear_off_flash), - ) - - self._bomb_wear_off_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME / 1000.0, bs.WeakCallStrict(self._bomb_wear_off) - ) - - elif msg.poweruptype == 'health': - if self.edg_eff: - f = self.color[0] - r = (2, 0, 0) - g = (0, 2, 0) - bs.animate_array(self.node, 'color', 3, {0: r, 0.6: g, 1.0: f}) - self.edg_eff = False - if self._cursed: - self._cursed = False - factory = SpazFactory.get() - for attr in ['materials', 'roller_materials']: - materials = getattr(self.node, attr) - if factory.curse_material in materials: - setattr( - self.node, - attr, - tuple(m for m in materials if m != factory.curse_material), - ) - self.node.curse_death_time = 0 - self.hitpoints = self.hitpoints_max - self._flash_billboard(PowerupBoxFactory.get().tex_health) - self.node.hurt = 0 - self._last_hit_time = None - self._num_times_hit = 0 - - elif msg.poweruptype == 'tank_shield': - self.tankshield['Tank'] = True - self.edg_eff = False - tex = factory.tex_tank_shield - self._flash_billboard(tex) - - elif msg.poweruptype == 'health_damage': - tex = factory.tex_health_damage - self.edg_eff = True - f = self.color[0] - i = (2, 0.5, 2) - bs.animate_array(self.node, 'color', 3, {0: i, 0.5: i, 0.6: f}) - self._flash_billboard(tex) - self.tankshield['Tank'] = False - self.freeze_punch = False - - elif msg.poweruptype == 'goodbye': - tex = factory.tex_goodbye - self._flash_billboard(tex) - self.kill_eff = True - - elif msg.poweruptype == 'fly_bombs': - self.bomb_type = 'fly' - tex = self._get_bomb_type_tex() - self._flash_billboard(tex) - if self.powerups_expire: - self.node.mini_billboard_2_texture = tex - t_ms = bs.time() * 1000 - assert isinstance(t_ms, int) - self.node.mini_billboard_2_start_time = t_ms - self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME - self._bomb_wear_off_flash_timer = bs.Timer( - (POWERUP_WEAR_OFF_TIME - 2000) / 1000.0, - bs.WeakCallStrict(self._bomb_wear_off_flash), - ) - - self._bomb_wear_off_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME / 1000.0, bs.WeakCallStrict(self._bomb_wear_off) - ) - - elif msg.poweruptype == 'fire_bombs': - self.bomb_type = 'fire' - tex = self._get_bomb_type_tex() - self._flash_billboard(tex) - if self.powerups_expire: - self.node.mini_billboard_2_texture = tex - t_ms = bs.time() * 1000 - assert isinstance(t_ms, int) - self.node.mini_billboard_2_start_time = t_ms - self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME - self._bomb_wear_off_flash_timer = bs.Timer( - (POWERUP_WEAR_OFF_TIME - 2000) / 1000.0, - bs.WeakCallStrict(self._bomb_wear_off_flash), - ) - - self._bomb_wear_off_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME / 1000.0, bs.WeakCallStrict(self._bomb_wear_off) - ) - - elif msg.poweruptype == 'impairment_bombs': - self.bomb_type = 'impairment' - tex = self._get_bomb_type_tex() - self._flash_billboard(tex) - if self.powerups_expire: - self.node.mini_billboard_2_texture = tex - t_ms = bs.time() * 1000 - assert isinstance(t_ms, int) - self.node.mini_billboard_2_start_time = t_ms - self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME - self._bomb_wear_off_flash_timer = bs.Timer( - (POWERUP_WEAR_OFF_TIME - 2000) / 1000.0, - bs.WeakCallStrict(self._bomb_wear_off_flash), - ) - - self._bomb_wear_off_timer = bs.Timer( - POWERUP_WEAR_OFF_TIME / 1000.0, bs.WeakCallStrict(self._bomb_wear_off) - ) - - elif msg.poweruptype == 'ice_man': - tex = factory.tex_ice_man - self.bomb_type = 'ice_bubble' - self.freeze_punch = True - self.edg_eff = False - self.node.color = (0, 1, 4) - self._flash_billboard(tex) - - if self.powerups_expire: - ice_man_time = 17000 - self.node.mini_billboard_2_texture = tex - t_ms = bs.time() * 1000 - assert isinstance(t_ms, int) - self.node.mini_billboard_2_start_time = t_ms - self.node.mini_billboard_2_end_time = t_ms + ice_man_time - - self.ice_man_flash_timer = bs.Timer( - (ice_man_time - 2000) / 1000.0, babase.CallPartial(_ice_man_off_flash, self) - ) - - self.ice_man_timer = bs.Timer( - ice_man_time / 1000.0, babase.CallPartial(_ice_man_wear_off, self) - ) - - elif msg.poweruptype == 'speed': - self.node.hockey = True - tex = factory.tex_speed - self._flash_billboard(tex) - if self.powerups_expire: - speed_time = 15000 - self.node.mini_billboard_2_texture = tex - t_ms = bs.time() * 1000 - assert isinstance(t_ms, int) - self.node.mini_billboard_2_start_time = t_ms - self.node.mini_billboard_2_end_time = t_ms + speed_time - - self.speed_flash_timer = bs.Timer( - (speed_time - 2000) / 1000.0, babase.Call(_speed_off_flash, self) - ) - - self.speed_timer = bs.Timer( - speed_time / 1000.0, bs.WeakCallPartial(_speed_wear_off, self) - ) - - self.bmb_color: list = [] - self.bmb_color.append(self.bomb_type) - - self.node.handlemessage('flash') - if msg.sourcenode: - msg.sourcenode.handlemessage(bs.PowerupAcceptMessage()) - return True - - elif isinstance(msg, bs.FreezeMessage): - if not self.node: - return None - if self.node.invincible: - SpazFactory.get().block_sound.play(1.0, self.node.position) - return None - if self.shield: - return None - if not self.frozen: - self.frozen = True - self.node.frozen = True - bs.timer(5.0, bs.WeakCallPartial(self.handlemessage, bs.ThawMessage())) - if self.hitpoints <= 0: - self.shatter() - if self.freeze_punch: - self.handlemessage(bs.ThawMessage()) - - elif isinstance(msg, bs.ThawMessage): - if self.frozen and not self.shattered and self.node: - self.frozen = False - self.node.frozen = False - - elif isinstance(msg, bs.HitMessage): - if not self.node: - return None - if self.node.invincible: - SpazFactory.get().block_sound.play(1.0, self.node.position) - return True - - local_time = bs.time() * 1000 - assert isinstance(local_time, int) - if self._last_hit_time is None or local_time - self._last_hit_time > 1000: - self._num_times_hit += 1 - self._last_hit_time = local_time - - mag = msg.magnitude * self.impact_scale - velocity_mag = msg.velocity_magnitude * self.impact_scale - damage_scale = 0.22 - - def fire_effect(): - if not self.shield: - if self.node.exists(): - bs.emitfx( - position=self.node.position, - scale=3, - count=50 * 2, - spread=0.3, - chunk_type='sweat', - ) - self.node.handlemessage('celebrate', 560) - else: - self._fire_time = None - else: - self._fire_time = None - - def fire(time, damage): - if not self.shield and not self._dead: - self.hitpoints -= damage - bs.show_damage_count(f'-{damage}HP', self.node.position, msg.force_direction) - bui.getsound('fuse01').play() - - if duration != time: - self._fire_time = bs.Timer(0.1, babase.CallPartial(fire_effect), repeat=True) - else: - self._fire_time = None - - if self.hitpoints < 0: - self.node.handlemessage(bs.DieMessage()) - - if msg.hit_subtype == 'fly': - damage_scale = 0.0 - - if self.shield: - self.shield_hitpoints -= 300 - - if self.shield_hitpoints < 0: - self.shield.delete() - self.shield = None - SpazFactory.get().shield_down_sound.play(1.0, self.node.position) - elif msg.hit_subtype == 'fire': - index = 1 - duration = 5 - damage = 103 - if not self.shield: - for firex in range(duration): - bs.timer(index, bs.CallPartial(fire, index, damage)) - self._fire_time = bs.Timer(0.1, babase.CallPartial(fire_effect), repeat=True) - index += 1 - else: - self.shield_hitpoints -= 80 - if self.shield_hitpoints < 1: - self.shield.delete() - self.shield = None - SpazFactory.get().shield_down_sound.play(1.0, self.node.position) - elif msg.hit_subtype == 'impairment': - damage_scale = 0 - - if self.shield: - self.shield.delete() - self.shield = None - SpazFactory.get().shield_down_sound.play(1.0, self.node.position) - else: - hitpoints = int(self.hitpoints * 0.80) - self.hitpoints -= int(hitpoints) - bs.show_damage_count( - (f'-{int(hitpoints / 10)}%'), self.node.position, msg.force_direction - ) - - if self.hitpoints < 0 or hitpoints < 95: - self.node.handlemessage(bs.DieMessage()) - - if self.shield: - if msg.flat_damage: - damage = msg.flat_damage * self.impact_scale - else: - assert msg.force_direction is not None - self.node.handlemessage( - 'impulse', - msg.pos[0], - msg.pos[1], - msg.pos[2], - msg.velocity[0], - msg.velocity[1], - msg.velocity[2], - mag, - velocity_mag, - msg.radius, - 1, - msg.force_direction[0], - msg.force_direction[1], - msg.force_direction[2], - ) - damage = damage_scale * self.node.damage - - assert self.shield_hitpoints is not None - self.shield_hitpoints -= int(damage) - self.shield.hurt = 1.0 - float(self.shield_hitpoints) / self.shield_hitpoints_max - - max_spillover = SpazFactory.get().max_shield_spillover_damage - if self.shield_hitpoints <= 0: - - self.shield.delete() - self.shield = None - SpazFactory.get().shield_down_sound.play(1.0, self.node.position) - - npos = self.node.position - bs.emitfx( - position=(npos[0], npos[1] + 0.9, npos[2]), - velocity=self.node.velocity, - count=random.randrange(20, 30), - scale=1.0, - spread=0.6, - chunk_type='spark', - ) - - else: - SpazFactory.get().shield_hit_sound.play(0.5, self.node.position) - - assert msg.force_direction is not None - bs.emitfx( - position=msg.pos, - velocity=( - msg.force_direction[0] * 1.0, - msg.force_direction[1] * 1.0, - msg.force_direction[2] * 1.0, - ), - count=min(30, 5 + int(damage * 0.005)), - scale=0.5, - spread=0.3, - chunk_type='spark', - ) - - if self.shield_hitpoints <= -max_spillover: - leftover_damage = -max_spillover - self.shield_hitpoints - shield_leftover_ratio = leftover_damage / damage - - mag *= shield_leftover_ratio - velocity_mag *= shield_leftover_ratio - else: - return True - else: - shield_leftover_ratio = 1.0 - - if msg.flat_damage: - damage = int(msg.flat_damage * self.impact_scale * shield_leftover_ratio) - else: - assert msg.force_direction is not None - self.node.handlemessage( - 'impulse', - msg.pos[0], - msg.pos[1], - msg.pos[2], - msg.velocity[0], - msg.velocity[1], - msg.velocity[2], - mag, - velocity_mag, - msg.radius, - 0, - msg.force_direction[0], - msg.force_direction[1], - msg.force_direction[2], - ) - - damage = int(damage_scale * self.node.damage) - - if self.tankshield['Reduction']: - porcentaje = percentage_tank_shield() - dism = int(damage * porcentaje) - damage = int(damage - dism) - - bs.show_damage_count('-' + str(int(damage / 10)) + '%', msg.pos, msg.force_direction) - - self.node.handlemessage('hurt_sound') - - if self.edg_eff: - porcentaje = percentage_health_damage() - dmg_dism = int(damage * porcentaje) - self.hitpoints += dmg_dism - - PopupText( - text=f'+{int(dmg_dism / 10)}%', - scale=1.5, - position=self.node.position, - color=(0, 1, 0), - ).autoretain() - bs.animate_array( - self.node, 'color', 3, {0: (0, 1, 0), 0.39: (0, 2, 0), 0.4: self.color[0]} - ) - bui.getsound('healthPowerup').play() - - if msg.hit_type == 'punch': - self.on_punched(damage) - - try: - if msg.get_source_player(bs.Player).actor.freeze_punch: - self.node.color = (0, 1, 4) - bui.getsound('freeze').play() - self.node.handlemessage(bs.FreezeMessage()) - except: - pass - - if damage > 350: - assert msg.force_direction is not None - bs.show_damage_count( - '-' + str(int(damage / 10)) + '%', msg.pos, msg.force_direction - ) - - if msg.hit_subtype == 'super_punch': - SpazFactory.get().punch_sound_stronger.play(1.0, self.node.position) - if damage > 500: - sounds = SpazFactory.get().punch_sound_strong - sound = sounds[random.randrange(len(sounds))] - else: - sound = SpazFactory.get().punch_sound - sound.play(1.0, self.node.position) - - assert msg.force_direction is not None - bs.emitfx( - position=msg.pos, - velocity=( - msg.force_direction[0] * 0.5, - msg.force_direction[1] * 0.5, - msg.force_direction[2] * 0.5, - ), - count=min(10, 1 + int(damage * 0.0025)), - scale=0.3, - spread=0.03, - ) - - bs.emitfx( - position=msg.pos, - chunk_type='sweat', - velocity=( - msg.force_direction[0] * 1.3, - msg.force_direction[1] * 1.3 + 5.0, - msg.force_direction[2] * 1.3, - ), - count=min(30, 1 + int(damage * 0.04)), - scale=0.9, - spread=0.28, - ) - - hurtiness = damage * 0.003 - punchpos = ( - msg.pos[0] + msg.force_direction[0] * 0.02, - msg.pos[1] + msg.force_direction[1] * 0.02, - msg.pos[2] + msg.force_direction[2] * 0.02, - ) - flash_color = (1.0, 0.8, 0.4) - light = bs.newnode( - 'light', - attrs={ - 'position': punchpos, - 'radius': 0.12 + hurtiness * 0.12, - 'intensity': 0.3 * (1.0 + 1.0 * hurtiness), - 'height_attenuated': False, - 'color': flash_color, - }, - ) - bs.timer(0.06, light.delete) - - flash = bs.newnode( - 'flash', - attrs={'position': punchpos, 'size': 0.17 + 0.17 * hurtiness, 'color': flash_color}, - ) - bs.timer(0.06, flash.delete) - - if msg.hit_type == 'impact': - assert msg.force_direction is not None - bs.emitfx( - position=msg.pos, - velocity=( - msg.force_direction[0] * 2.0, - msg.force_direction[1] * 2.0, - msg.force_direction[2] * 2.0, - ), - count=min(10, 1 + int(damage * 0.01)), - scale=0.4, - spread=0.1, - ) - if self.hitpoints > 0: - if msg.hit_type == 'impact' and damage > self.hitpoints: - newdamage = max(damage - 200, self.hitpoints - 10) - damage = newdamage - self.node.handlemessage('flash') - - if damage > 0.0 and self.node.hold_node: - self.node.hold_node = None - self.hitpoints -= damage - self.node.hurt = 1.0 - float(self.hitpoints) / self.hitpoints_max - - if self._cursed and damage > 0: - bs.timer( - 0.05, bs.WeakCallPartial(self.curse_explode, msg.get_source_player(bs.Player)) - ) - - if self.frozen and (damage > 200 or self.hitpoints <= 0): - self.shatter() - elif self.hitpoints <= 0: - self.node.handlemessage(bs.DieMessage(how=bs.DeathType.IMPACT)) - - if self.hitpoints <= 0: - damage_avg = self.node.damage_smoothed * damage_scale - if damage_avg > 1000: - self.shatter() - - elif isinstance(msg, BombDiedMessage): - self.bomb_count += 1 - - elif isinstance(msg, bs.DieMessage): - - def drop_bomb(): - for xbomb in range(3): - p = self.node.position - pos = (p[0] + xbomb, p[1] + 5, p[2] - xbomb) - ball = bomb.Bomb(position=pos, bomb_type='impact').autoretain() - ball.node.mesh_scale = 0.6 - ball.node.mesh = bs.getmesh('egg') - ball.node.gravity_scale = 2 - - if self.edg_eff: - self.edg_eff = False - - wasdead = self._dead - self._dead = True - self.hitpoints = 0 - if msg.immediate: - if self.node: - self.node.delete() - elif self.node: - self.node.hurt = 1.0 - if self.play_big_death_sound and not wasdead: - SpazFactory.get().single_player_death_sound.play() - self.node.dead = True - bs.timer(2.0, self.node.delete) - - t = 0 - if self.kill_eff: - for bombs in range(3): - bs.timer(t, babase.CallPartial(drop_bomb)) - t += 0.15 - self.kill_eff = False - - elif isinstance(msg, bs.OutOfBoundsMessage): - self.handlemessage(bs.DieMessage(how=bs.DeathType.FALL)) - - elif isinstance(msg, bs.StandMessage): - self._last_stand_pos = (msg.position[0], msg.position[1], msg.position[2]) - if self.node: - self.node.handlemessage( - 'stand', msg.position[0], msg.position[1], msg.position[2], msg.angle - ) - - elif isinstance(msg, CurseExplodeMessage): - self.curse_explode() - - elif isinstance(msg, PunchHitMessage): - if not self.node: - return None - node = bs.getcollision().opposingnode - - if node and (node not in self._punched_nodes): - - punch_momentum_angular = self.node.punch_momentum_angular * self._punch_power_scale - punch_power = self.node.punch_power * self._punch_power_scale - - if node.getnodetype() != 'spaz': - sounds = SpazFactory.get().impact_sounds_medium - sound = sounds[random.randrange(len(sounds))] - sound.play(1.0, self.node.position) - - ppos = self.node.punch_position - punchdir = self.node.punch_velocity - vel = self.node.punch_momentum_linear - - self._punched_nodes.add(node) - node.handlemessage( - bs.HitMessage( - pos=ppos, - velocity=vel, - magnitude=punch_power * punch_momentum_angular * 110.0, - velocity_magnitude=punch_power * 40, - radius=0, - srcnode=self.node, - source_player=self.source_player, - force_direction=punchdir, - hit_type='punch', - hit_subtype=('super_punch' if self._has_boxing_gloves else 'default'), - ) - ) - - mag = -400.0 - if self._hockey: - mag *= 0.5 - if len(self._punched_nodes) == 1: - self.node.handlemessage( - 'kick_back', - ppos[0], - ppos[1], - ppos[2], - punchdir[0], - punchdir[1], - punchdir[2], - mag, - ) - elif isinstance(msg, PickupMessage): - if not self.node: - return None - - try: - collision = bs.getcollision() - opposingnode = collision.opposingnode - opposingbody = collision.opposingbody - except bs.NotFoundError: - return True - - try: - if opposingnode.invincible: - return True - except Exception: - pass - - if ( - opposingnode.getnodetype() == 'spaz' - and not opposingnode.shattered - and opposingbody == 4 - ): - opposingbody = 1 - - held = self.node.hold_node - if held and held.getnodetype() == 'flag': - return True - - self.node.hold_body = opposingbody - self.node.hold_node = opposingnode - elif isinstance(msg, bs.CelebrateMessage): - if self.node: - self.node.handlemessage('celebrate', int(msg.duration * 1000)) - - return None - - -class PowerupManagerWindow(PopupWindow): - def __init__(self, transition='in_right'): - columns = 2 - self._width = width = 800 - self._height = height = 500 - self._sub_height = 200 - self._scroll_width = self._width * 0.90 - self._scroll_height = self._height - 180 - self._sub_width = self._scroll_width * 0.95 - self.tab_buttons: set = {} - self.list_cls_power: list = [] - self.default_powerups = default_powerups() - self.default_power_list = list(self.default_powerups) - self.coins = apg['Bear Coin'] - self.popup_cls_power = None - - if not STORE['Buy Firebombs']: - powerups['Fire Bombs'] = 0 - self.default_power_list.remove('Fire Bombs') - - self.charstr = [ - babase.charstr(babase.SpecialChar.LEFT_ARROW), - babase.charstr(babase.SpecialChar.RIGHT_ARROW), - babase.charstr(babase.SpecialChar.UP_ARROW), - babase.charstr(babase.SpecialChar.DOWN_ARROW), - ] - - self.tabdefs = { - "Action 1": ['levelIcon', (1, 1, 1)], - "Action 2": ['settingsIcon', (1, 1, 1)], - "Action 3": ['inventoryIcon', (1, 1, 1)], - "Action 4": ['storeIcon', (1, 1, 1)], - "Action 5": ['advancedIcon', (1, 1, 1)], - "About": ['achievementEmpty', (1, 1, 1)], - } - - if STORE['Buy Firebombs'] and STORE['Buy Option'] and STORE['Buy Percentage']: - self.tabdefs = { - "Action 1": ['levelIcon', (1, 1, 1)], - "Action 2": ['settingsIcon', (1, 1, 1)], - "Action 3": ['inventoryIcon', (1, 1, 1)], - "About": ['achievementEmpty', (1, 1, 1)], - } - - self.listdef = list(self.tabdefs) - - self.count = len(self.tabdefs) - - self._current_tab = GLOBAL['Tab'] - - app = bui.app.ui_v1 - uiscale = app.uiscale - - self._root_widget = bui.containerwidget( - size=(width + 90, height + 80), - transition=transition, - scale=1.5 if uiscale is babase.UIScale.SMALL else 1.0, - stack_offset=(0, -30) if uiscale is babase.UIScale.SMALL else (0, 0), - ) - - self._backButton = b = bui.buttonwidget( - parent=self._root_widget, - autoselect=True, - position=(60, self._height - 15), - size=(130, 60), - scale=0.8, - text_scale=1.2, - label=babase.Lstr(resource='backText'), - button_type='back', - on_activate_call=babase.CallPartial(self._back), - ) - bui.buttonwidget( - edit=self._backButton, - button_type='backSmall', - size=(60, 60), - label=babase.charstr(babase.SpecialChar.BACK), - ) - bui.containerwidget(edit=self._root_widget, cancel_button=b) - - self.titletext = bui.textwidget( - parent=self._root_widget, - position=(0, height - 15), - size=(width, 50), - h_align="center", - color=bui.app.ui_v1.title_color, - v_align="center", - maxwidth=width * 1.3, - ) - - index = 0 - for tab in range(self.count): - for tab2 in range(columns): - - tag = self.listdef[index] - - position = (620 + (tab2 * 120), self._height - 50 * 2.5 - (tab * 120)) - - if tag == 'About': - text = babase.Lstr(resource='gatherWindow.aboutText') - elif tab == 'Action 4': - text = babase.Lstr(resource='storeText') - else: - text = getlanguage(tag) - - self.tab_buttons[tag] = bui.buttonwidget( - parent=self._root_widget, - autoselect=True, - position=position, - size=(110, 110), - scale=1, - label='', - enable_sound=False, - button_type='square', - on_activate_call=babase.CallPartial(self._set_tab, tag, sound=True), - ) - - self.text = bui.textwidget( - parent=self._root_widget, - position=(position[0] + 55, position[1] + 30), - size=(0, 0), - scale=1, - color=bui.app.ui_v1.title_color, - draw_controller=self.tab_buttons[tag], - maxwidth=100, - text=text, - h_align='center', - v_align='center', - ) - - self.image = bui.imagewidget( - parent=self._root_widget, - size=(60, 60), - color=self.tabdefs[tag][1], - draw_controller=self.tab_buttons[tag], - position=(position[0] + 25, position[1] + 40), - texture=bui.gettexture(self.tabdefs[tag][0]), - ) - - index += 1 - - if self.count == index: - break - - if self.count == index: - break - - self._scrollwidget = None - self._tab_container = None - self._set_tab(self._current_tab) - - def __del__(self): - apg.apply_and_commit() - - def popup_menu_closing(self, window): - print("saliendo") - - def _set_tab(self, tab, sound: bool = False): - self.sound = sound - GLOBAL['Tab'] = tab - apg.apply_and_commit() - - if self._tab_container is not None and self._tab_container.exists(): - self._tab_container.delete() - - if self.sound: - bui.getsound('swish').play() - - if self._scrollwidget: - self._scrollwidget.delete() - - self._scrollwidget = bui.scrollwidget( - parent=self._root_widget, - position=(self._width * 0.08, 51 * 1.8), - size=(self._sub_width - 140, self._scroll_height + 60 * 1.2), - ) - - if tab == 'Action 4': - if self._scrollwidget: - self._scrollwidget.delete() - - self._scrollwidget = bui.scrollwidget( - parent=self._root_widget, - position=(self._width * 0.08, 51 * 1.8), - size=(self._sub_width - 140, self._scroll_height + 60 * 1.2), - capture_arrows=True, - center_small_content=False, - selection_loops_to_parent=True, - claims_left_right=True, - claims_up_down=False, - color=(0.3, 0.3, 0.4), - ) - bui.textwidget(edit=self.titletext, text=babase.Lstr(resource='storeText')) - - elif tab == 'About': - bui.textwidget(edit=self.titletext, text=babase.Lstr(resource='gatherWindow.aboutText')) - else: - bui.textwidget(edit=self.titletext, text=getlanguage(tab)) - - choices = ['Reset', 'Only Bombs', 'Only Items', 'New', 'Nothing'] - c_display = [] - - for display in choices: - choices_display = babase.Lstr(translate=("", getlanguage(display))) - c_display.append(choices_display) - - if tab == 'Action 1': - self.popup_cls_power = PopupMenu( - parent=self._root_widget, - position=(130, self._width * 0.61), - button_size=(150, 50), - scale=2.5, - choices=choices, - width=150, - choices_display=c_display, - current_choice=GLOBAL['Cls Powerup'], - on_value_change_call=self._set_concept, - ) - self.list_cls_power.append(self.popup_cls_power._button) - - self.button_cls_power = bui.buttonwidget( - parent=self._root_widget, - position=(500, self._width * 0.61), - size=(50, 50), - autoselect=True, - scale=1, - label=('%'), - text_scale=1, - button_type='square', - on_activate_call=self._percentage_window, - ) - self.list_cls_power.append(self.button_cls_power) - - rewindow = [self.popup_cls_power._button, self.button_cls_power] - - for ( - cls - ) in self.list_cls_power: # this is very important so that pupups don't accumulate - if cls not in rewindow: - cls.delete() - - elif tab == 'Action 4': - self.button_coin = bui.buttonwidget( - parent=self._root_widget, - icon=bui.gettexture('coin'), - position=(550, self._width * 0.614), - size=(160, 40), - textcolor=(0, 1, 0), - color=(0, 1, 6), - scale=1, - label=str(apg['Bear Coin']), - text_scale=1, - autoselect=True, - on_activate_call=None, - ) # self._percentage_window) - self.list_cls_power.append(self.button_coin) - - try: - rewindow.append(self.button_coin) - except: - rewindow = [self.button_coin] - for ( - cls - ) in self.list_cls_power: # this is very important so that pupups don't accumulate - if cls not in rewindow: - cls.delete() - - else: - try: - for cls in self.list_cls_power: - cls.delete() - except: - pass - - if tab == 'Action 1': - sub_height = len(self.default_power_list) * 90 - v = sub_height - 55 - width = 300 - posi = 0 - id_power = list(self.default_powerups) - new_powerups = id_power[9:] - self.listpower = {} - - self._tab_container = c = bui.containerwidget( - parent=self._scrollwidget, - size=(self._sub_width, sub_height), - background=False, - selection_loops_to_parent=True, - ) - - for power in self.default_power_list: - if power == id_power[0]: - text = 'helpWindow.powerupShieldNameText' - tex = bui.gettexture('powerupShield') - elif power == id_power[1]: - text = 'helpWindow.powerupPunchNameText' - tex = bui.gettexture('powerupPunch') - elif power == id_power[2]: - text = 'helpWindow.powerupLandMinesNameText' - tex = bui.gettexture('powerupLandMines') - elif power == id_power[3]: - text = 'helpWindow.powerupImpactBombsNameText' - tex = bui.gettexture('powerupImpactBombs') - elif power == id_power[4]: - text = 'helpWindow.powerupIceBombsNameText' - tex = bui.gettexture('powerupIceBombs') - elif power == id_power[5]: - text = 'helpWindow.powerupBombNameText' - tex = bui.gettexture('powerupBomb') - elif power == id_power[6]: - text = 'helpWindow.powerupStickyBombsNameText' - tex = bui.gettexture('powerupStickyBombs') - elif power == id_power[7]: - text = 'helpWindow.powerupCurseNameText' - tex = bui.gettexture('powerupCurse') - elif power == id_power[8]: - text = 'helpWindow.powerupHealthNameText' - tex = bui.gettexture('powerupHealth') - elif power == id_power[9]: - text = power - tex = bui.gettexture('powerupSpeed') - elif power == id_power[10]: - text = power - tex = bui.gettexture('heart') - elif power == id_power[11]: - text = "Goodbye!" - tex = bui.gettexture('achievementOnslaught') - elif power == id_power[12]: - text = power - tex = bui.gettexture('ouyaUButton') - elif power == id_power[13]: - text = power - tex = bui.gettexture('achievementSuperPunch') - elif power == id_power[14]: - text = power - tex = bui.gettexture('levelIcon') - elif power == id_power[15]: - text = power - tex = bui.gettexture('ouyaOButton') - elif power == id_power[16]: - text = power - tex = bui.gettexture('star') - - if power in new_powerups: - label = getlanguage(power) - else: - label = babase.Lstr(resource=text) - - apperance = powerups[power] - position = (90, v - posi) - - t = bui.textwidget( - parent=c, - position=(position[0] - 30, position[1] - 15), - size=(width, 50), - h_align="center", - color=(bui.app.ui_v1.title_color), - text=label, - v_align="center", - maxwidth=width * 1.3, - ) - - self.powprev = bui.imagewidget( - parent=c, - position=(position[0] - 70, position[1] - 10), - size=(50, 50), - texture=tex, - ) - - dipos = 0 - for direc in ['-', '+']: - bui.buttonwidget( - parent=c, - autoselect=True, - position=(position[0] + 270 + dipos, position[1] - 10), - size=(100, 100), - scale=0.4, - label=direc, - button_type='square', - text_scale=4, - on_activate_call=babase.CallPartial(self.apperance_powerups, power, direc), - ) - - dipos += 100 - - textwidget = bui.textwidget( - parent=c, - position=(position[0] + 190, position[1] - 15), - size=(width, 50), - h_align="center", - color=cls_pow_color()[apperance], - text=str(apperance), - v_align="center", - maxwidth=width * 1.3, - ) - self.listpower[power] = textwidget - - posi += 90 - - elif tab == 'Action 2': - sub_height = 370 if not STORE['Buy Option'] else 450 - v = sub_height - 55 - width = 300 - - self._tab_container = c = bui.containerwidget( - parent=self._scrollwidget, - size=(self._sub_width, sub_height), - background=False, - selection_loops_to_parent=True, - ) - - position = (40, v - 20) - - c_display = [] - choices = ['Auto', 'SY: BALL', 'SY: Impact', 'SY: Egg'] - for display in choices: - choices_display = babase.Lstr(translate=("", getlanguage(display))) - c_display.append(choices_display) - - popup = PopupMenu( - parent=c, - position=(position[0] + 300, position[1]), - button_size=(150, 50), - scale=2.5, - choices=choices, - width=150, - choices_display=c_display, - current_choice=config['Powerup Style'], - on_value_change_call=babase.CallPartial(self._all_popup, 'Powerup Style'), - ) - - text = getlanguage('Powerup Style') - wt = len(text) * 0.80 - t = bui.textwidget( - parent=c, - position=(position[0] - 60 + wt, position[1]), - size=(width, 50), - maxwidth=width * 0.9, - scale=1.1, - h_align="center", - color=bui.app.ui_v1.title_color, - text=getlanguage('Powerup Style'), - v_align="center", - ) - - dipos = 0 - for direc in ['-', '+']: - bui.buttonwidget( - parent=c, - autoselect=True, - position=(position[0] + 310 + dipos, position[1] - 100), - size=(100, 100), - repeat=True, - scale=0.4, - label=direc, - button_type='square', - text_scale=4, - on_activate_call=babase.CallPartial(self._powerups_scale, direc), - ) - dipos += 100 - - txt_scale = config['Powerup Scale'] - self.txt_scale = bui.textwidget( - parent=c, - position=(position[0] + 230, position[1] - 105), - size=(width, 50), - scale=1.1, - h_align="center", - color=(0, 1, 0), - text=str(txt_scale), - v_align="center", - maxwidth=width * 1.3, - ) - - text = getlanguage('Powerup Scale') - wt = len(text) * 0.80 - t = bui.textwidget( - parent=c, - position=(position[0] - 60 + wt, position[1] - 100), - size=(width, 50), - maxwidth=width * 0.9, - scale=1.1, - h_align="center", - color=bui.app.ui_v1.title_color, - text=text, - v_align="center", - ) - - position = (position[0] - 20, position[1] + 40) - - self.check = bui.checkboxwidget( - parent=c, - position=(position[0] + 30, position[1] - 230), - value=config['Powerup Name'], - on_value_change_call=babase.CallPartial(self._switches, 'Powerup Name'), - maxwidth=self._scroll_width * 0.9, - text=getlanguage('Powerup Name'), - autoselect=True, - ) - - self.check = bui.checkboxwidget( - parent=c, - position=(position[0] + 30, position[1] - 230 * 1.3), - value=config['Powerup With Shield'], - on_value_change_call=babase.CallPartial(self._switches, 'Powerup With Shield'), - maxwidth=self._scroll_width * 0.9, - text=getlanguage('Powerup With Shield'), - autoselect=True, - ) - - if STORE['Buy Option']: - self.check = bui.checkboxwidget( - parent=c, - position=(position[0] + 30, position[1] - 230 * 1.6), - value=config['Powerup Time'], - on_value_change_call=babase.CallPartial(self._switches, 'Powerup Time'), - maxwidth=self._scroll_width * 0.9, - text=getlanguage('Powerup Time'), - autoselect=True, - ) - - elif tab == 'Action 3': - sub_height = 300 - v = sub_height - 55 - width = 300 - - self._tab_container = c = bui.containerwidget( - parent=self._scrollwidget, - size=(self._sub_width, sub_height), - background=False, - selection_loops_to_parent=True, - ) - - v -= 20 - position = (110, v - 45 * 1.72) - - if not STORE['Buy Percentage']: - t = bui.textwidget( - parent=c, - position=(90, v - 100), - size=(30 + width, 50), - h_align="center", - text=getlanguage('Block Option Store'), - color=bui.app.ui_v1.title_color, - v_align="center", - maxwidth=width * 1.5, - scale=1.5, - ) - - i = bui.imagewidget( - parent=c, - position=(position[0] + 100, position[1] - 205), - size=(80, 80), - texture=bui.gettexture('lock'), - ) - else: - t = bui.textwidget( - parent=c, - position=(position[0] - 14, position[1] + 70), - size=(30 + width, 50), - h_align="center", - text=f"{getlanguage('Tank Shield PTG')} ({getlanguage('Tank Shield')})", - color=bui.app.ui_v1.title_color, - v_align="center", - maxwidth=width * 1.5, - scale=1.5, - ) - - b = bui.buttonwidget( - parent=c, - autoselect=True, - position=position, - size=(100, 100), - repeat=True, - scale=0.6, - label=self.charstr[3], - button_type='square', - text_scale=2, - on_activate_call=babase.CallPartial(self.tank_shield_percentage, 'Decrement'), - ) - - b = bui.buttonwidget( - parent=c, - autoselect=True, - repeat=True, - text_scale=2, - position=(position[0] * 3.2, position[1]), - size=(100, 100), - scale=0.6, - label=self.charstr[2], - button_type='square', - on_activate_call=babase.CallPartial(self.tank_shield_percentage, 'Increment'), - ) - - porcentaje = config['Tank Shield PTG'] - if porcentaje > 59: - color = (0, 1, 0) - elif porcentaje < 40: - color = (1, 1, 0) - else: - color = (0, 1, 0.8) - - self.tank_text = bui.textwidget( - parent=c, - position=(position[0] - 14, position[1] + 5), - size=(30 + width, 50), - h_align="center", - text=str(porcentaje) + '%', - color=color, - v_align="center", - maxwidth=width * 1.3, - scale=2, - ) - - # -----> - - position = (110, v - 160 * 1.6) - t = bui.textwidget( - parent=c, - position=(position[0] - 14, position[1] + 70), - size=(30 + width, 50), - h_align="center", - text=f"{getlanguage('Healing Damage PTG')}{_sp_}({getlanguage('Healing Damage')})", - color=bui.app.ui_v1.title_color, - v_align="center", - maxwidth=width * 1.3, - scale=1.4, - ) - - b = bui.buttonwidget( - parent=c, - autoselect=True, - position=position, - size=(100, 100), - repeat=True, - scale=0.6, - label=self.charstr[3], - button_type='square', - text_scale=2, - on_activate_call=babase.CallPartial(self.health_damage_percentage, 'Decrement'), - ) - - b = bui.buttonwidget( - parent=c, - autoselect=True, - repeat=True, - text_scale=2, - position=(position[0] * 3.2, position[1]), - size=(100, 100), - scale=0.6, - label=self.charstr[2], - button_type='square', - on_activate_call=babase.CallPartial(self.health_damage_percentage, 'Increment'), - ) - - porcentaje = config['Healing Damage PTG'] - if porcentaje > 59: - color = (0, 1, 0) - elif porcentaje < 40: - color = (1, 1, 0) - else: - color = (0, 1, 0.8) - - self.hlg_text = bui.textwidget( - parent=c, - position=(position[0] - 14, position[1] + 5), - size=(30 + width, 50), - h_align="center", - text=str(porcentaje) + '%', - color=color, - v_align="center", - maxwidth=width * 1.3, - scale=2, - ) - - elif tab == 'Percentage': - sub_height = len(self.default_power_list) * 90 - v = sub_height - 55 - width = 300 - posi = 0 - id_power = list(self.default_powerups) - new_powerups = id_power[9:] - self.listpower = {} - - self._tab_container = c = bui.containerwidget( - parent=self._scrollwidget, - size=(self._sub_width, sub_height), - background=False, - selection_loops_to_parent=True, - ) - - for power in self.default_power_list: - if power == id_power[0]: - text = 'helpWindow.powerupShieldNameText' - tex = bui.gettexture('powerupShield') - elif power == id_power[1]: - text = 'helpWindow.powerupPunchNameText' - tex = bui.gettexture('powerupPunch') - elif power == id_power[2]: - text = 'helpWindow.powerupLandMinesNameText' - tex = bui.gettexture('powerupLandMines') - elif power == id_power[3]: - text = 'helpWindow.powerupImpactBombsNameText' - tex = bui.gettexture('powerupImpactBombs') - elif power == id_power[4]: - text = 'helpWindow.powerupIceBombsNameText' - tex = bui.gettexture('powerupIceBombs') - elif power == id_power[5]: - text = 'helpWindow.powerupBombNameText' - tex = bui.gettexture('powerupBomb') - elif power == id_power[6]: - text = 'helpWindow.powerupStickyBombsNameText' - tex = bui.gettexture('powerupStickyBombs') - elif power == id_power[7]: - text = 'helpWindow.powerupCurseNameText' - tex = bui.gettexture('powerupCurse') - elif power == id_power[8]: - text = 'helpWindow.powerupHealthNameText' - tex = bui.gettexture('powerupHealth') - elif power == id_power[9]: - text = power - tex = bui.gettexture('powerupSpeed') - elif power == id_power[10]: - text = power - tex = bui.gettexture('heart') - elif power == id_power[11]: - text = "Goodbye!" - tex = bui.gettexture('achievementOnslaught') - elif power == id_power[12]: - text = power - tex = bui.gettexture('ouyaUButton') - elif power == id_power[13]: - text = power - tex = bui.gettexture('achievementSuperPunch') - elif power == id_power[14]: - text = power - tex = bui.gettexture('levelIcon') - elif power == id_power[15]: - text = power - tex = bui.gettexture('ouyaOButton') - elif power == id_power[16]: - text = power - tex = bui.gettexture('star') - - if power in new_powerups: - label = getlanguage(power) - else: - label = babase.Lstr(resource=text) - - apperance = powerups[power] - position = (90, v - posi) - - t = bui.textwidget( - parent=c, - position=(position[0] - 30, position[1] - 15), - size=(width, 50), - h_align="center", - color=(bui.app.ui_v1.title_color), - text=label, - v_align="center", - maxwidth=width * 1.3, - ) - - self.powprev = bui.imagewidget( - parent=c, - position=(position[0] - 70, position[1] - 10), - size=(50, 50), - texture=tex, - ) - - ptg = str(self.total_percentage(power)) - t = bui.textwidget( - parent=c, - position=(position[0] + 170, position[1] - 10), - size=(width, 50), - h_align="center", - color=(0, 1, 0), - text=(f'{ptg}%'), - v_align="center", - maxwidth=width * 1.3, - ) - - posi += 90 - - elif tab == 'Action 4': - sub_height = 370 - width = 300 - v = sub_height - 55 - u = width - 60 - - if not self._scrollwidget or not self._scrollwidget.exists(): - return - self._tab_container = c = bui.containerwidget( - parent=self._scrollwidget, - size=(width + 500, sub_height), - background=False, - selection_loops_to_parent=True, - ) - - position = (u + 150, v - 250) - n_pos = 0 - prices = [7560, 5150, 3360] - str_name = ["FireBombs Store", "Timer Store", "Percentages Store"] - images = ["ouyaOButton", "settingsIcon", "inventoryIcon"] - - index = 0 - for store in store_items(): - p = prices[index] - txt = str_name[index] - label = getlanguage(txt) - tx_pos = len(label) * 1.8 - lb_scale = len(label) * 0.20 - preview = images[index] - - if STORE[store]: - text = getlanguage('Bought') - icon = bui.gettexture('graphicsIcon') - color = (0.52, 0.48, 0.63) - txt_scale = 1.5 - else: - text = str(p) - icon = bui.gettexture('coin') - color = (0.5, 0.4, 0.93) - txt_scale = 2 - - b = bui.buttonwidget( - parent=c, - autoselect=True, - position=(position[0] + 210 - n_pos, position[1]), - size=(250, 80), - scale=0.7, - label=text, - text_scale=txt_scale, - icon=icon, - color=color, - iconscale=1.7, - on_activate_call=babase.CallPartial(self._buy_object, store, p), - ) - - s = 180 - b = bui.buttonwidget( - parent=c, - autoselect=True, - position=(position[0] + 210 - n_pos, position[1] + 55), - size=(s, s + 30), - scale=1, - label='', - color=color, - button_type='square', - on_activate_call=babase.CallPartial(self._buy_object, store, p), - ) - - s -= 80 - i = bui.imagewidget( - parent=c, - draw_controller=b, - position=(position[0] + 250 - n_pos, position[1] + 140), - size=(s, s), - texture=bui.gettexture(preview), - ) - - t = bui.textwidget( - parent=c, - position=(position[0] + 270 - n_pos, position[1] + 101), - h_align="center", - color=(bui.app.ui_v1.title_color), - text=label, - v_align="center", - maxwidth=130, - ) - - n_pos += 280 - index += 1 - - elif tab == 'Action 5': - sub_height = 370 - v = sub_height - 55 - width = 300 - - self._tab_container = c = bui.containerwidget( - parent=self._scrollwidget, - size=(self._sub_width, sub_height), - background=False, - selection_loops_to_parent=True, - ) - - position = (0, v - 30) - - t = bui.textwidget( - parent=c, - position=(position[0] + 80, position[1] - 30), - size=(width + 60, 50), - scale=1, - h_align="center", - color=(bui.app.ui_v1.title_color), - text=babase.Lstr(resource='settingsWindowAdvanced.enterPromoCodeText'), - v_align="center", - maxwidth=width * 1.3, - ) - - self.promocode_text = bui.textwidget( - parent=c, - position=(position[0] + 80, position[1] - 100), - size=(width + 60, 50), - scale=1, - editable=True, - h_align="center", - color=(bui.app.ui_v1.title_color), - text='', - v_align="center", - maxwidth=width * 1.3, - max_chars=30, - description=babase.Lstr(resource='settingsWindowAdvanced.enterPromoCodeText'), - ) - - self.promocode_button = bui.buttonwidget( - parent=c, - position=(position[0] + 160, position[1] - 170), - size=(200, 60), - scale=1.0, - label=babase.Lstr(resource='submitText'), - on_activate_call=self._promocode, - ) - - else: - sub_height = 0 - v = sub_height - 55 - width = 300 - - self._tab_container = c = bui.containerwidget( - parent=self._scrollwidget, - size=(self._sub_width, sub_height), - background=False, - selection_loops_to_parent=True, - ) - - t = bui.textwidget( - parent=c, - position=(110, v - 20), - size=(width, 50), - scale=1.4, - big=True, - color=(0.5, 0.5, 0.5), - h_align="center", - v_align="center", - text=("powerup manager 1.1.1"), - maxwidth=width * 30, - ) - - t = bui.textwidget( - parent=c, - position=(110, v - 90), - size=(width, 50), - scale=1, - color=(1.3, 0.5, 1.0), - h_align="center", - v_align="center", - text=getlanguage('Creator'), - maxwidth=width * 30, - ) - - t = bui.textwidget( - parent=c, - position=(110, v - 220), - size=(width, 50), - scale=1, - color=(1.0, 1.2, 0.3), - h_align="center", - v_align="center", - text=getlanguage('Mod Info'), - maxwidth=width * 30, - ) - - for select_tab, button_tab in self.tab_buttons.items(): - if select_tab == tab: - bui.buttonwidget(edit=button_tab, color=(0.5, 0.4, 1.5)) - else: - bui.buttonwidget(edit=button_tab, color=(0.52, 0.48, 0.63)) - - def _all_popup(self, tag: str, popup: str) -> None: - config[tag] = popup - apg.apply_and_commit() - - def _set_concept(self, concept: str) -> None: - GLOBAL['Cls Powerup'] = concept - - if concept == 'Reset': - for power, deflt in default_powerups().items(): - powerups[power] = deflt - elif concept == 'Nothing': - for power in default_powerups(): - powerups[power] = 0 - elif concept == 'Only Bombs': - for power, deflt in default_powerups().items(): - if 'Bombs' not in power: - powerups[power] = 0 - else: - powerups[power] = 3 - elif concept == 'Only Items': - for power, deflt in default_powerups().items(): - if 'Bombs' in power: - powerups[power] = 0 - else: - powerups[power] = deflt - elif concept == 'New': - default_power = default_powerups() - new_powerups = list(default_power)[9:] - for power, deflt in default_power.items(): - if power not in new_powerups: - powerups[power] = 0 - else: - powerups[power] = deflt - - if not STORE['Buy Firebombs']: - powerups['Fire Bombs'] = 0 - - self._set_tab('Action 1') - - def tank_shield_percentage(self, tag): - max = 96 - min = 40 - if tag == 'Increment': - config['Tank Shield PTG'] += 1 - if config['Tank Shield PTG'] > max: - config['Tank Shield PTG'] = min - elif tag == 'Decrement': - config['Tank Shield PTG'] -= 1 - if config['Tank Shield PTG'] < min: - config['Tank Shield PTG'] = max - - porcentaje = config['Tank Shield PTG'] - if porcentaje > 59: - color = (0, 1, 0) - elif porcentaje < 40: - color = (1, 1, 0) - else: - color = (0, 1, 0.8) - bui.textwidget(edit=self.tank_text, text=str(porcentaje) + '%', color=color) - - def health_damage_percentage(self, tag): - max = 80 - min = 35 - if tag == 'Increment': - config['Healing Damage PTG'] += 1 - if config['Healing Damage PTG'] > max: - config['Healing Damage PTG'] = min - elif tag == 'Decrement': - config['Healing Damage PTG'] -= 1 - if config['Healing Damage PTG'] < min: - config['Healing Damage PTG'] = max - - porcentaje = config['Healing Damage PTG'] - if porcentaje > 59: - color = (0, 1, 0) - elif porcentaje < 40: - color = (1, 1, 0) - else: - color = (0, 1, 0.8) - bui.textwidget(edit=self.hlg_text, text=str(porcentaje) + '%', color=color) - - def apperance_powerups(self, powerup: str, ID: str): - max = 7 - if ID == "-": - if powerups[powerup] == 0: - powerups[powerup] = max - else: - powerups[powerup] -= 1 - elif ID == "+": - if powerups[powerup] == max: - powerups[powerup] = 0 - else: - powerups[powerup] += 1 - enum = powerups[powerup] - bui.textwidget( - edit=self.listpower[powerup], text=str(powerups[powerup]), color=cls_pow_color()[enum] - ) - - def _powerups_scale(self, ID: str): - max = 1.5 - min = 0.5 - sc = 0.1 - if ID == "-": - if config['Powerup Scale'] < (min + 0.1): - config['Powerup Scale'] = max - else: - config['Powerup Scale'] -= sc - elif ID == "+": - if config['Powerup Scale'] > (max - 0.1): - config['Powerup Scale'] = min - else: - config['Powerup Scale'] += sc - config['Powerup Scale'] = round(config['Powerup Scale'], 1) - bui.textwidget(edit=self.txt_scale, text=str(config['Powerup Scale'])) - - def total_percentage(self, power): - total = 0 - pw = powerups[power] - for i, i2 in powerups.items(): - total += i2 - if total == 0: - return float(total) - else: - ptg = 100 * pw / total - result = round(ptg, 2) - return result - - def store_refresh(self, tag: str): - if tag == 'Buy Firebombs': - powerups['Fire Bombs'] = 3 - self.default_power_list.append('Fire Bombs') - self._set_tab('Action 4') - - def _buy_object(self, tag: str, price: int): - store = BearStore( - value=tag, price=price, callback=babase.CallPartial(self.store_refresh, tag) - ) - store.buy() - - def _promocode(self): - code = bui.textwidget(query=self.promocode_text) - promo = PromoCode(code=code) - promo.code_confirmation() - bui.textwidget(edit=self.promocode_text, text="") - - def _switches(self, tag, m): - config[tag] = False if m == 0 else True - apg.apply_and_commit() - - def _percentage_window(self): - self._set_tab('Percentage') - - def _back(self): - bui.containerwidget(edit=self._root_widget, transition='out_left') - babase.app.classic.profile_browser_window() - - -# ba_meta export babase.Plugin - - -class UltimatePowerupManager(babase.Plugin): - # ProfileBrowserWindow = NewProfileBrowserWindow - pupbox.PowerupBoxFactory = NewPowerupBoxFactory - pupbox.PowerupBox.__init__ = _pbx_ - Bomb.__init__ = _bomb_init - SpazBot.handlemessage = bot_handlemessage - Blast.handlemessage = bomb_handlemessage - Spaz.handlemessage = new_handlemessage - Spaz.__init__ = _init_spaz_ - Spaz._get_bomb_type_tex = new_get_bomb_type_tex - Spaz.on_punch_press = spaz_on_punch_press - Spaz.on_punch_release = spaz_on_punch_release - MainMenuActivity.on_transition_in = new_on_transition_in - - def __init__(self) -> None: - - # add_plugin() - ... - - def has_settings_ui(self): - return True - - def show_settings_ui(self, origin_widget): - PowerupManagerWindow() +# ba_meta require api 9 +from __future__ import annotations + +import babase +import bauiv1 as bui +import bascenev1 as bs +import random +from bascenev1lib.actor import bomb +from bascenev1lib.actor import powerupbox as pupbox +from bascenev1lib.actor.spazbot import SpazBot +from bascenev1lib.actor.bomb import Bomb, Blast +from bauiv1lib.popup import PopupWindow, PopupMenuWindow, PopupMenu +from bascenev1lib.actor.spaz import ( + Spaz, + SpazFactory, + PickupMessage, + PunchHitMessage, + CurseExplodeMessage, + BombDiedMessage, +) +from bascenev1lib.mainmenu import MainMenuActivity, MainMenuSession +from bascenev1lib.gameutils import SharedObjects +from bascenev1lib.actor.powerupbox import PowerupBoxFactory +from bascenev1lib.actor.popuptext import PopupText +from bauiv1lib.confirm import ConfirmWindow +from bascenev1lib.actor.spaz import * + +from typing import TYPE_CHECKING + +plugman = dict( + plugin_name="powerup_manager", + description="This plugin add new modded powerups and features to manage them", + external_url="", + authors=[ + {"name": "ATD", "email": "anasdhaoidi001@gmail.com", "discord": ""}, + ], + version="1.0.0", +) + + +_sp_ = '\n' + +if TYPE_CHECKING: + pass + + +# === Mod updated by ATD and Less === + + +def getlanguage(text, subs: str = None, almacen: list = []): + if almacen == []: + almacen = list(range(1000)) + lang = bs.app.lang.language + translate = { + "Reset": {"Spanish": "Reiniciar", "English": "Reset", "Portuguese": "Reiniciar"}, + "Nothing": { + "Spanish": "Sin potenciadores", + "English": "No powerups", + "Portuguese": "Sem powerups", + }, + "Action 1": {"Spanish": "Potenciadores", "English": "Powerups", "Portuguese": "Powerups"}, + "Action 2": {"Spanish": "Configuración", "English": "Settings", "Portuguese": "Definições"}, + "Action 3": {"Spanish": "Extras", "English": "Extras", "Portuguese": "Extras"}, + "Action 4": {"Spanish": "Tienda", "English": "Store", "Portuguese": "Loja"}, + "Action 5": { + "Spanish": "Canjear código", + "English": "Enter Code", + "Portuguese": "Código promocional", + }, + "Custom": {"Spanish": "", "English": "Customize", "Portuguese": "Customizar"}, + "Impairment Bombs": { + "Spanish": "Bombas menoscabo", + "English": "Hyperactive bombs", + "Portuguese": "Bombas hiperativas", + }, + "Speed": {"Spanish": "Velocidad", "English": "Speed", "Portuguese": "Velocidade"}, + "Fire Bombs": { + "Spanish": "Bombas de fuego", + "English": "Fire Bombs", + "Portuguese": "Bombas de fogo", + }, + "Ice Man": { + "Spanish": "Hombre de hielo", + "English": "Ice man", + "Portuguese": "Homem de gelo", + }, + "Fly Bombs": { + "Spanish": "Bombas expansivas", + "English": "Expansive bombs", + "Portuguese": "Bombas expansivas", + }, + "Goodbye": {"Spanish": "¡Hasta luego!", "English": "Goodbye!", "Portuguese": "Adeus!"}, + "Healing Damage": { + "Spanish": "Auto-curación", + "English": "Healing Damage", + "Portuguese": "Auto-cura", + }, + "Tank Shield": { + "Spanish": "Súper blindaje", + "English": "Reinforced shield", + "Portuguese": "Escudo reforçado", + }, + "Tank Shield PTG": { + "Spanish": "Porcentaje de disminución", + "English": "Percentage decreased", + "Portuguese": "Percentual reduzido", + }, + "Healing Damage PTG": { + "Spanish": "Porcentaje de recuperación de salud", + "English": "Percentage of health recovered", + "Portuguese": "Porcentagem de recuperação de saúde", + }, + "SY: BALL": {"Spanish": "Esfera", "English": "Sphere", "Portuguese": "Esfera"}, + "SY: Impact": {"Spanish": "Especial", "English": "Special", "Portuguese": "Especial"}, + "SY: Egg": {"Spanish": "Huevito", "English": "Egg shape", "Portuguese": "Ovo"}, + "Powerup Scale": { + "Spanish": "Tamaño del potenciador", + "English": "Powerups size", + "Portuguese": "Tamanho de powerups", + }, + "Powerup With Shield": { + "Spanish": "Potenciadores con escudo", + "English": "Powerups with shield", + "Portuguese": "Powerups com escudo", + }, + "Powerup Time": { + "Spanish": "Mostrar Temporizador", + "English": "Show end time", + "Portuguese": "Mostrar cronômetro", + }, + "Powerup Style": { + "Spanish": "Forma de los potenciadores", + "English": "Shape of powerup", + "Portuguese": "Forma de powerup", + }, + "Powerup Name": { + "Spanish": "Mostrar nombre en los potenciadores", + "English": "Show name on powerups", + "Portuguese": "Mostrar nome em powerups", + }, + "Percentage": { + "Spanish": "Probabilidad", + "English": "Show percentage", + "Portuguese": "Mostrar porcentagem", + }, + "Only Items": { + "Spanish": "Sólo Accesorios", + "English": "Only utensils", + "Portuguese": "Apenas utensilios", + }, + "New": {"Spanish": "Nuevo", "English": "New", "Portuguese": "Novo"}, + "Only Bombs": { + "Spanish": "Sólo Bombas", + "English": "Only bombs", + "Portuguese": "Apenas bombas", + }, + "Coins 0": { + "Spanish": "Monedas Insuficientes", + "English": "Insufficient coins", + "Portuguese": "Moedas insuficientes", + }, + "Purchase": { + "Spanish": "Compra realizada correctamente", + "English": "Successful purchase", + "Portuguese": "Compra Bem Sucedida", + }, + "Double Product": { + "Spanish": "Ya has comprado este artículo", + "English": "You've already bought this", + "Portuguese": "Voce ja comprou isto", + }, + "Bought": {"Spanish": "Comprado", "English": "Bought", "Portuguese": "Comprou"}, + "Confirm Purchase": { + "Spanish": f'Tienes {subs} monedas. {_sp_} ¿Deseas comprar esto?', + "English": f'You have {subs} coins. {_sp_} Do you want to buy this?', + "Portuguese": f'Você tem {subs} moedas. {_sp_} Deseja comprar isto?', + }, + "FireBombs Store": { + "Spanish": 'Bombas de fuego', + "English": 'Fire bombs', + "Portuguese": 'Bombas de incêndio', + }, + "Timer Store": {"Spanish": 'Temporizador', "English": 'Timer', "Portuguese": 'Timer'}, + "Percentages Store": {"Spanish": 'Extras', "English": 'Extras', "Portuguese": 'Extras'}, + "Block Option Store": { + "Spanish": f"Uuups..{_sp_}Esta opción está bloqueada.{_sp_} Para acceder a ella puedes {_sp_} comprarla en la tienda.{_sp_} Gracias...", + "English": f"Oooops...{_sp_}This option is blocked. {_sp_} To access it you can buy {_sp_} it in the store.{_sp_} Thank you...", + "Portuguese": f"Ooops...{_sp_}Esta opção está bloqueada. {_sp_} Para acessá-lo, você pode {_sp_} comprá-lo na loja.{_sp_} Obrigado...", + }, + "True Code": { + "Spanish": "¡Código canjeado!", + "English": "Successful code!", + "Portuguese": "¡Código válido!", + }, + "False Code": { + "Spanish": "Código ya canjeado", + "English": "Expired code", + "Portuguese": "Código expirado", + }, + "Invalid Code": { + "Spanish": "Código inválido", + "English": "Invalid code", + "Portuguese": "Código inválido", + }, + "Reward Code": { + "Spanish": f"¡Felicitaciones! ¡Ganaste {subs} monedas!", + "English": f"Congratulations! You've {subs} coins", + "Portuguese": f"Parabéns! Você tem {subs} moedas", + }, + "Creator": { + "Spanish": "Mod edited by ATD", + "English": "Mod edited by ATD", + "Portuguese": "Mod edited by ATD", + }, + "Mod Info": { + "Spanish": f"Un mod genial que te permite gestionar {_sp_} los potenciadores a tu antojo. {_sp_} también incluye 8 potenciadores extra{_sp_} dejando 17 en total... ¡Guay!", + "English": f"A cool mod that allows you to manage {_sp_} powerups at your whims. {_sp_} also includes 8 extra powerups{_sp_} leaving 17 in total... Wow!", + "Portuguese": f"Um mod legal que permite que você gerencie os{_sp_} powerups de de acordo com seus caprichos. {_sp_} também inclui 8 powerups extras,{_sp_} deixando 17 no total... Uau!", + }, + "Coins Message": { + "Spanish": f"Recompensa: {subs} Monedas", + "English": f"Reward: {subs} Coins", + "Portuguese": f"Recompensa: {subs} Moedas", + }, + "Coins Limit Message": { + "Spanish": f"Ganaste {almacen[0]} Monedas.{_sp_} Pero has superado el límite de {almacen[1]}", + "English": f"You won {almacen[0]} Coins. {_sp_} But you have exceeded the limit of {almacen[1]}", + "Portuguese": f"Você ganhou {almacen[0]} Moedas. {_sp_} Mas você excedeu o limite de {almacen[1]}", + }, + } + languages = ['Spanish', 'Portuguese', 'English'] + if lang not in languages: + lang = 'English' + + if text not in translate: + return text + + return translate[text][lang] + + +def settings_distribution(): + return { + "Powers Gravity": False, + "Tank Shield PTG": 96, + "Healing Damage PTG": 72, + "Powerup Style": 'Auto', + "Powerup Scale": 1.0, + "Powerup Name": False, + "Powerup With Shield": False, + "Powerup Time": False, + } + + +apg = babase.app.config +if "PPM Settings" in apg: + old = apg['PPM Settings'] + for settings in settings_distribution(): + if settings not in old: + apg['PPM Settings'] = settings_distribution() +else: + apg['PPM Settings'] = settings_distribution() +apg.apply_and_commit() + +config = apg['PPM Settings'] + + +def default_powerups(): + return { + "Shield": 2, + "Punch": 3, + "Mine Bombs": 2, + "Impact Bombs": 3, + "Ice Bombs": 3, + "Triple": 3, + "Sticky Bombs": 3, + "Curse": 1, + "Health": 1, + "Speed": 2, + "Healing Damage": 1, + "Goodbye": 2, + "Ice Man": 1, + "Tank Shield": 1, + "Impairment Bombs": 2, + "Fire Bombs": 3, + "Fly Bombs": 3, + } + + +if "Powerups" in config: + p_old = config['Powerups'] + for powerups in default_powerups(): + if powerups not in p_old: + config['Powerups'] = default_powerups() +else: + config['Powerups'] = default_powerups() +apg.apply_and_commit() + +powerups = config['Powerups'] + +# === EXTRAS === + +GLOBAL = {"Tab": 'Action 1', "Cls Powerup": 0, "Coins Message": []} + + +# === STORE === +def promo_codes(): + return { + "G-Am54igO42Os": [True, 1100], + "P-tRo8nM8dZ": [True, 2800], + "Y-tU2B3S": [True, 500], + "B-0mB3RYT2z": [True, 910], + "B-Asd14mON9G0D": [True, 910], + "D-rAcK0cJ23": [True, 910], + "E-a27ZO6f3Y": [True, 600], + "E-Am54igO42Os": [True, 600], + "E-M4uN3K34XB": [True, 840], + "PM-731ClcAF": [True, 50000], + } + + +def store_items(): + return {"Buy Firebombs": True, "Buy Option": True, "Buy Percentage": True} + + +if apg.get('Bear Coin') is None: + apg['Bear Coin'] = 0 + apg.apply_and_commit() + +if apg.get('Bear Coin') is not None: + if apg['Bear Coin'] <= 0: + apg['Bear Coin'] = 0 + apg['Bear Coin'] = int(apg['Bear Coin']) + +if apg.get('Bear Store') is None: + apg['Bear Store'] = {} + +for i, j in store_items().items(): + store = apg['Bear Store'] + if i not in store: + if store.get(i) is None: + store[i] = j + apg.apply_and_commit() + +STORE = apg['Bear Store'] + +if STORE.get('Promo Code') is None: + STORE['Promo Code'] = promo_codes() + +for i, x in promo_codes().items(): + pmcode = STORE['Promo Code'] + if i not in pmcode: + if pmcode.get(i) is None: + pmcode[i] = x + +apg.apply_and_commit() + + +class BearStore: + def __init__(self, price: int = 1000, value: str = '', callback: call = None): + + self.price = price + self.value = value + self.store = STORE[value] + self.coins = apg['Bear Coin'] + self.callback = callback + + def buy(self): + if not self.store: + if self.coins >= (self.price): + + def confirm(): + STORE[self.value] = True + apg['Bear Coin'] -= int(self.price) + bs.broadcastmessage(getlanguage('Purchase'), (0, 1, 0)) + bui.getsound('cashRegister').play() + apg.apply_and_commit() + self.callback() + + ConfirmWindow( + getlanguage('Confirm Purchase', subs=self.coins), + width=400, + height=120, + action=confirm, + ok_text=babase.Lstr(resource='okText'), + ) + else: + bs.broadcastmessage(getlanguage('Coins 0'), (1, 0, 0)) + bui.getsound('error').play() + else: + bs.broadcastmessage(getlanguage('Double Product'), (1, 0, 0)) + bui.getsound('error').play() + + def __del__(self): + apg['Bear Coin'] = int(apg['Bear Coin']) + apg.apply_and_commit() + + +class PromoCode: + def __init__(self, code: str = ''): + self.code = code + self.codes_store = STORE['Promo Code'] + if self.code in self.codes_store: + self.code_type = STORE['Promo Code'][code] + self.promo_code_expire = self.code_type[0] + self.promo_code_amount = self.code_type[1] + + def __del__(self): + apg['Bear Coin'] = int(apg['Bear Coin']) + apg.apply_and_commit() + + def code_confirmation(self): + if self.code != "": + bs.broadcastmessage(babase.Lstr(resource='submittingPromoCodeText'), (0, 1, 0)) + try: + babase.pushcall(babase.CallPartial(self.validate_code), from_other_thread=True) + except: + pass + + def validate_code(self): + if self.code in self.codes_store: + if self.promo_code_expire: + with babase.ContextRef.empty(): + babase.pushcall( + babase.CallPartial(self.successful_code), from_other_thread=True + ) + bs.broadcastmessage(getlanguage('True Code'), (0, 1, 0)) + bui.getsound('cheer').play() + self.code_type[0] = False + else: + bs.broadcastmessage(getlanguage('False Code'), (1, 0, 0)) + bui.getsound('error').play() + else: + bs.broadcastmessage(getlanguage('Invalid Code'), (1, 0, 0)) + bui.getsound('error').play() + + def successful_code(self): + apg['Bear Coin'] += self.promo_code_amount + bs.broadcastmessage(getlanguage('Reward Code', subs=self.promo_code_amount), (0, 1, 0)) + bui.getsound('cashRegister2').play() + + +MainMenuActivity.super_transition_in = MainMenuActivity.on_transition_in + + +def new_on_transition_in(self): + self.super_transition_in() + limit = 8400 + bear_coin = apg['Bear Coin'] + coins_message = GLOBAL['Coins Message'] + try: + if not (STORE['Buy Firebombs'] and STORE['Buy Option'] and STORE['Buy Percentage']): + + if coins_message != []: + result = 0 + for i in coins_message: + result += i + + if not bear_coin >= (limit - 1): + bs.broadcastmessage(getlanguage('Coins Message', subs=result), (0, 1, 0)) + bui.getsound('cashRegister').play() + else: + bs.broadcastmessage( + getlanguage('Coins Limit Message', almacen=[result, limit]), (1, 0, 0) + ) + bui.getsound('error').play() + self.bear_coin_message = True + GLOBAL['Coins Message'] = [] + except: + pass + + +SpazBot.super_handlemessage = SpazBot.handlemessage + + +def bot_handlemessage(self, msg: Any): + self.super_handlemessage(msg) + if isinstance(msg, bs.DieMessage): + if not self.die: + self.die = True + self.limit = 8400 + self.free_coins = random.randint(1, 25) + self.bear_coins = apg['Bear Coin'] + + if not self.bear_coins >= (self.limit): + self.bear_coins += self.free_coins + GLOBAL['Coins Message'].append(self.free_coins) + + if self.bear_coins >= (self.limit): + self.bear_coins = self.limit + + apg['Bear Coin'] = int(self.bear_coins) + apg.apply_and_commit() + + else: + GLOBAL['Coins Message'].append(self.free_coins) + + +def cls_pow_color(): + return [ + (1, 0.1, 0.1), + (0.1, 0.5, 0.9), + (0.1, 0.9, 0.9), + (0.1, 0.9, 0.1), + (0.1, 1, 0.5), + (1, 1, 0.2), + (2, 0.5, 0.5), + (1, 0, 6), + ] + + +def random_color(): + a = random.random() * 3 + b = random.random() * 3 + c = random.random() * 3 + return (a, b, c) + + +def powerup_dist(): + return ( + ('triple_bombs', powerups['Triple']), + ('ice_bombs', powerups['Ice Bombs']), + ('punch', powerups['Punch']), + ('impact_bombs', powerups['Impact Bombs']), + ('land_mines', powerups['Mine Bombs']), + ('sticky_bombs', powerups['Sticky Bombs']), + ('shield', powerups['Shield']), + ('health', powerups['Health']), + ('curse', powerups['Curse']), + ('speed', powerups['Speed']), + ('health_damage', powerups['Healing Damage']), + ('goodbye', powerups['Goodbye']), + ('ice_man', powerups['Ice Man']), + ('tank_shield', powerups['Tank Shield']), + ('impairment_bombs', powerups['Impairment Bombs']), + ('fire_bombs', powerups['Fire Bombs']), + ('fly_bombs', powerups['Fly Bombs']), + ) + + +def percentage_tank_shield(): + percentage = config['Tank Shield PTG'] + percentage_text = ('0.') + str(percentage) + return float(percentage_text) + + +def percentage_health_damage(): + percentage = config['Healing Damage PTG'] + percentage_text = ('0.') + str(percentage) + return float(percentage_text) + + +# === Modify class === + + +class NewProfileBrowserWindow: + def __init__( + self, + transition: str = 'in_right', + in_main_menu: bool = True, + selected_profile: str = None, + origin_widget: bui.Widget = None, + ): + super().__init__(transition, in_main_menu, selected_profile, origin_widget) + + self.session = bs.get_foreground_host_session() + uiscale = bui.app.ui_v1.uiscale + width = 100 if uiscale is babase.UIScale.SMALL else -14 + size = 50 + position = (width * 1.65, 300) + + if isinstance(self.session, MainMenuSession): + self.button = bui.buttonwidget( + parent=self._root_widget, + autoselect=True, + position=position, + size=(size, size), + button_type='square', + label='', + on_activate_call=babase.CallPartial(self.powerupmanager_window), + ) + + size = size * 0.60 + self.image = bui.imagewidget( + parent=self._root_widget, + size=(size, size), + draw_controller=self.button, + position=(position[0] + 10.5, position[1] + 17), + texture=bui.gettexture('powerupSpeed'), + ) + + self.text = bui.textwidget( + parent=self._root_widget, + position=(position[0] + 25, position[1] + 10), + size=(0, 0), + scale=0.45, + color=(0.7, 0.9, 0.7, 1.0), + draw_controller=self.button, + maxwidth=60, + text=(f"Ultimate Powerup {_sp_}Manager"), + h_align='center', + v_align='center', + ) + + def powerupmanager_window(self): + bui.containerwidget(edit=self._root_widget, transition='out_left') + PowerupManagerWindow() + + +class NewPowerupBoxFactory(pupbox.PowerupBoxFactory): + def __init__(self) -> None: + super().__init__() + self.tex_speed = bs.gettexture('powerupSpeed') + self.tex_health_damage = bs.gettexture('heart') + self.tex_goodbye = bs.gettexture('achievementOnslaught') + self.tex_ice_man = bs.gettexture('ouyaUButton') + self.tex_tank_shield = bs.gettexture('achievementSuperPunch') + self.tex_impairment_bombs = bs.gettexture('levelIcon') + self.tex_fire_bombs = bs.gettexture('ouyaOButton') + self.tex_fly_bombs = bs.gettexture('star') + + self._powerupdist = [] + for powerup, freq in powerup_dist(): + for _i in range(int(freq)): + self._powerupdist.append(powerup) + + def get_random_powerup_type(self, forcetype=None, excludetypes=None): + + try: + self.mapa = bs.getactivity()._map.getname() + except: + self.mapa = None + + speed_banned_maps = ['Hockey Stadium', 'Lake Frigid', 'Happy Thoughts'] + + if self.mapa in speed_banned_maps: + powerup_disable = ['speed'] + else: + powerup_disable = [] + + if excludetypes is None: + excludetypes = [] + if forcetype: + ptype = forcetype + else: + if self._lastpoweruptype == 'curse': + ptype = 'health' + else: + while True: + ptype = self._powerupdist[random.randint(0, len(self._powerupdist) - 1)] + if ptype not in excludetypes and ptype not in powerup_disable: + break + self._lastpoweruptype = ptype + return ptype + + +def fire_effect(self): + if self.node.exists(): + bs.emitfx( + position=self.node.position, scale=3, count=50 * 2, spread=0.3, chunk_type='sweat' + ) + else: + self.fire_effect_time = None + + +# BOMBS +Bomb._pm_old_bomb = Bomb.__init__ + + +def _bomb_init( + self, + position: Sequence[float] = (0.0, 1.0, 0.0), + velocity: Sequence[float] = (0.0, 0.0, 0.0), + bomb_type: str = 'normal', + blast_radius: float = 2.0, + bomb_scale: float = 1.0, + source_player: bs.Player = None, + owner: bs.Node = None, +): + + self.bm_type = bomb_type + new_bomb_type = 'ice' if bomb_type in ['ice_bubble', 'impairment', 'fire', 'fly'] else bomb_type + + # Call original __init__ + self._pm_old_bomb( + position=position, + velocity=velocity, + bomb_type=new_bomb_type, + blast_radius=blast_radius, + bomb_scale=bomb_scale, + source_player=source_player, + owner=owner, + ) + + tex = self.node.color_texture + + if self.bm_type == 'ice_bubble': + self.bomb_type = self.bm_type + self.node.mesh = None + self.shield_ice = bs.newnode( + 'shield', owner=self.node, attrs={'color': (0.5, 1.0, 7.0), 'radius': 0.6} + ) + self.node.connectattr('position', self.shield_ice, 'position') + + elif self.bm_type == 'fire': + self.bomb_type = self.bm_type + self.node.mesh = None + self.shield_fire = bs.newnode( + 'shield', owner=self.node, attrs={'color': (6.5, 6.5, 2.0), 'radius': 0.6} + ) + self.node.connectattr('position', self.shield_fire, 'position') + self.fire_effect_time = bs.Timer(0.1, bs.WeakCallPartial(fire_effect, self), repeat=True) + elif self.bm_type == 'impairment': + self.bomb_type = self.bm_type + tex = bs.gettexture('eggTex3') + elif self.bm_type == 'fly': + self.bomb_type = self.bm_type + tex = bs.gettexture('eggTex1') + + if 'tex' in locals(): + self.node.color_texture = tex + self.hit_subtype = self.bomb_type + + if self.bomb_type == 'ice_bubble': + self.blast_radius *= 1.2 + elif self.bomb_type == 'fly': + self.blast_radius *= 2.2 + + +def bomb_handlemessage(self, msg: Any) -> Any: + assert not self.expired + + if isinstance(msg, bs.DieMessage): + if self.node: + self.node.delete() + + elif isinstance(msg, bomb.ExplodeHitMessage): + node = bs.getcollision().opposingnode + assert self.node + nodepos = self.node.position + mag = 2000.0 + if self.blast_type in ('ice', 'ice_bubble'): + mag *= 0.5 + elif self.blast_type == 'land_mine': + mag *= 2.5 + elif self.blast_type == 'tnt': + mag *= 2.0 + elif self.blast_type == 'fire': + mag *= 0.6 + elif self.blast_type == 'fly': + mag *= 5.5 + + node.handlemessage( + bs.HitMessage( + pos=nodepos, + velocity=(0, 0, 0), + magnitude=mag, + hit_type=self.hit_type, + hit_subtype=self.hit_subtype, + radius=self.radius, + source_player=babase.existing(self._source_player), + ) + ) + if self.blast_type in ('ice', 'ice_bubble'): + bomb.BombFactory.get().freeze_sound.play(10) + node.handlemessage(bs.FreezeMessage()) + + return None + + +def powerup_translated(self, type: str): + powerups_names = { + 'triple_bombs': babase.Lstr(resource='helpWindow.' + 'powerupBombNameText'), + 'ice_bombs': babase.Lstr(resource='helpWindow.' + 'powerupIceBombsNameText'), + 'punch': babase.Lstr(resource='helpWindow.' + 'powerupPunchNameText'), + 'impact_bombs': babase.Lstr(resource='helpWindow.' + 'powerupImpactBombsNameText'), + 'land_mines': babase.Lstr(resource='helpWindow.' + 'powerupLandMinesNameText'), + 'sticky_bombs': babase.Lstr(resource='helpWindow.' + 'powerupStickyBombsNameText'), + 'shield': babase.Lstr(resource='helpWindow.' + 'powerupShieldNameText'), + 'health': babase.Lstr(resource='helpWindow.' + 'powerupHealthNameText'), + 'curse': babase.Lstr(resource='helpWindow.' + 'powerupCurseNameText'), + 'speed': getlanguage('Speed'), + 'health_damage': getlanguage('Healing Damage'), + 'goodbye': getlanguage('Goodbye'), + 'ice_man': getlanguage('Ice Man'), + 'tank_shield': getlanguage('Tank Shield'), + 'impairment_bombs': getlanguage('Impairment Bombs'), + 'fire_bombs': getlanguage('Fire Bombs'), + 'fly_bombs': getlanguage('Fly Bombs'), + } + self.texts['Name'].text = powerups_names[type] + + +# POWERUP +pupbox.PowerupBox._old_pbx_ = pupbox.PowerupBox.__init__ + + +def _pbx_( + self, + position: Sequence[float] = (0.0, 1.0, 0.0), + poweruptype: str = 'triple_bombs', + expire: bool = True, +): + self.news: list = [] + for x, i in powerup_dist(): + self.news.append(x) + + self.box: list = [] + self.texts = {} + self.news = self.news[9:] + self.box.append(poweruptype) + self.npowerup = self.box[0] + factory = NewPowerupBoxFactory.get() + + if self.npowerup in self.news: + new_poweruptype = 'shield' + else: + new_poweruptype = poweruptype + self._old_pbx_(position, new_poweruptype, expire) + + type = new_poweruptype + tex = self.node.color_texture + mesh = self.node.mesh + + if self.npowerup == 'speed': + type = self.npowerup + tex = factory.tex_speed + elif self.npowerup == 'health_damage': + type = self.npowerup + tex = factory.tex_health_damage + elif self.npowerup == 'goodbye': + type = self.npowerup + tex = factory.tex_goodbye + elif self.npowerup == 'ice_man': + type = self.npowerup + tex = factory.tex_ice_man + elif self.npowerup == 'tank_shield': + type = self.npowerup + tex = factory.tex_tank_shield + elif self.npowerup == 'impairment_bombs': + type = self.npowerup + tex = factory.tex_impairment_bombs + elif self.npowerup == 'fire_bombs': + type = self.npowerup + tex = factory.tex_fire_bombs + elif self.npowerup == 'fly_bombs': + type = self.npowerup + tex = factory.tex_fly_bombs + + self.poweruptype = type + self.node.mesh = mesh + self.node.color_texture = tex + n_scale = config['Powerup Scale'] + style = config['Powerup Style'] + + curve = bs.animate(self.node, 'mesh_scale', {0: 0, 0.14: 1.6, 0.2: n_scale}) + bs.timer(0.2, curve.delete) + + def util_text( + type: str, + text: str, + scale: float = 1, + color: list = [1, 1, 1], + position: list = [0, 0.7, 0], + colors_name: bool = False, + ): + m = bs.newnode( + 'math', + owner=self.node, + attrs={'input1': (position[0], position[1], position[2]), 'operation': 'add'}, + ) + self.node.connectattr('position', m, 'input2') + self.texts[type] = bs.newnode( + 'text', + owner=self.node, + attrs={ + 'text': str(text), + 'in_world': True, + 'scale': 0.02, + 'shadow': 0.5, + 'flatness': 1.0, + 'color': (color[0], color[1], color[2]), + 'h_align': 'center', + }, + ) + m.connectattr('output', self.texts[type], 'position') + bs.animate(self.texts[type], 'scale', {0: 0.017, 0.4: 0.017, 0.5: 0.01 * scale}) + + if colors_name: + bs.animate_array( + self.texts[type], + 'color', + 3, + { + 0: (1, 0, 0), + 0.2: (1, 0.5, 0), + 0.4: (1, 1, 0), + 0.6: (0, 1, 0), + 0.8: (0, 1, 1), + 1.0: (1, 0, 1), + 1.2: (1, 0, 0), + }, + loop=True, + ) + + def update_time(time): + if hasattr(self, 'texts') and 'Time' in self.texts and self.texts['Time']: + self.texts['Time'].text = str(time) + + if config['Powerup Time']: + interval = int(pupbox.DEFAULT_POWERUP_INTERVAL) + time2 = interval - 1 + time = 1 + + util_text( + 'Time', time2, scale=1.5, color=(2, 2, 2), position=[0, 0.9, 0], colors_name=False + ) + + while interval + 3: + bs.timer(time - 1, babase.CallPartial(update_time, f'{time2}s')) + + if time2 == 0: + break + + time += 1 + time2 -= 1 + + if config['Powerup With Shield']: + scale = config['Powerup Scale'] + self.shield = bs.newnode( + 'shield', owner=self.node, attrs={'color': (1, 1, 0), 'radius': 1.3 * scale} + ) + self.node.connectattr('position', self.shield, 'position') + bs.animate_array( + self.shield, + 'color', + 3, + {0: (2, 0, 0), 0.5: (0, 2, 0), 1: (0, 1, 6), 1.5: (2, 0, 0)}, + loop=True, + ) + + if config['Powerup Name']: + util_text('Name', self.poweruptype, scale=1.2, position=[0, 0.4, 0], colors_name=True) + powerup_translated(self, self.poweruptype) + + if style == 'SY: BALL': + self.node.mesh = bs.getmesh('frostyPelvis') + elif style == 'SY: Impact': + self.node.mesh = bs.getmesh('impactBomb') + elif style == 'SY: Egg': + self.node.mesh = bs.getmesh('egg') + + +# SPAZ +def _speed_off_flash(self): + if self.node: + factory = NewPowerupBoxFactory.get() + self.node.billboard_texture = factory.tex_speed + self.node.billboard_opacity = 1.0 + self.node.billboard_cross_out = True + + +def _speed_wear_off(self): + if self.node: + self.node.hockey = False + self.node.billboard_opacity = 0.0 + bui.getsound('powerdown01').play() + + +def _ice_man_off_flash(self): + if self.node: + factory = NewPowerupBoxFactory.get() + self.node.billboard_texture = factory.tex_ice_man + self.node.billboard_opacity = 1.0 + self.node.billboard_cross_out = True + + +def _ice_man_wear_off(self): + if self.node: + f = self.color[0] + i = (0, 1, 4) + + bomb = self.bmb_color[0] + if bomb != 'ice_bubble': + self.bomb_type = bomb + else: + self.bomb_type = 'normal' + + self.freeze_punch = False + self.node.billboard_opacity = 0.0 + bs.animate_array(self.node, 'color', 3, {0: f, 0.3: i, 0.6: f}) + bui.getsound('powerdown01').play() + + +Spaz._pm2_spz_old = Spaz.__init__ + + +def _init_spaz_(self, *args, **kwargs): + self._pm2_spz_old(*args, **kwargs) + self.edg_eff = False + self.kill_eff = False + self.freeze_punch = False + self.die = False + self.color: list = [] + self.color.append(self.node.color) + + self.tankshield = {"Tank": False, "Reduction": False, "Shield": None} + + +Spaz._super_on_punch_press = Spaz.on_punch_press + + +def spaz_on_punch_press(self) -> None: + self._super_on_punch_press() + + if self.tankshield['Tank']: + try: + self.tankshield['Reduction'] = True + + shield = bs.newnode( + 'shield', owner=self.node, attrs={'color': (4, 1, 4), 'radius': 1.3} + ) + self.node.connectattr('position_center', shield, 'position') + + self.tankshield['Shield'] = shield + except: + pass + + +Spaz._super_on_punch_release = Spaz.on_punch_release + + +def spaz_on_punch_release(self) -> None: + self._super_on_punch_release() + try: + self.tankshield['Shield'].delete() + self.tankshield['Reduction'] = False + except: + pass + + +def new_get_bomb_type_tex(self) -> babase.Texture: + factory = NewPowerupBoxFactory.get() + if self.bomb_type == 'sticky': + return factory.tex_sticky_bombs + if self.bomb_type == 'ice': + return factory.tex_ice_bombs + if self.bomb_type == 'impact': + return factory.tex_impact_bombs + if self.bomb_type == 'impairment': + return factory.tex_impairment_bombs + if self.bomb_type == 'fire': + return factory.tex_fire_bombs + if self.bomb_type == 'fly': + return factory.tex_fly_bombs + return None + + +def new_handlemessage(self, msg: Any) -> Any: + assert not self.expired + + if isinstance(msg, bs.PickedUpMessage): + if self.node: + self.node.handlemessage('hurt_sound') + self.node.handlemessage('picked_up') + + self._num_times_hit += 1 + + elif isinstance(msg, bs.ShouldShatterMessage): + bs.timer(0.001, bs.WeakCallPartial(self.shatter)) + + elif isinstance(msg, bs.ImpactDamageMessage): + bs.timer(0.001, bs.WeakCallPartial(self._hit_self, msg.intensity)) + elif isinstance(msg, bs.PowerupMessage): + factory = NewPowerupBoxFactory.get() + if self._dead or not self.node: + return True + if self.pick_up_powerup_callback is not None: + self.pick_up_powerup_callback(self) + if msg.poweruptype == 'triple_bombs': + tex = PowerupBoxFactory.get().tex_bomb + self._flash_billboard(tex) + self.set_bomb_count(3) + if self.powerups_expire: + self.node.mini_billboard_1_texture = tex + t_ms = int(bs.time() * 1000) + assert isinstance(t_ms, int) + self.node.mini_billboard_1_start_time = t_ms + self.node.mini_billboard_1_end_time = t_ms + POWERUP_WEAR_OFF_TIME + self._multi_bomb_wear_off_timer = bs.Timer( + (POWERUP_WEAR_OFF_TIME - 2000), + babase.CallPartial(self._multi_bomb_wear_off_flash), + ) + self._multi_bomb_wear_off_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME, babase.CallPartial(self._multi_bomb_wear_off) + ) + elif msg.poweruptype == 'land_mines': + self.set_land_mine_count(min(self.land_mine_count + 3, 3)) + elif msg.poweruptype == 'impact_bombs': + self.bomb_type = 'impact' + tex = self._get_bomb_type_tex() + self._flash_billboard(tex) + if self.powerups_expire: + self.node.mini_billboard_2_texture = tex + t_ms = int(bs.time() * 1000) + assert isinstance(t_ms, int) + self.node.mini_billboard_2_start_time = t_ms + self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME + self._bomb_wear_off_flash_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME - 2000, babase.CallPartial(self._bomb_wear_off_flash) + ) + self._bomb_wear_off_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME, babase.CallPartial(self._bomb_wear_off) + ) + elif msg.poweruptype == 'sticky_bombs': + self.bomb_type = 'sticky' + tex = self._get_bomb_type_tex() + self._flash_billboard(tex) + if self.powerups_expire: + self.node.mini_billboard_2_texture = tex + t_ms = int(bs.time() * 1000) + assert isinstance(t_ms, int) + self.node.mini_billboard_2_start_time = t_ms + self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME + self._bomb_wear_off_flash_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME - 2000, babase.CallPartial(self._bomb_wear_off_flash) + ) + self._bomb_wear_off_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME, babase.CallPartial(self._bomb_wear_off) + ) + elif msg.poweruptype == 'punch': + self._has_boxing_gloves = True + tex = PowerupBoxFactory.get().tex_punch + self._flash_billboard(tex) + self.equip_boxing_gloves() + if self.powerups_expire: + self.node.boxing_gloves_flashing = False + self.node.mini_billboard_3_texture = tex + t_ms = int(bs.time() * 1000) + assert isinstance(t_ms, int) + self.node.mini_billboard_3_start_time = t_ms + self.node.mini_billboard_3_end_time = t_ms + POWERUP_WEAR_OFF_TIME + self._boxing_gloves_wear_off_flash_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME - 2000, bs.WeakCallPartial(self._gloves_wear_off_flash) + ) + self._boxing_gloves_wear_off_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME, + bs.WeakCallPartial(self._gloves_wear_off), + ) + elif msg.poweruptype == 'shield': + factory = SpazFactory.get() + self.equip_shields(decay=factory.shield_decay_rate > 0) + elif msg.poweruptype == 'curse': + self.curse() + elif msg.poweruptype == 'ice_bombs': + self.bomb_type = 'ice' + tex = self._get_bomb_type_tex() + self._flash_billboard(tex) + if self.powerups_expire: + self.node.mini_billboard_2_texture = tex + t_ms = bs.time() * 1000 + assert isinstance(t_ms, int) + self.node.mini_billboard_2_start_time = t_ms + self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME + self._bomb_wear_off_flash_timer = bs.Timer( + (POWERUP_WEAR_OFF_TIME - 2000) / 1000.0, + bs.WeakCallPartial(self._bomb_wear_off_flash), + ) + + self._bomb_wear_off_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME / 1000.0, bs.WeakCallStrict(self._bomb_wear_off) + ) + + elif msg.poweruptype == 'health': + if self.edg_eff: + f = self.color[0] + r = (2, 0, 0) + g = (0, 2, 0) + bs.animate_array(self.node, 'color', 3, {0: r, 0.6: g, 1.0: f}) + self.edg_eff = False + if self._cursed: + self._cursed = False + factory = SpazFactory.get() + for attr in ['materials', 'roller_materials']: + materials = getattr(self.node, attr) + if factory.curse_material in materials: + setattr( + self.node, + attr, + tuple(m for m in materials if m != factory.curse_material), + ) + self.node.curse_death_time = 0 + self.hitpoints = self.hitpoints_max + self._flash_billboard(PowerupBoxFactory.get().tex_health) + self.node.hurt = 0 + self._last_hit_time = None + self._num_times_hit = 0 + + elif msg.poweruptype == 'tank_shield': + self.tankshield['Tank'] = True + self.edg_eff = False + tex = factory.tex_tank_shield + self._flash_billboard(tex) + + elif msg.poweruptype == 'health_damage': + tex = factory.tex_health_damage + self.edg_eff = True + f = self.color[0] + i = (2, 0.5, 2) + bs.animate_array(self.node, 'color', 3, {0: i, 0.5: i, 0.6: f}) + self._flash_billboard(tex) + self.tankshield['Tank'] = False + self.freeze_punch = False + + elif msg.poweruptype == 'goodbye': + tex = factory.tex_goodbye + self._flash_billboard(tex) + self.kill_eff = True + + elif msg.poweruptype == 'fly_bombs': + self.bomb_type = 'fly' + tex = self._get_bomb_type_tex() + self._flash_billboard(tex) + if self.powerups_expire: + self.node.mini_billboard_2_texture = tex + t_ms = bs.time() * 1000 + assert isinstance(t_ms, int) + self.node.mini_billboard_2_start_time = t_ms + self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME + self._bomb_wear_off_flash_timer = bs.Timer( + (POWERUP_WEAR_OFF_TIME - 2000) / 1000.0, + bs.WeakCallStrict(self._bomb_wear_off_flash), + ) + + self._bomb_wear_off_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME / 1000.0, bs.WeakCallStrict(self._bomb_wear_off) + ) + + elif msg.poweruptype == 'fire_bombs': + self.bomb_type = 'fire' + tex = self._get_bomb_type_tex() + self._flash_billboard(tex) + if self.powerups_expire: + self.node.mini_billboard_2_texture = tex + t_ms = bs.time() * 1000 + assert isinstance(t_ms, int) + self.node.mini_billboard_2_start_time = t_ms + self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME + self._bomb_wear_off_flash_timer = bs.Timer( + (POWERUP_WEAR_OFF_TIME - 2000) / 1000.0, + bs.WeakCallStrict(self._bomb_wear_off_flash), + ) + + self._bomb_wear_off_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME / 1000.0, bs.WeakCallStrict(self._bomb_wear_off) + ) + + elif msg.poweruptype == 'impairment_bombs': + self.bomb_type = 'impairment' + tex = self._get_bomb_type_tex() + self._flash_billboard(tex) + if self.powerups_expire: + self.node.mini_billboard_2_texture = tex + t_ms = bs.time() * 1000 + assert isinstance(t_ms, int) + self.node.mini_billboard_2_start_time = t_ms + self.node.mini_billboard_2_end_time = t_ms + POWERUP_WEAR_OFF_TIME + self._bomb_wear_off_flash_timer = bs.Timer( + (POWERUP_WEAR_OFF_TIME - 2000) / 1000.0, + bs.WeakCallStrict(self._bomb_wear_off_flash), + ) + + self._bomb_wear_off_timer = bs.Timer( + POWERUP_WEAR_OFF_TIME / 1000.0, bs.WeakCallStrict(self._bomb_wear_off) + ) + + elif msg.poweruptype == 'ice_man': + tex = factory.tex_ice_man + self.bomb_type = 'ice_bubble' + self.freeze_punch = True + self.edg_eff = False + self.node.color = (0, 1, 4) + self._flash_billboard(tex) + + if self.powerups_expire: + ice_man_time = 17000 + self.node.mini_billboard_2_texture = tex + t_ms = bs.time() * 1000 + assert isinstance(t_ms, int) + self.node.mini_billboard_2_start_time = t_ms + self.node.mini_billboard_2_end_time = t_ms + ice_man_time + + self.ice_man_flash_timer = bs.Timer( + (ice_man_time - 2000) / 1000.0, babase.CallPartial(_ice_man_off_flash, self) + ) + + self.ice_man_timer = bs.Timer( + ice_man_time / 1000.0, babase.CallPartial(_ice_man_wear_off, self) + ) + + elif msg.poweruptype == 'speed': + self.node.hockey = True + tex = factory.tex_speed + self._flash_billboard(tex) + if self.powerups_expire: + speed_time = 15000 + self.node.mini_billboard_2_texture = tex + t_ms = bs.time() * 1000 + assert isinstance(t_ms, int) + self.node.mini_billboard_2_start_time = t_ms + self.node.mini_billboard_2_end_time = t_ms + speed_time + + self.speed_flash_timer = bs.Timer( + (speed_time - 2000) / 1000.0, babase.Call(_speed_off_flash, self) + ) + + self.speed_timer = bs.Timer( + speed_time / 1000.0, bs.WeakCallPartial(_speed_wear_off, self) + ) + + self.bmb_color: list = [] + self.bmb_color.append(self.bomb_type) + + self.node.handlemessage('flash') + if msg.sourcenode: + msg.sourcenode.handlemessage(bs.PowerupAcceptMessage()) + return True + + elif isinstance(msg, bs.FreezeMessage): + if not self.node: + return None + if self.node.invincible: + SpazFactory.get().block_sound.play(1.0, self.node.position) + return None + if self.shield: + return None + if not self.frozen: + self.frozen = True + self.node.frozen = True + bs.timer(5.0, bs.WeakCallPartial(self.handlemessage, bs.ThawMessage())) + if self.hitpoints <= 0: + self.shatter() + if self.freeze_punch: + self.handlemessage(bs.ThawMessage()) + + elif isinstance(msg, bs.ThawMessage): + if self.frozen and not self.shattered and self.node: + self.frozen = False + self.node.frozen = False + + elif isinstance(msg, bs.HitMessage): + if not self.node: + return None + if self.node.invincible: + SpazFactory.get().block_sound.play(1.0, self.node.position) + return True + + local_time = bs.time() * 1000 + assert isinstance(local_time, int) + if self._last_hit_time is None or local_time - self._last_hit_time > 1000: + self._num_times_hit += 1 + self._last_hit_time = local_time + + mag = msg.magnitude * self.impact_scale + velocity_mag = msg.velocity_magnitude * self.impact_scale + damage_scale = 0.22 + + def fire_effect(): + if not self.shield: + if self.node.exists(): + bs.emitfx( + position=self.node.position, + scale=3, + count=50 * 2, + spread=0.3, + chunk_type='sweat', + ) + self.node.handlemessage('celebrate', 560) + else: + self._fire_time = None + else: + self._fire_time = None + + def fire(time, damage): + if not self.shield and not self._dead: + self.hitpoints -= damage + bs.show_damage_count(f'-{damage}HP', self.node.position, msg.force_direction) + bui.getsound('fuse01').play() + + if duration != time: + self._fire_time = bs.Timer(0.1, babase.CallPartial(fire_effect), repeat=True) + else: + self._fire_time = None + + if self.hitpoints < 0: + self.node.handlemessage(bs.DieMessage()) + + if msg.hit_subtype == 'fly': + damage_scale = 0.0 + + if self.shield: + self.shield_hitpoints -= 300 + + if self.shield_hitpoints < 0: + self.shield.delete() + self.shield = None + SpazFactory.get().shield_down_sound.play(1.0, self.node.position) + elif msg.hit_subtype == 'fire': + index = 1 + duration = 5 + damage = 103 + if not self.shield: + for firex in range(duration): + bs.timer(index, bs.WeakCallPartial(fire, index, damage)) + self._fire_time = bs.Timer(0.1, babase.CallPartial(fire_effect), repeat=True) + index += 1 + else: + self.shield_hitpoints -= 80 + if self.shield_hitpoints < 1: + self.shield.delete() + self.shield = None + SpazFactory.get().shield_down_sound.play(1.0, self.node.position) + elif msg.hit_subtype == 'impairment': + damage_scale = 0 + + if self.shield: + self.shield.delete() + self.shield = None + SpazFactory.get().shield_down_sound.play(1.0, self.node.position) + else: + hitpoints = int(self.hitpoints * 0.80) + self.hitpoints -= int(hitpoints) + bs.show_damage_count( + (f'-{int(hitpoints / 10)}%'), self.node.position, msg.force_direction + ) + + if self.hitpoints < 0 or hitpoints < 95: + self.node.handlemessage(bs.DieMessage()) + + if self.shield: + if msg.flat_damage: + damage = msg.flat_damage * self.impact_scale + else: + assert msg.force_direction is not None + self.node.handlemessage( + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + mag, + velocity_mag, + msg.radius, + 1, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) + damage = damage_scale * self.node.damage + + assert self.shield_hitpoints is not None + self.shield_hitpoints -= int(damage) + self.shield.hurt = 1.0 - float(self.shield_hitpoints) / self.shield_hitpoints_max + + max_spillover = SpazFactory.get().max_shield_spillover_damage + if self.shield_hitpoints <= 0: + + self.shield.delete() + self.shield = None + SpazFactory.get().shield_down_sound.play(1.0, self.node.position) + + npos = self.node.position + bs.emitfx( + position=(npos[0], npos[1] + 0.9, npos[2]), + velocity=self.node.velocity, + count=random.randrange(20, 30), + scale=1.0, + spread=0.6, + chunk_type='spark', + ) + + else: + SpazFactory.get().shield_hit_sound.play(0.5, self.node.position) + + assert msg.force_direction is not None + bs.emitfx( + position=msg.pos, + velocity=( + msg.force_direction[0] * 1.0, + msg.force_direction[1] * 1.0, + msg.force_direction[2] * 1.0, + ), + count=min(30, 5 + int(damage * 0.005)), + scale=0.5, + spread=0.3, + chunk_type='spark', + ) + + if self.shield_hitpoints <= -max_spillover: + leftover_damage = -max_spillover - self.shield_hitpoints + shield_leftover_ratio = leftover_damage / damage + + mag *= shield_leftover_ratio + velocity_mag *= shield_leftover_ratio + else: + return True + else: + shield_leftover_ratio = 1.0 + + if msg.flat_damage: + damage = int(msg.flat_damage * self.impact_scale * shield_leftover_ratio) + else: + assert msg.force_direction is not None + self.node.handlemessage( + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + mag, + velocity_mag, + msg.radius, + 0, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) + + damage = int(damage_scale * self.node.damage) + + if self.tankshield['Reduction']: + porcentaje = percentage_tank_shield() + dism = int(damage * porcentaje) + damage = int(damage - dism) + + bs.show_damage_count('-' + str(int(damage / 10)) + '%', msg.pos, msg.force_direction) + + self.node.handlemessage('hurt_sound') + + if self.edg_eff: + porcentaje = percentage_health_damage() + dmg_dism = int(damage * porcentaje) + self.hitpoints += dmg_dism + + PopupText( + text=f'+{int(dmg_dism / 10)}%', + scale=1.5, + position=self.node.position, + color=(0, 1, 0), + ).autoretain() + bs.animate_array( + self.node, 'color', 3, {0: (0, 1, 0), 0.39: (0, 2, 0), 0.4: self.color[0]} + ) + bui.getsound('healthPowerup').play() + + if msg.hit_type == 'punch': + self.on_punched(damage) + + try: + if msg.get_source_player(bs.Player).actor.freeze_punch: + self.node.color = (0, 1, 4) + bui.getsound('freeze').play() + self.node.handlemessage(bs.FreezeMessage()) + except: + pass + + if damage > 350: + assert msg.force_direction is not None + bs.show_damage_count( + '-' + str(int(damage / 10)) + '%', msg.pos, msg.force_direction + ) + + if msg.hit_subtype == 'super_punch': + SpazFactory.get().punch_sound_stronger.play(1.0, self.node.position) + if damage > 500: + sounds = SpazFactory.get().punch_sound_strong + sound = sounds[random.randrange(len(sounds))] + else: + sound = SpazFactory.get().punch_sound + sound.play(1.0, self.node.position) + + assert msg.force_direction is not None + bs.emitfx( + position=msg.pos, + velocity=( + msg.force_direction[0] * 0.5, + msg.force_direction[1] * 0.5, + msg.force_direction[2] * 0.5, + ), + count=min(10, 1 + int(damage * 0.0025)), + scale=0.3, + spread=0.03, + ) + + bs.emitfx( + position=msg.pos, + chunk_type='sweat', + velocity=( + msg.force_direction[0] * 1.3, + msg.force_direction[1] * 1.3 + 5.0, + msg.force_direction[2] * 1.3, + ), + count=min(30, 1 + int(damage * 0.04)), + scale=0.9, + spread=0.28, + ) + + hurtiness = damage * 0.003 + punchpos = ( + msg.pos[0] + msg.force_direction[0] * 0.02, + msg.pos[1] + msg.force_direction[1] * 0.02, + msg.pos[2] + msg.force_direction[2] * 0.02, + ) + flash_color = (1.0, 0.8, 0.4) + light = bs.newnode( + 'light', + attrs={ + 'position': punchpos, + 'radius': 0.12 + hurtiness * 0.12, + 'intensity': 0.3 * (1.0 + 1.0 * hurtiness), + 'height_attenuated': False, + 'color': flash_color, + }, + ) + bs.timer(0.06, light.delete) + + flash = bs.newnode( + 'flash', + attrs={'position': punchpos, 'size': 0.17 + 0.17 * hurtiness, 'color': flash_color}, + ) + bs.timer(0.06, flash.delete) + + if msg.hit_type == 'impact': + assert msg.force_direction is not None + bs.emitfx( + position=msg.pos, + velocity=( + msg.force_direction[0] * 2.0, + msg.force_direction[1] * 2.0, + msg.force_direction[2] * 2.0, + ), + count=min(10, 1 + int(damage * 0.01)), + scale=0.4, + spread=0.1, + ) + if self.hitpoints > 0: + if msg.hit_type == 'impact' and damage > self.hitpoints: + newdamage = max(damage - 200, self.hitpoints - 10) + damage = newdamage + self.node.handlemessage('flash') + + if damage > 0.0 and self.node.hold_node: + self.node.hold_node = None + self.hitpoints -= damage + self.node.hurt = 1.0 - float(self.hitpoints) / self.hitpoints_max + + if self._cursed and damage > 0: + bs.timer( + 0.05, bs.WeakCallPartial(self.curse_explode, msg.get_source_player(bs.Player)) + ) + + if self.frozen and (damage > 200 or self.hitpoints <= 0): + self.shatter() + elif self.hitpoints <= 0: + self.node.handlemessage(bs.DieMessage(how=bs.DeathType.IMPACT)) + + if self.hitpoints <= 0: + damage_avg = self.node.damage_smoothed * damage_scale + if damage_avg > 1000: + self.shatter() + + elif isinstance(msg, BombDiedMessage): + self.bomb_count += 1 + + elif isinstance(msg, bs.DieMessage): + + def drop_bomb(): + for xbomb in range(3): + p = self.node.position + pos = (p[0] + xbomb, p[1] + 5, p[2] - xbomb) + ball = bomb.Bomb(position=pos, bomb_type='impact').autoretain() + ball.node.mesh_scale = 0.6 + ball.node.mesh = bs.getmesh('egg') + ball.node.gravity_scale = 2 + + if self.edg_eff: + self.edg_eff = False + + wasdead = self._dead + self._dead = True + self.hitpoints = 0 + if msg.immediate: + if self.node: + self.node.delete() + elif self.node: + self.node.hurt = 1.0 + if self.play_big_death_sound and not wasdead: + SpazFactory.get().single_player_death_sound.play() + self.node.dead = True + bs.timer(2.0, self.node.delete) + + t = 0 + if self.kill_eff: + for bombs in range(3): + bs.timer(t, babase.CallPartial(drop_bomb)) + t += 0.15 + self.kill_eff = False + + elif isinstance(msg, bs.OutOfBoundsMessage): + self.handlemessage(bs.DieMessage(how=bs.DeathType.FALL)) + + elif isinstance(msg, bs.StandMessage): + self._last_stand_pos = (msg.position[0], msg.position[1], msg.position[2]) + if self.node: + self.node.handlemessage( + 'stand', msg.position[0], msg.position[1], msg.position[2], msg.angle + ) + + elif isinstance(msg, CurseExplodeMessage): + self.curse_explode() + + elif isinstance(msg, PunchHitMessage): + if not self.node: + return None + node = bs.getcollision().opposingnode + + if node and (node not in self._punched_nodes): + + punch_momentum_angular = self.node.punch_momentum_angular * self._punch_power_scale + punch_power = self.node.punch_power * self._punch_power_scale + + if node.getnodetype() != 'spaz': + sounds = SpazFactory.get().impact_sounds_medium + sound = sounds[random.randrange(len(sounds))] + sound.play(1.0, self.node.position) + + ppos = self.node.punch_position + punchdir = self.node.punch_velocity + vel = self.node.punch_momentum_linear + + self._punched_nodes.add(node) + node.handlemessage( + bs.HitMessage( + pos=ppos, + velocity=vel, + magnitude=punch_power * punch_momentum_angular * 110.0, + velocity_magnitude=punch_power * 40, + radius=0, + srcnode=self.node, + source_player=self.source_player, + force_direction=punchdir, + hit_type='punch', + hit_subtype=('super_punch' if self._has_boxing_gloves else 'default'), + ) + ) + + mag = -400.0 + if self._hockey: + mag *= 0.5 + if len(self._punched_nodes) == 1: + self.node.handlemessage( + 'kick_back', + ppos[0], + ppos[1], + ppos[2], + punchdir[0], + punchdir[1], + punchdir[2], + mag, + ) + elif isinstance(msg, PickupMessage): + if not self.node: + return None + + try: + collision = bs.getcollision() + opposingnode = collision.opposingnode + opposingbody = collision.opposingbody + except bs.NotFoundError: + return True + + try: + if opposingnode.invincible: + return True + except Exception: + pass + + if ( + opposingnode.getnodetype() == 'spaz' + and not opposingnode.shattered + and opposingbody == 4 + ): + opposingbody = 1 + + held = self.node.hold_node + if held and held.getnodetype() == 'flag': + return True + + self.node.hold_body = opposingbody + self.node.hold_node = opposingnode + elif isinstance(msg, bs.CelebrateMessage): + if self.node: + self.node.handlemessage('celebrate', int(msg.duration * 1000)) + + return None + + +class PowerupManagerWindow(PopupWindow): + def __init__(self, transition='in_right'): + columns = 2 + self._width = width = 800 + self._height = height = 500 + self._sub_height = 200 + self._scroll_width = self._width * 0.90 + self._scroll_height = self._height - 180 + self._sub_width = self._scroll_width * 0.95 + self.tab_buttons: set = {} + self.list_cls_power: list = [] + self.default_powerups = default_powerups() + self.default_power_list = list(self.default_powerups) + self.coins = apg['Bear Coin'] + self.popup_cls_power = None + + if not STORE['Buy Firebombs']: + powerups['Fire Bombs'] = 0 + self.default_power_list.remove('Fire Bombs') + + self.charstr = [ + babase.charstr(babase.SpecialChar.LEFT_ARROW), + babase.charstr(babase.SpecialChar.RIGHT_ARROW), + babase.charstr(babase.SpecialChar.UP_ARROW), + babase.charstr(babase.SpecialChar.DOWN_ARROW), + ] + + self.tabdefs = { + "Action 1": ['powerupIceBombs', (1, 1, 1)], + "Action 2": ['settingsIcon', (0, 1, 0)], + "Action 3": ['inventoryIcon', (1, 1, 1)], + "Action 4": ['storeIcon', (1, 1, 1)], + "Action 5": ['advancedIcon', (1, 1, 1)], + "About": ['heart', (1.5, 0.3, 0.3)], + } + + if STORE['Buy Firebombs'] and STORE['Buy Option'] and STORE['Buy Percentage']: + self.tabdefs = { + "Action 1": ['powerupIceBombs', (1, 1, 1)], + "Action 2": ['settingsIcon', (0, 1, 0)], + "Action 3": ['inventoryIcon', (1, 1, 1)], + "About": ['heart', (1.5, 0.3, 0.3)], + } + + self.listdef = list(self.tabdefs) + + self.count = len(self.tabdefs) + + self._current_tab = GLOBAL['Tab'] + + app = bui.app.ui_v1 + uiscale = app.uiscale + + self._root_widget = bui.containerwidget( + size=(width + 90, height + 80), + transition=transition, + scale=1.5 if uiscale is babase.UIScale.SMALL else 1.0, + stack_offset=(0, -30) if uiscale is babase.UIScale.SMALL else (0, 0), + ) + + self._backButton = b = bui.buttonwidget( + parent=self._root_widget, + autoselect=True, + position=(60, self._height - 15), + size=(130, 60), + scale=0.8, + text_scale=1.2, + label=babase.Lstr(resource='backText'), + button_type='back', + on_activate_call=babase.CallPartial(self._back), + ) + bui.buttonwidget( + edit=self._backButton, + button_type='backSmall', + size=(60, 60), + label=babase.charstr(babase.SpecialChar.BACK), + ) + bui.containerwidget(edit=self._root_widget, cancel_button=b) + + self.titletext = bui.textwidget( + parent=self._root_widget, + position=(0, height - 15), + size=(width, 50), + h_align="center", + color=bui.app.ui_v1.title_color, + v_align="center", + maxwidth=width * 1.3, + ) + + index = 0 + for tab in range(self.count): + for tab2 in range(columns): + + tag = self.listdef[index] + + position = (620 + (tab2 * 120), self._height - 50 * 2.5 - (tab * 120)) + + if tag == 'About': + text = babase.Lstr(resource='gatherWindow.aboutText') + elif tab == 'Action 4': + text = babase.Lstr(resource='storeText') + else: + text = getlanguage(tag) + + self.tab_buttons[tag] = bui.buttonwidget( + parent=self._root_widget, + autoselect=True, + position=position, + size=(110, 110), + scale=1, + label='', + enable_sound=False, + button_type='square', + on_activate_call=babase.CallPartial(self._set_tab, tag, sound=True), + ) + + self.text = bui.textwidget( + parent=self._root_widget, + position=(position[0] + 55, position[1] + 30), + size=(0, 0), + scale=1, + color=bui.app.ui_v1.title_color, + draw_controller=self.tab_buttons[tag], + maxwidth=100, + text=text, + h_align='center', + v_align='center', + ) + + self.image = bui.imagewidget( + parent=self._root_widget, + size=(60, 60), + color=self.tabdefs[tag][1], + draw_controller=self.tab_buttons[tag], + position=(position[0] + 25, position[1] + 40), + texture=bui.gettexture(self.tabdefs[tag][0]), + ) + + index += 1 + + if self.count == index: + break + + if self.count == index: + break + + self._scrollwidget = None + self._tab_container = None + self._set_tab(self._current_tab) + + def __del__(self): + apg.apply_and_commit() + + def popup_menu_closing(self, window): + print("saliendo") + + def _set_tab(self, tab, sound: bool = False): + self.sound = sound + GLOBAL['Tab'] = tab + apg.apply_and_commit() + + if self._tab_container is not None and self._tab_container.exists(): + self._tab_container.delete() + + if self.sound: + bui.getsound('swish').play() + + if self._scrollwidget: + self._scrollwidget.delete() + + self._scrollwidget = bui.scrollwidget( + parent=self._root_widget, + position=(self._width * 0.08, 51 * 1.8), + size=(self._sub_width - 140, self._scroll_height + 60 * 1.2), + ) + + if tab == 'Action 4': + if self._scrollwidget: + self._scrollwidget.delete() + + self._scrollwidget = bui.scrollwidget( + parent=self._root_widget, + position=(self._width * 0.08, 51 * 1.8), + size=(self._sub_width - 140, self._scroll_height + 60 * 1.2), + capture_arrows=True, + center_small_content=False, + selection_loops_to_parent=True, + claims_left_right=True, + claims_up_down=False, + color=(0.3, 0.3, 0.4), + ) + bui.textwidget(edit=self.titletext, text=babase.Lstr(resource='storeText')) + + elif tab == 'About': + bui.textwidget(edit=self.titletext, text=babase.Lstr(resource='gatherWindow.aboutText')) + else: + bui.textwidget(edit=self.titletext, text=getlanguage(tab)) + + choices = ['Reset', 'Only Bombs', 'Only Items', 'New', 'Nothing'] + c_display = [] + + for display in choices: + choices_display = babase.Lstr(translate=("", getlanguage(display))) + c_display.append(choices_display) + + if tab == 'Action 1': + self.popup_cls_power = PopupMenu( + parent=self._root_widget, + position=(130, self._width * 0.61), + button_size=(150, 50), + scale=2.5, + choices=choices, + width=150, + choices_display=c_display, + current_choice=GLOBAL['Cls Powerup'], + on_value_change_call=self._set_concept, + ) + self.list_cls_power.append(self.popup_cls_power._button) + + self.button_cls_power = bui.buttonwidget( + parent=self._root_widget, + position=(500, self._width * 0.61), + size=(50, 50), + autoselect=True, + scale=1, + label=('%'), + text_scale=1, + button_type='square', + on_activate_call=self._percentage_window, + ) + self.list_cls_power.append(self.button_cls_power) + + rewindow = [self.popup_cls_power._button, self.button_cls_power] + + for ( + cls + ) in self.list_cls_power: # this is very important so that pupups don't accumulate + if cls not in rewindow: + cls.delete() + + elif tab == 'Action 4': + self.button_coin = bui.buttonwidget( + parent=self._root_widget, + icon=bui.gettexture('coin'), + position=(550, self._width * 0.614), + size=(160, 40), + textcolor=(0, 1, 0), + color=(0, 1, 6), + scale=1, + label=str(apg['Bear Coin']), + text_scale=1, + autoselect=True, + on_activate_call=None, + ) # self._percentage_window) + self.list_cls_power.append(self.button_coin) + + try: + rewindow.append(self.button_coin) + except: + rewindow = [self.button_coin] + for ( + cls + ) in self.list_cls_power: # this is very important so that pupups don't accumulate + if cls not in rewindow: + cls.delete() + + else: + try: + for cls in self.list_cls_power: + cls.delete() + except: + pass + + if tab == 'Action 1': + sub_height = len(self.default_power_list) * 90 + v = sub_height - 55 + width = 300 + posi = 0 + id_power = list(self.default_powerups) + new_powerups = id_power[9:] + self.listpower = {} + + self._tab_container = c = bui.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, sub_height), + background=False, + selection_loops_to_parent=True, + ) + + for power in self.default_power_list: + if power == id_power[0]: + text = 'helpWindow.powerupShieldNameText' + tex = bui.gettexture('powerupShield') + elif power == id_power[1]: + text = 'helpWindow.powerupPunchNameText' + tex = bui.gettexture('powerupPunch') + elif power == id_power[2]: + text = 'helpWindow.powerupLandMinesNameText' + tex = bui.gettexture('powerupLandMines') + elif power == id_power[3]: + text = 'helpWindow.powerupImpactBombsNameText' + tex = bui.gettexture('powerupImpactBombs') + elif power == id_power[4]: + text = 'helpWindow.powerupIceBombsNameText' + tex = bui.gettexture('powerupIceBombs') + elif power == id_power[5]: + text = 'helpWindow.powerupBombNameText' + tex = bui.gettexture('powerupBomb') + elif power == id_power[6]: + text = 'helpWindow.powerupStickyBombsNameText' + tex = bui.gettexture('powerupStickyBombs') + elif power == id_power[7]: + text = 'helpWindow.powerupCurseNameText' + tex = bui.gettexture('powerupCurse') + elif power == id_power[8]: + text = 'helpWindow.powerupHealthNameText' + tex = bui.gettexture('powerupHealth') + elif power == id_power[9]: + text = power + tex = bui.gettexture('powerupSpeed') + elif power == id_power[10]: + text = power + tex = bui.gettexture('heart') + elif power == id_power[11]: + text = "Goodbye!" + tex = bui.gettexture('achievementOnslaught') + elif power == id_power[12]: + text = power + tex = bui.gettexture('ouyaUButton') + elif power == id_power[13]: + text = power + tex = bui.gettexture('achievementSuperPunch') + elif power == id_power[14]: + text = power + tex = bui.gettexture('levelIcon') + elif power == id_power[15]: + text = power + tex = bui.gettexture('ouyaOButton') + elif power == id_power[16]: + text = power + tex = bui.gettexture('star') + + if power in new_powerups: + label = getlanguage(power) + else: + label = babase.Lstr(resource=text) + + apperance = powerups[power] + position = (90, v - posi) + + t = bui.textwidget( + parent=c, + position=(position[0] - 30, position[1] - 15), + size=(width, 50), + h_align="center", + color=(bui.app.ui_v1.title_color), + text=label, + v_align="center", + maxwidth=width * 1.3, + ) + + self.powprev = bui.imagewidget( + parent=c, + position=(position[0] - 70, position[1] - 10), + size=(50, 50), + texture=tex, + ) + + dipos = 0 + for direc in ['-', '+']: + bui.buttonwidget( + parent=c, + autoselect=True, + position=(position[0] + 270 + dipos, position[1] - 10), + size=(100, 100), + scale=0.4, + label=direc, + button_type='square', + text_scale=4, + on_activate_call=babase.CallPartial(self.apperance_powerups, power, direc), + ) + + dipos += 100 + + textwidget = bui.textwidget( + parent=c, + position=(position[0] + 190, position[1] - 15), + size=(width, 50), + h_align="center", + color=cls_pow_color()[apperance], + text=str(apperance), + v_align="center", + maxwidth=width * 1.3, + ) + self.listpower[power] = textwidget + + posi += 90 + + elif tab == 'Action 2': + sub_height = 370 if not STORE['Buy Option'] else 450 + v = sub_height - 55 + width = 300 + + self._tab_container = c = bui.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, sub_height), + background=False, + selection_loops_to_parent=True, + ) + + position = (40, v - 20) + + c_display = [] + choices = ['Auto', 'SY: BALL', 'SY: Impact', 'SY: Egg'] + for display in choices: + choices_display = babase.Lstr(translate=("", getlanguage(display))) + c_display.append(choices_display) + + popup = PopupMenu( + parent=c, + position=(position[0] + 300, position[1]), + button_size=(150, 50), + scale=2.5, + choices=choices, + width=150, + choices_display=c_display, + current_choice=config['Powerup Style'], + on_value_change_call=babase.CallPartial(self._all_popup, 'Powerup Style'), + ) + + text = getlanguage('Powerup Style') + wt = len(text) * 0.80 + t = bui.textwidget( + parent=c, + position=(position[0] - 60 + wt, position[1]), + size=(width, 50), + maxwidth=width * 0.9, + scale=1.1, + h_align="center", + color=bui.app.ui_v1.title_color, + text=getlanguage('Powerup Style'), + v_align="center", + ) + + dipos = 0 + for direc in ['-', '+']: + bui.buttonwidget( + parent=c, + autoselect=True, + position=(position[0] + 310 + dipos, position[1] - 100), + size=(100, 100), + repeat=True, + scale=0.4, + label=direc, + button_type='square', + text_scale=4, + on_activate_call=babase.CallPartial(self._powerups_scale, direc), + ) + dipos += 100 + + txt_scale = config['Powerup Scale'] + self.txt_scale = bui.textwidget( + parent=c, + position=(position[0] + 230, position[1] - 105), + size=(width, 50), + scale=1.1, + h_align="center", + color=(0, 1, 0), + text=str(txt_scale), + v_align="center", + maxwidth=width * 1.3, + ) + + text = getlanguage('Powerup Scale') + wt = len(text) * 0.80 + t = bui.textwidget( + parent=c, + position=(position[0] - 60 + wt, position[1] - 100), + size=(width, 50), + maxwidth=width * 0.9, + scale=1.1, + h_align="center", + color=bui.app.ui_v1.title_color, + text=text, + v_align="center", + ) + + position = (position[0] - 20, position[1] + 40) + + self.check = bui.checkboxwidget( + parent=c, + position=(position[0] + 30, position[1] - 230), + value=config['Powerup Name'], + on_value_change_call=babase.CallPartial(self._switches, 'Powerup Name'), + maxwidth=self._scroll_width * 0.9, + text=getlanguage('Powerup Name'), + autoselect=True, + ) + + self.check = bui.checkboxwidget( + parent=c, + position=(position[0] + 30, position[1] - 230 * 1.3), + value=config['Powerup With Shield'], + on_value_change_call=babase.CallPartial(self._switches, 'Powerup With Shield'), + maxwidth=self._scroll_width * 0.9, + text=getlanguage('Powerup With Shield'), + autoselect=True, + ) + + if STORE['Buy Option']: + self.check = bui.checkboxwidget( + parent=c, + position=(position[0] + 30, position[1] - 230 * 1.6), + value=config['Powerup Time'], + on_value_change_call=babase.CallPartial(self._switches, 'Powerup Time'), + maxwidth=self._scroll_width * 0.9, + text=getlanguage('Powerup Time'), + autoselect=True, + ) + + elif tab == 'Action 3': + sub_height = 300 + v = sub_height - 55 + width = 300 + + self._tab_container = c = bui.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, sub_height), + background=False, + selection_loops_to_parent=True, + ) + + v -= 20 + position = (110, v - 45 * 1.72) + + if not STORE['Buy Percentage']: + t = bui.textwidget( + parent=c, + position=(90, v - 100), + size=(30 + width, 50), + h_align="center", + text=getlanguage('Block Option Store'), + color=bui.app.ui_v1.title_color, + v_align="center", + maxwidth=width * 1.5, + scale=1.5, + ) + + i = bui.imagewidget( + parent=c, + position=(position[0] + 100, position[1] - 205), + size=(80, 80), + texture=bui.gettexture('lock'), + ) + else: + t = bui.textwidget( + parent=c, + position=(position[0] - 14, position[1] + 70), + size=(30 + width, 50), + h_align="center", + text=f"{getlanguage('Tank Shield PTG')} ({getlanguage('Tank Shield')})", + color=bui.app.ui_v1.title_color, + v_align="center", + maxwidth=width * 1.5, + scale=1.5, + ) + + b = bui.buttonwidget( + parent=c, + autoselect=True, + position=position, + size=(100, 100), + repeat=True, + scale=0.6, + label=self.charstr[3], + button_type='square', + text_scale=2, + on_activate_call=babase.CallPartial(self.tank_shield_percentage, 'Decrement'), + ) + + b = bui.buttonwidget( + parent=c, + autoselect=True, + repeat=True, + text_scale=2, + position=(position[0] * 3.2, position[1]), + size=(100, 100), + scale=0.6, + label=self.charstr[2], + button_type='square', + on_activate_call=babase.CallPartial(self.tank_shield_percentage, 'Increment'), + ) + + porcentaje = config['Tank Shield PTG'] + if porcentaje > 59: + color = (0, 1, 0) + elif porcentaje < 40: + color = (1, 1, 0) + else: + color = (0, 1, 0.8) + + self.tank_text = bui.textwidget( + parent=c, + position=(position[0] - 14, position[1] + 5), + size=(30 + width, 50), + h_align="center", + text=str(porcentaje) + '%', + color=color, + v_align="center", + maxwidth=width * 1.3, + scale=2, + ) + + # -----> + + position = (110, v - 160 * 1.6) + t = bui.textwidget( + parent=c, + position=(position[0] - 14, position[1] + 70), + size=(30 + width, 50), + h_align="center", + text=f"{getlanguage('Healing Damage PTG')}{_sp_}({getlanguage('Healing Damage')})", + color=bui.app.ui_v1.title_color, + v_align="center", + maxwidth=width * 1.3, + scale=1.4, + ) + + b = bui.buttonwidget( + parent=c, + autoselect=True, + position=position, + size=(100, 100), + repeat=True, + scale=0.6, + label=self.charstr[3], + button_type='square', + text_scale=2, + on_activate_call=babase.CallPartial(self.health_damage_percentage, 'Decrement'), + ) + + b = bui.buttonwidget( + parent=c, + autoselect=True, + repeat=True, + text_scale=2, + position=(position[0] * 3.2, position[1]), + size=(100, 100), + scale=0.6, + label=self.charstr[2], + button_type='square', + on_activate_call=babase.CallPartial(self.health_damage_percentage, 'Increment'), + ) + + porcentaje = config['Healing Damage PTG'] + if porcentaje > 59: + color = (0, 1, 0) + elif porcentaje < 40: + color = (1, 1, 0) + else: + color = (0, 1, 0.8) + + self.hlg_text = bui.textwidget( + parent=c, + position=(position[0] - 14, position[1] + 5), + size=(30 + width, 50), + h_align="center", + text=str(porcentaje) + '%', + color=color, + v_align="center", + maxwidth=width * 1.3, + scale=2, + ) + + elif tab == 'Percentage': + sub_height = len(self.default_power_list) * 90 + v = sub_height - 55 + width = 300 + posi = 0 + id_power = list(self.default_powerups) + new_powerups = id_power[9:] + self.listpower = {} + + self._tab_container = c = bui.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, sub_height), + background=False, + selection_loops_to_parent=True, + ) + + for power in self.default_power_list: + if power == id_power[0]: + text = 'helpWindow.powerupShieldNameText' + tex = bui.gettexture('powerupShield') + elif power == id_power[1]: + text = 'helpWindow.powerupPunchNameText' + tex = bui.gettexture('powerupPunch') + elif power == id_power[2]: + text = 'helpWindow.powerupLandMinesNameText' + tex = bui.gettexture('powerupLandMines') + elif power == id_power[3]: + text = 'helpWindow.powerupImpactBombsNameText' + tex = bui.gettexture('powerupImpactBombs') + elif power == id_power[4]: + text = 'helpWindow.powerupIceBombsNameText' + tex = bui.gettexture('powerupIceBombs') + elif power == id_power[5]: + text = 'helpWindow.powerupBombNameText' + tex = bui.gettexture('powerupBomb') + elif power == id_power[6]: + text = 'helpWindow.powerupStickyBombsNameText' + tex = bui.gettexture('powerupStickyBombs') + elif power == id_power[7]: + text = 'helpWindow.powerupCurseNameText' + tex = bui.gettexture('powerupCurse') + elif power == id_power[8]: + text = 'helpWindow.powerupHealthNameText' + tex = bui.gettexture('powerupHealth') + elif power == id_power[9]: + text = power + tex = bui.gettexture('powerupSpeed') + elif power == id_power[10]: + text = power + tex = bui.gettexture('heart') + elif power == id_power[11]: + text = "Goodbye!" + tex = bui.gettexture('achievementOnslaught') + elif power == id_power[12]: + text = power + tex = bui.gettexture('ouyaUButton') + elif power == id_power[13]: + text = power + tex = bui.gettexture('achievementSuperPunch') + elif power == id_power[14]: + text = power + tex = bui.gettexture('levelIcon') + elif power == id_power[15]: + text = power + tex = bui.gettexture('ouyaOButton') + elif power == id_power[16]: + text = power + tex = bui.gettexture('star') + + if power in new_powerups: + label = getlanguage(power) + else: + label = babase.Lstr(resource=text) + + apperance = powerups[power] + position = (90, v - posi) + + t = bui.textwidget( + parent=c, + position=(position[0] - 30, position[1] - 15), + size=(width, 50), + h_align="center", + color=(bui.app.ui_v1.title_color), + text=label, + v_align="center", + maxwidth=width * 1.3, + ) + + self.powprev = bui.imagewidget( + parent=c, + position=(position[0] - 70, position[1] - 10), + size=(50, 50), + texture=tex, + ) + + ptg = str(self.total_percentage(power)) + t = bui.textwidget( + parent=c, + position=(position[0] + 170, position[1] - 10), + size=(width, 50), + h_align="center", + color=(0, 1, 0), + text=(f'{ptg}%'), + v_align="center", + maxwidth=width * 1.3, + ) + + posi += 90 + + elif tab == 'Action 4': + sub_height = 370 + width = 300 + v = sub_height - 55 + u = width - 60 + + if not self._scrollwidget or not self._scrollwidget.exists(): + return + self._tab_container = c = bui.containerwidget( + parent=self._scrollwidget, + size=(width + 500, sub_height), + background=False, + selection_loops_to_parent=True, + ) + + position = (u + 150, v - 250) + n_pos = 0 + prices = [7560, 5150, 3360] + str_name = ["FireBombs Store", "Timer Store", "Percentages Store"] + images = ["ouyaOButton", "settingsIcon", "inventoryIcon"] + + index = 0 + for store in store_items(): + p = prices[index] + txt = str_name[index] + label = getlanguage(txt) + tx_pos = len(label) * 1.8 + lb_scale = len(label) * 0.20 + preview = images[index] + + if STORE[store]: + text = getlanguage('Bought') + icon = bui.gettexture('graphicsIcon') + color = (0.52, 0.48, 0.63) + txt_scale = 1.5 + else: + text = str(p) + icon = bui.gettexture('coin') + color = (0.5, 0.4, 0.93) + txt_scale = 2 + + b = bui.buttonwidget( + parent=c, + autoselect=True, + position=(position[0] + 210 - n_pos, position[1]), + size=(250, 80), + scale=0.7, + label=text, + text_scale=txt_scale, + icon=icon, + color=color, + iconscale=1.7, + on_activate_call=babase.CallPartial(self._buy_object, store, p), + ) + + s = 180 + b = bui.buttonwidget( + parent=c, + autoselect=True, + position=(position[0] + 210 - n_pos, position[1] + 55), + size=(s, s + 30), + scale=1, + label='', + color=color, + button_type='square', + on_activate_call=babase.CallPartial(self._buy_object, store, p), + ) + + s -= 80 + i = bui.imagewidget( + parent=c, + draw_controller=b, + position=(position[0] + 250 - n_pos, position[1] + 140), + size=(s, s), + texture=bui.gettexture(preview), + ) + + t = bui.textwidget( + parent=c, + position=(position[0] + 270 - n_pos, position[1] + 101), + h_align="center", + color=(bui.app.ui_v1.title_color), + text=label, + v_align="center", + maxwidth=130, + ) + + n_pos += 280 + index += 1 + + elif tab == 'Action 5': + sub_height = 370 + v = sub_height - 55 + width = 300 + + self._tab_container = c = bui.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, sub_height), + background=False, + selection_loops_to_parent=True, + ) + + position = (0, v - 30) + + t = bui.textwidget( + parent=c, + position=(position[0] + 80, position[1] - 30), + size=(width + 60, 50), + scale=1, + h_align="center", + color=(bui.app.ui_v1.title_color), + text=babase.Lstr(resource='settingsWindowAdvanced.enterPromoCodeText'), + v_align="center", + maxwidth=width * 1.3, + ) + + self.promocode_text = bui.textwidget( + parent=c, + position=(position[0] + 80, position[1] - 100), + size=(width + 60, 50), + scale=1, + editable=True, + h_align="center", + color=(bui.app.ui_v1.title_color), + text='', + v_align="center", + maxwidth=width * 1.3, + max_chars=30, + description=babase.Lstr(resource='settingsWindowAdvanced.enterPromoCodeText'), + ) + + self.promocode_button = bui.buttonwidget( + parent=c, + position=(position[0] + 160, position[1] - 170), + size=(200, 60), + scale=1.0, + label=babase.Lstr(resource='submitText'), + on_activate_call=self._promocode, + ) + + else: + sub_height = 0 + v = sub_height - 55 + width = 300 + + self._tab_container = c = bui.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, sub_height), + background=False, + selection_loops_to_parent=True, + ) + + t = bui.textwidget( + parent=c, + position=(110, v - 20), + size=(width, 50), + scale=1.4, + color=(0.2, 1.2, 0.2), + h_align="center", + v_align="center", + text=("Ultimate Powerup Manager v2.5"), + maxwidth=width * 30, + ) + + t = bui.textwidget( + parent=c, + position=(110, v - 90), + size=(width, 50), + scale=1, + color=(1.3, 0.5, 1.0), + h_align="center", + v_align="center", + text=getlanguage('Creator'), + maxwidth=width * 30, + ) + + t = bui.textwidget( + parent=c, + position=(110, v - 220), + size=(width, 50), + scale=1, + color=(1.0, 1.2, 0.3), + h_align="center", + v_align="center", + text=getlanguage('Mod Info'), + maxwidth=width * 30, + ) + + for select_tab, button_tab in self.tab_buttons.items(): + if select_tab == tab: + bui.buttonwidget(edit=button_tab, color=(0.5, 0.4, 1.5)) + else: + bui.buttonwidget(edit=button_tab, color=(0.52, 0.48, 0.63)) + + def _all_popup(self, tag: str, popup: str) -> None: + config[tag] = popup + apg.apply_and_commit() + + def _set_concept(self, concept: str) -> None: + GLOBAL['Cls Powerup'] = concept + + if concept == 'Reset': + for power, deflt in default_powerups().items(): + powerups[power] = deflt + elif concept == 'Nothing': + for power in default_powerups(): + powerups[power] = 0 + elif concept == 'Only Bombs': + for power, deflt in default_powerups().items(): + if 'Bombs' not in power: + powerups[power] = 0 + else: + powerups[power] = 3 + elif concept == 'Only Items': + for power, deflt in default_powerups().items(): + if 'Bombs' in power: + powerups[power] = 0 + else: + powerups[power] = deflt + elif concept == 'New': + default_power = default_powerups() + new_powerups = list(default_power)[9:] + for power, deflt in default_power.items(): + if power not in new_powerups: + powerups[power] = 0 + else: + powerups[power] = deflt + + if not STORE['Buy Firebombs']: + powerups['Fire Bombs'] = 0 + + self._set_tab('Action 1') + + def tank_shield_percentage(self, tag): + max = 96 + min = 40 + if tag == 'Increment': + config['Tank Shield PTG'] += 1 + if config['Tank Shield PTG'] > max: + config['Tank Shield PTG'] = min + elif tag == 'Decrement': + config['Tank Shield PTG'] -= 1 + if config['Tank Shield PTG'] < min: + config['Tank Shield PTG'] = max + + porcentaje = config['Tank Shield PTG'] + if porcentaje > 59: + color = (0, 1, 0) + elif porcentaje < 40: + color = (1, 1, 0) + else: + color = (0, 1, 0.8) + bui.textwidget(edit=self.tank_text, text=str(porcentaje) + '%', color=color) + + def health_damage_percentage(self, tag): + max = 80 + min = 35 + if tag == 'Increment': + config['Healing Damage PTG'] += 1 + if config['Healing Damage PTG'] > max: + config['Healing Damage PTG'] = min + elif tag == 'Decrement': + config['Healing Damage PTG'] -= 1 + if config['Healing Damage PTG'] < min: + config['Healing Damage PTG'] = max + + porcentaje = config['Healing Damage PTG'] + if porcentaje > 59: + color = (0, 1, 0) + elif porcentaje < 40: + color = (1, 1, 0) + else: + color = (0, 1, 0.8) + bui.textwidget(edit=self.hlg_text, text=str(porcentaje) + '%', color=color) + + def apperance_powerups(self, powerup: str, ID: str): + max = 7 + if ID == "-": + if powerups[powerup] == 0: + powerups[powerup] = max + else: + powerups[powerup] -= 1 + elif ID == "+": + if powerups[powerup] == max: + powerups[powerup] = 0 + else: + powerups[powerup] += 1 + enum = powerups[powerup] + bui.textwidget( + edit=self.listpower[powerup], text=str(powerups[powerup]), color=cls_pow_color()[enum] + ) + + def _powerups_scale(self, ID: str): + max = 1.5 + min = 0.5 + sc = 0.1 + if ID == "-": + if config['Powerup Scale'] < (min + 0.1): + config['Powerup Scale'] = max + else: + config['Powerup Scale'] -= sc + elif ID == "+": + if config['Powerup Scale'] > (max - 0.1): + config['Powerup Scale'] = min + else: + config['Powerup Scale'] += sc + config['Powerup Scale'] = round(config['Powerup Scale'], 1) + bui.textwidget(edit=self.txt_scale, text=str(config['Powerup Scale'])) + + def total_percentage(self, power): + total = 0 + pw = powerups[power] + for i, i2 in powerups.items(): + total += i2 + if total == 0: + return float(total) + else: + ptg = 100 * pw / total + result = round(ptg, 2) + return result + + def store_refresh(self, tag: str): + if tag == 'Buy Firebombs': + powerups['Fire Bombs'] = 3 + self.default_power_list.append('Fire Bombs') + self._set_tab('Action 4') + + def _buy_object(self, tag: str, price: int): + store = BearStore( + value=tag, price=price, callback=babase.CallPartial(self.store_refresh, tag) + ) + store.buy() + + def _promocode(self): + code = bui.textwidget(query=self.promocode_text) + promo = PromoCode(code=code) + promo.code_confirmation() + bui.textwidget(edit=self.promocode_text, text="") + + def _switches(self, tag, m): + config[tag] = False if m == 0 else True + apg.apply_and_commit() + + def _percentage_window(self): + self._set_tab('Percentage') + + def _back(self): + bui.containerwidget(edit=self._root_widget, transition='out_left') + babase.app.classic.profile_browser_window() + + +def add_plugin(): + try: + from baBearModz import BearPlugin + except Exception as e: + return bs.timer(2.5, lambda e=e: bs.broadcastmessage('Error plugin: ' + str(e), (1, 0, 0))) + BearPlugin( + icon='logo', + creator='UPDATE TO API 9 BY ATD(anas) and less', + button_color=(1, 1, 0), + plugin=UltimatePowerupManager, + window=PowerupManagerWindow, + ) + + +# ba_meta export babase.Plugin + + +class UltimatePowerupManager(babase.Plugin): + # browser.ProfileBrowserWindow = NewProfileBrowserWindow + pupbox.PowerupBoxFactory = NewPowerupBoxFactory + pupbox.PowerupBox.__init__ = _pbx_ + Bomb.__init__ = _bomb_init + SpazBot.handlemessage = bot_handlemessage + Blast.handlemessage = bomb_handlemessage + Spaz.handlemessage = new_handlemessage + Spaz.__init__ = _init_spaz_ + Spaz._get_bomb_type_tex = new_get_bomb_type_tex + Spaz.on_punch_press = spaz_on_punch_press + Spaz.on_punch_release = spaz_on_punch_release + MainMenuActivity.on_transition_in = new_on_transition_in + + def __init__(self) -> None: + + # add_plugin() + ... + + def has_settings_ui(self): + return True + + def show_settings_ui(self, origin_widget): + PowerupManagerWindow() diff --git a/plugins/utilities/rank_system.py b/plugins/utilities/rank_system.py deleted file mode 100644 index 681bcb4..0000000 --- a/plugins/utilities/rank_system.py +++ /dev/null @@ -1,142 +0,0 @@ -# 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 diff --git a/plugins/utilities/sleep_on_afk.py b/plugins/utilities/sleep_on_afk.py deleted file mode 100644 index 3f3ad3b..0000000 --- a/plugins/utilities/sleep_on_afk.py +++ /dev/null @@ -1,129 +0,0 @@ -# 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