mirror of
https://github.com/bombsquad-community/plugin-manager.git
synced 2025-10-08 14:54:36 +00:00
2422 lines
88 KiB
Python
2422 lines
88 KiB
Python
"""Practice Tools Mod: V3.0
|
|
Made by Cross Joy"""
|
|
|
|
# If anyone wants to help me by giving suggestions/fixing bugs/ creating PR,
|
|
# Can visit my GitHub https://github.com/CrossJoy/Bombsquad-Modding
|
|
|
|
# You can contact me through Discord:
|
|
# My Discord ID: Cross Joy#0721
|
|
# My BS Discord Server: https://discord.gg/JyBY6haARJ
|
|
|
|
# I would appreciate some support. :')
|
|
# Support link: https://www.buymeacoffee.com/CrossJoy
|
|
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# V3.0 update
|
|
# - Updated to Api 9
|
|
# - Fixed the bomb radius visual not properly disappearing after being disabled.
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Powerful and comprehensive tools for practice purposes.
|
|
|
|
# Features:
|
|
# - Spawn any bot anywhere.
|
|
# - Can spawn power-ups on your own.
|
|
# - Bomb radius visualizer. (Thx Mikirog for some of the codes :D )
|
|
# - Bomb Countdown.
|
|
# and many more
|
|
|
|
# Go explore the tools yourself.:)
|
|
|
|
# Practice tabs can be accessed through the party window.
|
|
# Coop and local multiplayer compatible.
|
|
# Work on any 1.79+ ver.
|
|
|
|
# FAQ:
|
|
# Can I use it to practice with friends?
|
|
# - Yes, but you are the only one who can access the practice window.
|
|
|
|
# Does it work when I join a public server?
|
|
# - Not possible.
|
|
|
|
# Can I use it during Co-op game?
|
|
# - Yes, it works fine.
|
|
# ----------------------------------------------------------------------------
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
import random
|
|
import weakref
|
|
from enum import Enum
|
|
from typing import TYPE_CHECKING
|
|
|
|
import babase
|
|
import bascenev1 as bs
|
|
import bascenev1lib
|
|
import bauiv1 as bui
|
|
from bauiv1lib import mainmenu
|
|
from babase import app, Plugin
|
|
from bascenev1lib.actor.powerupbox import PowerupBox, PowerupBoxFactory
|
|
from bascenev1lib.actor.bomb import Bomb
|
|
from bascenev1lib.actor.spaz import Spaz
|
|
from bascenev1lib.actor import spawner
|
|
from bascenev1lib.actor.spazbot import (SpazBotSet, SpazBot, BrawlerBot,
|
|
TriggerBot,
|
|
ChargerBot, StickyBot, ExplodeyBot,
|
|
BouncyBot,
|
|
BomberBotPro, BrawlerBotPro,
|
|
TriggerBotPro,
|
|
ChargerBotPro, BomberBotProShielded,
|
|
BrawlerBotProShielded,
|
|
TriggerBotProShielded,
|
|
ChargerBotProShielded, BomberBotLite,
|
|
BrawlerBotLite)
|
|
from bascenev1lib.mainmenu import MainMenuSession
|
|
from bauiv1lib import popup
|
|
from bauiv1lib.party import PartyWindow as OriginalPartyWindow
|
|
from bauiv1lib.tabs import TabRow
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Any, Sequence, Callable, Optional
|
|
|
|
version = '3.0'
|
|
|
|
|
|
class ConfigLoader:
|
|
def __init__(self):
|
|
self.config_data = {
|
|
"Practice Tab": False,
|
|
"bombCountdown": False,
|
|
"bombRadiusVisual": False,
|
|
"stopBots": False,
|
|
"immortalDummy": False,
|
|
"powerupsExpire": False,
|
|
"invincible": False
|
|
}
|
|
self.config_names = ["Practice Tab", "bombCountdown",
|
|
"bombRadiusVisual", "stopBots",
|
|
"immortalDummy", "powerupsExpire", "invincible"]
|
|
|
|
def load_configs(self):
|
|
for config_name in self.config_names:
|
|
try:
|
|
existing_config = babase.app.config.get(config_name)
|
|
if existing_config is None:
|
|
babase.app.config[config_name] = self.config_data[
|
|
config_name]
|
|
else:
|
|
babase.app.config.get(config_name)
|
|
except:
|
|
babase.app.config[config_name] = self.config_data[config_name]
|
|
|
|
|
|
class PartyWindow(bui.Window):
|
|
_redefine_methods = ['__init__']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
getattr(self, '__init___old')(*args, **kwargs)
|
|
|
|
self.bg_color = (.5, .5, .5)
|
|
|
|
self._edit_movements_button = bui.buttonwidget(
|
|
parent=self._root_widget,
|
|
scale=0.7,
|
|
position=(360, self._height - 47),
|
|
# (self._width - 80, self._height - 47)
|
|
size=(100, 50),
|
|
label='Practice',
|
|
autoselect=True,
|
|
button_type='square',
|
|
on_activate_call=bs.Call(doTestButton, self),
|
|
color=self.bg_color,
|
|
iconscale=1.2)
|
|
|
|
|
|
def redefine(obj: object, name: str, new: callable,
|
|
new_name: str = None) -> None:
|
|
if not new_name:
|
|
new_name = name + '_old'
|
|
if hasattr(obj, name):
|
|
setattr(obj, new_name, getattr(obj, name))
|
|
setattr(obj, name, new)
|
|
|
|
|
|
def redefine_class(original_cls: object, cls: object) -> None:
|
|
for method in cls._redefine_methods:
|
|
redefine(original_cls, method, getattr(cls, method))
|
|
|
|
|
|
def main(plugin: Plugin) -> None:
|
|
print(f'Practice Tools v{plugin.__version__}')
|
|
app.practice_tool = plugin
|
|
redefine_class(OriginalPartyWindow, PartyWindow)
|
|
|
|
|
|
class NewMainMenuWindow(mainmenu.MainMenuWindow):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Display chat icon, but if user open/close gather it may disappear
|
|
bui.set_party_window_open(True)
|
|
|
|
|
|
# ba_meta require api 9
|
|
# ba_meta export plugin
|
|
class Practice(Plugin):
|
|
__version__ = version
|
|
|
|
def on_app_running(self) -> None:
|
|
"""Plugin start point."""
|
|
|
|
if app.env.engine_build_number < 20427:
|
|
bui.screenmessage(
|
|
'ok',
|
|
color=(.8, .1, .1))
|
|
raise RuntimeError(
|
|
'sad')
|
|
mainmenu.MainMenuWindow = NewMainMenuWindow
|
|
config_loader = ConfigLoader()
|
|
config_loader.load_configs()
|
|
return main(self)
|
|
|
|
|
|
def new_bomb_init(func):
|
|
def setting(*args, **kwargs):
|
|
func(*args, **kwargs)
|
|
|
|
bomb_type = args[0].bomb_type
|
|
fuse_bomb = ('land_mine', 'tnt', 'impact')
|
|
|
|
args[0].radius_visualizer = bs.newnode('locator',
|
|
owner=args[0].node,
|
|
# Remove itself when the bomb node dies.
|
|
attrs={
|
|
'shape': 'circle',
|
|
'color': (1, 0, 0),
|
|
'opacity': 0.05,
|
|
'draw_beauty': False,
|
|
'additive': False
|
|
})
|
|
args[0].node.connectattr('position', args[0].radius_visualizer,
|
|
'position')
|
|
|
|
|
|
|
|
args[0].radius_visualizer_circle = bs.newnode(
|
|
'locator',
|
|
owner=args[
|
|
0].node,
|
|
# Remove itself when the bomb node dies.
|
|
attrs={
|
|
'shape': 'circleOutline',
|
|
'size': [
|
|
args[
|
|
0].blast_radius * 2.0],
|
|
# Here's that bomb's blast radius value again!
|
|
'color': (
|
|
1, 1, 0),
|
|
'draw_beauty': False,
|
|
'additive': True
|
|
})
|
|
args[0].node.connectattr('position',
|
|
args[0].radius_visualizer_circle,
|
|
'position')
|
|
|
|
|
|
if bomb_type == 'tnt':
|
|
args[0].fatal = bs.newnode('locator',
|
|
owner=args[0].node,
|
|
# Remove itself when the bomb node dies.
|
|
attrs={
|
|
'shape': 'circle',
|
|
'color': (
|
|
0.7, 0, 0),
|
|
'opacity': 0.10,
|
|
'draw_beauty': False,
|
|
'additive': False
|
|
})
|
|
args[0].node.connectattr('position',
|
|
args[0].fatal,
|
|
'position')
|
|
|
|
update_bomb_visual(args[0])
|
|
|
|
if babase.app.config.get(
|
|
"bombCountdown") and bomb_type not in fuse_bomb:
|
|
color = (1.0, 1.0, 0.0)
|
|
count_bomb(*args, count='3', color=color)
|
|
color = (1.0, 0.5, 0.0)
|
|
bs.timer(1, bs.Call(count_bomb, *args, count='2', color=color))
|
|
color = (1.0, 0.15, 0.15)
|
|
bs.timer(2, bs.Call(count_bomb, *args, count='1', color=color))
|
|
|
|
return setting
|
|
|
|
|
|
def update_bomb_visual(bomb):
|
|
|
|
if babase.app.config.get("bombRadiusVisual"):
|
|
bs.animate_array(bomb.radius_visualizer, 'size', 1, {
|
|
0.0: [0.0],
|
|
0.2: [bomb.blast_radius * 2.2],
|
|
0.25: [bomb.blast_radius * 2.0]
|
|
})
|
|
|
|
bs.animate(
|
|
bomb.radius_visualizer_circle, 'opacity', {
|
|
0: 0.0,
|
|
0.4: 0.1
|
|
})
|
|
if bomb.bomb_type == 'tnt':
|
|
bs.animate_array(bomb.fatal, 'size', 1, {
|
|
0.0: [0.0],
|
|
0.2: [bomb.blast_radius * 2.2 * 0.7],
|
|
0.25: [bomb.blast_radius * 2.0 * 0.7]
|
|
})
|
|
else:
|
|
bs.animate_array(bomb.radius_visualizer, 'size', 1, {
|
|
0.0: [0.0],
|
|
0.2: [0.0],
|
|
})
|
|
|
|
bs.animate(
|
|
bomb.radius_visualizer_circle, 'opacity', {
|
|
0: 0.0,
|
|
0.4: 0.0
|
|
})
|
|
if bomb.bomb_type == 'tnt':
|
|
bs.animate_array(bomb.fatal, 'size', 1, {
|
|
0.0: [0.0],
|
|
0.2: [0.0],
|
|
})
|
|
|
|
bascenev1lib.actor.bomb.Bomb.__init__ = new_bomb_init(
|
|
bascenev1lib.actor.bomb.Bomb.__init__)
|
|
|
|
|
|
def _init_spaz_(func):
|
|
def wrapper(*args, **kwargs):
|
|
func(*args, **kwargs)
|
|
args[0].bot_radius = bs.newnode('locator',
|
|
owner=args[0].node,
|
|
attrs={
|
|
'shape': 'circle',
|
|
'color': (0, 0, 1),
|
|
'opacity': 0.0,
|
|
'draw_beauty': False,
|
|
'additive': False
|
|
})
|
|
args[0].node.connectattr('position',
|
|
args[0].bot_radius,
|
|
'position')
|
|
|
|
args[0].radius_visualizer_circle = bs.newnode(
|
|
'locator',
|
|
owner=args[0].node,
|
|
attrs={
|
|
'shape': 'circleOutline',
|
|
'size': [(args[0].hitpoints_max - args[0].hitpoints) * 0.0048],
|
|
'color': (0, 1, 1),
|
|
'draw_beauty': False,
|
|
'additive': True
|
|
})
|
|
|
|
args[0].node.connectattr('position', args[0].radius_visualizer_circle,
|
|
'position')
|
|
|
|
args[0].curse_visualizer = bs.newnode('locator',
|
|
owner=args[0].node,
|
|
attrs={
|
|
'shape': 'circle',
|
|
'color': (1, 0, 0),
|
|
'size': (0.0, 0.0, 0.0),
|
|
'opacity': 0.05,
|
|
'draw_beauty': False,
|
|
'additive': False
|
|
})
|
|
args[0].node.connectattr('position', args[0].curse_visualizer,
|
|
'position')
|
|
|
|
args[0].curse_visualizer_circle = bs.newnode(
|
|
'locator',
|
|
owner=args[0].node,
|
|
attrs={
|
|
'shape': 'circleOutline',
|
|
'size': [3 * 2.0],
|
|
'color': (
|
|
1, 1, 0),
|
|
'opacity': 0.0,
|
|
'draw_beauty': False,
|
|
'additive': True
|
|
})
|
|
args[0].node.connectattr('position',
|
|
args[0].curse_visualizer_circle,
|
|
'position')
|
|
|
|
args[0].curse_visualizer_fatal = bs.newnode('locator',
|
|
owner=args[0].node,
|
|
attrs={
|
|
'shape': 'circle',
|
|
'color': (
|
|
0.7, 0, 0),
|
|
'size': (0.0, 0.0, 0.0),
|
|
'opacity': 0.10,
|
|
'draw_beauty': False,
|
|
'additive': False
|
|
})
|
|
args[0].node.connectattr('position',
|
|
args[0].curse_visualizer_fatal,
|
|
'position')
|
|
|
|
def invincible() -> None:
|
|
for i in bs.get_foreground_host_activity().players:
|
|
try:
|
|
if i.node:
|
|
if babase.app.config.get("invincible"):
|
|
i.actor.node.invincible = True
|
|
else:
|
|
i.actor.node.invincible = False
|
|
except:
|
|
pass
|
|
|
|
bs.timer(1.001, bs.Call(invincible))
|
|
|
|
return wrapper
|
|
|
|
|
|
Spaz.__init__ = _init_spaz_(Spaz.__init__)
|
|
|
|
|
|
def new_cursed(func):
|
|
def wrapper(*args, **kwargs):
|
|
if args[0].node.invincible:
|
|
return
|
|
func(*args, **kwargs)
|
|
if babase.app.config.get("bombRadiusVisual"):
|
|
bs.animate_array(args[0].curse_visualizer, 'size', 1, {
|
|
0.0: [0.0],
|
|
0.2: [3 * 2.2],
|
|
0.5: [3 * 2.0],
|
|
5.0: [3 * 2.0],
|
|
5.1: [0.0],
|
|
})
|
|
|
|
bs.animate(
|
|
args[0].curse_visualizer_circle, 'opacity', {
|
|
0: 0.0,
|
|
0.4: 0.1,
|
|
5.0: 0.1,
|
|
5.1: 0.0,
|
|
})
|
|
|
|
bs.animate_array(args[0].curse_visualizer_fatal, 'size', 1, {
|
|
0.0: [0.0],
|
|
0.2: [2.2],
|
|
0.5: [2.0],
|
|
5.0: [2.0],
|
|
5.1: [0.0],
|
|
})
|
|
|
|
return wrapper
|
|
|
|
|
|
Spaz.curse = new_cursed(Spaz.curse)
|
|
|
|
|
|
def bot_handlemessage(func):
|
|
def wrapper(*args, **kwargs):
|
|
func(*args, **kwargs)
|
|
if isinstance(args[1], bs.PowerupMessage):
|
|
if args[1].poweruptype == 'health':
|
|
if babase.app.config.get("bombRadiusVisual"):
|
|
update_hit_visual(args[0])
|
|
|
|
if not (babase.app.config.get("powerupsExpire") and
|
|
args[0].powerups_expire):
|
|
if args[1].poweruptype == 'triple_bombs':
|
|
tex = PowerupBoxFactory.get().tex_bomb
|
|
args[0]._flash_billboard(tex)
|
|
args[0].set_bomb_count(3)
|
|
|
|
elif args[1].poweruptype == 'impact_bombs':
|
|
args[0].bomb_type = 'impact'
|
|
tex = args[0]._get_bomb_type_tex()
|
|
args[0]._flash_billboard(tex)
|
|
|
|
elif args[1].poweruptype == 'sticky_bombs':
|
|
args[0].bomb_type = 'sticky'
|
|
tex = args[0]._get_bomb_type_tex()
|
|
args[0]._flash_billboard(tex)
|
|
|
|
elif args[1].poweruptype == 'punch':
|
|
tex = PowerupBoxFactory.get().tex_punch
|
|
args[0]._flash_billboard(tex)
|
|
args[0].equip_boxing_gloves()
|
|
|
|
elif args[1].poweruptype == 'ice_bombs':
|
|
args[0].bomb_type = 'ice'
|
|
tex = args[0]._get_bomb_type_tex()
|
|
args[0]._flash_billboard(tex)
|
|
|
|
if args[1].poweruptype in ['triple_bombs', 'impact_bombs',
|
|
'sticky_bombs', 'punch',
|
|
'ice_bombs']:
|
|
args[0].node.handlemessage('flash')
|
|
if args[1].sourcenode:
|
|
args[1].sourcenode.handlemessage(
|
|
bs.PowerupAcceptMessage())
|
|
return True
|
|
|
|
|
|
|
|
if isinstance(args[1], bs.HitMessage):
|
|
update_hit_visual(args[0])
|
|
|
|
return wrapper
|
|
|
|
|
|
def update_hit_visual(node):
|
|
if node._cursed:
|
|
bs.animate_array(node.curse_visualizer, 'size', 1, {
|
|
0.0: [3 * 2.0],
|
|
0.2: [0.0],
|
|
})
|
|
|
|
bs.animate(
|
|
node.curse_visualizer_circle, 'opacity', {
|
|
0.0: 0.1,
|
|
0.2: 0.0,
|
|
})
|
|
|
|
bs.animate_array(node.curse_visualizer_fatal, 'size',
|
|
1,
|
|
{
|
|
0.0: [2.0],
|
|
0.2: [0.0],
|
|
})
|
|
|
|
if node.hitpoints <= 0:
|
|
bs.animate(node.bot_radius, 'opacity', {
|
|
0.0: 0.00
|
|
})
|
|
bs.animate(
|
|
node.radius_visualizer_circle, 'opacity', {
|
|
0.0: 0.00
|
|
})
|
|
elif babase.app.config.get('bombRadiusVisual'):
|
|
|
|
bs.animate_array(node.bot_radius, 'size', 1, {
|
|
0.0: [(node.hitpoints_max - node.hitpoints) * 0.0045],
|
|
0.25: [(node.hitpoints_max - node.hitpoints) * 0.0045]
|
|
})
|
|
bs.animate(node.bot_radius, 'opacity', {
|
|
0.0: 0.00,
|
|
0.25: 0.05
|
|
})
|
|
|
|
bs.animate_array(node.radius_visualizer_circle, 'size', 1, {
|
|
0.0: [(node.hitpoints_max - node.hitpoints) * 0.0045],
|
|
0.25: [(node.hitpoints_max - node.hitpoints) * 0.0045]
|
|
})
|
|
|
|
bs.animate(
|
|
node.radius_visualizer_circle, 'opacity', {
|
|
0.0: 0.00,
|
|
0.25: 0.1
|
|
})
|
|
|
|
if not babase.app.config.get('bombRadiusVisual'):
|
|
bs.animate_array(node.bot_radius, 'size', 1, {
|
|
0.0: [0],
|
|
0.25: [0]
|
|
})
|
|
bs.animate(node.bot_radius, 'opacity', {
|
|
0.0: 0.00,
|
|
0.25: 0.0
|
|
})
|
|
|
|
bs.animate_array(node.radius_visualizer_circle, 'size',
|
|
1, {
|
|
0.0: [0],
|
|
0.25: [0]
|
|
})
|
|
|
|
bs.animate(
|
|
node.radius_visualizer_circle, 'opacity', {
|
|
0.0: 0.00,
|
|
0.25: 0.0
|
|
})
|
|
|
|
Spaz.handlemessage = bot_handlemessage(Spaz.handlemessage)
|
|
|
|
|
|
def count_bomb(*args, count, color):
|
|
if args[0].node.exists():
|
|
text = bs.newnode('math', owner=args[0].node,
|
|
attrs={'input1': (0, 0.7, 0),
|
|
'operation': 'add'})
|
|
args[0].node.connectattr('position', text, 'input2')
|
|
args[0].spaztext = bs.newnode('text',
|
|
owner=args[0].node,
|
|
attrs={
|
|
'text': count,
|
|
'in_world': True,
|
|
'color': color,
|
|
'shadow': 1.0,
|
|
'flatness': 1.0,
|
|
'scale': 0.012,
|
|
'h_align': 'center',
|
|
})
|
|
|
|
args[0].node.connectattr('position', args[0].spaztext,
|
|
'position')
|
|
bs.animate(args[0].spaztext, 'scale',
|
|
{0: 0, 0.3: 0.03, 0.5: 0.025, 0.8: 0.025, 1.0: 0.0})
|
|
|
|
|
|
def doTestButton(self):
|
|
if isinstance(bs.get_foreground_host_session(), MainMenuSession):
|
|
bui.screenmessage('Join any map to start using it.', color=(.8, .8, .1))
|
|
return
|
|
|
|
activity = bs.get_foreground_host_activity()
|
|
if activity is not None:
|
|
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
|
bs.Call(PracticeWindow())
|
|
else:
|
|
bs.screenmessage('Only works on local games.', color=(.8, .8, .1))
|
|
|
|
|
|
# ---------------------------------------------------------------
|
|
|
|
|
|
class NewBotSet(SpazBotSet):
|
|
|
|
def __init__(self):
|
|
"""Create a bot-set."""
|
|
|
|
# We spread our bots out over a few lists so we can update
|
|
# them in a staggered fashion.
|
|
super().__init__()
|
|
|
|
def start_moving(self) -> None:
|
|
"""Start processing bot AI updates so they start doing their thing."""
|
|
self._bot_update_timer = bui.AppTimer(
|
|
0.05, bs.WeakCall(self._update), repeat=True
|
|
)
|
|
|
|
def _update(self) -> None:
|
|
# Update one of our bot lists each time through.
|
|
# First off, remove no-longer-existing bots from the list.
|
|
try:
|
|
bot_list = self._bot_lists[self._bot_update_list] = ([
|
|
b for b in self._bot_lists[self._bot_update_list] if b
|
|
])
|
|
except Exception:
|
|
bot_list = []
|
|
babase.print_exception('Error updating bot list: ' +
|
|
str(self._bot_lists[
|
|
self._bot_update_list]))
|
|
self._bot_update_list = (self._bot_update_list +
|
|
1) % self._bot_list_count
|
|
|
|
# Update our list of player points for the bots to use.
|
|
player_pts = []
|
|
for player in bs.getactivity().players:
|
|
assert isinstance(player, bs.Player)
|
|
try:
|
|
# TODO: could use abstracted player.position here so we
|
|
# don't have to assume their actor type, but we have no
|
|
# abstracted velocity as of yet.
|
|
if player.is_alive():
|
|
assert isinstance(player.actor, Spaz)
|
|
assert player.actor.node
|
|
player_pts.append(
|
|
(bs.Vec3(player.actor.node.position),
|
|
bs.Vec3(
|
|
player.actor.node.velocity)))
|
|
except Exception:
|
|
babase.print_exception('Error on bot-set _update.')
|
|
|
|
for bot in bot_list:
|
|
if not babase.app.config.get('stopBots'):
|
|
bot.set_player_points(player_pts)
|
|
bot.update_ai()
|
|
|
|
def clear(self) -> None:
|
|
"""Immediately clear out any bots in the set."""
|
|
# Don't do this if the activity is shutting down or dead.
|
|
activity = bs.getactivity(doraise=False)
|
|
if activity is None or activity.expired:
|
|
return
|
|
|
|
for i, bot_list in enumerate(self._bot_lists):
|
|
for bot in bot_list:
|
|
bot.handlemessage(bs.DieMessage(immediate=True))
|
|
self._bot_lists[i] = []
|
|
|
|
def spawn_bot(
|
|
self,
|
|
bot_type: type[SpazBot],
|
|
pos: Sequence[float],
|
|
spawn_time: float = 3.0,
|
|
on_spawn_call: Callable[[SpazBot], Any] | None = None) -> None:
|
|
"""Spawn a bot from this set."""
|
|
|
|
spawner.Spawner(
|
|
pt=pos,
|
|
spawn_time=spawn_time,
|
|
send_spawn_message=False,
|
|
spawn_callback=bs.Call(
|
|
self._spawn_bot, bot_type, pos, on_spawn_call
|
|
),
|
|
)
|
|
self._spawning_count += 1
|
|
|
|
def _spawn_bot(self, bot_type: type[SpazBot], pos: Sequence[float],
|
|
on_spawn_call: Callable[[SpazBot], Any] | None) -> None:
|
|
spaz = bot_type().autoretain()
|
|
bs.getsound('spawn').play(position=pos)
|
|
assert spaz.node
|
|
spaz.node.handlemessage('flash')
|
|
spaz.node.is_area_of_interest = False
|
|
spaz.handlemessage(bs.StandMessage(pos, random.uniform(0, 360)))
|
|
self.add_bot(spaz)
|
|
self._spawning_count -= 1
|
|
if on_spawn_call is not None:
|
|
on_spawn_call(spaz)
|
|
|
|
|
|
class DummyBotSet(NewBotSet):
|
|
|
|
def _update(self) -> None:
|
|
|
|
try:
|
|
# Update one of our bot lists each time through.
|
|
# First off, remove no-longer-existing bots from the list.
|
|
try:
|
|
bot_list = self._bot_lists[self._bot_update_list] = ([
|
|
b for b in self._bot_lists[self._bot_update_list] if b
|
|
])
|
|
except Exception:
|
|
babase.print_exception('Error updating bot list: ' +
|
|
str(self._bot_lists[
|
|
self._bot_update_list]))
|
|
self._bot_update_list = (self._bot_update_list +
|
|
1) % self._bot_list_count
|
|
|
|
except:
|
|
pass
|
|
|
|
|
|
class DummyBot(SpazBot):
|
|
character = 'Bones'
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
if babase.app.config.get('immortalDummy'):
|
|
bs.timer(0.2, self.immortal,
|
|
repeat=True)
|
|
|
|
def immortal(self):
|
|
self.hitpoints = self.hitpoints_max = 10000
|
|
try:
|
|
bs.emitfx(
|
|
position=self.node.position,
|
|
count=20,
|
|
emit_type='fairydust')
|
|
except:
|
|
pass
|
|
|
|
|
|
class NewChargerBotPro(ChargerBotPro):
|
|
default_shields = False
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
|
|
class PracticeTab:
|
|
"""Defines a tab for use in the gather UI."""
|
|
|
|
def __init__(self, window: PracticeWindow) -> None:
|
|
self._window = weakref.ref(window)
|
|
|
|
@property
|
|
def window(self) -> PracticeWindow:
|
|
"""The GatherWindow that this tab belongs to."""
|
|
window = self._window()
|
|
if window is None:
|
|
raise bs.NotFoundError("PracticeTab's window no longer exists.")
|
|
return window
|
|
|
|
def on_activate(
|
|
self,
|
|
parent_widget: bs.Widget,
|
|
tab_button: bs.Widget,
|
|
region_width: float,
|
|
region_height: float,
|
|
scroll_widget: bs.Widget,
|
|
extra_x: float,
|
|
) -> bs.Widget:
|
|
"""Called when the tab becomes the active one.
|
|
|
|
The tab should create and return a container widget covering the
|
|
specified region.
|
|
"""
|
|
raise RuntimeError('Should not get here.')
|
|
|
|
def on_deactivate(self) -> None:
|
|
"""Called when the tab will no longer be the active one."""
|
|
|
|
def save_state(self) -> None:
|
|
"""Called when the parent window is saving state."""
|
|
|
|
def restore_state(self) -> None:
|
|
"""Called when the parent window is restoring state."""
|
|
|
|
|
|
def _check_value_change(setting: int, widget: bs.Widget,
|
|
value: str) -> None:
|
|
bui.textwidget(edit=widget,
|
|
text=bs.Lstr(resource='onText') if value else bs.Lstr(
|
|
resource='offText'))
|
|
|
|
if setting == 0:
|
|
if value:
|
|
babase.app.config["stopBots"] = True
|
|
else:
|
|
babase.app.config["stopBots"] = False
|
|
elif setting == 1:
|
|
if value:
|
|
babase.app.config["immortalDummy"] = True
|
|
else:
|
|
babase.app.config["immortalDummy"] = False
|
|
|
|
|
|
class BotsPracticeTab(PracticeTab):
|
|
"""The about tab in the practice UI"""
|
|
|
|
def __init__(self, window: PracticeWindow
|
|
) -> None:
|
|
|
|
super().__init__(window)
|
|
activity = bs.get_foreground_host_activity()
|
|
with activity.context:
|
|
try:
|
|
if not activity.bot1 or not activity.bot2:
|
|
activity.bot1 = DummyBotSet()
|
|
activity.bot2 = NewBotSet()
|
|
except:
|
|
activity.bot1 = DummyBotSet()
|
|
activity.bot2 = NewBotSet()
|
|
bot_index, count, radius = self.load_settings()
|
|
self._container: bs.Widget | None = None
|
|
self.count = count
|
|
self.radius = radius
|
|
self.radius_array = (['Small', 'Medium', 'Big'])
|
|
self.parent_widget = None
|
|
self.bot1 = activity.bot1
|
|
self.bot2 = activity.bot2
|
|
self.activity = bs.get_foreground_host_activity()
|
|
self.image_array = (
|
|
['bonesIcon', 'neoSpazIcon', 'kronkIcon', 'neoSpazIcon',
|
|
'kronkIcon',
|
|
'zoeIcon', 'ninjaIcon', 'melIcon', 'jackIcon', 'bunnyIcon',
|
|
'neoSpazIcon', 'kronkIcon', 'zoeIcon', 'ninjaIcon',
|
|
'neoSpazIcon', 'kronkIcon', 'zoeIcon', 'ninjaIcon'])
|
|
self.bot_array_name = (
|
|
['Dummy', 'Bomber Lite', 'Brawler Lite', 'Bomber', 'Brawler',
|
|
'Trigger', 'Charger', 'Sticky',
|
|
'Explodey', 'Bouncy', 'Pro Bomber',
|
|
'Pro Brawler', 'Pro Trigger', 'Pro Charger',
|
|
'S.Pro Bomber', 'S.Pro Brawler',
|
|
'S.Pro Trigger', 'S.Pro Charger'])
|
|
|
|
self.setting_name = (['Stop Bots', 'Immortal Dummy'])
|
|
self.config = (['stopBots', 'immortalDummy'])
|
|
|
|
self.bot_array = (
|
|
[DummyBot, BomberBotLite, BrawlerBotLite, SpazBot, BrawlerBot,
|
|
TriggerBot,
|
|
ChargerBot, StickyBot, ExplodeyBot, BouncyBot,
|
|
BomberBotPro, BrawlerBotPro, TriggerBotPro, NewChargerBotPro,
|
|
BomberBotProShielded, BrawlerBotProShielded,
|
|
TriggerBotProShielded, ChargerBotProShielded])
|
|
|
|
self._icon_index = bot_index
|
|
|
|
def on_activate(
|
|
self,
|
|
parent_widget: bs.Widget,
|
|
tab_button: bs.Widget,
|
|
region_width: float,
|
|
region_height: float,
|
|
scroll_widget: bs.Widget,
|
|
extra_x: float,
|
|
) -> bui.Widget:
|
|
|
|
b_size_2 = 100
|
|
spacing_h = -50
|
|
mask_texture = bui.gettexture('characterIconMask')
|
|
spacing_v = 60
|
|
|
|
self.parent_widget = parent_widget
|
|
|
|
self._scroll_width = region_width * 0.8
|
|
self._scroll_height = region_height * 0.6
|
|
self._scroll_position = ((region_width - self._scroll_width) * 0.5,
|
|
(region_height - self._scroll_height) * 0.5)
|
|
|
|
self._sub_width = self._scroll_width
|
|
self._sub_height = 200
|
|
|
|
self.container_h = 600
|
|
bots_height = self.container_h - 50
|
|
|
|
self._subcontainer = bui.containerwidget(
|
|
parent=scroll_widget,
|
|
size=(self._sub_width, self.container_h),
|
|
background=False,
|
|
selection_loops_to_parent=True)
|
|
|
|
bui.textwidget(parent=self._subcontainer,
|
|
position=(self._sub_width * 0.5,
|
|
bots_height),
|
|
size=(0, 0),
|
|
color=(1.0, 1.0, 1.0),
|
|
scale=1.3,
|
|
h_align='center',
|
|
v_align='center',
|
|
text='Spawn Bot',
|
|
maxwidth=200)
|
|
|
|
tint1, tint2, color = self.check_color()
|
|
|
|
self._bot_button = bot = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
autoselect=True,
|
|
position=(self._sub_width * 0.5 - b_size_2 * 0.5,
|
|
bots_height + spacing_h * 3),
|
|
on_activate_call=self._bot_window,
|
|
size=(b_size_2, b_size_2),
|
|
label='',
|
|
color=color,
|
|
tint_texture=(bui.gettexture(
|
|
self.image_array[self._icon_index] + 'ColorMask')),
|
|
tint_color=tint1,
|
|
tint2_color=tint2,
|
|
texture=bui.gettexture(self.image_array[self._icon_index]),
|
|
mask_texture=mask_texture)
|
|
|
|
bui.textwidget(
|
|
parent=self._subcontainer,
|
|
h_align='center',
|
|
v_align='center',
|
|
position=(self._sub_width * 0.5,
|
|
bots_height + spacing_h * 4 + 10),
|
|
size=(0, 0),
|
|
draw_controller=bot,
|
|
text='Bot Type',
|
|
scale=1.0,
|
|
color=bui.app.ui_v1.title_color,
|
|
maxwidth=130)
|
|
|
|
bui.textwidget(parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.005,
|
|
bots_height
|
|
+ spacing_h * 7),
|
|
size=(100, 30),
|
|
text='Count',
|
|
h_align='left',
|
|
color=(0.8, 0.8, 0.8),
|
|
v_align='center',
|
|
maxwidth=200)
|
|
self.count_text = txt = bui.textwidget(parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.85 - spacing_v * 2,
|
|
bots_height
|
|
+ spacing_h * 7),
|
|
size=(0, 28),
|
|
text=str(self.count),
|
|
editable=False,
|
|
color=(0.6, 1.0, 0.6),
|
|
maxwidth=150,
|
|
h_align='center',
|
|
v_align='center',
|
|
padding=2)
|
|
self.button_bot_left = btn1 = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.85 - spacing_v - 14,
|
|
bots_height
|
|
+ spacing_h * 7),
|
|
size=(28, 28),
|
|
label='-',
|
|
autoselect=True,
|
|
on_activate_call=self.decrease_count,
|
|
repeat=True)
|
|
self.button_bot_right = btn2 = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.85 - 14,
|
|
bots_height
|
|
+ spacing_h * 7),
|
|
size=(28, 28),
|
|
label='+',
|
|
autoselect=True,
|
|
on_activate_call=self.increase_count,
|
|
repeat=True)
|
|
|
|
bui.textwidget(parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.005,
|
|
bots_height
|
|
+ spacing_h * 8),
|
|
size=(100, 30),
|
|
text='Spawn Radius',
|
|
h_align='left',
|
|
color=(0.8, 0.8, 0.8),
|
|
v_align='center',
|
|
maxwidth=200)
|
|
|
|
self.radius_text = txt = bui.textwidget(parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.85 - spacing_v * 2,
|
|
bots_height
|
|
+ spacing_h * 8),
|
|
size=(0, 28),
|
|
text=self.radius_array[
|
|
self.radius],
|
|
editable=False,
|
|
color=(0.6, 1.0, 0.6),
|
|
maxwidth=50,
|
|
h_align='center',
|
|
v_align='center',
|
|
padding=2)
|
|
self.button_bot_left = btn1 = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.85 - spacing_v - 14,
|
|
bots_height
|
|
+ spacing_h * 8),
|
|
size=(28, 28),
|
|
label='-',
|
|
autoselect=True,
|
|
on_activate_call=self.decrease_radius,
|
|
repeat=True)
|
|
self.button_bot_right = btn2 = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.85 - 14,
|
|
bots_height
|
|
+ spacing_h * 8),
|
|
size=(28, 28),
|
|
label='+',
|
|
autoselect=True,
|
|
on_activate_call=self.increase_radius,
|
|
repeat=True)
|
|
|
|
self.button = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.25 - 40,
|
|
bots_height
|
|
+ spacing_h * 6),
|
|
size=(80, 50),
|
|
autoselect=True,
|
|
button_type='square',
|
|
label='Spawn',
|
|
on_activate_call=self.do_spawn_bot)
|
|
|
|
self.button = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.75 - 40,
|
|
bots_height
|
|
+ spacing_h * 6),
|
|
size=(80, 50),
|
|
autoselect=True,
|
|
button_type='square',
|
|
color=(1, 0.2, 0.2),
|
|
label='Clear',
|
|
on_activate_call=self.clear_bot)
|
|
|
|
i = 0
|
|
for name in self.setting_name:
|
|
bui.textwidget(parent=self._subcontainer,
|
|
position=(self._sub_width * 0.005,
|
|
bots_height + spacing_h * (9 + i)),
|
|
size=(100, 30),
|
|
text=name,
|
|
h_align='left',
|
|
color=(0.8, 0.8, 0.8),
|
|
v_align='center',
|
|
maxwidth=200)
|
|
value = babase.app.config.get(self.config[i])
|
|
txt2 = bui.textwidget(
|
|
parent=self._subcontainer,
|
|
position=(self._sub_width * 0.8 - spacing_v / 2,
|
|
bots_height +
|
|
spacing_h * (9 + i)),
|
|
size=(0, 28),
|
|
text=bs.Lstr(resource='onText') if value else bs.Lstr(
|
|
resource='offText'),
|
|
editable=False,
|
|
color=(0.6, 1.0, 0.6),
|
|
maxwidth=50,
|
|
h_align='right',
|
|
v_align='center',
|
|
padding=2)
|
|
bui.checkboxwidget(parent=self._subcontainer,
|
|
text='',
|
|
position=(self._sub_width * 0.8 - 15,
|
|
bots_height +
|
|
spacing_h * (9 + i)),
|
|
size=(30, 30),
|
|
autoselect=False,
|
|
textcolor=(0.8, 0.8, 0.8),
|
|
value=value,
|
|
on_value_change_call=bs.Call(
|
|
_check_value_change,
|
|
i, txt2))
|
|
i += 1
|
|
|
|
return self._subcontainer
|
|
|
|
def _bot_window(self) -> None:
|
|
BotPicker(
|
|
parent=self.parent_widget,
|
|
delegate=self)
|
|
|
|
def increase_count(self):
|
|
if self.count < 10:
|
|
self.count += 1
|
|
|
|
bui.textwidget(edit=self.count_text,
|
|
text=str(self.count))
|
|
self.save_settings()
|
|
|
|
def decrease_count(self):
|
|
if self.count > 1:
|
|
self.count -= 1
|
|
|
|
bui.textwidget(edit=self.count_text,
|
|
text=str(self.count))
|
|
self.save_settings()
|
|
|
|
def increase_radius(self):
|
|
if self.radius < 2:
|
|
self.radius += 1
|
|
|
|
bui.textwidget(edit=self.radius_text,
|
|
text=self.radius_array[self.radius])
|
|
self.save_settings()
|
|
|
|
def decrease_radius(self):
|
|
if self.radius > 0:
|
|
self.radius -= 1
|
|
|
|
bui.textwidget(edit=self.radius_text,
|
|
text=self.radius_array[self.radius])
|
|
self.save_settings()
|
|
|
|
def clear_bot(self):
|
|
bs.screenmessage('Cleared', color=(1, 0.1, 0.1))
|
|
activity = bs.get_foreground_host_activity()
|
|
with activity.context:
|
|
self.bot1.clear()
|
|
self.bot2.clear()
|
|
|
|
def do_spawn_bot(self, clid: int = -1) -> None:
|
|
bs.screenmessage('Spawned', color=(0.2, 1, 0.2))
|
|
activity = bs.get_foreground_host_activity()
|
|
with activity.context:
|
|
for i in bs.get_foreground_host_activity().players:
|
|
if i.sessionplayer.inputdevice.client_id == clid:
|
|
if i.node:
|
|
bot_type = self._icon_index
|
|
for a in range(self.count):
|
|
x = (random.randrange
|
|
(-10, 10) / 10) * math.pow(self.radius + 1, 2)
|
|
z = (random.randrange
|
|
(-10, 10) / 10) * math.pow(self.radius + 1, 2)
|
|
pos = (i.node.position[0] + x,
|
|
i.node.position[1],
|
|
i.node.position[2] + z)
|
|
if bot_type == 0:
|
|
self.bot1.spawn_bot(self.bot_array[0],
|
|
pos=pos,
|
|
spawn_time=1.0)
|
|
else:
|
|
self.bot2.spawn_bot(self.bot_array[bot_type],
|
|
pos=pos,
|
|
spawn_time=1.0)
|
|
break
|
|
|
|
def on_bots_picker_pick(self, character: str) -> None:
|
|
"""A bots has been selected by the picker."""
|
|
if not self.parent_widget:
|
|
return
|
|
|
|
# The player could have bought a new one while the picker was u
|
|
self._icon_index = self.bot_array_name.index(
|
|
character) if character in self.bot_array_name else 0
|
|
self._update_character()
|
|
|
|
def _update_character(self, change: int = 0) -> None:
|
|
if self._bot_button:
|
|
tint1, tint2, color = self.check_color()
|
|
|
|
bui.buttonwidget(
|
|
edit=self._bot_button,
|
|
texture=bui.gettexture(self.image_array[self._icon_index]),
|
|
tint_texture=(bui.gettexture(
|
|
self.image_array[self._icon_index] + 'ColorMask')),
|
|
color=color,
|
|
tint_color=tint1,
|
|
tint2_color=tint2)
|
|
self.save_settings()
|
|
|
|
def load_settings(self):
|
|
try:
|
|
if babase.app.config.get("botsSpawnSetting") is None:
|
|
babase.app.config["botsSpawnSetting"] = (0, 1, 0)
|
|
bot_index, count, radius = babase.app.config.get(
|
|
"botsSpawnSetting")
|
|
else:
|
|
bot_index, count, radius = babase.app.config.get(
|
|
"botsSpawnSetting")
|
|
except:
|
|
babase.app.config["botsSpawnSetting"] = (0, 1, 0)
|
|
bot_index, count, radius = babase.app.config.get("botsSpawnSetting")
|
|
values = bot_index, count, radius
|
|
return values
|
|
|
|
def save_settings(self):
|
|
babase.app.config["botsSpawnSetting"] = (self._icon_index, self.count,
|
|
self.radius)
|
|
babase.app.config.commit()
|
|
|
|
def check_color(self):
|
|
if self.bot_array_name[self._icon_index] in (
|
|
'Pro Bomber', 'Pro Brawler',
|
|
'Pro Trigger', 'Pro Charger',
|
|
'S.Pro Bomber', 'S.Pro Brawler',
|
|
'S.Pro Trigger', 'S.Pro Charger'):
|
|
tint1 = (1.0, 0.2, 0.1)
|
|
tint2 = (0.6, 0.1, 0.05)
|
|
elif self.bot_array_name[self._icon_index] in 'Bouncy':
|
|
tint1 = (1, 1, 1)
|
|
tint2 = (1.0, 0.5, 0.5)
|
|
elif self.bot_array_name[self._icon_index] in ('Brawler Lite',
|
|
'Bomber Lite'):
|
|
tint1 = (1.2, 0.9, 0.2)
|
|
tint2 = (1.0, 0.5, 0.6)
|
|
else:
|
|
tint1 = (0.6, 0.6, 0.6)
|
|
tint2 = (0.1, 0.3, 0.1)
|
|
|
|
if self.bot_array_name[self._icon_index] in (
|
|
'S.Pro Bomber', 'S.Pro Brawler',
|
|
'S.Pro Trigger', 'S.Pro Charger'):
|
|
color = (1.3, 1.2, 3.0)
|
|
else:
|
|
color = (1.0, 1.0, 1.0)
|
|
|
|
colors = tint1, tint2, color
|
|
return colors
|
|
|
|
|
|
class PowerUpPracticeTab(PracticeTab):
|
|
"""The about tab in the practice UI"""
|
|
|
|
def __init__(self, window: PracticeWindow) -> None:
|
|
super().__init__(window)
|
|
self._container: bs.Widget | None = None
|
|
self.count = 1
|
|
self.parent_widget = None
|
|
self.activity = bs.get_foreground_host_activity()
|
|
|
|
self.power_list = (['Bomb', 'Curse', 'Health', 'IceBombs',
|
|
'ImpactBombs', 'LandMines', 'Punch',
|
|
'Shield', 'StickyBombs'])
|
|
|
|
self.power_list_type_name = (
|
|
['Tripple Bombs', 'Curse', 'Health', 'Ice Bombs',
|
|
'Impact Bombs', 'Land Mines', 'Punch',
|
|
'Shield', 'Sticky Bombs'])
|
|
|
|
self.power_list_type = (
|
|
['triple_bombs', 'curse', 'health', 'ice_bombs',
|
|
'impact_bombs', 'land_mines', 'punch',
|
|
'shield', 'sticky_bombs'])
|
|
self._icon_index = self.load_settings()
|
|
|
|
self.setting_name = (
|
|
['Bomb Countdown', 'Bomb Radius Visualizer', 'Powerups Expire'])
|
|
self.config = (['bombCountdown', 'bombRadiusVisual', 'powerupsExpire'])
|
|
|
|
def on_activate(
|
|
self,
|
|
parent_widget: bs.Widget,
|
|
tab_button: bs.Widget,
|
|
region_width: float,
|
|
region_height: float,
|
|
scroll_widget: bs.Widget,
|
|
extra_x: float,
|
|
) -> bs.Widget:
|
|
|
|
b_size_2 = 100
|
|
spacing_h = -50
|
|
spacing_v = 60
|
|
|
|
self.parent_widget = parent_widget
|
|
|
|
self._scroll_width = region_width * 0.8
|
|
self._scroll_height = region_height * 0.6
|
|
self._scroll_position = ((region_width - self._scroll_width) * 0.5,
|
|
(region_height - self._scroll_height) * 0.5)
|
|
|
|
self._sub_width = self._scroll_width
|
|
self._sub_height = 200
|
|
|
|
self.container_h = 550
|
|
power_height = self.container_h - 50
|
|
|
|
self._subcontainer = bui.containerwidget(
|
|
parent=scroll_widget,
|
|
size=(self._sub_width, self.container_h),
|
|
background=False,
|
|
selection_loops_to_parent=True)
|
|
|
|
bui.textwidget(parent=self._subcontainer,
|
|
position=(self._sub_width * 0.5,
|
|
power_height),
|
|
size=(0, 0),
|
|
color=(1.0, 1.0, 1.0),
|
|
scale=1.3,
|
|
h_align='center',
|
|
v_align='center',
|
|
text='Spawn Power Up',
|
|
maxwidth=200)
|
|
|
|
self._power_button = bot = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
autoselect=True,
|
|
position=(self._sub_width * 0.5 - b_size_2 * 0.5,
|
|
power_height + spacing_h * 3),
|
|
on_activate_call=self._power_window,
|
|
size=(b_size_2, b_size_2),
|
|
label='',
|
|
color=(1, 1, 1),
|
|
texture=bui.gettexture('powerup' +
|
|
self.power_list[
|
|
self._icon_index]))
|
|
|
|
bui.textwidget(
|
|
parent=self._subcontainer,
|
|
h_align='center',
|
|
v_align='center',
|
|
position=(self._sub_width * 0.5,
|
|
power_height + spacing_h * 4 + 10),
|
|
size=(0, 0),
|
|
draw_controller=bot,
|
|
text='Power Up Type',
|
|
scale=1.0,
|
|
color=bui.app.ui_v1.title_color,
|
|
maxwidth=300)
|
|
|
|
self.button = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.25 - 40,
|
|
power_height
|
|
+ spacing_h * 6),
|
|
size=(80, 50),
|
|
autoselect=True,
|
|
button_type='square',
|
|
label='Spawn',
|
|
on_activate_call=self.get_powerup)
|
|
|
|
self.button = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
position=(
|
|
self._sub_width * 0.75 - 40,
|
|
power_height
|
|
+ spacing_h * 6),
|
|
size=(80, 50),
|
|
autoselect=True,
|
|
button_type='square',
|
|
color=(1, 0.2, 0.2),
|
|
label='Debuff',
|
|
on_activate_call=self.debuff)
|
|
|
|
i = 0
|
|
for name in self.setting_name:
|
|
bui.textwidget(parent=self._subcontainer,
|
|
position=(self._sub_width * 0.005,
|
|
power_height + spacing_h * (7 + i)),
|
|
size=(100, 30),
|
|
text=name,
|
|
h_align='left',
|
|
color=(0.8, 0.8, 0.8),
|
|
v_align='center',
|
|
maxwidth=200)
|
|
value = babase.app.config.get(self.config[i])
|
|
txt2 = bui.textwidget(
|
|
parent=self._subcontainer,
|
|
position=(self._sub_width * 0.8 - spacing_v / 2,
|
|
power_height +
|
|
spacing_h * (7 + i)),
|
|
size=(0, 28),
|
|
text=bs.Lstr(resource='onText') if value else bs.Lstr(
|
|
resource='offText'),
|
|
editable=False,
|
|
color=(0.6, 1.0, 0.6),
|
|
maxwidth=400,
|
|
h_align='right',
|
|
v_align='center',
|
|
padding=2)
|
|
bui.checkboxwidget(parent=self._subcontainer,
|
|
text='',
|
|
position=(self._sub_width * 0.8 - 15,
|
|
power_height +
|
|
spacing_h * (7 + i)),
|
|
size=(30, 30),
|
|
autoselect=False,
|
|
textcolor=(0.8, 0.8, 0.8),
|
|
value=value,
|
|
on_value_change_call=bs.Call(
|
|
self._check_value_change,
|
|
i, txt2))
|
|
i += 1
|
|
|
|
return self._subcontainer
|
|
|
|
def debuff(self):
|
|
bs.screenmessage('Debuffed', color=(1, 0.1, 0.1))
|
|
activity = bs.get_foreground_host_activity()
|
|
with activity.context:
|
|
for i in activity.players:
|
|
Spaz._gloves_wear_off(i.actor)
|
|
Spaz._multi_bomb_wear_off(i.actor)
|
|
Spaz._bomb_wear_off(i.actor)
|
|
i.actor.node.mini_billboard_1_end_time = 0
|
|
i.actor.node.mini_billboard_2_end_time = 0
|
|
i.actor.node.mini_billboard_3_end_time = 0
|
|
i.actor._multi_bomb_wear_off_flash_timer = None
|
|
i.actor._boxing_gloves_wear_off_flash_timer = None
|
|
i.actor._bomb_wear_off_flash_timer = None
|
|
Spaz.set_land_mine_count(i.actor, min(0, 0))
|
|
i.actor.shield_hitpoints = 1
|
|
|
|
def get_powerup(self, clid: int = -1) -> None:
|
|
bs.screenmessage('Spawned', color=(0.2, 1, 0.2))
|
|
activity = bs.get_foreground_host_activity()
|
|
with activity.context:
|
|
for i in activity.players:
|
|
if i.sessionplayer.inputdevice.client_id == clid:
|
|
if i.node:
|
|
x = (random.choice([-7, 7]) / 10)
|
|
z = (random.choice([-7, 7]) / 10)
|
|
pos = (i.node.position[0] + x,
|
|
i.node.position[1],
|
|
i.node.position[2] + z)
|
|
PowerupBox(position=pos,
|
|
poweruptype=self.power_list_type
|
|
[self._icon_index]).autoretain()
|
|
|
|
def _power_window(self) -> None:
|
|
PowerPicker(
|
|
parent=self.parent_widget,
|
|
delegate=self)
|
|
|
|
def on_power_picker_pick(self, power: str) -> None:
|
|
"""A power up has been selected by the picker."""
|
|
if not self.parent_widget:
|
|
return
|
|
|
|
# The player could have bought a new one while the picker was u
|
|
self._icon_index = self.power_list.index(
|
|
power) if power in self.power_list else 0
|
|
self._update_power()
|
|
|
|
def _update_power(self, change: int = 0) -> None:
|
|
if self._power_button:
|
|
bui.buttonwidget(
|
|
edit=self._power_button,
|
|
texture=(bui.gettexture('powerup' +
|
|
self.power_list[
|
|
self._icon_index])))
|
|
self.save_settings()
|
|
|
|
def _check_value_change(self, setting: int, widget: bs.Widget,
|
|
value: str) -> None:
|
|
bui.textwidget(edit=widget,
|
|
text=bs.Lstr(resource='onText') if value else bs.Lstr(
|
|
resource='offText'))
|
|
|
|
activity = bs.get_foreground_host_activity()
|
|
with activity.context:
|
|
if setting == 0:
|
|
if value:
|
|
babase.app.config["bombCountdown"] = True
|
|
else:
|
|
babase.app.config["bombCountdown"] = False
|
|
elif setting == 1:
|
|
if value:
|
|
babase.app.config["bombRadiusVisual"] = True
|
|
else:
|
|
babase.app.config["bombRadiusVisual"] = False
|
|
activity = bs.get_foreground_host_activity()
|
|
# Iterate through all nodes in the activity's scene
|
|
for i in self.get_all_bombs():
|
|
update_bomb_visual(i)
|
|
for i in activity.players:
|
|
update_hit_visual(i.actor)
|
|
|
|
elif setting == 2:
|
|
if value:
|
|
babase.app.config["powerupsExpire"] = True
|
|
else:
|
|
babase.app.config["powerupsExpire"] = False
|
|
|
|
# def get_all_bombs(self) -> list[bs.Node]:
|
|
# """Returns all bomb nodes in the current activity."""
|
|
# return [_.getdelegate(object) for _ in bs.getnodes() if _.getnodetype() in ('bomb','prop')]
|
|
|
|
import bascenev1 as bs
|
|
|
|
def get_all_bombs(self) -> list:
|
|
"""Return all bomb actors (including TNT) but exclude powerups."""
|
|
bombs = []
|
|
|
|
for node in bs.getnodes():
|
|
if not node.exists():
|
|
continue
|
|
# if node.getnodetype() in ('bomb', 'prop'):
|
|
# Try to resolve this node as a Bomb delegate
|
|
delegate = node.getdelegate(Bomb)
|
|
if delegate is not None:
|
|
bombs.append(delegate)
|
|
return bombs
|
|
|
|
def load_settings(self):
|
|
try:
|
|
if babase.app.config.get("powerSpawnSetting") is None:
|
|
babase.app.config["powerSpawnSetting"] = 0
|
|
power_index = babase.app.config.get("powerSpawnSetting")
|
|
else:
|
|
power_index = babase.app.config.get(
|
|
"powerSpawnSetting")
|
|
except:
|
|
babase.app.config["powerSpawnSetting"] = 0
|
|
power_index = babase.app.config.get("powerSpawnSetting")
|
|
values = power_index
|
|
return values
|
|
|
|
def save_settings(self):
|
|
babase.app.config["powerSpawnSetting"] = self._icon_index
|
|
babase.app.config.commit()
|
|
|
|
|
|
class OthersPracticeTab(PracticeTab):
|
|
"""The about tab in the practice UI"""
|
|
|
|
def __init__(self, window: PracticeWindow) -> None:
|
|
super().__init__(window)
|
|
self._container: bs.Widget | None = None
|
|
self.count = 1
|
|
self.parent_widget = None
|
|
self.activity = bs.get_foreground_host_activity()
|
|
self.setting_name = (['Pause On Window', 'Invincible', 'Epic Mode'])
|
|
self.config = (['pause', 'invincible'])
|
|
|
|
def on_activate(
|
|
self,
|
|
parent_widget: bs.Widget,
|
|
tab_button: bs.Widget,
|
|
region_width: float,
|
|
region_height: float,
|
|
scroll_widget: bs.Widget,
|
|
extra_x: float,
|
|
) -> bs.Widget:
|
|
spacing_v = 60
|
|
spacing_h = -50
|
|
|
|
self.parent_widget = parent_widget
|
|
|
|
self._scroll_width = region_width * 0.8
|
|
self._scroll_height = region_height * 0.6
|
|
self._scroll_position = ((region_width - self._scroll_width) * 0.5,
|
|
(region_height - self._scroll_height) * 0.5)
|
|
|
|
self._sub_width = self._scroll_width
|
|
|
|
self.container_h = 300
|
|
other_height = self.container_h - 50
|
|
|
|
self._subcontainer = bui.containerwidget(
|
|
parent=scroll_widget,
|
|
size=(self._sub_width, self.container_h),
|
|
background=False,
|
|
selection_loops_to_parent=True)
|
|
|
|
bui.textwidget(parent=self._subcontainer,
|
|
position=(self._sub_width * 0.5,
|
|
other_height),
|
|
size=(0, 0),
|
|
color=(1.0, 1.0, 1.0),
|
|
scale=1.3,
|
|
h_align='center',
|
|
v_align='center',
|
|
text='Others',
|
|
maxwidth=200)
|
|
|
|
i = 0
|
|
for name in self.setting_name:
|
|
bui.textwidget(parent=self._subcontainer,
|
|
position=(self._sub_width * 0.005,
|
|
other_height + spacing_h * (2 + i)),
|
|
size=(100, 30),
|
|
text=name,
|
|
h_align='left',
|
|
color=(0.8, 0.8, 0.8),
|
|
v_align='center',
|
|
maxwidth=200)
|
|
if name == 'Epic Mode':
|
|
activity = bs.get_foreground_host_activity()
|
|
value = activity.globalsnode.slow_motion
|
|
else:
|
|
value = babase.app.config.get(self.config[i])
|
|
txt2 = bui.textwidget(
|
|
parent=self._subcontainer,
|
|
position=(self._sub_width * 0.8 - spacing_v / 2,
|
|
other_height +
|
|
spacing_h * (2 + i)),
|
|
size=(0, 28),
|
|
text=bs.Lstr(resource='onText') if value else bs.Lstr(
|
|
resource='offText'),
|
|
editable=False,
|
|
color=(0.6, 1.0, 0.6),
|
|
maxwidth=400,
|
|
h_align='right',
|
|
v_align='center',
|
|
padding=2)
|
|
bui.checkboxwidget(parent=self._subcontainer,
|
|
text='',
|
|
position=(self._sub_width * 0.8 - 15,
|
|
other_height +
|
|
spacing_h * (2 + i)),
|
|
size=(30, 30),
|
|
autoselect=False,
|
|
textcolor=(0.8, 0.8, 0.8),
|
|
value=value,
|
|
on_value_change_call=bs.Call(
|
|
self._check_value_change,
|
|
i, txt2))
|
|
i += 1
|
|
|
|
return self._subcontainer
|
|
|
|
def _check_value_change(self, setting: int, widget: bs.Widget,
|
|
value: str) -> None:
|
|
bui.textwidget(edit=widget,
|
|
text=bs.Lstr(resource='onText') if value else bs.Lstr(
|
|
resource='offText'))
|
|
|
|
activity = bs.get_foreground_host_activity()
|
|
with activity.context:
|
|
if setting == 0:
|
|
if value:
|
|
babase.app.config["pause"] = True
|
|
self.activity.globalsnode.paused = True
|
|
else:
|
|
babase.app.config["pause"] = False
|
|
self.activity.globalsnode.paused = False
|
|
elif setting == 1:
|
|
if value:
|
|
babase.app.config["invincible"] = True
|
|
else:
|
|
babase.app.config["invincible"] = False
|
|
for i in bs.get_foreground_host_activity().players:
|
|
try:
|
|
if i.node:
|
|
if babase.app.config.get("invincible"):
|
|
i.actor.node.invincible = True
|
|
else:
|
|
i.actor.node.invincible = False
|
|
except:
|
|
pass
|
|
elif setting == 2:
|
|
activity = bs.get_foreground_host_activity()
|
|
if value:
|
|
activity.globalsnode.slow_motion = True
|
|
else:
|
|
activity.globalsnode.slow_motion = False
|
|
|
|
|
|
class PracticeWindow(bui.Window):
|
|
class TabID(Enum):
|
|
"""Our available tab types."""
|
|
|
|
BOTS = 'bots'
|
|
POWERUP = 'power up'
|
|
OTHERS = 'others'
|
|
|
|
def __del__(self):
|
|
bui.set_party_window_open(True)
|
|
self.activity.globalsnode.paused = False
|
|
|
|
def __init__(self,
|
|
transition: Optional[str] = 'in_right'):
|
|
|
|
self.activity = bs.get_foreground_host_activity()
|
|
bui.set_party_window_open(False)
|
|
if babase.app.config.get("pause"):
|
|
self.activity.globalsnode.paused = True
|
|
uiscale = bui.app.ui_v1.uiscale
|
|
self.pick = 0
|
|
self._width = 500
|
|
|
|
self._height = (578 if uiscale is babase.UIScale.SMALL else
|
|
670 if uiscale is babase.UIScale.MEDIUM else 800)
|
|
extra_x = 100 if uiscale is babase.UIScale.SMALL else 0
|
|
self.extra_x = extra_x
|
|
|
|
self._transitioning_out = False
|
|
|
|
b_size_2 = 100
|
|
|
|
spacing_h = -50
|
|
spacing = -450
|
|
spacing_v = 60
|
|
self.container_h = 500
|
|
v = self._height - 115.0
|
|
v -= spacing_v * 3.0
|
|
|
|
super().__init__(root_widget=bui.containerwidget(
|
|
size=(self._width, self._height),
|
|
transition=transition,
|
|
scale=(1.3 if uiscale is babase.UIScale.SMALL else
|
|
0.97 if uiscale is babase.UIScale.MEDIUM else 0.8),
|
|
stack_offset=(0, -10) if uiscale is babase.UIScale.SMALL else (
|
|
240, 0) if uiscale is babase.UIScale.MEDIUM else (330, 20)))
|
|
|
|
self._sub_height = 200
|
|
|
|
self._scroll_width = self._width * 0.8
|
|
self._scroll_height = self._height * 0.6
|
|
self._scroll_position = ((self._width - self._scroll_width) * 0.5,
|
|
(self._height - self._scroll_height) * 0.5)
|
|
|
|
self._scrollwidget = bui.scrollwidget(parent=self._root_widget,
|
|
size=(self._scroll_width,
|
|
self._scroll_height),
|
|
color=(0.55, 0.55, 0.55),
|
|
highlight=False,
|
|
position=self._scroll_position)
|
|
|
|
bui.containerwidget(edit=self._scrollwidget,
|
|
claims_left_right=True)
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
x_offs = 100 if uiscale is babase.UIScale.SMALL else 0
|
|
|
|
self._current_tab: PracticeWindow.TabID | None = None
|
|
extra_top = 20 if uiscale is babase.UIScale.SMALL else 0
|
|
self._r = 'gatherWindow'
|
|
|
|
tabdefs: list[tuple[PracticeWindow.TabID, str]] = [
|
|
(self.TabID.BOTS, 'Bots')
|
|
]
|
|
if bui.app.plus.get_v1_account_misc_read_val(
|
|
'enablePublicParties', True
|
|
):
|
|
tabdefs.append(
|
|
(
|
|
self.TabID.POWERUP,
|
|
'Power Ups')
|
|
)
|
|
tabdefs.append(
|
|
(self.TabID.OTHERS, 'Others')
|
|
)
|
|
|
|
condensed = uiscale is not babase.UIScale.LARGE
|
|
t_offs_y = (
|
|
0 if not condensed else 25 if uiscale is babase.UIScale.MEDIUM else 17
|
|
)
|
|
|
|
tab_buffer_h = (320 if condensed else 250) + 2 * x_offs
|
|
|
|
self._sub_width = self._width * 0.8
|
|
|
|
# On small UI, push our tabs up closer to the top of the screen to
|
|
# save a bit of space.
|
|
tabs_top_extra = 42 if condensed else 0
|
|
self._tab_row = TabRow(
|
|
self._root_widget,
|
|
tabdefs,
|
|
pos=(
|
|
self._width * 0.5 - self._sub_width * 0.5,
|
|
self._height * 0.79),
|
|
size=(self._sub_width, 50),
|
|
on_select_call=bui.WeakCall(self._set_tab),
|
|
)
|
|
|
|
# Now instantiate handlers for these tabs.
|
|
tabtypes: dict[PracticeWindow.TabID, type[PracticeTab]] = {
|
|
self.TabID.BOTS: BotsPracticeTab,
|
|
self.TabID.POWERUP: PowerUpPracticeTab,
|
|
self.TabID.OTHERS: OthersPracticeTab,
|
|
}
|
|
self._tabs: dict[PracticeWindow.TabID, PracticeTab] = {}
|
|
for tab_id in self._tab_row.tabs:
|
|
tabtype = tabtypes.get(tab_id)
|
|
if tabtype is not None:
|
|
self._tabs[tab_id] = tabtype(self)
|
|
|
|
bui.widget(
|
|
edit=self._tab_row.tabs[tabdefs[-1][0]].button,
|
|
right_widget=bui.get_special_widget('back_button'),
|
|
)
|
|
if uiscale is babase.UIScale.SMALL:
|
|
bui.widget(
|
|
edit=self._tab_row.tabs[tabdefs[0][0]].button,
|
|
left_widget=bui.get_special_widget('back_button'),
|
|
)
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
self.back_button = btn = bui.buttonwidget(
|
|
parent=self._root_widget,
|
|
autoselect=True,
|
|
position=(self._width * 0.15 - 30,
|
|
self._height * 0.95 - 30),
|
|
size=(60, 60),
|
|
scale=1.1,
|
|
label=bui.charstr(bui.SpecialChar.BACK),
|
|
button_type='backSmall',
|
|
on_activate_call=self.close)
|
|
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
|
|
|
bui.textwidget(parent=self._root_widget,
|
|
position=(self._width * 0.5,
|
|
self._height * 0.95),
|
|
size=(0, 0),
|
|
color=bui.app.ui_v1.title_color,
|
|
scale=1.5,
|
|
h_align='center',
|
|
v_align='center',
|
|
text='Practice Tools',
|
|
maxwidth=400)
|
|
|
|
self.info_button = bui.buttonwidget(
|
|
parent=self._root_widget,
|
|
autoselect=True,
|
|
position=(self._width * 0.8 - 30,
|
|
self._height * 0.15 - 30),
|
|
on_activate_call=self._info_window,
|
|
size=(60, 60),
|
|
label='')
|
|
|
|
bui.imagewidget(
|
|
parent=self._root_widget,
|
|
position=(self._width * 0.8 - 25,
|
|
self._height * 0.15 - 25),
|
|
size=(50, 50),
|
|
draw_controller=self.info_button,
|
|
texture=bui.gettexture('achievementEmpty'),
|
|
color=(1.0, 1.0, 1.0))
|
|
|
|
self._tab_container: bui.Widget | None = None
|
|
|
|
self._restore_state()
|
|
|
|
# # -------------------------------------------------------
|
|
|
|
def _info_window(self):
|
|
InfoWindow(
|
|
parent=self._root_widget)
|
|
|
|
def _button(self) -> None:
|
|
bui.buttonwidget(edit=None,
|
|
color=(0.2, 0.4, 0.8))
|
|
|
|
def close(self) -> None:
|
|
"""Close the window."""
|
|
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
|
|
|
def _set_tab(self, tab_id: TabID) -> None:
|
|
if self._current_tab is tab_id:
|
|
return
|
|
prev_tab_id = self._current_tab
|
|
self._current_tab = tab_id
|
|
|
|
# We wanna preserve our current tab between runs.
|
|
cfg = babase.app.config
|
|
cfg['Practice Tab'] = tab_id.value
|
|
cfg.commit()
|
|
|
|
# Update tab colors based on which is selected.
|
|
self._tab_row.update_appearance(tab_id)
|
|
|
|
if prev_tab_id is not None:
|
|
prev_tab = self._tabs.get(prev_tab_id)
|
|
if prev_tab is not None:
|
|
prev_tab.on_deactivate()
|
|
|
|
# Clear up prev container if it hasn't been done.
|
|
if self._tab_container:
|
|
self._tab_container.delete()
|
|
|
|
tab = self._tabs.get(tab_id)
|
|
if tab is not None:
|
|
self._tab_container = tab.on_activate(
|
|
self._root_widget,
|
|
self._tab_row.tabs[tab_id].button,
|
|
self._width,
|
|
self._height,
|
|
self._scrollwidget,
|
|
self.extra_x,
|
|
)
|
|
return
|
|
|
|
def _restore_state(self) -> None:
|
|
try:
|
|
for tab in self._tabs.values():
|
|
tab.restore_state()
|
|
|
|
sel: bui.Widget | None
|
|
assert bui.app.classic is not None
|
|
winstate = bui.app.ui_v1.window_states.get(type(self), {})
|
|
sel_name = winstate.get('sel_name', None)
|
|
assert isinstance(sel_name, (str, type(None)))
|
|
current_tab = self.TabID.BOTS
|
|
gather_tab_val = bui.app.config.get('Practice Tab')
|
|
try:
|
|
stored_tab = 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 == 'TabContainer':
|
|
sel = self._tab_container
|
|
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
|
|
try:
|
|
sel_tab_id = self.TabID(sel_name.split(':')[-1])
|
|
except ValueError:
|
|
sel_tab_id = self.TabID.BOTS
|
|
sel = self._tab_row.tabs[sel_tab_id].button
|
|
else:
|
|
sel = self._tab_row.tabs[current_tab].button
|
|
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
|
|
|
except Exception:
|
|
logging.exception('Error restoring state for %s.', self)
|
|
|
|
|
|
org_begin = bs._activity.Activity.on_begin
|
|
|
|
|
|
def new_begin(self):
|
|
"""Runs when game is began."""
|
|
org_begin(self)
|
|
bui.set_party_window_open(True)
|
|
|
|
|
|
bs._activity.Activity.on_begin = new_begin
|
|
|
|
|
|
class BotPicker(popup.PopupWindow):
|
|
"""Popup window for selecting bots to spwan."""
|
|
|
|
def __init__(self,
|
|
parent: bui.Widget,
|
|
position: tuple[float, float] = (0.0, 0.0),
|
|
delegate: Any = None,
|
|
scale: float | None = None,
|
|
offset: tuple[float, float] = (0.0, 0.0),
|
|
selected_character: str | None = None):
|
|
del parent # unused here
|
|
uiscale = bui.app.ui_v1.uiscale
|
|
if scale is None:
|
|
scale = (1.85 if uiscale is babase.UIScale.SMALL else
|
|
1.65 if uiscale is babase.UIScale.MEDIUM else 1.23)
|
|
|
|
self._delegate = delegate
|
|
self._transitioning_out = False
|
|
|
|
count = 16
|
|
|
|
columns = 3
|
|
rows = int(math.ceil(float(count) / columns))
|
|
|
|
button_width = 100
|
|
button_height = 100
|
|
button_buffer_h = 10
|
|
button_buffer_v = 15
|
|
|
|
self._width = (10 + columns * (button_width + 2 * button_buffer_h) *
|
|
(1.0 / 0.95) * (1.0 / 0.8))
|
|
self._height = self._width * (0.8
|
|
if uiscale is babase.UIScale.SMALL else 1.06)
|
|
|
|
self._scroll_width = self._width * 0.8
|
|
self._scroll_height = self._height * 0.8
|
|
self._scroll_position = ((self._width - self._scroll_width) * 0.5,
|
|
(self._height - self._scroll_height) * 0.5)
|
|
|
|
# creates our _root_widget
|
|
popup.PopupWindow.__init__(self,
|
|
position=position,
|
|
size=(self._width, self._height),
|
|
scale=scale,
|
|
bg_color=(0.5, 0.5, 0.5),
|
|
offset=offset,
|
|
focus_position=self._scroll_position,
|
|
focus_size=(self._scroll_width,
|
|
self._scroll_height))
|
|
|
|
self._scrollwidget = bui.scrollwidget(parent=self.root_widget,
|
|
size=(self._scroll_width,
|
|
self._scroll_height),
|
|
color=(0.55, 0.55, 0.55),
|
|
highlight=False,
|
|
position=self._scroll_position)
|
|
|
|
bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
|
|
|
|
self._sub_width = self._scroll_width * 0.95
|
|
self._sub_height = 5 + rows * (button_height +
|
|
2 * button_buffer_v) + 100
|
|
self._subcontainer = bui.containerwidget(parent=self._scrollwidget,
|
|
size=(self._sub_width,
|
|
self._sub_height),
|
|
background=False)
|
|
|
|
mask_texture = bui.gettexture('characterIconMask')
|
|
|
|
bot_list = (['bones', 'neoSpaz', 'kronk', 'neoSpaz', 'kronk', 'zoe',
|
|
'ninja', 'mel', 'jack', 'bunny',
|
|
'neoSpaz', 'kronk', 'zoe',
|
|
'ninja',
|
|
'neoSpaz', 'kronk', 'zoe',
|
|
'ninja'])
|
|
bot_list_type = (
|
|
['Dummy', 'Bomber Lite', 'Brawler Lite', 'Bomber', 'Brawler',
|
|
'Trigger',
|
|
'Charger', 'Sticky', 'Explodey', 'Bouncy',
|
|
'Pro Bomber', 'Pro Brawler', 'Pro Trigger',
|
|
'Pro Charger', 'S.Pro Bomber', 'S.Pro Brawler',
|
|
'S.Pro Trigger', 'S.Pro Charger'])
|
|
|
|
index = 0
|
|
for y in range(rows):
|
|
for x in range(columns):
|
|
|
|
if bot_list_type[index] in ('Pro Bomber', 'Pro Brawler',
|
|
'Pro Trigger', 'Pro Charger',
|
|
'S.Pro Bomber', 'S.Pro Brawler',
|
|
'S.Pro Trigger', 'S.Pro Charger'):
|
|
tint1 = (1.0, 0.2, 0.1)
|
|
tint2 = (0.6, 0.1, 0.05)
|
|
elif bot_list_type[index] in 'Bouncy':
|
|
tint1 = (1, 1, 1)
|
|
tint2 = (1.0, 0.5, 0.5)
|
|
elif bot_list_type[index] in ('Brawler Lite',
|
|
'Bomber Lite'):
|
|
tint1 = (1.2, 0.9, 0.2)
|
|
tint2 = (1.0, 0.5, 0.6)
|
|
else:
|
|
tint1 = (0.6, 0.6, 0.6)
|
|
tint2 = (0.1, 0.3, 0.1)
|
|
|
|
if bot_list_type[index] in ('S.Pro Bomber', 'S.Pro Brawler',
|
|
'S.Pro Trigger', 'S.Pro Charger'):
|
|
color = (1.3, 1.2, 3.0)
|
|
else:
|
|
color = (1.0, 1.0, 1.0)
|
|
pos = (x * (button_width + 2 * button_buffer_h) +
|
|
button_buffer_h, self._sub_height - (y + 1) *
|
|
(button_height + 2 * button_buffer_v) + 12)
|
|
btn = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
button_type='square',
|
|
position=(pos[0],
|
|
pos[1]),
|
|
size=(button_width, button_height),
|
|
autoselect=True,
|
|
texture=bui.gettexture(bot_list[index] + 'Icon'),
|
|
tint_texture=bui.gettexture(
|
|
bot_list[index] + 'IconColorMask'),
|
|
mask_texture=mask_texture,
|
|
label='',
|
|
color=color,
|
|
tint_color=tint1,
|
|
tint2_color=tint2,
|
|
on_activate_call=bui.Call(self._select_character,
|
|
character=bot_list_type[index]))
|
|
bui.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
|
|
|
|
name = bot_list_type[index]
|
|
bui.textwidget(parent=self._subcontainer,
|
|
text=name,
|
|
position=(pos[0] + button_width * 0.5,
|
|
pos[1] - 12),
|
|
size=(0, 0),
|
|
scale=0.5,
|
|
maxwidth=button_width,
|
|
draw_controller=btn,
|
|
h_align='center',
|
|
v_align='center',
|
|
color=(0.8, 0.8, 0.8, 0.8))
|
|
|
|
index += 1
|
|
if index >= len(bot_list):
|
|
break
|
|
if index >= len(bot_list):
|
|
break
|
|
|
|
def _select_character(self, character: str) -> None:
|
|
if self._delegate is not None:
|
|
self._delegate.on_bots_picker_pick(character)
|
|
self._transition_out()
|
|
|
|
def _transition_out(self) -> None:
|
|
if not self._transitioning_out:
|
|
self._transitioning_out = True
|
|
bui.containerwidget(edit=self.root_widget, transition='out_scale')
|
|
|
|
def on_popup_cancel(self) -> None:
|
|
bui.getsound('swish').play()
|
|
self._transition_out()
|
|
|
|
|
|
class PowerPicker(popup.PopupWindow):
|
|
"""Popup window for selecting power up."""
|
|
|
|
def __init__(self,
|
|
parent: bui.Widget,
|
|
position: tuple[float, float] = (0.0, 0.0),
|
|
delegate: Any = None,
|
|
scale: float | None = None,
|
|
offset: tuple[float, float] = (0.0, 0.0),
|
|
selected_character: str | None = None):
|
|
del parent # unused here
|
|
|
|
if scale is None:
|
|
uiscale = bui.app.ui_v1.uiscale
|
|
scale = (1.85 if uiscale is babase.UIScale.SMALL else
|
|
1.65 if uiscale is babase.UIScale.MEDIUM else 1.23)
|
|
|
|
self._delegate = delegate
|
|
self._transitioning_out = False
|
|
|
|
count = 7
|
|
|
|
columns = 3
|
|
rows = int(math.ceil(float(count) / columns))
|
|
|
|
button_width = 100
|
|
button_height = 100
|
|
button_buffer_h = 10
|
|
button_buffer_v = 15
|
|
|
|
self._width = (10 + columns * (button_width + 2 * button_buffer_h) *
|
|
(1.0 / 0.95) * (1.0 / 0.8))
|
|
self._height = self._width * (0.8
|
|
if uiscale is babase.UIScale.SMALL else 1.06)
|
|
|
|
self._scroll_width = self._width * 0.8
|
|
self._scroll_height = self._height * 0.8
|
|
self._scroll_position = ((self._width - self._scroll_width) * 0.5,
|
|
(self._height - self._scroll_height) * 0.5)
|
|
|
|
# creates our _root_widget
|
|
popup.PopupWindow.__init__(self,
|
|
position=position,
|
|
size=(self._width, self._height),
|
|
scale=scale,
|
|
bg_color=(0.5, 0.5, 0.5),
|
|
offset=offset,
|
|
focus_position=self._scroll_position,
|
|
focus_size=(self._scroll_width,
|
|
self._scroll_height))
|
|
|
|
self._scrollwidget = bui.scrollwidget(parent=self.root_widget,
|
|
size=(self._scroll_width,
|
|
self._scroll_height),
|
|
color=(0.55, 0.55, 0.55),
|
|
highlight=False,
|
|
position=self._scroll_position)
|
|
|
|
bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
|
|
|
|
self._sub_width = self._scroll_width * 0.95
|
|
self._sub_height = 5 + rows * (button_height +
|
|
2 * button_buffer_v) + 100
|
|
self._subcontainer = bui.containerwidget(parent=self._scrollwidget,
|
|
size=(self._sub_width,
|
|
self._sub_height),
|
|
background=False)
|
|
|
|
power_list = (['Bomb', 'Curse', 'Health', 'IceBombs',
|
|
'ImpactBombs', 'LandMines', 'Punch',
|
|
'Shield', 'StickyBombs'])
|
|
|
|
power_list_type = (['Tripple Bomb', 'Curse', 'Health', 'Ice Bombs',
|
|
'Impact Bombs', 'Land Mines', 'Punch',
|
|
'Shield', 'Sticky Bombs'])
|
|
|
|
index = 0
|
|
for y in range(rows):
|
|
for x in range(columns):
|
|
pos = (x * (button_width + 2 * button_buffer_h) +
|
|
button_buffer_h, self._sub_height - (y + 1) *
|
|
(button_height + 2 * button_buffer_v) + 12)
|
|
btn = bui.buttonwidget(
|
|
parent=self._subcontainer,
|
|
button_type='square',
|
|
position=(pos[0],
|
|
pos[1]),
|
|
size=(button_width, button_height),
|
|
autoselect=True,
|
|
texture=bui.gettexture('powerup' + power_list[index]),
|
|
label='',
|
|
color=(1, 1, 1),
|
|
on_activate_call=bui.Call(self._select_power,
|
|
power=power_list[index]))
|
|
bui.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
|
|
|
|
name = power_list_type[index]
|
|
bui.textwidget(parent=self._subcontainer,
|
|
text=name,
|
|
position=(pos[0] + button_width * 0.5,
|
|
pos[1] - 12),
|
|
size=(0, 0),
|
|
scale=0.5,
|
|
maxwidth=button_width,
|
|
draw_controller=btn,
|
|
h_align='center',
|
|
v_align='center',
|
|
color=(0.8, 0.8, 0.8, 0.8))
|
|
|
|
index += 1
|
|
if index >= len(power_list):
|
|
break
|
|
if index >= len(power_list):
|
|
break
|
|
|
|
def _select_power(self, power: str) -> None:
|
|
if self._delegate is not None:
|
|
self._delegate.on_power_picker_pick(power)
|
|
self._transition_out()
|
|
|
|
def _transition_out(self) -> None:
|
|
if not self._transitioning_out:
|
|
self._transitioning_out = True
|
|
bui.containerwidget(edit=self.root_widget, transition='out_scale')
|
|
|
|
def on_popup_cancel(self) -> None:
|
|
bui.getsound('swish').play()
|
|
self._transition_out()
|
|
|
|
|
|
class InfoWindow(popup.PopupWindow):
|
|
"""Popup window for Infos."""
|
|
|
|
def __init__(self,
|
|
parent: bs.Widget,
|
|
position: tuple[float, float] = (0.0, 0.0),
|
|
delegate: Any = None,
|
|
scale: float | None = None,
|
|
offset: tuple[float, float] = (0.0, 0.0),
|
|
selected_character: str | None = None):
|
|
del parent # unused here
|
|
|
|
if scale is None:
|
|
uiscale = bui.app.ui_v1.uiscale
|
|
scale = (1.85 if uiscale is babase.UIScale.SMALL else
|
|
1.65 if uiscale is babase.UIScale.MEDIUM else 1.23)
|
|
|
|
self._delegate = delegate
|
|
self._transitioning_out = False
|
|
|
|
self._width = 600
|
|
self._height = self._width * (0.6
|
|
if uiscale is babase.UIScale.SMALL else 0.795)
|
|
|
|
# creates our _root_widget
|
|
popup.PopupWindow.__init__(self,
|
|
position=position,
|
|
size=(self._width, self._height),
|
|
scale=scale,
|
|
bg_color=(0.5, 0.5, 0.5),
|
|
offset=offset,
|
|
focus_size=(self._width,
|
|
self._height))
|
|
|
|
bui.textwidget(parent=self.root_widget,
|
|
position=(self._width * 0.5,
|
|
self._height * 0.9),
|
|
size=(0, 0),
|
|
color=bui.app.ui_v1.title_color,
|
|
scale=1.3,
|
|
h_align='center',
|
|
v_align='center',
|
|
text='About',
|
|
maxwidth=200)
|
|
|
|
text = ('Practice Tools Mod\n'
|
|
'Made By Cross Joy\n'
|
|
'version v' + version + '\n'
|
|
'\n'
|
|
'Thx to\n'
|
|
'Mikirog for the Bomb radius visualizer mod.\n'
|
|
)
|
|
|
|
lines = text.splitlines()
|
|
line_height = 16
|
|
scale_t = 0.56
|
|
|
|
voffs = 0
|
|
i = 0
|
|
for line in lines:
|
|
i += 1
|
|
if i <= 3:
|
|
color = (1.0, 1.0, 1.0, 1.0)
|
|
else:
|
|
color = (0.4, 1.0, 1.4, 1.0)
|
|
|
|
bui.textwidget(
|
|
parent=self.root_widget,
|
|
padding=4,
|
|
color=color,
|
|
scale=scale_t,
|
|
flatness=1.0,
|
|
size=(0, 0),
|
|
position=(self._width * 0.5, self._height * 0.8 + voffs),
|
|
h_align='center',
|
|
v_align='top',
|
|
text=line)
|
|
voffs -= line_height
|
|
|
|
text_spacing = 70
|
|
|
|
self.button_discord = bui.buttonwidget(
|
|
parent=self.root_widget,
|
|
position=(
|
|
self._width * 0.25 - 40, self._height * 0.2 - 40),
|
|
size=(80, 80),
|
|
autoselect=True,
|
|
button_type='square',
|
|
color=(0.447, 0.537, 0.854),
|
|
label='',
|
|
on_activate_call=self._discord)
|
|
bui.imagewidget(
|
|
parent=self.root_widget,
|
|
position=(self._width * 0.25 - 25,
|
|
self._height * 0.2 - 25),
|
|
size=(50, 50),
|
|
draw_controller=self.button_discord,
|
|
texture=bui.gettexture('discordLogo'),
|
|
color=(5, 5, 5))
|
|
bui.textwidget(
|
|
parent=self.root_widget,
|
|
position=(self._width * 0.25,
|
|
self._height * 0.2 + text_spacing),
|
|
size=(0, 0),
|
|
scale=0.75,
|
|
draw_controller=self.button_discord,
|
|
text='Join us. :D',
|
|
h_align='center',
|
|
v_align='center',
|
|
maxwidth=150,
|
|
color=(0.447, 0.537, 0.854))
|
|
|
|
self.button_github = bui.buttonwidget(
|
|
parent=self.root_widget,
|
|
position=(
|
|
self._width * 0.5 - 40, self._height * 0.2 - 40),
|
|
size=(80, 80),
|
|
autoselect=True,
|
|
button_type='square',
|
|
color=(0.129, 0.122, 0.122),
|
|
label='',
|
|
on_activate_call=self._github)
|
|
bui.imagewidget(
|
|
parent=self.root_widget,
|
|
position=(self._width * 0.5 - 25,
|
|
self._height * 0.2 - 25),
|
|
size=(50, 50),
|
|
draw_controller=self.button_github,
|
|
texture=bui.gettexture('githubLogo'),
|
|
color=(1, 1, 1))
|
|
bui.textwidget(
|
|
parent=self.root_widget,
|
|
position=(self._width * 0.5,
|
|
self._height * 0.2 + text_spacing),
|
|
size=(0, 0),
|
|
scale=0.75,
|
|
draw_controller=self.button_github,
|
|
text='Found Bugs?',
|
|
h_align='center',
|
|
v_align='center',
|
|
maxwidth=150,
|
|
color=(0.129, 0.122, 0.122))
|
|
|
|
self.button_support = bui.buttonwidget(
|
|
parent=self.root_widget,
|
|
position=(
|
|
self._width * 0.75 - 40, self._height * 0.2 - 40),
|
|
size=(80, 80),
|
|
autoselect=True,
|
|
button_type='square',
|
|
color=(0.83, 0.69, 0.21),
|
|
label='',
|
|
on_activate_call=self._support)
|
|
bui.imagewidget(
|
|
parent=self.root_widget,
|
|
position=(self._width * 0.75 - 25,
|
|
self._height * 0.2 - 25),
|
|
size=(50, 50),
|
|
draw_controller=self.button_support,
|
|
texture=bui.gettexture('heart'),
|
|
color=(1, 1, 1))
|
|
bui.textwidget(
|
|
parent=self.root_widget,
|
|
position=(self._width * 0.75,
|
|
self._height * 0.2 + text_spacing),
|
|
size=(0, 0),
|
|
scale=0.75,
|
|
draw_controller=self.button_support,
|
|
text='Support uwu.',
|
|
h_align='center',
|
|
v_align='center',
|
|
maxwidth=150,
|
|
color=(0.83, 0.69, 0.21))
|
|
|
|
def _discord(self):
|
|
bui.open_url('https://discord.gg/JyBY6haARJ')
|
|
|
|
def _github(self):
|
|
bui.open_url('https://github.com/CrossJoy/Bombsquad-Modding')
|
|
|
|
def _support(self):
|
|
bui.open_url('https://www.buymeacoffee.com/CrossJoy')
|
|
|
|
def _transition_out(self) -> None:
|
|
if not self._transitioning_out:
|
|
self._transitioning_out = True
|
|
bui.containerwidget(edit=self.root_widget, transition='out_scale')
|
|
|
|
def on_popup_cancel(self) -> None:
|
|
bui.getsound('swish').play()
|
|
self._transition_out()
|