diff --git a/plugins/utilities/custom_death.py b/plugins/utilities/custom_death.py new file mode 100644 index 0000000..b3730e1 --- /dev/null +++ b/plugins/utilities/custom_death.py @@ -0,0 +1,40 @@ +# ba_meta require api 7 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import ba +from bastd.actor.spaz import Spaz + +if TYPE_CHECKING: + from typing import Any + + +Spaz.oldhandlemessage = Spaz.handlemessage + + +def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, ba.DieMessage): + if self.node: + self.node.color_texture = ba.gettexture('bonesColor') + self.node.color_mask_texture = ba.gettexture('bonesColorMask') + self.node.head_model = ba.getmodel('bonesHead') + self.node.torso_model = ba.getmodel('bonesTorso') + self.node.pelvis_model = ba.getmodel('bonesPelvis') + self.node.upper_arm_model = ba.getmodel('bonesUpperArm') + self.node.forearm_model = ba.getmodel('bonesForeArm') + self.node.hand_model = ba.getmodel('bonesHand') + self.node.upper_leg_model = ba.getmodel('bonesUpperLeg') + self.node.lower_leg_model = ba.getmodel('bonesLowerLeg') + self.node.toes_model = ba.getmodel('bonesToes') + self.node.style = 'bones' + self.oldhandlemessage(msg) + else: + return self.oldhandlemessage(msg) + + +# ba_meta export plugin +class CustomDeath(ba.Plugin): + Spaz.handlemessage = handlemessage diff --git a/plugins/utilities/max_players.py b/plugins/utilities/max_players.py new file mode 100644 index 0000000..6d6257a --- /dev/null +++ b/plugins/utilities/max_players.py @@ -0,0 +1,368 @@ +"""===========MAX_PLAYERS===========""" + +# ba_meta require api 7 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations +from typing import TYPE_CHECKING + +import ba +import _ba +from ba._session import Session +from ba._coopsession import CoopSession, TEAM_COLORS, TEAM_NAMES +from ba._multiteamsession import MultiTeamSession +from bastd.ui.gather import GatherWindow +from bastd.ui.popup import PopupWindow + +if TYPE_CHECKING: + from typing import List, Any, Optional, Sequence + + +cfg = ba.app.config +cmp = {'coop_max_players': 4, + 'teams_max_players': 8, + 'ffa_max_players': 8} + +lang = ba.app.lang.language +if lang == 'Spanish': + title_text = 'Máximo de Jugadores' + title_short_text = 'Jugadores' + coop_text = 'Cooperativo' + teams_text = 'Equipos' + ffa_text = 'Todos Contra Todos' +else: + title_text = 'Max Players' + title_short_text = 'Players' + coop_text = 'Co-op' + teams_text = 'Teams' + ffa_text = 'FFA' + + +class ConfigNumberEdit: + + def __init__(self, + parent: ba.Widget, + position: Tuple[float, float], + value: int, + config: str, + text: str): + self._increment = 1 + self._minval = 1 + self._maxval = 100 + self._value = value + self._config = config + + textscale = 1.0 + self.nametext = ba.textwidget( + parent=parent, + position=(position[0], position[1]), + size=(100, 30), + text=text, + maxwidth=150, + color=(0.8, 0.8, 0.8, 1.0), + h_align='left', + v_align='center', + scale=textscale) + self.valuetext = ba.textwidget( + parent=parent, + position=(position[0]+150, position[1]), + size=(60, 28), + editable=False, + color=(0.3, 1.0, 0.3, 1.0), + h_align='right', + v_align='center', + text=str(value), + padding=2) + self.minusbutton = ba.buttonwidget( + parent=parent, + position=(position[0]+240, position[1]), + size=(28, 28), + label='-', + autoselect=True, + on_activate_call=ba.Call(self._down), + repeat=True) + self.plusbutton = ba.buttonwidget( + parent=parent, + position=(position[0]+290, position[1]), + size=(28, 28), + label='+', + autoselect=True, + on_activate_call=ba.Call(self._up), + repeat=True) + + def _up(self) -> None: + self._value = min(self._maxval, self._value + self._increment) + self._update_display() + + def _down(self) -> None: + self._value = max(self._minval, self._value - self._increment) + self._update_display() + + def _update_display(self) -> None: + ba.textwidget(edit=self.valuetext, text=str(self._value)) + cfg['Config Max Players'][self._config] = self._value + cfg.apply_and_commit() + + +class SettingsMaxPlayers(PopupWindow): + + def __init__(self): + # pylint: disable=too-many-locals + uiscale = ba.app.ui.uiscale + self._transitioning_out = False + self._width = 400 + self._height = 220 + bg_color = (0.5, 0.4, 0.6) + + # creates our _root_widget + PopupWindow.__init__(self, + position=(0.0, 0.0), + size=(self._width, self._height), + scale=1.2, + bg_color=bg_color) + + self._cancel_button = ba.buttonwidget( + parent=self.root_widget, + position=(25, self._height - 40), + size=(50, 50), + scale=0.58, + label='', + color=bg_color, + on_activate_call=self._on_cancel_press, + autoselect=True, + icon=ba.gettexture('crossOut'), + iconscale=1.2) + ba.containerwidget(edit=self.root_widget, + cancel_button=self._cancel_button) + + ba.textwidget( + parent=self.root_widget, + position=(self._width * 0.5, self._height - 30), + size=(0, 0), + h_align='center', + v_align='center', + scale=0.8, + text=title_text, + maxwidth=200, + color=ba.app.ui.title_color) + + posx = 33 + posy = self._height + + # co-op + ConfigNumberEdit(parent=self.root_widget, + position=(posx, posy*0.6), + value=cfg['Config Max Players']['coop_max_players'], + config='coop_max_players', + text=coop_text) + + # teams + ConfigNumberEdit(parent=self.root_widget, + position=(posx, posy*0.38), + value=cfg['Config Max Players']['teams_max_players'], + config='teams_max_players', + text=teams_text) + + # ffa + ConfigNumberEdit(parent=self.root_widget, + position=(posx, posy*0.16), + value=cfg['Config Max Players']['ffa_max_players'], + config='ffa_max_players', + text=ffa_text) + + def _on_cancel_press(self) -> None: + self._transition_out() + + def _transition_out(self) -> None: + if not self._transitioning_out: + self._transitioning_out = True + ba.containerwidget(edit=self.root_widget, transition='out_scale') + + def on_popup_cancel(self) -> None: + ba.playsound(ba.getsound('swish')) + self._transition_out() + + +def __init__(self) -> None: + """Instantiate a co-op mode session.""" + # pylint: disable=cyclic-import + from ba._campaign import getcampaign + from bastd.activity.coopjoin import CoopJoinActivity + + _ba.increment_analytics_count('Co-op session start') + app = _ba.app + + # If they passed in explicit min/max, honor that. + # Otherwise defer to user overrides or defaults. + if 'min_players' in app.coop_session_args: + min_players = app.coop_session_args['min_players'] + else: + min_players = 1 + if 'max_players' in app.coop_session_args: + max_players = app.coop_session_args['max_players'] + else: + max_players = app.config.get( + 'Coop Game Max Players', + cfg['Config Max Players']['coop_max_players']) + + # print('FIXME: COOP SESSION WOULD CALC DEPS.') + depsets: Sequence[ba.DependencySet] = [] + + Session.__init__(self, + depsets, + team_names=TEAM_NAMES, + team_colors=TEAM_COLORS, + min_players=min_players, + max_players=max_players) + + # Tournament-ID if we correspond to a co-op tournament (otherwise None) + self.tournament_id: Optional[str] = ( + app.coop_session_args.get('tournament_id')) + + self.campaign = getcampaign(app.coop_session_args['campaign']) + self.campaign_level_name: str = app.coop_session_args['level'] + + self._ran_tutorial_activity = False + self._tutorial_activity: Optional[ba.Activity] = None + self._custom_menu_ui: List[Dict[str, Any]] = [] + + # Start our joining screen. + self.setactivity(_ba.newactivity(CoopJoinActivity)) + + self._next_game_instance: Optional[ba.GameActivity] = None + self._next_game_level_name: Optional[str] = None + self._update_on_deck_game_instances() + + +def get_max_players(self) -> int: + """Return max number of ba.Players allowed to join the game at once.""" + if self.use_teams: + return _ba.app.config.get( + 'Team Game Max Players', + cfg['Config Max Players']['teams_max_players']) + return _ba.app.config.get( + 'Free-for-All Max Players', + cfg['Config Max Players']['ffa_max_players']) + + +GatherWindow.__old_init__ = GatherWindow.__init__ + + +def __gather_init__(self, + transition: Optional[str] = 'in_right', + origin_widget: ba.Widget = None): + self.__old_init__(transition, origin_widget) + + def _do_max_players(): + SettingsMaxPlayers() + self._max_players_button = ba.buttonwidget( + parent=self._root_widget, + position=(self._width*0.72, self._height*0.91), + size=(220, 60), + scale=1.0, + color=(0.6, 0.0, 0.9), + icon=ba.gettexture('usersButton'), + iconscale=1.5, + autoselect=True, + label=title_short_text, + button_type='regular', + on_activate_call=_do_max_players) + + +def _save_state(self) -> None: + try: + for tab in self._tabs.values(): + tab.save_state() + + sel = self._root_widget.get_selected_child() + selected_tab_ids = [ + tab_id for tab_id, tab in self._tab_row.tabs.items() + if sel == tab.button + ] + if sel == self._back_button: + sel_name = 'Back' + elif sel == self._max_players_button: + sel_name = 'Max Players' + elif selected_tab_ids: + assert len(selected_tab_ids) == 1 + sel_name = f'Tab:{selected_tab_ids[0].value}' + elif sel == self._tab_container: + sel_name = 'TabContainer' + else: + raise ValueError(f'unrecognized selection: \'{sel}\'') + ba.app.ui.window_states[type(self)] = { + 'sel_name': sel_name, + } + except Exception: + ba.print_exception(f'Error saving state for {self}.') + + +def _restore_state(self) -> None: + from efro.util import enum_by_value + try: + for tab in self._tabs.values(): + tab.restore_state() + + sel: Optional[ba.Widget] + winstate = ba.app.ui.window_states.get(type(self), {}) + sel_name = winstate.get('sel_name', None) + assert isinstance(sel_name, (str, type(None))) + current_tab = self.TabID.ABOUT + gather_tab_val = ba.app.config.get('Gather Tab') + try: + stored_tab = enum_by_value(self.TabID, gather_tab_val) + if stored_tab in self._tab_row.tabs: + current_tab = stored_tab + except ValueError: + pass + self._set_tab(current_tab) + if sel_name == 'Back': + sel = self._back_button + elif sel_name == 'Max Players': + sel = self._back_button + elif sel_name == 'TabContainer': + sel = self._tab_container + elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): + try: + sel_tab_id = enum_by_value(self.TabID, + sel_name.split(':')[-1]) + except ValueError: + sel_tab_id = self.TabID.ABOUT + sel = self._tab_row.tabs[sel_tab_id].button + else: + sel = self._tab_row.tabs[current_tab].button + ba.containerwidget(edit=self._root_widget, selected_child=sel) + except Exception: + ba.print_exception('Error restoring gather-win state.') + +# ba_meta export plugin + + +class MaxPlayersPlugin(ba.Plugin): + + def has_settings_ui(self) -> bool: + return True + + def show_settings_ui(self, source_widget: ba.Widget | None) -> None: + SettingsMaxPlayers() + + if 'Config Max Players' in ba.app.config: + old_config = ba.app.config['Config Max Players'] + for setting in cmp: + if setting not in old_config: + ba.app.config['Config Max Players'].update({setting: cmp[setting]}) + remove_list = [] + for setting in old_config: + if setting not in cmp: + remove_list.append(setting) + for element in remove_list: + ba.app.config['Config Max Players'].pop(element) + else: + ba.app.config['Config Max Players'] = cmp + ba.app.config.apply_and_commit() + + CoopSession.__init__ = __init__ + MultiTeamSession.get_max_players = get_max_players + GatherWindow.__init__ = __gather_init__ + GatherWindow._save_state = _save_state + GatherWindow._restore_state = _restore_state diff --git a/plugins/utilities/quick_custom_game.py b/plugins/utilities/quick_custom_game.py new file mode 100644 index 0000000..259dff2 --- /dev/null +++ b/plugins/utilities/quick_custom_game.py @@ -0,0 +1,381 @@ +# ba_meta require api 7 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import ba +import _ba +from bastd.ui.play import PlayWindow +from bastd.ui.playlist.addgame import PlaylistAddGameWindow +from ba._freeforallsession import FreeForAllSession +from bastd.activity.multiteamjoin import MultiTeamJoinActivity + +if TYPE_CHECKING: + pass + + +lang = ba.app.lang.language + +if lang == 'Spanish': + custom_txt = 'personalizar...' +else: + custom_txt = 'custom...' + + +if 'quick_game_button' in ba.app.config: + config = ba.app.config['quick_game_button'] +else: + config = {'selected': None, 'config': None} + ba.app.config['quick_game_button'] = config + ba.app.config.commit() + + +def start_game(session: ba.Session, fadeout: bool = True): + def callback(): + if fadeout: + _ba.unlock_all_input() + try: + _ba.new_host_session(session) + except Exception: + from bastd import mainmenu + ba.print_exception('exception running session', session) + + # Drop back into a main menu session. + _ba.new_host_session(mainmenu.MainMenuSession) + + if fadeout: + _ba.fade_screen(False, time=0.25, endcall=callback) + _ba.lock_all_input() + else: + callback() + + +class SimplePlaylist: + + def __init__(self, + settings: dict, + gametype: type[ba.GameActivity]): + self.settings = settings + self.gametype = gametype + + def pull_next(self) -> None: + if 'map' not in self.settings['settings']: + settings = dict( + map=self.settings['map'], **self.settings['settings']) + else: + settings = self.settings['settings'] + return dict(resolved_type=self.gametype, settings=settings) + + +class CustomSession(FreeForAllSession): + + def __init__(self, *args, **kwargs): + # pylint: disable=cyclic-import + self.use_teams = False + self._tutorial_activity_instance = None + ba.Session.__init__(self, depsets=[], + team_names=None, + team_colors=None, + min_players=1, + max_players=self.get_max_players()) + + self._series_length = 1 + self._ffa_series_length = 1 + + # Which game activity we're on. + self._game_number = 0 + self._playlist = SimplePlaylist(self._config, self._gametype) + config['selected'] = self._gametype.__name__ + config['config'] = self._config + ba.app.config.commit() + + # Get a game on deck ready to go. + self._current_game_spec: Optional[Dict[str, Any]] = None + self._next_game_spec: Dict[str, Any] = self._playlist.pull_next() + self._next_game: Type[ba.GameActivity] = ( + self._next_game_spec['resolved_type']) + + # Go ahead and instantiate the next game we'll + # use so it has lots of time to load. + self._instantiate_next_game() + + # Start in our custom join screen. + self.setactivity(_ba.newactivity(MultiTeamJoinActivity)) + + +class SelectGameWindow(PlaylistAddGameWindow): + + def __init__(self, transition: str = 'in_right'): + class EditController: + _sessiontype = ba.FreeForAllSession + + def get_session_type(self) -> Type[ba.Session]: + return self._sessiontype + + self._editcontroller = EditController() + self._r = 'addGameWindow' + uiscale = ba.app.ui.uiscale + self._width = 750 if uiscale is ba.UIScale.SMALL else 650 + x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 + self._height = (346 if uiscale is ba.UIScale.SMALL else + 380 if uiscale is ba.UIScale.MEDIUM else 440) + top_extra = 30 if uiscale is ba.UIScale.SMALL else 20 + self._scroll_width = 210 + + self._root_widget = ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + scale=(2.17 if uiscale is ba.UIScale.SMALL else + 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), + stack_offset=(0, 1) if uiscale is ba.UIScale.SMALL else (0, 0)) + + self._back_button = ba.buttonwidget(parent=self._root_widget, + position=(58 + x_inset, + self._height - 53), + size=(165, 70), + scale=0.75, + text_scale=1.2, + label=ba.Lstr(resource='backText'), + autoselect=True, + button_type='back', + on_activate_call=self._back) + self._select_button = select_button = ba.buttonwidget( + parent=self._root_widget, + position=(self._width - (172 + x_inset), self._height - 50), + autoselect=True, + size=(160, 60), + scale=0.75, + text_scale=1.2, + label=ba.Lstr(resource='selectText'), + on_activate_call=self._add) + + if ba.app.ui.use_toolbars: + ba.widget(edit=select_button, + right_widget=_ba.get_special_widget('party_button')) + + ba.textwidget(parent=self._root_widget, + position=(self._width * 0.5, self._height - 28), + size=(0, 0), + scale=1.0, + text=ba.Lstr(resource=self._r + '.titleText'), + h_align='center', + color=ba.app.ui.title_color, + maxwidth=250, + v_align='center') + v = self._height - 64 + + self._selected_title_text = ba.textwidget( + parent=self._root_widget, + position=(x_inset + self._scroll_width + 50 + 30, v - 15), + size=(0, 0), + scale=1.0, + color=(0.7, 1.0, 0.7, 1.0), + maxwidth=self._width - self._scroll_width - 150 - x_inset * 2, + h_align='left', + v_align='center') + v -= 30 + + self._selected_description_text = ba.textwidget( + parent=self._root_widget, + position=(x_inset + self._scroll_width + 50 + 30, v), + size=(0, 0), + scale=0.7, + color=(0.5, 0.8, 0.5, 1.0), + maxwidth=self._width - self._scroll_width - 150 - x_inset * 2, + h_align='left') + + scroll_height = self._height - 100 + + v = self._height - 60 + + self._scrollwidget = ba.scrollwidget(parent=self._root_widget, + position=(x_inset + 61, + v - scroll_height), + size=(self._scroll_width, + scroll_height), + highlight=False) + ba.widget(edit=self._scrollwidget, + up_widget=self._back_button, + left_widget=self._back_button, + right_widget=select_button) + self._column: Optional[ba.Widget] = None + + v -= 35 + ba.containerwidget(edit=self._root_widget, + cancel_button=self._back_button, + start_button=select_button) + self._selected_game_type: Optional[Type[ba.GameActivity]] = None + + ba.containerwidget(edit=self._root_widget, + selected_child=self._scrollwidget) + + self._game_types: list[type[ba.GameActivity]] = [] + + # Get actual games loading in the bg. + ba.app.meta.load_exported_classes(ba.GameActivity, + self._on_game_types_loaded, + completion_cb_in_bg_thread=True) + + # Refresh with our initial empty list. We'll refresh again once + # game loading is complete. + self._refresh() + + if config['selected']: + for gt in self._game_types: + if gt.__name__ == config['selected']: + self._refresh(selected=gt) + self._set_selected_game_type(gt) + + def _refresh(self, + select_get_more_games_button: bool = False, + selected: bool = None) -> None: + # from ba.internal import get_game_types + + if self._column is not None: + self._column.delete() + + self._column = ba.columnwidget(parent=self._scrollwidget, + border=2, + margin=0) + + for i, gametype in enumerate(self._game_types): + + def _doit() -> None: + if self._select_button: + ba.timer(0.1, + self._select_button.activate, + timetype=ba.TimeType.REAL) + + txt = ba.textwidget(parent=self._column, + position=(0, 0), + size=(self._width - 88, 24), + text=gametype.get_display_string(), + h_align='left', + v_align='center', + color=(0.8, 0.8, 0.8, 1.0), + maxwidth=self._scroll_width * 0.8, + on_select_call=ba.Call( + self._set_selected_game_type, gametype), + always_highlight=True, + selectable=True, + on_activate_call=_doit) + if i == 0: + ba.widget(edit=txt, up_widget=self._back_button) + + self._get_more_games_button = ba.buttonwidget( + parent=self._column, + autoselect=True, + label=ba.Lstr(resource=self._r + '.getMoreGamesText'), + color=(0.54, 0.52, 0.67), + textcolor=(0.7, 0.65, 0.7), + on_activate_call=self._on_get_more_games_press, + size=(178, 50)) + if select_get_more_games_button: + ba.containerwidget(edit=self._column, + selected_child=self._get_more_games_button, + visible_child=self._get_more_games_button) + + def _add(self) -> None: + _ba.lock_all_input() # Make sure no more commands happen. + ba.timer(0.1, _ba.unlock_all_input, timetype=ba.TimeType.REAL) + gameconfig = {} + if config['selected'] == self._selected_game_type.__name__: + if config['config']: + gameconfig = config['config'] + if 'map' in gameconfig: + gameconfig['settings']['map'] = gameconfig.pop('map') + self._selected_game_type.create_settings_ui( + self._editcontroller.get_session_type(), + gameconfig, + self._edit_game_done) + + def _edit_game_done(self, config: Optional[Dict[str, Any]]) -> None: + if config: + CustomSession._config = config + CustomSession._gametype = self._selected_game_type + start_game(CustomSession) + else: + ba.app.ui.clear_main_menu_window(transition='out_right') + ba.app.ui.set_main_menu_window( + SelectGameWindow(transition='in_left').get_root_widget()) + + def _back(self) -> None: + ba.containerwidget(edit=self._root_widget, transition='out_right') + ba.app.ui.set_main_menu_window( + PlayWindow(transition='in_left').get_root_widget()) + + +PlayWindow._old_init = PlayWindow.__init__ + + +def __init__(self, *args, **kwargs): + self._old_init() + + width = 800 + height = 550 + + def do_quick_game() -> None: + self._save_state() + ba.containerwidget(edit=self._root_widget, transition='out_left') + ba.app.ui.set_main_menu_window( + SelectGameWindow().get_root_widget()) + + self._quick_game_button = ba.buttonwidget( + parent=self._root_widget, + position=(width - 55 - 120, height - 132), + autoselect=True, + size=(120, 60), + scale=1.1, + text_scale=1.2, + label=custom_txt, + on_activate_call=do_quick_game, + color=(0.54, 0.52, 0.67), + textcolor=(0.7, 0.65, 0.7)) + + self._restore_state() + + +def states(self) -> None: + return { + 'Team Games': self._teams_button, + 'Co-op Games': self._coop_button, + 'Free-for-All Games': self._free_for_all_button, + 'Back': self._back_button, + 'Quick Game': self._quick_game_button + } + + +def _save_state(self) -> None: + swapped = {v: k for k, v in states(self).items()} + if self._root_widget.get_selected_child() in swapped: + ba.app.ui.window_states[ + self.__class__.__name__] = swapped[ + self._root_widget.get_selected_child()] + else: + ba.print_exception(f'Error saving state for {self}.') + + +def _restore_state(self) -> None: + if not hasattr(self, '_quick_game_button'): + return # ensure that our monkey patched init ran + if self.__class__.__name__ not in ba.app.ui.window_states: + ba.containerwidget(edit=self._root_widget, + selected_child=self._coop_button) + return + sel = states(self).get( + ba.app.ui.window_states[self.__class__.__name__], None) + if sel: + ba.containerwidget(edit=self._root_widget, selected_child=sel) + else: + ba.containerwidget(edit=self._root_widget, + selected_child=self._coop_button) + ba.print_exception(f'Error restoring state for {self}.') + + +# ba_meta export plugin +class QuickGamePlugin(ba.Plugin): + PlayWindow.__init__ = __init__ + PlayWindow._save_state = _save_state + PlayWindow._restore_state = _restore_state diff --git a/plugins/utilities/random_colors.py b/plugins/utilities/random_colors.py new file mode 100644 index 0000000..3ea0ba6 --- /dev/null +++ b/plugins/utilities/random_colors.py @@ -0,0 +1,48 @@ +# ba_meta require api 7 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import ba +import random +from bastd.actor import bomb + +if TYPE_CHECKING: + from typing import Sequence + + +class NewBlast(bomb.Blast): + def __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: ba.Player | None = None, + hit_type: str = 'explosion', + hit_subtype: str = 'normal', + ): + super().__init__(position, velocity, blast_radius, blast_type, + source_player, hit_type, hit_subtype) + scorch_radius = light_radius = self.radius + if self.blast_type == 'tnt': + scorch_radius *= 1.15 + scorch = ba.newnode( + 'scorch', + attrs={ + 'position': position, + 'size': scorch_radius * 0.5, + 'big': (self.blast_type == 'tnt'), + }, + ) + random_color = (random.random(), random.random(), random.random()) + scorch.color = ba.safecolor(random_color) + ba.animate(scorch, 'presence', {3.000: 1, 13.000: 0}) + ba.timer(13.0, scorch.delete) + + +# ba_meta export plugin +class RandomColorsPlugin(ba.Plugin): + bomb.Blast = NewBlast