Cleaned up music playback

This commit is contained in:
Eric Froemling 2020-04-01 01:22:26 -07:00
parent 6f3f1644b4
commit 2a2e64b7ab
36 changed files with 386 additions and 305 deletions

View File

@ -436,6 +436,7 @@
<w>domreg</w> <w>domreg</w>
<w>domsg</w> <w>domsg</w>
<w>dont</w> <w>dont</w>
<w>doplay</w>
<w>doraise</w> <w>doraise</w>
<w>dosend</w> <w>dosend</w>
<w>dosomething</w> <w>dosomething</w>
@ -1066,6 +1067,7 @@
<w>mtime</w> <w>mtime</w>
<w>mtrans</w> <w>mtrans</w>
<w>mtvos</w> <w>mtvos</w>
<w>mtype</w>
<w>mult</w> <w>mult</w>
<w>multibytecodec</w> <w>multibytecodec</w>
<w>multikillcount</w> <w>multikillcount</w>
@ -1074,6 +1076,8 @@
<w>multiteamendscreen</w> <w>multiteamendscreen</w>
<w>multiteamjoinscreen</w> <w>multiteamjoinscreen</w>
<w>multithreaded</w> <w>multithreaded</w>
<w>musicinfo</w>
<w>musicinfos</w>
<w>musicplayer</w> <w>musicplayer</w>
<w>musictype</w> <w>musictype</w>
<w>musopen</w> <w>musopen</w>
@ -1526,6 +1530,7 @@
<w>socketmodule</w> <w>socketmodule</w>
<w>socketserver</w> <w>socketserver</w>
<w>somevar</w> <w>somevar</w>
<w>soundtrackname</w>
<w>sourceimages</w> <w>sourceimages</w>
<w>sourcelines</w> <w>sourcelines</w>
<w>spacelen</w> <w>spacelen</w>

View File

@ -50,6 +50,9 @@
- The bs.Vector class is no more; in its place is a shiny new ba.Vec3 which is implemented internally in C++ so its nice and speedy. Will probably update certain things like vector node attrs to support this class in the future since it makes vector math nice and convenient. - The bs.Vector class is no more; in its place is a shiny new ba.Vec3 which is implemented internally in C++ so its nice and speedy. Will probably update certain things like vector node attrs to support this class in the future since it makes vector math nice and convenient.
- Ok you get the point.. - Ok you get the point..
### 1.4.151 (14371)
- Added Chinese-Traditional language and improved translations for others.
### 1.4.150 (14369) ### 1.4.150 (14369)
- Telnet port can now be specified in the config - Telnet port can now be specified in the config
- Telnet socket no longer opens on headless build when telnet access is off (reduces DoS attack potential) - Telnet socket no longer opens on headless build when telnet access is off (reduces DoS attack potential)

View File

