More modernizing and cleanup

This commit is contained in:
Eric Froemling 2020-05-28 00:50:37 -07:00
parent 08ea64bdc5
commit e1f1cf0ed9
20 changed files with 284 additions and 235 deletions

View File

@ -4132,16 +4132,16 @@
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c",
"assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ca/db/9c7cfd4e4f4a1f7a7adc980bca42",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4e/0b/231e38ff29d932df7552050891c5",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f2/56/bb316ec28ee98ece5c0c3a04b77f",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ef/92/d787c99db6cc85f70b7131ff2c0c",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/97/b9/9c6c3c90f10d319250a9f3d287b3",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f0/2a/60bdf1c4d4e13bdbb5f4df121e3e",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/66/25/79ea606983dc91ac0cd79c1e7da6",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/70/c6/0ab2cdf222ffcadade37dd3b8462",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/3c/65/450a67dab189c0832b6bf28a9e9c",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7e/a9/e1ab6defb8bcf536dff46d0c62b2",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ab/fc/d00336dae2b1c7323b31518b52aa",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ed/98/dbea1af1da83bfa1a3283175b234"
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/95/a8/318c6db7a9c94989c601f9388211",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6c/53/dda3c28a824749358279d01d85a6",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/cb/5e/04efb608c6a0d3fc80baee7f2c0e",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/91/74/c92748de53d860aa936f969c4699",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b9/a0/202236991664bc72a33affee2911",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b3/1a/7f626564f3659f4cbd00d62cbd5a",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/94/b8/4f2c26af58e4386d58f2de2b8f3f",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8e/f3/40da5e70872c27cb1716a0e7bc10",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/a0/a9/9ca7e5a2a62c7198f6dccf29a1e2",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/54/e7/fd4f9d4af81fed229a1fd2d486f1",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/54/5f/90a09221a6cb5ca24cf2a6b0f4e7",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c2/14/82ced0d7340340cd09a73987c82f"
}

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)
# SOURCES_HASH=265401783818737452594582363319036908124
# SOURCES_HASH=317793613698101603244532998583646381571
# I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression
@ -2310,21 +2310,31 @@ def get_ui_input_device() -> ba.InputDevice:
return ba.InputDevice()
def getactivity(doraise: bool = True) -> ba.Activity:
"""getactivity(doraise: bool = True) -> ba.Activity
# Show that our return type varies based on "doraise" value:
@overload
def getactivity(doraise: Literal[True] = True) -> ba.Activity:
...
Returns the current ba.Activity instance.
@overload
def getactivity(doraise: Literal[False]) -> Optional[ba.Activity]:
...
def getactivity(doraise: bool = True) -> Optional[ba.Activity]:
"""getactivity(doraise: bool = True) -> <varies>
Return the current ba.Activity instance.
Category: Gameplay Functions
Note that this is based on context; thus code run in a timer generated
in Activity 'foo' will properly return 'foo' here, even if another
Activity has since been created or is transitioning in.
If there is no current Activity an Exception is raised, or if doraise is
False then None is returned instead.
If there is no current Activity, raises a ba.ActivityNotFoundError.
If doraise is False, None will be returned instead in that case.
"""
import ba # pylint: disable=cyclic-import
return ba.Activity(settings={})
return None
def getcollidemodel(name: str) -> ba.CollideModel:
@ -2422,8 +2432,19 @@ def getnodes() -> list:
return list()
def getsession(doraise: bool = True) -> ba.Session:
"""getsession(doraise: bool = True) -> ba.Session
# Show that our return type varies based on "doraise" value:
@overload
def getsession(doraise: Literal[True] = True) -> ba.Session:
...
@overload
def getsession(doraise: Literal[False]) -> Optional[ba.Session]:
...
def getsession(doraise: bool = True) -> Optional[ba.Session]:
"""getsession(doraise: bool = True) -> <varies>
Category: Gameplay Functions
@ -2433,8 +2454,7 @@ def getsession(doraise: bool = True) -> ba.Session:
exists, etc. If there is no current Session, an Exception is raised, or
if doraise is False then None is returned instead.
"""
import ba # pylint: disable=cyclic-import
return ba.Session([])
return None
def getsound(name: str) -> ba.Sound:

View File

@ -39,7 +39,7 @@ from _ba import (CollideModel, Context, ContextCall, Data, InputDevice,
open_url, widget)
from ba._activity import Activity
from ba._actor import Actor
from ba._player import Player, playercast, playercast_o
from ba._player import PlayerInfo, Player, playercast, playercast_o
from ba._nodeactor import NodeActor
from ba._app import App
from ba._coopgame import CoopGameActivity
@ -47,13 +47,12 @@ from ba._coopsession import CoopSession
from ba._dependency import (Dependency, DependencyComponent, DependencySet,
AssetPackage)
from ba._enums import TimeType, Permission, TimeFormat, SpecialChar
from ba._error import (print_exception, print_error, NotFoundError,
PlayerNotFoundError, SessionPlayerNotFoundError,
NodeNotFoundError, ActorNotFoundError,
InputDeviceNotFoundError, WidgetNotFoundError,
ActivityNotFoundError, TeamNotFoundError,
SessionTeamNotFoundError, SessionNotFoundError,
DelegateNotFoundError, DependencyError)
from ba._error import (
print_exception, print_error, ContextError, NotFoundError,
PlayerNotFoundError, SessionPlayerNotFoundError, NodeNotFoundError,
ActorNotFoundError, InputDeviceNotFoundError, WidgetNotFoundError,
ActivityNotFoundError, TeamNotFoundError, SessionTeamNotFoundError,
SessionNotFoundError, DelegateNotFoundError, DependencyError)
from ba._freeforallsession import FreeForAllSession
from ba._gameactivity import GameActivity
from ba._gameresults import TeamGameResults

View File

