Merge branch 'master' into pubsync

This commit is contained in:
Eric Froemling 2020-04-11 22:14:56 -07:00
commit c0f55750db
16 changed files with 133 additions and 150 deletions

View File

@ -771,7 +771,10 @@ class App:
return False return False
# Ok, we're good to go. # Ok, we're good to go.
self.coop_session_args = {'campaign': campaignname, 'level': levelname} self.coop_session_args = {
'campaign': campaignname,
'level': levelname,
}
for arg_name, arg_val in list(args.items()): for arg_name, arg_val in list(args.items()):
self.coop_session_args[arg_name] = arg_val self.coop_session_args[arg_name] = arg_val
@ -782,8 +785,8 @@ class App:
except Exception: except Exception:
from ba import _error from ba import _error
_error.print_exception() _error.print_exception()
from bastd import mainmenu from bastd.mainmenu import MainMenuSession
_ba.new_host_session(mainmenu.MainMenuSession) _ba.new_host_session(MainMenuSession)
_ba.fade_screen(False, endcall=_fade_end) _ba.fade_screen(False, endcall=_fade_end)
return True return True

View File

@ -353,6 +353,7 @@ class CoopSession(Session):
if results.get_score_type() in ('seconds', 'milliseconds', if results.get_score_type() in ('seconds', 'milliseconds',
'time'): 'time'):
score_type = 'time' score_type = 'time'
# Results contains milliseconds; ScoreScreen wants # Results contains milliseconds; ScoreScreen wants
# hundredths; need to fix :-/ # hundredths; need to fix :-/
if score is not None: if score is not None:

View File

@ -289,10 +289,10 @@ class GameActivity(Activity):
@classmethod @classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
"""Return whether this game supports the provided Session type.""" """Return whether this game supports the provided Session type."""
from ba import _teambasesession from ba._teambasesession import TeamBaseSession
# By default, games support any versus mode # By default, games support any versus mode
return issubclass(sessiontype, _teambasesession.TeamBaseSession) return issubclass(sessiontype, TeamBaseSession)
def __init__(self, settings: Dict[str, Any]): def __init__(self, settings: Dict[str, Any]):
"""Instantiate the Activity.""" """Instantiate the Activity."""
@ -546,6 +546,7 @@ class GameActivity(Activity):
self.end_game() self.end_game()
# FIXME: this logic should live in the session classes.
def _game_begin_analytics(self) -> None: def _game_begin_analytics(self) -> None:
"""Update analytics events for the start of the game.""" """Update analytics events for the start of the game."""
# pylint: disable=too-many-branches # pylint: disable=too-many-branches

View File

@ -191,7 +191,7 @@ class TeamGameResults:
sval.append(team) sval.append(team)
results: List[Tuple[Optional[int], results: List[Tuple[Optional[int],
List[ba.Team]]] = list(winners.items()) List[ba.Team]]] = list(winners.items())
results.sort(reverse=not self._lower_is_better) results.sort(reverse=not self._lower_is_better, key=lambda x: x[0])
# Also group the 'None' scores. # Also group the 'None' scores.
none_teams: List[ba.Team] = [] none_teams: List[ba.Team] = []

View File