@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand.
""" """
# (hash we can use to see if this file is out of date) # (hash we can use to see if this file is out of date)
# SOURCES_HASH=107308741262112812748560676667362932520 # SOURCES_HASH=237130603961569354119127975507628873256
# I'm sorry Pylint. I know this file saddens you. Be strong. # I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression # pylint: disable=useless-suppression
@ -2737,62 +2737,6 @@ def is_running_on_ouya() -> bool:
return bool() return bool()
def itunes_get_library_source() -> None:
"""itunes_get_library_source() -> None
(internal)
"""
return None
def itunes_get_playlists() -> List[str]:
"""itunes_get_playlists() -> List[str]
(internal)
"""
return ["blah", "blah2"]
def itunes_get_volume() -> int:
"""itunes_get_volume() -> int
(internal)
"""
return int()
def itunes_init() -> None:
"""itunes_init() -> None
(internal)
"""
return None
def itunes_play_playlist(playlist: str) -> bool:
"""itunes_play_playlist(playlist: str) -> bool
(internal)
"""
return bool()
def itunes_set_volume(volume: int) -> None:
"""itunes_set_volume(volume: int) -> None
(internal)
"""
return None
def itunes_stop() -> None:
"""itunes_stop() -> None
(internal)
"""
return None
def lock_all_input() -> None: def lock_all_input() -> None:
"""lock_all_input() -> None """lock_all_input() -> None
@ -2825,6 +2769,62 @@ def log(message: str,
return None return None
def mac_music_app_get_library_source() -> None:
"""mac_music_app_get_library_source() -> None
(internal)
"""
return None
def mac_music_app_get_playlists() -> List[str]:
"""mac_music_app_get_playlists() -> List[str]
(internal)
"""
return ["blah", "blah2"]
def mac_music_app_get_volume() -> int:
"""mac_music_app_get_volume() -> int
(internal)
"""
return int()
def mac_music_app_init() -> None:
"""mac_music_app_init() -> None
(internal)
"""
return None
def mac_music_app_play_playlist(playlist: str) -> bool:
"""mac_music_app_play_playlist(playlist: str) -> bool
(internal)
"""
return bool()
def mac_music_app_set_volume(volume: int) -> None:
"""mac_music_app_set_volume(volume: int) -> None
(internal)
"""
return None
def mac_music_app_stop() -> None:
"""mac_music_app_stop() -> None
(internal)
"""
return None
def mark_config_dirty() -> None: def mark_config_dirty() -> None:
"""mark_config_dirty() -> None """mark_config_dirty() -> None

View File

@ -78,7 +78,7 @@ from ba._messages import (OutOfBoundsMessage, DieMessage, StandMessage,
DroppedMessage, ShouldShatterMessage, DroppedMessage, ShouldShatterMessage,
ImpactDamageMessage, FreezeMessage, ThawMessage, ImpactDamageMessage, FreezeMessage, ThawMessage,
HitMessage) HitMessage)
from ba._music import setmusic, MusicPlayer from ba._music import setmusic, MusicPlayer, MusicType, MusicPlayMode
from ba._powerup import PowerupMessage, PowerupAcceptMessage from ba._powerup import PowerupMessage, PowerupAcceptMessage
from ba._teambasesession import TeamBaseSession from ba._teambasesession import TeamBaseSession
from ba.ui import (OldWindow, UILocation, UILocationWindow, UIController, from ba.ui import (OldWindow, UILocation, UILocationWindow, UIController,

View File

@ -25,7 +25,8 @@ import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import _ba import _ba
from ba import _activity from ba._activity import Activity
from ba._music import setmusic, MusicType
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
@ -33,7 +34,7 @@ if TYPE_CHECKING:
from ba._lobby import JoinInfo from ba._lobby import JoinInfo
class EndSessionActivity(_activity.Activity): class EndSessionActivity(Activity):
"""Special ba.Activity to fade out and end the current ba.Session.""" """Special ba.Activity to fade out and end the current ba.Session."""
def __init__(self, settings: Dict[str, Any]): def __init__(self, settings: Dict[str, Any]):
@ -61,7 +62,7 @@ class EndSessionActivity(_activity.Activity):
call_after_ad(Call(_ba.new_host_session, MainMenuSession)) call_after_ad(Call(_ba.new_host_session, MainMenuSession))
class JoiningActivity(_activity.Activity): class JoiningActivity(Activity):
"""Standard activity for waiting for players to join. """Standard activity for waiting for players to join.
It shows tips and other info and waits for all players to check ready. It shows tips and other info and waits for all players to check ready.
@ -88,18 +89,17 @@ class JoiningActivity(_activity.Activity):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bastd.actor.tipstext import TipsText from bastd.actor.tipstext import TipsText
from bastd.actor.background import Background from bastd.actor.background import Background
from ba import _music
super().on_transition_in() super().on_transition_in()
self._background = Background(fade_time=0.5, self._background = Background(fade_time=0.5,
start_faded=True, start_faded=True,
show_logo=True) show_logo=True)
self._tips_text = TipsText() self._tips_text = TipsText()
_music.setmusic('CharSelect') setmusic(MusicType.CHAR_SELECT)
self._join_info = self.session.lobby.create_join_info() self._join_info = self.session.lobby.create_join_info()
_ba.set_analytics_screen('Joining Screen') _ba.set_analytics_screen('Joining Screen')
class TransitionActivity(_activity.Activity): class TransitionActivity(Activity):
"""A simple overlay fade out/in. """A simple overlay fade out/in.
Useful as a bare minimum transition between two level based activities. Useful as a bare minimum transition between two level based activities.
@ -132,7 +132,7 @@ class TransitionActivity(_activity.Activity):
_ba.timer(0.1, self.end) _ba.timer(0.1, self.end)
class ScoreScreenActivity(_activity.Activity): class ScoreScreenActivity(Activity):
"""A standard score screen that fades in and shows stuff for a while. """A standard score screen that fades in and shows stuff for a while.
After a specified delay, player input is assigned to end the activity. After a specified delay, player input is assigned to end the activity.
@ -151,7 +151,7 @@ class ScoreScreenActivity(_activity.Activity):
self._tips_text: Optional[ba.Actor] = None self._tips_text: Optional[ba.Actor] = None
self._kicked_off_server_shutdown = False self._kicked_off_server_shutdown = False
self._kicked_off_server_restart = False self._kicked_off_server_restart = False
self._default_music: Optional[str] = 'Scores' self._default_music: Optional[MusicType] = MusicType.SCORES
self._default_show_tips = True self._default_show_tips = True
def on_player_join(self, player: ba.Player) -> None: def on_player_join(self, player: ba.Player) -> None:
@ -168,14 +168,13 @@ class ScoreScreenActivity(_activity.Activity):
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
from bastd.actor.tipstext import TipsText from bastd.actor.tipstext import TipsText
from bastd.actor.background import Background from bastd.actor.background import Background
from ba import _music
super().on_transition_in() super().on_transition_in()
self._background = Background(fade_time=0.5, self._background = Background(fade_time=0.5,
start_faded=False, start_faded=False,
show_logo=True) show_logo=True)
if self._default_show_tips: if self._default_show_tips:
self._tips_text = TipsText() self._tips_text = TipsText()
_music.setmusic(self._default_music) setmusic(self._default_music)
def on_begin(self, custom_continue_message: ba.Lstr = None) -> None: def on_begin(self, custom_continue_message: ba.Lstr = None) -> None:
# FIXME: Unify args. # FIXME: Unify args.

View File

@ -259,6 +259,7 @@ class App:
the single shared instance. the single shared instance.
""" """
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
from ba._music import MusicPlayMode
# _test_https() # _test_https()
@ -270,26 +271,44 @@ class App:
# refreshed/etc. # refreshed/etc.
self.fg_state = 0 self.fg_state = 0
# Environment stuff (pulling these out as attrs so we can type-check # Environment stuff.
# them). # (pulling these into attrs so we can type-check them)
env = _ba.env() env = _ba.env()
self._build_number: int = env['build_number'] self._build_number: int = env['build_number']
assert isinstance(self._build_number, int)
self._config_file_path: str = env['config_file_path'] self._config_file_path: str = env['config_file_path']
assert isinstance(self._config_file_path, str)
self._locale: str = env['locale'] self._locale: str = env['locale']
assert isinstance(self._locale, str)
self._user_agent_string: str = env['user_agent_string'] self._user_agent_string: str = env['user_agent_string']
assert isinstance(self._user_agent_string, str)
self._version: str = env['version'] self._version: str = env['version']
assert isinstance(self._version, str)
self._debug_build: bool = env['debug_build'] self._debug_build: bool = env['debug_build']
assert isinstance(self._debug_build, bool)
self._test_build: bool = env['test_build'] self._test_build: bool = env['test_build']
assert isinstance(self._test_build, bool)
self._user_scripts_directory: str = env['user_scripts_directory'] self._user_scripts_directory: str = env['user_scripts_directory']
assert isinstance(self._user_scripts_directory, str)
self._system_scripts_directory: str = env['system_scripts_directory'] self._system_scripts_directory: str = env['system_scripts_directory']
assert isinstance(self._system_scripts_directory, str)
self._platform: str = env['platform'] self._platform: str = env['platform']
assert isinstance(self._platform, str)
self._subplatform: str = env['subplatform'] self._subplatform: str = env['subplatform']
assert isinstance(self._subplatform, str)
self._interface_type: str = env['interface_type'] self._interface_type: str = env['interface_type']
assert isinstance(self._interface_type, str)
self._on_tv: bool = env['on_tv'] self._on_tv: bool = env['on_tv']
assert isinstance(self._on_tv, bool)
self._vr_mode: bool = env['vr_mode'] self._vr_mode: bool = env['vr_mode']
assert isinstance(self._vr_mode, bool)
self.protocol_version: int = env['protocol_version'] self.protocol_version: int = env['protocol_version']
assert isinstance(self.protocol_version, int)
self.toolbar_test: bool = env['toolbar_test'] self.toolbar_test: bool = env['toolbar_test']
assert isinstance(self.toolbar_test, bool)
self.kiosk_mode: bool = env['kiosk_mode'] self.kiosk_mode: bool = env['kiosk_mode']
assert isinstance(self.kiosk_mode, bool)
# Misc. # Misc.
self.default_language = self._get_default_language() self.default_language = self._get_default_language()
@ -339,12 +358,12 @@ class App:
# Music. # Music.
self.music: Optional[ba.Node] = None self.music: Optional[ba.Node] = None
self.music_mode: str = 'regular' self.music_mode: ba.MusicPlayMode = MusicPlayMode.REGULAR
self.music_player: Optional[ba.MusicPlayer] = None self.music_player: Optional[ba.MusicPlayer] = None
self.music_player_type: Optional[Type[ba.MusicPlayer]] = None self.music_player_type: Optional[Type[ba.MusicPlayer]] = None
self.music_types: Dict[str, Optional[str]] = { self.music_types: Dict[ba.MusicPlayMode, Optional[ba.MusicType]] = {
'regular': None, MusicPlayMode.REGULAR: None,
'test': None MusicPlayMode.TEST: None
} }
# Language. # Language.
@ -443,15 +462,16 @@ class App:
cfg = self.config cfg = self.config
# Set up our app delegate.
self.delegate = appdelegate.AppDelegate() self.delegate = appdelegate.AppDelegate()
self.uicontroller = bsui.UIController() self.uicontroller = bsui.UIController()
_achievement.init_achievements() _achievement.init_achievements()
spazappearance.register_appearances() spazappearance.register_appearances()
_campaign.init_campaigns() _campaign.init_campaigns()
if _ba.env()['platform'] == 'android': if _ba.env()['platform'] == 'android':
self.music_player_type = _music.InternalMusicPlayer self.music_player_type = _music.InternalMusicPlayer
elif _ba.env()['platform'] == 'mac' and hasattr(_ba, 'itunes_init'): elif _ba.env()['platform'] == 'mac' and hasattr(
_ba, 'mac_music_app_init'):
self.music_player_type = _music.MacITunesMusicPlayer self.music_player_type = _music.MacITunesMusicPlayer
# FIXME: This should not be hard-coded. # FIXME: This should not be hard-coded.
@ -550,6 +570,8 @@ class App:
launch_count = cfg.get('launchCount', 0) launch_count = cfg.get('launchCount', 0)
launch_count += 1 launch_count += 1
# So we know how many times we've run the game at various
# version milestones.
for key in ('lc14173', 'lc14292'): for key in ('lc14173', 'lc14292'):
cfg.setdefault(key, launch_count) cfg.setdefault(key, launch_count)
@ -615,7 +637,6 @@ class App:
If there's a foreground host-activity that says it's pausable, tell it If there's a foreground host-activity that says it's pausable, tell it
to pause ..we now no longer pause if there are connected clients. to pause ..we now no longer pause if there are connected clients.
""" """
# pylint: disable=cyclic-import
activity = _ba.get_foreground_host_activity() activity = _ba.get_foreground_host_activity()
if (activity is not None and activity.allow_pausing if (activity is not None and activity.allow_pausing
and not _ba.have_connected_clients()): and not _ba.have_connected_clients()):

View File

@ -308,7 +308,7 @@ class GameActivity(Activity):
# Music that should play in on_transition_in() # Music that should play in on_transition_in()
# (unless overridden by the map). # (unless overridden by the map).
self._default_music: Optional[str] = None self._default_music: Optional[ba.MusicType] = None
# Go ahead and get our map loading. # Go ahead and get our map loading.
map_name: str map_name: str

View File

@ -196,7 +196,7 @@ class Map(Actor):
return cls.name return cls.name
@classmethod @classmethod
def get_music_type(cls) -> Optional[str]: def get_music_type(cls) -> Optional[ba.MusicType]:
"""Return a music-type string that should be played on this map. """Return a music-type string that should be played on this map.
If None is returned, default music will be used. If None is returned, default music will be used.

View File

@ -26,11 +26,44 @@ import os
import random import random
import threading import threading
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from enum import Enum
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable, Any, List, Optional, Dict, Union from typing import Callable, Any, List, Optional, Dict, Union, Tuple
class MusicType(Enum):
"""Types of music available to play in-game."""
MENU = 'Menu'
VICTORY = 'Victory'
CHAR_SELECT = 'CharSelect'
RUN_AWAY = 'RunAway'
ONSLAUGHT = 'Onslaught'
KEEP_AWAY = 'Keep Away'
RACE = 'Race'
EPIC_RACE = 'Epic Race'
SCORES = 'Scores'
GRAND_ROMP = 'GrandRomp'
TO_THE_DEATH = 'ToTheDeath'
CHOSEN_ONE = 'Chosen One'
FORWARD_MARCH = 'ForwardMarch'
FLAG_CATCHER = 'FlagCatcher'
SURVIVAL = 'Survival'
EPIC = 'Epic'
SPORTS = 'Sports'
HOCKEY = 'Hockey'
FOOTBALL = 'Football'
FLYING = 'Flying'
SCARY = 'Scary'
MARCHING = 'Marching'
class MusicPlayMode(Enum):
"""Influences behavior when playing music."""
REGULAR = 'regular'
TEST = 'test'
class MusicPlayer: class MusicPlayer:
@ -229,12 +262,13 @@ class InternalMusicPlayer(MusicPlayer):
# For internal music player. # For internal music player.
# FIXME: this only applies to Android currently.
def get_valid_music_file_extensions() -> List[str]: def get_valid_music_file_extensions() -> List[str]:
"""Return file extensions for types playable on this device.""" """Return file extensions for types playable on this device."""
return ['mp3', 'ogg', 'm4a', 'wav', 'flac', 'mid'] return ['mp3', 'ogg', 'm4a', 'wav', 'flac', 'mid']
class ITunesThread(threading.Thread): class MacMusicAppThread(threading.Thread):
"""Thread which wrangles iTunes/Music.app playback""" """Thread which wrangles iTunes/Music.app playback"""
def __init__(self) -> None: def __init__(self) -> None:
@ -246,16 +280,16 @@ class ITunesThread(threading.Thread):
self._orig_volume: Optional[int] = None self._orig_volume: Optional[int] = None
def run(self) -> None: def run(self) -> None:
"""Run the iTunes/Music.app thread.""" """Run the Music.app thread."""
from ba._general import Call from ba._general import Call
from ba._lang import Lstr from ba._lang import Lstr
from ba._enums import TimeType from ba._enums import TimeType
_ba.set_thread_name("BA_ITunesThread") _ba.set_thread_name("BA_MacMusicAppThread")
_ba.itunes_init() _ba.mac_music_app_init()
# It looks like launching iTunes here on 10.7/10.8 knocks us # Let's mention to the user we're launching Music.app in case
# out of fullscreen; ick. That might be a bug, but for now we # it causes any funny business (this used to background the app
# can work around it by reactivating ourself after. # sometimes, though I think that is fixed now)
def do_print() -> None: def do_print() -> None:
_ba.timer(1.0, _ba.timer(1.0,
Call(_ba.screenmessage, Lstr(resource='usingItunesText'), Call(_ba.screenmessage, Lstr(resource='usingItunesText'),
@ -265,12 +299,8 @@ class ITunesThread(threading.Thread):
_ba.pushcall(do_print, from_other_thread=True) _ba.pushcall(do_print, from_other_thread=True)
# Here we grab this to force the actual launch. # Here we grab this to force the actual launch.
# Currently (on 10.8 at least) this is causing a switch _ba.mac_music_app_get_volume()
# away from our fullscreen window. to work around this we _ba.mac_music_app_get_library_source()
# explicitly focus our main window to bring ourself back.
_ba.itunes_get_volume()
_ba.pushcall(_ba.focus_window, from_other_thread=True)
_ba.itunes_get_library_source()
done = False done = False
while not done: while not done:
self._commands_available.wait() self._commands_available.wait()
@ -307,8 +337,8 @@ class ITunesThread(threading.Thread):
if old_volume > 0.0 and volume == 0.0: if old_volume > 0.0 and volume == 0.0:
try: try:
assert self._orig_volume is not None assert self._orig_volume is not None
_ba.itunes_stop() _ba.mac_music_app_stop()
_ba.itunes_set_volume(self._orig_volume) _ba.mac_music_app_set_volume(self._orig_volume)
except Exception as exc: except Exception as exc:
print('Error stopping iTunes music:', exc) print('Error stopping iTunes music:', exc)
elif self._volume > 0: elif self._volume > 0:
@ -316,8 +346,8 @@ class ITunesThread(threading.Thread):
# If volume was zero, store pre-playing volume and start # If volume was zero, store pre-playing volume and start
# playing. # playing.
if old_volume == 0.0: if old_volume == 0.0:
self._orig_volume = _ba.itunes_get_volume() self._orig_volume = _ba.mac_music_app_get_volume()
self._update_itunes_volume() self._update_mac_music_app_volume()
if old_volume == 0.0: if old_volume == 0.0:
self._play_current_playlist() self._play_current_playlist()
@ -341,7 +371,7 @@ class ITunesThread(threading.Thread):
self, target: Callable[[List[str]], None]) -> None: self, target: Callable[[List[str]], None]) -> None:
from ba._general import Call from ba._general import Call
try: try:
playlists = _ba.itunes_get_playlists() playlists = _ba.mac_music_app_get_playlists()
playlists = [ playlists = [
p for p in playlists if p not in [ p for p in playlists if p not in [
'Music', 'Movies', 'TV Shows', 'Podcasts', 'iTunes\xa0U', 'Music', 'Movies', 'TV Shows', 'Podcasts', 'iTunes\xa0U',
@ -360,8 +390,8 @@ class ITunesThread(threading.Thread):
if self._current_playlist is not None and self._volume > 0: if self._current_playlist is not None and self._volume > 0:
try: try:
assert self._orig_volume is not None assert self._orig_volume is not None
_ba.itunes_stop() _ba.mac_music_app_stop()
_ba.itunes_set_volume(self._orig_volume) _ba.mac_music_app_set_volume(self._orig_volume)
except Exception as exc: except Exception as exc:
print('Error stopping iTunes music:', exc) print('Error stopping iTunes music:', exc)
self._current_playlist = None self._current_playlist = None
@ -371,16 +401,16 @@ class ITunesThread(threading.Thread):
if self._current_playlist is not None and self._volume > 0: if self._current_playlist is not None and self._volume > 0:
try: try:
assert self._orig_volume is not None assert self._orig_volume is not None
_ba.itunes_stop() _ba.mac_music_app_stop()
_ba.itunes_set_volume(self._orig_volume) _ba.mac_music_app_set_volume(self._orig_volume)
except Exception as exc: except Exception as exc:
print('Error stopping iTunes music:', exc) print('Error stopping iTunes music:', exc)
# Set our playlist and play it if our volume is up. # Set our playlist and play it if our volume is up.
self._current_playlist = target self._current_playlist = target
if self._volume > 0: if self._volume > 0:
self._orig_volume = (_ba.itunes_get_volume()) self._orig_volume = (_ba.mac_music_app_get_volume())
self._update_itunes_volume() self._update_mac_music_app_volume()
self._play_current_playlist() self._play_current_playlist()
def _handle_die_command(self) -> None: def _handle_die_command(self) -> None:
@ -390,8 +420,8 @@ class ITunesThread(threading.Thread):
if self._current_playlist is not None and self._volume > 0: if self._current_playlist is not None and self._volume > 0:
try: try:
assert self._orig_volume is not None assert self._orig_volume is not None
_ba.itunes_stop() _ba.mac_music_app_stop()
_ba.itunes_set_volume(self._orig_volume) _ba.mac_music_app_set_volume(self._orig_volume)
except Exception as exc: except Exception as exc:
print('Error stopping iTunes music:', exc) print('Error stopping iTunes music:', exc)
@ -400,7 +430,7 @@ class ITunesThread(threading.Thread):
from ba import _lang from ba import _lang
from ba._general import Call from ba._general import Call
assert self._current_playlist is not None assert self._current_playlist is not None
if _ba.itunes_play_playlist(self._current_playlist): if _ba.mac_music_app_play_playlist(self._current_playlist):
pass pass
else: else:
_ba.pushcall(Call( _ba.pushcall(Call(
@ -413,8 +443,9 @@ class ITunesThread(threading.Thread):
_error.print_exception( _error.print_exception(
f"error playing playlist {self._current_playlist}") f"error playing playlist {self._current_playlist}")
def _update_itunes_volume(self) -> None: def _update_mac_music_app_volume(self) -> None:
_ba.itunes_set_volume(max(0, min(100, int(100.0 * self._volume)))) _ba.mac_music_app_set_volume(
max(0, min(100, int(100.0 * self._volume))))
class MacITunesMusicPlayer(MusicPlayer): class MacITunesMusicPlayer(MusicPlayer):
@ -425,7 +456,7 @@ class MacITunesMusicPlayer(MusicPlayer):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._thread = ITunesThread() self._thread = MacMusicAppThread()
self._thread.start() self._thread.start()
def on_select_entry(self, callback: Callable[[Any], None], def on_select_entry(self, callback: Callable[[Any], None],
@ -479,7 +510,8 @@ def music_volume_changed(val: float) -> None:
app.music_player.set_volume(val) app.music_player.set_volume(val)
def set_music_play_mode(mode: str, force_restart: bool = False) -> None: def set_music_play_mode(mode: MusicPlayMode,
force_restart: bool = False) -> None:
"""Sets music play mode; used for soundtrack testing/etc.""" """Sets music play mode; used for soundtrack testing/etc."""
app = _ba.app app = _ba.app
old_mode = app.music_mode old_mode = app.music_mode
@ -490,8 +522,9 @@ def set_music_play_mode(mode: str, force_restart: bool = False) -> None:
# actually play anything until its requested. # actually play anything until its requested.
# If we're switching *out* of test mode though # If we're switching *out* of test mode though
# we want to go back to whatever the normal song was. # we want to go back to whatever the normal song was.
if mode == 'regular': if mode is MusicPlayMode.REGULAR:
do_play_music(app.music_types['regular']) mtype = app.music_types[MusicPlayMode.REGULAR]
do_play_music(None if mtype is None else mtype.value)
def supports_soundtrack_entry_type(entry_type: str) -> bool: def supports_soundtrack_entry_type(entry_type: str) -> bool:
@ -556,17 +589,11 @@ def get_soundtrack_entry_name(entry: Any) -> str:
return 'default' return 'default'
def setmusic(musictype: Optional[str], continuous: bool = False) -> None: def setmusic(musictype: Optional[MusicType], continuous: bool = False) -> None:
"""Set or stop the current music based on a string musictype. """Set or stop the current music based on a string musictype.
category: Gameplay Functions category: Gameplay Functions
Current valid values for 'musictype': 'Menu', 'Victory', 'CharSelect',
'RunAway', 'Onslaught', 'Keep Away', 'Race', 'Epic Race', 'Scores',
'GrandRomp', 'ToTheDeath', 'Chosen One', 'ForwardMarch', 'FlagCatcher',
'Survival', 'Epic', 'Sports', 'Hockey', 'Football', 'Flying', 'Scary',
'Marching'.
This function will handle loading and playing sound media as necessary, This function will handle loading and playing sound media as necessary,
and also supports custom user soundtracks on specific platforms so the and also supports custom user soundtracks on specific platforms so the
user can override particular game music with their own. user can override particular game music with their own.
@ -585,7 +612,7 @@ def setmusic(musictype: Optional[str], continuous: bool = False) -> None:
# not an actual sound node create. # not an actual sound node create.
gnode = _gameutils.sharedobj('globals') gnode = _gameutils.sharedobj('globals')
gnode.music_continuous = continuous gnode.music_continuous = continuous
gnode.music = '' if musictype is None else musictype gnode.music = '' if musictype is None else musictype.value
gnode.music_count += 1 gnode.music_count += 1
@ -595,27 +622,33 @@ def handle_app_resume() -> None:
do_play_music(None) do_play_music(None)
def do_play_music(musictype: Optional[str], def do_play_music(musictype: Union[MusicType, str, None],
continuous: bool = False, continuous: bool = False,
mode: str = 'regular', mode: MusicPlayMode = MusicPlayMode.REGULAR,
testsoundtrack: Dict = None) -> None: testsoundtrack: Dict[str, Any] = None) -> None:
"""Plays the requested music type/mode. """Plays the requested music type/mode.
For most cases setmusic() is the proper call to use, which itself calls For most cases setmusic() is the proper call to use, which itself calls
this. Certain cases, however, such as soundtrack testing, may require this. Certain cases, however, such as soundtrack testing, may require
calling this directly. calling this directly.
""" """
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements # We can be passed a MusicType or the string value of one.
if musictype is not None:
try:
musictype = MusicType(musictype)
except ValueError:
print(f"Invalid music type: '{musictype}'")
musictype = None
app = _ba.app app = _ba.app
with _ba.Context('ui'): with _ba.Context('ui'):
# If they don't want to restart music and we're already # If they don't want to restart music and we're already
# playing what's requested, we're done. # playing what's requested, we're done.
if continuous and app.music_types[mode] == musictype: if continuous and app.music_types[mode] is musictype:
return return
app.music_types[mode] = musictype app.music_types[mode] = musictype
cfg = app.config
# If the OS tells us there's currently music playing, # If the OS tells us there's currently music playing,
# all our operations default to playing nothing. # all our operations default to playing nothing.
@ -632,127 +665,104 @@ def do_play_music(musictype: Optional[str],
# entry for this music-type, and if we have one, have the music-player # entry for this music-type, and if we have one, have the music-player
# play it. If not, we'll play game music ourself. # play it. If not, we'll play game music ourself.
if musictype is not None and app.music_player_type is not None: if musictype is not None and app.music_player_type is not None:
try: if testsoundtrack is not None:
soundtrack: Dict soundtrack = testsoundtrack
if testsoundtrack is not None: else:
soundtrack = testsoundtrack soundtrack = _get_user_soundtrack()
else: entry = soundtrack.get(musictype.value)
soundtrack = cfg['Soundtracks'][cfg['Soundtrack']]
entry = soundtrack[musictype]
except Exception:
entry = None
else: else:
entry = None entry = None
# Go through music-player. # Go through music-player.
if entry is not None: if entry is not None:
_play_music_player_music(entry)
# Stop any existing internal music.
if app.music is not None:
app.music.delete()
app.music = None
# Play music-player music.
get_music_player().play(entry)
# Handle via internal music. # Handle via internal music.
else: else:
if musictype is not None: _play_internal_music(musictype)
loop = True
if musictype == 'Menu':
filename = 'menuMusic'
volume = 5.0
elif musictype == 'Victory':
filename = 'victoryMusic'
volume = 6.0
loop = False
elif musictype == 'CharSelect':
filename = 'charSelectMusic'
volume = 2.0
elif musictype == 'RunAway':
filename = 'runAwayMusic'
volume = 6.0
elif musictype == 'Onslaught':
filename = 'runAwayMusic'
volume = 6.0
elif musictype == 'Keep Away':
filename = 'runAwayMusic'
volume = 6.0
elif musictype == 'Race':
filename = 'runAwayMusic'
volume = 6.0
elif musictype == 'Epic Race':
filename = 'slowEpicMusic'
volume = 6.0
elif musictype == 'Scores':
filename = 'scoresEpicMusic'
volume = 3.0
loop = False
elif musictype == 'GrandRomp':
filename = 'grandRompMusic'
volume = 6.0
elif musictype == 'ToTheDeath':
filename = 'toTheDeathMusic'
volume = 6.0
elif musictype == 'Chosen One':
filename = 'survivalMusic'
volume = 4.0
elif musictype == 'ForwardMarch':
filename = 'forwardMarchMusic'
volume = 4.0
elif musictype == 'FlagCatcher':
filename = 'flagCatcherMusic'
volume = 6.0
elif musictype == 'Survival':
filename = 'survivalMusic'
volume = 4.0
elif musictype == 'Epic':
filename = 'slowEpicMusic'
volume = 6.0
elif musictype == 'Sports':
filename = 'sportsMusic'
volume = 4.0
elif musictype == 'Hockey':
filename = 'sportsMusic'
volume = 4.0
elif musictype == 'Football':
filename = 'sportsMusic'
volume = 4.0
elif musictype == 'Flying':
filename = 'flyingMusic'
volume = 4.0
elif musictype == 'Scary':
filename = 'scaryMusic'
volume = 4.0
elif musictype == 'Marching':
filename = 'whenJohnnyComesMarchingHomeMusic'
volume = 4.0
else:
print("Unknown music: '" + musictype + "'")
filename = 'flagCatcherMusic'
volume = 6.0
# Stop any existing music-player playback.
if app.music_player is not None:
app.music_player.stop()
# Stop any existing internal music. def _get_user_soundtrack() -> Dict[str, Any]:
if app.music: """Return current user soundtrack or empty dict otherwise."""
app.music.delete() cfg = _ba.app.config
app.music = None soundtrack: Dict[str, Any] = {}
soundtrackname = cfg.get('Soundtrack')
if soundtrackname is not None:
try:
soundtrack = cfg['Soundtracks'][soundtrackname]
except Exception as exc:
print(f"Error looking up user soundtrack: {exc}")
soundtrack = {}
return soundtrack
# Start up new internal music.
if musictype is not None:
# FIXME: Currently this won't start playing if we're paused def _play_music_player_music(entry: Any) -> None:
# since attr values don't get updated until app = _ba.app
# node updates happen. :-(
# Update: hmm I don't think that's true anymore. Should check. # Stop any existing internal music.
app.music = _ba.newnode(type='sound', if app.music is not None:
attrs={ app.music.delete()
'sound': _ba.getsound(filename), app.music = None
'positional': False,
'music': True, # Do the thing.
'volume': volume, get_music_player().play(entry)
'loop': loop
})
def _play_internal_music(musictype: Optional[MusicType]) -> None:
app = _ba.app
# Stop any existing music-player playback.
if app.music_player is not None:
app.music_player.stop()
# Stop any existing internal music.
if app.music:
app.music.delete()
app.music = None
# Start up new internal music.
if musictype is not None:
# Filenames/volume/loop for our built-in music.
musicinfos: Dict[MusicType, Tuple[str, float, bool]] = {
MusicType.MENU: ('menuMusic', 5.0, True),
MusicType.VICTORY: ('victoryMusic', 6.0, False),
MusicType.CHAR_SELECT: ('charSelectMusic', 2.0, True),
MusicType.RUN_AWAY: ('runAwayMusic', 6.0, True),
MusicType.ONSLAUGHT: ('runAwayMusic', 6.0, True),
MusicType.KEEP_AWAY: ('runAwayMusic', 6.0, True),
MusicType.RACE: ('runAwayMusic', 6.0, True),
MusicType.EPIC_RACE: ('slowEpicMusic', 6.0, True),
MusicType.SCORES: ('scoresEpicMusic', 3.0, False),
MusicType.GRAND_ROMP: ('grandRompMusic', 6.0, True),
MusicType.TO_THE_DEATH: ('toTheDeathMusic', 6.0, True),
MusicType.CHOSEN_ONE: ('survivalMusic', 4.0, True),
MusicType.FORWARD_MARCH: ('forwardMarchMusic', 4.0, True),
MusicType.FLAG_CATCHER: ('flagCatcherMusic', 6.0, True),
MusicType.SURVIVAL: ('survivalMusic', 4.0, True),
MusicType.EPIC: ('slowEpicMusic', 6.0, True),
MusicType.SPORTS: ('sportsMusic', 4.0, True),
MusicType.HOCKEY: ('sportsMusic', 4.0, True),
MusicType.FOOTBALL: ('sportsMusic', 4.0, True),
MusicType.FLYING: ('flyingMusic', 4.0, True),
MusicType.SCARY: ('scaryMusic', 4.0, True),
MusicType.MARCHING:
('whenJohnnyComesMarchingHomeMusic', 4.0, True),
}
musicinfo = musicinfos.get(musictype)
if musicinfo is None:
print(f"Unknown music: '{musictype}'")
filename = 'flagCatcherMusic'
volume = 6.0
loop = True
else:
filename, volume, loop = musicinfo
app.music = _ba.newnode(type='sound',
attrs={
'sound': _ba.getsound(filename),
'positional': False,
'music': True,
'volume': volume,
'loop': loop
})

View File

@ -340,7 +340,7 @@ class TeamSeriesVictoryScoreScreenActivity(TeamsScoreScreenActivity):
# Make sure we don't stomp on the next activity's music choice. # Make sure we don't stomp on the next activity's music choice.
if not self.is_transitioning_out(): if not self.is_transitioning_out():
ba.setmusic('Victory') ba.setmusic(ba.MusicType.VICTORY)
def _show_winner(self, team: ba.Team) -> None: def _show_winner(self, team: ba.Team) -> None:
from bastd.actor.image import Image from bastd.actor.image import Image

View File

@ -94,8 +94,8 @@ class AssaultGame(ba.TeamGameActivity):
return 'touch ${ARG1} flags', self.settings['Score to Win'] return 'touch ${ARG1} flags', self.settings['Score to Win']
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = ('Epic' if self.settings['Epic Mode'] else self._default_music = (ba.MusicType.EPIC if self.settings['Epic Mode']
'ForwardMarch') else ba.MusicType.FORWARD_MARCH)
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -135,8 +135,8 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
return 'return ${ARG1} flags', self.settings['Score to Win'] return 'return ${ARG1} flags', self.settings['Score to Win']
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = ('Epic' if self.settings['Epic Mode'] else self._default_music = (ba.MusicType.EPIC if self.settings['Epic Mode']
'FlagCatcher') else ba.MusicType.FLAG_CATCHER)
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -118,8 +118,8 @@ class ChosenOneGame(ba.TeamGameActivity):
return 'There can be only one.' return 'There can be only one.'
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = ('Epic' self._default_music = (ba.MusicType.EPIC if self.settings['Epic Mode']
if self.settings['Epic Mode'] else 'Chosen One') else ba.MusicType.CHOSEN_ONE)
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -123,8 +123,8 @@ class ConquestGame(ba.TeamGameActivity):
return 'secure all ${ARG1} flags', len(self.map.flag_points) return 'secure all ${ARG1} flags', len(self.map.flag_points)
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = ('Epic' self._default_music = (ba.MusicType.EPIC if self.settings['Epic Mode']
if self.settings['Epic Mode'] else 'GrandRomp') else ba.MusicType.GRAND_ROMP)
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -115,8 +115,8 @@ class DeathMatchGame(ba.TeamGameActivity):
return 'kill ${ARG1} enemies', self._score_to_win return 'kill ${ARG1} enemies', self._score_to_win
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = ('Epic' self._default_music = (ba.MusicType.EPIC if self.settings['Epic Mode']
if self.settings['Epic Mode'] else 'ToTheDeath') else ba.MusicType.TO_THE_DEATH)
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -97,7 +97,7 @@ class EasterEggHuntGame(ba.TeamGameActivity):
# ..we can go ahead and set our music and whatnot. # ..we can go ahead and set our music and whatnot.
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'ForwardMarch' self._default_music = ba.MusicType.FORWARD_MARCH
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -244,8 +244,8 @@ class EliminationGame(ba.TeamGameActivity):
self.session, ba.TeamsSession) else 'last one standing wins' self.session, ba.TeamsSession) else 'last one standing wins'
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = ('Epic' self._default_music = (ba.MusicType.EPIC if self.settings['Epic Mode']
if self.settings['Epic Mode'] else 'Survival') else ba.MusicType.SURVIVAL)
super().on_transition_in() super().on_transition_in()
self._start_time = ba.time() self._start_time = ba.time()

View File

@ -146,7 +146,7 @@ class FootballTeamGame(ba.TeamGameActivity):
return 'score a touchdown' return 'score a touchdown'
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'Football' self._default_music = ba.MusicType.FOOTBALL
super().on_transition_in() super().on_transition_in()
def on_begin(self) -> None: def on_begin(self) -> None:
@ -397,7 +397,7 @@ class FootballCoopGame(ba.CoopGameActivity):
self._flag: Optional[FootballFlag] = None self._flag: Optional[FootballFlag] = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'Football' self._default_music = ba.MusicType.FOOTBALL
super().on_transition_in() super().on_transition_in()
self._scoreboard = Scoreboard() self._scoreboard = Scoreboard()
self._flag_spawn_pos = self.map.get_flag_position(None) self._flag_spawn_pos = self.map.get_flag_position(None)
@ -748,7 +748,7 @@ class FootballCoopGame(ba.CoopGameActivity):
if team is self._bot_team: if team is self._bot_team:
self.continue_or_end_game() self.continue_or_end_game()
else: else:
ba.setmusic('Victory') ba.setmusic(ba.MusicType.VICTORY)
# Completion achievements. # Completion achievements.
assert self._bot_team is not None assert self._bot_team is not None

View File

@ -207,7 +207,7 @@ class HockeyGame(ba.TeamGameActivity):
return 'score ${ARG1} goals', self.settings['Score to Win'] return 'score ${ARG1} goals', self.settings['Score to Win']
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'Hockey' self._default_music = ba.MusicType.HOCKEY
super().on_transition_in() super().on_transition_in()
def on_begin(self) -> None: def on_begin(self) -> None:

View File

@ -124,7 +124,7 @@ class KeepAwayGame(ba.TeamGameActivity):
self.settings['Hold Time']) self.settings['Hold Time'])
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'Keep Away' self._default_music = ba.MusicType.KEEP_AWAY
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -133,7 +133,7 @@ class KingOfTheHillGame(ba.TeamGameActivity):
self.settings['Hold Time']) self.settings['Hold Time'])
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'Scary' self._default_music = ba.MusicType.SCARY
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -91,8 +91,8 @@ class MeteorShowerGame(ba.TeamGameActivity):
# Called when our game is transitioning in but not ready to start; # Called when our game is transitioning in but not ready to start;
# ..we can go ahead and set our music and whatnot. # ..we can go ahead and set our music and whatnot.
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = ('Epic' self._default_music = (ba.MusicType.EPIC if self.settings['Epic Mode']
if self.settings['Epic Mode'] else 'Survival') else ba.MusicType.SURVIVAL)
super().on_transition_in() super().on_transition_in()
# Called when our game actually starts. # Called when our game actually starts.

View File

@ -84,7 +84,7 @@ class NinjaFightGame(ba.TeamGameActivity):
# Called when our game is transitioning in but not ready to begin; # Called when our game is transitioning in but not ready to begin;
# we can go ahead and start creating stuff, playing music, etc. # we can go ahead and start creating stuff, playing music, etc.
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'ToTheDeath' self._default_music = ba.MusicType.TO_THE_DEATH
super().on_transition_in() super().on_transition_in()
# Called when our game actually begins. # Called when our game actually begins.

View File

@ -180,7 +180,7 @@ class OnslaughtGame(ba.CoopGameActivity):
'color': (0.3, 0.8, 0.3, 1.0), 'color': (0.3, 0.8, 0.3, 1.0),
'text': '' 'text': ''
})) }))
ba.setmusic('Onslaught') ba.setmusic(ba.MusicType.ONSLAUGHT)
self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),
score_split=0.5) score_split=0.5)
@ -769,7 +769,7 @@ class OnslaughtGame(ba.CoopGameActivity):
base_delay += 0.85 base_delay += 0.85
ba.playsound(self._winsound) ba.playsound(self._winsound)
ba.cameraflash() ba.cameraflash()
ba.setmusic('Victory') ba.setmusic(ba.MusicType.VICTORY)
self._game_over = True self._game_over = True
# Can't just pass delay to do_end because our extra bonuses # Can't just pass delay to do_end because our extra bonuses

