mirror of
https://github.com/bombsquad-community/plugin-manager.git
synced 2025-10-08 14:54:36 +00:00
313 lines
10 KiB
Python
313 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, TypeVar
|
|
|
|
import _ba
|
|
import ba
|
|
import ba.internal
|
|
import random
|
|
from bastd.ui.gather.publictab import PublicGatherTab, PartyEntry,PingThread
|
|
if TYPE_CHECKING:
|
|
from typing import Callable
|
|
|
|
ClassType = TypeVar('ClassType')
|
|
MethodType = TypeVar('Methodtype')
|
|
|
|
|
|
def override(cls: ClassType) -> Callable[[MethodType], MethodType]:
|
|
def decorator(newfunc: MethodType) -> MethodType:
|
|
funcname = newfunc.__code__.co_name
|
|
if hasattr(cls, funcname):
|
|
oldfunc = getattr(cls, funcname)
|
|
setattr(cls, f'_old_{funcname}', oldfunc)
|
|
|
|
setattr(cls, funcname, newfunc)
|
|
return newfunc
|
|
|
|
return decorator
|
|
|
|
# Can this stuff break mro? (P.S. yes, so we're not using super() anymore).
|
|
# Although it gives nice auto-completion.
|
|
# And anyways, why not just GatherPublicTab = NewGatherPublicTab?
|
|
# But hmm, if we imagine someone used `from blah.blah import Blah`, using
|
|
# `blah.Blah = NewBlah` AFTERWARDS would be meaningless.
|
|
class NewPublicGatherTab(PublicGatherTab,PingThread):
|
|
|
|
@override(PublicGatherTab)
|
|
def _build_join_tab(self, region_width: float,
|
|
region_height: float,
|
|
oldfunc: Callable = None) -> None:
|
|
# noinspection PyUnresolvedReferences
|
|
self._old__build_join_tab(region_width, region_height)
|
|
|
|
# Copy-pasted from original function.
|
|
c_width = region_width
|
|
c_height = region_height - 20
|
|
sub_scroll_height = c_height - 125
|
|
sub_scroll_width = 830
|
|
v = c_height - 35
|
|
v -= 60
|
|
|
|
self._random_join_button = ba.buttonwidget(
|
|
parent=self._container,
|
|
label='random',
|
|
size=(90, 45),
|
|
position=(710, v + 10),
|
|
on_activate_call=ba.WeakCall(self._join_random_server),
|
|
)
|
|
ba.widget(edit=self._random_join_button, up_widget=self._host_text,
|
|
left_widget=self._filter_text)
|
|
|
|
# We could place it somewhere under plugin settings which is kind of
|
|
# official way to customise plugins. Although it's too deep:
|
|
# Gather Window -> Main Menu -> Settings -> Advanced -(scroll)->
|
|
# Plugins -(scroll probably)-> RandomJoin Settings.
|
|
self._random_join_settings_button = ba.buttonwidget(
|
|
parent=self._container,
|
|
icon=ba.gettexture('settingsIcon'),
|
|
size=(40, 40),
|
|
position=(820, v + 13),
|
|
on_activate_call=ba.WeakCall(self._show_random_join_settings),
|
|
)
|
|
|
|
@override(PublicGatherTab)
|
|
def _show_random_join_settings(self) -> None:
|
|
RandomJoinSettingsPopup(
|
|
origin_widget=self._random_join_settings_button)
|
|
|
|
@override(PublicGatherTab)
|
|
def _get_parties_list(self) -> list[PartyEntry]:
|
|
if (self._parties_sorted and
|
|
(randomjoin.maximum_ping == 9999 or
|
|
# Ensure that we've pinged at least 10%.
|
|
len([p for k, p in self._parties_sorted
|
|
if p.ping is not None]) > len(self._parties_sorted) / 10)):
|
|
randomjoin.cached_parties = [p for k, p in self._parties_sorted]
|
|
return randomjoin.cached_parties
|
|
|
|
@override(PublicGatherTab)
|
|
def _join_random_server(self) -> None:
|
|
name_prefixes = set()
|
|
parties = [p for p in self._get_parties_list() if
|
|
(p.size >= randomjoin.minimum_players
|
|
and p.size < p.size_max and (randomjoin.maximum_ping == 9999
|
|
or (p.ping is not None
|
|
and p.ping <= randomjoin.maximum_ping)))]
|
|
|
|
if not parties:
|
|
ba.screenmessage('No suitable servers found; wait',
|
|
color=(1, 0, 0))
|
|
ba.playsound(ba.getsound('error'))
|
|
return
|
|
|
|
for party in parties:
|
|
name_prefixes.add(party.name[:6])
|
|
|
|
random.choice(list(name_prefixes))
|
|
|
|
party = random.choice(
|
|
[p for p in parties if p.name[:6] in name_prefixes])
|
|
|
|
ba.internal.connect_to_party(party.address, party.port)
|
|
|
|
|
|
class RandomJoinSettingsPopup(ba.Window):
|
|
def __init__(self, origin_widget: ba.Widget) -> None:
|
|
c_width = 600
|
|
c_height = 400
|
|
uiscale = ba.app.ui.uiscale
|
|
super().__init__(root_widget=ba.containerwidget(
|
|
scale=(
|
|
1.8
|
|
if uiscale is ba.UIScale.SMALL
|
|
else 1.55
|
|
if uiscale is ba.UIScale.MEDIUM
|
|
else 1.0
|
|
),
|
|
scale_origin_stack_offset=origin_widget.get_screen_space_center(),
|
|
stack_offset=(0, -10)
|
|
if uiscale is ba.UIScale.SMALL
|
|
else (0, 15)
|
|
if uiscale is ba.UIScale.MEDIUM
|
|
else (0, 0),
|
|
size=(c_width, c_height),
|
|
transition='in_scale',
|
|
))
|
|
|
|
ba.textwidget(
|
|
parent=self._root_widget,
|
|
size=(0, 0),
|
|
h_align='center',
|
|
v_align='center',
|
|
text='Random Join Settings',
|
|
scale=1.5,
|
|
color=(0.6, 1.0, 0.6),
|
|
maxwidth=c_width * 0.8,
|
|
position=(c_width * 0.5, c_height - 60),
|
|
)
|
|
|
|
v = c_height - 120
|
|
ba.textwidget(
|
|
parent=self._root_widget,
|
|
size=(0, 0),
|
|
h_align='right',
|
|
v_align='center',
|
|
text='Maximum ping',
|
|
maxwidth=c_width * 0.3,
|
|
position=(c_width * 0.4, v),
|
|
)
|
|
self._maximum_ping_edit = ba.textwidget(
|
|
parent=self._root_widget,
|
|
size=(c_width * 0.3, 40),
|
|
h_align='left',
|
|
v_align='center',
|
|
text=str(randomjoin.maximum_ping),
|
|
editable=True,
|
|
description='Maximum ping (ms)',
|
|
position=(c_width * 0.6, v - 20),
|
|
autoselect=True,
|
|
max_chars=4,
|
|
)
|
|
v -= 60
|
|
ba.textwidget(
|
|
parent=self._root_widget,
|
|
size=(0, 0),
|
|
h_align='right',
|
|
v_align='center',
|
|
text='Minimum players',
|
|
maxwidth=c_width * 0.3,
|
|
position=(c_width * 0.4, v),
|
|
)
|
|
self._minimum_players_edit = ba.textwidget(
|
|
parent=self._root_widget,
|
|
size=(c_width * 0.3, 40),
|
|
h_align='left',
|
|
v_align='center',
|
|
text=str(randomjoin.minimum_players),
|
|
editable=True,
|
|
description='Minimum number of players',
|
|
position=(c_width * 0.6, v - 20),
|
|
autoselect=True,
|
|
max_chars=4,
|
|
)
|
|
v -= 60
|
|
|
|
# Cancel button.
|
|
self.cancel_button = btn = ba.buttonwidget(
|
|
parent=self._root_widget,
|
|
label=ba.Lstr(resource='cancelText'),
|
|
size=(180, 60),
|
|
color=(1.0, 0.2, 0.2),
|
|
position=(40, 30),
|
|
on_activate_call=self._cancel,
|
|
autoselect=True,
|
|
)
|
|
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
|
|
|
|
# Save button.
|
|
self.savebtn = btn = ba.buttonwidget(
|
|
parent=self._root_widget,
|
|
label=ba.Lstr(resource='saveText'),
|
|
size=(180, 60),
|
|
position=(c_width - 200, 30),
|
|
on_activate_call=self._save,
|
|
autoselect=True,
|
|
)
|
|
ba.containerwidget(edit=self._root_widget, start_button=btn)
|
|
|
|
def _save(self) -> None:
|
|
errored = False
|
|
minimum_players: int | None = None
|
|
maximum_ping: int | None = None
|
|
try:
|
|
minimum_players = int(
|
|
ba.textwidget(query=self._minimum_players_edit))
|
|
except ValueError:
|
|
ba.screenmessage('"Minimum players" should be integer',
|
|
color=(1, 0, 0))
|
|
ba.playsound(ba.getsound('error'))
|
|
errored = True
|
|
try:
|
|
maximum_ping = int(
|
|
ba.textwidget(query=self._maximum_ping_edit))
|
|
except ValueError:
|
|
ba.screenmessage('"Maximum ping" should be integer',
|
|
color=(1, 0, 0))
|
|
ba.playsound(ba.getsound('error'))
|
|
errored = True
|
|
if errored:
|
|
return
|
|
|
|
assert minimum_players is not None
|
|
assert maximum_ping is not None
|
|
|
|
if minimum_players < 0:
|
|
ba.screenmessage('"Minimum players" should be at least 0',
|
|
color=(1, 0, 0))
|
|
ba.playsound(ba.getsound('error'))
|
|
errored = True
|
|
|
|
if maximum_ping <= 0:
|
|
ba.screenmessage('"Maximum ping" should be greater than 0',
|
|
color=(1, 0, 0))
|
|
ba.playsound(ba.getsound('error'))
|
|
ba.screenmessage('(use 9999 as dont-care value)',
|
|
color=(1, 0, 0))
|
|
errored = True
|
|
|
|
if errored:
|
|
return
|
|
|
|
randomjoin.maximum_ping = maximum_ping
|
|
randomjoin.minimum_players = minimum_players
|
|
|
|
randomjoin.commit_config()
|
|
ba.playsound(ba.getsound('shieldUp'))
|
|
self._transition_out()
|
|
|
|
def _cancel(self) -> None:
|
|
ba.playsound(ba.getsound('shieldDown'))
|
|
self._transition_out()
|
|
|
|
def _transition_out(self) -> None:
|
|
ba.containerwidget(edit=self._root_widget, transition='out_scale')
|
|
|
|
|
|
class RandomJoin:
|
|
def __init__(self) -> None:
|
|
self.cached_parties: list[PartyEntry] = []
|
|
self.maximum_ping: int = 9999
|
|
self.minimum_players: int = 2
|
|
self.load_config()
|
|
|
|
def load_config(self) -> None:
|
|
cfg = ba.app.config.get('Random Join', {
|
|
'maximum_ping': self.maximum_ping,
|
|
'minimum_players': self.minimum_players,
|
|
})
|
|
try:
|
|
self.maximum_ping = cfg['maximum_ping']
|
|
self.minimum_players = cfg['minimum_players']
|
|
except KeyError:
|
|
ba.screenmessage('Error: RandomJoin config is broken, resetting..',
|
|
color=(1, 0, 0), log=True)
|
|
ba.playsound(ba.getsound('error'))
|
|
self.commit_config()
|
|
|
|
def commit_config(self) -> None:
|
|
ba.app.config['Random Join'] = {
|
|
'maximum_ping': self.maximum_ping,
|
|
'minimum_players': self.minimum_players,
|
|
}
|
|
ba.app.config.commit()
|
|
|
|
|
|
randomjoin = RandomJoin()
|
|
|
|
|
|
# ba_meta require api 7
|
|
# ba_meta export ba.Plugin
|
|
class RandomJoinPlugin(ba.Plugin):
|
|
def on_app_running(self) -> None:
|
|
# I feel bad that all patching logic happens not here.
|
|
pass
|