@ -60,26 +60,29 @@ def filter_playlist(playlist: PlaylistType,
unowned_game_types = set() unowned_game_types = set()
for entry in copy.deepcopy(playlist): for entry in copy.deepcopy(playlist):
# 'map' used to be called 'level' here # 'map' used to be called 'level' here.
if 'level' in entry: if 'level' in entry:
entry['map'] = entry['level'] entry['map'] = entry['level']
del entry['level'] del entry['level']
# we now stuff map into settings instead of it being its own thing
# We now stuff map into settings instead of it being its own thing.
if 'map' in entry: if 'map' in entry:
entry['settings']['map'] = entry['map'] entry['settings']['map'] = entry['map']
del entry['map'] del entry['map']
# update old map names to new ones
# Update old map names to new ones.
entry['settings']['map'] = _map.get_filtered_map_name( entry['settings']['map'] = _map.get_filtered_map_name(
entry['settings']['map']) entry['settings']['map'])
if remove_unowned and entry['settings']['map'] in unowned_maps: if remove_unowned and entry['settings']['map'] in unowned_maps:
continue continue
# ok, for each game in our list, try to import the module and grab
# 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 # the actual game class. add successful ones to our initial list
# to present to the user # to present to the user.
if not isinstance(entry['type'], str): if not isinstance(entry['type'], str):
raise Exception("invalid entry format") raise Exception("invalid entry format")
try: try:
# do some type filters for backwards compat. # Do some type filters for backwards compat.
if entry['type'] in ('Assault.AssaultGame', if entry['type'] in ('Assault.AssaultGame',
'Happy_Thoughts.HappyThoughtsGame', 'Happy_Thoughts.HappyThoughtsGame',
'bsAssault.AssaultGame', 'bsAssault.AssaultGame',
@ -147,7 +150,7 @@ def filter_playlist(playlist: PlaylistType,
if mark_unowned and gameclass in unowned_game_types: if mark_unowned and gameclass in unowned_game_types:
entry['is_unowned_game'] = True entry['is_unowned_game'] = True
# make sure all settings the game defines are present # Make sure all settings the game defines are present.
neededsettings = gameclass.get_settings(sessiontype) neededsettings = gameclass.get_settings(sessiontype)
for setting_name, setting in neededsettings: for setting_name, setting in neededsettings:
if (setting_name not in entry['settings'] if (setting_name not in entry['settings']
@ -164,6 +167,7 @@ def filter_playlist(playlist: PlaylistType,
def get_default_free_for_all_playlist() -> PlaylistType: def get_default_free_for_all_playlist() -> PlaylistType:
"""Return a default playlist for free-for-all mode.""" """Return a default playlist for free-for-all mode."""
# NOTE: these are currently using old type/map names, # NOTE: these are currently using old type/map names,
# but filtering translates them properly to the new ones. # but filtering translates them properly to the new ones.
# (is kinda a handy way to ensure filtering is working). # (is kinda a handy way to ensure filtering is working).
@ -177,8 +181,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Doom Shroom' 'map': 'Doom Shroom'
}, },
'type': 'bs_death_match.DeathMatchGame' 'type': 'bs_death_match.DeathMatchGame'
}, }, {
{
'settings': { 'settings': {
'Chosen One Gets Gloves': True, 'Chosen One Gets Gloves': True,
'Chosen One Gets Shield': False, 'Chosen One Gets Shield': False,
@ -189,8 +192,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Monkey Face' 'map': 'Monkey Face'
}, },
'type': 'bs_chosen_one.ChosenOneGame' 'type': 'bs_chosen_one.ChosenOneGame'
}, }, {
{
'settings': { 'settings': {
'Hold Time': 30, 'Hold Time': 30,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -198,15 +200,13 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Zigzag' 'map': 'Zigzag'
}, },
'type': 'bs_king_of_the_hill.KingOfTheHillGame' 'type': 'bs_king_of_the_hill.KingOfTheHillGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'map': 'Rampage' 'map': 'Rampage'
}, },
'type': 'bs_meteor_shower.MeteorShowerGame' 'type': 'bs_meteor_shower.MeteorShowerGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': 1, 'Epic Mode': 1,
'Lives Per Player': 1, 'Lives Per Player': 1,
@ -215,8 +215,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Tip Top' 'map': 'Tip Top'
}, },
'type': 'bs_elimination.EliminationGame' 'type': 'bs_elimination.EliminationGame'
}, }, {
{
'settings': { 'settings': {
'Hold Time': 30, 'Hold Time': 30,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -224,8 +223,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'The Pad' 'map': 'The Pad'
}, },
'type': 'bs_keep_away.KeepAwayGame' 'type': 'bs_keep_away.KeepAwayGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': True, 'Epic Mode': True,
'Kills to Win Per Player': 10, 'Kills to Win Per Player': 10,
@ -234,8 +232,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Rampage' 'map': 'Rampage'
}, },
'type': 'bs_death_match.DeathMatchGame' 'type': 'bs_death_match.DeathMatchGame'
}, }, {
{
'settings': { 'settings': {
'Bomb Spawning': 1000, 'Bomb Spawning': 1000,
'Epic Mode': False, 'Epic Mode': False,
@ -246,8 +243,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Big G' 'map': 'Big G'
}, },
'type': 'bs_race.RaceGame' 'type': 'bs_race.RaceGame'
}, }, {
{
'settings': { 'settings': {
'Hold Time': 30, 'Hold Time': 30,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -255,8 +251,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Happy Thoughts' 'map': 'Happy Thoughts'
}, },
'type': 'bs_king_of_the_hill.KingOfTheHillGame' 'type': 'bs_king_of_the_hill.KingOfTheHillGame'
}, }, {
{
'settings': { 'settings': {
'Enable Impact Bombs': 1, 'Enable Impact Bombs': 1,
'Enable Triple Bombs': False, 'Enable Triple Bombs': False,
@ -264,8 +259,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Doom Shroom' 'map': 'Doom Shroom'
}, },
'type': 'bs_target_practice.TargetPracticeGame' 'type': 'bs_target_practice.TargetPracticeGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Lives Per Player': 5, 'Lives Per Player': 5,
@ -274,8 +268,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Step Right Up' 'map': 'Step Right Up'
}, },
'type': 'bs_elimination.EliminationGame' 'type': 'bs_elimination.EliminationGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Kills to Win Per Player': 10, 'Kills to Win Per Player': 10,
@ -284,8 +277,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Crag Castle' 'map': 'Crag Castle'
}, },
'type': 'bs_death_match.DeathMatchGame' 'type': 'bs_death_match.DeathMatchGame'
}, }, {
{
'map': 'Lake Frigid', 'map': 'Lake Frigid',
'settings': { 'settings': {
'Bomb Spawning': 0, 'Bomb Spawning': 0,
@ -296,7 +288,7 @@ def get_default_free_for_all_playlist() -> PlaylistType:
'map': 'Lake Frigid' 'map': 'Lake Frigid'
}, },
'type': 'bs_race.RaceGame' 'type': 'bs_race.RaceGame'
}] # yapf: disable }]
def get_default_teams_playlist() -> PlaylistType: def get_default_teams_playlist() -> PlaylistType:
@ -317,8 +309,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Bridgit' 'map': 'Bridgit'
}, },
'type': 'bs_capture_the_flag.CTFGame' 'type': 'bs_capture_the_flag.CTFGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -327,8 +318,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Step Right Up' 'map': 'Step Right Up'
}, },
'type': 'bs_assault.AssaultGame' 'type': 'bs_assault.AssaultGame'
}, }, {
{
'settings': { 'settings': {
'Balance Total Lives': False, 'Balance Total Lives': False,
'Epic Mode': False, 'Epic Mode': False,
@ -339,8 +329,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Rampage' 'map': 'Rampage'
}, },
'type': 'bs_elimination.EliminationGame' 'type': 'bs_elimination.EliminationGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Kills to Win Per Player': 5, 'Kills to Win Per Player': 5,
@ -349,8 +338,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Roundabout' 'map': 'Roundabout'
}, },
'type': 'bs_death_match.DeathMatchGame' 'type': 'bs_death_match.DeathMatchGame'
}, }, {
{
'settings': { 'settings': {
'Respawn Times': 1.0, 'Respawn Times': 1.0,
'Score to Win': 1, 'Score to Win': 1,
@ -358,8 +346,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Hockey Stadium' 'map': 'Hockey Stadium'
}, },
'type': 'bs_hockey.HockeyGame' 'type': 'bs_hockey.HockeyGame'
}, }, {
{
'settings': { 'settings': {
'Hold Time': 30, 'Hold Time': 30,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -367,8 +354,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Monkey Face' 'map': 'Monkey Face'
}, },
'type': 'bs_keep_away.KeepAwayGame' 'type': 'bs_keep_away.KeepAwayGame'
}, }, {
{
'settings': { 'settings': {
'Balance Total Lives': False, 'Balance Total Lives': False,
'Epic Mode': True, 'Epic Mode': True,
@ -379,8 +365,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Tip Top' 'map': 'Tip Top'
}, },
'type': 'bs_elimination.EliminationGame' 'type': 'bs_elimination.EliminationGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -389,8 +374,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Crag Castle' 'map': 'Crag Castle'
}, },
'type': 'bs_assault.AssaultGame' 'type': 'bs_assault.AssaultGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Kills to Win Per Player': 5, 'Kills to Win Per Player': 5,
@ -399,15 +383,13 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Doom Shroom' 'map': 'Doom Shroom'
}, },
'type': 'bs_death_match.DeathMatchGame' 'type': 'bs_death_match.DeathMatchGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'map': 'Rampage' 'map': 'Rampage'
}, },
'type': 'bs_meteor_shower.MeteorShowerGame' 'type': 'bs_meteor_shower.MeteorShowerGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Flag Idle Return Time': 30, 'Flag Idle Return Time': 30,
@ -418,8 +400,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Roundabout' 'map': 'Roundabout'
}, },
'type': 'bs_capture_the_flag.CTFGame' 'type': 'bs_capture_the_flag.CTFGame'
}, }, {
{
'settings': { 'settings': {
'Respawn Times': 1.0, 'Respawn Times': 1.0,
'Score to Win': 21, 'Score to Win': 21,
@ -427,8 +408,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Football Stadium' 'map': 'Football Stadium'
}, },
'type': 'bs_football.FootballTeamGame' 'type': 'bs_football.FootballTeamGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': True, 'Epic Mode': True,
'Respawn Times': 0.25, 'Respawn Times': 0.25,
@ -437,8 +417,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Bridgit' 'map': 'Bridgit'
}, },
'type': 'bs_assault.AssaultGame' 'type': 'bs_assault.AssaultGame'
}, }, {
{
'map': 'Doom Shroom', 'map': 'Doom Shroom',
'settings': { 'settings': {
'Enable Impact Bombs': 1, 'Enable Impact Bombs': 1,
@ -447,8 +426,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Doom Shroom' 'map': 'Doom Shroom'
}, },
'type': 'bs_target_practice.TargetPracticeGame' 'type': 'bs_target_practice.TargetPracticeGame'
}, }, {
{
'settings': { 'settings': {
'Hold Time': 30, 'Hold Time': 30,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -456,8 +434,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Tip Top' 'map': 'Tip Top'
}, },
'type': 'bs_king_of_the_hill.KingOfTheHillGame' 'type': 'bs_king_of_the_hill.KingOfTheHillGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -466,8 +443,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Zigzag' 'map': 'Zigzag'
}, },
'type': 'bs_assault.AssaultGame' 'type': 'bs_assault.AssaultGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Flag Idle Return Time': 30, 'Flag Idle Return Time': 30,
@ -478,8 +454,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Happy Thoughts' 'map': 'Happy Thoughts'
}, },
'type': 'bs_capture_the_flag.CTFGame' 'type': 'bs_capture_the_flag.CTFGame'
}, }, {
{
'settings': { 'settings': {
'Bomb Spawning': 1000, 'Bomb Spawning': 1000,
'Epic Mode': True, 'Epic Mode': True,
@ -489,8 +464,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Big G' 'map': 'Big G'
}, },
'type': 'bs_race.RaceGame' 'type': 'bs_race.RaceGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Kills to Win Per Player': 5, 'Kills to Win Per Player': 5,
@ -499,8 +473,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Monkey Face' 'map': 'Monkey Face'
}, },
'type': 'bs_death_match.DeathMatchGame' 'type': 'bs_death_match.DeathMatchGame'
}, }, {
{
'settings': { 'settings': {
'Hold Time': 30, 'Hold Time': 30,
'Respawn Times': 1.0, 'Respawn Times': 1.0,
@ -508,8 +481,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Lake Frigid' 'map': 'Lake Frigid'
}, },
'type': 'bs_keep_away.KeepAwayGame' 'type': 'bs_keep_away.KeepAwayGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': False, 'Epic Mode': False,
'Flag Idle Return Time': 30, 'Flag Idle Return Time': 30,
@ -520,8 +492,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Tip Top' 'map': 'Tip Top'
}, },
'type': 'bs_capture_the_flag.CTFGame' 'type': 'bs_capture_the_flag.CTFGame'
}, }, {
{
'settings': { 'settings': {
'Balance Total Lives': False, 'Balance Total Lives': False,
'Epic Mode': False, 'Epic Mode': False,
@ -532,8 +503,7 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Crag Castle' 'map': 'Crag Castle'
}, },
'type': 'bs_elimination.EliminationGame' 'type': 'bs_elimination.EliminationGame'
}, }, {
{
'settings': { 'settings': {
'Epic Mode': True, 'Epic Mode': True,
'Respawn Times': 0.25, 'Respawn Times': 0.25,
@ -541,4 +511,4 @@ def get_default_teams_playlist() -> PlaylistType:
'map': 'Zigzag' 'map': 'Zigzag'
}, },
'type': 'bs_conquest.ConquestGame' 'type': 'bs_conquest.ConquestGame'
}] # yapf: disable }]