View File

@ -172,8 +172,9 @@ class RaceGame(ba.TeamGameActivity):
return 'run 1 lap' return 'run 1 lap'
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = ('Epic Race' self._default_music = (ba.MusicType.EPIC_RACE
if self.settings['Epic Mode'] else 'Race') if self.settings['Epic Mode'] else
ba.MusicType.RACE)
super().on_transition_in() super().on_transition_in()
pts = self.map.get_def_points('race_point') pts = self.map.get_def_points('race_point')

View File

@ -130,7 +130,7 @@ class RunaroundGame(ba.CoopGameActivity):
self._wave_update_timer: Optional[ba.Timer] = None self._wave_update_timer: Optional[ba.Timer] = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'Marching' self._default_music = ba.MusicType.MARCHING
super().on_transition_in() super().on_transition_in()
self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),
score_split=0.5) score_split=0.5)
@ -630,7 +630,7 @@ class RunaroundGame(ba.CoopGameActivity):
base_delay += 0.85 base_delay += 0.85
ba.playsound(self._winsound) ba.playsound(self._winsound)
ba.cameraflash() ba.cameraflash()
ba.setmusic('Victory') ba.setmusic(ba.MusicType.VICTORY)
self._game_over = True self._game_over = True
ba.timer(base_delay, ba.Call(self.do_end, 'victory')) ba.timer(base_delay, ba.Call(self.do_end, 'victory'))
return return