@ -373,6 +373,7 @@ class Achievement:
# pylint: disable=cyclic-import
from ba._lang import Lstr
from ba._enums import SpecialChar
from ba._coopsession import CoopSession
from bastd.actor.image import Image
from bastd.actor.text import Text
@ -404,12 +405,16 @@ class Achievement:
hmo = False
else:
try:
campaign = _ba.getsession().campaign
assert campaign is not None
hmo = (self._hard_mode_only and campaign.name == 'Easy')
session = _ba.getsession()
if isinstance(session, CoopSession):
campaign = session.campaign
assert campaign is not None
hmo = (self._hard_mode_only and campaign.name == 'Easy')
else:
hmo = False
except Exception:
from ba import _error
_error.print_exception('unable to determine campaign')
_error.print_exception('Error determining campaign')
hmo = False
objs: List[ba.Actor]
@ -678,7 +683,7 @@ class Achievement:
# Just piggy-back onto any current activity
# (should we use the session instead?..)
activity: Optional[ba.Activity] = _ba.getactivity(doraise=False)
activity = _ba.getactivity(doraise=False)
# If this gets called while this achievement is occupying a slot
# already, ignore it. (probably should never happen in real

View File

@ -351,7 +351,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
raise TypeError('non-actor passed to add_actor_weak_ref')
if (self.has_transitioned_in()
and _ba.time() - self._last_prune_dead_actors_time > 10.0):
print_error('it looks like nodes/actors are '
print_error('It looks like nodes/actors are '
'not being pruned in your activity;'
' did you call Activity.on_transition_in()'
' from your subclass?; ' + str(self) + ' (loc. b)')

View File

@ -73,7 +73,6 @@ def suppress_debug_reports() -> None:
This should be called in devel/debug situations to avoid spamming
the master server with spurious logs.
"""
# _ba.screenmessage("Suppressing debug reports.", color=(1, 0, 0))
_ba.app.suppress_debug_reports = True
@ -189,21 +188,23 @@ def print_live_object_warnings(when: Any,
"""Print warnings for remaining objects in the current context."""
# pylint: disable=cyclic-import
import gc
from ba import _session as bs_session
from ba import _actor as bs_actor
from ba import _activity as bs_activity
from ba._session import Session
from ba._actor import Actor
from ba._activity import Activity
sessions: List[ba.Session] = []
activities: List[ba.Activity] = []
actors = []
actors: List[ba.Actor] = []
# Once we come across leaked stuff, printing again is probably
# redundant.
if _ba.app.printed_live_object_warning:
# print 'skipping live obj check due to previous found live object(s)'
return
for obj in gc.get_objects():
if isinstance(obj, bs_actor.Actor):
if isinstance(obj, Actor):
actors.append(obj)
elif isinstance(obj, bs_session.Session):
elif isinstance(obj, Session):
sessions.append(obj)
elif isinstance(obj, bs_activity.Activity):
elif isinstance(obj, Activity):
activities.append(obj)
# Complain about any remaining sessions.
@ -211,66 +212,19 @@ def print_live_object_warnings(when: Any,
if session is ignore_session:
continue
_ba.app.printed_live_object_warning = True
print('ERROR: Session found', when, ':', session)
# refs = list(gc.get_referrers(session))
# i = 1
# for ref in refs:
# if type(ref) is types.FrameType: continue
# print ' ref', i, ':', ref
# i += 1
# if type(ref) is list or type(ref) is tuple or type(ref) is dict:
# refs2 = list(gc.get_referrers(ref))
# j = 1
# for ref2 in refs2:
# if type(ref2) is types.FrameType: continue
# print ' ref\'s ref', j, ':', ref2
# j += 1
print(f'ERROR: Session found {when}: {session}')
# Complain about any remaining activities.
for activity in activities:
if activity is ignore_activity:
continue
_ba.app.printed_live_object_warning = True
print('ERROR: Activity found', when, ':', activity)
# refs = list(gc.get_referrers(activity))
# i = 1
# for ref in refs:
# if type(ref) is types.FrameType: continue
# print ' ref', i, ':', ref
# i += 1
# if type(ref) is list or type(ref) is tuple or type(ref) is dict:
# refs2 = list(gc.get_referrers(ref))
# j = 1
# for ref2 in refs2:
# if type(ref2) is types.FrameType: continue
# print ' ref\'s ref', j, ':', ref2
# j += 1
print(f'ERROR: Activity found {when}: {activity}')
# Complain about any remaining actors.
for actor in actors:
_ba.app.printed_live_object_warning = True
print('ERROR: Actor found', when, ':', actor)
# if isinstance(actor, bs_actor.Actor):
# try:
# if actor.node:
# print(' - contains node:',
# actor.node.getnodetype(), ';',
# actor.node.get_name())
# except Exception as exc:
# print(' - exception checking actor node:', exc)
# refs = list(gc.get_referrers(actor))
# i = 1
# for ref in refs:
# if type(ref) is types.FrameType: continue
# print ' ref', i, ':', ref
# i += 1
# if type(ref) is list or type(ref) is tuple or type(ref) is dict:
# refs2 = list(gc.get_referrers(ref))
# j = 1
# for ref2 in refs2:
# if type(ref2) is types.FrameType: continue
# print ' ref\'s ref', j, ':', ref2
# j += 1
print(f'ERROR: Actor found {when}: {actor}')
def print_corrupt_file_error() -> None:

View File

@ -25,6 +25,7 @@ from typing import TYPE_CHECKING, TypeVar
import _ba
from ba._gameactivity import GameActivity
from ba._general import WeakCall
if TYPE_CHECKING:
from typing import Type, Dict, Any, Set, List, Sequence, Optional
@ -41,10 +42,13 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
Category: Gameplay Classes
"""
# We can assume our session is a CoopSession.
session: ba.CoopSession
@classmethod
def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
from ba import _coopsession
return issubclass(sessiontype, _coopsession.CoopSession)
from ba._coopsession import CoopSession
return issubclass(sessiontype, CoopSession)
def __init__(self, settings: Dict[str, Any]):
super().__init__(settings)
@ -57,16 +61,14 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
self._warn_beeps_sound = _ba.getsound('warnBeeps')
def on_begin(self) -> None:
from ba import _general
super().on_begin()
# Show achievements remaining.
if not _ba.app.kiosk_mode:
_ba.timer(3.8,
_general.WeakCall(self._show_remaining_achievements))
_ba.timer(3.8, WeakCall(self._show_remaining_achievements))
# Preload achievement images in case we get some.
_ba.timer(2.0, _general.WeakCall(self._preload_achievements))
_ba.timer(2.0, WeakCall(self._preload_achievements))
# Let's ask the server for a 'time-to-beat' value.
levelname = self._get_coop_level_name()
@ -76,7 +78,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
self.settings_raw['name']).get_score_version_string().replace(
' ', '_'))
_ba.get_scores_to_beat(levelname, config_str,
_general.WeakCall(self._on_got_scores_to_beat))
WeakCall(self._on_got_scores_to_beat))
def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None:
pass
@ -153,18 +155,18 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
def _show_remaining_achievements(self) -> None:
# pylint: disable=cyclic-import
from ba import _achievement
from ba import _lang
from ba._achievement import get_achievements_for_coop_level
from ba._lang import Lstr
from bastd.actor.text import Text
ts_h_offs = 30
v_offs = -200
achievements = [
a for a in _achievement.get_achievements_for_coop_level(
a for a in get_achievements_for_coop_level(
self._get_coop_level_name()) if not a.complete
]
vrmode = _ba.app.vr_mode
if achievements:
Text(_lang.Lstr(resource='achievementsRemainingText'),
Text(Lstr(resource='achievementsRemainingText'),
host_only=True,
position=(ts_h_offs - 10 + 40, v_offs - 10),
transition=Text.Transition.FADE_IN,
@ -208,12 +210,12 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
Returns True if a banner will be shown;
False otherwise
"""
from ba import _achievement
from ba._achievement import get_achievement
if achievement_name in self._achievements_awarded:
return
ach = _achievement.get_achievement(achievement_name)
ach = get_achievement(achievement_name)
# If we're in the easy campaign and this achievement is hard-mode-only,
# ignore it.
@ -223,8 +225,8 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
if ach.hard_mode_only and campaign.name == 'Easy':
return
except Exception:
from ba import _error
_error.print_exception()
from ba._error import print_exception
print_exception()
# If we haven't awarded this one, check to see if we've got it.
# If not, set it through the game service *and* add a transaction
@ -261,10 +263,9 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
def setup_low_life_warning_sound(self) -> None:
"""Set up a beeping noise to play when any players are near death."""
from ba import _general
self._life_warning_beep = None
self._life_warning_beep_timer = _ba.Timer(
1.0, _general.WeakCall(self._update_life_warning), repeat=True)
1.0, WeakCall(self._update_life_warning), repeat=True)
def _update_life_warning(self) -> None:
# Beep continuously if anyone is close to death.

View File

@ -42,11 +42,21 @@ class CoopSession(Session):
These generally consist of 1-4 players against
the computer and include functionality such as
high score lists.
Attrs:
campaign
The ba.Campaign instance this Session represents, or None if
there is no associated Campaign.
"""
use_teams = True
use_team_colors = False
allow_mid_activity_joins = False
# Note: even though these are instance vars, we annotate them at the
# class level so that docs generation can access their types.
campaign: Optional[ba.Campaign]
def __init__(self) -> None:
"""Instantiate a co-op mode session."""
# pylint: disable=cyclic-import
@ -77,16 +87,11 @@ class CoopSession(Session):
max_players=max_players)
# Tournament-ID if we correspond to a co-op tournament (otherwise None)
self.tournament_id = (app.coop_session_args['tournament_id']
if 'tournament_id' in app.coop_session_args else
None)
self.tournament_id: Optional[str] = (
app.coop_session_args.get('tournament_id'))
# FIXME: Could be nice to pass this in as actual args.
self.campaign_state = {
'campaign': (app.coop_session_args['campaign']),
'level': app.coop_session_args['level']
}
self.campaign = get_campaign(self.campaign_state['campaign'])
self.campaign = get_campaign(app.coop_session_args['campaign'])
self.campaign_level_name: str = app.coop_session_args['level']
self._ran_tutorial_activity = False
self._tutorial_activity: Optional[ba.Activity] = None
@ -96,7 +101,7 @@ class CoopSession(Session):
self.set_activity(_ba.new_activity(CoopJoinActivity))
self._next_game_instance: Optional[ba.GameActivity] = None
self._next_game_name: Optional[str] = None
self._next_game_level_name: Optional[str] = None
self._update_on_deck_game_instances()
def get_current_game_instance(self) -> ba.GameActivity:
@ -107,12 +112,11 @@ class CoopSession(Session):
# pylint: disable=cyclic-import
from ba._gameactivity import GameActivity
# Instantiates levels we might be running soon
# so they have time to load.
# Instantiate levels we may be running soon to let them load in the bg.
# Build an instance for the current level.
assert self.campaign is not None
level = self.campaign.get_level(self.campaign_state['level'])
level = self.campaign.get_level(self.campaign_level_name)
gametype = level.gametype
settings = level.get_settings()
@ -128,7 +132,7 @@ class CoopSession(Session):
# Find the next level and build an instance for it too.
levels = self.campaign.get_levels()
level = self.campaign.get_level(self.campaign_state['level'])
level = self.campaign.get_level(self.campaign_level_name)
nextlevel: Optional[ba.Level]
if level.index < len(levels) - 1:
@ -149,15 +153,15 @@ class CoopSession(Session):
newactivity = _ba.new_activity(gametype, settings)
assert isinstance(newactivity, GameActivity)
self._next_game_instance = newactivity
self._next_game_name = nextlevel.name
self._next_game_level_name = nextlevel.name
else:
self._next_game_instance = None
self._next_game_name = None
self._next_game_level_name = None
# Special case:
# If our current level is 'onslaught training', instantiate
# our tutorial so its ready to go. (if we haven't run it yet).
if (self.campaign_state['level'] == 'Onslaught Training'
if (self.campaign_level_name == 'Onslaught Training'
and self._tutorial_activity is None
and not self._ran_tutorial_activity):
from bastd.tutorial import TutorialActivity
@ -248,6 +252,7 @@ class CoopSession(Session):
from ba._coopgame import CoopGameActivity
from ba._gameresults import TeamGameResults
from ba._score import ScoreType
from ba._player import PlayerInfo
from bastd.tutorial import TutorialActivity
from bastd.activity.coopscore import CoopScoreScreen
@ -278,9 +283,9 @@ class CoopSession(Session):
if outcome == 'next_level':
if self._next_game_instance is None:
raise Exception()
assert self._next_game_name is not None
self.campaign_state['level'] = self._next_game_name
raise RuntimeError()
assert self._next_game_level_name is not None
self.campaign_level_name = self._next_game_level_name
next_game = self._next_game_instance
else:
next_game = self._current_game_instance
@ -289,10 +294,10 @@ class CoopSession(Session):
# and will be going into onslaught-training, show the
# tutorial first.
if (isinstance(activity, JoinActivity)
and self.campaign_state['level'] == 'Onslaught Training'
and self.campaign_level_name == 'Onslaught Training'
and not app.kiosk_mode):
if self._tutorial_activity is None:
raise RuntimeError('tutorial not preloaded properly')
raise RuntimeError('Tutorial not preloaded properly.')
self.set_activity(self._tutorial_activity)
self._tutorial_activity = None
self._ran_tutorial_activity = True
@ -336,6 +341,8 @@ class CoopSession(Session):
self.set_activity(_ba.new_activity(TransitionActivity))
else:
player_info: List[ba.PlayerInfo]
# Generic team games.
if isinstance(results, TeamGameResults):
player_info = results.get_player_info()
@ -364,8 +371,7 @@ class CoopSession(Session):
# Old coop-game-specific results; should migrate away from these.
else:
player_info = (results['player_info']
if 'player_info' in results else None)
player_info = results.get('player_info')
score = results['score'] if 'score' in results else None
fail_message = (results['fail_message']
if 'fail_message' in results else None)
@ -376,6 +382,11 @@ class CoopSession(Session):
assert activity_score_type is not None
score_type = activity_score_type
# Validate types.
if player_info is not None:
assert isinstance(player_info, list)
assert (isinstance(i, PlayerInfo) for i in player_info)
# Looks like we were in a round - check the outcome and
# go from there.
if outcome == 'restart':
@ -393,7 +404,7 @@ class CoopSession(Session):
'score_type': score_type,
'outcome': outcome,
'campaign': self.campaign,
'level': self.campaign_state['level']
'level': self.campaign_level_name
}))
# No matter what, get the next 2 levels ready to go.

