2026-05-31 20:43:43 -04:00
# 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
2026-06-01 20:25:17 -04:00
import babase
import _babase
import math
import random
2026-05-31 20:43:43 -04:00
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 (
2026-06-01 20:25:17 -04:00
plugin_name = " volley_punch " ,
2026-05-31 20:43:43 -04:00
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 " } ,
] ,
2026-06-01 20:26:09 -04:00
version = " 1.1.0 " ,
2026-05-31 20:43:43 -04:00
)
2026-06-01 20:25:17 -04:00
2026-05-31 20:43:43 -04:00
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 ' )
2026-06-01 20:25:17 -04:00
self . three_sound = bs . getsound ( ' announceThree ' ) # unused.. yet
2026-05-31 20:43:43 -04:00
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 ) ) )
2026-06-01 20:25:17 -04:00
self . _fake_wall_material = bs . Material ( ) # i'll fix this. trust
2026-05-31 20:43:43 -04:00
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 ' ] )
2026-06-01 20:25:17 -04:00
self . credit_text = bool ( settings [ ' Enable Credits ' ] ) # i did the freaku credit thing
2026-05-31 20:43:43 -04:00
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 )
2026-06-01 20:25:17 -04:00
self . s : list = [ ]
self . w : list = [ ]
self . n : list = [ ]
2026-05-31 20:43:43 -04:00
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 )
2026-06-01 20:25:17 -04:00
self . _puck_spawn_pos = ( - 4 , 1 , 0 ) if random . random ( ) < 0.5 else ( 4 , 1 , 0 )
2026-05-31 20:43:43 -04:00
self . _spawn_puck ( )
# Set up the two score regions.
self . _score_regions = [ ]
self . commentary_text = bs . newnode (
2026-06-01 20:25:17 -04:00
' 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 ' ,
) )
2026-05-31 20:43:43 -04:00
self . touches_indicator = bs . newnode (
2026-06-01 20:25:17 -04:00
' 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 ' ,
) )
2026-05-31 20:43:43 -04:00
self . guide = bs . newnode (
2026-06-01 20:25:17 -04:00
' text ' ,
attrs = dict (
text = ' PUNCH TO TAP THE BALL \n PICKUP TO CUFF \n BOMB TO GLIDE / DIVE \n JUMP UNDER A MID AIR BALL WITH THE BAR FILLED TO SMASH \n THE 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
2026-05-31 20:43:43 -04:00
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 ' } )
2026-06-01 20:25:17 -04:00
# Create the net and regions.
mat = bs . Material ( ) # spam this whenever u want a material THEN add some properties
2026-05-31 20:43:43 -04:00
mat . add_actions ( actions = ( ' modify_part_collision ' ,
2026-06-01 20:25:17 -04:00
' collide ' , False ) )
2026-05-31 20:43:43 -04:00
net = bs . newnode (
' prop ' ,
attrs = {
' body ' : ' puck ' ,
' body_scale ' : 0 ,
2026-06-01 20:25:17 -04:00
' position ' : ( 0 , 0 , 0 ) ,
2026-05-31 20:43:43 -04:00
' 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 ' : (
2026-06-01 20:25:17 -04:00
20 , 20 , 0.8 ) , ' type ' : ' box ' , ' materials ' : ( self . _net_wall_material , self . _net_material ) } ) ) )
2026-05-31 20:43:43 -04:00
self . n . append ( bs . NodeActor ( bs . newnode ( ' region ' , attrs = { ' position ' : ( 0 , 0 , 5.9 ) , ' scale ' : (
2026-06-01 20:25:17 -04:00
20 , 20 , 0.8 ) , ' type ' : ' box ' , ' materials ' : ( self . _net_wall_material , self . _net_material ) } ) ) )
2026-05-31 20:43:43 -04:00
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 ]
2026-06-01 20:25:17 -04:00
self . _timers . append (
bs . Timer ( 0.9 + 3.0 + ( 0.015 * ( total_chars - i ) ) , hide , repeat = False ) )
2026-05-31 20:43:43 -04:00
start_clear ( )
def vignette ( self , final_color , longer ) :
""" Aesthetic reasons. """
glb = bs . getactivity ( ) . globalsnode
2026-06-01 20:25:17 -04:00
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 } )
2026-05-31 20:43:43 -04:00
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 :
2026-06-01 20:25:17 -04:00
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 ] )
2026-05-31 20:43:43 -04:00
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 )
2026-06-01 20:25:17 -04:00
for human in everything : # doesn't make much sense, but I hope u understand I want players nodes.
2026-05-31 20:43:43 -04:00
if human . getnodetype ( ) == " spaz " :
everyone . append ( human )
for player_node in everyone :
2026-06-01 20:25:17 -04:00
if not player_node . exists ( ) : # why wouldn't u exist at first
2026-05-31 20:43:43 -04:00
continue
2026-06-01 20:25:17 -04:00
# not wanting to do that Euclidean thingy
distance = math . dist ( player_node . position , self . _puck . node . position )
2026-05-31 20:43:43 -04:00
player_node . hold_node = None
player_node . getdelegate ( object ) . impact_scale = 0
if distance < closest_distance :
2026-06-01 20:25:17 -04:00
closest_player = player_node # him, the closest one
closest_distance = distance # his distance with the volleyball
2026-05-31 20:43:43 -04:00
2026-06-01 20:25:17 -04:00
good : bool = ( self . _puck . touches < 3 and
bs . time ( ) - self . last_interaction_time > 0.4 and
not self . _puck . scored )
2026-05-31 20:43:43 -04:00
# bs.time() is the current bombsquad time :) bs for bombsquad.
delegate = closest_player . getdelegate ( object )
now = bs . time ( )
2026-06-01 20:25:17 -04:00
can_jump = ( delegate . last_jump_time_ms >
delegate . _jump_cooldown and closest_player . position [ 1 ] < 1 )
2026-05-31 20:43:43 -04:00
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
2026-06-01 20:25:17 -04:00
# SO if broski is the nearest AND broski is near enough...
if closest_player and closest_distance < 4 and good :
2026-05-31 20:43:43 -04:00
player = spaz = delegate
ball = self . _puck . node # ball node
you = closest_player . position # already a node
in_air = ( you [ 1 ] > 0.8 )
2026-06-01 20:25:17 -04:00
# 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 ) :
2026-05-31 20:43:43 -04:00
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
2026-06-01 20:25:17 -04:00
# This is for the sound, you probably didn't know that!
bs . getsound ( " impactHard " ) . play ( )
2026-05-31 20:43:43 -04:00
2026-06-01 20:25:17 -04:00
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
2026-05-31 20:43:43 -04:00
2026-06-01 20:25:17 -04:00
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
2026-05-31 20:43:43 -04:00
2026-06-01 20:25:17 -04:00
z_offset = - 4.0 if sight [ 0 ] < 0 else 4.0
2026-05-31 20:43:43 -04:00
2026-06-01 20:25:17 -04:00
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 )
2026-05-31 20:43:43 -04:00
else :
2026-06-01 20:25:17 -04:00
# 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
2026-05-31 20:43:43 -04:00
else :
2026-06-01 20:25:17 -04:00
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 ] )
2026-05-31 20:43:43 -04:00
if in_air and delegate . percentage > 95 :
2026-06-01 20:25:17 -04:00
audio = random . choice ( [ ' explosion01 ' , ' explosion02 ' ,
' explosion03 ' , ' explosion04 ' , ' explosion05 ' ] )
2026-05-31 20:43:43 -04:00
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 )
2026-06-01 20:25:17 -04:00
# prettiness here
2026-05-31 20:43:43 -04:00
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
)
2026-06-01 20:25:17 -04:00
# Ball goes straight up in the air. idk what it is called.
if closest_distance < 1.35 and ( closest_player . pickup_pressed or pickup ) :
2026-05-31 20:43:43 -04:00
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 )
2026-06-01 20:25:17 -04:00
direction = ( bs . Vec3 ( you ) -
bs . Vec3 ( closest_player . position_forward ) ) . normalized ( )
2026-05-31 20:43:43 -04:00
ball . velocity = ( - you [ 0 ] * 0.04 , 12.8 , direction [ 2 ] * 0.1 )
2026-06-01 20:25:17 -04:00
if closest_player . bomb_pressed and not delegate . gliding or bombed and not delegate . gliding : # gliding.. variant coming soon
2026-05-31 20:43:43 -04:00
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 ,
2026-06-01 20:25:17 -04:00
' color ' : ( 0 , 0 , 0 ) ,
2026-05-31 20:43:43 -04:00
' 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 :
2026-06-01 20:25:17 -04:00
player . actor . handlemessage ( bs . CelebrateMessage ( 2.0 ) ) # yay we scored
2026-05-31 20:43:43 -04:00
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).
2026-06-01 20:25:17 -04:00
# Animation ! 0 sec = 0.25 of size in our case
bs . animate ( self . _puck . node , ' mesh_scale ' , { 0 : 0.25 , 0.4 : 0 } )
2026-05-31 20:43:43 -04:00
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 )
2026-06-01 20:25:17 -04:00
spaz . bomb_count = 0 # No bombs for yall bahaha
2026-05-31 20:43:43 -04:00
# 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
2026-06-01 20:25:17 -04:00
spaz . rotation_direction = 0 # 0: neutre, 1: clockwise, -1: not clockwise
2026-05-31 20:43:43 -04:00
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
2026-06-01 20:25:17 -04:00
spaz . node . name = ' ' # bahaha but you'll sspazuseful
2026-05-31 20:43:43 -04:00
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
2026-06-01 20:25:17 -04:00
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
2026-05-31 20:43:43 -04:00
2026-06-01 20:25:17 -04:00
delta = current_angle - spaz . last_angle
if delta > 180 :
delta - = 360
if delta < - 180 :
delta + = 360
2026-05-31 20:43:43 -04:00
2026-06-01 20:25:17 -04:00
current_dir = 1 if delta > 0 else - 1
2026-05-31 20:43:43 -04:00
2026-06-01 20:25:17 -04:00
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
2026-05-31 20:43:43 -04:00
spaz . check_timer1 = bs . Timer ( 1 / 30 , check , repeat = True )
2026-06-01 20:25:17 -04:00
# 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
2026-05-31 20:43:43 -04:00
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 ( ) :
2026-06-01 20:25:17 -04:00
bs . timer ( 2 , self . _spawn_puck ) # 2 secs to breathe bahahaha
2026-05-31 20:43:43 -04:00
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 )
2026-06-01 20:25:17 -04:00
self . timer = bs . Timer ( 1 / 144 , self . lock_it , repeat = True ) # 144 ?.. yes
2026-05-31 20:43:43 -04:00
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 ( )
2026-05-31 20:55:10 -04:00
bs . apptimer ( 2.5 , _do_install )