View File

@ -81,7 +81,7 @@ class TargetPracticeGame(ba.TeamGameActivity):
self._countdown: Optional[OnScreenCountdown] = None self._countdown: Optional[OnScreenCountdown] = None
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
self._default_music = 'ForwardMarch' self._default_music = ba.MusicType.FORWARD_MARCH
super().on_transition_in() super().on_transition_in()
def on_team_join(self, team: ba.Team) -> None: def on_team_join(self, team: ba.Team) -> None:

View File

@ -98,7 +98,7 @@ class TheLastStandGame(ba.CoopGameActivity):
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
from bastd.actor.scoreboard import Scoreboard from bastd.actor.scoreboard import Scoreboard
self._default_music = 'Epic' self._default_music = ba.MusicType.EPIC
super().on_transition_in() super().on_transition_in()
ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound)) ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound))
self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),

View File

@ -822,7 +822,7 @@ class MainMenuActivity(ba.Activity):
with ba.Context(self): with ba.Context(self):
_preload1() _preload1()
ba.timer(0.5, lambda: ba.setmusic('Menu')) ba.timer(0.5, lambda: ba.setmusic(ba.MusicType.MENU))
def _preload1() -> None: def _preload1() -> None:

View File

@ -1216,8 +1216,8 @@ class HappyThoughts(ba.Map):
return data return data
@classmethod @classmethod
def get_music_type(cls) -> str: def get_music_type(cls) -> ba.MusicType:
return 'Flying' return ba.MusicType.FLYING
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(vr_overlay_offset=(0, -3.7, 2.5)) super().__init__(vr_overlay_offset=(0, -3.7, 2.5))

