added random join plugin

This commit is contained in:
* 2022-12-27 03:46:21 +05:30
parent 2c8b17629b
commit f213f2429b
2 changed files with 329 additions and 1 deletions

View file

@ -0,0 +1,313 @@
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