2025-10-28 16:42:56 +00:00
"""
Bots Fighting game . Pretty cool for if u want to bet with your mates , or let fate ( randomess ) decide on the winning team .
- credits -
made by rabbitboom , aka Johnny仔 ( YouTube ) or iCommerade ( gaming world ( ? ? ? ) )
original idea by Froshlee08 ( hence a bot named in his honour )
shoutout to EraOS who gave me quite a lot of solution and Brotherboard who also helped .
"""
# ba_meta require api 9
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING , override
2025-10-28 16:44:49 +00:00
import logging
import random
import weakref
2025-10-28 16:42:56 +00:00
import bascenev1 as bs
import efro . debug as Edebug
from bascenev1lib . actor . spaz import Spaz , PunchHitMessage
from bascenev1lib . actor . playerspaz import PlayerSpaz
from bascenev1lib . actor . spazfactory import SpazFactory
from bascenev1lib . actor . spazbot import (
SpazBotDiedMessage ,
SpazBotSet ,
ChargerBot ,
StickyBot ,
SpazBot ,
BrawlerBot ,
TriggerBot ,
BrawlerBotProShielded ,
ChargerBotProShielded ,
BouncyBot ,
TriggerBotProShielded ,
BomberBotProShielded ,
)
if TYPE_CHECKING :
from typing import Any , Sequence , Optional , List
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
class Player ( bs . Player [ ' Team ' ] ) :
""" Our player type for this game. """
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
class SpazBot2 ( SpazBot ) :
2025-10-28 16:44:49 +00:00
# custom bots that have custom behaviours requiring more msg and modules from spaz.py will have them imported here.
2025-10-28 16:42:56 +00:00
from bascenev1lib . actor . spaz import Spaz , PunchHitMessage
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
class BouncyBotSemiLite ( BouncyBot ) :
2025-10-28 16:44:49 +00:00
highlight = ( 1 , 1 , 0.8 )
2025-10-28 16:42:56 +00:00
punchiness = 0.85
run = False
default_boxing_gloves = False
charge_dist_min = 5
points_mult = 1
class FroshBot ( SpazBot ) :
2025-10-28 16:44:49 +00:00
# Slightly beefier version of BomberBots, with slightly altered behaviour.
color = ( 0.13 , 0.13 , 0.13 )
highlight = ( 0.2 , 1 , 1 )
2025-10-28 16:42:56 +00:00
character = ' Bernard '
run = True
default_bomb_count = 2
throw_rate = 0.8
throwiness = 0.2
punchiness = 0.85
charge_dist_max = 3.0
charge_speed_min = 0.3
default_hitpoints = 1300
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
class FroshBotShielded ( FroshBot ) :
default_shields = True
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
class StickyBotShielded ( StickyBot ) :
default_shields = True
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
class IcePuncherBot ( SpazBot2 ) :
2025-10-28 16:44:49 +00:00
# A bot who can freeze anyone on punch and is immune to freezing.
color = ( 0 , 0 , 1 )
highlight = ( .2 , .2 , 1 )
2025-10-28 16:42:56 +00:00
character = ' Pascal '
run = True
punchiness = 0.8
charge_dist_min = 6
charge_dist_max = 9999
charge_speed_min = 0.3
charge_speed_max = 0.85
throw_dist_min = 9999
throw_dist_max = 9999
def handlemessage ( self , msg ) :
2025-10-28 16:44:49 +00:00
# Add the ability to freeze anyone on punch.
2025-10-28 16:42:56 +00:00
if isinstance ( msg , self . PunchHitMessage ) :
node = bs . getcollision ( ) . opposingnode
try :
node . handlemessage ( bs . FreezeMessage ( ) )
bs . getsound ( ' freeze ' ) . play ( )
except Exception :
print ( ' freeze failed. ' )
return super ( ) . handlemessage ( msg )
elif isinstance ( msg , bs . FreezeMessage ) :
2025-10-28 16:44:49 +00:00
pass # resistant to freezing.
2025-10-28 16:42:56 +00:00
else :
return super ( ) . handlemessage ( msg )
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
class IcePuncherBotShielded ( IcePuncherBot ) :
default_shields = True
class GliderBot ( BrawlerBot ) :
2025-10-28 16:44:49 +00:00
# A bot who can glide on terrain and navigate it safely as if it's ice skating.
color = ( 0.5 , 0.5 , 0.5 )
highlight = ( 0 , 10 , 0 )
2025-10-28 16:42:56 +00:00
character = ' B-9000 '
charge_speed_min = 0.3
charge_speed_max = 1.0
def __init__ ( self ) - > None :
super ( ) . __init__ ( )
2025-10-28 16:44:49 +00:00
self . node . hockey = True # Added the ability to move fast "like in hockey".
2025-10-28 16:42:56 +00:00
class GliderBotShielded ( GliderBot ) :
2025-10-28 16:44:49 +00:00
# Shielded version of GliderBot.
2025-10-28 16:42:56 +00:00
default_shields = True
class TeamBotSet ( SpazBotSet ) :
""" Bots that can gather in a team and kill other bots. """
activity : BotsVSBotsGame
2025-10-28 16:44:49 +00:00
def __init__ ( self , team : Team ) - > None :
2025-10-28 16:42:56 +00:00
super ( ) . __init__ ( )
activity = bs . getactivity ( )
self . _pro_bots = activity . _pro_bots
self . _bonus_bots = activity . _bonus_bots
self . _punching_bots_only = activity . _punching_bots_only
self . spawn_time = activity . spawn_time
self . _team = weakref . ref ( team )
def colorforteam ( self , spaz ) - > None :
spaz . node . color = self . team . color
2025-10-28 16:44:49 +00:00
def yell ( self ) - > None :
2025-10-28 16:42:56 +00:00
for botlist in self . _bot_lists :
for bot in botlist :
if bot :
assert bot . node
self . celebrate ( self . spawn_time )
bs . Call ( bot . node . handlemessage , ' jump_sound ' )
bs . timer ( 0 , bs . Call ( bot . node . handlemessage , ' attack_sound ' ) )
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 = [ ]
logging . exception (
' Error updating bot list: %s ' ,
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 = [ ]
try :
for n in bs . getnodes ( ) :
if n . getnodetype ( ) == ' spaz ' :
s = n . getdelegate ( object )
if isinstance ( s , SpazBot ) :
if not s in self . get_living_bots ( ) :
if s . is_alive ( ) :
player_pts . append ( (
bs . Vec3 ( n . position ) ,
bs . Vec3 ( n . velocity ) ) )
except Exception :
logging . exception ( ' Error on bot-set _update. ' )
for bot in bot_list :
bot . set_player_points ( player_pts )
bot . update_ai ( )
def get_bot_type ( self ) :
bot_types = [
BrawlerBot ,
ChargerBot ,
BouncyBotSemiLite ,
2025-10-28 16:44:49 +00:00
]
2025-10-28 16:42:56 +00:00
if self . _punching_bots_only == False :
bot_types + = [
SpazBot ,
StickyBot ,
]
if self . _pro_bots == True :
bot_types + = [
BrawlerBotProShielded ,
ChargerBotProShielded ,
BouncyBot ,
2025-10-28 16:44:49 +00:00
]
2025-10-28 16:42:56 +00:00
if self . _punching_bots_only == False :
bot_types + = [
BomberBotProShielded ,
2025-10-28 16:44:49 +00:00
]
2025-10-28 16:42:56 +00:00
if self . _bonus_bots == True :
bot_types + = [
IcePuncherBot ,
GliderBot ,
]
if self . _punching_bots_only == False :
bot_types + = [
FroshBot ,
2025-10-28 16:44:49 +00:00
]
2025-10-28 16:42:56 +00:00
if self . _bonus_bots and self . _pro_bots == True :
bot_types + = [
IcePuncherBotShielded ,
GliderBotShielded ,
]
if self . _punching_bots_only == False :
bot_types + = [
FroshBotShielded ,
StickyBotShielded ,
2025-10-28 16:44:49 +00:00
]
return random . choice ( bot_types )
2025-10-28 16:42:56 +00:00
@property
def team ( self ) - > Team :
""" The bot ' s team. """
return self . _team ( )
class Team ( bs . Team [ TeamBotSet ] ) :
""" Our team type for this game. """
def __init__ ( self ) - > None :
self . score = 0
self . bots = TeamBotSet ( self )
# ba_meta export bascenev1.GameActivity
class BotsVSBotsGame ( bs . TeamGameActivity [ bs . Player , Team ] ) :
""" A game type based on acquiring kills. """
name = ' Bots VS Bots '
2025-10-28 16:44:49 +00:00
description = ' Sit down and enjoy mobs from your team fight the opposing team for you. \n Feel free to enjoy some popcorn with it or place a bet with your friends. ' # , self._score_to_win
2025-10-28 16:42:56 +00:00
@override
@classmethod
def get_available_settings (
cls , sessiontype : type [ bs . Session ]
) - > list [ bs . Setting ] :
settings = [
bs . IntSetting (
' Bots Per Team ' ,
min_value = 5 ,
default = 20 ,
increment = 5 ,
) ,
bs . BoolSetting ( ' Epic Mode ' , default = False ) ,
bs . BoolSetting ( ' Punchers only ' , default = False ) ,
bs . BoolSetting ( ' Pro bots ' , default = False ) ,
bs . BoolSetting ( ' Bonus bots ' , default = True ) ,
]
return settings
@override
@classmethod
def supports_session_type ( cls , sessiontype : type [ bs . Session ] ) - > bool :
return issubclass ( sessiontype , bs . DualTeamSession )
2025-10-28 16:44:49 +00:00
@override
2025-10-28 16:42:56 +00:00
@classmethod
def get_supported_maps ( cls , sessiontype : Type [ bs . Session ] ) - > List [ str ] :
return [ ' Football Stadium ' ]
def __init__ ( self , settings : dict ) :
super ( ) . __init__ ( settings )
self . _bots_per_team = int ( settings [ ' Bots Per Team ' ] )
self . _dingsound = bs . getsound ( ' dingSmall ' )
self . _punching_bots_only = bool ( settings [ ' Punchers only ' ] )
self . _pro_bots = bool ( settings [ ' Pro bots ' ] )
self . _bonus_bots = bool ( settings [ ' Bonus bots ' ] )
self . _epic_mode = bool ( settings [ ' Epic Mode ' ] )
self . _cheersound = bs . getsound ( ' cheer ' )
self . _marchsoundA = bs . getsound ( ' footimpact01 ' )
self . _marchsoundB = bs . getsound ( ' footimpact02 ' )
self . _marchsoundC = bs . getsound ( ' footimpact03 ' )
# Base class overrides.
self . slow_motion = self . _epic_mode
self . spawn_time = 1.6 if self . _epic_mode else 4.0
self . default_music = (
bs . MusicType . TO_THE_DEATH
)
@override
def get_instance_description ( self ) - > str | Sequence :
return ' Enjoy the battle. '
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
@override
def get_instance_description_short ( self ) - > str | Sequence :
return ' Enjoy the battle. '
2025-10-28 16:44:49 +00:00
2025-10-28 16:42:56 +00:00
@override
def on_team_join ( self , team : Team ) - > None :
self . spawn_team ( team )
@override
def on_begin ( self ) - > None :
bs . TeamGameActivity . on_begin ( self )
def spawn_player ( self , player : Player ) - > None :
return None
2025-10-28 16:44:49 +00:00
# def team_pois_set
2025-10-28 16:42:56 +00:00
def spawn_team ( self , team : Team ) - > None :
camp = self . map . get_flag_position ( team . id )
for i in range ( self . _bots_per_team ) :
team . bots . spawn_bot ( team . bots . get_bot_type ( ) ,
2025-10-28 16:44:49 +00:00
( camp [ 0 ] , camp [ 1 ] + 1.8 , random . randrange ( - 4 , 5 ) ) ,
self . spawn_time , team . bots . colorforteam )
2025-10-28 16:42:56 +00:00
bs . timer ( float ( self . spawn_time ) + 1.7 , team . bots . yell )
def update ( self ) - > None :
if len ( self . _get_living_teams ( ) ) < 2 :
self . _round_end_timer = bs . Timer ( 0 , self . end_game )
def _get_living_teams ( self ) - > list [ Team ] :
return [
team
for team in self . teams
if team . bots . have_living_bots ( )
]
@override
def handlemessage ( self , msg : Any ) - > Any :
if isinstance ( msg , SpazBotDiedMessage ) :
bs . pushcall ( self . update )
else :
return super ( ) . handlemessage ( msg )
return None
@override
def end_game ( self ) - > None :
if self . has_ended ( ) :
return
results = bs . GameResults ( )
self . _cheersound . play ( )
for team in self . teams :
team . bots . final_celebrate ( )
results . set_team_score ( team , len ( team . bots . get_living_bots ( ) ) )
self . end ( results = results )