mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-11-07 17:36:08 +00:00
Delete dist directory
This commit is contained in:
parent
b2bfb6afd8
commit
9e59089810
1776 changed files with 0 additions and 548278 deletions
527
dist/ba_data/python/ba/_music.py
vendored
527
dist/ba_data/python/ba/_music.py
vendored
|
|
@ -1,527 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Music related functionality."""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
import _ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Any
|
||||
import ba
|
||||
|
||||
|
||||
class MusicType(Enum):
|
||||
"""Types of music available to play in-game.
|
||||
|
||||
Category: **Enums**
|
||||
|
||||
These do not correspond to specific pieces of music, but rather to
|
||||
'situations'. The actual music played for each type can be overridden
|
||||
by the game or by the user.
|
||||
"""
|
||||
|
||||
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.
|
||||
|
||||
Category: **Enums**
|
||||
"""
|
||||
|
||||
REGULAR = 'regular'
|
||||
TEST = 'test'
|
||||
|
||||
|
||||
@dataclass
|
||||
class AssetSoundtrackEntry:
|
||||
"""A music entry using an internal asset.
|
||||
|
||||
Category: **App Classes**
|
||||
"""
|
||||
|
||||
assetname: str
|
||||
volume: float = 1.0
|
||||
loop: bool = True
|
||||
|
||||
|
||||
# What gets played by default for our different music types:
|
||||
ASSET_SOUNDTRACK_ENTRIES: dict[MusicType, AssetSoundtrackEntry] = {
|
||||
MusicType.MENU: AssetSoundtrackEntry('menuMusic'),
|
||||
MusicType.VICTORY: AssetSoundtrackEntry(
|
||||
'victoryMusic', volume=1.2, loop=False
|
||||
),
|
||||
MusicType.CHAR_SELECT: AssetSoundtrackEntry('charSelectMusic', volume=0.4),
|
||||
MusicType.RUN_AWAY: AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.ONSLAUGHT: AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.KEEP_AWAY: AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.RACE: AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.EPIC_RACE: AssetSoundtrackEntry('slowEpicMusic', volume=1.2),
|
||||
MusicType.SCORES: AssetSoundtrackEntry(
|
||||
'scoresEpicMusic', volume=0.6, loop=False
|
||||
),
|
||||
MusicType.GRAND_ROMP: AssetSoundtrackEntry('grandRompMusic', volume=1.2),
|
||||
MusicType.TO_THE_DEATH: AssetSoundtrackEntry('toTheDeathMusic', volume=1.2),
|
||||
MusicType.CHOSEN_ONE: AssetSoundtrackEntry('survivalMusic', volume=0.8),
|
||||
MusicType.FORWARD_MARCH: AssetSoundtrackEntry(
|
||||
'forwardMarchMusic', volume=0.8
|
||||
),
|
||||
MusicType.FLAG_CATCHER: AssetSoundtrackEntry(
|
||||
'flagCatcherMusic', volume=1.2
|
||||
),
|
||||
MusicType.SURVIVAL: AssetSoundtrackEntry('survivalMusic', volume=0.8),
|
||||
MusicType.EPIC: AssetSoundtrackEntry('slowEpicMusic', volume=1.2),
|
||||
MusicType.SPORTS: AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.HOCKEY: AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.FOOTBALL: AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.FLYING: AssetSoundtrackEntry('flyingMusic', volume=0.8),
|
||||
MusicType.SCARY: AssetSoundtrackEntry('scaryMusic', volume=0.8),
|
||||
MusicType.MARCHING: AssetSoundtrackEntry(
|
||||
'whenJohnnyComesMarchingHomeMusic', volume=0.8
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class MusicSubsystem:
|
||||
"""Subsystem for music playback in the app.
|
||||
|
||||
Category: **App Classes**
|
||||
|
||||
Access the single shared instance of this class at 'ba.app.music'.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
self._music_node: _ba.Node | None = None
|
||||
self._music_mode: MusicPlayMode = MusicPlayMode.REGULAR
|
||||
self._music_player: MusicPlayer | None = None
|
||||
self._music_player_type: type[MusicPlayer] | None = None
|
||||
self.music_types: dict[MusicPlayMode, MusicType | None] = {
|
||||
MusicPlayMode.REGULAR: None,
|
||||
MusicPlayMode.TEST: None,
|
||||
}
|
||||
|
||||
# Set up custom music players for platforms that support them.
|
||||
# FIXME: should generalize this to support arbitrary players per
|
||||
# platform (which can be discovered via ba_meta).
|
||||
# Our standard asset playback should probably just be one of them
|
||||
# instead of a special case.
|
||||
if self.supports_soundtrack_entry_type('musicFile'):
|
||||
from ba.osmusic import OSMusicPlayer
|
||||
|
||||
self._music_player_type = OSMusicPlayer
|
||||
elif self.supports_soundtrack_entry_type('iTunesPlaylist'):
|
||||
from ba.macmusicapp import MacMusicAppMusicPlayer
|
||||
|
||||
self._music_player_type = MacMusicAppMusicPlayer
|
||||
|
||||
def on_app_launch(self) -> None:
|
||||
"""Should be called by app on_app_launch()."""
|
||||
|
||||
# If we're using a non-default playlist, lets go ahead and get our
|
||||
# music-player going since it may hitch (better while we're faded
|
||||
# out than later).
|
||||
try:
|
||||
cfg = _ba.app.config
|
||||
if 'Soundtrack' in cfg and cfg['Soundtrack'] not in [
|
||||
'__default__',
|
||||
'Default Soundtrack',
|
||||
]:
|
||||
self.get_music_player()
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('error prepping music-player')
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
"""Should be called when the app is shutting down."""
|
||||
if self._music_player is not None:
|
||||
self._music_player.shutdown()
|
||||
|
||||
def have_music_player(self) -> bool:
|
||||
"""Returns whether a music player is present."""
|
||||
return self._music_player_type is not None
|
||||
|
||||
def get_music_player(self) -> MusicPlayer:
|
||||
"""Returns the system music player, instantiating if necessary."""
|
||||
if self._music_player is None:
|
||||
if self._music_player_type is None:
|
||||
raise TypeError('no music player type set')
|
||||
self._music_player = self._music_player_type()
|
||||
return self._music_player
|
||||
|
||||
def music_volume_changed(self, val: float) -> None:
|
||||
"""Should be called when changing the music volume."""
|
||||
if self._music_player is not None:
|
||||
self._music_player.set_volume(val)
|
||||
|
||||
def set_music_play_mode(
|
||||
self, mode: MusicPlayMode, force_restart: bool = False
|
||||
) -> None:
|
||||
"""Sets music play mode; used for soundtrack testing/etc."""
|
||||
old_mode = self._music_mode
|
||||
self._music_mode = mode
|
||||
if old_mode != self._music_mode or force_restart:
|
||||
|
||||
# If we're switching into test mode we don't
|
||||
# actually play anything until its requested.
|
||||
# If we're switching *out* of test mode though
|
||||
# we want to go back to whatever the normal song was.
|
||||
if mode is MusicPlayMode.REGULAR:
|
||||
mtype = self.music_types[MusicPlayMode.REGULAR]
|
||||
self.do_play_music(None if mtype is None else mtype.value)
|
||||
|
||||
def supports_soundtrack_entry_type(self, entry_type: str) -> bool:
|
||||
"""Return whether provided soundtrack entry type is supported here."""
|
||||
uas = _ba.env()['user_agent_string']
|
||||
assert isinstance(uas, str)
|
||||
|
||||
# FIXME: Generalize this.
|
||||
if entry_type == 'iTunesPlaylist':
|
||||
return 'Mac' in uas
|
||||
if entry_type in ('musicFile', 'musicFolder'):
|
||||
return (
|
||||
'android' in uas
|
||||
and _ba.android_get_external_files_dir() is not None
|
||||
)
|
||||
if entry_type == 'default':
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_soundtrack_entry_type(self, entry: Any) -> str:
|
||||
"""Given a soundtrack entry, returns its type, taking into
|
||||
account what is supported locally."""
|
||||
try:
|
||||
if entry is None:
|
||||
entry_type = 'default'
|
||||
|
||||
# Simple string denotes iTunesPlaylist (legacy format).
|
||||
elif isinstance(entry, str):
|
||||
entry_type = 'iTunesPlaylist'
|
||||
|
||||
# For other entries we expect type and name strings in a dict.
|
||||
elif (
|
||||
isinstance(entry, dict)
|
||||
and 'type' in entry
|
||||
and isinstance(entry['type'], str)
|
||||
and 'name' in entry
|
||||
and isinstance(entry['name'], str)
|
||||
):
|
||||
entry_type = entry['type']
|
||||
else:
|
||||
raise TypeError(
|
||||
'invalid soundtrack entry: '
|
||||
+ str(entry)
|
||||
+ ' (type '
|
||||
+ str(type(entry))
|
||||
+ ')'
|
||||
)
|
||||
if self.supports_soundtrack_entry_type(entry_type):
|
||||
return entry_type
|
||||
raise ValueError('invalid soundtrack entry:' + str(entry))
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception()
|
||||
return 'default'
|
||||
|
||||
def get_soundtrack_entry_name(self, entry: Any) -> str:
|
||||
"""Given a soundtrack entry, returns its name."""
|
||||
try:
|
||||
if entry is None:
|
||||
raise TypeError('entry is None')
|
||||
|
||||
# Simple string denotes an iTunesPlaylist name (legacy entry).
|
||||
if isinstance(entry, str):
|
||||
return entry
|
||||
|
||||
# For other entries we expect type and name strings in a dict.
|
||||
if (
|
||||
isinstance(entry, dict)
|
||||
and 'type' in entry
|
||||
and isinstance(entry['type'], str)
|
||||
and 'name' in entry
|
||||
and isinstance(entry['name'], str)
|
||||
):
|
||||
return entry['name']
|
||||
raise ValueError('invalid soundtrack entry:' + str(entry))
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception()
|
||||
return 'default'
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
"""Should be run when the app resumes from a suspended state."""
|
||||
if _ba.is_os_playing_music():
|
||||
self.do_play_music(None)
|
||||
|
||||
def do_play_music(
|
||||
self,
|
||||
musictype: MusicType | str | None,
|
||||
continuous: bool = False,
|
||||
mode: MusicPlayMode = MusicPlayMode.REGULAR,
|
||||
testsoundtrack: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Plays the requested music type/mode.
|
||||
|
||||
For most cases, setmusic() is the proper call to use, which itself
|
||||
calls this. Certain cases, however, such as soundtrack testing, may
|
||||
require calling this directly.
|
||||
"""
|
||||
|
||||
# We can be passed a MusicType or the string value corresponding
|
||||
# to one.
|
||||
if musictype is not None:
|
||||
try:
|
||||
musictype = MusicType(musictype)
|
||||
except ValueError:
|
||||
print(f"Invalid music type: '{musictype}'")
|
||||
musictype = None
|
||||
|
||||
with _ba.Context('ui'):
|
||||
|
||||
# If they don't want to restart music and we're already
|
||||
# playing what's requested, we're done.
|
||||
if continuous and self.music_types[mode] is musictype:
|
||||
return
|
||||
self.music_types[mode] = musictype
|
||||
|
||||
# If the OS tells us there's currently music playing,
|
||||
# all our operations default to playing nothing.
|
||||
if _ba.is_os_playing_music():
|
||||
musictype = None
|
||||
|
||||
# If we're not in the mode this music is being set for,
|
||||
# don't actually change what's playing.
|
||||
if mode != self._music_mode:
|
||||
return
|
||||
|
||||
# Some platforms have a special music-player for things like iTunes
|
||||
# soundtracks, mp3s, etc. if this is the case, attempt to grab an
|
||||
# entry for this music-type, and if we have one, have the
|
||||
# music-player play it. If not, we'll play game music ourself.
|
||||
if musictype is not None and self._music_player_type is not None:
|
||||
if testsoundtrack is not None:
|
||||
soundtrack = testsoundtrack
|
||||
else:
|
||||
soundtrack = self._get_user_soundtrack()
|
||||
entry = soundtrack.get(musictype.value)
|
||||
else:
|
||||
entry = None
|
||||
|
||||
# Go through music-player.
|
||||
if entry is not None:
|
||||
self._play_music_player_music(entry)
|
||||
|
||||
# Handle via internal music.
|
||||
else:
|
||||
self._play_internal_music(musictype)
|
||||
|
||||
def _get_user_soundtrack(self) -> dict[str, Any]:
|
||||
"""Return current user soundtrack or empty dict otherwise."""
|
||||
cfg = _ba.app.config
|
||||
soundtrack: dict[str, Any] = {}
|
||||
soundtrackname = cfg.get('Soundtrack')
|
||||
if soundtrackname is not None and soundtrackname != '__default__':
|
||||
try:
|
||||
soundtrack = cfg.get('Soundtracks', {})[soundtrackname]
|
||||
except Exception as exc:
|
||||
print(f'Error looking up user soundtrack: {exc}')
|
||||
soundtrack = {}
|
||||
return soundtrack
|
||||
|
||||
def _play_music_player_music(self, entry: Any) -> None:
|
||||
|
||||
# Stop any existing internal music.
|
||||
if self._music_node is not None:
|
||||
self._music_node.delete()
|
||||
self._music_node = None
|
||||
|
||||
# Do the thing.
|
||||
self.get_music_player().play(entry)
|
||||
|
||||
def _play_internal_music(self, musictype: MusicType | None) -> None:
|
||||
|
||||
# Stop any existing music-player playback.
|
||||
if self._music_player is not None:
|
||||
self._music_player.stop()
|
||||
|
||||
# Stop any existing internal music.
|
||||
if self._music_node:
|
||||
self._music_node.delete()
|
||||
self._music_node = None
|
||||
|
||||
# Start up new internal music.
|
||||
if musictype is not None:
|
||||
|
||||
entry = ASSET_SOUNDTRACK_ENTRIES.get(musictype)
|
||||
if entry is None:
|
||||
print(f"Unknown music: '{musictype}'")
|
||||
entry = ASSET_SOUNDTRACK_ENTRIES[MusicType.FLAG_CATCHER]
|
||||
|
||||
self._music_node = _ba.newnode(
|
||||
type='sound',
|
||||
attrs={
|
||||
'sound': _ba.getsound(entry.assetname),
|
||||
'positional': False,
|
||||
'music': True,
|
||||
'volume': entry.volume * 5.0,
|
||||
'loop': entry.loop,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class MusicPlayer:
|
||||
"""Wrangles soundtrack music playback.
|
||||
|
||||
Category: **App Classes**
|
||||
|
||||
Music can be played either through the game itself
|
||||
or via a platform-specific external player.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._have_set_initial_volume = False
|
||||
self._entry_to_play: Any = None
|
||||
self._volume = 1.0
|
||||
self._actually_playing = False
|
||||
|
||||
def select_entry(
|
||||
self,
|
||||
callback: Callable[[Any], None],
|
||||
current_entry: Any,
|
||||
selection_target_name: str,
|
||||
) -> Any:
|
||||
"""Summons a UI to select a new soundtrack entry."""
|
||||
return self.on_select_entry(
|
||||
callback, current_entry, selection_target_name
|
||||
)
|
||||
|
||||
def set_volume(self, volume: float) -> None:
|
||||
"""Set player volume (value should be between 0 and 1)."""
|
||||
self._volume = volume
|
||||
self.on_set_volume(volume)
|
||||
self._update_play_state()
|
||||
|
||||
def play(self, entry: Any) -> None:
|
||||
"""Play provided entry."""
|
||||
if not self._have_set_initial_volume:
|
||||
self._volume = _ba.app.config.resolve('Music Volume')
|
||||
self.on_set_volume(self._volume)
|
||||
self._have_set_initial_volume = True
|
||||
self._entry_to_play = copy.deepcopy(entry)
|
||||
|
||||
# If we're currently *actually* playing something,
|
||||
# switch to the new thing.
|
||||
# Otherwise update state which will start us playing *only*
|
||||
# if proper (volume > 0, etc).
|
||||
if self._actually_playing:
|
||||
self.on_play(self._entry_to_play)
|
||||
else:
|
||||
self._update_play_state()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop any playback that is occurring."""
|
||||
self._entry_to_play = None
|
||||
self._update_play_state()
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""Shutdown music playback completely."""
|
||||
self.on_app_shutdown()
|
||||
|
||||
def on_select_entry(
|
||||
self,
|
||||
callback: Callable[[Any], None],
|
||||
current_entry: Any,
|
||||
selection_target_name: str,
|
||||
) -> Any:
|
||||
"""Present a GUI to select an entry.
|
||||
|
||||
The callback should be called with a valid entry or None to
|
||||
signify that the default soundtrack should be used.."""
|
||||
|
||||
# Subclasses should override the following:
|
||||
|
||||
def on_set_volume(self, volume: float) -> None:
|
||||
"""Called when the volume should be changed."""
|
||||
|
||||
def on_play(self, entry: Any) -> None:
|
||||
"""Called when a new song/playlist/etc should be played."""
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Called when the music should stop."""
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
"""Called on final app shutdown."""
|
||||
|
||||
def _update_play_state(self) -> None:
|
||||
|
||||
# If we aren't playing, should be, and have positive volume, do so.
|
||||
if not self._actually_playing:
|
||||
if self._entry_to_play is not None and self._volume > 0.0:
|
||||
self.on_play(self._entry_to_play)
|
||||
self._actually_playing = True
|
||||
else:
|
||||
if self._actually_playing and (
|
||||
self._entry_to_play is None or self._volume <= 0.0
|
||||
):
|
||||
self.on_stop()
|
||||
self._actually_playing = False
|
||||
|
||||
|
||||
def setmusic(musictype: ba.MusicType | None, continuous: bool = False) -> None:
|
||||
"""Set the app to play (or stop playing) a certain type of music.
|
||||
|
||||
category: **Gameplay Functions**
|
||||
|
||||
This function will handle loading and playing sound assets as necessary,
|
||||
and also supports custom user soundtracks on specific platforms so the
|
||||
user can override particular game music with their own.
|
||||
|
||||
Pass None to stop music.
|
||||
|
||||
if 'continuous' is True and musictype is the same as what is already
|
||||
playing, the playing track will not be restarted.
|
||||
"""
|
||||
|
||||
# All we do here now is set a few music attrs on the current globals
|
||||
# node. The foreground globals' current playing music then gets fed to
|
||||
# the do_play_music call in our music controller. This way we can
|
||||
# seamlessly support custom soundtracks in replays/etc since we're being
|
||||
# driven purely by node data.
|
||||
gnode = _ba.getactivity().globalsnode
|
||||
gnode.music_continuous = continuous
|
||||
gnode.music = '' if musictype is None else musictype.value
|
||||
gnode.music_count += 1
|
||||
|
||||
|
||||
def do_play_music(*args: Any, **keywds: Any) -> None:
|
||||
"""A passthrough used by the C++ layer."""
|
||||
_ba.app.music.do_play_music(*args, **keywds)
|
||||
Loading…
Add table
Add a link
Reference in a new issue