View File

@ -49,6 +49,16 @@ class DependencyError(Exception):
return self._deps
class ContextError(Exception):
"""Exception raised when a call is made in an invalid context.
category: Exception Classes
Examples of this include calling UI functions within an Activity context
or calling scene manipulation functions outside of a game context.
"""
class NotFoundError(Exception):
"""Exception raised when a referenced object does not exist.

View File

@ -30,6 +30,7 @@ from ba._activity import Activity
from ba._score import ScoreInfo
from ba._lang import Lstr
from ba._messages import PlayerDiedMessage
from ba._error import NotFoundError, print_error, print_exception
import _ba
if TYPE_CHECKING:
@ -273,15 +274,18 @@ class GameActivity(Activity[PlayerType, TeamType]):
# Set some defaults.
self.allow_pausing = True
self.allow_kick_idle_players = True
self._spawn_sound = _ba.getsound('spawn')
# Whether to show points for kills.
self._show_kill_points = True
self.show_kill_points = True
# If not None, the music type that should play in on_transition_in()
# (unless overridden by the map).
self.default_music: Optional[ba.MusicType] = None
# Holds some flattened info about the player set at the point
# when on_begin() is called.
self.initial_player_info: Optional[List[ba.PlayerInfo]] = None
# Go ahead and get our map loading.
map_name: str
if 'map' in settings:
@ -298,13 +302,13 @@ class GameActivity(Activity[PlayerType, TeamType]):
_ba.screenmessage(Lstr(resource='noValidMapsErrorText'))
raise Exception('No valid maps')
map_name = valid_maps[random.randrange(len(valid_maps))]
self._spawn_sound = _ba.getsound('spawn')
self._map_type = _map.get_map_class(map_name)
self._map_type.preload()
self._map: Optional[ba.Map] = None
self._powerup_drop_timer: Optional[ba.Timer] = None
self._tnt_spawners: Optional[Dict[int, TNTSpawner]] = None
self._tnt_drop_timer: Optional[ba.Timer] = None
self.initial_player_info: Optional[List[Dict[str, Any]]] = None
self._game_scoreboard_name_text: Optional[ba.Actor] = None
self._game_scoreboard_description_text: Optional[ba.Actor] = None
self._standard_time_limit_time: Optional[int] = None
@ -333,7 +337,6 @@ class GameActivity(Activity[PlayerType, TeamType]):
Raises a ba.NotFoundError if the map does not currently exist.
"""
if self._map is None:
from ba._error import NotFoundError
raise NotFoundError
return self._map
@ -355,10 +358,9 @@ class GameActivity(Activity[PlayerType, TeamType]):
campaign = self.session.campaign
assert campaign is not None
return campaign.get_level(
self.session.campaign_state['level']).displayname
self.session.campaign_level_name).displayname
except Exception:
from ba import _error
_error.print_error('error getting campaign level name')
print_error('error getting campaign level name')
return self.get_instance_display_string()
def get_instance_description(self) -> Union[str, Sequence]:
@ -415,19 +417,15 @@ class GameActivity(Activity[PlayerType, TeamType]):
return ''
def on_transition_in(self) -> None:
super().on_transition_in()
# Make our map.
self._map = self._map_type()
music = self.default_music
# give our map a chance to override the music
# (for happy-thoughts and other such themed maps)
override_music = self._map_type.get_music_type()
if override_music is not None:
music = override_music
# Give our map a chance to override the music
# (for happy-thoughts and other such themed maps).
map_music = self._map_type.get_music_type()
music = map_music if map_music is not None else self.default_music
if music is not None:
from ba import _music
@ -470,9 +468,9 @@ class GameActivity(Activity[PlayerType, TeamType]):
and calls either end_game or continue_game depending on the result"""
# pylint: disable=too-many-nested-blocks
# pylint: disable=cyclic-import
from bastd.ui import continues
from ba import _gameutils
from ba import _general
from bastd.ui.continues import ContinuesWindow
from ba._gameutils import sharedobj
from ba._general import WeakCall
from ba._coopsession import CoopSession
from ba._enums import TimeType
@ -490,7 +488,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
if isinstance(session, CoopSession):
assert session.campaign is not None
if session.campaign.sequential:
gnode = _gameutils.sharedobj('globals')
gnode = sharedobj('globals')
# Only attempt this if we're not currently paused
# and there appears to be no UI.
@ -501,19 +499,18 @@ class GameActivity(Activity[PlayerType, TeamType]):
with _ba.Context('ui'):
_ba.timer(
0.5,
lambda: continues.ContinuesWindow(
lambda: ContinuesWindow(
self,
self._continue_cost,
continue_call=_general.WeakCall(
continue_call=WeakCall(
self._continue_choice, True),
cancel_call=_general.WeakCall(
cancel_call=WeakCall(
self._continue_choice, False)),
timetype=TimeType.REAL)
return
except Exception:
from ba import _error
_error.print_exception('error continuing game')
print_exception('error continuing game')
self.end_game()
@ -525,8 +522,8 @@ class GameActivity(Activity[PlayerType, TeamType]):
from ba._freeforallsession import FreeForAllSession
from ba._coopsession import CoopSession
session = self.session
campaign = session.campaign
if isinstance(session, CoopSession):
campaign = session.campaign
assert campaign is not None
_ba.set_analytics_screen(
'Coop Game: ' + campaign.name + ' ' +
@ -576,13 +573,13 @@ class GameActivity(Activity[PlayerType, TeamType]):
def on_begin(self) -> None:
from ba._general import WeakCall
from ba._player import PlayerInfo
super().on_begin()
try:
self._game_begin_analytics()
except Exception:
from ba import _error
_error.print_exception('error in game-begin-analytics')
print_exception('error in game-begin-analytics')
# We don't do this in on_transition_in because it may depend on
# players/teams which aren't available until now.
@ -591,26 +588,27 @@ class GameActivity(Activity[PlayerType, TeamType]):
_ba.timer(2.5, WeakCall(self._show_tip))
# Store some basic info about players present at start time.
self.initial_player_info = [{
'name': p.get_name(full=True),
'character': p.character
} for p in self.players]
self.initial_player_info = [
PlayerInfo(name=p.get_name(full=True), character=p.character)
for p in self.players
]
# Sort this by name so high score lists/etc will be consistent
# regardless of player join order.
self.initial_player_info.sort(key=lambda x: x['name'])
self.initial_player_info.sort(key=lambda x: x.name)
# If this is a tournament, query info about it such as how much
# time is left.
tournament_id = self.session.tournament_id
if tournament_id is not None:
_ba.tournament_query(args={
'tournamentIDs': [tournament_id],
'source': 'in-game time remaining query'
},
callback=WeakCall(
self._on_tournament_query_response))
_ba.tournament_query(
args={
'tournamentIDs': [tournament_id],
'source': 'in-game time remaining query'
},
callback=WeakCall(self._on_tournament_query_response),
)
def _on_tournament_query_response(self, data: Optional[Dict[str,
Any]]) -> None:
@ -670,7 +668,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
kill=True,
victim_player=player,
importance=importance,
showpoints=self._show_kill_points)
showpoints=self.show_kill_points)
def show_scoreboard_info(self) -> None:
"""Create the game info display.
@ -916,19 +914,19 @@ class GameActivity(Activity[PlayerType, TeamType]):
force: bool = False) -> None:
from ba._gameresults import TeamGameResults
# if results is a standard team-game-results, associate it with us
# so it can grab our score prefs
# If results is a standard team-game-results, associate it with us
# so it can grab our score prefs.
if isinstance(results, TeamGameResults):
results.set_game(self)
# if we had a standard time-limit that had not expired, stop it so
# it doesnt tick annoyingly
# If we had a standard time-limit that had not expired, stop it so
# it doesnt tick annoyingly.
if (self._standard_time_limit_time is not None
and self._standard_time_limit_time > 0):
self._standard_time_limit_timer = None
self._standard_time_limit_text = None
# ditto with tournament time limits
# Ditto with tournament time limits.
if (self._tournament_time_limit is not None
and self._tournament_time_limit > 0):
self._tournament_time_limit_timer = None