View File

@ -234,7 +234,7 @@ class TutorialActivity(ba.Activity):
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
super().on_transition_in() super().on_transition_in()
ba.setmusic('CharSelect', continuous=True) ba.setmusic(ba.MusicType.CHAR_SELECT, continuous=True)
self._map = self._map_type() self._map = self._map_type()
def on_begin(self) -> None: def on_begin(self) -> None:

View File

@ -188,7 +188,7 @@ class SoundtrackBrowserWindow(ba.OldWindow):
ba.widget(edit=btn, ba.widget(edit=btn,
left_widget=_ba.get_special_widget('back_button')) left_widget=_ba.get_special_widget('back_button'))
# keep our lock images up to date/etc. # Keep our lock images up to date/etc.
self._update_timer = ba.Timer(1.0, self._update_timer = ba.Timer(1.0,
ba.WeakCall(self._update), ba.WeakCall(self._update),
timetype=ba.TimeType.REAL, timetype=ba.TimeType.REAL,
@ -282,10 +282,10 @@ class SoundtrackBrowserWindow(ba.OldWindow):
else: else:
sdtk = cfg['Soundtracks'][self._selected_soundtrack] sdtk = cfg['Soundtracks'][self._selected_soundtrack]
# find a valid dup name that doesn't exist # Find a valid dup name that doesn't exist.
test_index = 1 test_index = 1
copy_text = ba.Lstr(resource='copyOfText').evaluate() copy_text = ba.Lstr(resource='copyOfText').evaluate()
# get just 'Copy' or whatnot # Get just 'Copy' or whatnot.
copy_word = copy_text.replace('${NAME}', '').strip() copy_word = copy_text.replace('${NAME}', '').strip()
base_name = self._get_soundtrack_display_name( base_name = self._get_soundtrack_display_name(
self._selected_soundtrack).evaluate() self._selected_soundtrack).evaluate()
@ -294,7 +294,7 @@ class SoundtrackBrowserWindow(ba.OldWindow):
assert isinstance(base_name, bytes) assert isinstance(base_name, bytes)
base_name = base_name.decode('utf-8') base_name = base_name.decode('utf-8')
# if it looks like a copy, strip digits and spaces off the end # If it looks like a copy, strip digits and spaces off the end.
if copy_word in base_name: if copy_word in base_name:
while base_name[-1].isdigit() or base_name[-1] == ' ': while base_name[-1].isdigit() or base_name[-1] == ' ':
base_name = base_name[:-1] base_name = base_name[:-1]
@ -320,14 +320,14 @@ class SoundtrackBrowserWindow(ba.OldWindow):
cfg = ba.app.config cfg = ba.app.config
current_soundtrack = cfg.setdefault('Soundtrack', '__default__') current_soundtrack = cfg.setdefault('Soundtrack', '__default__')
# if it varies from current, commit and play # If it varies from current, commit and play.
if current_soundtrack != name and self._allow_changing_soundtracks: if current_soundtrack != name and self._allow_changing_soundtracks:
ba.playsound(ba.getsound('gunCocking')) ba.playsound(ba.getsound('gunCocking'))
cfg['Soundtrack'] = self._selected_soundtrack cfg['Soundtrack'] = self._selected_soundtrack
cfg.commit() cfg.commit()
# just play whats already playing.. this'll grab it from the # Just play whats already playing.. this'll grab it from the
# new soundtrack # new soundtrack.
do_play_music(ba.app.music_types['regular']) do_play_music(ba.app.music_types[ba.MusicPlayMode.REGULAR])
def _back(self) -> None: def _back(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import

View File

@ -280,7 +280,7 @@ class SoundtrackEditWindow(ba.OldWindow):
size=(50, 32), size=(50, 32),
label=ba.Lstr(resource=self._r + '.testText'), label=ba.Lstr(resource=self._r + '.testText'),
text_scale=0.6, text_scale=0.6,
on_activate_call=ba.Call(self._test, song_type), on_activate_call=ba.Call(self._test, ba.MusicType(song_type)),
up_widget=prev_test_button up_widget=prev_test_button
if prev_test_button is not None else self._text_field) if prev_test_button is not None else self._text_field)
if prev_test_button is not None: if prev_test_button is not None:
@ -330,7 +330,7 @@ class SoundtrackEditWindow(ba.OldWindow):
ba.Call(self._restore_editor, state, song_type), entry, ba.Call(self._restore_editor, state, song_type), entry,
selection_target_name).get_root_widget()) selection_target_name).get_root_widget())
def _test(self, song_type: str) -> None: def _test(self, song_type: ba.MusicType) -> None:
from ba.internal import set_music_play_mode, do_play_music from ba.internal import set_music_play_mode, do_play_music
# Warn if volume is zero. # Warn if volume is zero.
@ -339,8 +339,10 @@ class SoundtrackEditWindow(ba.OldWindow):
ba.screenmessage(ba.Lstr(resource=self._r + ba.screenmessage(ba.Lstr(resource=self._r +
'.musicVolumeZeroWarning'), '.musicVolumeZeroWarning'),
color=(1, 0.5, 0)) color=(1, 0.5, 0))
set_music_play_mode('test') set_music_play_mode(ba.MusicPlayMode.TEST)
do_play_music(song_type, mode='test', testsoundtrack=self._soundtrack) do_play_music(song_type,
mode=ba.MusicPlayMode.TEST,
testsoundtrack=self._soundtrack)
def _get_entry_button_display_name(self, def _get_entry_button_display_name(self,
entry: Any) -> Union[str, ba.Lstr]: entry: Any) -> Union[str, ba.Lstr]:
@ -369,7 +371,7 @@ class SoundtrackEditWindow(ba.OldWindow):
from ba.internal import set_music_play_mode from ba.internal import set_music_play_mode
from bastd.ui.soundtrack import browser as stb from bastd.ui.soundtrack import browser as stb
# Resets music back to normal. # Resets music back to normal.
set_music_play_mode('regular') set_music_play_mode(ba.MusicPlayMode.REGULAR)
ba.containerwidget(edit=self._root_widget, transition='out_right') ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.main_menu_window = (stb.SoundtrackBrowserWindow( ba.app.main_menu_window = (stb.SoundtrackBrowserWindow(
transition='in_left').get_root_widget()) transition='in_left').get_root_widget())
@ -411,7 +413,7 @@ class SoundtrackEditWindow(ba.OldWindow):
ba.containerwidget(edit=self._root_widget, transition='out_right') ba.containerwidget(edit=self._root_widget, transition='out_right')
# Resets music back to normal. # Resets music back to normal.
set_music_play_mode('regular', force_restart=True) set_music_play_mode(ba.MusicPlayMode.REGULAR, force_restart=True)
ba.app.main_menu_window = (stb.SoundtrackBrowserWindow( ba.app.main_menu_window = (stb.SoundtrackBrowserWindow(
transition='in_left').get_root_widget()) transition='in_left').get_root_widget())

