Add files via upload

This commit is contained in:
Loup 2022-11-03 22:59:16 +05:30 committed by GitHub
parent 2d29459177
commit d513809ad3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 3560 additions and 0 deletions

View file

@ -0,0 +1,754 @@
# Released under the MIT License. See LICENSE for details.
# ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba, _ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.actor.powerupbox import PowerupBoxFactory
from bastd.gameutils import SharedObjects
from bastd.actor import playerspaz as ps
from bastd import maps
if TYPE_CHECKING:
from typing import Any, Sequence, Dict, Type, List, Optional, Union
bsuSpaz = None
def getlanguage(text, sub: str = ''):
lang = _ba.app.lang.language
translate = {
"Name":
{"Spanish": "Baloncesto",
"English": "Basketbomb",
"Portuguese": "Basketbomb"},
"Info":
{"Spanish": "Anota todas las canastas y sé el MVP.",
"English": "Score all the baskets and be the MVP.",
"Portuguese": "Marque cada cesta e seja o MVP."},
"Info-Short":
{"Spanish": f"Anota {sub} canasta(s) para ganar",
"English": f"Score {sub} baskets to win",
"Portuguese": f"Cestas de {sub} pontos para ganhar"},
"S: Powerups":
{"Spanish": "Aparecer Potenciadores",
"English": "Powerups Spawn",
"Portuguese": "Habilitar Potenciadores"},
"S: Velocity":
{"Spanish": "Activar velocidad",
"English": "Enable speed",
"Portuguese": "Ativar velocidade"},
}
languages = ['Spanish','Portuguese','English']
if lang not in languages: lang = 'English'
if text not in translate:
return text
return translate[text][lang]
class BallDiedMessage:
def __init__(self, ball: Ball):
self.ball = ball
class Ball(ba.Actor):
def __init__(self, position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
velocty = (0.0, 8.0, 0.0)
_scale = 1.2
self._spawn_pos = (position[0], position[1] + 0.5, position[2])
self.last_players_to_touch: Dict[int, Player] = {}
self.scored = False
assert activity is not None
assert isinstance(activity, BasketGame)
pmats = [shared.object_material, activity.ball_material]
self.node = ba.newnode('prop',
delegate=self,
attrs={
'model': activity.ball_model,
'color_texture': activity.ball_tex,
'body': 'sphere',
'reflection': 'soft',
'body_scale': 1.0 * _scale,
'reflection_scale': [1.3],
'shadow_size': 1.0,
'gravity_scale': 0.92,
'density': max(0.4 * _scale, 0.3),
'position': self._spawn_pos,
'velocity': velocty,
'materials': pmats})
self.scale = scale = 0.25 * _scale
ba.animate(self.node, 'model_scale', {0: 0, 0.2: scale*1.3, 0.26: scale})
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
assert self.node
self.node.delete()
activity = self._activity()
if activity and not msg.immediate:
activity.handlemessage(BallDiedMessage(self))
elif isinstance(msg, ba.OutOfBoundsMessage):
assert self.node
self.node.position = self._spawn_pos
self.node.velocity = (0.0, 0.0, 0.0)
elif isinstance(msg, ba.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])
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(ba.Player['Team']):
"""Our player type for this game."""
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
class Points:
postes = dict()
postes['pal_0'] = (10.64702320098877, 0.0000000000000000, 0.0000000000000000) #10.736066818237305, 0.3002409040927887, 0.5281256437301636
postes['pal_1'] = (-10.64702320098877, 0.0000000000000000, 0.0000000000000000)
# ba_meta export game
class BasketGame(ba.TeamGameActivity[Player, Team]):
name = getlanguage('Name')
description = getlanguage('Info')
available_settings = [
ba.IntSetting(
'Score to Win',
min_value=1,
default=1,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting(getlanguage('S: Powerups'), default=True),
ba.BoolSetting(getlanguage('S: Velocity'), default=False),
ba.BoolSetting('Epic Mode', default=False),
]
default_music = ba.MusicType.HOCKEY
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return issubclass(sessiontype, ba.DualTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ['BasketBall Stadium', 'BasketBall Stadium V2']
def __init__(self, settings: dict):
super().__init__(settings)
shared = SharedObjects.get()
self._scoreboard = Scoreboard()
self._cheer_sound = ba.getsound('cheer')
self._chant_sound = ba.getsound('crowdChant')
self._foghorn_sound = ba.getsound('foghorn')
self._swipsound = ba.getsound('swip')
self._whistle_sound = ba.getsound('refWhistle')
self.ball_model = ba.getmodel('shield')
self.ball_tex = ba.gettexture('fontExtras3')
self._ball_sound = ba.getsound('bunnyJump')
self._powerups = bool(settings[getlanguage('S: Powerups')])
self._speed = bool(settings[getlanguage('S: Velocity')])
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
self.ball_material = ba.Material()
self.ball_material.add_actions(actions=(('modify_part_collision',
'friction', 0.5)))
self.ball_material.add_actions(conditions=('they_have_material',
shared.pickup_material),
actions=('modify_part_collision',
'collide', True))
self.ball_material.add_actions(
conditions=(
('we_are_younger_than', 100),
'and',
('they_have_material', shared.object_material),
),
actions=('modify_node_collision', 'collide', False),
)
self.ball_material.add_actions(conditions=('they_have_material',
shared.footing_material),
actions=('impact_sound',
self._ball_sound, 0.2, 5))
# Keep track of which player last touched the ball
self.ball_material.add_actions(
conditions=('they_have_material', shared.player_material),
actions=(('call', 'at_connect',
self._handle_ball_player_collide), ))
self._score_region_material = ba.Material()
self._score_region_material.add_actions(
conditions=('they_have_material', self.ball_material),
actions=(('modify_part_collision', 'collide',
True), ('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_score)))
self._ball_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[ba.NodeActor]] = None
self._ball: Optional[Ball] = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
def get_instance_description(self) -> Union[str, Sequence]:
return getlanguage('Info-Short', sub=self._score_to_win)
def get_instance_description_short(self) -> Union[str, Sequence]:
return getlanguage('Info-Short', sub=self._score_to_win)
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
if self._powerups:
self.setup_standard_powerup_drops()
self._ball_spawn_pos = self.map.get_flag_position(None)
self._spawn_ball()
defs = self.map.defs
self._score_regions = []
self._score_regions.append(
ba.NodeActor(
ba.newnode('region',
attrs={
'position': defs.boxes['goal1'][0:3],
'scale': defs.boxes['goal1'][6:9],
'type': 'box',
'materials': []
})))
self._score_regions.append(
ba.NodeActor(
ba.newnode('region',
attrs={
'position': defs.boxes['goal2'][0:3],
'scale': defs.boxes['goal2'][6:9],
'type': 'box',
'materials': []
})))
self._update_scoreboard()
ba.playsound(self._chant_sound)
for id, team in enumerate(self.teams):
self.postes(id)
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def _handle_ball_player_collide(self) -> None:
collision = ba.getcollision()
try:
ball = collision.sourcenode.getdelegate(Ball, True)
player = collision.opposingnode.getdelegate(PlayerSpaz,
True).getplayer(
Player, True)
except ba.NotFoundError:
return
ball.last_players_to_touch[player.team.id] = player
def _kill_ball(self) -> None:
self._ball = None
def _handle_score(self, team_index: int = None) -> None:
assert self._ball is not None
assert self._score_regions is not None
if self._ball.scored:
return
region = ba.getcollision().sourcenode
index = 0
for index in range(len(self._score_regions)):
if region == self._score_regions[index].node:
break
if team_index is not None:
index = team_index
for team in self.teams:
if team.id == index:
scoring_team = team
team.score += 1
for player in team.players:
if player.actor:
player.actor.handlemessage(ba.CelebrateMessage(2.0))
if (scoring_team.id in self._ball.last_players_to_touch
and self._ball.last_players_to_touch[scoring_team.id]):
self.stats.player_scored(
self._ball.last_players_to_touch[scoring_team.id],
100, big_message=True)
if team.score >= self._score_to_win:
self.end_game()
#ba.playsound(self._foghorn_sound)
ba.playsound(self._cheer_sound)
self._ball.scored = True
# Kill the ball (it'll respawn itself shortly).
ba.timer(1.0, self._kill_ball)
light = ba.newnode('light',
attrs={
'position': ba.getcollision().position,
'height_attenuated': False,
'color': (1, 0, 0)
})
ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
ba.timer(1.0, light.delete)
ba.cameraflash(duration=10.0)
self._update_scoreboard()
def end_game(self) -> None:
results = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)
def _update_scoreboard(self) -> None:
winscore = self._score_to_win
for id, team in enumerate(self.teams):
self._scoreboard.set_team_value(team, team.score, winscore)
#self.postes(id)
def spawn_player(self, player: Player) -> ba.Actor:
if bsuSpaz is None:
spaz = self.spawn_player_spaz(player)
else:
ps.PlayerSpaz = bsuSpaz.BskSpaz
spaz = self.spawn_player_spaz(player)
ps.PlayerSpaz = bsuSpaz.OldPlayerSpaz
if self._speed:
spaz.node.hockey = True
return spaz
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
super().handlemessage(msg)
self.respawn_player(msg.getplayer(Player))
elif isinstance(msg, BallDiedMessage):
if not self.has_ended():
ba.timer(3.0, self._spawn_ball)
else:
super().handlemessage(msg)
def postes(self, team_id: int):
if not hasattr(self._map, 'poste_'+str(team_id)):
setattr(self._map, 'poste_'+str(team_id),
Palos(team=team_id,
position=Points.postes['pal_' +
str(team_id)]).autoretain())
def _flash_ball_spawn(self) -> None:
light = ba.newnode('light',
attrs={
'position': self._ball_spawn_pos,
'height_attenuated': False,
'color': (1, 0, 0)
})
ba.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
def _spawn_ball(self) -> None:
ba.playsound(self._swipsound)
ba.playsound(self._whistle_sound)
self._flash_ball_spawn()
assert self._ball_spawn_pos is not None
self._ball = Ball(position=self._ball_spawn_pos)
class Aro(ba.Actor):
def __init__(self, team: int = 0,
position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
act = self.getactivity()
shared = SharedObjects.get()
setattr(self, 'team', team)
setattr(self, 'locs', [])
# Material Para; Traspasar Objetos
self.no_collision = ba.Material()
self.no_collision.add_actions(
actions=(('modify_part_collision', 'collide', False)))
self.collision = ba.Material()
self.collision.add_actions(
actions=(('modify_part_collision', 'collide', True)))
# Score
self._score_region_material = ba.Material()
self._score_region_material.add_actions(
conditions=('they_have_material', act.ball_material),
actions=(('modify_part_collision', 'collide',
True), ('modify_part_collision', 'physical', False),
('call', 'at_connect', self._annotation)))
self._spawn_pos = (position[0], position[1], position[2])
self._materials_region0 = [self.collision,
shared.footing_material]
model = None
tex = ba.gettexture('null')
pmats = [self.no_collision]
self.node = ba.newnode('prop',
delegate=self,
attrs={
'model': model,
'color_texture': tex,
'body': 'box',
'reflection': 'soft',
'reflection_scale': [1.5],
'shadow_size': 0.1,
'position': self._spawn_pos,
'materials': pmats})
self.scale = scale = 1.4
ba.animate(self.node, 'model_scale', {0: 0})
pos = (position[0], position[1]+0.6, position[2])
self.regions: List[ba.Node] = [
ba.newnode('region',
attrs={'position': position,
'scale': (0.6, 0.05, 0.6),
'type': 'box',
'materials': self._materials_region0}),
ba.newnode('region',
attrs={'position': pos,
'scale': (0.5, 0.3, 0.9),
'type': 'box',
'materials': [self._score_region_material]})
]
self.regions[0].connectattr('position', self.node, 'position')
#self.regions[0].connectattr('position', self.regions[1], 'position')
locs_count = 9
pos = list(position)
try:
id = 0 if team == 1 else 1
color = act.teams[id].color
except: color = (1,1,1)
while locs_count > 1:
scale = (1.5 * 0.1 * locs_count) + 0.8
self.locs.append(ba.newnode('locator',
owner=self.node,
attrs={'shape': 'circleOutline',
'position': pos,
'color': color,
'opacity': 1.0,
'size': [scale],
'draw_beauty': True,
'additive': False}))
pos[1] -= 0.1
locs_count -= 1
def _annotation(self):
assert len(self.regions) >= 2
ball = self.getactivity()._ball
if ball:
p = self.regions[0].position
ball.node.position = p
ball.node.velocity = (0.0, 0.0, 0.0)
act = self.getactivity()
act._handle_score(self.team)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
if self.node.exists():
self.node.delete()
else:
super().handlemessage(msg)
class Cuadro(ba.Actor):
def __init__(self, team: int = 0,
position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
act = self.getactivity()
shared = SharedObjects.get()
setattr(self, 'locs', [])
self.collision = ba.Material()
self.collision.add_actions(
actions=(('modify_part_collision', 'collide', True)))
pos = (position[0], position[1]+0.9, position[2]+1.5)
self.region: ba.Node = ba.newnode('region',
attrs={'position': pos,
'scale': (0.5, 2.7, 2.5),
'type': 'box',
'materials': [self.collision,
shared.footing_material]})
#self.shield = ba.newnode('shield', attrs={'radius': 1.0, 'color': (0,10,0)})
#self.region.connectattr('position', self.shield, 'position')
position = (position[0], position[1], position[2]+0.09)
pos = list(position)
oldpos = list(position)
old_count = 14
count = old_count
count_y = 9
try:
id = 0 if team == 1 else 1
color = act.teams[id].color
except: color = (1,1,1)
while(count_y != 1):
while(count != 1):
pos[2] += 0.19
self.locs.append(
ba.newnode('locator',
owner=self.region,
attrs={'shape': 'circle',
'position': pos,
'size': [0.5],
'color': color,
'opacity': 1.0,
'draw_beauty': True,
'additive': False}))
count -= 1
count = old_count
pos[1] += 0.2
pos[2] = oldpos[2]
count_y -= 1
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
if self.node.exists():
self.node.delete()
else:
super().handlemessage(msg)
class Palos(ba.Actor):
def __init__(self, team: int = 0,
position: Sequence[float] = (0.0, 1.0, 0.0)):
super().__init__()
shared = SharedObjects.get()
activity = self.getactivity()
self._pos = position
self.aro = None
self.cua = None
# Material Para; Traspasar Objetos
self.no_collision = ba.Material()
self.no_collision.add_actions(
actions=(('modify_part_collision', 'collide', False)))
#
self.collision = ba.Material()
self.collision.add_actions(
actions=(('modify_part_collision', 'collide', True)))
# Spawn just above the provided point.
self._spawn_pos = (position[0], position[2]+2.5, position[2])
model = ba.getmodel('flagPole')
tex = ba.gettexture('flagPoleColor')
pmats = [self.no_collision]
self.node = ba.newnode('prop',
delegate=self,
attrs={
'model': model,
'color_texture': tex,
'body': 'puck',
'reflection': 'soft',
'reflection_scale': [2.6],
'shadow_size': 0,
'is_area_of_interest': True,
'position': self._spawn_pos,
'materials': pmats
})
self.scale = scale = 4.0
ba.animate(self.node, 'model_scale', {0: scale})
self.loc = ba.newnode('locator',
owner=self.node,
attrs={'shape': 'circle',
'position': position,
'color': (1,1,0),
'opacity': 1.0,
'draw_beauty': False,
'additive': True})
self._y = _y = 0.30
_x = -0.25 if team == 1 else 0.25
_pos = (position[0]+_x, position[1]-1.5 + _y, position[2])
self.region = ba.newnode('region',
attrs={
'position': _pos,
'scale': (0.4, 8, 0.4),
'type': 'box',
'materials': [self.collision]})
self.region.connectattr('position', self.node, 'position')
_y = self._y
position = self._pos
if team == 0:
pos = (position[0]-0.8, position[1] + 2.0 + _y, position[2])
else: pos = (position[0]+0.8, position[1] + 2.0 + _y, position[2])
if self.aro is None:
self.aro = Aro(team, pos).autoretain()
if self.cua is None:
pos = (position[0], position[1] + 1.8 + _y, position[2]-1.4)
self.cua = Cuadro(team, pos).autoretain()
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage):
if self.node.exists():
self.node.delete()
else:
super().handlemessage(msg)
class BasketMap(maps.FootballStadium):
name = 'BasketBall Stadium'
@classmethod
def get_play_types(cls) -> List[str]:
"""Return valid play types for this map."""
return []
def __init__(self) -> None:
super().__init__()
gnode = ba.getactivity().globalsnode
gnode.tint = [(0.806, 0.8, 1.0476), (1.3, 1.2, 1.0)][0]
gnode.ambient_color = (1.3, 1.2, 1.0)
gnode.vignette_outer = (0.57, 0.57, 0.57)
gnode.vignette_inner = (0.9, 0.9, 0.9)
gnode.vr_camera_offset = (0, -0.8, -1.1)
gnode.vr_near_clip = 0.5
class BasketMapV2(maps.HockeyStadium):
name = 'BasketBall Stadium V2'
def __init__(self) -> None:
super().__init__()
shared = SharedObjects.get()
self.node.materials = [shared.footing_material]
self.node.collide_model = ba.getcollidemodel('footballStadiumCollide')
self.node.model = None
self.stands.model = None
self.floor.reflection = 'soft'
self.floor.reflection_scale = [1.6]
self.floor.color = (1.1, 0.05, 0.8)
self.background = ba.newnode('terrain',
attrs={'model': ba.getmodel('thePadBG'),
'lighting': False,
'background': True,
'color': (1.0, 0.2, 1.0),
'color_texture': ba.gettexture('menuBG')})
gnode = ba.getactivity().globalsnode
gnode.floor_reflection = True
gnode.debris_friction = 0.3
gnode.debris_kill_height = -0.3
gnode.tint = [(1.2, 1.3, 1.33), (0.7, 0.9, 1.0)][1]
gnode.ambient_color = (1.15, 1.25, 1.6)
gnode.vignette_outer = (0.66, 0.67, 0.73)
gnode.vignette_inner = (0.93, 0.93, 0.95)
gnode.vr_camera_offset = (0, -0.8, -1.1)
gnode.vr_near_clip = 0.5
self.is_hockey = False
##################
self.collision = ba.Material()
self.collision.add_actions(
actions=(('modify_part_collision', 'collide', True)))
self.regions: List[ba.Node] = [
ba.newnode('region',
attrs={'position': (12.676897048950195, 0.2997918128967285, 5.583303928375244),
'scale': (1.01, 12, 28),
'type': 'box',
'materials': [self.collision]}),
ba.newnode('region',
attrs={'position': (11.871315956115723, 0.29975247383117676, 5.711406707763672),
'scale': (50, 12, 0.9),
'type': 'box',
'materials': [self.collision]}),
ba.newnode('region',
attrs={'position': (-12.776557922363281, 0.30036890506744385, 4.96237850189209),
'scale': (1.01, 12, 28),
'type': 'box',
'materials': [self.collision]}),
]
ba._map.register_map(BasketMap)
ba._map.register_map(BasketMapV2)

240
plugins/minigames/Boxing.py Normal file
View file

@ -0,0 +1,240 @@
# ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.game.deathmatch import DeathMatchGame
if TYPE_CHECKING:
from typing import Any, Sequence
lang = ba.app.lang.language
if lang == 'Spanish':
name = 'Super Boxeo'
description = ('¡Sin bombas!\n'
'¡Noquea a los enemigos con tus propias manos!\n')
super_jump_text = 'Super Salto'
enable_powerups = 'Habilitar Potenciadores'
else:
name = 'Super Boxing'
description = ('No bombs!\n'
'Knock out your enemies using your bare hands!\n')
super_jump_text = 'Super Jump'
enable_powerups = 'Enable Powerups'
class NewPlayerSpaz(PlayerSpaz):
def __init__(self,
player: ba.Player,
color: Sequence[float] = (1.0, 1.0, 1.0),
highlight: Sequence[float] = (0.5, 0.5, 0.5),
character: str = 'Spaz',
powerups_expire: bool = True,
super_jump: bool = False):
super().__init__(player=player,
color=color,
highlight=highlight,
character=character,
powerups_expire=powerups_expire)
from bastd.gameutils import SharedObjects
shared = SharedObjects.get()
self._super_jump = super_jump
self.jump_mode = False
self.super_jump_material = ba.Material()
self.super_jump_material.add_actions(
conditions=('they_have_material', shared.footing_material),
actions=(
('call', 'at_connect', ba.Call(self.jump_state, True)),
('call', 'at_disconnect', ba.Call(self.jump_state, False))
),
)
self.node.roller_materials += (self.super_jump_material, )
def jump_state(self, mode: bool) -> None:
self.jump_mode = mode
def on_jump_press(self) -> None:
"""
Called to 'press jump' on this spaz;
used by player or AI connections.
"""
if not self.node:
return
t_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
assert isinstance(t_ms, int)
if t_ms - self.last_jump_time_ms >= self._jump_cooldown:
self.node.jump_pressed = True
self.last_jump_time_ms = t_ms
if self._player.is_alive() and self.jump_mode and (
self._super_jump):
def do_jump():
self.node.handlemessage(
'impulse',
self.node.position[0],
self.node.position[1],
self.node.position[2],
0, 0, 0, 150, 150, 0, 0, 0, 1, 0
)
ba.timer(0.0, do_jump)
ba.timer(0.1, do_jump)
ba.timer(0.2, do_jump)
self._turbo_filter_add_press('jump')
# ba_meta export game
class BoxingGame(DeathMatchGame):
name = name
description = description
@classmethod
def get_available_settings(
cls, sessiontype: type[ba.Session]
) -> list[ba.Setting]:
settings = [
ba.IntSetting(
'Kills to Win Per Player',
min_value=1,
default=5,
increment=1,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting(super_jump_text, default=False),
ba.BoolSetting(enable_powerups, default=False),
ba.BoolSetting('Epic Mode', default=False),
]
# In teams mode, a suicide gives a point to the other team, but in
# free-for-all it subtracts from your own score. By default we clamp
# this at zero to benefit new players, but pro players might like to
# be able to go negative. (to avoid a strategy of just
# suiciding until you get a good drop)
if issubclass(sessiontype, ba.FreeForAllSession):
settings.append(
ba.BoolSetting('Allow Negative Scores', default=False)
)
return settings
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_to_win: int | None = None
self._dingsound = ba.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode'])
self._kills_to_win_per_player = int(settings['Kills to Win Per Player'])
self._time_limit = float(settings['Time Limit'])
self._allow_negative_scores = bool(
settings.get('Allow Negative Scores', False)
)
self._super_jump = bool(settings[super_jump_text])
self._enable_powerups = bool(settings[enable_powerups])
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (
ba.MusicType.EPIC if self._epic_mode else ba.MusicType.TO_THE_DEATH
)
def on_begin(self) -> None:
ba.TeamGameActivity.on_begin(self)
self.setup_standard_time_limit(self._time_limit)
if self._enable_powerups:
self.setup_standard_powerup_drops()
# Base kills needed to win on the size of the largest team.
self._score_to_win = self._kills_to_win_per_player * max(
1, max(len(t.players) for t in self.teams)
)
self._update_scoreboard()
def _standard_drop_powerup(self, index: int, expire: bool = True) -> None:
# pylint: disable=cyclic-import
from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory
PowerupBox(
position=self.map.powerup_spawn_points[index],
poweruptype=PowerupBoxFactory.get().get_random_powerup_type(
excludetypes=['triple_bombs','ice_bombs','impact_bombs',
'land_mines','sticky_bombs','punch']
),
expire=expire,
).autoretain()
def spawn_player(self, player: Player) -> ba.Actor:
import random
from ba import _math
from ba._gameutils import animate
from ba._coopsession import CoopSession
if isinstance(self.session, ba.DualTeamSession):
position = self.map.get_start_position(player.team.id)
else:
# otherwise do free-for-all spawn locations
position = self.map.get_ffa_start_position(self.players)
angle = None
name = player.getname()
color = player.color
highlight = player.highlight
light_color = _math.normalized_color(color)
display_color = ba.safecolor(color, target_intensity=0.75)
spaz = NewPlayerSpaz(color=color,
highlight=highlight,
character=player.character,
player=player,
super_jump=self._super_jump)
player.actor = spaz
assert spaz.node
spaz.node.name = name
spaz.node.name_color = display_color
# Move to the stand position and add a flash of light.
spaz.handlemessage(
ba.StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
light = ba.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
ba.timer(0.5, light.delete)
# custom
spaz.connect_controls_to_player(enable_bomb=False)
spaz.equip_boxing_gloves()
return spaz

View file

@ -0,0 +1,406 @@
# ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
import ba
from bastd.actor.bomb import Bomb
from bastd.actor.onscreentimer import OnScreenTimer
if TYPE_CHECKING:
from typing import Any, Sequence
lang = ba.app.lang.language
if lang == 'Spanish':
name = 'Lluvia de Meteoritos v2'
bomb_type = 'Tipo de Bomba'
ice = 'hielo'
sticky = 'pegajosa'
impact = 'insta-bomba'
land_mine = 'mina terrestre'
random_bomb = 'aleatoria'
normal_rain = 'Lluvia Normal'
frozen_rain = 'Lluvia Congelada'
sticky_rain = 'Lluvia Pegajosa'
impact_rain = 'Lluvia de Impacto'
mine_rain = 'Lluvia de Minas'
tnt_rain = 'Lluvia de TNT'
random_rain = 'Lluvia Aleatoria'
else:
name = 'Meteor Shower v2'
bomb_type = 'Bomb Type'
ice = 'ice'
sticky = 'sticky'
impact = 'impact'
land_mine = 'land mine'
random_bomb = 'random'
normal_rain = 'Normal Rain'
frozen_rain = 'Frozen Rain'
sticky_rain = 'Sticky Rain'
impact_rain = 'Impact Rain'
mine_rain = 'Mine Rain'
tnt_rain = 'TNT Rain'
random_rain = 'Random Rain'
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
super().__init__()
self.death_time: float | None = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
# ba_meta export game
class MeteorShowerv2Game(ba.TeamGameActivity[Player, Team]):
"""Minigame involving dodging falling bombs."""
name = name
description = 'Dodge the falling bombs.'
scoreconfig = ba.ScoreConfig(
label='Survived', scoretype=ba.ScoreType.MILLISECONDS, version='B'
)
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
allow_mid_activity_joins = False
@classmethod
def get_available_settings(
cls, sessiontype: type[ba.Session]
) -> list[ba.Setting]:
settings = [
ba.IntChoiceSetting(
bomb_type,
choices=[
('normal', 0),
(ice, 1),
(sticky, 2),
(impact, 3),
(land_mine, 4),
('tnt', 5),
(random_bomb, 6)
],
default=0,
),
ba.BoolSetting('Epic Mode', default=False),
]
return settings
# We're currently hard-coded for one map.
@classmethod
def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
return ['Rampage']
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
return (
issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession)
or issubclass(sessiontype, ba.CoopSession)
)
def __init__(self, settings: dict):
super().__init__(settings)
btype = int(settings[bomb_type])
if btype == 0:
newbtype = 'normal'
elif btype == 1:
newbtype = 'ice'
elif btype == 2:
newbtype = 'sticky'
elif btype == 3:
newbtype = 'impact'
elif btype == 4:
newbtype = 'land_mine'
elif btype == 5:
newbtype = 'tnt'
else:
newbtype = 'random'
self._bomb_type = newbtype
self._epic_mode = settings.get('Epic Mode', False)
self._last_player_death_time: float | None = None
self._meteor_time = 2.0
self._timer: OnScreenTimer | None = None
# Some base class overrides:
self.default_music = (
ba.MusicType.EPIC if self._epic_mode else ba.MusicType.SURVIVAL
)
if self._epic_mode:
self.slow_motion = True
def on_begin(self) -> None:
super().on_begin()
# Drop a wave every few seconds.. and every so often drop the time
# between waves ..lets have things increase faster if we have fewer
# players.
delay = 5.0 if len(self.players) > 2 else 2.5
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._decrement_meteor_time, repeat=True)
# Kick off the first wave in a few seconds.
delay = 3.0
if self._epic_mode:
delay *= 0.25
ba.timer(delay, self._set_meteor_timer)
self._timer = OnScreenTimer()
self._timer.start()
# Check for immediate end (if we've only got 1 player, etc).
ba.timer(5.0, self._check_end_game)
def on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
# A departing player may trigger game-over.
self._check_end_game()
# overriding the default character spawning..
def spawn_player(self, player: Player) -> ba.Actor:
spaz = self.spawn_player_spaz(player)
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
spaz.connect_controls_to_player(
enable_punch=False, enable_bomb=False, enable_pickup=False
)
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
return spaz
# Various high-level game events come through this method.
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
curtime = ba.time()
# Record the player's moment of death.
# assert isinstance(msg.spaz.player
msg.getplayer(Player).death_time = curtime
# In co-op mode, end the game the instant everyone dies
# (more accurate looking).
# In teams/ffa, allow a one-second fudge-factor so we can
# get more draws if players die basically at the same time.
if isinstance(self.session, ba.CoopSession):
# Teams will still show up if we check now.. check in
# the next cycle.
ba.pushcall(self._check_end_game)
# Also record this for a final setting of the clock.
self._last_player_death_time = curtime
else:
ba.timer(1.0, self._check_end_game)
else:
# Default handler:
return super().handlemessage(msg)
return None
def _check_end_game(self) -> None:
living_team_count = 0
for team in self.teams:
for player in team.players:
if player.is_alive():
living_team_count += 1
break
# In co-op, we go till everyone is dead.. otherwise we go
# until one team remains.
if isinstance(self.session, ba.CoopSession):
if living_team_count <= 0:
self.end_game()
else:
if living_team_count <= 1:
self.end_game()
def _set_meteor_timer(self) -> None:
ba.timer(
(1.0 + 0.2 * random.random()) * self._meteor_time,
self._drop_bomb_cluster,
)
def _drop_bomb_cluster(self) -> None:
# Random note: code like this is a handy way to plot out extents
# and debug things.
loc_test = False
if loc_test:
ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})
# Drop several bombs in series.
delay = 0.0
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
pos = (
-7.3 + 15.3 * random.random(),
11,
-5.57 + 2.1 * random.random(),
)
dropdir = -1.0 if pos[0] > 0 else 1.0
vel = (
(-5.0 + random.random() * 30.0) * dropdir,
random.uniform(-3.066, -4.12),
0,
)
ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
def _drop_bomb(
self, position: Sequence[float], velocity: Sequence[float]
) -> None:
if self._bomb_type == 'tnt':
bomb_type = random.choice(['tnt','tnt','tnt','tnt','impact'])
elif self._bomb_type == 'land_mine':
bomb_type = random.choice([
'land_mine','land_mine','land_mine','land_mine','impact'])
elif self._bomb_type == 'random':
bomb_type = random.choice([
'normal','ice','sticky','impact','land_mine','tnt'])
else:
bomb_type = self._bomb_type
Bomb(position=position,
velocity=velocity,
bomb_type=bomb_type).autoretain()
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None:
cur_time = ba.time()
assert self._timer is not None
start_time = self._timer.getstarttime()
# Mark death-time as now for any still-living players
# and award players points for how long they lasted.
# (these per-player scores are only meaningful in team-games)
for team in self.teams:
for player in team.players:
survived = False
# Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did.
if player.death_time is None:
survived = True
player.death_time = cur_time + 1
# Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score).
score = int(player.death_time - self._timer.getstarttime())
if survived:
score += 50 # A bit extra for survivors.
self.stats.player_scored(player, score, screenmessage=False)
# Stop updating our time text, and set the final time to match
# exactly when our last guy died.
self._timer.stop(endtime=self._last_player_death_time)
# Ok now calc game results: set a score for each team and then tell
# the game to end.
results = ba.GameResults()
# Remember that 'free-for-all' mode is simply a special form
# of 'teams' mode where each player gets their own team, so we can
# just always deal in teams and have all cases covered.
for team in self.teams:
# Set the team score to the max time survived by any player on
# that team.
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
longest_life = max(longest_life, player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self.end(results=results)
# ba_meta export plugin
class MeteorShowerv2Coop(ba.Plugin):
def on_app_running(self) -> None:
ba.app.add_coop_practice_level(
ba.Level(
normal_rain,
gametype=MeteorShowerv2Game,
settings={bomb_type: 0},
preview_texture_name='rampagePreview',
)
)
ba.app.add_coop_practice_level(
ba.Level(
frozen_rain,
gametype=MeteorShowerv2Game,
settings={bomb_type: 1},
preview_texture_name='rampagePreview',
)
)
ba.app.add_coop_practice_level(
ba.Level(
sticky_rain,
gametype=MeteorShowerv2Game,
settings={bomb_type: 2},
preview_texture_name='rampagePreview',
)
)
ba.app.add_coop_practice_level(
ba.Level(
impact_rain,
gametype=MeteorShowerv2Game,
settings={bomb_type: 3},
preview_texture_name='rampagePreview',
)
)
ba.app.add_coop_practice_level(
ba.Level(
mine_rain,
gametype=MeteorShowerv2Game,
settings={bomb_type: 4},
preview_texture_name='rampagePreview',
)
)
ba.app.add_coop_practice_level(
ba.Level(
tnt_rain,
gametype=MeteorShowerv2Game,
settings={bomb_type: 5},
preview_texture_name='rampagePreview',
)
)
ba.app.add_coop_practice_level(
ba.Level(
random_rain,
gametype=MeteorShowerv2Game,
settings={bomb_type: 6},
preview_texture_name='rampagePreview',
)
)

View file

@ -0,0 +1,333 @@
#SimonSays
# you had really better do what Simon says...
# ba_meta require api 7
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any, Union, Sequence
from ba import _gameutils
import ba
import random
class CustomText(ba.Actor):
"""Text that pops up above a position to denote something special.
category: Gameplay Classes
"""
def __init__(self,
text: Union[str, ba.Lstr],
position: Sequence[float] = (0.0, 0.0, 0.0),
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
random_offset: float = 0.5,
duration: float = 1.5,
offset: Sequence[float] = (0.0, 0.0, 0.0),
scale: float = 1.0):
super().__init__()
if len(color) == 3:
color = (color[0], color[1], color[2], 1.0)
pos = (position[0] + offset[0] + random_offset *
(0.5 - random.random()), position[1] + offset[0] +
random_offset * (0.5 - random.random()), position[2] +
offset[0] + random_offset * (0.5 - random.random()))
self.node = ba.newnode('text',
attrs={
'text': text,
'in_world': True,
'shadow': 1.0,
'flatness': 1.0,
'h_align': 'center'},delegate=self)
lifespan = duration
ba.animate(
self.node, 'scale', {
0: 0.0,
lifespan * 0.11: 0.020 * 0.7 * scale,
lifespan * 0.16: 0.013 * 0.7 * scale,
lifespan * 0.25: 0.014 * 0.7 * scale
})
self._tcombine = ba.newnode('combine',
owner=self.node,
attrs={
'input0': pos[0],
'input2': pos[2],
'size': 3
})
ba.animate(self._tcombine, 'input1', {
0: pos[1] + 1.5,
lifespan: pos[1] + 2.0
})
self._tcombine.connectattr('output', self.node, 'position')
# fade our opacity in/out
self._combine = ba.newnode('combine',
owner=self.node,
attrs={
'input0': color[0],
'input1': color[1],
'input2': color[2],
'size': 4
})
for i in range(4):
ba.animate(
self._combine, 'input' + str(i), {
0.13 * lifespan: color[i],
0.18 * lifespan: 4.0 * color[i],
0.22 * lifespan: color[i]})
ba.animate(self._combine, 'input3', {
0: 0,
0.1 * lifespan: color[3],
0.7 * lifespan: color[3],
lifespan: 0})
self._combine.connectattr('output', self.node, 'color')
self._die_timer = ba.Timer(
lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage()))
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, ba.DieMessage):
if self.node:
self.node.delete()
else:
super().handlemessage(msg)
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.score = 0
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
class SimonSays(ba.TeamGameActivity[Player, Team]):
name = "Simon Says"
description = "You have to better do what Simon says!"
@classmethod
def get_available_settings(cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.BoolSetting("Epic Mode", default=False),
ba.BoolSetting("Enable Jumping", default=False),
ba.BoolSetting("Enable Punching", default=False),
ba.BoolSetting("Enable Picking Up", default=False),
ba.IntChoiceSetting("Timer Speed",
choices=[("Snaily", 1200),
("Slow", 900),
("Normal", 655),
("Fast", 544),
("Turbo", 460)], default=655),
ba.FloatChoiceSetting("Text Duration",
choices=[("Slow", 2.5),
("Normal", 1.5),
("Mediocre", 1.0),
("Quick", 0.75)], default=1.5)]
return settings
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ["Courtyard"]
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return issubclass(sessiontype, ba.FreeForAllSession)
def __init__(self, settings: dict):
super().__init__(settings)
self.settings = settings
self._is_slow_motion = bool(settings['Epic Mode'])
self.speed = float(settings['Timer Speed'])
self.lifespan = float(settings['Text Duration'])
self.round_num = 0
self.string = ""
self.now = 0
self.simon = False
self.ended = False
self.counter_loop = None
self.time = 5000
self._r1 = 2
self.ct_text = ba.newnode('text',attrs={
'in_world': True,
'text':'......',
'shadow': 1.0,
'color': (1.0,1.0,1.0),
'flatness': 0.5,
'position': (-5.627144702, 3.3275475, -9.572879116),
'scale': 0.05})
self.n1 = ba.newnode('locator',attrs={'shape':'circle','position':(-4,0,-6),
'color':(1,0,0),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.n2 = ba.newnode('locator',attrs={'shape':'circle','position':(0,0,-6),
'color':(0,1,0),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.n3 = ba.newnode('locator',attrs={'shape':'circle','position':(4,0,-6),
'color':(0,0,1),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.n4 = ba.newnode('locator',attrs={'shape':'circle','position':(-4,0,-2),
'color':(1,1,0),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.n5 = ba.newnode('locator',attrs={'shape':'circle','position':(0,0,-2),
'color':(0,1,1),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.n6 = ba.newnode('locator',attrs={'shape':'circle','position':(4,0,-2),
'color':(1,0,1),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.n7 = ba.newnode('locator',attrs={'shape':'circle','position':(-4,0,2),
'color':(.5,.5,.5),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.n8 = ba.newnode('locator',attrs={'shape':'circle','position':(0,0,2),
'color':(.5,.325,0),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.n9 = ba.newnode('locator',attrs={'shape':'circle','position':(4,0,2),
'color':(1,1,1),'opacity':0.5,
'draw_beauty':True,'additive':True})
self.options = ["red", "green", "blue", "yellow", "teal", "purple", "gray", "orange", "white", "top", "bottom", "middle row", "left", "right", "center column", "outside"]
self.default_music = ba.MusicType.FLAG_CATCHER
def get_instance_description(self) -> str:
return 'Follow the commands... but only when \"Simon says!"'
def on_player_join(self, player: Player) -> None:
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource = 'playerDelayedJoinText',
subs = [('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),)
return
else:
self.spawn_player(player)
def on_begin(self) -> None:
super().on_begin()
s = self.settings
_gameutils.animate_array(self.n1,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
_gameutils.animate_array(self.n2,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
_gameutils.animate_array(self.n3,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
_gameutils.animate_array(self.n4,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
_gameutils.animate_array(self.n5,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
_gameutils.animate_array(self.n6,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
_gameutils.animate_array(self.n7,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
_gameutils.animate_array(self.n8,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
_gameutils.animate_array(self.n9,'size',1,{0:[0.0],0.2:[self._r1*2.0]})
for team in self.teams:
team.score = 0
for player in self.players:
player.score = 0
# check for immediate end if theres only 1 player
if len(self.players) == 1:
ba.timer(4000, lambda: self.check_end(),timeformat=ba.TimeFormat.MILLISECONDS)
else:
ba.timer(6000, self.call_round, timeformat=ba.TimeFormat.MILLISECONDS)
def spawn_player(self, player: PlayerType) -> ba.Actor:
assert player
spaz = self.spawn_player_spaz(player, position=(0 + random.uniform(-3.6, 3.6), 2.9, -2 + random.uniform(-3.6, 3.6)))
assert spaz.node
spaz.connect_controls_to_player(
enable_bomb=False,
enable_run = True,
enable_punch = self.settings["Enable Punching"],
enable_pickup = self.settings["Enable Picking Up"],
enable_jump = self.settings["Enable Jumping"])
def call_round(self) -> None:
if self.ended: return
self.round_num += 1
self.num = random.randint(0, 15)
self.numa = self.num
self.simon = random.choice([True, False])
false_prefix = random.choices(['Simon say r', 'Simon said r', 'Simon r', 'Simons says r', 'Simons r', 'R'], weights=[35,45,45,39,49,100])[0]
if self.numa < 9:
if not self.simon: line = false_prefix + "un to the " + self.options[self.numa] + " circle!"
else: line = "Run to the " + self.options[self.numa] + " circle!"
elif self.numa < 15:
if not self.simon: line = false_prefix + "un to the " + self.options[self.numa] + "!"
else: line = "Run to the " + self.options[self.numa] + "!"
else:
if not self.simon: line = false_prefix + "un outside of the circles!"
else: line = "Run outside of the circles!"
if self.simon:
line = "Simon says " + line[0].lower() + line[1:]
self.text = CustomText(line,
position=(0, 5, -4),
color=(0.68, 0.95, 1.12),
random_offset=0.5,
offset=(0, 0, 0),
duration=self.lifespan,
scale=2.0).autoretain()
self.now = 6
def dummy_check():
self.string = "...."
self.check_round()
def set_counter():
self.now = self.now - 1
if self.now == 0:
self.string = "0"
self.ct_text.text = self.string
self.counter_loop = None
ba.timer(1, dummy_check, timeformat=ba.TimeFormat.MILLISECONDS)
else:
self.ct_text.text = str(self.now)
ba.playsound(ba.getsound('tick'))
self.counter_loop = ba.Timer(self.speed, set_counter ,timeformat=ba.TimeFormat.MILLISECONDS,repeat=True)
def check_round(self) -> None:
if self.ended: return
for player in self.players:
if player.is_alive():
safe = True if self.options[self.numa] in self.in_circle(player.actor.node.position_center) else False
if ((self.simon and safe == False) or ((not self.simon) and safe == True)):
player.team.score = self.round_num
player.actor.handlemessage(ba.DieMessage())
ba.timer(1633, self.call_round, timeformat=ba.TimeFormat.MILLISECONDS)
def in_circle(self, pos) -> None:
circles = []
x = pos[0]
z = pos[2]
if (x + 4) ** 2 + (z + 6) ** 2 < 4: circles.append("red")
elif (x) ** 2 + (z + 6) ** 2 < 4: circles.append("green")
elif (x - 4) ** 2 + (z + 6) ** 2 < 4: circles.append("blue")
elif (x + 4) ** 2 + (z + 2) ** 2 < 4: circles.append("yellow")
elif (x) ** 2 + (z + 2) ** 2 < 4: circles.append("teal")
elif (x - 4) ** 2 + (z + 2) ** 2 < 4: circles.append("purple")
elif (x + 4) ** 2 + (z - 2) ** 2 < 4: circles.append("gray")
elif (x) ** 2 + (z - 2) ** 2 < 4: circles.append("orange")
elif (x - 4) ** 2 + (z - 2) ** 2 < 4: circles.append("white")
else: circles.append("outside")
if x < -2: circles.append("left")
if x > 2: circles.append("right")
if x > -2 and x < 2: circles.append("center column")
if z > 0: circles.append("bottom")
if z < -4: circles.append("top")
if z < 0 and z > -4: circles.append("middle row")
return circles
def handlemessage(self, msg) -> None:
if isinstance(msg, ba.PlayerDiedMessage):
self.check_end()
else:
super().handlemessage(msg)
def end_game(self) -> None:
self.ended = True
results = ba.GameResults()
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)
def check_end(self) -> None:
i = 0
for player in self.players:
if player.is_alive():
i += 1
if i <= 2 :
ba.timer(0.6, lambda: self.end_game())

View file

@ -0,0 +1,949 @@
# Released under the MIT License. See LICENSE for details.
#
"""Defines Race mini-game."""
# ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from dataclasses import dataclass
import ba
from bastd.actor.bomb import Bomb, Blast, ExplodeHitMessage
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.gameutils import SharedObjects
if TYPE_CHECKING:
from typing import (Any, Type, Tuple, List, Sequence, Optional, Dict,
Union)
from bastd.actor.onscreentimer import OnScreenTimer
class NewBlast(Blast):
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ExplodeHitMessage):
pass
else:
return super().handlemessage(msg)
@dataclass
class RaceMine:
"""Holds info about a mine on the track."""
point: Sequence[float]
mine: Optional[Bomb]
class RaceRegion(ba.Actor):
"""Region used to track progress during a race."""
def __init__(self, pt: Sequence[float], index: int):
super().__init__()
activity = self.activity
assert isinstance(activity, RaceGame)
self.pos = pt
self.index = index
self.node = ba.newnode(
'region',
delegate=self,
attrs={
'position': pt[:3],
'scale': (pt[3] * 2.0, pt[4] * 2.0, pt[5] * 2.0),
'type': 'box',
'materials': [activity.race_region_material]
})
class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.distance_txt: Optional[ba.Node] = None
self.last_region = 0
self.lap = 0
self.distance = 0.0
self.finished = False
self.rank: Optional[int] = None
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.time: Optional[float] = None
self.lap = 0
self.finished = False
# ba_meta export game
class SquidRaceGame(ba.TeamGameActivity[Player, Team]):
"""Game of racing around a track."""
name = 'Squid Race'
description = 'Run real fast!'
scoreconfig = ba.ScoreConfig(label='Time',
lower_is_better=True,
scoretype=ba.ScoreType.MILLISECONDS)
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
settings = [
ba.IntSetting('Laps', min_value=1, default=3, increment=1),
ba.IntChoiceSetting(
'Time Limit',
default=0,
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
),
ba.IntChoiceSetting(
'Mine Spawning',
default=4000,
choices=[
('No Mines', 0),
('8 Seconds', 8000),
('4 Seconds', 4000),
('2 Seconds', 2000),
],
),
ba.IntChoiceSetting(
'Bomb Spawning',
choices=[
('None', 0),
('8 Seconds', 8000),
('4 Seconds', 4000),
('2 Seconds', 2000),
('1 Second', 1000),
],
default=2000,
),
ba.BoolSetting('Epic Mode', default=False),
]
# We have some specific settings in teams mode.
if issubclass(sessiontype, ba.DualTeamSession):
settings.append(
ba.BoolSetting('Entire Team Must Finish', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
return issubclass(sessiontype, ba.MultiTeamSession)
@classmethod
def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
return ba.getmaps('race')
def __init__(self, settings: dict):
self._race_started = False
super().__init__(settings)
self._scoreboard = Scoreboard()
self._score_sound = ba.getsound('score')
self._swipsound = ba.getsound('swip')
self._last_team_time: Optional[float] = None
self._front_race_region: Optional[int] = None
self._nub_tex = ba.gettexture('nub')
self._beep_1_sound = ba.getsound('raceBeep1')
self._beep_2_sound = ba.getsound('raceBeep2')
self.race_region_material: Optional[ba.Material] = None
self._regions: List[RaceRegion] = []
self._team_finish_pts: Optional[int] = None
self._time_text: Optional[ba.Actor] = None
self._timer: Optional[OnScreenTimer] = None
self._race_mines: Optional[List[RaceMine]] = None
self._race_mine_timer: Optional[ba.Timer] = None
self._scoreboard_timer: Optional[ba.Timer] = None
self._player_order_update_timer: Optional[ba.Timer] = None
self._start_lights: Optional[List[ba.Node]] = None
self._squid_lights: Optional[List[ba.Node]] = None
self._countdown_timer: int = 0
self._sq_mode: str = 'Easy'
self._tick_timer: Optional[ba.Timer] = None
self._bomb_spawn_timer: Optional[ba.Timer] = None
self._laps = int(settings['Laps'])
self._entire_team_must_finish = bool(
settings.get('Entire Team Must Finish', False))
self._time_limit = float(settings['Time Limit'])
self._mine_spawning = int(settings['Mine Spawning'])
self._bomb_spawning = int(settings['Bomb Spawning'])
self._epic_mode = bool(settings['Epic Mode'])
self._countdownsounds = {
10: ba.getsound('announceTen'),
9: ba.getsound('announceNine'),
8: ba.getsound('announceEight'),
7: ba.getsound('announceSeven'),
6: ba.getsound('announceSix'),
5: ba.getsound('announceFive'),
4: ba.getsound('announceFour'),
3: ba.getsound('announceThree'),
2: ba.getsound('announceTwo'),
1: ba.getsound('announceOne')
}
# Base class overrides.
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC_RACE
if self._epic_mode else ba.MusicType.RACE)
def get_instance_description(self) -> Union[str, Sequence]:
if (isinstance(self.session, ba.DualTeamSession)
and self._entire_team_must_finish):
t_str = ' Your entire team has to finish.'
else:
t_str = ''
if self._laps > 1:
return 'Run ${ARG1} laps.' + t_str, self._laps
return 'Run 1 lap.' + t_str
def get_instance_description_short(self) -> Union[str, Sequence]:
if self._laps > 1:
return 'run ${ARG1} laps', self._laps
return 'run 1 lap'
def on_transition_in(self) -> None:
super().on_transition_in()
shared = SharedObjects.get()
pts = self.map.get_def_points('race_point')
mat = self.race_region_material = ba.Material()
mat.add_actions(conditions=('they_have_material',
shared.player_material),
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('call', 'at_connect',
self._handle_race_point_collide),
))
for rpt in pts:
self._regions.append(RaceRegion(rpt, len(self._regions)))
def _flash_player(self, player: Player, scale: float) -> None:
assert isinstance(player.actor, PlayerSpaz)
assert player.actor.node
pos = player.actor.node.position
light = ba.newnode('light',
attrs={
'position': pos,
'color': (1, 1, 0),
'height_attenuated': False,
'radius': 0.4
})
ba.timer(0.5, light.delete)
ba.animate(light, 'intensity', {0: 0, 0.1: 1.0 * scale, 0.5: 0})
def _handle_race_point_collide(self) -> None:
# FIXME: Tidy this up.
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-nested-blocks
collision = ba.getcollision()
try:
region = collision.sourcenode.getdelegate(RaceRegion, True)
player = collision.opposingnode.getdelegate(PlayerSpaz,
True).getplayer(
Player, True)
except ba.NotFoundError:
return
last_region = player.last_region
this_region = region.index
if last_region != this_region:
# If a player tries to skip regions, smite them.
# Allow a one region leeway though (its plausible players can get
# blown over a region, etc).
if this_region > last_region + 2:
if player.is_alive():
assert player.actor
player.actor.handlemessage(ba.DieMessage())
ba.screenmessage(ba.Lstr(
translate=('statements', 'Killing ${NAME} for'
' skipping part of the track!'),
subs=[('${NAME}', player.getname(full=True))]),
color=(1, 0, 0))
else:
# If this player is in first, note that this is the
# front-most race-point.
if player.rank == 0:
self._front_race_region = this_region
player.last_region = this_region
if last_region >= len(self._regions) - 2 and this_region == 0:
team = player.team
player.lap = min(self._laps, player.lap + 1)
# In teams mode with all-must-finish on, the team lap
# value is the min of all team players.
# Otherwise its the max.
if isinstance(self.session, ba.DualTeamSession
) and self._entire_team_must_finish:
team.lap = min([p.lap for p in team.players])
else:
team.lap = max([p.lap for p in team.players])
# A player is finishing.
if player.lap == self._laps:
# In teams mode, hand out points based on the order
# players come in.
if isinstance(self.session, ba.DualTeamSession):
assert self._team_finish_pts is not None
if self._team_finish_pts > 0:
self.stats.player_scored(player,
self._team_finish_pts,
screenmessage=False)
self._team_finish_pts -= 25
# Flash where the player is.
self._flash_player(player, 1.0)
player.finished = True
assert player.actor
player.actor.handlemessage(
ba.DieMessage(immediate=True))
# Makes sure noone behind them passes them in rank
# while finishing.
player.distance = 9999.0
# If the whole team has finished the race.
if team.lap == self._laps:
ba.playsound(self._score_sound)
player.team.finished = True
assert self._timer is not None
elapsed = ba.time() - self._timer.getstarttime()
self._last_team_time = player.team.time = elapsed
# Team has yet to finish.
else:
ba.playsound(self._swipsound)
# They've just finished a lap but not the race.
else:
ba.playsound(self._swipsound)
self._flash_player(player, 0.3)
# Print their lap number over their head.
try:
assert isinstance(player.actor, PlayerSpaz)
mathnode = ba.newnode('math',
owner=player.actor.node,
attrs={
'input1': (0, 1.9, 0),
'operation': 'add'
})
player.actor.node.connectattr(
'torso_position', mathnode, 'input2')
tstr = ba.Lstr(resource='lapNumberText',
subs=[('${CURRENT}',
str(player.lap + 1)),
('${TOTAL}', str(self._laps))
])
txtnode = ba.newnode('text',
owner=mathnode,
attrs={
'text': tstr,
'in_world': True,
'color': (1, 1, 0, 1),
'scale': 0.015,
'h_align': 'center'
})
mathnode.connectattr('output', txtnode, 'position')
ba.animate(txtnode, 'scale', {
0.0: 0,
0.2: 0.019,
2.0: 0.019,
2.2: 0
})
ba.timer(2.3, mathnode.delete)
except Exception:
ba.print_exception('Error printing lap.')
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
def on_player_join(self, player: Player) -> None:
# Don't allow joining after we start
# (would enable leave/rejoin tomfoolery).
if self.has_begun():
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
self.spawn_player(player)
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
# A player leaving disqualifies the team if 'Entire Team Must Finish'
# is on (otherwise in teams mode everyone could just leave except the
# leading player to win).
if (isinstance(self.session, ba.DualTeamSession)
and self._entire_team_must_finish):
ba.screenmessage(ba.Lstr(
translate=('statements',
'${TEAM} is disqualified because ${PLAYER} left'),
subs=[('${TEAM}', player.team.name),
('${PLAYER}', player.getname(full=True))]),
color=(1, 1, 0))
player.team.finished = True
player.team.time = None
player.team.lap = 0
ba.playsound(ba.getsound('boo'))
for otherplayer in player.team.players:
otherplayer.lap = 0
otherplayer.finished = True
try:
if otherplayer.actor is not None:
otherplayer.actor.handlemessage(ba.DieMessage())
except Exception:
ba.print_exception('Error sending DieMessage.')
# Defer so team/player lists will be updated.
ba.pushcall(self._check_end_game)
def _update_scoreboard(self) -> None:
for team in self.teams:
distances = [player.distance for player in team.players]
if not distances:
teams_dist = 0.0
else:
if (isinstance(self.session, ba.DualTeamSession)
and self._entire_team_must_finish):
teams_dist = min(distances)
else:
teams_dist = max(distances)
self._scoreboard.set_team_value(
team,
teams_dist,
self._laps,
flash=(teams_dist >= float(self._laps)),
show_value=False)
def on_begin(self) -> None:
from bastd.actor.onscreentimer import OnScreenTimer
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self._team_finish_pts = 100
# Throw a timer up on-screen.
self._time_text = ba.NodeActor(
ba.newnode('text',
attrs={
'v_attach': 'top',
'h_attach': 'center',
'h_align': 'center',
'color': (1, 1, 0.5, 1),
'flatness': 0.5,
'shadow': 0.5,
'position': (0, -50),
'scale': 1.4,
'text': ''
}))
self._timer = OnScreenTimer()
if self._mine_spawning != 0:
self._race_mines = [
RaceMine(point=p, mine=None)
for p in self.map.get_def_points('race_mine')
]
if self._race_mines:
self._race_mine_timer = ba.Timer(0.001 * self._mine_spawning,
self._update_race_mine,
repeat=True)
self._scoreboard_timer = ba.Timer(0.25,
self._update_scoreboard,
repeat=True)
self._player_order_update_timer = ba.Timer(0.25,
self._update_player_order,
repeat=True)
if self.slow_motion:
t_scale = 0.4
light_y = 50
else:
t_scale = 1.0
light_y = 150
lstart = 7.1 * t_scale
inc = 1.25 * t_scale
ba.timer(lstart, self._do_light_1)
ba.timer(lstart + inc, self._do_light_2)
ba.timer(lstart + 2 * inc, self._do_light_3)
ba.timer(lstart + 3 * inc, self._start_race)
self._start_lights = []
for i in range(4):
lnub = ba.newnode('image',
attrs={
'texture': ba.gettexture('nub'),
'opacity': 1.0,
'absolute_scale': True,
'position': (-75 + i * 50, light_y),
'scale': (50, 50),
'attach': 'center'
})
ba.animate(
lnub, 'opacity', {
4.0 * t_scale: 0,
5.0 * t_scale: 1.0,
12.0 * t_scale: 1.0,
12.5 * t_scale: 0.0
})
ba.timer(13.0 * t_scale, lnub.delete)
self._start_lights.append(lnub)
self._start_lights[0].color = (0.2, 0, 0)
self._start_lights[1].color = (0.2, 0, 0)
self._start_lights[2].color = (0.2, 0.05, 0)
self._start_lights[3].color = (0.0, 0.3, 0)
self._squid_lights = []
for i in range(2):
lnub = ba.newnode('image',
attrs={
'texture': ba.gettexture('nub'),
'opacity': 1.0,
'absolute_scale': True,
'position': (-33 + i * 65, 220),
'scale': (60, 60),
'attach': 'center'
})
ba.animate(
lnub, 'opacity', {
4.0 * t_scale: 0,
5.0 * t_scale: 1.0})
self._squid_lights.append(lnub)
self._squid_lights[0].color = (0.2, 0, 0)
self._squid_lights[1].color = (0.0, 0.3, 0)
ba.timer(1.0, self._check_squid_end, repeat=True)
self._squidgame_countdown()
def _squidgame_countdown(self) -> None:
self._countdown_timer = 80 * self._laps # 80
ba.newnode(
'image',
attrs={
'opacity': 0.7,
'color': (0.2, 0.2, 0.2),
'attach': 'topCenter',
'position': (-220, -40),
'scale': (135, 45),
'texture': ba.gettexture('bar')})
ba.newnode(
'image',
attrs={
'opacity': 1.0,
'color': (1.0, 0.0, 0.0),
'attach': 'topCenter',
'position': (-220, -38),
'scale':(155, 65),
'texture': ba.gettexture('uiAtlas'),
'model_transparent': ba.getmodel('meterTransparent')})
self._sgcountdown_text = ba.newnode(
'text',
attrs={
'v_attach': 'top',
'h_attach': 'center',
'h_align': 'center',
'position': (-220, -57),
'shadow': 0.5,
'flatness': 0.5,
'scale': 1.1,
'text': str(self._countdown_timer)+"s"})
def _update_sgcountdown(self) -> None:
self._countdown_timer -= 1
self._countdown_timer
if self._countdown_timer <= 0:
self._countdown_timer = 0
self._squid_game_all_die()
if self._countdown_timer == 20:
self._sq_mode = 'Hard'
ba.playsound(ba.getsound('alarm'))
if self._countdown_timer == 40:
self._sq_mode = 'Normal'
if self._countdown_timer <= 20:
self._sgcountdown_text.color = (1.2, 0.0, 0.0)
self._sgcountdown_text.scale = 1.2
if self._countdown_timer in self._countdownsounds:
ba.playsound(self._countdownsounds[self._countdown_timer])
else:
self._sgcountdown_text.color = (1.0, 1.0, 1.0)
self._sgcountdown_text.text = str(self._countdown_timer)+"s"
def _squid_game_all_die(self) -> None:
for player in self.players:
if player.is_alive():
player.actor._cursed = True
player.actor.handlemessage(ba.DieMessage())
NewBlast(
position=player.actor.node.position,
velocity=player.actor.node.velocity,
blast_radius=3.0,
blast_type='normal').autoretain()
player.actor.handlemessage(
ba.HitMessage(
pos=player.actor.node.position,
velocity=player.actor.node.velocity,
magnitude=2000,
hit_type='explosion',
hit_subtype='normal',
radius=2.0,
source_player=None))
player.actor._cursed = False
def _do_ticks(self) -> None:
def do_ticks():
if self._ticks:
ba.playsound(ba.getsound('tick'))
self._tick_timer = ba.timer(1.0, do_ticks, repeat=True)
def _start_squid_game(self) -> None:
easy = [4.5,5,5.5,6]
normal = [4,4.5,5]
hard = [3,3.5,4]
random_number = random.choice(
hard if self._sq_mode == 'Hard' else
normal if self._sq_mode == 'Normal' else easy)
# if random_number == 6:
# ba.playsound(ba.getsound('lrlg_06s'))
# elif random_number == 5.5:
# ba.playsound(ba.getsound('lrlg_055s'))
# elif random_number == 5:
# ba.playsound(ba.getsound('lrlg_05s'))
# elif random_number == 4.5:
# ba.playsound(ba.getsound('lrlg_045s'))
# elif random_number == 4:
# ba.playsound(ba.getsound('lrlg_04s'))
# elif random_number == 3.5:
# ba.playsound(ba.getsound('lrlg_035s'))
# elif random_number == 3:
# ba.playsound(ba.getsound('lrlg_03s'))
self._squid_lights[0].color = (0.2, 0, 0)
self._squid_lights[1].color = (0.0, 1.0, 0)
self._do_delete = False
self._ticks = True
ba.timer(random_number, self._stop_squid_game)
def _stop_squid_game(self) -> None:
self._ticks = False
self._squid_lights[0].color = (1.0, 0, 0)
self._squid_lights[1].color = (0.0, 0.3, 0)
ba.timer(0.2, self._check_delete)
def _check_delete(self) -> None:
for player in self.players:
if player.is_alive():
player.customdata['position'] = None
player.customdata['position'] = player.actor.node.position
self._do_delete = True
ba.timer(3.0 if self._sq_mode == 'Hard' else 4.0,
self._start_squid_game)
def _start_delete(self) -> None:
for player in self.players:
if player.is_alive() and self._do_delete:
posx = float("%.1f" % player.customdata['position'][0])
posz = float("%.1f" % player.customdata['position'][1])
posy = float("%.1f" % player.customdata['position'][2])
posx_list = [
round(posx,1),round(posx+0.1,1),round(posx+0.2,1),
round(posx-0.1,1),round(posx-0.2,1)]
current_posx = float("%.1f" % player.actor.node.position[0])
posz_list = [
round(posz,1),round(posz+0.1,1),round(posz+0.2,1),
round(posz-0.1,1),round(posz-0.2,1)]
current_posz = float("%.1f" % player.actor.node.position[1])
posy_list = [
round(posy,1),round(posy+0.1,1),round(posy+0.2,1),
round(posy-0.1,1),round(posy-0.2,1)]
current_posy = float("%.1f" % player.actor.node.position[2])
if not (current_posx in posx_list) or not (
current_posz in posz_list) or not (
current_posy in posy_list):
player.actor._cursed = True
player.actor.handlemessage(ba.DieMessage())
NewBlast(
position=player.actor.node.position,
velocity=player.actor.node.velocity,
blast_radius=3.0,
blast_type='normal').autoretain()
player.actor.handlemessage(
ba.HitMessage(
pos=player.actor.node.position,
velocity=player.actor.node.velocity,
magnitude=2000,
hit_type='explosion',
hit_subtype='normal',
radius=2.0,
source_player=None))
player.actor._cursed = False
def _check_squid_end(self) -> None:
squid_player_alive = 0
for player in self.players:
if player.is_alive():
squid_player_alive += 1
break
if squid_player_alive < 1:
self.end_game()
def _do_light_1(self) -> None:
assert self._start_lights is not None
self._start_lights[0].color = (1.0, 0, 0)
ba.playsound(self._beep_1_sound)
def _do_light_2(self) -> None:
assert self._start_lights is not None
self._start_lights[1].color = (1.0, 0, 0)
ba.playsound(self._beep_1_sound)
def _do_light_3(self) -> None:
assert self._start_lights is not None
self._start_lights[2].color = (1.0, 0.3, 0)
ba.playsound(self._beep_1_sound)
def _start_race(self) -> None:
assert self._start_lights is not None
self._start_lights[3].color = (0.0, 1.0, 0)
ba.playsound(self._beep_2_sound)
for player in self.players:
if player.actor is not None:
try:
assert isinstance(player.actor, PlayerSpaz)
player.actor.connect_controls_to_player()
except Exception:
ba.print_exception('Error in race player connects.')
assert self._timer is not None
self._timer.start()
if self._bomb_spawning != 0:
self._bomb_spawn_timer = ba.Timer(0.001 * self._bomb_spawning,
self._spawn_bomb,
repeat=True)
self._race_started = True
self._squid_lights[1].color = (0.0, 1.0, 0)
self._start_squid_game()
self._do_ticks()
ba.timer(0.2, self._start_delete, repeat=True)
ba.timer(1.0, self._update_sgcountdown, repeat=True)
def _update_player_order(self) -> None:
# Calc all player distances.
for player in self.players:
pos: Optional[ba.Vec3]
try:
pos = player.position
except ba.NotFoundError:
pos = None
if pos is not None:
r_index = player.last_region
rg1 = self._regions[r_index]
r1pt = ba.Vec3(rg1.pos[:3])
rg2 = self._regions[0] if r_index == len(
self._regions) - 1 else self._regions[r_index + 1]
r2pt = ba.Vec3(rg2.pos[:3])
r2dist = (pos - r2pt).length()
amt = 1.0 - (r2dist / (r2pt - r1pt).length())
amt = player.lap + (r_index + amt) * (1.0 / len(self._regions))
player.distance = amt
# Sort players by distance and update their ranks.
p_list = [(player.distance, player) for player in self.players]
p_list.sort(reverse=True, key=lambda x: x[0])
for i, plr in enumerate(p_list):
plr[1].rank = i
if plr[1].actor:
node = plr[1].distance_txt
if node:
node.text = str(i + 1) if plr[1].is_alive() else ''
def _spawn_bomb(self) -> None:
if self._front_race_region is None:
return
region = (self._front_race_region + 3) % len(self._regions)
pos = self._regions[region].pos
# Don't use the full region so we're less likely to spawn off a cliff.
region_scale = 0.8
x_range = ((-0.5, 0.5) if pos[3] == 0 else
(-region_scale * pos[3], region_scale * pos[3]))
z_range = ((-0.5, 0.5) if pos[5] == 0 else
(-region_scale * pos[5], region_scale * pos[5]))
pos = (pos[0] + random.uniform(*x_range), pos[1] + 1.0,
pos[2] + random.uniform(*z_range))
ba.timer(random.uniform(0.0, 2.0),
ba.WeakCall(self._spawn_bomb_at_pos, pos))
def _spawn_bomb_at_pos(self, pos: Sequence[float]) -> None:
if self.has_ended():
return
Bomb(position=pos, bomb_type='normal').autoretain()
def _make_mine(self, i: int) -> None:
assert self._race_mines is not None
rmine = self._race_mines[i]
rmine.mine = Bomb(position=rmine.point[:3], bomb_type='land_mine')
rmine.mine.arm()
def _flash_mine(self, i: int) -> None:
assert self._race_mines is not None
rmine = self._race_mines[i]
light = ba.newnode('light',
attrs={
'position': rmine.point[:3],
'color': (1, 0.2, 0.2),
'radius': 0.1,
'height_attenuated': False
})
ba.animate(light, 'intensity', {0.0: 0, 0.1: 1.0, 0.2: 0}, loop=True)
ba.timer(1.0, light.delete)
def _update_race_mine(self) -> None:
assert self._race_mines is not None
m_index = -1
rmine = None
for _i in range(3):
m_index = random.randrange(len(self._race_mines))
rmine = self._race_mines[m_index]
if not rmine.mine:
break
assert rmine is not None
if not rmine.mine:
self._flash_mine(m_index)
ba.timer(0.95, ba.Call(self._make_mine, m_index))
def spawn_player(self, player: Player) -> ba.Actor:
if player.team.finished:
# FIXME: This is not type-safe!
# This call is expected to always return an Actor!
# Perhaps we need something like can_spawn_player()...
# noinspection PyTypeChecker
return None # type: ignore
pos = self._regions[player.last_region].pos
# Don't use the full region so we're less likely to spawn off a cliff.
region_scale = 0.8
x_range = ((-0.5, 0.5) if pos[3] == 0 else
(-region_scale * pos[3], region_scale * pos[3]))
z_range = ((-0.5, 0.5) if pos[5] == 0 else
(-region_scale * pos[5], region_scale * pos[5]))
pos = (pos[0] + random.uniform(*x_range), pos[1],
pos[2] + random.uniform(*z_range))
spaz = self.spawn_player_spaz(
player, position=pos, angle=90 if not self._race_started else None)
assert spaz.node
# Prevent controlling of characters before the start of the race.
if not self._race_started:
spaz.disconnect_controls_from_player()
mathnode = ba.newnode('math',
owner=spaz.node,
attrs={
'input1': (0, 1.4, 0),
'operation': 'add'
})
spaz.node.connectattr('torso_position', mathnode, 'input2')
distance_txt = ba.newnode('text',
owner=spaz.node,
attrs={
'text': '',
'in_world': True,
'color': (1, 1, 0.4),
'scale': 0.02,
'h_align': 'center'
})
player.distance_txt = distance_txt
mathnode.connectattr('output', distance_txt, 'position')
return spaz
def _check_end_game(self) -> None:
# If there's no teams left racing, finish.
teams_still_in = len([t for t in self.teams if not t.finished])
if teams_still_in == 0:
self.end_game()
return
# Count the number of teams that have completed the race.
teams_completed = len(
[t for t in self.teams if t.finished and t.time is not None])
if teams_completed > 0:
session = self.session
# In teams mode its over as soon as any team finishes the race
# FIXME: The get_ffa_point_awards code looks dangerous.
if isinstance(session, ba.DualTeamSession):
self.end_game()
else:
# In ffa we keep the race going while there's still any points
# to be handed out. Find out how many points we have to award
# and how many teams have finished, and once that matches
# we're done.
assert isinstance(session, ba.FreeForAllSession)
points_to_award = len(session.get_ffa_point_awards())
if teams_completed >= points_to_award - teams_completed:
self.end_game()
return
def end_game(self) -> None:
# Stop updating our time text, and set it to show the exact last
# finish time if we have one. (so users don't get upset if their
# final time differs from what they see onscreen by a tiny amount)
assert self._timer is not None
if self._timer.has_started():
self._timer.stop(
endtime=None if self._last_team_time is None else (
self._timer.getstarttime() + self._last_team_time))
results = ba.GameResults()
for team in self.teams:
if team.time is not None:
# We store time in seconds, but pass a score in milliseconds.
results.set_team_score(team, int(team.time * 1000.0))
else:
results.set_team_score(team, None)
# We don't announce a winner in ffa mode since its probably been a
# while since the first place guy crossed the finish line so it seems
# odd to be announcing that now.
self.end(results=results,
announce_winning_team=isinstance(self.session,
ba.DualTeamSession))
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment default behavior.
super().handlemessage(msg)
else:
super().handlemessage(msg)

View file

@ -0,0 +1,878 @@
# ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
from typing import TYPE_CHECKING
import ba
import _ba
import copy
import random
from ba import _math
from ba._coopsession import CoopSession
from ba._messages import PlayerDiedMessage, StandMessage
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.game.elimination import Icon, Player
from bastd.actor.spaz import PickupMessage
from bastd.actor.spazbot import SpazBotSet, BrawlerBot, SpazBotDiedMessage
from bastd.actor.spazfactory import SpazFactory
if TYPE_CHECKING:
from typing import Any, Sequence
class PlayerSpaz_Zom(PlayerSpaz):
def handlemessage(self, m: Any) -> Any:
if isinstance(m, ba.HitMessage):
if not self.node:
return
if not m._source_player is None:
try:
playa = m._source_player.getname(True, False)
if not playa is None:
if m._source_player.lives < 1:
super().handlemessage(m)
except:
super().handlemessage(m)
else:
super().handlemessage(m)
elif isinstance(m, ba.FreezeMessage):
pass
elif isinstance(m, PickupMessage):
if not self.node:
return None
try:
collision = ba.getcollision()
opposingnode = collision.opposingnode
opposingbody = collision.opposingbody
except ba.NotFoundError:
return True
try:
if opposingnode.invincible:
return True
except Exception:
pass
try:
playa = opposingnode._source_player.getname(True, False)
if not playa is None:
if opposingnode._source_player.lives > 0:
return True
except Exception:
pass
if (opposingnode.getnodetype() == 'spaz'
and not opposingnode.shattered and opposingbody == 4):
opposingbody = 1
held = self.node.hold_node
if held and held.getnodetype() == 'flag':
return True
self.node.hold_body = opposingbody
self.node.hold_node = opposingnode
else:
return super().handlemessage(m)
return None
class PlayerZombie(PlayerSpaz):
def handlemessage(self, m: Any) -> Any:
if isinstance(m, ba.HitMessage):
if not self.node:
return None
if not m._source_player is None:
try:
playa = m._source_player.getname(True, False)
if playa is None:
pass
else:
super().handlemessage(m)
except:
super().handlemessage(m)
else:
super().handlemessage(m)
else:
super().handlemessage(m)
class zBotSet(SpazBotSet):
def start_moving(self) -> None:
"""Start processing bot AI updates so they start doing their thing."""
self._bot_update_timer = ba.Timer(0.05,
ba.WeakCall(self.zUpdate),
repeat=True)
def zUpdate(self) -> None:
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 = []
ba.print_exception('Error updating bot list: ' +
str(self._bot_lists[self._bot_update_list]))
self._bot_update_list = (self._bot_update_list +
1) % self._bot_list_count
player_pts = []
for player in ba.getactivity().players:
assert isinstance(player, ba.Player)
try:
if player.is_alive():
assert isinstance(player.actor, Spaz)
assert player.actor.node
if player.lives > 0:
player_pts.append(
(ba.Vec3(player.actor.node.position),
ba.Vec3(player.actor.node.velocity)))
except Exception:
ba.print_exception('Error on bot-set _update.')
for bot in bot_list:
bot.set_player_points(player_pts)
bot.update_ai()
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
self.spawn_order: list[Player] = []
# ba_meta export game
class ZombieHorde(ba.TeamGameActivity[Player, Team]):
name = 'Zombie Horde'
description = 'Kill walkers for points!'
scoreconfig = ba.ScoreConfig(label='Score',
scoretype=ba.ScoreType.POINTS,
none_is_winner=False,
lower_is_better=False)
# Show messages when players die since it's meaningful here.
announce_player_deaths = True
@classmethod
def get_available_settings(
cls, sessiontype: type[ba.Session]) -> list[ba.Setting]:
settings = [
ba.IntSetting(
'Lives Per Player',
default=1,
min_value=1,
max_value=10,
increment=1,
),
ba.IntSetting(
'Max Zombies',
default=10,
min_value=5,
max_value=50,
increment=5,
),
ba.IntChoiceSetting(
'Time Limit',
choices=[
('None', 0),
('1 Minute', 60),
('2 Minutes', 120),
('5 Minutes', 300),
('10 Minutes', 600),
('20 Minutes', 1200),
],
default=0,
),
ba.FloatChoiceSetting(
'Respawn Times',
choices=[
('Shorter', 0.25),
('Short', 0.5),
('Normal', 1.0),
('Long', 2.0),
('Longer', 4.0),
],
default=1.0,
),
ba.BoolSetting('Epic Mode', default=False),
]
if issubclass(sessiontype, ba.DualTeamSession):
settings.append(ba.BoolSetting('Solo Mode', default=False))
settings.append(
ba.BoolSetting('Balance Total Lives', default=False))
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.DualTeamSession)
or issubclass(sessiontype, ba.FreeForAllSession))
@classmethod
def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
return ba.getmaps('melee')
def __init__(self, settings: dict):
super().__init__(settings)
self._scoreboard = Scoreboard()
self._start_time: float | None = None
self._vs_text: ba.Actor | None = None
self._round_end_timer: ba.Timer | None = None
self._epic_mode = bool(settings['Epic Mode'])
self._lives_per_player = int(settings['Lives Per Player'])
self._max_zombies = int(settings['Max Zombies'])
self._time_limit = float(settings['Time Limit'])
self._balance_total_lives = bool(
settings.get('Balance Total Lives', False))
self._solo_mode = bool(settings.get('Solo Mode', False))
# Base class overrides:
self.slow_motion = self._epic_mode
self.default_music = (ba.MusicType.EPIC
if self._epic_mode else ba.MusicType.SURVIVAL)
self.spazList = []
self.zombieQ = 0
activity = ba.getactivity()
my_factory = SpazFactory.get()
appears = ['Kronk','Zoe','Pixel','Agent Johnson',
'Bones','Frosty','Kronk2']
myAppear = copy.copy(ba.app.spaz_appearances['Kronk'])
myAppear.name = 'Kronk2'
ba.app.spaz_appearances['Kronk2'] = myAppear
for appear in appears:
my_factory.get_media(appear)
med = my_factory.spaz_media
med['Kronk2']['head_model'] = med['Zoe']['head_model']
med['Kronk2']['color_texture'] = med['Agent Johnson']['color_texture']
med['Kronk2']['color_mask_texture']=med['Pixel']['color_mask_texture']
med['Kronk2']['torso_model'] = med['Bones']['torso_model']
med['Kronk2']['pelvis_model'] = med['Pixel']['pelvis_model']
med['Kronk2']['upper_arm_model'] = med['Frosty']['upper_arm_model']
med['Kronk2']['forearm_model'] = med['Frosty']['forearm_model']
med['Kronk2']['hand_model'] = med['Bones']['hand_model']
med['Kronk2']['upper_leg_model'] = med['Bones']['upper_leg_model']
med['Kronk2']['lower_leg_model'] = med['Pixel']['lower_leg_model']
med['Kronk2']['toes_model'] = med['Bones']['toes_model']
def get_instance_description(self) -> str | Sequence:
return ('Kill walkers for points! ',
'Dead player walker: 2 points!') if isinstance(
self.session, ba.DualTeamSession) else (
'Kill walkers for points! Dead player walker: 2 points!')
def get_instance_description_short(self) -> str | Sequence:
return ('Kill walkers for points! ',
'Dead player walker: 2 points!') if isinstance(
self.session, ba.DualTeamSession) else (
'Kill walkers for points! Dead player walker: 2 points!')
def on_player_join(self, player: Player) -> None:
if self.has_begun():
player.lives = 0
player.icons = []
ba.screenmessage(
ba.Lstr(resource='playerDelayedJoinText',
subs=[('${PLAYER}', player.getname(full=True))]),
color=(0, 1, 0),
)
return
player.lives = self._lives_per_player
if self._solo_mode:
player.icons = []
player.team.spawn_order.append(player)
self._update_solo_mode()
else:
player.icons = [Icon(player, position=(0, 50), scale=0.8)]
if player.lives > 0:
self.spawn_player(player)
if self.has_begun():
self._update_icons()
def _update_solo_mode(self) -> None:
for team in self.teams:
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
break
def on_begin(self) -> None:
super().on_begin()
self._start_time = ba.time()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
self.zombieQ = 1
if self._solo_mode:
self._vs_text = ba.NodeActor(
ba.newnode('text',
attrs={
'position': (0, 105),
'h_attach': 'center',
'h_align': 'center',
'maxwidth': 200,
'shadow': 0.5,
'vr_depth': 390,
'scale': 0.6,
'v_attach': 'bottom',
'color': (0.8, 0.8, 0.3, 1.0),
'text': ba.Lstr(resource='vsText')
}))
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
if (isinstance(self.session, ba.DualTeamSession)
and self._balance_total_lives and self.teams[0].players
and self.teams[1].players):
if self._get_total_team_lives(
self.teams[0]) < self._get_total_team_lives(self.teams[1]):
lesser_team = self.teams[0]
greater_team = self.teams[1]
else:
lesser_team = self.teams[1]
greater_team = self.teams[0]
add_index = 0
while (self._get_total_team_lives(lesser_team) <
self._get_total_team_lives(greater_team)):
lesser_team.players[add_index].lives += 1
add_index = (add_index + 1) % len(lesser_team.players)
self._bots = zBotSet()
#Set colors and character for ToughGuyBot to be zombie
setattr(BrawlerBot, 'color', (0.4,0.1,0.05))
setattr(BrawlerBot, 'highlight', (0.2,0.4,0.3))
setattr(BrawlerBot, 'character', 'Kronk2')
# start some timers to spawn bots
thePt = self.map.get_ffa_start_position(self.players)
self._update_icons()
# We could check game-over conditions at explicit trigger points,
# but lets just do the simple thing and poll it.
ba.timer(1.0, self._update, repeat=True)
def _update_icons(self) -> None:
# pylint: disable=too-many-branches
# In free-for-all mode, everyone is just lined up along the bottom.
if isinstance(self.session, ba.FreeForAllSession):
count = len(self.teams)
x_offs = 85
xval = x_offs * (count - 1) * -0.5
for team in self.teams:
if len(team.players) == 1:
player = team.players[0]
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
# In teams mode we split up teams.
else:
if self._solo_mode:
# First off, clear out all icons.
for player in self.players:
player.icons = []
# Now for each team, cycle through our available players
# adding icons.
for team in self.teams:
if team.id == 0:
xval = -60
x_offs = -78
else:
xval = 60
x_offs = 78
is_first = True
test_lives = 1
while True:
players_with_lives = [
p for p in team.spawn_order
if p and p.lives >= test_lives
]
if not players_with_lives:
break
for player in players_with_lives:
player.icons.append(
Icon(player,
position=(xval, (40 if is_first else 25)),
scale=1.0 if is_first else 0.5,
name_maxwidth=130 if is_first else 75,
name_scale=0.8 if is_first else 1.0,
flatness=0.0 if is_first else 1.0,
shadow=0.5 if is_first else 1.0,
show_death=is_first,
show_lives=False))
xval += x_offs * (0.8 if is_first else 0.56)
is_first = False
test_lives += 1
# Non-solo mode.
else:
for team in self.teams:
if team.id == 0:
xval = -50
x_offs = -85
else:
xval = 50
x_offs = 85
for player in team.players:
for icon in player.icons:
icon.set_position_and_scale((xval, 30), 0.7)
icon.update_for_lives()
xval += x_offs
def _get_spawn_point(self, player: Player) -> ba.Vec3 | None:
del player # Unused.
# In solo-mode, if there's an existing live player on the map, spawn at
# whichever spot is farthest from them (keeps the action spread out).
if self._solo_mode:
living_player = None
living_player_pos = None
for team in self.teams:
for tplayer in team.players:
if tplayer.is_alive():
assert tplayer.node
ppos = tplayer.node.position
living_player = tplayer
living_player_pos = ppos
break
if living_player:
assert living_player_pos is not None
player_pos = ba.Vec3(living_player_pos)
points: list[tuple[float, ba.Vec3]] = []
for team in self.teams:
start_pos = ba.Vec3(self.map.get_start_position(team.id))
points.append(
((start_pos - player_pos).length(), start_pos))
# Hmm.. we need to sorting vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1]
return None
def spawn_player(self, player: Player) -> ba.Actor:
position = self.map.get_ffa_start_position(self.players)
angle = 20
name = player.getname()
light_color = _math.normalized_color(player.color)
display_color = _ba.safecolor(player.color, target_intensity=0.75)
spaz = PlayerSpaz_Zom(color=player.color,
highlight=player.highlight,
character=player.character,
player=player)
player.actor = spaz
assert spaz.node
self.spazList.append(spaz)
if isinstance(self.session, CoopSession) and self.map.getname() in [
'Courtyard', 'Tower D'
]:
mat = self.map.preloaddata['collide_with_wall_material']
assert isinstance(spaz.node.materials, tuple)
assert isinstance(spaz.node.roller_materials, tuple)
spaz.node.materials += (mat, )
spaz.node.roller_materials += (mat, )
spaz.node.name = name
spaz.node.name_color = display_color
spaz.connect_controls_to_player()
factory = SpazFactory()
# Move to the stand position and add a flash of light.
spaz.handlemessage(
StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
_ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
light = _ba.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
_ba.timer(0.5, light.delete)
if not self._solo_mode:
ba.timer(0.3, ba.Call(self._print_lives, player))
for icon in player.icons:
icon.handle_player_spawned()
return spaz
def respawn_player_zombie(self,
player: Player,
respawn_time: float | None = None) -> None:
# pylint: disable=cyclic-import
assert player
if respawn_time is None:
teamsize = len(player.team.players)
if teamsize == 1:
respawn_time = 3.0
elif teamsize == 2:
respawn_time = 5.0
elif teamsize == 3:
respawn_time = 6.0
else:
respawn_time = 7.0
# If this standard setting is present, factor it in.
if 'Respawn Times' in self.settings_raw:
respawn_time *= self.settings_raw['Respawn Times']
# We want whole seconds.
assert respawn_time is not None
respawn_time = round(max(1.0, respawn_time), 0)
if player.actor and not self.has_ended():
from bastd.actor.respawnicon import RespawnIcon
player.customdata['respawn_timer'] = _ba.Timer(
respawn_time, ba.WeakCall(
self.spawn_player_if_exists_as_zombie, player))
player.customdata['respawn_icon'] = RespawnIcon(
player, respawn_time)
def spawn_player_if_exists_as_zombie(self, player: PlayerType) -> None:
"""
A utility method which calls self.spawn_player() *only* if the
ba.Player provided still exists; handy for use in timers and whatnot.
There is no need to override this; just override spawn_player().
"""
if player:
self.spawn_player_zombie(player)
def spawn_player_zombie(self, player: PlayerType) -> ba.Actor:
position = self.map.get_ffa_start_position(self.players)
angle = 20
name = player.getname()
light_color = _math.normalized_color(player.color)
display_color = _ba.safecolor(player.color, target_intensity=0.75)
spaz = PlayerSpaz_Zom(color=player.color,
highlight=player.highlight,
character='Kronk2',
player=player)
player.actor = spaz
assert spaz.node
self.spazList.append(spaz)
if isinstance(self.session, CoopSession) and self.map.getname() in [
'Courtyard', 'Tower D'
]:
mat = self.map.preloaddata['collide_with_wall_material']
assert isinstance(spaz.node.materials, tuple)
assert isinstance(spaz.node.roller_materials, tuple)
spaz.node.materials += (mat, )
spaz.node.roller_materials += (mat, )
spaz.node.name = name
spaz.node.name_color = display_color
spaz.connect_controls_to_player(enable_punch=True,
enable_bomb=False,
enable_pickup=False)
# Move to the stand position and add a flash of light.
spaz.handlemessage(
StandMessage(
position,
angle if angle is not None else random.uniform(0, 360)))
_ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
light = _ba.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
_ba.timer(0.5, light.delete)
if not self._solo_mode:
ba.timer(0.3, ba.Call(self._print_lives, player))
for icon in player.icons:
icon.handle_player_spawned()
return spaz
def _print_lives(self, player: Player) -> None:
from bastd.actor import popuptext
# We get called in a timer so it's possible our player has left/etc.
if not player or not player.is_alive() or not player.node:
return
try:
pos = player.actor.node.position
except Exception as e:
print('EXC getting player pos in bsElim',e)
return
if player.lives > 0:
popuptext.PopupText('x' + str(player.lives - 1),
color=(1, 1, 0, 1),
offset=(0, -0.8, 0),
random_offset=0.0,
scale=1.8,
position=pos).autoretain()
else:
popuptext.PopupText('Dead!',
color=(1, 1, 0, 1),
offset=(0, -0.8, 0),
random_offset=0.0,
scale=1.8,
position=pos).autoretain()
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
player.icons = []
# Remove us from spawn-order.
if self._solo_mode:
if player in player.team.spawn_order:
player.team.spawn_order.remove(player)
# Update icons in a moment since our team will be gone from the
# list then.
ba.timer(0, self._update_icons)
def _get_total_team_lives(self, team: Team) -> int:
return sum(player.lives for player in team.players)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
player: Player = msg.getplayer(Player)
if player.lives > 0:
player.lives -= 1
else:
if msg._killerplayer:
if msg._killerplayer.lives > 0:
msg._killerplayer.team.score += 2
self._update_scoreboard()
if msg._player in self.spazList:
self.spazList.remove(msg._player)
if player.lives < 0:
ba.print_error(
"Got lives < 0 in Elim; this shouldn't happen. solo:" +
str(self._solo_mode))
player.lives = 0
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
# Play big death sound on our last death
# or for every one in solo mode.
if self._solo_mode or player.lives == 0:
ba.playsound(SpazFactory.get().single_player_death_sound)
# If we hit zero lives, we're dead (and our team might be too).
if player.lives == 0:
self.respawn_player_zombie(player)
else:
# Otherwise, in regular mode, respawn.
if not self._solo_mode:
self.respawn_player(player)
# In solo, put ourself at the back of the spawn order.
if self._solo_mode:
player.team.spawn_order.remove(player)
player.team.spawn_order.append(player)
elif isinstance(msg, SpazBotDiedMessage):
self._onSpazBotDied(msg)
super().handlemessage(msg)#bs.PopupText("died",position=self._position,color=popupColor,scale=popupScale).autoRetain()
else:
super().handlemessage(msg)
def _update(self) -> None:
if self.zombieQ > 0:
self.zombieQ -= 1
self.spawn_zombie()
if self._solo_mode:
# For both teams, find the first player on the spawn order
# list with lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
break
# If we're down to 1 or fewer living teams, start a timer to end
# the game (allows the dust to settle and draws to occur if deaths
# are close enough).
teamsRemain = self._get_living_teams()
if len(teamsRemain) < 2:
if len(teamsRemain) == 1:
theScores = []
for team in self.teams:
theScores.append(team.score)
if teamsRemain[0].score < max(theScores):
pass
elif teamsRemain[0].score == max(
theScores) and theScores.count(max(theScores)) > 1:
pass
else:
self._round_end_timer = ba.Timer(0.5, self.end_game)
else:
self._round_end_timer = ba.Timer(0.5, self.end_game)
def spawn_zombie(self) -> None:
#We need a Z height...
thePt = list(self.get_random_point_in_play())
thePt2 = self.map.get_ffa_start_position(self.players)
thePt[1] = thePt2[1]
ba.timer(0.1, ba.Call(
self._bots.spawn_bot, BrawlerBot, pos=thePt, spawn_time=1.0))
def _onSpazBotDied(self,DeathMsg) -> None:
#Just in case we are over max...
if len(self._bots.get_living_bots()) < self._max_zombies:
self.zombieQ += 1
if DeathMsg.killerplayer is None:
pass
else:
player = DeathMsg.killerplayer
if not player:
return
if player.lives < 1:
return
player.team.score += 1
self.zombieQ += 1
self._update_scoreboard()
def get_random_point_in_play(self) -> None:
myMap = self.map.getname()
if myMap == 'Doom Shroom':
while True:
x = random.uniform(-1.0,1.0)
y = random.uniform(-1.0,1.0)
if x*x+y*y < 1.0: break
return ((8.0*x,8.0,-3.5+5.0*y))
elif myMap == 'Rampage':
x = random.uniform(-6.0,7.0)
y = random.uniform(-6.0,-2.5)
return ((x, 8.0, y))
elif myMap == 'Hockey Stadium':
x = random.uniform(-11.5,11.5)
y = random.uniform(-4.5,4.5)
return ((x, 5.0, y))
elif myMap == 'Courtyard':
x = random.uniform(-4.3,4.3)
y = random.uniform(-4.4,0.3)
return ((x, 8.0, y))
elif myMap == 'Crag Castle':
x = random.uniform(-6.7,8.0)
y = random.uniform(-6.0,0.0)
return ((x, 12.0, y))
elif myMap == 'Big G':
x = random.uniform(-8.7,8.0)
y = random.uniform(-7.5,6.5)
return ((x, 8.0, y))
elif myMap == 'Football Stadium':
x = random.uniform(-12.5,12.5)
y = random.uniform(-5.0,5.5)
return ((x, 8.0, y))
else:
x = random.uniform(-5.0,5.0)
y = random.uniform(-6.0,0.0)
return ((x, 8.0, y))
def _update_scoreboard(self) -> None:
for team in self.teams:
self._scoreboard.set_team_value(team, team.score)
def _get_living_teams(self) -> list[Team]:
return [
team for team in self.teams
if len(team.players) > 0 and any(player.lives > 0
for player in team.players)
]
def end_game(self) -> None:
if self.has_ended():
return
setattr(BrawlerBot, 'color', (0.6, 0.6, 0.6))
setattr(BrawlerBot, 'highlight', (0.6, 0.6, 0.6))
setattr(BrawlerBot, 'character', 'Kronk')
results = ba.GameResults()
self._vs_text = None # Kill our 'vs' if its there.
for team in self.teams:
results.set_team_score(team, team.score)
self.end(results=results)
# ba_meta export game
class ZombieHordeCoop(ZombieHorde):
name = 'Zombie Horde'
@classmethod
def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
return ['Football Stadium']
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
return (issubclass(sessiontype, ba.CoopSession))
def _update(self) -> None:
if self.zombieQ > 0:
self.zombieQ -= 1
self.spawn_zombie()
if self._solo_mode:
# For both teams, find the first player on the spawn order
# list with lives remaining and spawn them if they're not alive.
for team in self.teams:
# Prune dead players from the spawn order.
team.spawn_order = [p for p in team.spawn_order if p]
for player in team.spawn_order:
assert isinstance(player, Player)
if player.lives > 0:
if not player.is_alive():
self.spawn_player(player)
self._update_icons()
break
if not any(player.is_alive() for player in self.teams[0].players):
self._round_end_timer = ba.Timer(0.5, self.end_game)
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
ba.TeamGameActivity.handlemessage(self, msg)
player: Player = msg.getplayer(Player)
# If we have any icons, update their state.
for icon in player.icons:
icon.handle_player_died()
else:
super().handlemessage(msg)
# ba_meta export plugin
class ZombieHordeLevel(ba.Plugin):
def on_app_running(self) -> None:
ba.app.add_coop_practice_level(
ba.Level(
'Zombie Horde',
gametype=ZombieHordeCoop,
settings={},
preview_texture_name='footballStadiumPreview',
)
)