View File

@ -57,7 +57,7 @@ class TeamGameResults:
self._scores: Dict[int, Tuple[ReferenceType[ba.SessionTeam],
Optional[int]]] = {}
self._teams: Optional[List[ReferenceType[ba.SessionTeam]]] = None
self._player_info: Optional[List[Dict[str, Any]]] = None
self._player_info: Optional[List[ba.PlayerInfo]] = None
self._lower_is_better: Optional[bool] = None
self._score_label: Optional[str] = None
self._none_is_winner: Optional[bool] = None
@ -141,7 +141,7 @@ class TeamGameResults:
return Lstr(value=str(score[1]))
return Lstr(value='-')
def get_player_info(self) -> List[Dict[str, Any]]:
def get_player_info(self) -> List[ba.PlayerInfo]:
"""Get info about the players represented by the results."""
if not self._game_set:
raise RuntimeError("Can't get player-info until game is set.")

View File

@ -97,7 +97,7 @@ def sharedobj(name: str) -> Any:
# We store these on the current context; whether its an activity or
# session.
activity: Optional[ba.Activity] = _ba.getactivity(doraise=False)
activity = _ba.getactivity(doraise=False)
if activity is not None:
# Grab shared-objs dict.
@ -105,8 +105,8 @@ def sharedobj(name: str) -> Any:
# Grab item out of it.
try:
return sharedobjs[name]
except Exception:
return sharedobjs[name] # (pylint bug?) pylint: disable=E1136
except KeyError:
pass
obj: Any
@ -141,7 +141,7 @@ def sharedobj(name: str) -> Any:
"unrecognized shared object (activity context): '" + name +
"'")
else:
session: Optional[ba.Session] = _ba.getsession(doraise=False)
session = _ba.getsession(doraise=False)
if session is not None:
# Grab shared-objs dict (creating if necessary).

