bombsquad-plugin-manager/plugins/utilities/players_list.py

1250 lines
40 KiB
Python
Raw Normal View History

# Hard Work (took me 54 hours), and thx for Polish
2026-02-07 13:20:52 +01:00
# ba_meta require api 9
"""
Players List v1.7 - An Live Players View.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
2026-02-07 13:20:52 +01:00
import babase
import bauiv1 as bui
import bascenev1 as bs
from bauiv1 import (
containerwidget as cw,
buttonwidget as bw,
textwidget as tw,
checkboxwidget as cbw,
scrollwidget as sw,
get_special_widget as gsw
)
from bauiv1lib.popup import PopupMenu
from colorsys import hsv_to_rgb
2026-02-07 13:20:52 +01:00
if TYPE_CHECKING:
from typing import Any
plugman = dict(
plugin_name="players_list",
description="A plugin that shows you live players list",
external_url="https://m.youtube.com/drap_bs",
authors=[
{"name": "Drap", "email": "mrunittest@gmail.com", "discord": "drap_bs"}
],
version="1.7.0",
)
PLAYERS_ICON = ""
2026-02-07 13:20:52 +01:00
class ColorPickerWindow:
"""Advanced color picker with custom RGB and preset colors"""
# Preset colors grid (4 rows × 8 columns = 32 colors)
PRESET_COLORS = [
# Row 1 - Reds to Greens
[(1.2, 0.0, 0.0), (0.65, 0.0, 0.0), (1.2, 0.8, 0.0), (0.89, 0.59, 0.0),
(0.67, 1.0, 0.0), (0.52, 0.78, 0.0), (0.0, 1.2, 0.0), (0.0, 0.78, 0.0)],
# Row 2 - Cyans to Purples
[(0.0, 1.2, 0.8), (0.0, 0.78, 0.52), (0.0, 0.8, 1.2), (0.0, 0.52, 0.78),
(0.0, 0.0, 1.2), (0.0, 0.0, 0.78), (0.8, 0.0, 1.2), (0.52, 0.0, 0.78)],
# Row 3 - Browns/Tans
[(1.0, 0.82, 0.48), (0.8, 0.62, 0.28), (0.8, 0.8, 0.0), (0.55, 0.55, 0.0)],
]
def __init__(self, callback, parent_window):
self._callback = callback
self._parent = parent_window
self._width = 1100
self._height = 750
# Current RGB values
self._r = 1.25
self._g = 1.25
self._b = 1.25
# Load saved colors from config
cfg = babase.app.config
self._saved_colors = cfg.get('Players Display - Saved Colors', [])
self._root_widget = cw(
parent=gsw('overlay_stack'),
size=(self._width, self._height),
transition='in_scale',
stack_offset=(250.0, -11.0),
color=(0.25, 0.25, 0.25)
)
# ═══════════════════════════════════════════════════
# LEFT SECTION - Custom Color Creator
# ═══════════════════════════════════════════════════
left_container = cw(
parent=self._root_widget,
position=(-531.9, 5.9),
size=(520.0, 750.0),
color=(0.25, 0.25, 0.25),
root_selectable=True
)
# "Custom" title
tw(
parent=self._root_widget,
position=(-306.8, 711.5),
size=(100, 30),
text='Custom',
color=(0.75, 0.75, 0.75),
scale=1.65
)
# ═══ RGB Color Displays ═══
# Red display
self._red_display = bw(
parent=self._root_widget,
position=(-481.5, 602.3),
size=(120.0, 85.0),
label='',
button_type='square',
color=(self._r, 0, 0),
enable_sound=False
)
# Green display
self._green_display = bw(
parent=self._root_widget,
position=(-481.5, 516.3),
size=(120.0, 85.0),
label='',
button_type='square',
color=(0, self._g, 0),
enable_sound=False
)
# Blue display
self._blue_display = bw(
parent=self._root_widget,
position=(-481.5, 429.3),
size=(120.0, 85.0),
label='',
button_type='square',
color=(0, 0, self._b),
enable_sound=False
)
# Preview display (gray box)
self._preview_display = bw(
parent=self._root_widget,
position=(-481.5, 316.3),
size=(120.0, 85.0),
label='',
button_type='square',
color=(self._r, self._g, self._b),
enable_sound=False
)
# ═══ RGB Control Buttons ═══
# RED controls
bw(
parent=self._root_widget,
position=(-358.4, 599.3),
size=(150.0, 90.0),
label='+',
button_type='square',
textcolor=(0.75, 0.0, 0.0),
text_scale=2,
color=(0.15, 0.1, 0.1),
on_activate_call=lambda: self._adjust_color('r', 0.05)
)
bw(
parent=self._root_widget,
position=(-208.4, 599.3),
size=(130.0, 90.0),
label='',
button_type='square',
color=(0.15, 0.1, 0.1),
text_scale=2,
textcolor=(0.75, 0, 0),
on_activate_call=lambda: self._adjust_color('r', -0.05)
)
# GREEN controls
bw(
parent=self._root_widget,
position=(-358.4, 509.3),
size=(150.0, 90.0),
label='+',
button_type='square',
textcolor=(0, 0.75, 0.0),
text_scale=2,
color=(0.1, 0.15, 0.1),
on_activate_call=lambda: self._adjust_color('g', 0.05)
)
bw(
parent=self._root_widget,
position=(-208.4, 509.3),
size=(130.0, 90.0),
label='',
button_type='square',
color=(0.1, 0.15, 0.1),
text_scale=2,
textcolor=(0, 0.75, 0),
on_activate_call=lambda: self._adjust_color('g', -0.05)
)
# BLUE controls
bw(
parent=self._root_widget,
position=(-358.4, 419.3),
size=(150.0, 90.0),
label='+',
button_type='square',
textcolor=(0, 0.0, 0.75),
text_scale=2,
color=(0.1, 0.1, 0.15),
on_activate_call=lambda: self._adjust_color('b', 0.05)
)
bw(
parent=self._root_widget,
position=(-208.4, 418.3),
size=(130.0, 90.0),
label='',
button_type='square',
color=(0.1, 0.1, 0.15),
text_scale=2,
textcolor=(0, 0, 0.75),
on_activate_call=lambda: self._adjust_color('b', -0.05)
)
# ═══ RGB Value Displays ═══
self._r_text = tw(
parent=self._root_widget,
position=(-343.5, 343.4),
size=(100, 30),
text=f'{self._r:.2f}',
color=(1.0, 0.0, 0.0)
)
self._g_text = tw(
parent=self._root_widget,
position=(-253.5, 343.4),
size=(100, 30),
text=f'{self._g:.2f}',
color=(0.0, 1.0, 0.0)
)
self._b_text = tw(
parent=self._root_widget,
position=(-153.5, 343.4),
size=(100, 30),
text=f'{self._b:.2f}',
color=(0.0, 0.0, 1.0)
)
# ═══ Separator Lines ═══
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.6,
position=(-460.6, 406.2),
size=(370.0, 4.0),
parent=self._root_widget,
color=(0.4, 0.4, 0.4)
)
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.6,
position=(-478.6, 246.2),
size=(400.0, 4.0),
parent=self._root_widget,
color=(0.4, 0.4, 0.4)
)
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.2,
position=(-281.3, 250.8),
size=(4.0, 52.0),
parent=self._root_widget
)
# ═══ Action Buttons ═══
# Save button
bw(
parent=self._root_widget,
position=(-483.2, 255.0),
size=(200.0, 45.0),
label='Save',
button_type='square',
textcolor=(1.2, 1.2, 1.2),
color=(0.0, 0.89, 0.0),
on_activate_call=self._save_color
)
# Apply button
bw(
parent=self._root_widget,
position=(-271.2, 256.0),
size=(200.0, 45.0),
label='Apply',
button_type='square',
textcolor=(1.2, 1.2, 1.2),
color=(0.0, 0.89, 0.0),
on_activate_call=self._apply_custom_color
)
# ═══ Saved Colors Section ═══
tw(
parent=self._root_widget,
position=(-476.8, 212.5),
size=(100, 30),
text='Saved',
color=(0.65, 0.65, 0.65),
scale=1.2
)
self._saved_scroll = bui.hscrollwidget(
parent=self._root_widget,
position=(-487.9, 63.4),
size=(420.0, 140.0),
capture_arrows=True,
border_opacity=1.2
)
# ═══════════════════════════════════════════════════
# RIGHT SECTION - Preset Colors
# ═══════════════════════════════════════════════════
# "Colors" title
tw(
parent=self._root_widget,
position=(193.2, 711.5),
size=(100, 30),
text='Colors',
color=(0.75, 0.75, 0.75),
scale=1.65
)
tw(
parent=self._root_widget,
position=(273.2, 702.5),
size=(100, 30),
text='pick one for your list !',
color=(0.55, 0.55, 0.55),
scale=0.875
)
# Separator line
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.6,
position=(139.4, 396.2),
size=(750.0, 7.5),
parent=self._root_widget,
color=(0.4, 0.4, 0.4)
)
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.2,
position=(495.7, 402.8),
size=(7.5, 87.5),
parent=self._root_widget
)
# Preset colors grid
self._create_preset_colors()
# "More Colors Soon...." text
tw(
parent=self._root_widget,
position=(442.6, 347.5),
size=(100, 30),
text='More Colors Soon....',
color=(0.32, 0.32, 0.32),
shadow=False
)
# Loading spinner
bui.spinnerwidget(
parent=self._root_widget,
position=(554.3, 323.2),
size=45.0
)
# Back button (top left corner)
bw(
parent=self._root_widget,
position=(-510, 690),
size=(60, 50),
label='<',
color=(0.6, 0.1, 0.1),
textcolor=(1, 0.8, 0.8),
text_scale=1.5,
on_activate_call=self._close
)
# Load saved colors
self._load_saved_colors()
cw(edit=self._root_widget, on_cancel_call=self._close)
def _load_saved_colors(self):
"""Load and display saved colors from config"""
if not self._saved_colors:
return
# Create container for saved colors
self._saved_container = bui.containerwidget(
parent=self._saved_scroll,
size=(max(1000, len(self._saved_colors) * 70), 100),
background=False
)
# Add all saved color buttons
for idx, color in enumerate(self._saved_colors):
x_pos = idx * 70
bw(
parent=self._saved_container,
position=(x_pos, 10),
size=(60, 90),
label='',
button_type='square',
color=color,
on_activate_call=lambda c=color: self._select_saved_color(c)
)
def _create_preset_colors(self):
"""Create preset color buttons grid"""
# Starting positions
start_x = 88.1
button_size = 80.0
spacing = 20.0
row_positions = [589.1, 499.1, 409.1]
for row_idx, row_colors in enumerate(self.PRESET_COLORS):
y = row_positions[row_idx]
for col_idx, color in enumerate(row_colors):
x = start_x + col_idx * (button_size + spacing)
bw(
parent=self._root_widget,
position=(x, y),
size=(button_size, button_size),
label='',
button_type='square',
color=color,
on_activate_call=lambda c=color: self._select_preset_color(c)
)
def _adjust_color(self, channel: str, amount: float):
"""Adjust RGB values"""
if channel == 'r':
self._r = max(0.0, min(1.5, self._r + amount))
elif channel == 'g':
self._g = max(0.0, min(1.5, self._g + amount))
elif channel == 'b':
self._b = max(0.0, min(1.5, self._b + amount))
self._update_displays()
bui.getsound('click01').play()
def _update_displays(self):
"""Update all color displays"""
# Update individual color displays
bw(edit=self._red_display, color=(self._r, 0, 0))
bw(edit=self._green_display, color=(0, self._g, 0))
bw(edit=self._blue_display, color=(0, 0, self._b))
# Update preview
bw(edit=self._preview_display, color=(self._r, self._g, self._b))
# Update RGB text displays
tw(edit=self._r_text, text=f'{self._r:.2f}')
tw(edit=self._g_text, text=f'{self._g:.2f}')
tw(edit=self._b_text, text=f'{self._b:.2f}')
def _save_color(self):
"""Save current color to saved list"""
current_color = (self._r, self._g, self._b)
self._saved_colors.append(current_color)
# Save to config
cfg = babase.app.config
cfg['Players Display - Saved Colors'] = self._saved_colors
cfg.commit()
# Create container for saved colors if doesn't exist
if not hasattr(self, '_saved_container'):
self._saved_container = bui.containerwidget(
parent=self._saved_scroll,
size=(1000, 100),
background=False
)
# Add color button in horizontal line
x_pos = (len(self._saved_colors) - 1) * 70 # 70 = button width + spacing
saved_btn = bw(
parent=self._saved_container,
position=(x_pos, 10),
size=(60, 90),
label='',
button_type='square',
color=current_color,
on_activate_call=lambda c=current_color: self._select_saved_color(c)
)
bui.screenmessage('Saved!', color=(0, 1, 0))
bui.getsound('cashRegister').play()
def _select_saved_color(self, color: tuple):
"""Select a saved color and apply it"""
# Update custom RGB values
self._r, self._g, self._b = color
self._update_displays()
# Apply the color
self._callback(color)
bui.screenmessage('Applied!', color=(0, 1, 0))
bui.getsound('ding').play()
def _apply_custom_color(self):
"""Apply the custom RGB color (don't close)"""
self._callback((self._r, self._g, self._b))
bui.screenmessage('Applied!', color=(0, 1, 0))
bui.getsound('dingSmall').play()
def _select_preset_color(self, color: tuple):
"""Select a preset color and close the window"""
self._callback(color)
self._close() # Close the window after selecting
def _close(self):
cw(edit=self._root_widget, transition='out_scale')
2026-02-07 13:20:52 +01:00
class SettingsWindow:
"""Main settings window with live preview"""
# Design styles
DESIGNS = ['box', 'simple', 'minimal']
def __init__(self, source_widget=None):
self._width = 1150
self._height = 700
# Get current settings
cfg = babase.app.config
self._temp_show_full = cfg.get('Players Display - Show Full Name', False)
self._temp_name_type = cfg.get('Players Display - Name Type', 'Player Name')
self._temp_color = cfg.get('Players Display - Color', (1, 1, 1))
self._temp_rainbow = cfg.get('Players Display - Rainbow', False)
self._temp_show_id = cfg.get('Players Display - Show Client ID', True)
self._temp_style = cfg.get('Players Display - Style', 'box')
self._root_widget = cw(
parent=gsw('overlay_stack'),
size=(self._width, self._height),
transition='in_scale',
stack_offset=(-250.0, -20.0),
color=(0.25, 0.25, 0.25)
)
# ═══════════════════════════════════════════════════
# HEADER
# ═══════════════════════════════════════════════════
# Back button
bw(
parent=self._root_widget,
position=(9.6, 626.3),
size=(135.0, 120.0),
label='«',
color=(1.2, 0.0, 0.0),
textcolor=(1.5, 1.5, 1.5),
text_scale=2,
scale=0.75,
on_activate_call=self._close
)
# Settings title
tw(
parent=self._root_widget,
position=(188.3, 661.9),
size=(100, 30),
text='Settings',
color=(0.75, 0.75, 0.75),
scale=2
)
tw(
parent=self._root_widget,
position=(318.3, 651.9),
size=(100, 30),
text='Customize Your Players List.',
color=(0.55, 0.55, 0.55),
scale=0.85,
shadow=0.4
)
# Apply button
bw(
parent=self._root_widget,
position=(877.9, 639.1),
size=(185.0, 100.0),
label='Apply',
color=(0.0, 0.89, 0.0),
textcolor=(1, 1, 1),
on_activate_call=self._apply_changes
)
# ═══════════════════════════════════════════════════
# MAIN SCROLL AREA
# ═══════════════════════════════════════════════════
scroll = sw(
parent=self._root_widget,
position=(86.8, 61.7),
size=(1000.0, 569.0),
border_opacity=1.25
)
# ═══════════════════════════════════════════════════
# SHOW PLAYER SECTION
# ═══════════════════════════════════════════════════
tw(
parent=self._root_widget,
position=(111.4, 581.4),
size=(100, 30),
text='Show Player',
scale=1.25,
color=(0.85, 0.85, 0.85)
)
# Separator line
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.6,
position=(94.3, 480.9),
size=(975.0, 4.0),
parent=self._root_widget
)
# Name type label
tw(
parent=self._root_widget,
position=(95, 535),
size=(0, 0),
text='Name Type:',
color=(0.85, 0.85, 0.85),
h_align='left',
v_align='center',
scale=0.8
)
# Popup menu for name type selection
PopupMenu(
parent=self._root_widget,
position=(95, 487),
choices=['Account Name', 'Player Name'],
current_choice=self._temp_name_type,
on_value_change_call=self._set_name_type,
button_size=(185, 45)
)
# Vertical separator 1
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.45,
position=(275.8, 483.6),
size=(4.0, 90.0),
parent=self._root_widget
)
# Full Name checkbox (shows both names)
cbw(
parent=self._root_widget,
position=(288.0, 513.1),
size=(100, 30),
text='Full Name',
color=(0.0, 0.89, 0.0),
scale=1.2,
textcolor=(1, 1, 1),
value=self._temp_show_full,
on_value_change_call=self._on_full_name_change
)
# Vertical separator 2
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.45,
position=(495.8, 483.6),
size=(4.0, 90.0),
parent=self._root_widget
)
# Client ID checkbox
cbw(
parent=self._root_widget,
position=(509.9, 510.8),
size=(100, 30),
text='Client ID',
textcolor=(1, 1, 1.0),
color=(0.05, 0.78, 0.05),
scale=1.25,
value=self._temp_show_id,
on_value_change_call=self._on_client_id_change
)
# ═══════════════════════════════════════════════════
# COLOR SECTION
# ═══════════════════════════════════════════════════
tw(
parent=self._root_widget,
position=(108.3, 431.9),
size=(100, 30),
text='Color',
color=(0.75, 0.75, 0.75),
scale=1.35
)
# Separator line
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.6,
position=(94.3, 336.9),
size=(975.0, 2.5),
parent=self._root_widget
)
# Color display button
self._color_display = bw(
parent=self._root_widget,
position=(97.0, 341.5),
size=(85.0, 85.0),
label='',
color=self._temp_color,
enable_sound=False
)
# Vertical separator
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.45,
position=(192.8, 338.6),
size=(5.0, 80.0),
parent=self._root_widget
)
# Pick button
bw(
parent=self._root_widget,
position=(198.6, 343.3),
size=(160.0, 80.0),
label='Pick',
button_type='square',
color=(0.0, 0.0, 0.3555),
textcolor=(0.9, 0.9, 1),
on_activate_call=self._open_color_picker
)
# Vertical separator
bui.imagewidget(
texture=bui.gettexture('white'),
opacity=0.45,
position=(362.8, 338.6),
size=(5.0, 80.0),
parent=self._root_widget
)
# Rainbow checkbox
cbw(
parent=self._root_widget,
position=(378.0, 353.1),
size=(100, 30),
text='Rainbow',
color=(0.0, 0.89, 0.0),
scale=1.2,
textcolor=(1, 1, 1),
value=self._temp_rainbow,
on_value_change_call=self._on_rainbow_change
)
# ═══════════════════════════════════════════════════
# DESIGN SECTION
# ═══════════════════════════════════════════════════
tw(
parent=self._root_widget,
position=(108.3, 301.9),
size=(100, 30),
text='Design',
color=(0.75, 0.75, 0.75),
scale=1.35
)
# Design buttons (3 styles)
self._design_btns = []
# Box style
btn1 = bw(
parent=self._root_widget,
position=(84.2, 63.8),
size=(350.0, 225.0),
label='BOX Style',
button_type='square',
color=(0.1, 1.2, 0.1) if self._temp_style == 'box' else (0.75, 0.85, 0.75),
on_activate_call=lambda: self._set_style('box')
)
self._design_btns.append(btn1)
# Simple style
btn2 = bw(
parent=self._root_widget,
position=(417.2, 63.8),
size=(350.0, 225.0),
label='Simple Style',
button_type='square',
color=(0.1, 1.2, 0.1) if self._temp_style == 'simple' else (0.75, 0.85, 0.75),
on_activate_call=lambda: self._set_style('simple')
)
self._design_btns.append(btn2)
# Minimal style
btn3 = bw(
parent=self._root_widget,
position=(744.2, 63.8),
size=(350.0, 225.0),
label='Minimal Style',
button_type='square',
color=(0.1, 1.2, 0.1) if self._temp_style == 'minimal' else (0.75, 0.85, 0.75),
on_activate_call=lambda: self._set_style('minimal')
)
self._design_btns.append(btn3)
# ═══════════════════════════════════════════════════
# RESULT SECTION (Preview)
# ═══════════════════════════════════════════════════
result_container = cw(
parent=self._root_widget,
position=(1147.2, 121.9),
size=(450.0, 500.0),
color=(0.25, 0.25, 0.25)
)
tw(
parent=self._root_widget,
position=(1328.3, 581.9),
size=(100, 30),
text='Result',
color=(0.75, 0.75, 0.75),
scale=1.55
)
# Preview text widget
self._preview_text = tw(
parent=result_container,
position=(225, 350),
size=(0, 0),
text=self._generate_preview_text(),
color=self._temp_color,
h_align='center',
v_align='center',
scale=0.5,
maxwidth=400
)
# Start preview update loop
self._update_preview()
cw(edit=self._root_widget, on_cancel_call=self._close)
def _generate_preview_text(self):
"""Generate preview text based on current settings"""
player_count = 3 # Sample count
# Get icon
icon_text = f"{PLAYERS_ICON} " if PLAYERS_ICON else ""
"""Generate preview text based on current settings"""
player_count = 3 # Sample count
if self._temp_style == 'box':
header = "╔═══════════════════╗\n"
header += f"║ PLAYERS ({player_count}) ║\n"
header += "╠═══════════════════╣\n\n"
footer = "\n\n╚═══════════════════╝"
elif self._temp_style == 'simple':
header = "━━━━━━━━━━━━━━━━━━━\n"
header += f" PLAYERS ({player_count})\n"
header += "━━━━━━━━━━━━━━━━━━━\n\n"
footer = "\n\n━━━━━━━━━━━━━━━━━━━"
else: # minimal
header = f" PLAYERS ({player_count})\n\n"
footer = ""
# Sample names
player_name = "YT!Drap"
account_name = "Drap"
# Build player line based on settings
if self._temp_show_full:
# Full name: show both
name_part = f"{player_name} | {account_name}"
else:
# Show based on name type
if self._temp_name_type == 'Account Name':
name_part = account_name
else:
name_part = player_name
# Add client ID if enabled
if self._temp_show_id:
player_line = f"{name_part} [#42]"
else:
player_line = f"{name_part}"
return header + player_line + footer
def _update_preview(self):
"""Update preview with rainbow effect if enabled"""
if not hasattr(self, '_preview_text') or not self._preview_text.exists():
return
# Update preview text
tw(edit=self._preview_text, text=self._generate_preview_text())
# Update color (rainbow or static)
if self._temp_rainbow:
# Smooth rainbow loop
if not hasattr(self, '_rainbow_hue'):
self._rainbow_hue = 0.0
self._rainbow_hue = (self._rainbow_hue + 0.02) % 1.0
color = hsv_to_rgb(self._rainbow_hue, 1.0, 1.0)
tw(edit=self._preview_text, color=color)
else:
tw(edit=self._preview_text, color=self._temp_color)
# Schedule next update
babase.apptimer(0.05, babase.WeakCall(self._update_preview))
def _set_name_type(self, name_type: str):
"""Set name type"""
self._temp_name_type = name_type
def _set_style(self, style: str):
"""Set design style and update button colors"""
self._temp_style = style
# Update all design buttons
for idx, btn in enumerate(self._design_btns):
if self.DESIGNS[idx] == style:
bw(edit=btn, color=(0.1, 1.2, 0.1))
else:
bw(edit=btn, color=(0.75, 0.85, 0.75))
def _on_full_name_change(self, value: bool):
self._temp_show_full = value
def _on_client_id_change(self, value: bool):
self._temp_show_id = value
def _on_rainbow_change(self, value: bool):
self._temp_rainbow = value
def _on_color_selected(self, color: tuple):
self._temp_color = color
bw(edit=self._color_display, color=color)
def _open_color_picker(self):
ColorPickerWindow(self._on_color_selected, self)
def _apply_changes(self):
"""Apply all changes and save to config"""
cfg = babase.app.config
cfg['Players Display - Show Full Name'] = self._temp_show_full
cfg['Players Display - Name Type'] = self._temp_name_type
cfg['Players Display - Color'] = self._temp_color
cfg['Players Display - Rainbow'] = self._temp_rainbow
cfg['Players Display - Show Client ID'] = self._temp_show_id
cfg['Players Display - Style'] = self._temp_style
cfg.commit()
bui.screenmessage('Settings Applied!', color=(0, 1, 0))
bui.getsound('cashRegister').play()
def _close(self):
cw(edit=self._root_widget, transition='out_scale')
class PlayersDisplay:
"""Main display class"""
def __init__(self):
self.container = None
self.rainbow_hue = 0.0
self.current_players = {} # Track: {client_id: (player_name, account_name)}
def on_app_running(self):
self._update_loop()
def _update_loop(self):
self._update_display()
# Check if rainbow mode is enabled
cfg = babase.app.config
is_rainbow = cfg.get('Players Display - Rainbow', False)
# Use faster loop for smooth rainbow, slower for normal updates
delay = 0.05 if is_rainbow else 0.5
babase.apptimer(delay, babase.WeakCall(self._update_loop))
def _get_display_color(self):
cfg = babase.app.config
if cfg.get('Players Display - Rainbow', False):
# Smooth rainbow loop with smaller increment
self.rainbow_hue = (self.rainbow_hue + 0.01) % 1.0
return hsv_to_rgb(self.rainbow_hue, 1.0, 1.0)
else:
return cfg.get('Players Display - Color', (0.7, 0.7, 0.7))
def _show_message(self, message: str, color: tuple, duration: float = 1.0):
"""Show a temporary message with fade in/out animation"""
try:
activity = bs.get_foreground_host_activity()
if activity is None:
return
with activity.context:
# Create message node
msg_node = bs.newnode(
'text',
attrs={
'text': message,
'scale': 0.65,
'color': color,
'h_attach': 'right',
'v_attach': 'top',
'h_align': 'left',
'v_align': 'top',
'position': (-240, -270), # Below the main list
'flatness': 0.0,
'shadow': 1.2,
'opacity': 0.0 # Start invisible
}
)
# Fade in animation (0.2 seconds)
bs.animate(msg_node, 'opacity', {
0.0: 0.0,
0.2: 0.95
})
# Fade out animation (starts after duration)
bs.animate(msg_node, 'opacity', {
duration: 0.95,
duration + 0.3: 0.0
})
# Delete node after animations complete
bs.timer(duration + 0.4, msg_node.delete)
except Exception:
pass
def _check_player_changes(self, new_players: dict):
"""Check for players joining or leaving
new_players: {client_id: (player_name, account_name)}
"""
new_ids = set(new_players.keys())
old_ids = set(self.current_players.keys())
# Players who joined
joined = new_ids - old_ids
for player_id in joined:
player_name, account_name = new_players[player_id]
message = f"{account_name} joined the server"
color = self._get_display_color()
self._show_message(message, color, duration=1.0)
# Players who left
left = old_ids - new_ids
for player_id in left:
player_name, account_name = self.current_players[player_id]
message = f"{player_name} left the server"
color = (1.2, 0.2, 0.2) # Glowing red
self._show_message(message, color, duration=1.0)
# Update current players
self.current_players = new_players.copy()
def _update_display(self):
try:
activity = bs.get_foreground_host_activity()
except Exception:
activity = None
2026-02-07 13:20:52 +01:00
if activity is None:
if self.container is not None:
try:
self.container.delete()
except Exception:
pass
2026-02-07 13:20:52 +01:00
self.container = None
return
cfg = babase.app.config
show_full = cfg.get('Players Display - Show Full Name', False)
name_type = cfg.get('Players Display - Name Type', 'Player Name')
show_client_id = cfg.get('Players Display - Show Client ID', True)
style = cfg.get('Players Display - Style', 'box')
2026-02-07 13:20:52 +01:00
player_names = []
new_players_data = {} # {client_id: (player_name, account_name)}
2026-02-07 13:20:52 +01:00
for p in activity.players:
try:
# Get both player name and account name
player_name = p.getname(full=True)
try:
account_name = p.sessionplayer.inputdevice.get_v1_account_name(full=True)
if not account_name:
account_name = player_name
except Exception:
account_name = player_name
# Get client ID
try:
client_id = p.sessionplayer.inputdevice.client_id
except Exception:
client_id = id(p) # Fallback to object id
# Store for tracking
new_players_data[client_id] = (player_name, account_name)
# Build name based on settings
if show_full:
# Full name: show both names
if player_name != account_name:
name = f"{player_name} | {account_name}"
else:
name = player_name
else:
# Show based on name type
if name_type == 'Account Name':
name = account_name
else:
name = player_name
# Add client ID if enabled
if show_client_id:
try:
player_names.append(f"{name} [#{client_id}]")
except Exception:
player_names.append(f"{name}")
else:
player_names.append(f"{name}")
except Exception:
continue
# Check for player changes (join/leave)
self._check_player_changes(new_players_data)
# Get player count
player_count = len(player_names)
# Get icon
icon_text = f"{PLAYERS_ICON} " if PLAYERS_ICON else ""
# Different styles with player count
if style == 'box':
header = "╔═══════════════════╗\n"
header += f"║  PLAYERS ({player_count}) ║\n"
header += "╠═══════════════════╣\n\n"
footer = "\n\n╚═══════════════════╝"
elif style == 'simple':
header = "━━━━━━━━━━━━━━━━━━━\n"
header += f"{icon_text}PLAYERS ({player_count})\n"
header += "━━━━━━━━━━━━━━━━━━━\n\n"
footer = "\n\n━━━━━━━━━━━━━━━━━━━"
else: # minimal
header = f" PLAYERS ({player_count})\n\n"
footer = ""
if player_names:
players_str = "\n".join(player_names)
else:
players_str = " ⏳ Waiting...\n No players yet"
2026-02-07 13:20:52 +01:00
final_text = header + players_str + footer
current_color = self._get_display_color()
try:
with activity.context:
if self.container is None or not self.container.exists():
self.container = bs.newnode(
'text',
attrs={
'text': final_text,
'scale': 0.7,
'color': current_color,
'h_attach': 'right',
'v_attach': 'top',
'h_align': 'left',
'v_align': 'top',
'position': (-240, -90),
'flatness': 0.0,
'shadow': 1.2,
'opacity': 0.95
}
)
else:
self.container.text = final_text
self.container.color = current_color
except Exception:
pass
2026-02-07 13:20:52 +01:00
# My First Cool Mod ,WooHoo.
# ba_meta export babase.Plugin
class CoolList(babase.Plugin):
"""Main plugin class"""
def __init__(self):
cfg = babase.app.config
if 'Players Display - Show Full Name' not in cfg:
cfg['Players Display - Show Full Name'] = False # Default to single name
if 'Players Display - Name Type' not in cfg:
cfg['Players Display - Name Type'] = 'Player Name' # Default to player name
if 'Players Display - Color' not in cfg:
cfg['Players Display - Color'] = (1, 1, 1)
if 'Players Display - Rainbow' not in cfg:
cfg['Players Display - Rainbow'] = False
if 'Players Display - Show Client ID' not in cfg:
cfg['Players Display - Show Client ID'] = True
if 'Players Display - Style' not in cfg:
cfg['Players Display - Style'] = 'box'
cfg.commit()
self.display = PlayersDisplay()
self.display.on_app_running()
def has_settings_ui(self) -> bool:
return True
def show_settings_ui(self, source_widget: Any) -> None:
SettingsWindow(source_widget)