View File

@ -75,13 +75,14 @@ class Session:
""" """
# Annotate our attrs at class level so they're available for introspection. # Note: even though these are instance vars, we annotate them at the
teams: List[ba.Team] # class level so that docs generation can access their types.
campaign: Optional[ba.Campaign] campaign: Optional[ba.Campaign]
lobby: ba.Lobby lobby: ba.Lobby
min_players: int
max_players: int max_players: int
min_players: int
players: List[ba.Player] players: List[ba.Player]
teams: List[ba.Team]
def __init__(self, def __init__(self,
depsets: Sequence[ba.DependencySet], depsets: Sequence[ba.DependencySet],
@ -97,7 +98,6 @@ class Session:
instances; one for each ba.Activity the session may potentially run. instances; one for each ba.Activity the session may potentially run.
""" """
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from ba._lobby import Lobby from ba._lobby import Lobby
@ -108,26 +108,23 @@ class Session:
from ba._error import DependencyError from ba._error import DependencyError
from ba._dependency import Dependency, AssetPackage from ba._dependency import Dependency, AssetPackage
# print(' WOULD LOOK AT DEP SETS', depsets) # First off, resolve all dependency-sets we were passed.
# If things are missing, we'll try to gather them into a single
# first off, resolve all dep-sets we were passed. # missing-deps exception if possible to give the caller a clean
# if things are missing, we'll try to gather them into # path to download missing stuff and try again.
# a single missing-deps exception if possible
# to give the caller a clean path to download missing
# stuff and try again.
missing_asset_packages: Set[str] = set() missing_asset_packages: Set[str] = set()
for depset in depsets: for depset in depsets:
try: try:
depset.resolve() depset.resolve()
except DependencyError as exc: except DependencyError as exc:
# we gather/report missing assets only; barf on anything else # Gather/report missing assets only; barf on anything else.
if all(issubclass(d.cls, AssetPackage) for d in exc.deps): if all(issubclass(d.cls, AssetPackage) for d in exc.deps):
for dep in exc.deps: for dep in exc.deps:
assert isinstance(dep.config, str) assert isinstance(dep.config, str)
missing_asset_packages.add(dep.config) missing_asset_packages.add(dep.config)
else: else:
missing_info = [(d.cls, d.config) for d in exc.deps] missing_info = [(d.cls, d.config) for d in exc.deps]
raise Exception( raise RuntimeError(
f'Missing non-asset dependencies: {missing_info}') f'Missing non-asset dependencies: {missing_info}')
# Throw a combined exception if we found anything missing. # Throw a combined exception if we found anything missing.
@ -146,11 +143,6 @@ class Session:
# print('Would set host-session asset-reqs to:', # print('Would set host-session asset-reqs to:',
# required_asset_packages) # required_asset_packages)
if team_names is None:
team_names = ['Good Guys']
if team_colors is None:
team_colors = [(0.6, 0.2, 1.0)]
# First thing, wire up our internal engine data. # First thing, wire up our internal engine data.
self._sessiondata = _ba.register_session(self) self._sessiondata = _ba.register_session(self)
@ -159,8 +151,8 @@ class Session:
# FIXME: This stuff shouldn't be here. # FIXME: This stuff shouldn't be here.
self.sharedobjs: Dict[str, Any] = {} self.sharedobjs: Dict[str, Any] = {}
# TeamGameActivity uses this to display a help overlay on # TeamGameActivity uses this to display a help overlay on the first
# the first activity only. # activity only.
self.have_shown_controls_help_overlay = False self.have_shown_controls_help_overlay = False
self.campaign = None self.campaign = None
@ -186,9 +178,8 @@ class Session:
self._activity_weak: ReferenceType[ba.Activity] self._activity_weak: ReferenceType[ba.Activity]
self._activity_weak = weakref.ref(_EmptyObj()) # type: ignore self._activity_weak = weakref.ref(_EmptyObj()) # type: ignore
if self._activity_weak() is not None: if self._activity_weak() is not None:
raise Exception("error creating empty weak ref") raise Exception("Error creating empty activity weak ref.")
self._next_activity: Optional[ba.Activity] = None self._next_activity: Optional[ba.Activity] = None
self.wants_to_end = False self.wants_to_end = False
@ -196,7 +187,10 @@ class Session:
self.min_players = min_players self.min_players = min_players
self.max_players = max_players self.max_players = max_players
# Create Teams.
if self._use_teams: if self._use_teams:
assert team_names is not None
assert team_colors is not None
for i, color in enumerate(team_colors): for i, color in enumerate(team_colors):
team = Team(team_id=self._next_team_id, team = Team(team_id=self._next_team_id,
name=GameActivity.get_team_display_string( name=GameActivity.get_team_display_string(
@ -210,8 +204,8 @@ class Session:
self.on_team_join(team) self.on_team_join(team)
except Exception: except Exception:
from ba import _error from ba import _error
_error.print_exception('exception in on_team_join for', _error.print_exception(
self) f'Error in on_team_join for {self}.')
self.lobby = Lobby() self.lobby = Lobby()
self.stats = Stats() self.stats = Stats()
@ -242,8 +236,8 @@ class Session:
if len(self.players) >= self.max_players: if len(self.players) >= self.max_players:
# Print a rejection message *only* to the client trying to join # Print a rejection message *only* to the client trying to
# (prevents spamming everyone else in the game). # join (prevents spamming everyone else in the game).
_ba.playsound(_ba.getsound('error')) _ba.playsound(_ba.getsound('error'))
_ba.screenmessage( _ba.screenmessage(
Lstr(resource='playerLimitReachedText', Lstr(resource='playerLimitReachedText',
@ -291,7 +285,7 @@ class Session:
_error.print_exception( _error.print_exception(
'Error in Lobby.remove_chooser()') 'Error in Lobby.remove_chooser()')
# *if* he was actually in the game, announce his departure # *If* they were actually in the game, announce their departure.
if team is not None: if team is not None:
_ba.screenmessage( _ba.screenmessage(
Lstr(resource='playerLeftText', Lstr(resource='playerLeftText',
@ -302,7 +296,7 @@ class Session:
# team lists every activity) # team lists every activity)
if team is not None and player in team.players: if team is not None and player in team.players:
# Testing.. can remove this eventually. # Testing; can remove this eventually.
if isinstance(self, FreeForAllSession): if isinstance(self, FreeForAllSession):
if len(team.players) != 1: if len(team.players) != 1:
_error.print_error("expected 1 player in FFA team") _error.print_error("expected 1 player in FFA team")
@ -328,7 +322,7 @@ class Session:
player.set_activity(None) player.set_activity(None)
player.set_node(None) player.set_node(None)
# reset the player - this will remove its actor-ref and clear # Reset the player; this will remove its actor-ref and clear
# its calls/etc # its calls/etc
try: try:
with _ba.Context(activity): with _ba.Context(activity):
@ -377,6 +371,7 @@ class Session:
except Exception: except Exception:
_error.print_exception( _error.print_exception(
'exception in on_team_leave for session', self) 'exception in on_team_leave for session', self)
# Clear the team's session-data (so dying stuff will # Clear the team's session-data (so dying stuff will
# have proper context). # have proper context).
try: try:
@ -454,20 +449,22 @@ class Session:
""" """
from ba._general import Call from ba._general import Call
from ba._enums import TimeType from ba._enums import TimeType
# only pay attention if this is coming from our current activity..
# Only pay attention if this is coming from our current activity.
if activity is not self._activity_retained: if activity is not self._activity_retained:
return return
# if this activity hasn't begun yet, just set it up to end immediately # If this activity hasn't begun yet, just set it up to end immediately
# once it does # once it does.
if not activity.has_begun(): if not activity.has_begun():
activity.set_immediate_end(results, delay, force) activity.set_immediate_end(results, delay, force)
# the activity has already begun; get ready to end it.. # The activity has already begun; get ready to end it.
else: else:
if (not activity.has_ended()) or force: if (not activity.has_ended()) or force:
activity.set_has_ended(True) activity.set_has_ended(True)
# set a timer to set in motion this activity's demise
# Set a timer to set in motion this activity's demise.
self._activity_end_timer = _ba.Timer( self._activity_end_timer = _ba.Timer(
delay, delay,
Call(self._complete_end_activity, activity, results), Call(self._complete_end_activity, activity, results),
@ -483,8 +480,8 @@ class Session:
return None return None
if isinstance(msg, PlayerProfilesChangedMessage): if isinstance(msg, PlayerProfilesChangedMessage):
# if we have a current activity with a lobby, ask it to # If we have a current activity with a lobby, ask it to reload
# reload profiles # profiles.
with _ba.Context(self): with _ba.Context(self):
self.lobby.reload_profiles() self.lobby.reload_profiles()
return None return None
@ -511,7 +508,7 @@ class Session:
"Session.set_activity() cannot be called recursively.") "Session.set_activity() cannot be called recursively.")
if activity.session is not _ba.getsession(): if activity.session is not _ba.getsession():
raise Exception("provided activity's session is not current") raise Exception("Provided Activity's Session is not current.")
# Quietly ignore this if the whole session is going down. # Quietly ignore this if the whole session is going down.
if self._ending: if self._ending:
@ -574,7 +571,7 @@ class Session:
prev_activity._transitioning_out = True prev_activity._transitioning_out = True
# pylint: enable=protected-access # pylint: enable=protected-access
# activity will be None until the next one begins. # Activity will be None until the next one begins.
with _ba.Context(prev_activity): with _ba.Context(prev_activity):
prev_activity.on_transition_out() prev_activity.on_transition_out()
@ -613,6 +610,7 @@ class Session:
_ba.timer(activity.transition_time, _ba.timer(activity.transition_time,
prev_activity._destroy, prev_activity._destroy,
timetype=TimeType.REAL) timetype=TimeType.REAL)
# Just run immediately. # Just run immediately.
else: else:
# noinspection PyProtectedMember # noinspection PyProtectedMember
@ -720,6 +718,7 @@ class Session:
_ba.playsound(_ba.getsound('error')) _ba.playsound(_ba.getsound('error'))
else: else:
return return
# Otherwise just add players on the fly. # Otherwise just add players on the fly.
else: else:
self._add_chosen_player(chooser) self._add_chosen_player(chooser)

View File

@ -53,7 +53,7 @@ class TeamBaseSession(Session):
def __init__(self) -> None: def __init__(self) -> None:
"""Set up playlists and launches a ba.Activity to accept joiners.""" """Set up playlists and launches a ba.Activity to accept joiners."""
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from ba import _playlist as bsplaylist from ba import _playlist
from bastd.activity import multiteamjoinscreen from bastd.activity import multiteamjoinscreen
app = _ba.app app = _ba.app
cfg = app.config cfg = app.config
@ -67,6 +67,7 @@ class TeamBaseSession(Session):
# print('FIXME: TEAM BASE SESSION WOULD CALC DEPS.') # print('FIXME: TEAM BASE SESSION WOULD CALC DEPS.')
depsets: Sequence[ba.DependencySet] = [] depsets: Sequence[ba.DependencySet] = []
super().__init__(depsets, super().__init__(depsets,
team_names=team_names, team_names=team_names,
team_colors=team_colors, team_colors=team_colors,
@ -100,22 +101,23 @@ class TeamBaseSession(Session):
if (self._playlist_name != '__default__' if (self._playlist_name != '__default__'
and self._playlist_name in playlists): and self._playlist_name in playlists):
# Make sure to copy this, as we muck with it in place once we've # Make sure to copy this, as we muck with it in place once we've
# got it and we don't want that to affect our config. # got it and we don't want that to affect our config.
playlist = copy.deepcopy(playlists[self._playlist_name]) playlist = copy.deepcopy(playlists[self._playlist_name])
else: else:
if self._use_teams: if self._use_teams:
playlist = bsplaylist.get_default_teams_playlist() playlist = _playlist.get_default_teams_playlist()
else: else:
playlist = bsplaylist.get_default_free_for_all_playlist() playlist = _playlist.get_default_free_for_all_playlist()
# Resolve types and whatnot to get our final playlist. # Resolve types and whatnot to get our final playlist.
playlist_resolved = bsplaylist.filter_playlist(playlist, playlist_resolved = _playlist.filter_playlist(playlist,
sessiontype=type(self), sessiontype=type(self),
add_resolved_type=True) add_resolved_type=True)
if not playlist_resolved: if not playlist_resolved:
raise Exception("playlist contains no valid games") raise RuntimeError("Playlist contains no valid games.")
self._playlist = ShuffleList(playlist_resolved, self._playlist = ShuffleList(playlist_resolved,
shuffle=self._playlist_randomize) shuffle=self._playlist_randomize)
@ -176,22 +178,21 @@ class TeamBaseSession(Session):
TeamSeriesVictoryScoreScreenActivity) TeamSeriesVictoryScoreScreenActivity)
from ba import _activitytypes from ba import _activitytypes
# If we have a tutorial to show, # If we have a tutorial to show, that's the first thing we do no
# that's the first thing we do no matter what. # matter what.
if self._tutorial_activity_instance is not None: if self._tutorial_activity_instance is not None:
self.set_activity(self._tutorial_activity_instance) self.set_activity(self._tutorial_activity_instance)
self._tutorial_activity_instance = None self._tutorial_activity_instance = None
# If we're leaving the tutorial activity, # If we're leaving the tutorial activity, pop a transition activity
# pop a transition activity to transition # to transition us into a round gracefully (otherwise we'd snap from
# us into a round gracefully (otherwise we'd # one terrain to another instantly).
# snap from one terrain to another instantly).
elif isinstance(activity, TutorialActivity): elif isinstance(activity, TutorialActivity):
self.set_activity( self.set_activity(
_ba.new_activity(_activitytypes.TransitionActivity)) _ba.new_activity(_activitytypes.TransitionActivity))
# If we're in a between-round activity or a restart-activity, # If we're in a between-round activity or a restart-activity, hop
# hop into a round. # into a round.
elif isinstance( elif isinstance(
activity, activity,
(_activitytypes.JoiningActivity, _activitytypes.TransitionActivity, (_activitytypes.JoiningActivity, _activitytypes.TransitionActivity,
@ -203,6 +204,7 @@ class TeamBaseSession(Session):
self._game_number = 0 self._game_number = 0
for team in self.teams: for team in self.teams:
team.sessiondata['score'] = 0 team.sessiondata['score'] = 0
# Otherwise just set accum (per-game) scores. # Otherwise just set accum (per-game) scores.
else: else:
self.stats.reset_accum() self.stats.reset_accum()
@ -216,7 +218,7 @@ class TeamBaseSession(Session):
# Instantiate the next now so they have plenty of time to load. # Instantiate the next now so they have plenty of time to load.
self._instantiate_next_game() self._instantiate_next_game()
# (re)register all players and wire stats to our next activity # (Re)register all players and wire stats to our next activity.
for player in self.players: for player in self.players:
# ..but only ones who have been placed on a team # ..but only ones who have been placed on a team
# (ie: no longer sitting in the lobby). # (ie: no longer sitting in the lobby).
@ -321,6 +323,7 @@ class ShuffleList:
continue continue
if test_obj['type'] == self.last_gotten['type']: if test_obj['type'] == self.last_gotten['type']:
continue continue
# Sufficiently different; lets go with it. # Sufficiently different; lets go with it.
break break

View File

@ -674,7 +674,8 @@ class CoopScoreScreen(ba.Activity):
our_score = None our_score = None
try: try:
our_high_scores.sort(reverse=self._score_order == 'increasing') our_high_scores.sort(reverse=self._score_order == 'increasing',
key=lambda x: x[0])
except Exception: except Exception:
ba.print_exception('Error sorting scores') ba.print_exception('Error sorting scores')
print('our_high_scores:', our_high_scores) print('our_high_scores:', our_high_scores)
@ -923,7 +924,8 @@ class CoopScoreScreen(ba.Activity):
results.remove(score) results.remove(score)
break break
results.append(our_score_entry) results.append(our_score_entry)
results.sort(reverse=self._score_order == 'increasing') results.sort(reverse=self._score_order == 'increasing',
key=lambda x: x[0])
# If we're not submitting our own score, we still want to change the # If we're not submitting our own score, we still want to change the
# name of our own score to 'Me'. # name of our own score to 'Me'.

View File

@ -80,11 +80,11 @@ class TeamSeriesVictoryScoreScreenActivity(TeamsScoreScreenActivity):
player_entries.append( player_entries.append(
(prec.player.team.sessiondata['score'], (prec.player.team.sessiondata['score'],
prec.get_name(full=True), prec)) prec.get_name(full=True), prec))
player_entries.sort(reverse=True) player_entries.sort(reverse=True, key=lambda x: x[0])
else: else:
for _pkey, prec in self.stats.get_records().items(): for _pkey, prec in self.stats.get_records().items():
player_entries.append((prec.score, prec.name_full, prec)) player_entries.append((prec.score, prec.name_full, prec))
player_entries.sort(reverse=True) player_entries.sort(reverse=True, key=lambda x: x[0])
ts_height = 300.0 ts_height = 300.0
ts_h_offs = -390.0 ts_h_offs = -390.0

View File

@ -290,6 +290,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity):
if (not team.gamedata['home_flag_at_base'] if (not team.gamedata['home_flag_at_base']
and flag.held_count == 0): and flag.held_count == 0):
time_out_counting_down = True time_out_counting_down = True
if flag.time_out_respawn_time is None:
flag.reset_return_times()
assert flag.time_out_respawn_time is not None
flag.time_out_respawn_time -= 1 flag.time_out_respawn_time -= 1
if flag.time_out_respawn_time <= 0: if flag.time_out_respawn_time <= 0:
flag.handlemessage(ba.DieMessage()) flag.handlemessage(ba.DieMessage())

View File

@ -59,7 +59,7 @@ class Icon(ba.Actor):
icon = player.get_icon() icon = player.get_icon()
self.node = ba.newnode('image', self.node = ba.newnode('image',
owner=self, delegate=self,
attrs={ attrs={
'texture': icon['texture'], 'texture': icon['texture'],
'tint_texture': icon['tint_texture'], 'tint_texture': icon['tint_texture'],
@ -400,7 +400,8 @@ class EliminationGame(ba.TeamGameActivity):
self.map.get_start_position(team.get_id())) self.map.get_start_position(team.get_id()))
points.append( points.append(
((start_pos - player_pos).length(), start_pos)) ((start_pos - player_pos).length(), start_pos))
points.sort() # Hmm.. we need to sorting vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1] return points[-1][1]
return None return None

View File

@ -69,6 +69,7 @@ class Puck(ba.Actor):
'position': self._spawn_pos, 'position': self._spawn_pos,
'materials': pmats 'materials': pmats
}) })
ba.animate(self.node, "model_scale", {0: 0, 0.2: 1.3, 0.26: 1})
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.DieMessage): if isinstance(msg, ba.DieMessage):

View File

@ -430,11 +430,11 @@ class OnslaughtGame(ba.CoopGameActivity):
{'entries': [ {'entries': [
{'type': spazbot.ChargerBotProShielded, {'type': spazbot.ChargerBotProShielded,
'point': 'bottom_right'}, 'point': 'bottom_right'},
{'type': spazbot.ChargerBotProShielded, 'point': 'Bottom'} {'type': spazbot.ChargerBotProShielded, 'point': 'bottom'}
if player_count > 2 else None, if player_count > 2 else None,
{'type': spazbot.ChargerBotProShielded, {'type': spazbot.ChargerBotProShielded,
'point': 'bottom_left'}, 'point': 'bottom_left'},
{'type': spazbot.ChargerBotProShielded, 'point': 'Top'} {'type': spazbot.ChargerBotProShielded, 'point': 'top'}
if hard else None, if hard else None,
{'type': spazbot.BomberBotProStatic, {'type': spazbot.BomberBotProStatic,
'point': 'turret_top_middle'}, 'point': 'turret_top_middle'},

View File

@ -555,9 +555,7 @@ class RaceGame(ba.TeamGameActivity):
p_list = [[player.gamedata['distance'], player] p_list = [[player.gamedata['distance'], player]
for player in self.players] for player in self.players]
p_list.sort(reverse=True) p_list.sort(reverse=True, key=lambda x: x[0])
# FIXME - need another way to sort p_list.
# It tries to compare ba.Player objects.
for i, plr in enumerate(p_list): for i, plr in enumerate(p_list):
try: try:
plr[1].gamedata['rank'] = i plr[1].gamedata['rank'] = i

View File

@ -1451,8 +1451,7 @@ class GatherWindow(ba.Window):
key=lambda p: ( key=lambda p: (
p['queue'] is None, # Show non-queued last. p['queue'] is None, # Show non-queued last.
p['ping'] if p['ping'] is not None else 999999, p['ping'] if p['ping'] is not None else 999999,
p['index'], p['index']))
p))
existing_selection = self._public_party_list_selection existing_selection = self._public_party_list_selection
first = True first = True

View File

@ -303,4 +303,6 @@ def make_hash(obj: Any) -> int:
for k, v in new_obj.items(): for k, v in new_obj.items():
new_obj[k] = make_hash(v) new_obj[k] = make_hash(v)
# NOTE: there is sorted works correctly because it compares only
# unique first values (i.e. dict keys)
return hash(tuple(frozenset(sorted(new_obj.items())))) return hash(tuple(frozenset(sorted(new_obj.items()))))