View File

@ -83,7 +83,7 @@ class ServerCallThread(threading.Thread):
self._context = _ba.Context('current')
# Save and restore the context we were created from.
activity: Optional[ba.Activity] = _ba.getactivity(doraise=False)
activity = _ba.getactivity(doraise=False)
self._activity = weakref.ref(
activity) if activity is not None else None

View File

@ -22,6 +22,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, TypeVar, Generic
import _ba
@ -35,6 +36,16 @@ PlayerType = TypeVar('PlayerType', bound='ba.Player')
TeamType = TypeVar('TeamType', bound='ba.Team')
@dataclass
class PlayerInfo:
"""Holds basic info about a player.
Category: Gameplay Classes
"""
name: str
character: str
class Player(Generic[TeamType]):
"""A player in a specific ba.Activity.

View File

@ -70,10 +70,6 @@ class Session:
Be aware this value may be None if a Session does not allow
any such selection.
campaign
The ba.Campaign instance this Session represents, or None if
there is no associated Campaign.
use_teams
Whether this session groups players into an explicit set of
teams. If this is off, a unique team is generated for each
@ -95,7 +91,6 @@ class Session:
# Note: even though these are instance vars, we annotate them at the
# class level so that docs generation can access their types.
campaign: Optional[ba.Campaign]
lobby: ba.Lobby
max_players: int
min_players: int
@ -161,30 +156,28 @@ class Session:
# print('Would set host-session asset-reqs to:',
# required_asset_packages)
# Stuff in this section should be removed from this class if possible.
# Init our C++ layer data.
self._sessiondata = _ba.register_session(self)
# Stuff in this section should be removed from this class if possible.
self.tournament_id: Optional[str] = None
self.sharedobjs: Dict[str, Any] = {}
self.have_shown_controls_help_overlay = False
self.campaign = None
self.campaign_state: Dict[str, str] = {}
self.teams = []
self.players = []
self.min_players = min_players
self.max_players = max_players
self._in_set_activity = False
self._next_team_id = 0
self._activity_retained: Optional[ba.Activity] = None
self._launch_end_session_activity_time: Optional[float] = None
self._activity_end_timer: Optional[ba.Timer] = None
self._activity_weak = empty_weakref(Activity)
if self._activity_weak() is not None:
raise Exception('Error creating empty activity weak ref.')
self._next_activity: Optional[ba.Activity] = None
self.wants_to_end = False
self._ending = False
self.min_players = min_players
self.max_players = max_players
# Create static teams if we're using them.
if self.use_teams:

View File

@ -35,17 +35,21 @@ if TYPE_CHECKING:
class CoopJoinActivity(JoinActivity):
"""Join-screen for co-op mode."""
# We can assume our session is a CoopSession.
session: ba.CoopSession
def __init__(self, settings: Dict[str, Any]):
super().__init__(settings)
session = ba.getsession()
session = self.session
assert isinstance(session, ba.CoopSession)
# Let's show a list of scores-to-beat for 1 player at least.
assert session.campaign is not None
level_name_full = (session.campaign.name + ':' +
session.campaign_state['level'])
config_str = (
'1p' + session.campaign.get_level(session.campaign_state['level']).
get_score_version_string().replace(' ', '_'))
session.campaign_level_name)
config_str = ('1p' + session.campaign.get_level(
session.campaign_level_name).get_score_version_string().replace(
' ', '_'))
_ba.get_scores_to_beat(level_name_full, config_str,
ba.WeakCall(self._on_got_scores_to_beat))
@ -53,9 +57,10 @@ class CoopJoinActivity(JoinActivity):
from bastd.actor.controlsguide import ControlsGuide
from bastd.actor.text import Text
super().on_transition_in()
assert isinstance(self.session, ba.CoopSession)
assert self.session.campaign
Text(self.session.campaign.get_level(
self.session.campaign_state['level']).displayname,
self.session.campaign_level_name).displayname,
scale=1.3,
h_attach=Text.HAttach.CENTER,
h_align=Text.HAlign.CENTER,
@ -163,8 +168,9 @@ class CoopJoinActivity(JoinActivity):
# Now list our remaining achievements for this level.
assert self.session.campaign is not None
assert isinstance(self.session, ba.CoopSession)
levelname = (self.session.campaign.name + ':' +
self.session.campaign_state['level'])
self.session.campaign_level_name)
ts_h_offs = 60
if not ba.app.kiosk_mode:

View File

@ -137,7 +137,10 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._tournament_time_remaining_text: Optional[Text] = None
self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None
self._player_info = settings['player_info']
self._player_info: List[ba.PlayerInfo] = settings['player_info']
assert isinstance(self._player_info, list)
assert (isinstance(i, ba.PlayerInfo) for i in self._player_info)
self._score: Optional[int] = settings['score']
assert isinstance(self._score, (int, type(None)))
@ -633,7 +636,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ba.pushcall(ba.WeakCall(self._show_fail))
self._name_str = name_str = ', '.join(
[p['name'] for p in self._player_info])
[p.name for p in self._player_info])
if self._show_friend_scores:
self._friends_loading_status = Text(
@ -668,7 +671,10 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
if self._score is not None:
our_score: Optional[list] = [
self._score, {
'players': self._player_info
'players': [{
'name': p.name,
'character': p.character
} for p in self._player_info]
}
]
our_high_scores.append(our_score)

View File

@ -37,6 +37,8 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
def __init__(self, settings: Dict[str, Any]):
super().__init__(settings=settings)
self._winner: ba.SessionTeam = settings['winner']
assert isinstance(self._winner, ba.SessionTeam)
def on_begin(self) -> None:
from ba.deprecated import get_resource
@ -80,7 +82,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
i * 0.2, shift_time - (i * 0.150 + 0.150)))
ba.timer(i * 0.150 + 0.5,
ba.Call(ba.playsound, self._score_display_sound_small))
scored = (team is self.settings_raw['winner'])
scored = (team is self._winner)
delay = 0.2
if scored:
delay = 1.2

View File

@ -999,7 +999,7 @@ class SpazBotSet:
"""Immediately clear out any bots in the set."""
# Don't do this if the activity is shutting down or dead.
activity: Optional[ba.Activity] = ba.getactivity(doraise=False)
activity = ba.getactivity(doraise=False)
if activity is None or activity.expired:
return

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-05-27 for Ballistica version 1.5.0 build 20030</em></h4>
<h4><em>last updated on 2020-05-28 for Ballistica version 1.5.0 build 20031</em></h4>
<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>
<hr>
@ -26,6 +26,7 @@
<li><a href="#class_ba_Material">ba.Material</a></li>
<li><a href="#class_ba_Node">ba.Node</a></li>
<li><a href="#class_ba_Player">ba.Player</a></li>
<li><a href="#class_ba_PlayerInfo">ba.PlayerInfo</a></li>
<li><a href="#class_ba_PlayerRecord">ba.PlayerRecord</a></li>
<li><a href="#class_ba_ScoreInfo">ba.ScoreInfo</a></li>
<li><a href="#class_ba_Session">ba.Session</a></li>
@ -185,6 +186,7 @@
</ul>
<h4><a name="class_category_Exception_Classes">Exception Classes</a></h4>
<ul>
<li><a href="#class_ba_ContextError">ba.ContextError</a></li>
<li><a href="#class_ba_DependencyError">ba.DependencyError</a></li>
<li><a href="#class_ba_NotFoundError">ba.NotFoundError</a></li>
<ul>
@ -1514,6 +1516,19 @@ start_some_long_action(callback_when_done=self.dosomething)</pre>
<span><em><small># to our activity.</small></em></span>
start_long_action(callback_when_done=<a href="#class_ba_ContextCall">ba.ContextCall</a>(self.mycallback))</pre>
<hr>
<h2><strong><a name="class_ba_ContextError">ba.ContextError</a></strong></h3>
<p>Inherits from: Exception, BaseException</p>
<p>Exception raised when a call is made in an invalid context.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a></p>
<p> Examples of this include calling UI functions within an Activity context
or calling scene manipulation functions outside of a game context.
</p>
<h3>Methods:</h3>
<p>&lt;all methods inherited from <a href="#class_builtins_Exception">builtins.Exception</a>&gt;</p>
<hr>
<h2><strong><a name="class_ba_CoopGameActivity">ba.CoopGameActivity</a></strong></h3>
<p>Inherits from: <a href="#class_ba_GameActivity">ba.GameActivity</a>, <a href="#class_ba_Activity">ba.Activity</a>, <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="#class_typing_Generic">typing.Generic</a></p>
@ -1639,11 +1654,19 @@ and it should begin its actual game logic.</p>
<p> These generally consist of 1-4 players against
the computer and include functionality such as
high score lists.
</p>
high score lists.</p>
<h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h3>Attributes Defined Here:</h3>
<dl>
<dt><h4><a name="attr_ba_CoopSession__campaign">campaign</a></h4></dt><dd>
<p><span>Optional[<a href="#class_ba_Campaign">ba.Campaign</a>]</span></p>
<p>The <a href="#class_ba_Campaign">ba.Campaign</a> instance this Session represents, or None if
there is no associated Campaign.</p>
</dd>
</dl>
<h3>Methods Inherited:</h3>
<h5><a href="#method_ba_Session__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_Session__end">end()</a>, <a href="#method_ba_Session__end_activity">end_activity()</a>, <a href="#method_ba_Session__getactivity">getactivity()</a>, <a href="#method_ba_Session__handlemessage">handlemessage()</a>, <a href="#method_ba_Session__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_Session__on_player_request">on_player_request()</a>, <a href="#method_ba_Session__on_team_join">on_team_join()</a>, <a href="#method_ba_Session__on_team_leave">on_team_leave()</a>, <a href="#method_ba_Session__set_activity">set_activity()</a>, <a href="#method_ba_Session__transitioning_out_activity_was_freed">transitioning_out_activity_was_freed()</a></h5>
<h3>Methods Defined or Overridden:</h3>
@ -1986,7 +2009,7 @@ its time with lingering corpses, sound effects, etc.</p>
</p>
<h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h3>Methods Inherited:</h3>
<h5><a href="#method_ba_MultiTeamSession__announce_game_results">announce_game_results()</a>, <a href="#method_ba_MultiTeamSession__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_MultiTeamSession__end">end()</a>, <a href="#method_ba_MultiTeamSession__end_activity">end_activity()</a>, <a href="#method_ba_MultiTeamSession__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_MultiTeamSession__get_ffa_series_length">get_ffa_series_length()</a>, <a href="#method_ba_MultiTeamSession__get_game_number">get_game_number()</a>, <a href="#method_ba_MultiTeamSession__get_max_players">get_max_players()</a>, <a href="#method_ba_MultiTeamSession__get_next_game_description">get_next_game_description()</a>, <a href="#method_ba_MultiTeamSession__get_series_length">get_series_length()</a>, <a href="#method_ba_MultiTeamSession__getactivity">getactivity()</a>, <a href="#method_ba_MultiTeamSession__handlemessage">handlemessage()</a>, <a href="#method_ba_MultiTeamSession__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_MultiTeamSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_MultiTeamSession__on_player_leave">on_player_leave()</a>, <a href="#method_ba_MultiTeamSession__on_player_request">on_player_request()</a>, <a href="#method_ba_MultiTeamSession__on_team_join">on_team_join()</a>, <a href="#method_ba_MultiTeamSession__on_team_leave">on_team_leave()</a>, <a href="#method_ba_MultiTeamSession__set_activity">set_activity()</a>, <a href="#method_ba_MultiTeamSession__transitioning_out_activity_was_freed">transitioning_out_activity_was_freed()</a></h5>
<h3>Methods Defined or Overridden:</h3>
@ -2024,7 +2047,7 @@ its time with lingering corpses, sound effects, etc.</p>
</p>
<h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h3>Methods Inherited:</h3>
<h5><a href="#method_ba_MultiTeamSession__announce_game_results">announce_game_results()</a>, <a href="#method_ba_MultiTeamSession__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_MultiTeamSession__end">end()</a>, <a href="#method_ba_MultiTeamSession__end_activity">end_activity()</a>, <a href="#method_ba_MultiTeamSession__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_MultiTeamSession__get_ffa_series_length">get_ffa_series_length()</a>, <a href="#method_ba_MultiTeamSession__get_game_number">get_game_number()</a>, <a href="#method_ba_MultiTeamSession__get_max_players">get_max_players()</a>, <a href="#method_ba_MultiTeamSession__get_next_game_description">get_next_game_description()</a>, <a href="#method_ba_MultiTeamSession__get_series_length">get_series_length()</a>, <a href="#method_ba_MultiTeamSession__getactivity">getactivity()</a>, <a href="#method_ba_MultiTeamSession__handlemessage">handlemessage()</a>, <a href="#method_ba_MultiTeamSession__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_MultiTeamSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_MultiTeamSession__on_player_leave">on_player_leave()</a>, <a href="#method_ba_MultiTeamSession__on_player_request">on_player_request()</a>, <a href="#method_ba_MultiTeamSession__on_team_join">on_team_join()</a>, <a href="#method_ba_MultiTeamSession__on_team_leave">on_team_leave()</a>, <a href="#method_ba_MultiTeamSession__set_activity">set_activity()</a>, <a href="#method_ba_MultiTeamSession__transitioning_out_activity_was_freed">transitioning_out_activity_was_freed()</a></h5>
<h3>Methods Defined or Overridden:</h3>
@ -3328,7 +3351,7 @@ Use <a href="#function_ba_getmodel">ba.getmodel</a>() to instantiate one.</p>
</p>
<h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h3>Methods Inherited:</h3>
<h5><a href="#method_ba_Session__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_Session__end">end()</a>, <a href="#method_ba_Session__end_activity">end_activity()</a>, <a href="#method_ba_Session__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_Session__getactivity">getactivity()</a>, <a href="#method_ba_Session__handlemessage">handlemessage()</a>, <a href="#method_ba_Session__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_Session__on_player_leave">on_player_leave()</a>, <a href="#method_ba_Session__on_player_request">on_player_request()</a>, <a href="#method_ba_Session__on_team_leave">on_team_leave()</a>, <a href="#method_ba_Session__set_activity">set_activity()</a>, <a href="#method_ba_Session__transitioning_out_activity_was_freed">transitioning_out_activity_was_freed()</a></h5>
<h3>Methods Defined or Overridden:</h3>
@ -3956,6 +3979,22 @@ If False, they left the game or the round ended.</p>
<p>The type of player for the current activity should be passed so that
the type-checker properly identifies the returned value as one.</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_PlayerInfo">ba.PlayerInfo</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>Holds basic info about a player.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a>
</p>
<h3>Methods:</h3>
<dl>
<dt><h4><a name="method_ba_PlayerInfo____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.PlayerInfo(name: str, character: str)</span></p>
</dd>
</dl>
<hr>
@ -4256,19 +4295,13 @@ Pass 0 or a negative number for no ban time.</p>
maintaining state between them (players, teams, score tallies, etc).</p>
<h3>Attributes:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<dl>
<dt><h4><a name="attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a></h4></dt><dd>
<p><span>bool</span></p>
<p>Whether players should be allowed to join in the middle of
activities.</p>
</dd>
<dt><h4><a name="attr_ba_Session__campaign">campaign</a></h4></dt><dd>
<p><span>Optional[<a href="#class_ba_Campaign">ba.Campaign</a>]</span></p>
<p>The <a href="#class_ba_Campaign">ba.Campaign</a> instance this Session represents, or None if
there is no associated Campaign.</p>
</dd>
<dt><h4><a name="attr_ba_Session__lobby">lobby</a></h4></dt><dd>
<p><span><a href="#class_ba_Lobby">ba.Lobby</a></span></p>
@ -5081,7 +5114,7 @@ Results for a completed <a href="#class_ba_TeamGameActivity">ba.TeamGameActivity
</dd>
<dt><h4><a name="method_ba_TeamGameResults__get_player_info">get_player_info()</a></dt></h4><dd>
<p><span>get_player_info(self) -&gt; List[Dict[str, Any]]</span></p>
<p><span>get_player_info(self) -&gt; List[<a href="#class_ba_PlayerInfo">ba.PlayerInfo</a>]</span></p>
<p>Get info about the players represented by the results.</p>
@ -5804,17 +5837,17 @@ version of the game are ignored.</p>
<hr>
<h2><strong><a name="function_ba_getactivity">ba.getactivity()</a></strong></h3>
<p><span>getactivity(doraise: bool = True) -&gt; <a href="#class_ba_Activity">ba.Activity</a></span></p>
<p><span>getactivity(doraise: bool = True) -&gt; &lt;varies&gt;</span></p>
<p>Returns the current <a href="#class_ba_Activity">ba.Activity</a> instance.</p>
<p>Return the current <a href="#class_ba_Activity">ba.Activity</a> instance.</p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>
<p>Note that this is based on context; thus code run in a timer generated
in Activity 'foo' will properly return 'foo' here, even if another
Activity has since been created or is transitioning in.
If there is no current Activity an Exception is raised, or if doraise is
False then None is returned instead.</p>
If there is no current Activity, raises a <a href="#class_ba_ActivityNotFoundError">ba.ActivityNotFoundError</a>.
If doraise is False, None will be returned instead in that case.</p>
<hr>
<h2><strong><a name="function_ba_getcollidemodel">ba.getcollidemodel()</a></strong></h3>
@ -5908,7 +5941,7 @@ Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a>
<hr>
<h2><strong><a name="function_ba_getsession">ba.getsession()</a></strong></h3>
<p><span>getsession(doraise: bool = True) -&gt; <a href="#class_ba_Session">ba.Session</a></span></p>
<p><span>getsession(doraise: bool = True) -&gt; &lt;varies&gt;</span></p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>