mirror of
https://github.com/bombsquad-community/plugin-manager.git
synced 2026-08-15 13:03:08 +00:00
1293 lines
48 KiB
Python
1293 lines
48 KiB
Python
# VolleyPunch ? ig i'll name it like that
|
|
|
|
# Made by Learn.py
|
|
# but this was def inspired by some freaky guy
|
|
|
|
|
|
# ba_meta require api 9
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import babase
|
|
import _babase
|
|
import math
|
|
import random
|
|
import bascenev1 as bs
|
|
import bauiv1 as bui
|
|
import math
|
|
import time
|
|
import babase
|
|
from bascenev1lib.actor.playerspaz import PlayerSpaz
|
|
from bascenev1lib.actor.scoreboard import Scoreboard
|
|
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
|
|
from bascenev1lib.actor.bomb import BombFactory
|
|
from bascenev1lib.gameutils import SharedObjects
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Any, Sequence, Dict, Type, List, Optional, Union
|
|
|
|
|
|
plugman = dict(
|
|
plugin_name="volley_punch",
|
|
description="Have fun with your friends in this new volley game !",
|
|
external_url="https://github.com/Scriptz1/Learn.py-Installer/blob/main/volleypunch.py",
|
|
authors=[
|
|
{"name": "Learn.py", "email": "phillipians36@gmail.com", "discord": "Learning .py"},
|
|
],
|
|
version="1.1.0",
|
|
)
|
|
|
|
|
|
class PuckDiedMessage:
|
|
"""Inform something that a puck has died."""
|
|
|
|
def __init__(self, puck: Puck):
|
|
self.puck = puck
|
|
|
|
|
|
class Puck(bs.Actor):
|
|
def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0)):
|
|
super().__init__()
|
|
shared = SharedObjects.get()
|
|
activity = self.getactivity()
|
|
|
|
# Spawn just above the provided point.
|
|
self._spawn_pos = (position[0], position[1] + 1.05, position[2])
|
|
self.last_players_to_touch: Dict[int, Player] = {}
|
|
self.scored = False
|
|
|
|
self.touches = 0
|
|
assert activity is not None
|
|
assert isinstance(activity, VolleyBallGame)
|
|
pmats = [shared.object_material, activity.puck_material]
|
|
|
|
# so this is the volleyball we gonna mesh with today
|
|
self.node = bs.newnode('prop',
|
|
delegate=self,
|
|
attrs={
|
|
'mesh': activity.puck_mesh,
|
|
'color_texture': activity.puck_tex,
|
|
'body': 'sphere',
|
|
'reflection': 'soft',
|
|
'reflection_scale': [0.2],
|
|
'shadow_size': 0.3,
|
|
'mesh_scale': activity.ball_size,
|
|
'body_scale': 1.07,
|
|
'gravity_scale': 1,
|
|
'is_area_of_interest': True,
|
|
'position': self._spawn_pos,
|
|
'materials': pmats
|
|
})
|
|
|
|
def handlemessage(self, msg: Any) -> Any:
|
|
if isinstance(msg, bs.DieMessage):
|
|
assert self.node
|
|
self.node.delete()
|
|
activity = self._activity()
|
|
if activity and not msg.immediate:
|
|
activity.handlemessage(PuckDiedMessage(self))
|
|
|
|
# If we go out of bounds, move back to where we started.
|
|
# copied that stuff from hockey game ngl
|
|
elif isinstance(msg, bs.OutOfBoundsMessage):
|
|
assert self.node
|
|
self.node.position = self._spawn_pos
|
|
|
|
elif isinstance(msg, bs.HitMessage):
|
|
assert self.node
|
|
assert msg.force_direction is not None
|
|
self.node.handlemessage(
|
|
'impulse', msg.pos[0], msg.pos[1], msg.pos[2], msg.velocity[0],
|
|
msg.velocity[1], msg.velocity[2], 1.0 * msg.magnitude,
|
|
1.0 * msg.velocity_magnitude, msg.radius, 0,
|
|
msg.force_direction[0], msg.force_direction[1],
|
|
msg.force_direction[2])
|
|
|
|
# If this hit came from a player, log them as the last to touch us.
|
|
s_player = msg.get_source_player(Player)
|
|
if s_player is not None:
|
|
activity = self._activity()
|
|
if activity:
|
|
if s_player in activity.players:
|
|
self.last_players_to_touch[s_player.team.id] = s_player
|
|
else:
|
|
super().handlemessage(msg)
|
|
|
|
|
|
class Player(bs.Player['Team']):
|
|
"""Our player type for this game."""
|
|
|
|
|
|
class Team(bs.Team[Player]):
|
|
"""Our team type for this game."""
|
|
|
|
def __init__(self) -> None:
|
|
self.score = 0
|
|
|
|
|
|
# ba_meta export bascenev1.GameActivity
|
|
class VolleyBallGame(bs.TeamGameActivity[Player, Team]):
|
|
name = 'VolleyPunch'
|
|
description = 'Punch the Volleyball !'
|
|
available_settings = [
|
|
bs.IntSetting(
|
|
'Score to Win',
|
|
min_value=1,
|
|
default=5,
|
|
increment=1,
|
|
),
|
|
bs.IntChoiceSetting(
|
|
'Time Limit',
|
|
choices=[
|
|
('None', 0),
|
|
('1 Minute', 60),
|
|
('2 Minutes', 120),
|
|
('5 Minutes', 300),
|
|
('10 Minutes', 600),
|
|
('20 Minutes', 1200),
|
|
],
|
|
default=0,
|
|
),
|
|
bs.FloatChoiceSetting(
|
|
'Respawn Times',
|
|
choices=[
|
|
('Shorter', 0.25),
|
|
('Short', 0.5),
|
|
('Normal', 1.0),
|
|
('Long', 2.0),
|
|
('Longer', 4.0),
|
|
],
|
|
default=1.0,
|
|
),
|
|
bs.FloatSetting(
|
|
'Ball size (m)',
|
|
min_value=0.15,
|
|
default=0.25,
|
|
max_value=0.35,
|
|
increment=0.05,
|
|
),
|
|
bs.FloatChoiceSetting(
|
|
'Ball Average Speed',
|
|
choices=[
|
|
('Slower', 0.9899),
|
|
('Normal', 1.0),
|
|
],
|
|
default=1.0,
|
|
),
|
|
bs.BoolSetting('Epic Mode', False),
|
|
bs.BoolSetting('Enable Credits', True),
|
|
]
|
|
default_music = bs.MusicType.HOCKEY
|
|
|
|
@classmethod
|
|
def supports_session_type(cls, sessiontype: Type[bs.Session]) -> bool:
|
|
return issubclass(sessiontype, bs.DualTeamSession)
|
|
|
|
@classmethod
|
|
def get_supported_maps(cls, sessiontype: Type[bs.Session]) -> List[str]:
|
|
return ['Football Stadium']
|
|
|
|
def __init__(self, settings: dict):
|
|
super().__init__(settings)
|
|
shared = SharedObjects.get()
|
|
self._scoreboard = Scoreboard()
|
|
self._cheer_sound = bs.getsound('cheer')
|
|
self._chant_sound = bs.getsound('crowdChant')
|
|
self.one_sound = bs.getsound('announceOne')
|
|
self.two_sound = bs.getsound('announceTwo')
|
|
self.three_sound = bs.getsound('announceThree') # unused.. yet
|
|
self._swipsound = bs.getsound('swip')
|
|
self._whistle_sound = bs.getsound('refWhistle')
|
|
self.puck_mesh = bs.getmesh('shield')
|
|
self.puck_tex = bs.gettexture('ouyaUButton')
|
|
self._puck_sound = bs.getsound('metalHit')
|
|
self.puck_material = bs.Material()
|
|
self.puck_material.add_actions(actions=(('modify_part_collision',
|
|
'friction', 4)))
|
|
self.puck_material.add_actions(conditions=('they_have_material',
|
|
shared.pickup_material),
|
|
actions=('modify_part_collision',
|
|
'collide', True))
|
|
self.puck_material.add_actions(
|
|
conditions=(
|
|
('we_are_younger_than', 100),
|
|
'and',
|
|
('they_have_material', shared.object_material),
|
|
),
|
|
actions=('modify_node_collision', 'collide', False),
|
|
)
|
|
self.puck_material.add_actions(conditions=('they_have_material',
|
|
shared.footing_material),
|
|
actions=('impact_sound',
|
|
self._puck_sound, 0.2, 5))
|
|
|
|
# Keep track of which player last touched the puck
|
|
self.puck_material.add_actions(
|
|
conditions=('they_have_material', shared.player_material),
|
|
actions=(('call', 'at_connect',
|
|
self._handle_puck_player_collide),
|
|
('modify_part_collision', 'physical', False)))
|
|
|
|
# We want the puck to kill powerups; not get stopped by them
|
|
self.puck_material.add_actions(
|
|
conditions=('they_have_material',
|
|
PowerupBoxFactory.get().powerup_material),
|
|
actions=(('modify_part_collision', 'physical', False),
|
|
('message', 'their_node', 'at_connect', bs.DieMessage())))
|
|
self._score_region_material = bs.Material()
|
|
self._score_region_material.add_actions(
|
|
conditions=('they_have_material', self.puck_material),
|
|
actions=(('modify_part_collision', 'collide',
|
|
True), ('modify_part_collision', 'physical', False),
|
|
('call', 'at_connect', self._handle_score)))
|
|
|
|
self._fake_wall_material = bs.Material() # i'll fix this. trust
|
|
|
|
self._net_wall_material = bs.Material()
|
|
self._net_material = bs.Material()
|
|
self._net_wall_material.add_actions(
|
|
conditions=('they_have_material', shared.player_material),
|
|
actions=(
|
|
('modify_part_collision', 'collide', True),
|
|
('modify_part_collision', 'physical', True)
|
|
|
|
))
|
|
self._net_material.add_actions(
|
|
conditions=('they_have_material', shared.object_material),
|
|
actions=(
|
|
('modify_part_collision', 'collide', True),
|
|
('modify_part_collision', 'physical', True),
|
|
('modify_part_collision',
|
|
'friction', -10),
|
|
('call', 'at_connect', self.slow_down)))
|
|
|
|
self._puck_spawn_pos: Optional[Sequence[float]] = None
|
|
self._score_regions: Optional[List[bs.NodeActor]] = None
|
|
self._puck: Optional[Puck] = None
|
|
self._score_to_win = int(settings['Score to Win'])
|
|
self._time_limit = float(settings['Time Limit'])
|
|
self.credit_text = bool(settings['Enable Credits']) # i did the freaku credit thing
|
|
self._epic_mode = bool(settings['Epic Mode'])
|
|
|
|
# the ball is too fast for the players and the map is soo huge its a good poc - brostos
|
|
self.ball_size = float(settings['Ball size (m)'])
|
|
self.ball_speed = float(settings['Ball Average Speed'])
|
|
|
|
# Base class overrides.
|
|
self.slow_motion = self._epic_mode
|
|
self.default_music = (bs.MusicType.EPIC if self._epic_mode else
|
|
bs.MusicType.TO_THE_DEATH)
|
|
self.s: list = []
|
|
self.w: list = []
|
|
self.n: list = []
|
|
self.last_interaction_time = -9999
|
|
self.smashed = -9999
|
|
self.interactions_timer = None
|
|
self.curve = True
|
|
self.difficulty = 0.5
|
|
self.smasher = None
|
|
self.commentary_text = None
|
|
self._text_task = None # Référence pour annuler l'animation en cours
|
|
self._clear_timer = None # Référence pour le timer de disparition
|
|
self.touches_indicator = None
|
|
self.guide = None
|
|
self.curving = False
|
|
|
|
def get_instance_description(self) -> Union[str, Sequence]:
|
|
if self._score_to_win == 1:
|
|
return 'Score a goal.'
|
|
return 'Score ${ARG1} goals.', self._score_to_win
|
|
|
|
def get_instance_description_short(self) -> Union[str, Sequence]:
|
|
if self._score_to_win == 1:
|
|
return 'score a goal'
|
|
return 'score ${ARG1} goals', self._score_to_win
|
|
|
|
def on_begin(self) -> None:
|
|
super().on_begin()
|
|
|
|
self.setup_standard_time_limit(self._time_limit)
|
|
self._puck_spawn_pos = (-4, 1, 0) if random.random() < 0.5 else (4, 1, 0)
|
|
self._spawn_puck()
|
|
|
|
# Set up the two score regions.
|
|
self._score_regions = []
|
|
self.commentary_text = bs.newnode(
|
|
'text',
|
|
attrs=dict(
|
|
text='',
|
|
scale=0.012,
|
|
position=(0, 3.1, -5.8),
|
|
in_world=True,
|
|
flatness=1,
|
|
shadow=0,
|
|
color=(1, 1, 1, 0.5),
|
|
h_align='center',
|
|
v_align='top',
|
|
h_attach='center',
|
|
v_attach='top',
|
|
))
|
|
self.touches_indicator = bs.newnode(
|
|
'text',
|
|
attrs=dict(
|
|
text='',
|
|
scale=0.025,
|
|
position=(0, 3, -5.8),
|
|
in_world=True,
|
|
flatness=1,
|
|
shadow=0,
|
|
color=(1, 1, 1, 0.5),
|
|
h_align='center',
|
|
v_align='top',
|
|
h_attach='center',
|
|
v_attach='top',
|
|
))
|
|
self.guide = bs.newnode(
|
|
'text',
|
|
attrs=dict(
|
|
text='PUNCH TO TAP THE BALL \nPICKUP TO CUFF \nBOMB TO GLIDE / DIVE \nJUMP UNDER A MID AIR BALL WITH THE BAR FILLED TO SMASH \nTHE ULT BAR FILLS AS YOU PLAY',
|
|
scale=0.005,
|
|
position=(0, 2.7, -5.8),
|
|
in_world=True,
|
|
flatness=1,
|
|
shadow=0,
|
|
color=(1, 1, 1, 0.5),
|
|
h_align='center',
|
|
v_align='top',
|
|
h_attach='center',
|
|
v_attach='top',
|
|
))
|
|
bs.animate_array(self.guide, 'color', 4, {0: (1, 1, 1, 0.5), 1: (
|
|
1, 1, 1, 1), 2: (1, 1, 1, 0.5)}, loop=True)
|
|
self.interactions_timer = bs.Timer(
|
|
1 / 120, self.interactions, repeat=True) # time, function, repeat
|
|
self._score_regions.append(
|
|
bs.NodeActor(
|
|
bs.newnode('region',
|
|
attrs={
|
|
'position': (9, 0, 0),
|
|
'scale': (17.3, 0.002, 12),
|
|
'type': 'box',
|
|
'materials': [self._score_region_material]
|
|
})))
|
|
self._score_regions.append(
|
|
bs.NodeActor(
|
|
bs.newnode('region',
|
|
attrs={
|
|
'position': (-9, 0, 0),
|
|
'scale': (17.3, 0.002, 20),
|
|
'type': 'box',
|
|
'materials': [self._score_region_material]
|
|
})))
|
|
self._update_scoreboard()
|
|
self._chant_sound.play()
|
|
if self.credit_text:
|
|
t = bs.newnode('text',
|
|
attrs={'text': "Created by Learn.py",
|
|
'scale': 0.7,
|
|
'position': (0, 0),
|
|
'shadow': 0.5,
|
|
'flatness': 1.2,
|
|
'color': (1, 1, 1),
|
|
'h_align': 'center',
|
|
'v_attach': 'bottom'})
|
|
|
|
# Create the net and regions.
|
|
mat = bs.Material() # spam this whenever u want a material THEN add some properties
|
|
mat.add_actions(actions=('modify_part_collision',
|
|
'collide', False))
|
|
net = bs.newnode(
|
|
'prop',
|
|
attrs={
|
|
'body': 'puck',
|
|
'body_scale': 0,
|
|
'position': (0, 0, 0),
|
|
'mesh_scale': 1,
|
|
'mesh': bs.getmesh('volleyball_net'),
|
|
'color_texture': bs.gettexture('black'),
|
|
'reflection': 'soft',
|
|
'reflection_scale': [1.5],
|
|
'sticky': False,
|
|
'shadow_size': 0.2,
|
|
'gravity_scale': 0,
|
|
'materials': [mat],
|
|
},
|
|
)
|
|
|
|
self.s.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 2.4, 0), 'scale': (
|
|
0.8, 6, 20), 'type': 'box', 'materials': (self._fake_wall_material, )})))
|
|
|
|
self.w.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, 0), 'scale': (
|
|
0.6, 10, 20), 'type': 'box', 'materials': (self._net_wall_material, )})))
|
|
self.n.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, 0), 'scale': (
|
|
0.6, 2.75, 20), 'type': 'box', 'materials': (self._net_material, )})))
|
|
self.n.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, -5.9), 'scale': (
|
|
20, 20, 0.8), 'type': 'box', 'materials': (self._net_wall_material, self._net_material)})))
|
|
self.n.append(bs.NodeActor(bs.newnode('region', attrs={'position': (0, 0, 5.9), 'scale': (
|
|
20, 20, 0.8), 'type': 'box', 'materials': (self._net_wall_material, self._net_material)})))
|
|
|
|
def slow_down(self):
|
|
if not self._puck:
|
|
return
|
|
puck_node = self._puck.node
|
|
mult = 0.88
|
|
v = puck_node.velocity
|
|
puck_node.velocity = (v[0] * mult, v[1] * mult, v[2] * mult)
|
|
|
|
def write(self, text, color):
|
|
"""types"""
|
|
if hasattr(self, '_timers'):
|
|
for t in self._timers:
|
|
t = None
|
|
self._timers = []
|
|
|
|
if not text:
|
|
return
|
|
|
|
self.commentary_text.color = color
|
|
total_chars = len(text)
|
|
|
|
interval = 0.9 / max(total_chars, 1)
|
|
|
|
for i in range(1, total_chars + 1):
|
|
def show(index=i):
|
|
if self.commentary_text.exists():
|
|
self.commentary_text.text = text[:index]
|
|
|
|
self._timers.append(bs.Timer(interval * i, show, repeat=False))
|
|
|
|
def start_clear():
|
|
for i in range(total_chars, -1, -1):
|
|
def hide(index=i):
|
|
if self.commentary_text.exists():
|
|
self.commentary_text.text = text[:index]
|
|
|
|
self._timers.append(
|
|
bs.Timer(0.9 + 3.0 + (0.015 * (total_chars - i)), hide, repeat=False))
|
|
|
|
start_clear()
|
|
|
|
def vignette(self, final_color, longer):
|
|
"""Aesthetic reasons."""
|
|
glb = bs.getactivity().globalsnode
|
|
base_color = (0.57, 0.57, 0.57)
|
|
bs.animate_array(glb, 'vignette_outer', 3, {0: base_color, 0.1: (
|
|
final_color[0] / 1.2, final_color[1] / 1.2, final_color[2] / 1.2), (1 if longer else 0.5): base_color})
|
|
|
|
def interactions(self):
|
|
"""Some personalized stuff and my deepest secrets.
|
|
I don't know why, but I feel SoK may like this."""
|
|
if self._puck is None:
|
|
return
|
|
|
|
vel = self._puck.node.velocity
|
|
# apply the mult
|
|
self._puck.node.velocity = (
|
|
vel[0],
|
|
vel[1] * self.ball_speed,
|
|
vel[2]
|
|
)
|
|
|
|
green = bs.gettexture('ouyaOButton')
|
|
yellow = bs.gettexture('ouyaYButton')
|
|
red = bs.gettexture('ouyaAButton')
|
|
blue = bs.gettexture('ouyaUButton')
|
|
|
|
# If it passes the middle.. green.
|
|
puck_node = self._puck.node
|
|
pos = puck_node.position
|
|
x_diff = abs(pos[0])
|
|
|
|
if x_diff < 0.25 or self.curve:
|
|
puck_node.color_texture = blue
|
|
self._puck.touches = 0
|
|
|
|
if pos[1] < 2.6 and (bs.time() - self.last_interaction_time > 0.5):
|
|
mult = 0.935
|
|
v = puck_node.velocity
|
|
puck_node.velocity = (v[0] * mult, v[1] * mult, v[2] * mult)
|
|
|
|
self.save_text = random.choice([
|
|
"dived to the rescue!",
|
|
"kept the ball alive!",
|
|
"flew in to save the day!",
|
|
"is too fast for the floor!",
|
|
"bounced it back just in time!",
|
|
"kept the dream alive!",
|
|
"barely missed the ground!",
|
|
"saved the rally!",
|
|
"made a clutch save!",
|
|
"refused to let it drop!",
|
|
"pulled a superman save!",
|
|
"made a desperate save!",
|
|
"proved the floor is not an option!",
|
|
"landed a miracle touch!",
|
|
"saved the point!",
|
|
"pulled off a heroic dive!",
|
|
"stretched to the limit!",
|
|
"barely kept it up!",
|
|
"showed reflexes of a cat!",
|
|
"denied the floor!",
|
|
"made a clutch save!",
|
|
"is staying in the game!",
|
|
"pulled an epic recovery!",
|
|
"reacted in the nick of time!",
|
|
"won't let the ball drop!",
|
|
"is pure hustle!",
|
|
"made an impossible save!",
|
|
"is defying gravity!",
|
|
"made sure the ground stays empty!",
|
|
"saved it!",
|
|
"pulled off last-second magic!",
|
|
"is floating to save it!",
|
|
"is too clutch to fail!",
|
|
"got the ball back in the air!",
|
|
"is denying the dirt!",
|
|
"saved it by a hair!",
|
|
"is fighting for every ball!",
|
|
"is pure energy!",
|
|
"never gives up!",
|
|
"keeps it in play!",
|
|
"saved it barely!",
|
|
"kept the floor clean!",
|
|
"is pure determination!",
|
|
"pulled a miraculous save!",
|
|
"denied the game over!",
|
|
"is still alive!",
|
|
"is keeping the rally going!",
|
|
"made an out-of-reach save!",
|
|
"made a ninja-like save!",
|
|
"bounced back from trouble!"])
|
|
self.hit_text = random.choice([
|
|
"sent it over!",
|
|
"cleared the net!",
|
|
"powered it across!",
|
|
"nailed a deep shot!",
|
|
"bounced it into their zone!",
|
|
"launched an attack!",
|
|
"sent a rocket over!",
|
|
"is dominating the net!",
|
|
"found the open spot!",
|
|
"placed it perfectly!",
|
|
"smashed it back!",
|
|
"is applying the pressure!",
|
|
"sent them scrambling!",
|
|
"over the net it goes!",
|
|
"hit a beauty!",
|
|
"is controlling the court!",
|
|
"forced a tough return!",
|
|
"keeps the pressure high!",
|
|
"landed a sweet strike!",
|
|
"sent it deep!",
|
|
"is playing aggressively!",
|
|
"hit a laser beam!",
|
|
"made a solid connection!",
|
|
"is taking charge!",
|
|
"sent a tricky shot over!",
|
|
"is firing back!",
|
|
"put it right on target!",
|
|
"sent the ball flying!",
|
|
"beat the defense!",
|
|
"is crushing it!",
|
|
"nailed that serve return!",
|
|
"showed off some power!",
|
|
"sent a high arc over!",
|
|
"is making moves!",
|
|
"kept them on their toes!",
|
|
"delivered a powerful hit!",
|
|
"is pushing the pace!",
|
|
"found the gap!",
|
|
"sent a curveball over!",
|
|
"is looking sharp!",
|
|
"hit it with precision!",
|
|
"made them work for it!",
|
|
"is owning the net!",
|
|
"sent a heavy strike!",
|
|
"is dictating the play!",
|
|
"put it out of reach!",
|
|
"landed the perfect hit!",
|
|
"is on fire!",
|
|
"sent a swift strike!",
|
|
"is forcing errors!"
|
|
])
|
|
self.control_text = random.choice([
|
|
"is showing off sweet control!",
|
|
"has the ball on a string!",
|
|
"handles it with grace!",
|
|
"shows off some nice touch!",
|
|
"keeps it close and tight!",
|
|
"is demonstrating perfect poise!",
|
|
"has ice in their veins!",
|
|
"is keeping it under control!",
|
|
"shows a velvet touch!",
|
|
"is masterfully handling the ball!",
|
|
"keeps the ball glued to them!",
|
|
"is displaying total composure!",
|
|
"makes it look so easy!",
|
|
"is controlling the pace!",
|
|
"shows off pure finesse!",
|
|
"has the ball perfectly placed!",
|
|
"is staying cool under pressure!",
|
|
"has incredible ball handling!",
|
|
"is maneuvering with ease!",
|
|
"keeps the ball within reach!",
|
|
"shows off some fancy footwork!",
|
|
"is keeping the ball calm!",
|
|
"has the softest touch!",
|
|
"is dictating the rhythm!",
|
|
"keeps it balanced and steady!",
|
|
"is showing high-level control!",
|
|
"has the situation handled!",
|
|
"is playing with surgical precision!",
|
|
"keeps the ball in their zone!",
|
|
"shows off expert handling!",
|
|
"is keeping it smooth!",
|
|
"has the ball right where they want it!",
|
|
"demonstrates a gentle touch!",
|
|
"is keeping the game slow!",
|
|
"shows off calm concentration!",
|
|
"has everything under control!",
|
|
"is moving with grace!",
|
|
"keeps the ball softly hovering!",
|
|
"shows off true talent!",
|
|
"is maintaining perfect flow!",
|
|
"has a magical touch!",
|
|
"is playing it smart!",
|
|
"keeps the ball very close!",
|
|
"shows off professional control!",
|
|
"is handling the pressure well!",
|
|
"has a very steady hand!",
|
|
"is keeping it precise!",
|
|
"shows off masterful technique!",
|
|
"is keeping the ball in check!",
|
|
"has the perfect feel for it!"
|
|
])
|
|
if self.touches_indicator is not None and self.touches_indicator.exists():
|
|
self.touches_indicator.position = (self._puck.node.position[0], self._puck.node.position[1] + 0.35,
|
|
self._puck.node.position[2])
|
|
else:
|
|
self.touches_indicator = bs.newnode(
|
|
'text',
|
|
attrs=dict(
|
|
text='',
|
|
scale=0.025,
|
|
position=(0, 3, -5.8),
|
|
in_world=True,
|
|
flatness=1,
|
|
shadow=0,
|
|
color=(1, 1, 1, 0.5),
|
|
h_align='center',
|
|
v_align='top',
|
|
h_attach='center',
|
|
v_attach='top',
|
|
))
|
|
|
|
everything = bs.getnodes()
|
|
everyone = []
|
|
closest_distance = 9999
|
|
closest_player = None
|
|
|
|
# i won't explain what "curve" is... look by yourself
|
|
if not self.curve and not self._puck.scored:
|
|
if bs.time() < self.smashed:
|
|
random_pos = (random.uniform(
|
|
0.1, 0.7) + self._puck.node.position[0], self._puck.node.position[1], random.uniform(0.1, 0.7) + self._puck.node.position[2])
|
|
e = bs.newnode(
|
|
'explosion',
|
|
attrs={
|
|
'position': random_pos,
|
|
'color': self.smasher.color,
|
|
'radius': random.uniform(0.1, 0.7),
|
|
'big': False,
|
|
}
|
|
)
|
|
bs.timer(1, e.delete)
|
|
self.smasher.getdelegate(object).percentage /= 1.1
|
|
else:
|
|
ball_tex = self._puck.node.color_texture
|
|
|
|
texture_to_name = {
|
|
green: 'green',
|
|
yellow: 'yellow',
|
|
red: 'red',
|
|
blue: 'blue'
|
|
}
|
|
|
|
colors = {
|
|
'green': (0.6, 1, 0.6),
|
|
'yellow': (1, 0.3, 0),
|
|
'red': (1, 0, 0),
|
|
'blue': (0.6, 0.6, 1)
|
|
}
|
|
|
|
color_name = texture_to_name.get(ball_tex)
|
|
color = colors.get(color_name)
|
|
|
|
e = bs.newnode(
|
|
'explosion',
|
|
attrs={
|
|
'position': self._puck.node.position,
|
|
'color': color,
|
|
'radius': 0.1,
|
|
'big': False,
|
|
}
|
|
)
|
|
indicator_color = (*color, 0.5)
|
|
bs.timer(1, e.delete)
|
|
self.touches_indicator.color = indicator_color
|
|
self.touches_indicator.text = str(3 - self._puck.touches)
|
|
|
|
for human in everything: # doesn't make much sense, but I hope u understand I want players nodes.
|
|
if human.getnodetype() == "spaz":
|
|
everyone.append(human)
|
|
|
|
for player_node in everyone:
|
|
if not player_node.exists(): # why wouldn't u exist at first
|
|
continue
|
|
|
|
# not wanting to do that Euclidean thingy
|
|
distance = math.dist(player_node.position, self._puck.node.position)
|
|
player_node.hold_node = None
|
|
player_node.getdelegate(object).impact_scale = 0
|
|
if distance < closest_distance:
|
|
closest_player = player_node # him, the closest one
|
|
closest_distance = distance # his distance with the volleyball
|
|
|
|
good: bool = (self._puck.touches < 3 and
|
|
bs.time() - self.last_interaction_time > 0.4 and
|
|
not self._puck.scored)
|
|
# bs.time() is the current bombsquad time :) bs for bombsquad.
|
|
|
|
delegate = closest_player.getdelegate(object)
|
|
now = bs.time()
|
|
can_jump = (delegate.last_jump_time_ms >
|
|
delegate._jump_cooldown and closest_player.position[1] < 1)
|
|
|
|
if closest_player.jump_pressed and can_jump:
|
|
ppos = closest_player.position
|
|
delegate.last_jump_time_ms = int(bs.time() * 1000)
|
|
closest_player.handlemessage(
|
|
'kick_back',
|
|
ppos[0],
|
|
ppos[1],
|
|
ppos[2],
|
|
0,
|
|
1,
|
|
0,
|
|
50, # small force
|
|
)
|
|
|
|
punched = now < delegate.punched
|
|
pickup = now < delegate.pickup
|
|
bombed = now < delegate.bombed
|
|
curved = now < delegate.curved
|
|
|
|
# SO if broski is the nearest AND broski is near enough...
|
|
if closest_player and closest_distance < 4 and good:
|
|
player = spaz = delegate
|
|
ball = self._puck.node # ball node
|
|
you = closest_player.position # already a node
|
|
in_air = (you[1] > 0.8)
|
|
# Ball goes where you look but always tries to go to the other side. (tell me if u guys want it to go totally where you're looking at)
|
|
if closest_distance < (1.35 if not in_air else 1.6) and (closest_player.punch_pressed or punched):
|
|
self.difficulty += 0.6 / 75
|
|
e = bs.newnode(
|
|
'explosion',
|
|
attrs={
|
|
'position': self._puck.node.position,
|
|
'color': closest_player.color,
|
|
'radius': 0.8,
|
|
'big': False,
|
|
}
|
|
)
|
|
bs.timer(1, e.delete)
|
|
self.last_interaction_time = bs.time()
|
|
self._puck.touches += 1
|
|
self.curve = False
|
|
if self._puck.touches == 2:
|
|
spaz.percentage = min(spaz.percentage + 8, 100)
|
|
ball.color_texture = yellow
|
|
self.two_sound.play()
|
|
elif self._puck.touches == 3:
|
|
spaz.percentage = min(spaz.percentage + 12, 100)
|
|
ball.color_texture = red
|
|
self.one_sound.play()
|
|
else:
|
|
ball.color_texture = green
|
|
# This is for the sound, you probably didn't know that!
|
|
bs.getsound("impactHard").play()
|
|
|
|
sight = (bs.Vec3(you) - bs.Vec3(closest_player.position_forward)
|
|
).normalized() * (12 if delegate.percentage < 95 else 30) # Sight factor
|
|
speed = 6 if delegate.percentage < 95 else 20 # bar full
|
|
|
|
if curved:
|
|
start_pos = ball.position
|
|
end_pos = (sight[0] * 2, -1, -sight[2])
|
|
duration = 1.1
|
|
fps = 144
|
|
total_steps = int(duration * fps)
|
|
curved_time = bs.time() + 0.15
|
|
self.curving = True
|
|
|
|
z_offset = -4.0 if sight[0] < 0 else 4.0
|
|
|
|
def move_to_point(step):
|
|
if not ball.exists():
|
|
return
|
|
if ball.position[1] < 1 and bs.time() > curved_time:
|
|
return
|
|
if not self.curving:
|
|
return
|
|
|
|
t = step / total_steps
|
|
|
|
x = start_pos[0] + (end_pos[0] - start_pos[0]) * t
|
|
y = start_pos[1] + (end_pos[1] - start_pos[1]) * t
|
|
z = start_pos[2] + (end_pos[2] - start_pos[2]) * t
|
|
|
|
y += math.sin(t * math.pi) * 3.0
|
|
|
|
# Ajout de la courbe latérale
|
|
z += math.sin(t * math.pi) * z_offset
|
|
|
|
# --- LIMITE SUR L'AXE Z ---
|
|
# On force z à rester entre -5 et 5
|
|
z = max(-5.0, min(5.0, z))
|
|
# --------------------------
|
|
|
|
ball.position = (x, y, z)
|
|
|
|
if step < total_steps:
|
|
bs.timer(1 / fps, lambda: move_to_point(step + 1))
|
|
|
|
move_to_point(0)
|
|
else:
|
|
# normal
|
|
self.curving = False
|
|
if in_air and delegate.percentage > 95:
|
|
if closest_player.position[0] < 0:
|
|
vel = (12 * 1.6, 0, sight[2]) * speed
|
|
else:
|
|
vel = (-12 * 1.6, 0, sight[2]) * speed
|
|
else:
|
|
if closest_player.position[0] < 0:
|
|
vel = (4.8 * 1.6, 0, sight[2]) * speed
|
|
else:
|
|
vel = (-4.8 * 1.6, 0, sight[2]) * speed
|
|
|
|
# smth
|
|
dist_from_middle = math.dist(you, (0, 2, 0))
|
|
height = (max(dist_from_middle * 2.75, 7) * 0.8)
|
|
if in_air and delegate.percentage > 95:
|
|
height = -4 * 2 / dist_from_middle
|
|
|
|
ball.velocity = (vel[0], height, vel[2])
|
|
if in_air and delegate.percentage > 95:
|
|
audio = random.choice(['explosion01', 'explosion02',
|
|
'explosion03', 'explosion04', 'explosion05'])
|
|
bs.getsound(audio).play()
|
|
self.smashed = bs.time() + 1
|
|
self.smasher = closest_player
|
|
self.vignette(closest_player.color, False)
|
|
delegate.on_punch_press()
|
|
delegate.on_punch_release()
|
|
self.write(
|
|
f'{player.name.upper()} SMASHED THE BALL !' if random.random() < 0.5 else f'{player.name} hit the ball with all his might !', closest_player.color)
|
|
else:
|
|
self.write(
|
|
f'{player.name} {self.hit_text}', closest_player.color)
|
|
|
|
# prettiness here
|
|
ppos = you
|
|
f = (bs.Vec3(ball.position) - bs.Vec3(you)).normalized()
|
|
closest_player.handlemessage(
|
|
'kick_back',
|
|
ppos[0],
|
|
ppos[1],
|
|
ppos[2],
|
|
f[0],
|
|
f[1],
|
|
f[2],
|
|
400, # small force
|
|
)
|
|
# Ball goes straight up in the air. idk what it is called.
|
|
if closest_distance < 1.35 and (closest_player.pickup_pressed or pickup):
|
|
self.write(
|
|
f'{player.name} {self.control_text}', closest_player.color)
|
|
y_diff = abs(self._puck.node.position[1] - closest_player.position[1])
|
|
|
|
if y_diff < 1:
|
|
self.difficulty += 0.6 / 75
|
|
self.last_interaction_time = bs.time()
|
|
self.curve = False
|
|
|
|
self._puck.touches += 1
|
|
|
|
ball = self._puck.node
|
|
if self._puck.touches == 2:
|
|
spaz.percentage = min(spaz.percentage + 4, 100)
|
|
ball.color_texture = yellow
|
|
self.two_sound.play()
|
|
elif self._puck.touches == 3:
|
|
spaz.percentage = min(spaz.percentage + 6, 100)
|
|
ball.color_texture = red
|
|
self.one_sound.play()
|
|
else:
|
|
ball.color_texture = green
|
|
|
|
closest_player.handlemessage('celebrate', 400)
|
|
bs.getsound("impactMedium").play()
|
|
e = bs.newnode(
|
|
'explosion',
|
|
attrs={
|
|
'position': self._puck.node.position,
|
|
'color': closest_player.color,
|
|
'radius': 0.5,
|
|
'big': True,
|
|
}
|
|
)
|
|
bs.timer(1, e.delete)
|
|
|
|
direction = (bs.Vec3(you) -
|
|
bs.Vec3(closest_player.position_forward)).normalized()
|
|
ball.velocity = (-you[0] * 0.04, 12.8, direction[2] * 0.1)
|
|
if closest_player.bomb_pressed and not delegate.gliding or bombed and not delegate.gliding: # gliding.. variant coming soon
|
|
self.write(
|
|
f'{player.name} {self.save_text}', closest_player.color)
|
|
delegate.gliding = True
|
|
ppos = you
|
|
f = (bs.Vec3(ball.position) - bs.Vec3(you)).normalized()
|
|
distance = math.dist(ball.position, you)
|
|
closest_player.handlemessage(
|
|
'kick_back',
|
|
ppos[0],
|
|
ppos[1],
|
|
ppos[2],
|
|
f[0],
|
|
0,
|
|
f[2],
|
|
1000 - (500/distance), # tiny force
|
|
)
|
|
closest_player.handlemessage('knockout', 100)
|
|
print(1)
|
|
spaz.pickup = now + 0.8
|
|
|
|
bs.getsound("impactHard").play(volume=0.5)
|
|
e = bs.newnode(
|
|
'explosion',
|
|
attrs={
|
|
'position': you,
|
|
'color': (0, 0.3, 0.5),
|
|
'radius': 1.1,
|
|
'big': False,
|
|
}
|
|
)
|
|
bs.timer(1, e.delete)
|
|
bs.timer(0.6, lambda: setattr(delegate, 'gliding', False))
|
|
|
|
# variant soon
|
|
|
|
def on_team_join(self, team: Team) -> None:
|
|
self._update_scoreboard()
|
|
|
|
def _handle_puck_player_collide(self) -> None:
|
|
collision = bs.getcollision()
|
|
try:
|
|
puck = collision.sourcenode.getdelegate(Puck, True)
|
|
player = collision.opposingnode.getdelegate(PlayerSpaz,
|
|
True).getplayer(
|
|
Player, True)
|
|
except bs.NotFoundError:
|
|
return
|
|
|
|
puck.last_players_to_touch[player.team.id] = player
|
|
|
|
def _kill_puck(self) -> None:
|
|
self._puck = None
|
|
self.curve = True
|
|
|
|
def _handle_score(self) -> None:
|
|
assert self._puck is not None
|
|
assert self._score_regions is not None
|
|
|
|
# Our puck might stick around for a second or two
|
|
# we don't want it to be able to score again
|
|
# to not be getting a corrupted score.
|
|
if self._puck.scored:
|
|
return
|
|
E = bs.newnode(
|
|
'explosion',
|
|
attrs={
|
|
'position': self._puck.node.position,
|
|
'color': (0, 0, 0),
|
|
'radius': .1,
|
|
'big': True,
|
|
}
|
|
)
|
|
bs.timer(1, E.delete)
|
|
bs.animate(self.touches_indicator, 'scale', {0: 0, 2.65: 0, 3: 0.025})
|
|
self.touches_indicator.text = '0'
|
|
if self.smashed > 0:
|
|
self._puck.node.sticky = True
|
|
self.smashed = -9999
|
|
|
|
region = bs.getcollision().sourcenode
|
|
index = 0
|
|
for index in range(len(self._score_regions)):
|
|
if region == self._score_regions[index].node:
|
|
break
|
|
|
|
for team in self.teams:
|
|
if team.id == index:
|
|
team.score += 1
|
|
|
|
# Puck Spawn
|
|
if team.id == 0: # left side scored
|
|
self._puck_spawn_pos = (5, 0.7, 0)
|
|
elif team.id == 1: # right side scored
|
|
self._puck_spawn_pos = (-5, 0.7, 0)
|
|
else: # what the heck
|
|
self._puck_spawn_pos = (0, 0.7, 0)
|
|
|
|
for player in team.players:
|
|
if player.actor:
|
|
player.actor.handlemessage(bs.CelebrateMessage(2.0)) # yay we scored
|
|
player.actor.percentage = min(player.actor.percentage + 15, 100)
|
|
# dont mind this ;(
|
|
scorch = bs.newnode(
|
|
'scorch',
|
|
attrs={
|
|
'position': self._puck.node.position,
|
|
'size': 1.4,
|
|
'color': player.actor.node.color,
|
|
'big': True,
|
|
},
|
|
)
|
|
bs.animate(scorch, 'presence', {3.000: 1, 7.000: 0})
|
|
bs.timer(7, scorch.delete)
|
|
self.vignette(player.actor.node.color, longer=True)
|
|
|
|
# End game if we won.
|
|
if team.score >= self._score_to_win:
|
|
self.end_game()
|
|
|
|
self._cheer_sound.play()
|
|
|
|
self._puck.scored = True
|
|
|
|
# Kill the puck (it'll respawn itself shortly).
|
|
# Animation ! 0 sec = 0.25 of size in our case
|
|
bs.animate(self._puck.node, 'mesh_scale', {0: 0.25, 0.4: 0})
|
|
bs.timer(0.7, self._kill_puck)
|
|
|
|
bs.cameraflash(duration=7.0)
|
|
self._update_scoreboard()
|
|
|
|
def end_game(self) -> None:
|
|
results = bs.GameResults()
|
|
for team in self.teams:
|
|
results.set_team_score(team, team.score)
|
|
self.interactions_timer = None
|
|
self.end(results=results)
|
|
|
|
def on_transition_in(self) -> None:
|
|
super().on_transition_in()
|
|
|
|
def _update_scoreboard(self) -> None:
|
|
winscore = self._score_to_win
|
|
for team in self.teams:
|
|
self._scoreboard.set_team_value(team, team.score, winscore)
|
|
|
|
# overriding the default character spawning..
|
|
def spawn_player(self, player: Player) -> bs.Actor:
|
|
spaz = self.spawn_player_spaz(player)
|
|
spaz.bomb_count = 0 # No bombs for yall bahaha
|
|
# Now we want to make that your punches are useless too
|
|
# But we will need the button to work so let's put the damage to 0 and some custom stuff
|
|
spaz._punch_power_scale = 0
|
|
spaz.impact_scale = 0
|
|
spaz.check_timer = 0
|
|
|
|
spaz.pickup = 0
|
|
spaz.punched = 0
|
|
spaz.bombed = 0
|
|
spaz.curved = 0
|
|
|
|
spaz.can_curve = False
|
|
|
|
spaz.last_angle = 0
|
|
spaz.total_rotation = 0.0
|
|
spaz.rotation_direction = 0 # 0: neutre, 1: clockwise, -1: not clockwise
|
|
|
|
spaz.gliding = False
|
|
spaz.smashed = False
|
|
spaz.percentage = 0
|
|
spaz._jump_cooldown = 2000
|
|
spaz.name = spaz.node.name
|
|
spaz.bar = bs.newnode(
|
|
'shield',
|
|
owner=spaz.node,
|
|
attrs={
|
|
'position': spaz.node.position,
|
|
'radius': .01,
|
|
'color': (0, 0, 0),
|
|
}
|
|
)
|
|
spaz.node.connectattr('position', spaz.bar, 'position')
|
|
spaz.bar.always_show_health_bar = True
|
|
spaz.node.name = '' # bahaha but you'll sspazuseful
|
|
|
|
def check():
|
|
if spaz.node.exists():
|
|
# Controls part
|
|
now = bs.time()
|
|
|
|
if spaz.node.punch_pressed:
|
|
spaz.punched = now + 0.25
|
|
elif spaz.node.pickup_pressed:
|
|
spaz.pickup = now + 0.40
|
|
elif spaz.node.bomb_pressed:
|
|
spaz.bombed = now + 0.20
|
|
elif spaz.node.jump_pressed and spaz.percentage > 95:
|
|
spaz.punched = now + 0.50
|
|
|
|
if spaz.percentage > 95:
|
|
spaz.node.name = 'SMASH'
|
|
else:
|
|
spaz.node.name = ''
|
|
|
|
spaz.bar.hurt = min(1.0 - float(spaz.percentage) / 100, 1)
|
|
|
|
# Curved ball part
|
|
|
|
pos = spaz.node.position
|
|
fwd = spaz.node.position_forward
|
|
|
|
dx = fwd[0] - pos[0]
|
|
dz = fwd[2] - pos[2]
|
|
|
|
length = math.sqrt(dx * dx + dz * dz)
|
|
if length > 0.0001:
|
|
dx /= length
|
|
dz /= length
|
|
|
|
spaz.angle = math.degrees(math.atan2(-dx, -dz)) # trust
|
|
|
|
# checks if u turn ! Not in the game yet !
|
|
def check_270_rotation():
|
|
now = bs.time()
|
|
current_angle = spaz.angle
|
|
|
|
delta = current_angle - spaz.last_angle
|
|
if delta > 180:
|
|
delta -= 360
|
|
if delta < -180:
|
|
delta += 360
|
|
|
|
current_dir = 1 if delta > 0 else -1
|
|
|
|
if spaz.rotation_direction != 0 and current_dir != spaz.rotation_direction:
|
|
spaz.total_rotation = 0
|
|
|
|
spaz.rotation_direction = current_dir
|
|
|
|
spaz.total_rotation += abs(delta)
|
|
|
|
if spaz.total_rotation >= 250:
|
|
spaz.curved = now + 0.40
|
|
spaz.total_rotation = 0
|
|
|
|
spaz.last_angle = current_angle
|
|
|
|
spaz.check_timer1 = bs.Timer(1/30, check, repeat=True)
|
|
# spaz.check_timer2 = bs.Timer(1/30, check_270_rotation, repeat=True)
|
|
|
|
# spaz.node.hold_node = spaz.node ... nevermind
|
|
|
|
# By the way when you have a node but want his player just do
|
|
# player = node.getdelegate(object)
|
|
# i dont know why you'll need it but here it is
|
|
|
|
return spaz
|
|
|
|
def handlemessage(self, msg: Any) -> Any:
|
|
|
|
# Respawn dead players if they're still in the game.
|
|
# Because if we don't.. they won't like our game ;(
|
|
if isinstance(msg, bs.PlayerDiedMessage):
|
|
# Augment standard behavior...
|
|
super().handlemessage(msg)
|
|
self.respawn_player(msg.getplayer(Player))
|
|
|
|
# Respawn dead pucks.
|
|
elif isinstance(msg, PuckDiedMessage):
|
|
if not self.has_ended():
|
|
bs.timer(2, self._spawn_puck) # 2 secs to breathe bahahaha
|
|
else:
|
|
super().handlemessage(msg)
|
|
|
|
def _spawn_puck(self) -> None:
|
|
self._swipsound.play()
|
|
self._whistle_sound.play()
|
|
assert self._puck_spawn_pos is not None
|
|
self._puck = Puck(position=self._puck_spawn_pos)
|
|
self.timer = bs.Timer(1 / 144, self.lock_it, repeat=True) # 144 ?.. yes
|
|
|
|
def lock_it(self):
|
|
if self._puck is None:
|
|
return
|
|
if self.curve:
|
|
self._puck.node.position = self._puck_spawn_pos
|
|
else:
|
|
self._puck.node.shadow_size = min(self._puck.node.position[1] * 0.3, 2)
|
|
|
|
|
|
# ba_meta export babase.Plugin
|
|
class justlearn(babase.Plugin):
|
|
def __init__(self):
|
|
self.installer = ModInstaller()
|
|
self.installer.run_full_install()
|
|
|
|
|
|
class ModInstaller:
|
|
def __init__(self) -> None:
|
|
self.python_user = _babase.env()["python_directory_user"]
|
|
self.files = self.python_user + '/VolleyBallMap/'
|
|
self.app_dir = _babase.env()["python_directory_app"] + '/'
|
|
self.data_dir = self.app_dir + '../'
|
|
self.meshes_dir = self.data_dir + 'meshes/'
|
|
self.platform = _babase.app.classic.platform
|
|
|
|
def run_full_install(self) -> None:
|
|
import os
|
|
import urllib.request
|
|
import shutil
|
|
import babase
|
|
|
|
target_path = os.path.join(self.files, 'volleyball_net.bob')
|
|
destination_path = os.path.join(self.meshes_dir, 'volleyball_net.bob')
|
|
|
|
if os.path.exists(destination_path):
|
|
return
|
|
|
|
def _do_install():
|
|
try:
|
|
if not os.path.exists(self.files):
|
|
os.makedirs(self.files)
|
|
|
|
url = "https://raw.githubusercontent.com/Scriptz1/Learn.py-Installer/main/volleyball_net.bob"
|
|
urllib.request.urlretrieve(url, target_path)
|
|
shutil.copy(target_path, destination_path)
|
|
|
|
bui.screenmessage("Installation worked !", color=(0, 1, 0.3))
|
|
bui.getsound('ding').play()
|
|
|
|
except Exception as e:
|
|
print(f"DEBUG ERROR: {e}")
|
|
bui.screenmessage("Installation Failed!", color=(1, 0, 0))
|
|
bui.getsound('kronk2').play()
|
|
|
|
bs.apptimer(2.5, _do_install)
|