diff --git a/.efrocachemap b/.efrocachemap
index 4179fd4f..e1ace103 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -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"
}
\ No newline at end of file
diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py
index 9df9b873..10e4bbe4 100644
--- a/assets/src/ba_data/python/_ba.py
+++ b/assets/src/ba_data/python/_ba.py
@@ -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) ->
+
+ 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) ->
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:
diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py
index 12225c01..6b79f6e0 100644
--- a/assets/src/ba_data/python/ba/__init__.py
+++ b/assets/src/ba_data/python/ba/__init__.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_achievement.py b/assets/src/ba_data/python/ba/_achievement.py
index c024dfbd..44292c28 100644
--- a/assets/src/ba_data/python/ba/_achievement.py
+++ b/assets/src/ba_data/python/ba/_achievement.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py
index 241d1d70..fe501643 100644
--- a/assets/src/ba_data/python/ba/_activity.py
+++ b/assets/src/ba_data/python/ba/_activity.py
@@ -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)')
diff --git a/assets/src/ba_data/python/ba/_apputils.py b/assets/src/ba_data/python/ba/_apputils.py
index 1c8b1da5..cf0f0137 100644
--- a/assets/src/ba_data/python/ba/_apputils.py
+++ b/assets/src/ba_data/python/ba/_apputils.py
@@ -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:
diff --git a/assets/src/ba_data/python/ba/_coopgame.py b/assets/src/ba_data/python/ba/_coopgame.py
index 89421b82..ee60ea96 100644
--- a/assets/src/ba_data/python/ba/_coopgame.py
+++ b/assets/src/ba_data/python/ba/_coopgame.py
@@ -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.
diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py
index be5f5450..9fdd94fa 100644
--- a/assets/src/ba_data/python/ba/_coopsession.py
+++ b/assets/src/ba_data/python/ba/_coopsession.py
@@ -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.
diff --git a/assets/src/ba_data/python/ba/_error.py b/assets/src/ba_data/python/ba/_error.py
index afb36569..bf1db183 100644
--- a/assets/src/ba_data/python/ba/_error.py
+++ b/assets/src/ba_data/python/ba/_error.py
@@ -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.
diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py
index 9d0de5ee..dad009cd 100644
--- a/assets/src/ba_data/python/ba/_gameactivity.py
+++ b/assets/src/ba_data/python/ba/_gameactivity.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_gameresults.py b/assets/src/ba_data/python/ba/_gameresults.py
index a4997e37..ff185d56 100644
--- a/assets/src/ba_data/python/ba/_gameresults.py
+++ b/assets/src/ba_data/python/ba/_gameresults.py
@@ -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.")
diff --git a/assets/src/ba_data/python/ba/_gameutils.py b/assets/src/ba_data/python/ba/_gameutils.py
index e6cb6adf..491f4140 100644
--- a/assets/src/ba_data/python/ba/_gameutils.py
+++ b/assets/src/ba_data/python/ba/_gameutils.py
@@ -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).
diff --git a/assets/src/ba_data/python/ba/_netutils.py b/assets/src/ba_data/python/ba/_netutils.py
index d7a7b371..dc0180c4 100644
--- a/assets/src/ba_data/python/ba/_netutils.py
+++ b/assets/src/ba_data/python/ba/_netutils.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py
index bc5c44bb..d4f6a7bf 100644
--- a/assets/src/ba_data/python/ba/_player.py
+++ b/assets/src/ba_data/python/ba/_player.py
@@ -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.
diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py
index c84e42ea..30b4f8b7 100644
--- a/assets/src/ba_data/python/ba/_session.py
+++ b/assets/src/ba_data/python/ba/_session.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/activity/coopjoin.py b/assets/src/ba_data/python/bastd/activity/coopjoin.py
index 048dfa68..78addd43 100644
--- a/assets/src/ba_data/python/bastd/activity/coopjoin.py
+++ b/assets/src/ba_data/python/bastd/activity/coopjoin.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/activity/coopscore.py b/assets/src/ba_data/python/bastd/activity/coopscore.py
index 80dfe6e9..4e1bacdb 100644
--- a/assets/src/ba_data/python/bastd/activity/coopscore.py
+++ b/assets/src/ba_data/python/bastd/activity/coopscore.py
@@ -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)
diff --git a/assets/src/ba_data/python/bastd/activity/dualteamscore.py b/assets/src/ba_data/python/bastd/activity/dualteamscore.py
index 6fcb0305..eb354366 100644
--- a/assets/src/ba_data/python/bastd/activity/dualteamscore.py
+++ b/assets/src/ba_data/python/bastd/activity/dualteamscore.py
@@ -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
diff --git a/assets/src/ba_data/python/bastd/actor/spazbot.py b/assets/src/ba_data/python/bastd/actor/spazbot.py
index 984d209d..303beb37 100644
--- a/assets/src/ba_data/python/bastd/actor/spazbot.py
+++ b/assets/src/ba_data/python/bastd/actor/spazbot.py
@@ -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
diff --git a/docs/ba_module.md b/docs/ba_module.md
index 7ad463ee..d1e9401b 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -1,5 +1,5 @@
-last updated on 2020-05-27 for Ballistica version 1.5.0 build 20030
+last updated on 2020-05-28 for Ballistica version 1.5.0 build 20031
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 let me know. Happy modding!
@@ -26,6 +26,7 @@
ba.Material
ba.Node
ba.Player
+ ba.PlayerInfo
ba.PlayerRecord
ba.ScoreInfo
ba.Session
@@ -185,6 +186,7 @@
These generally consist of 1-4 players against
the computer and include functionality such as
- high score lists.
-
+ high score lists.
Attributes Inherited:
-
+
+Attributes Defined Here:
+
+-
+
Optional[ba.Campaign]
+The ba.Campaign instance this Session represents, or None if
+there is no associated Campaign.
+
+
+
Methods Inherited:
Methods Defined or Overridden:
@@ -1986,7 +2009,7 @@ its time with lingering corpses, sound effects, etc.
Attributes Inherited:
-
+
Methods Inherited:
Methods Defined or Overridden:
@@ -2024,7 +2047,7 @@ its time with lingering corpses, sound effects, etc.
Attributes Inherited:
-
+
Methods Inherited:
Methods Defined or Overridden:
@@ -3328,7 +3351,7 @@ Use ba.getmodel() to instantiate one.
Attributes Inherited:
-
+
Methods Inherited:
Methods Defined or Overridden:
@@ -3956,6 +3979,22 @@ If False, they left the game or the round ended.
The type of player for the current activity should be passed so that
the type-checker properly identifies the returned value as one.
+
+
+
+<top level class>
+
+Holds basic info about a player.
+
+Category: Gameplay Classes
+
+
+Methods:
+
+-
+
ba.PlayerInfo(name: str, character: str)
+
@@ -4256,19 +4295,13 @@ Pass 0 or a negative number for no ban time.
maintaining state between them (players, teams, score tallies, etc).
Attributes:
-
+
-
bool
Whether players should be allowed to join in the middle of
activities.
-
--
-
Optional[ba.Campaign]
-The ba.Campaign instance this Session represents, or None if
-there is no associated Campaign.
-
-
ba.Lobby
@@ -5081,7 +5114,7 @@ Results for a completed ba.TeamGameActivity
-
-
get_player_info(self) -> List[Dict[str, Any]]
+get_player_info(self) -> List[ba.PlayerInfo]
Get info about the players represented by the results.
@@ -5804,17 +5837,17 @@ version of the game are ignored.
getactivity(doraise: bool = True) -> ba.Activity
+getactivity(doraise: bool = True) -> <varies>
-Returns the current ba.Activity instance.
+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.
getsession(doraise: bool = True) -> ba.Session
+getsession(doraise: bool = True) -> <varies>
Category: Gameplay Functions