View File

@ -51,11 +51,12 @@ class SoundtrackEntryTypeSelectWindow(ba.OldWindow):
spacing = 80 spacing = 80
do_default = True do_default = True
do_itunes_playlist = supports_soundtrack_entry_type('iTunesPlaylist') do_mac_music_app_playlist = supports_soundtrack_entry_type(
'iTunesPlaylist')
do_music_file = supports_soundtrack_entry_type('musicFile') do_music_file = supports_soundtrack_entry_type('musicFile')
do_music_folder = supports_soundtrack_entry_type('musicFolder') do_music_folder = supports_soundtrack_entry_type('musicFolder')
if do_itunes_playlist: if do_mac_music_app_playlist:
self._height += spacing self._height += spacing
if do_music_file: if do_music_file:
self._height += spacing self._height += spacing
@ -108,13 +109,13 @@ class SoundtrackEntryTypeSelectWindow(ba.OldWindow):
ba.containerwidget(edit=self._root_widget, selected_child=btn) ba.containerwidget(edit=self._root_widget, selected_child=btn)
v -= spacing v -= spacing
if do_itunes_playlist: if do_mac_music_app_playlist:
btn = ba.buttonwidget( btn = ba.buttonwidget(
parent=self._root_widget, parent=self._root_widget,
size=(self._width - 100, 60), size=(self._width - 100, 60),
position=(50, v), position=(50, v),
label=ba.Lstr(resource=self._r + '.useITunesPlaylistText'), label=ba.Lstr(resource=self._r + '.useITunesPlaylistText'),
on_activate_call=self._on_itunes_playlist_press, on_activate_call=self._on_mac_music_app_playlist_press,
icon=None) icon=None)
if current_entry_type == 'iTunesPlaylist': if current_entry_type == 'iTunesPlaylist':
ba.containerwidget(edit=self._root_widget, selected_child=btn) ba.containerwidget(edit=self._root_widget, selected_child=btn)
@ -145,7 +146,7 @@ class SoundtrackEntryTypeSelectWindow(ba.OldWindow):
ba.containerwidget(edit=self._root_widget, selected_child=btn) ba.containerwidget(edit=self._root_widget, selected_child=btn)
v -= spacing v -= spacing
def _on_itunes_playlist_press(self) -> None: def _on_mac_music_app_playlist_press(self) -> None:
from ba.internal import (get_soundtrack_entry_type, from ba.internal import (get_soundtrack_entry_type,
get_soundtrack_entry_name) get_soundtrack_entry_name)
from bastd.ui.soundtrack import itunes from bastd.ui.soundtrack import itunes

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND --> <!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-03-31 for Ballistica version 1.5.0 build 20001</em></h4> <h4><em>last updated on 2020-04-01 for Ballistica version 1.5.0 build 20001</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module, <p>This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p> which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr> <hr>
@ -174,7 +174,7 @@
<li><a href="#class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></li> <li><a href="#class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></li>
</ul> </ul>
</ul> </ul>
<h4><a name="class_category_Misc">Misc</a></h4> <h4><a name="class_category_Misc_Classes">Misc Classes</a></h4>
<ul> <ul>
<li><a href="#class_ba_Achievement">ba.Achievement</a></li> <li><a href="#class_ba_Achievement">ba.Achievement</a></li>
<li><a href="#class_ba_AppDelegate">ba.AppDelegate</a></li> <li><a href="#class_ba_AppDelegate">ba.AppDelegate</a></li>
@ -184,10 +184,12 @@
<li><a href="#class_ba_DependencySet">ba.DependencySet</a></li> <li><a href="#class_ba_DependencySet">ba.DependencySet</a></li>
<li><a href="#class_ba_Lobby">ba.Lobby</a></li> <li><a href="#class_ba_Lobby">ba.Lobby</a></li>
<li><a href="#class_ba_MusicPlayer">ba.MusicPlayer</a></li> <li><a href="#class_ba_MusicPlayer">ba.MusicPlayer</a></li>
<li><a href="#class_ba_MusicPlayMode">ba.MusicPlayMode</a></li>
<li><a href="#class_ba_MusicType">ba.MusicType</a></li>
<li><a href="#class_ba_OldWindow">ba.OldWindow</a></li> <li><a href="#class_ba_OldWindow">ba.OldWindow</a></li>
<li><a href="#class_ba_UIController">ba.UIController</a></li> <li><a href="#class_ba_UIController">ba.UIController</a></li>
</ul> </ul>
<h4><a name="function_category_Misc">Misc</a></h4> <h4><a name="function_category_Misc_Functions">Misc Functions</a></h4>
<ul> <ul>
<li><a href="#function_ba_show_damage_count">ba.show_damage_count()</a></li> <li><a href="#function_ba_show_damage_count">ba.show_damage_count()</a></li>
</ul> </ul>
@ -2806,7 +2808,7 @@ as far from these players as possible.</p>
</dd> </dd>
<dt><h4><a name="method_ba_Map__get_music_type">get_music_type()</a></dt></h4><dd> <dt><h4><a name="method_ba_Map__get_music_type">get_music_type()</a></dt></h4><dd>
<h5><span><em>&lt;class method&gt;</span></em></h5> <h5><span><em>&lt;class method&gt;</span></em></h5>
<p><span>get_music_type() -&gt; Optional[str] </span></p> <p><span>get_music_type() -&gt; Optional[<a href="#class_ba_MusicType">ba.MusicType</a>] </span></p>
<p>Return a music-type string that should be played on this map.</p> <p>Return a music-type string that should be played on this map.</p>
@ -3146,6 +3148,46 @@ signify that the default soundtrack should be used..</p>
</dd> </dd>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_MusicPlayMode">ba.MusicPlayMode</a></strong></h3>
<p>inherits from: enum.Enum</p>
<p>Influences behavior when playing music.</p>
<h3>Values:</h3>
<ul>
<li>REGULAR</li>
<li>TEST</li>
</ul>
<hr>
<h2><strong><a name="class_ba_MusicType">ba.MusicType</a></strong></h3>
<p>inherits from: enum.Enum</p>
<p>Types of music available to play in-game.</p>
<h3>Values:</h3>
<ul>
<li>MENU</li>
<li>VICTORY</li>
<li>CHAR_SELECT</li>
<li>RUN_AWAY</li>
<li>ONSLAUGHT</li>
<li>KEEP_AWAY</li>
<li>RACE</li>
<li>EPIC_RACE</li>
<li>SCORES</li>
<li>GRAND_ROMP</li>
<li>TO_THE_DEATH</li>
<li>CHOSEN_ONE</li>
<li>FORWARD_MARCH</li>
<li>FLAG_CATCHER</li>
<li>SURVIVAL</li>
<li>EPIC</li>
<li>SPORTS</li>
<li>HOCKEY</li>
<li>FOOTBALL</li>
<li>FLYING</li>
<li>SCARY</li>
<li>MARCHING</li>
</ul>
<hr>
<h2><strong><a name="class_ba_Node">ba.Node</a></strong></h3> <h2><strong><a name="class_ba_Node">ba.Node</a></strong></h3>
<p><em>&lt;top level class&gt;</em> <p><em>&lt;top level class&gt;</em>
</p> </p>
@ -5627,18 +5669,12 @@ are applied to the Widget.</p>
<hr> <hr>
<h2><strong><a name="function_ba_setmusic">ba.setmusic()</a></strong></h3> <h2><strong><a name="function_ba_setmusic">ba.setmusic()</a></strong></h3>
<p><span>setmusic(musictype: Optional[str], continuous: bool = False) -&gt; None</span></p> <p><span>setmusic(musictype: Optional[MusicType], continuous: bool = False) -&gt; None</span></p>
<p>Set or stop the current music based on a string musictype.</p> <p>Set or stop the current music based on a string musictype.</p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p> <p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>
<p>Current valid values for 'musictype': 'Menu', 'Victory', 'CharSelect',
'RunAway', 'Onslaught', 'Keep Away', 'Race', 'Epic Race', 'Scores',
'GrandRomp', 'ToTheDeath', 'Chosen One', 'ForwardMarch', 'FlagCatcher',
'Survival', 'Epic', 'Sports', 'Hockey', 'Football', 'Flying', 'Scary',
'Marching'.</p>
<p>This function will handle loading and playing sound media as necessary, <p>This function will handle loading and playing sound media as necessary,
and also supports custom user soundtracks on specific platforms so the and also supports custom user soundtracks on specific platforms so the
user can override particular game music with their own.</p> user can override particular game music with their own.</p>

View File

@ -105,6 +105,9 @@ class App:
self._check_sync_states() self._check_sync_states()
self._find_sources_and_headers('src/ballistica') self._find_sources_and_headers('src/ballistica')
# FIXME: It might make more sense to have some of these checks
# run via 'make check' rather than here through 'make update'.
self._check_source_files() self._check_source_files()
self._check_headers() self._check_headers()