Source code for bascenev1._playlist

# Released under the MIT License. See LICENSE for details.
#
"""Playlist related functionality."""

from __future__ import annotations

import copy
import logging
from typing import Any, TYPE_CHECKING

import babase

if TYPE_CHECKING:
    from typing import Sequence

    from bascenev1._session import Session

PlaylistType = list[dict[str, Any]]


[docs] def filter_playlist( playlist: PlaylistType, sessiontype: type[Session], *, add_resolved_type: bool = False, remove_unowned: bool = True, mark_unowned: bool = False, name: str = '?', ) -> PlaylistType: """Return a filtered version of a playlist. Strips out or replaces invalid or unowned game types, makes sure all settings are present, and adds in a 'resolved_type' which is the actual type. """ # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements from bascenev1._map import get_filtered_map_name from bascenev1._gameactivity import GameActivity assert babase.app.classic is not None goodlist: list[dict] = [] unowned_maps: Sequence[str] available_maps: list[str] = list(babase.app.classic.maps.keys()) if (remove_unowned or mark_unowned) and babase.app.classic is not None: unowned_maps = babase.app.classic.store.get_unowned_maps() unowned_game_types = babase.app.classic.store.get_unowned_game_types() else: unowned_maps = [] unowned_game_types = set() for entry in copy.deepcopy(playlist): # 'map' used to be called 'level' here. if 'level' in entry: entry['map'] = entry['level'] del entry['level'] # We now stuff map into settings instead of it being its own thing. if 'map' in entry: entry['settings']['map'] = entry['map'] del entry['map'] # Update old map names to new ones. entry['settings']['map'] = get_filtered_map_name( entry['settings']['map'] ) if remove_unowned and entry['settings']['map'] in unowned_maps: continue # Ok, for each game in our list, try to import the module and grab # the actual game class. add successful ones to our initial list # to present to the user. if not isinstance(entry['type'], str): raise TypeError('invalid entry format') try: # Do some type filters for backwards compat. if entry['type'] in ( 'Assault.AssaultGame', 'Happy_Thoughts.HappyThoughtsGame', 'bsAssault.AssaultGame', 'bs_assault.AssaultGame', 'bastd.game.assault.AssaultGame', ): entry['type'] = 'bascenev1lib.game.assault.AssaultGame' if entry['type'] in ( 'King_of_the_Hill.KingOfTheHillGame', 'bsKingOfTheHill.KingOfTheHillGame', 'bs_king_of_the_hill.KingOfTheHillGame', 'bastd.game.kingofthehill.KingOfTheHillGame', ): entry['type'] = ( 'bascenev1lib.game.kingofthehill.KingOfTheHillGame' ) if entry['type'] in ( 'Capture_the_Flag.CTFGame', 'bsCaptureTheFlag.CTFGame', 'bs_capture_the_flag.CTFGame', 'bastd.game.capturetheflag.CaptureTheFlagGame', ): entry['type'] = ( 'bascenev1lib.game.capturetheflag.CaptureTheFlagGame' ) if entry['type'] in ( 'Death_Match.DeathMatchGame', 'bsDeathMatch.DeathMatchGame', 'bs_death_match.DeathMatchGame', 'bastd.game.deathmatch.DeathMatchGame', ): entry['type'] = 'bascenev1lib.game.deathmatch.DeathMatchGame' if entry['type'] in ( 'ChosenOne.ChosenOneGame', 'bsChosenOne.ChosenOneGame', 'bs_chosen_one.ChosenOneGame', 'bastd.game.chosenone.ChosenOneGame', ): entry['type'] = 'bascenev1lib.game.chosenone.ChosenOneGame' if entry['type'] in ( 'Conquest.Conquest', 'Conquest.ConquestGame', 'bsConquest.ConquestGame', 'bs_conquest.ConquestGame', 'bastd.game.conquest.ConquestGame', ): entry['type'] = 'bascenev1lib.game.conquest.ConquestGame' if entry['type'] in ( 'Elimination.EliminationGame', 'bsElimination.EliminationGame', 'bs_elimination.EliminationGame', 'bastd.game.elimination.EliminationGame', ): entry['type'] = 'bascenev1lib.game.elimination.EliminationGame' if entry['type'] in ( 'Football.FootballGame', 'bsFootball.FootballTeamGame', 'bs_football.FootballTeamGame', 'bastd.game.football.FootballTeamGame', ): entry['type'] = 'bascenev1lib.game.football.FootballTeamGame' if entry['type'] in ( 'Hockey.HockeyGame', 'bsHockey.HockeyGame', 'bs_hockey.HockeyGame', 'bastd.game.hockey.HockeyGame', ): entry['type'] = 'bascenev1lib.game.hockey.HockeyGame' if entry['type'] in ( 'Keep_Away.KeepAwayGame', 'bsKeepAway.KeepAwayGame', 'bs_keep_away.KeepAwayGame', 'bastd.game.keepaway.KeepAwayGame', ): entry['type'] = 'bascenev1lib.game.keepaway.KeepAwayGame' if entry['type'] in ( 'Race.RaceGame', 'bsRace.RaceGame', 'bs_race.RaceGame', 'bastd.game.race.RaceGame', ): entry['type'] = 'bascenev1lib.game.race.RaceGame' if entry['type'] in ( 'bsEasterEggHunt.EasterEggHuntGame', 'bs_easter_egg_hunt.EasterEggHuntGame', 'bastd.game.easteregghunt.EasterEggHuntGame', ): entry['type'] = ( 'bascenev1lib.game.easteregghunt.EasterEggHuntGame' ) if entry['type'] in ( 'bsMeteorShower.MeteorShowerGame', 'bs_meteor_shower.MeteorShowerGame', 'bastd.game.meteorshower.MeteorShowerGame', ): entry['type'] = ( 'bascenev1lib.game.meteorshower.MeteorShowerGame' ) if entry['type'] in ( 'bsTargetPractice.TargetPracticeGame', 'bs_target_practice.TargetPracticeGame', 'bastd.game.targetpractice.TargetPracticeGame', ): entry['type'] = ( 'bascenev1lib.game.targetpractice.TargetPracticeGame' ) gameclass = babase.getclass(entry['type'], GameActivity) if entry['settings']['map'] not in available_maps: raise babase.MapNotFoundError() if remove_unowned and gameclass in unowned_game_types: continue if add_resolved_type: entry['resolved_type'] = gameclass if mark_unowned and entry['settings']['map'] in unowned_maps: entry['is_unowned_map'] = True if mark_unowned and gameclass in unowned_game_types: entry['is_unowned_game'] = True # Make sure all settings the game defines are present. neededsettings = gameclass.get_available_settings(sessiontype) for setting in neededsettings: if setting.name not in entry['settings']: entry['settings'][setting.name] = setting.default goodlist.append(entry) except babase.MapNotFoundError: logging.warning( 'Map \'%s\' not found while scanning playlist \'%s\'.', entry['settings']['map'], name, ) except ImportError as exc: logging.warning( 'Import failed while scanning playlist \'%s\': %s', name, exc ) except Exception: logging.exception('Error in filter_playlist.') return goodlist
[docs] def get_default_free_for_all_playlist() -> PlaylistType: """Return a default playlist for free-for-all mode.""" # NOTE: these are currently using old type/map names, # but filtering translates them properly to the new ones. # (is kinda a handy way to ensure filtering is working). # Eventually should update these though. return [ { 'settings': { 'Epic Mode': False, 'Kills to Win Per Player': 10, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Doom Shroom', }, 'type': 'bs_death_match.DeathMatchGame', }, { 'settings': { 'Chosen One Gets Gloves': True, 'Chosen One Gets Shield': False, 'Chosen One Time': 30, 'Epic Mode': 0, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Monkey Face', }, 'type': 'bs_chosen_one.ChosenOneGame', }, { 'settings': { 'Hold Time': 30, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Zigzag', }, 'type': 'bs_king_of_the_hill.KingOfTheHillGame', }, { 'settings': {'Epic Mode': False, 'map': 'Rampage'}, 'type': 'bs_meteor_shower.MeteorShowerGame', }, { 'settings': { 'Epic Mode': 1, 'Lives Per Player': 1, 'Respawn Times': 1.0, 'Time Limit': 120, 'map': 'Tip Top', }, 'type': 'bs_elimination.EliminationGame', }, { 'settings': { 'Hold Time': 30, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'The Pad', }, 'type': 'bs_keep_away.KeepAwayGame', }, { 'settings': { 'Epic Mode': True, 'Kills to Win Per Player': 10, 'Respawn Times': 0.25, 'Time Limit': 120, 'map': 'Rampage', }, 'type': 'bs_death_match.DeathMatchGame', }, { 'settings': { 'Bomb Spawning': 1000, 'Epic Mode': False, 'Laps': 3, 'Mine Spawn Interval': 4000, 'Mine Spawning': 4000, 'Time Limit': 300, 'map': 'Big G', }, 'type': 'bs_race.RaceGame', }, { 'settings': { 'Hold Time': 30, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Happy Thoughts', }, 'type': 'bs_king_of_the_hill.KingOfTheHillGame', }, { 'settings': { 'Enable Impact Bombs': 1, 'Enable Triple Bombs': False, 'Target Count': 2, 'map': 'Doom Shroom', }, 'type': 'bs_target_practice.TargetPracticeGame', }, { 'settings': { 'Epic Mode': False, 'Lives Per Player': 5, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Step Right Up', }, 'type': 'bs_elimination.EliminationGame', }, { 'settings': { 'Epic Mode': False, 'Kills to Win Per Player': 10, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Crag Castle', }, 'type': 'bs_death_match.DeathMatchGame', }, { 'map': 'Lake Frigid', 'settings': { 'Bomb Spawning': 0, 'Epic Mode': False, 'Laps': 6, 'Mine Spawning': 2000, 'Time Limit': 300, 'map': 'Lake Frigid', }, 'type': 'bs_race.RaceGame', }, ]
[docs] def get_default_teams_playlist() -> PlaylistType: """Return a default playlist for teams mode.""" # NOTE: these are currently using old type/map names, # but filtering translates them properly to the new ones. # (is kinda a handy way to ensure filtering is working). # Eventually should update these though. return [ { 'settings': { 'Epic Mode': False, 'Flag Idle Return Time': 30, 'Flag Touch Return Time': 0, 'Respawn Times': 1.0, 'Score to Win': 3, 'Time Limit': 600, 'map': 'Bridgit', }, 'type': 'bs_capture_the_flag.CTFGame', }, { 'settings': { 'Epic Mode': False, 'Respawn Times': 1.0, 'Score to Win': 3, 'Time Limit': 600, 'map': 'Step Right Up', }, 'type': 'bs_assault.AssaultGame', }, { 'settings': { 'Balance Total Lives': False, 'Epic Mode': False, 'Lives Per Player': 3, 'Respawn Times': 1.0, 'Solo Mode': True, 'Time Limit': 600, 'map': 'Rampage', }, 'type': 'bs_elimination.EliminationGame', }, { 'settings': { 'Epic Mode': False, 'Kills to Win Per Player': 5, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Roundabout', }, 'type': 'bs_death_match.DeathMatchGame', }, { 'settings': { 'Respawn Times': 1.0, 'Score to Win': 1, 'Time Limit': 600, 'map': 'Hockey Stadium', }, 'type': 'bs_hockey.HockeyGame', }, { 'settings': { 'Hold Time': 30, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Monkey Face', }, 'type': 'bs_keep_away.KeepAwayGame', }, { 'settings': { 'Balance Total Lives': False, 'Epic Mode': True, 'Lives Per Player': 1, 'Respawn Times': 1.0, 'Solo Mode': False, 'Time Limit': 120, 'map': 'Tip Top', }, 'type': 'bs_elimination.EliminationGame', }, { 'settings': { 'Epic Mode': False, 'Respawn Times': 1.0, 'Score to Win': 3, 'Time Limit': 300, 'map': 'Crag Castle', }, 'type': 'bs_assault.AssaultGame', }, { 'settings': { 'Epic Mode': False, 'Kills to Win Per Player': 5, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Doom Shroom', }, 'type': 'bs_death_match.DeathMatchGame', }, { 'settings': {'Epic Mode': False, 'map': 'Rampage'}, 'type': 'bs_meteor_shower.MeteorShowerGame', }, { 'settings': { 'Epic Mode': False, 'Flag Idle Return Time': 30, 'Flag Touch Return Time': 0, 'Respawn Times': 1.0, 'Score to Win': 2, 'Time Limit': 600, 'map': 'Roundabout', }, 'type': 'bs_capture_the_flag.CTFGame', }, { 'settings': { 'Respawn Times': 1.0, 'Score to Win': 21, 'Time Limit': 600, 'map': 'Football Stadium', }, 'type': 'bs_football.FootballTeamGame', }, { 'settings': { 'Epic Mode': True, 'Respawn Times': 0.25, 'Score to Win': 3, 'Time Limit': 120, 'map': 'Bridgit', }, 'type': 'bs_assault.AssaultGame', }, { 'map': 'Doom Shroom', 'settings': { 'Enable Impact Bombs': 1, 'Enable Triple Bombs': False, 'Target Count': 2, 'map': 'Doom Shroom', }, 'type': 'bs_target_practice.TargetPracticeGame', }, { 'settings': { 'Hold Time': 30, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Tip Top', }, 'type': 'bs_king_of_the_hill.KingOfTheHillGame', }, { 'settings': { 'Epic Mode': False, 'Respawn Times': 1.0, 'Score to Win': 2, 'Time Limit': 300, 'map': 'Zigzag', }, 'type': 'bs_assault.AssaultGame', }, { 'settings': { 'Epic Mode': False, 'Flag Idle Return Time': 30, 'Flag Touch Return Time': 0, 'Respawn Times': 1.0, 'Score to Win': 3, 'Time Limit': 300, 'map': 'Happy Thoughts', }, 'type': 'bs_capture_the_flag.CTFGame', }, { 'settings': { 'Bomb Spawning': 1000, 'Epic Mode': True, 'Laps': 1, 'Mine Spawning': 2000, 'Time Limit': 300, 'map': 'Big G', }, 'type': 'bs_race.RaceGame', }, { 'settings': { 'Epic Mode': False, 'Kills to Win Per Player': 5, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Monkey Face', }, 'type': 'bs_death_match.DeathMatchGame', }, { 'settings': { 'Hold Time': 30, 'Respawn Times': 1.0, 'Time Limit': 300, 'map': 'Lake Frigid', }, 'type': 'bs_keep_away.KeepAwayGame', }, { 'settings': { 'Epic Mode': False, 'Flag Idle Return Time': 30, 'Flag Touch Return Time': 3, 'Respawn Times': 1.0, 'Score to Win': 2, 'Time Limit': 300, 'map': 'Tip Top', }, 'type': 'bs_capture_the_flag.CTFGame', }, { 'settings': { 'Balance Total Lives': False, 'Epic Mode': False, 'Lives Per Player': 3, 'Respawn Times': 1.0, 'Solo Mode': False, 'Time Limit': 300, 'map': 'Crag Castle', }, 'type': 'bs_elimination.EliminationGame', }, { 'settings': { 'Epic Mode': True, 'Respawn Times': 0.25, 'Time Limit': 120, 'map': 'Zigzag', }, 'type': 'bs_conquest.ConquestGame', }, ]
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future