More type safety and simplifying

This commit is contained in:
Eric Froemling 2020-06-02 01:45:12 -07:00
parent ae0c9dde20
commit 24f2792234
33 changed files with 444 additions and 391 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/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/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", "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/63/2c/d64f68f0e4ecbb55f3731ad95530", "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/15/7a/d4e39ad022b8365418ecee4d026b",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/86/cd/c8c9b3e4a265ac816c5d249e4638", "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ab/ad/7e371dfb7ed7b10d46f0bf9497d8",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/29/92/c312438bf1dbdb599bd020a5c220", "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/92/28/faa6501d779b381dec7fb9ac19c5",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/68/75/6686aa5b6e0f88e1a40d444fd013", "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/45/57/97d03c6230cfc4d9f0687249f408",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c1/bc/23b3f5d0929adf8c497accad24bb", "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d3/e1/a3a86c40804f1e724c59ee04f1a2",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/81/07/d95b4d6354acf4003f70307ba7cb", "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ae/78/7a6522e860506fe1046808ce7b0b",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/2c/bd/18dff8eda46be2d2edeee0a7ece9", "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/08/71/09be3ad2f8cf99f885549a7296a0",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8d/64/a0beab66455ed6be694583fb6d53", "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/87/49/3c1d3c8a995df400e46df3470b02",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/a2/ba/1e71e85b38eed56a55f8e0d85821", "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/43/d4/48ec85af308fd027d7dd06eaa650",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/dd/38/d98994fed8732ab774e879b0c8a5", "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/cc/1b/303089d3c1f3b7620c632ee864c3",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/2c/e0/97ebbdf740a3bfbc5324f510204e", "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f2/55/890f1b498ccf5988dee573d5f3ca",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/4e/69/2af7295ec87bcf480e57f41e19a7" "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/b9/b1/d96d693c3ad52cedd4f517fd9d94"
} }

View File

@ -398,6 +398,7 @@
<w>curstate</w> <w>curstate</w>
<w>curtime</w> <w>curtime</w>
<w>curtimestr</w> <w>curtimestr</w>
<w>customdata</w>
<w>customizebrowser</w> <w>customizebrowser</w>
<w>cutscenes</w> <w>cutscenes</w>
<w>cval</w> <w>cval</w>
@ -604,6 +605,7 @@
<w>factoryclass</w> <w>factoryclass</w>
<w>fallbacks</w> <w>fallbacks</w>
<w>farthestpt</w> <w>farthestpt</w>
<w>fback</w>
<w>fbase</w> <w>fbase</w>
<w>fclose</w> <w>fclose</w>
<w>fcmd</w> <w>fcmd</w>
@ -874,6 +876,7 @@
<w>icns</w> <w>icns</w>
<w>iconpicker</w> <w>iconpicker</w>
<w>iconscale</w> <w>iconscale</w>
<w>iconsstorename</w>
<w>ident</w> <w>ident</w>
<w>idevices</w> <w>idevices</w>
<w>ifeq</w> <w>ifeq</w>
@ -910,6 +913,7 @@
<w>infotextcolor</w> <w>infotextcolor</w>
<w>inidividual</w> <w>inidividual</w>
<w>initializers</w> <w>initializers</w>
<w>initialplayerinfos</w>
<w>initing</w> <w>initing</w>
<w>inits</w> <w>inits</w>
<w>inmobi</w> <w>inmobi</w>
@ -1110,6 +1114,8 @@
<w>mapselect</w> <w>mapselect</w>
<w>maptype</w> <w>maptype</w>
<w>markupbase</w> <w>markupbase</w>
<w>masktex</w>
<w>masktexstorename</w>
<w>mathmodule</w> <w>mathmodule</w>
<w>mathnode</w> <w>mathnode</w>
<w>mathutils</w> <w>mathutils</w>
@ -1163,6 +1169,7 @@
<w>moduledir</w> <w>moduledir</w>
<w>modulefinder</w> <w>modulefinder</w>
<w>modulename</w> <w>modulename</w>
<w>modulepath</w>
<w>modutils</w> <w>modutils</w>
<w>moola</w> <w>moola</w>
<w>mopaque</w> <w>mopaque</w>
@ -1204,6 +1211,7 @@
<w>myhome</w> <w>myhome</w>
<w>myinput</w> <w>myinput</w>
<w>mylist</w> <w>mylist</w>
<w>mymodule</w>
<w>mynode</w> <w>mynode</w>
<w>myobj</w> <w>myobj</w>
<w>myprojname</w> <w>myprojname</w>
@ -1214,6 +1222,7 @@
<w>mypytype</w> <w>mypytype</w>
<w>mysound</w> <w>mysound</w>
<w>mytextnode</w> <w>mytextnode</w>
<w>mythingie</w>
<w>myweakcall</w> <w>myweakcall</w>
<w>mywidget</w> <w>mywidget</w>
<w>namedarg</w> <w>namedarg</w>
@ -1360,6 +1369,7 @@
<w>pkgutil</w> <w>pkgutil</w>
<w>playercast</w> <w>playercast</w>
<w>playerdata</w> <w>playerdata</w>
<w>playerinfos</w>
<w>playerlostspaz</w> <w>playerlostspaz</w>
<w>playernode</w> <w>playernode</w>
<w>playerpt</w> <w>playerpt</w>
@ -1533,6 +1543,7 @@
<w>qrcode</w> <w>qrcode</w>
<w>qrencode</w> <w>qrencode</w>
<w>qual</w> <w>qual</w>
<w>qualname</w>
<w>quoprimime</w> <w>quoprimime</w>
<w>rando</w> <w>rando</w>
<w>randommodule</w> <w>randommodule</w>
@ -1786,8 +1797,10 @@
<w>steelseries</w> <w>steelseries</w>
<w>stickman</w> <w>stickman</w>
<w>storable</w> <w>storable</w>
<w>storagename</w>
<w>storedhash</w> <w>storedhash</w>
<w>storeitemui</w> <w>storeitemui</w>
<w>storename</w>
<w>strftime</w> <w>strftime</w>
<w>stringprep</w> <w>stringprep</w>
<w>stringptr</w> <w>stringptr</w>
@ -1913,6 +1926,7 @@
<w>thanvannispen</w> <w>thanvannispen</w>
<w>thelaststand</w> <w>thelaststand</w>
<w>themself</w> <w>themself</w>
<w>thingie</w>
<w>threadtype</w> <w>threadtype</w>
<w>throwiness</w> <w>throwiness</w>
<w>timedisplay</w> <w>timedisplay</w>
@ -2095,6 +2109,8 @@
<w>willeval</w> <w>willeval</w>
<w>wincfg</w> <w>wincfg</w>
<w>wincount</w> <w>wincount</w>
<w>winnergroup</w>
<w>winnergroups</w>
<w>winplt</w> <w>winplt</w>
<w>winprj</w> <w>winprj</w>
<w>winref</w> <w>winref</w>

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) # (hash we can use to see if this file is out of date)
# SOURCES_HASH=247084187994045015646460724420234222242 # SOURCES_HASH=214847179904844500339904334878206016957
# I'm sorry Pylint. I know this file saddens you. Be strong. # I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression # pylint: disable=useless-suppression
@ -822,16 +822,11 @@ class SessionPlayer:
This bool value will be True once the Player has completed This bool value will be True once the Player has completed
any lobby character/team selection. any lobby character/team selection.
team: ba.SessionTeam sessionteam: ba.SessionTeam
The ba.SessionTeam this Player is on. If the SessionPlayer The ba.SessionTeam this Player is on. If the SessionPlayer
is still in its lobby selecting a team/etc. then a is still in its lobby selecting a team/etc. then a
ba.SessionTeamNotFoundError will be raised. ba.SessionTeamNotFoundError will be raised.
sessiondata: Dict
A dict for use by the current ba.Session for
storing data associated with this player.
This persists for the duration of the session.
inputdevice: ba.InputDevice inputdevice: ba.InputDevice
The input device associated with the player. The input device associated with the player.
@ -853,8 +848,7 @@ class SessionPlayer:
""" """
id: int id: int
in_game: bool in_game: bool
team: ba.SessionTeam sessionteam: ba.SessionTeam
sessiondata: Dict
inputdevice: ba.InputDevice inputdevice: ba.InputDevice
color: Sequence[float] color: Sequence[float]
highlight: Sequence[float] highlight: Sequence[float]

View File

@ -73,7 +73,7 @@ from ba._campaign import Campaign
from ba._gameutils import (animate, animate_array, show_damage_count, from ba._gameutils import (animate, animate_array, show_damage_count,
timestring, cameraflash) timestring, cameraflash)
from ba._general import (WeakCall, Call, existing, Existable, from ba._general import (WeakCall, Call, existing, Existable,
verify_object_death) verify_object_death, storagename)
from ba._level import Level from ba._level import Level
from ba._lobby import Lobby, Chooser from ba._lobby import Lobby, Chooser
from ba._math import normalized_color, is_point_in_box, vec3validate from ba._math import normalized_color, is_point_in_box, vec3validate

View File

@ -215,7 +215,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self.lobby = None self.lobby = None
self._stats: Optional[ba.Stats] = None self._stats: Optional[ba.Stats] = None
self._gamedata: Optional[dict] = {} self._customdata: Optional[dict] = {}
def __del__(self) -> None: def __del__(self) -> None:
@ -265,15 +265,15 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
""" """
@property @property
def gamedata(self) -> dict: def customdata(self) -> dict:
"""Entities needing to store simple data with an activity can put it """Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired objects generally do not need to worry about handling expired
activities. activities.
""" """
assert not self._expired assert not self._expired
assert isinstance(self._gamedata, dict) assert isinstance(self._customdata, dict)
return self._gamedata return self._customdata
@property @property
def expired(self) -> bool: def expired(self) -> bool:
@ -574,9 +574,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
def add_player(self, sessionplayer: ba.SessionPlayer) -> None: def add_player(self, sessionplayer: ba.SessionPlayer) -> None:
"""(internal)""" """(internal)"""
assert sessionplayer.team is not None assert sessionplayer.sessionteam is not None
sessionplayer.resetinput() sessionplayer.resetinput()
sessionteam = sessionplayer.team sessionteam = sessionplayer.sessionteam
assert sessionplayer in sessionteam.players assert sessionplayer in sessionteam.players
team = sessionteam.gameteam team = sessionteam.gameteam
assert team is not None assert team is not None
@ -608,7 +608,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
player: Any = sessionplayer.gameplayer player: Any = sessionplayer.gameplayer
assert isinstance(player, self._playertype) assert isinstance(player, self._playertype)
team: Any = sessionplayer.team.gameteam team: Any = sessionplayer.sessionteam.gameteam
assert isinstance(team, self._teamtype) assert isinstance(team, self._teamtype)
assert player in team.players assert player in team.players
@ -795,9 +795,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
print_exception(f'Error in Activity on_expire() for {self}.') print_exception(f'Error in Activity on_expire() for {self}.')
try: try:
self._gamedata = None self._customdata = None
except Exception: except Exception:
print_exception(f'Error clearing gamedata for {self}.') print_exception(f'Error clearing customdata for {self}.')
# Don't want to be holding any delay-delete refs at this point. # Don't want to be holding any delay-delete refs at this point.
self._prune_delay_deletes() self._prune_delay_deletes()

View File

@ -27,8 +27,8 @@ import _ba
from ba._activity import Activity from ba._activity import Activity
from ba._music import setmusic, MusicType from ba._music import setmusic, MusicType
# False positive due to our class_generics_filter custom pylint filter. # False positive due to our class_generics_filter custom pylint filter.
from ba._player import Player # pylint: disable=W0611 from ba import _player
from ba._team import Team # pylint: disable=W0611 from ba import _team
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
@ -36,6 +36,17 @@ if TYPE_CHECKING:
from ba._lobby import JoinInfo from ba._lobby import JoinInfo
# Even though we don't need custom Player/Team types, define empty ones
# so we don't have ba.Player[Any] and ba.Team[Any] as our types which
# reduces type safety.
class Player(_player.Player['Team']):
"""Our player type for this game."""
class Team(_team.Team[Player]):
"""Our team type for this game."""
class EndSessionActivity(Activity[Player, Team]): class EndSessionActivity(Activity[Player, Team]):
"""Special ba.Activity to fade out and end the current ba.Session.""" """Special ba.Activity to fade out and end the current ba.Session."""
@ -158,7 +169,7 @@ class ScoreScreenActivity(Activity[Player, Team]):
self._custom_continue_message: Optional[ba.Lstr] = None self._custom_continue_message: Optional[ba.Lstr] = None
self._server_transitioning: Optional[bool] = None self._server_transitioning: Optional[bool] = None
def on_player_join(self, player: ba.Player) -> None: def on_player_join(self, player: Player) -> None:
from ba import _general from ba import _general
super().on_player_join(player) super().on_player_join(player)
time_till_assign = max( time_till_assign = max(
@ -224,7 +235,7 @@ class ScoreScreenActivity(Activity[Player, Team]):
# Otherwise end the activity normally. # Otherwise end the activity normally.
self.end() self.end()
def _safe_assign(self, player: ba.Player) -> None: def _safe_assign(self, player: Player) -> None:
# Just to be extra careful, don't assign if we're transitioning out. # Just to be extra careful, don't assign if we're transitioning out.
# (though theoretically that would be ok). # (though theoretically that would be ok).

View File

@ -341,37 +341,35 @@ class CoopSession(Session):
self.setactivity(_ba.new_activity(TransitionActivity)) self.setactivity(_ba.new_activity(TransitionActivity))
else: else:
player_info: List[ba.PlayerInfo] playerinfos: List[ba.PlayerInfo]
# Generic team games. # Generic team games.
if isinstance(results, TeamGameResults): if isinstance(results, TeamGameResults):
player_info = results.get_player_info() playerinfos = results.playerinfos
score = results.get_team_score(results.get_teams()[0]) score = results.get_team_score(results.sessionteams[0])
fail_message = None fail_message = None
score_order = ('decreasing' if results.get_lower_is_better() score_order = ('decreasing'
else 'increasing') if results.lower_is_better else 'increasing')
if results.get_score_type() in (ScoreType.SECONDS, if results.scoretype in (ScoreType.SECONDS,
ScoreType.MILLISECONDS): ScoreType.MILLISECONDS):
score_type = 'time' scoretype = 'time'
# ScoreScreen wants hundredths of a second. # ScoreScreen wants hundredths of a second.
if score is not None: if score is not None:
if results.get_score_type() is ScoreType.SECONDS: if results.scoretype is ScoreType.SECONDS:
score *= 100 score *= 100
elif (results.get_score_type() is elif results.scoretype is ScoreType.MILLISECONDS:
ScoreType.MILLISECONDS):
score //= 10 score //= 10
else: else:
raise RuntimeError('FIXME') raise RuntimeError('FIXME')
else: else:
if results.get_score_type() is not ScoreType.POINTS: if results.scoretype is not ScoreType.POINTS:
print(f'Unknown ScoreType:' print(f'Unknown ScoreType:' f' "{results.scoretype}"')
f' "{results.get_score_type()}"') scoretype = 'points'
score_type = 'points'
# Old coop-game-specific results; should migrate away from these. # Old coop-game-specific results; should migrate away from these.
else: else:
player_info = results.get('player_info') playerinfos = results.get('playerinfos')
score = results['score'] if 'score' in results else None score = results['score'] if 'score' in results else None
fail_message = (results['fail_message'] fail_message = (results['fail_message']
if 'fail_message' in results else None) if 'fail_message' in results else None)
@ -380,12 +378,12 @@ class CoopSession(Session):
activity_score_type = (activity.get_score_type() if isinstance( activity_score_type = (activity.get_score_type() if isinstance(
activity, CoopGameActivity) else None) activity, CoopGameActivity) else None)
assert activity_score_type is not None assert activity_score_type is not None
score_type = activity_score_type scoretype = activity_score_type
# Validate types. # Validate types.
if player_info is not None: if playerinfos is not None:
assert isinstance(player_info, list) assert isinstance(playerinfos, list)
assert (isinstance(i, PlayerInfo) for i in player_info) assert (isinstance(i, PlayerInfo) for i in playerinfos)
# Looks like we were in a round - check the outcome and # Looks like we were in a round - check the outcome and
# go from there. # go from there.
@ -397,11 +395,11 @@ class CoopSession(Session):
self.setactivity( self.setactivity(
_ba.new_activity( _ba.new_activity(
CoopScoreScreen, { CoopScoreScreen, {
'player_info': player_info, 'playerinfos': playerinfos,
'score': score, 'score': score,
'fail_message': fail_message, 'fail_message': fail_message,
'score_order': score_order, 'score_order': score_order,
'score_type': score_type, 'score_type': scoretype,
'outcome': outcome, 'outcome': outcome,
'campaign': self.campaign, 'campaign': self.campaign,
'level': self.campaign_level_name 'level': self.campaign_level_name

View File

@ -52,18 +52,17 @@ class DualTeamSession(MultiTeamSession):
TeamVictoryScoreScreenActivity) TeamVictoryScoreScreenActivity)
from bastd.activity.multiteamvictory import ( from bastd.activity.multiteamvictory import (
TeamSeriesVictoryScoreScreenActivity) TeamSeriesVictoryScoreScreenActivity)
winners = results.get_winners() winners = results.winnergroups
# If everyone has the same score, call it a draw. # If everyone has the same score, call it a draw.
if len(winners) < 2: if len(winners) < 2:
self.setactivity(_ba.new_activity(DrawScoreScreenActivity)) self.setactivity(_ba.new_activity(DrawScoreScreenActivity))
else: else:
winner = winners[0].teams[0] winner = winners[0].teams[0]
winner.sessiondata['score'] += 1 winner.customdata['score'] += 1
# If a team has won, show final victory screen. # If a team has won, show final victory screen.
if winner.sessiondata['score'] >= (self._series_length - if winner.customdata['score'] >= (self._series_length - 1) / 2 + 1:
1) / 2 + 1:
self.setactivity( self.setactivity(
_ba.new_activity(TeamSeriesVictoryScoreScreenActivity, _ba.new_activity(TeamSeriesVictoryScoreScreenActivity,
{'winner': winner})) {'winner': winner}))

View File

@ -76,7 +76,7 @@ class FreeForAllSession(MultiTeamSession):
TeamSeriesVictoryScoreScreenActivity) TeamSeriesVictoryScoreScreenActivity)
from bastd.activity.freeforallvictory import ( from bastd.activity.freeforallvictory import (
FreeForAllVictoryScoreScreenActivity) FreeForAllVictoryScoreScreenActivity)
winners = results.get_winners() winners = results.winnergroups
# If there's multiple players and everyone has the same score, # If there's multiple players and everyone has the same score,
# call it a draw. # call it a draw.
@ -91,20 +91,20 @@ class FreeForAllSession(MultiTeamSession):
for i, winner in enumerate(winners): for i, winner in enumerate(winners):
for team in winner.teams: for team in winner.teams:
points = (point_awards[i] if i in point_awards else 0) points = (point_awards[i] if i in point_awards else 0)
team.sessiondata['previous_score'] = ( team.customdata['previous_score'] = (
team.sessiondata['score']) team.customdata['score'])
team.sessiondata['score'] += points team.customdata['score'] += points
series_winners = [ series_winners = [
team for team in self.teams team for team in self.teams
if team.sessiondata['score'] >= self._ffa_series_length if team.customdata['score'] >= self._ffa_series_length
] ]
series_winners.sort(reverse=True, series_winners.sort(reverse=True,
key=lambda tm: (tm.sessiondata['score'])) key=lambda tm: (tm.customdata['score']))
if (len(series_winners) == 1 if (len(series_winners) == 1
or (len(series_winners) > 1 or (len(series_winners) > 1
and series_winners[0].sessiondata['score'] != and series_winners[0].customdata['score'] !=
series_winners[1].sessiondata['score'])): series_winners[1].customdata['score'])):
self.setactivity( self.setactivity(
_ba.new_activity(TeamSeriesVictoryScoreScreenActivity, _ba.new_activity(TeamSeriesVictoryScoreScreenActivity,
{'winner': series_winners[0]})) {'winner': series_winners[0]}))

View File

@ -284,7 +284,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
# Holds some flattened info about the player set at the point # Holds some flattened info about the player set at the point
# when on_begin() is called. # when on_begin() is called.
self.initial_player_info: Optional[List[ba.PlayerInfo]] = None self.initialplayerinfos: Optional[List[ba.PlayerInfo]] = None
# Go ahead and get our map loading. # Go ahead and get our map loading.
self._map_type = _map.get_map_class(self._calc_map_name(settings)) self._map_type = _map.get_map_class(self._calc_map_name(settings))
@ -511,14 +511,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
_ba.timer(2.5, self._show_tip) _ba.timer(2.5, self._show_tip)
# Store some basic info about players present at start time. # Store some basic info about players present at start time.
self.initial_player_info = [ self.initialplayerinfos = [
PlayerInfo(name=p.getname(full=True), character=p.character) PlayerInfo(name=p.getname(full=True), character=p.character)
for p in self.players for p in self.players
] ]
# Sort this by name so high score lists/etc will be consistent # Sort this by name so high score lists/etc will be consistent
# regardless of player join order. # regardless of player join order.
self.initial_player_info.sort(key=lambda x: x.name) self.initialplayerinfos.sort(key=lambda x: x.name)
# If this is a tournament, query info about it such as how much # If this is a tournament, query info about it such as how much
# time is left. # time is left.
@ -890,9 +890,10 @@ class GameActivity(Activity[PlayerType, TeamType]):
if player.actor and not self.has_ended(): if player.actor and not self.has_ended():
from bastd.actor.respawnicon import RespawnIcon from bastd.actor.respawnicon import RespawnIcon
player.gamedata['respawn_timer'] = _ba.Timer( player.customdata['respawn_timer'] = _ba.Timer(
respawn_time, WeakCall(self.spawn_player_if_exists, player)) respawn_time, WeakCall(self.spawn_player_if_exists, player))
player.gamedata['respawn_icon'] = RespawnIcon(player, respawn_time) player.customdata['respawn_icon'] = RespawnIcon(
player, respawn_time)
def spawn_player_if_exists(self, player: PlayerType) -> None: def spawn_player_if_exists(self, player: PlayerType) -> None:
""" """

View File

@ -26,8 +26,6 @@ import weakref
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ba._team import Team
if TYPE_CHECKING: if TYPE_CHECKING:
from weakref import ReferenceType from weakref import ReferenceType
from typing import Sequence, Tuple, Any, Optional, Dict, List, Union from typing import Sequence, Tuple, Any, Optional, Dict, List, Union
@ -56,56 +54,54 @@ class TeamGameResults:
self._game_set = False self._game_set = False
self._scores: Dict[int, Tuple[ReferenceType[ba.SessionTeam], self._scores: Dict[int, Tuple[ReferenceType[ba.SessionTeam],
Optional[int]]] = {} Optional[int]]] = {}
self._teams: Optional[List[ReferenceType[ba.SessionTeam]]] = None self._sessionteams: Optional[List[ReferenceType[
self._player_info: Optional[List[ba.PlayerInfo]] = None ba.SessionTeam]]] = None
self._playerinfos: Optional[List[ba.PlayerInfo]] = None
self._lower_is_better: Optional[bool] = None self._lower_is_better: Optional[bool] = None
self._score_label: Optional[str] = None self._score_label: Optional[str] = None
self._none_is_winner: Optional[bool] = None self._none_is_winner: Optional[bool] = None
self._score_type: Optional[ba.ScoreType] = None self._scoretype: Optional[ba.ScoreType] = None
def set_game(self, game: ba.GameActivity) -> None: def set_game(self, game: ba.GameActivity) -> None:
"""Set the game instance these results are applying to.""" """Set the game instance these results are applying to."""
if self._game_set: if self._game_set:
raise RuntimeError('Game set twice for TeamGameResults.') raise RuntimeError('Game set twice for TeamGameResults.')
self._game_set = True self._game_set = True
self._teams = [weakref.ref(team) for team in game.teams] self._sessionteams = [weakref.ref(team) for team in game.teams]
score_info = game.get_score_info() score_info = game.get_score_info()
self._player_info = copy.deepcopy(game.initial_player_info) self._playerinfos = copy.deepcopy(game.initialplayerinfos)
self._lower_is_better = score_info.lower_is_better self._lower_is_better = score_info.lower_is_better
self._score_label = score_info.label self._score_label = score_info.label
self._none_is_winner = score_info.none_is_winner self._none_is_winner = score_info.none_is_winner
self._score_type = score_info.scoretype self._scoretype = score_info.scoretype
def set_team_score(self, team: Union[ba.SessionTeam, ba.Team], def set_team_score(self, team: ba.Team, score: Optional[int]) -> None:
score: Optional[int]) -> None:
"""Set the score for a given ba.Team. """Set the score for a given ba.Team.
This can be a number or None. This can be a number or None.
(see the none_is_winner arg in the constructor) (see the none_is_winner arg in the constructor)
""" """
if isinstance(team, Team): sessionteam = team.sessionteam
team = team.sessionteam self._scores[sessionteam.id] = (weakref.ref(sessionteam), score)
self._scores[team.id] = (weakref.ref(team), score)
def get_team_score(self, team: Union[ba.SessionTeam, def get_team_score(self,
ba.Team]) -> Optional[int]: sessionteam: Union[ba.SessionTeam]) -> Optional[int]:
"""Return the score for a given team.""" """Return the score for a given ba.SessionTeam."""
if isinstance(team, Team):
team = team.sessionteam
for score in list(self._scores.values()): for score in list(self._scores.values()):
if score[0]() is team: if score[0]() is sessionteam:
return score[1] return score[1]
# If we have no score value, assume None. # If we have no score value, assume None.
return None return None
def get_teams(self) -> List[ba.SessionTeam]: @property
def sessionteams(self) -> List[ba.SessionTeam]:
"""Return all ba.SessionTeams in the results.""" """Return all ba.SessionTeams in the results."""
if not self._game_set: if not self._game_set:
raise RuntimeError("Can't get teams until game is set.") raise RuntimeError("Can't get teams until game is set.")
teams = [] teams = []
assert self._teams is not None assert self._sessionteams is not None
for team_ref in self._teams: for team_ref in self._sessionteams:
team = team_ref() team = team_ref()
if team is not None: if team is not None:
teams.append(team) teams.append(team)
@ -130,55 +126,61 @@ class TeamGameResults:
if score[0]() is team.sessionteam: if score[0]() is team.sessionteam:
if score[1] is None: if score[1] is None:
return Lstr(value='-') return Lstr(value='-')
if self._score_type is ScoreType.SECONDS: if self._scoretype is ScoreType.SECONDS:
return timestring(score[1] * 1000, return timestring(score[1] * 1000,
centi=False, centi=False,
timeformat=TimeFormat.MILLISECONDS) timeformat=TimeFormat.MILLISECONDS)
if self._score_type is ScoreType.MILLISECONDS: if self._scoretype is ScoreType.MILLISECONDS:
return timestring(score[1], return timestring(score[1],
centi=True, centi=True,
timeformat=TimeFormat.MILLISECONDS) timeformat=TimeFormat.MILLISECONDS)
return Lstr(value=str(score[1])) return Lstr(value=str(score[1]))
return Lstr(value='-') return Lstr(value='-')
def get_player_info(self) -> List[ba.PlayerInfo]: @property
def playerinfos(self) -> List[ba.PlayerInfo]:
"""Get info about the players represented by the results.""" """Get info about the players represented by the results."""
if not self._game_set: if not self._game_set:
raise RuntimeError("Can't get player-info until game is set.") raise RuntimeError("Can't get player-info until game is set.")
assert self._player_info is not None assert self._playerinfos is not None
return self._player_info return self._playerinfos
def get_score_type(self) -> ba.ScoreType: @property
"""Get the type of score.""" def scoretype(self) -> ba.ScoreType:
"""The type of score."""
if not self._game_set: if not self._game_set:
raise RuntimeError("Can't get score-type until game is set.") raise RuntimeError("Can't get score-type until game is set.")
assert self._score_type is not None assert self._scoretype is not None
return self._score_type return self._scoretype
def get_score_name(self) -> str: @property
"""Get the name associated with scores ('points', etc).""" def score_label(self) -> str:
"""The label associated with scores ('points', etc)."""
if not self._game_set: if not self._game_set:
raise RuntimeError("Can't get score-name until game is set.") raise RuntimeError("Can't get score-label until game is set.")
assert self._score_label is not None assert self._score_label is not None
return self._score_label return self._score_label
def get_lower_is_better(self) -> bool: @property
"""Return whether lower scores are better.""" def lower_is_better(self) -> bool:
"""Whether lower scores are better."""
if not self._game_set: if not self._game_set:
raise RuntimeError("Can't get lower-is-better until game is set.") raise RuntimeError("Can't get lower-is-better until game is set.")
assert self._lower_is_better is not None assert self._lower_is_better is not None
return self._lower_is_better return self._lower_is_better
def get_winning_team(self) -> Optional[ba.SessionTeam]: @property
"""Get the winning ba.Team if there is exactly one; None otherwise.""" def winning_team(self) -> Optional[ba.SessionTeam]:
"""The winning ba.SessionTeam if there is exactly one, or else None."""
if not self._game_set: if not self._game_set:
raise RuntimeError("Can't get winners until game is set.") raise RuntimeError("Can't get winners until game is set.")
winners = self.get_winners() winners = self.winnergroups
if winners and len(winners[0].teams) == 1: if winners and len(winners[0].teams) == 1:
return winners[0].teams[0] return winners[0].teams[0]
return None return None
def get_winners(self) -> List[WinnerGroup]: @property
def winnergroups(self) -> List[WinnerGroup]:
"""Get an ordered list of winner groups.""" """Get an ordered list of winner groups."""
if not self._game_set: if not self._game_set:
raise RuntimeError("Can't get winners until game is set.") raise RuntimeError("Can't get winners until game is set.")
@ -200,17 +202,18 @@ class TeamGameResults:
results.sort(reverse=not self._lower_is_better, key=lambda x: x[0]) results.sort(reverse=not self._lower_is_better, key=lambda x: x[0])
# Also group the 'None' scores. # Also group the 'None' scores.
none_teams: List[ba.SessionTeam] = [] none_sessionteams: List[ba.SessionTeam] = []
for score in self._scores.values(): for score in self._scores.values():
scoreteam = score[0]() scoreteam = score[0]()
if scoreteam is not None and score[1] is None: if scoreteam is not None and score[1] is None:
none_teams.append(scoreteam) none_sessionteams.append(scoreteam)
# Add the Nones to the list (either as winners or losers # Add the Nones to the list (either as winners or losers
# depending on the rules). # depending on the rules).
if none_teams: if none_sessionteams:
nones: List[Tuple[Optional[int], nones: List[Tuple[Optional[int], List[ba.SessionTeam]]] = [
List[ba.SessionTeam]]] = [(None, none_teams)] (None, none_sessionteams)
]
if self._none_is_winner: if self._none_is_winner:
results = nones + results results = nones + results
else: else:

View File

@ -25,6 +25,7 @@ import gc
import types import types
import weakref import weakref
import random import random
import inspect
from typing import TYPE_CHECKING, TypeVar from typing import TYPE_CHECKING, TypeVar
from typing_extensions import Protocol from typing_extensions import Protocol
@ -332,7 +333,45 @@ def _verify_object_death(wref: ReferenceType) -> None:
print(f'{Clr.YLW}Active References:{Clr.RST}') print(f'{Clr.YLW}Active References:{Clr.RST}')
i = 1 i = 1
for ref in refs: for ref in refs:
# if isinstance(ref, types.FrameType):
# continue
print(f'{Clr.YLW} reference {i}:{Clr.BLU} {ref}{Clr.RST}') print(f'{Clr.YLW} reference {i}:{Clr.BLU} {ref}{Clr.RST}')
i += 1 i += 1
def storagename(basename: str) -> str:
"""Generate a (hopefully) unique name for storing things in public places.
Category: General Utility Functions
This consists of a leading underscore, the module path at the
call site with dots replaced by underscores, the class name, and
the provided suffix. When storing data in public places such as
'customdata' dicts, this minimizes the chance of collisions if a
module or class is duplicated or renamed.
# Example: generate a unique name for storage purposes:
class MyThingie:
# This will give something like '_mymodule_submodule_mythingie_data'.
_STORENAME = ba.storagename('data')
def __init__(self, activity):
# Store some data in the Activity we were passed
activity.customdata[self._STORENAME] = {}
"""
frame = inspect.currentframe()
if frame is None:
raise RuntimeError('Cannot get current stack frame.')
fback = frame.f_back
if fback is None:
raise RuntimeError('Cannot get parent stack frame.')
modulepath = fback.f_globals.get('__name__')
if modulepath is None:
raise RuntimeError('Cannot get parent stack module path.')
assert isinstance(modulepath, str)
qualname = fback.f_locals.get('__qualname__')
if qualname is not None:
assert isinstance(qualname, str)
fullpath = f'_{modulepath}_{qualname.lower()}_{basename}'
else:
fullpath = f'_{modulepath}_{basename}'
return fullpath.replace('.', '_')

View File

@ -156,7 +156,7 @@ class MultiTeamSession(Session):
return self._game_number return self._game_number
def on_team_join(self, team: ba.SessionTeam) -> None: def on_team_join(self, team: ba.SessionTeam) -> None:
team.sessiondata['previous_score'] = team.sessiondata['score'] = 0 team.customdata['previous_score'] = team.customdata['score'] = 0
def get_max_players(self) -> int: def get_max_players(self) -> int:
"""Return max number of ba.Players allowed to join the game at once.""" """Return max number of ba.Players allowed to join the game at once."""
@ -174,7 +174,8 @@ class MultiTeamSession(Session):
from bastd.tutorial import TutorialActivity from bastd.tutorial import TutorialActivity
from bastd.activity.multiteamvictory import ( from bastd.activity.multiteamvictory import (
TeamSeriesVictoryScoreScreenActivity) TeamSeriesVictoryScoreScreenActivity)
from ba import _activitytypes from ba._activitytypes import (TransitionActivity, JoinActivity,
ScoreScreenActivity)
# If we have a tutorial to show, that's the first thing we do no # If we have a tutorial to show, that's the first thing we do no
# matter what. # matter what.
@ -186,22 +187,20 @@ class MultiTeamSession(Session):
# to transition us into a round gracefully (otherwise we'd snap from # to transition us into a round gracefully (otherwise we'd snap from
# one terrain to another instantly). # one terrain to another instantly).
elif isinstance(activity, TutorialActivity): elif isinstance(activity, TutorialActivity):
self.setactivity( self.setactivity(_ba.new_activity(TransitionActivity))
_ba.new_activity(_activitytypes.TransitionActivity))
# If we're in a between-round activity or a restart-activity, hop # If we're in a between-round activity or a restart-activity, hop
# into a round. # into a round.
elif isinstance( elif isinstance(
activity, activity,
(_activitytypes.JoinActivity, _activitytypes.TransitionActivity, (JoinActivity, TransitionActivity, ScoreScreenActivity)):
_activitytypes.ScoreScreenActivity)):
# If we're coming from a series-end activity, reset scores. # If we're coming from a series-end activity, reset scores.
if isinstance(activity, TeamSeriesVictoryScoreScreenActivity): if isinstance(activity, TeamSeriesVictoryScoreScreenActivity):
self.stats.reset() self.stats.reset()
self._game_number = 0 self._game_number = 0
for team in self.teams: for team in self.teams:
team.sessiondata['score'] = 0 team.customdata['score'] = 0
# Otherwise just set accum (per-game) scores. # Otherwise just set accum (per-game) scores.
else: else:
@ -221,7 +220,7 @@ class MultiTeamSession(Session):
# ..but only ones who have been placed on a team # ..but only ones who have been placed on a team
# (ie: no longer sitting in the lobby). # (ie: no longer sitting in the lobby).
try: try:
has_team = (player.team is not None) has_team = (player.sessionteam is not None)
except NotFoundError: except NotFoundError:
has_team = False has_team = False
if has_team: if has_team:
@ -263,7 +262,7 @@ class MultiTeamSession(Session):
_ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell'))) _ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell')))
if announce_winning_team: if announce_winning_team:
winning_team = results.get_winning_team() winning_team = results.winning_team
if winning_team is not None: if winning_team is not None:
# Have all players celebrate. # Have all players celebrate.
celebrate_msg = CelebrateMessage(duration=10.0) celebrate_msg = CelebrateMessage(duration=10.0)

View File

@ -23,7 +23,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, TypeVar, Generic from typing import TYPE_CHECKING, TypeVar, Generic, cast
import _ba import _ba
from ba._error import SessionPlayerNotFoundError, print_exception from ba._error import SessionPlayerNotFoundError, print_exception
@ -85,8 +85,7 @@ class Player(Generic[TeamType]):
_nodeactor: Optional[ba.NodeActor] _nodeactor: Optional[ba.NodeActor]
_expired: bool _expired: bool
_postinited: bool _postinited: bool
sessiondata: dict _customdata: dict
_gamedata: dict
# NOTE: avoiding having any __init__() here since it seems to not # NOTE: avoiding having any __init__() here since it seems to not
# get called by default if a dataclass inherits from us. # get called by default if a dataclass inherits from us.
@ -118,10 +117,9 @@ class Player(Generic[TeamType]):
self.character = sessionplayer.character self.character = sessionplayer.character
self.color = sessionplayer.color self.color = sessionplayer.color
self.highlight = sessionplayer.highlight self.highlight = sessionplayer.highlight
self._team = sessionplayer.team.gameteam # type: ignore self._team = cast(TeamType, sessionplayer.sessionteam.gameteam)
assert self._team is not None assert self._team is not None
self.sessiondata = sessionplayer.sessiondata self._customdata = {}
self._gamedata = {}
self._expired = False self._expired = False
self._postinited = True self._postinited = True
node = _ba.newnode('player', attrs={'playerID': sessionplayer.id}) node = _ba.newnode('player', attrs={'playerID': sessionplayer.id})
@ -144,7 +142,7 @@ class Player(Generic[TeamType]):
print_exception(f'Error killing actor on leave for {self}') print_exception(f'Error killing actor on leave for {self}')
self._nodeactor = None self._nodeactor = None
del self._team del self._team
del self._gamedata del self._customdata
def expire(self) -> None: def expire(self) -> None:
"""Called when the Player is expiring (when its Activity does so). """Called when the Player is expiring (when its Activity does so).
@ -163,7 +161,7 @@ class Player(Generic[TeamType]):
self._nodeactor = None self._nodeactor = None
self.actor = None self.actor = None
del self._team del self._team
del self._gamedata del self._customdata
def on_expire(self) -> None: def on_expire(self) -> None:
"""Can be overridden to handle player expiration. """Can be overridden to handle player expiration.
@ -182,7 +180,7 @@ class Player(Generic[TeamType]):
return self._team return self._team
@property @property
def gamedata(self) -> dict: def customdata(self) -> dict:
"""Arbitrary values associated with the player. """Arbitrary values associated with the player.
Though it is encouraged that most player values be properly defined Though it is encouraged that most player values be properly defined
on the ba.Player subclass, it may be useful for player-agnostic on the ba.Player subclass, it may be useful for player-agnostic
@ -193,7 +191,7 @@ class Player(Generic[TeamType]):
""" """
assert self._postinited assert self._postinited
assert not self._expired assert not self._expired
return self._gamedata return self._customdata
@property @property
def sessionplayer(self) -> ba.SessionPlayer: def sessionplayer(self) -> ba.SessionPlayer:

View File

@ -259,7 +259,7 @@ class Session:
else: else:
# Ok, they've already entered the game. Remove them from # Ok, they've already entered the game. Remove them from
# teams/activities/etc. # teams/activities/etc.
sessionteam = sessionplayer.team sessionteam = sessionplayer.sessionteam
assert sessionteam is not None assert sessionteam is not None
_ba.screenmessage( _ba.screenmessage(
@ -317,7 +317,7 @@ class Session:
else: else:
print('Team no in Session teams in on_player_leave.') print('Team no in Session teams in on_player_leave.')
try: try:
sessionteam.reset_sessiondata() sessionteam.leave()
except Exception: except Exception:
print_exception(f'Error clearing sessiondata' print_exception(f'Error clearing sessiondata'
f' for team {sessionteam} in session {self}.') f' for team {sessionteam} in session {self}.')

View File

@ -74,7 +74,7 @@ class PlayerRecord:
self._multi_kill_timer: Optional[ba.Timer] = None self._multi_kill_timer: Optional[ba.Timer] = None
self._multi_kill_count = 0 self._multi_kill_count = 0
self._stats = weakref.ref(stats) self._stats = weakref.ref(stats)
self._last_player: Optional[ba.SessionPlayer] = None self._last_sessionplayer: Optional[ba.SessionPlayer] = None
self._player: Optional[ba.SessionPlayer] = None self._player: Optional[ba.SessionPlayer] = None
self._team: Optional[ReferenceType[ba.SessionTeam]] = None self._team: Optional[ReferenceType[ba.SessionTeam]] = None
self.streak = 0 self.streak = 0
@ -110,7 +110,7 @@ class PlayerRecord:
def get_icon(self) -> Dict[str, Any]: def get_icon(self) -> Dict[str, Any]:
"""Get the icon for this instance's player.""" """Get the icon for this instance's player."""
player = self._last_player player = self._last_sessionplayer
assert player is not None assert player is not None
return player.get_icon() return player.get_icon()
@ -127,12 +127,12 @@ class PlayerRecord:
return stats.getactivity() return stats.getactivity()
return None return None
def associate_with_player(self, player: ba.SessionPlayer) -> None: def associate_with_player(self, sessionplayer: ba.SessionPlayer) -> None:
"""Associate this entry with a ba.Player.""" """Associate this entry with a ba.SessionPlayer."""
self._team = weakref.ref(player.team) self._team = weakref.ref(sessionplayer.sessionteam)
self.character = player.character self.character = sessionplayer.character
self._last_player = player self._last_sessionplayer = sessionplayer
self._player = player self._player = sessionplayer
self.streak = 0 self.streak = 0
def _end_multi_kill(self) -> None: def _end_multi_kill(self) -> None:
@ -141,8 +141,8 @@ class PlayerRecord:
def get_last_player(self) -> ba.SessionPlayer: def get_last_player(self) -> ba.SessionPlayer:
"""Return the last ba.Player we were associated with.""" """Return the last ba.Player we were associated with."""
assert self._last_player is not None assert self._last_sessionplayer is not None
return self._last_player return self._last_sessionplayer
def submit_kill(self, showpoints: bool = True) -> None: def submit_kill(self, showpoints: bool = True) -> None:
"""Submit a kill for this player entry.""" """Submit a kill for this player entry."""
@ -307,15 +307,14 @@ class Stats:
def register_player(self, player: ba.SessionPlayer) -> None: def register_player(self, player: ba.SessionPlayer) -> None:
"""Register a player with this score-set.""" """Register a player with this score-set."""
assert player.exists() # Invalid refs should never be passed to funcs.
name = player.getname() name = player.getname()
name_full = player.getname(full=True) if name in self._player_records:
try:
# If the player already exists, update his character and such as # If the player already exists, update his character and such as
# it may have changed. # it may have changed.
self._player_records[name].associate_with_player(player) self._player_records[name].associate_with_player(player)
except Exception: else:
# FIXME: Shouldn't use top level Exception catch for logic. name_full = player.getname(full=True)
# Should only have this as a fallback and always log it.
self._player_records[name] = PlayerRecord(name, name_full, player, self._player_records[name] = PlayerRecord(name, name_full, player,
self) self)
@ -331,11 +330,6 @@ class Stats:
records[record_id] = record records[record_id] = record
return records return records
def player_got_hit(self, player: ba.SessionPlayer) -> None:
"""Call this when a player got hit."""
s_player = self._player_records[player.getname()]
s_player.streak = 0
def player_scored(self, def player_scored(self,
player: ba.Player, player: ba.Player,
base_points: int = 1, base_points: int = 1,

View File

@ -56,15 +56,10 @@ class SessionTeam:
players players
The list of ba.SessionPlayers on the team. The list of ba.SessionPlayers on the team.
gamedata customdata
A dict for use by the current ba.Activity
for storing data associated with this team.
This gets cleared for each new ba.Activity.
sessiondata
A dict for use by the current ba.Session for A dict for use by the current ba.Session for
storing data associated with this team. storing data associated with this team.
Unlike gamedata, this persists for the duration Unlike customdata, this persists for the duration
of the session. of the session.
""" """
@ -72,8 +67,7 @@ class SessionTeam:
name: Union[ba.Lstr, str] name: Union[ba.Lstr, str]
color: Tuple[float, ...] # FIXME: can't we make this fixed len? color: Tuple[float, ...] # FIXME: can't we make this fixed len?
players: List[ba.SessionPlayer] players: List[ba.SessionPlayer]
gamedata: Dict customdata: dict
sessiondata: Dict
id: int id: int
def __init__(self, def __init__(self,
@ -90,12 +84,12 @@ class SessionTeam:
self.name = name self.name = name
self.color = tuple(color) self.color = tuple(color)
self.players = [] self.players = []
self.sessiondata = {} self.customdata = {}
self.gameteam: Optional[Team] = None self.gameteam: Optional[Team] = None
def reset_sessiondata(self) -> None: def leave(self) -> None:
"""(internal)""" """(internal)"""
self.sessiondata = {} self.customdata = {}
PlayerType = TypeVar('PlayerType', bound='ba.Player') PlayerType = TypeVar('PlayerType', bound='ba.Player')
@ -119,10 +113,7 @@ class Team(Generic[PlayerType]):
_sessionteam: ReferenceType[SessionTeam] _sessionteam: ReferenceType[SessionTeam]
_expired: bool _expired: bool
_postinited: bool _postinited: bool
_gamedata: dict _customdata: dict
# TODO: kill these.
sessiondata: dict
# NOTE: avoiding having any __init__() here since it seems to not # NOTE: avoiding having any __init__() here since it seems to not
# get called by default if a dataclass inherits from us. # get called by default if a dataclass inherits from us.
@ -149,8 +140,7 @@ class Team(Generic[PlayerType]):
self.id = sessionteam.id self.id = sessionteam.id
self.name = sessionteam.name self.name = sessionteam.name
self.color = sessionteam.color self.color = sessionteam.color
self.sessiondata = sessionteam.sessiondata self._customdata = {}
self._gamedata = {}
self._expired = False self._expired = False
self._postinited = True self._postinited = True
@ -160,13 +150,12 @@ class Team(Generic[PlayerType]):
self.id = team_id self.id = team_id
self.name = name self.name = name
self.color = color self.color = color
self._gamedata = {} self._customdata = {}
self.sessiondata = {}
self._expired = False self._expired = False
self._postinited = True self._postinited = True
@property @property
def gamedata(self) -> dict: def customdata(self) -> dict:
"""Arbitrary values associated with the team. """Arbitrary values associated with the team.
Though it is encouraged that most player values be properly defined Though it is encouraged that most player values be properly defined
on the ba.Team subclass, it may be useful for player-agnostic on the ba.Team subclass, it may be useful for player-agnostic
@ -177,7 +166,7 @@ class Team(Generic[PlayerType]):
""" """
assert self._postinited assert self._postinited
assert not self._expired assert not self._expired
return self._gamedata return self._customdata
def leave(self) -> None: def leave(self) -> None:
"""Called when the Team leaves a running game. """Called when the Team leaves a running game.
@ -186,7 +175,7 @@ class Team(Generic[PlayerType]):
""" """
assert self._postinited assert self._postinited
assert not self._expired assert not self._expired
del self._gamedata del self._customdata
del self.players del self.players
def expire(self) -> None: def expire(self) -> None:
@ -203,7 +192,7 @@ class Team(Generic[PlayerType]):
except Exception: except Exception:
print_exception(f'Error in on_expire for {self}.') print_exception(f'Error in on_expire for {self}.')
del self._gamedata del self._customdata
del self.players del self.players
def on_expire(self) -> None: def on_expire(self) -> None:

View File

@ -137,9 +137,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._tournament_time_remaining_text: Optional[Text] = None self._tournament_time_remaining_text: Optional[Text] = None
self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None
self._player_info: List[ba.PlayerInfo] = settings['player_info'] self._playerinfos: List[ba.PlayerInfo] = settings['playerinfos']
assert isinstance(self._player_info, list) assert isinstance(self._playerinfos, list)
assert (isinstance(i, ba.PlayerInfo) for i in self._player_info) assert (isinstance(i, ba.PlayerInfo) for i in self._playerinfos)
self._score: Optional[int] = settings['score'] self._score: Optional[int] = settings['score']
assert isinstance(self._score, (int, type(None))) assert isinstance(self._score, (int, type(None)))
@ -174,7 +174,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._game_name_str = self._campaign.name + ':' + self._level_name self._game_name_str = self._campaign.name + ':' + self._level_name
self._game_config_str = str(len( self._game_config_str = str(len(
self._player_info)) + 'p' + self._campaign.get_level( self._playerinfos)) + 'p' + self._campaign.get_level(
self._level_name).get_score_version_string().replace(' ', '_') self._level_name).get_score_version_string().replace(' ', '_')
# If game-center/etc scores are available we show our friends' # If game-center/etc scores are available we show our friends'
@ -580,12 +580,12 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ba.timer(5.2, ba.Call(ba.playsound, self._dingsound)) ba.timer(5.2, ba.Call(ba.playsound, self._dingsound))
offs_x = -195 offs_x = -195
if len(self._player_info) > 1: if len(self._playerinfos) > 1:
pstr = ba.Lstr(value='- ${A} -', pstr = ba.Lstr(value='- ${A} -',
subs=[('${A}', subs=[('${A}',
ba.Lstr(resource='multiPlayerCountText', ba.Lstr(resource='multiPlayerCountText',
subs=[('${COUNT}', subs=[('${COUNT}',
str(len(self._player_info))) str(len(self._playerinfos)))
]))]) ]))])
else: else:
pstr = ba.Lstr(value='- ${A} -', pstr = ba.Lstr(value='- ${A} -',
@ -636,7 +636,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ba.pushcall(ba.WeakCall(self._show_fail)) ba.pushcall(ba.WeakCall(self._show_fail))
self._name_str = name_str = ', '.join( self._name_str = name_str = ', '.join(
[p.name for p in self._player_info]) [p.name for p in self._playerinfos])
if self._show_friend_scores: if self._show_friend_scores:
self._friends_loading_status = Text( self._friends_loading_status = Text(
@ -662,10 +662,10 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
our_high_scores_all = self._campaign.get_level( our_high_scores_all = self._campaign.get_level(
self._level_name).get_high_scores() self._level_name).get_high_scores()
try: try:
our_high_scores = our_high_scores_all[str(len(self._player_info)) + our_high_scores = our_high_scores_all[str(len(self._playerinfos)) +
' Player'] ' Player']
except Exception: except Exception:
our_high_scores = our_high_scores_all[str(len(self._player_info)) + our_high_scores = our_high_scores_all[str(len(self._playerinfos)) +
' Player'] = [] ' Player'] = []
if self._score is not None: if self._score is not None:
@ -674,7 +674,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
'players': [{ 'players': [{
'name': p.name, 'name': p.name,
'character': p.character 'character': p.character
} for p in self._player_info] } for p in self._playerinfos]
} }
] ]
our_high_scores.append(our_score) our_high_scores.append(our_score)
@ -789,7 +789,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
v_offs_extra = 20 v_offs_extra = 20
v_offs_names = 0 v_offs_names = 0
scale = 1.0 scale = 1.0
p_count = len(self._player_info) p_count = len(self._playerinfos)
h_offs_extra -= 75 h_offs_extra -= 75
if p_count > 1: if p_count > 1:
h_offs_extra -= 20 h_offs_extra -= 20
@ -841,7 +841,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
position=(ts_h_offs + 35 + h_offs_extra, position=(ts_h_offs + 35 + h_offs_extra,
v_offs_extra + ts_height / 2 + -ts_height * v_offs_extra + ts_height / 2 + -ts_height *
(i + 1) / 10 + v_offs_names + v_offs + 11.0), (i + 1) / 10 + v_offs_names + v_offs + 11.0),
maxwidth=80.0 + 100.0 * len(self._player_info), maxwidth=80.0 + 100.0 * len(self._playerinfos),
v_align=Text.VAlign.CENTER, v_align=Text.VAlign.CENTER,
color=color1, color=color1,
flash=flash, flash=flash,
@ -1086,7 +1086,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
h_offs_extra = 0 h_offs_extra = 0
v_offs_names = 0 v_offs_names = 0
scale = 1.0 scale = 1.0
p_count = len(self._player_info) p_count = len(self._playerinfos)
if p_count > 1: if p_count > 1:
h_offs_extra -= 40 h_offs_extra -= 40
if self._score_type != 'points': if self._score_type != 'points':
@ -1146,7 +1146,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
position=(ts_h_offs + 35 + h_offs_extra, position=(ts_h_offs + 35 + h_offs_extra,
ts_height / 2 + -ts_height * (i + 1) / 10 + ts_height / 2 + -ts_height * (i + 1) / 10 +
v_offs_names + v_offs + 11.0), v_offs_names + v_offs + 11.0),
maxwidth=80.0 + 100.0 * len(self._player_info), maxwidth=80.0 + 100.0 * len(self._playerinfos),
v_align=Text.VAlign.CENTER, v_align=Text.VAlign.CENTER,
color=color1, color=color1,
flash=flash, flash=flash,

View File

@ -103,7 +103,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
def _show_team_name(self, pos_v: float, team: ba.SessionTeam, def _show_team_name(self, pos_v: float, team: ba.SessionTeam,
kill_delay: float, shiftdelay: float) -> None: kill_delay: float, shiftdelay: float) -> None:
del kill_delay # unused arg del kill_delay # Unused arg.
ZoomText(ba.Lstr(value='${A}:', subs=[('${A}', team.name)]), ZoomText(ba.Lstr(value='${A}:', subs=[('${A}', team.name)]),
position=(100, pos_v), position=(100, pos_v),
shiftposition=(-150, pos_v), shiftposition=(-150, pos_v),
@ -115,9 +115,9 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
color=team.color, color=team.color,
jitter=1.0).autoretain() jitter=1.0).autoretain()
def _show_team_old_score(self, pos_v: float, team: ba.SessionTeam, def _show_team_old_score(self, pos_v: float, sessionteam: ba.SessionTeam,
shiftdelay: float) -> None: shiftdelay: float) -> None:
ZoomText(str(team.sessiondata['score'] - 1), ZoomText(str(sessionteam.customdata['score'] - 1),
position=(150, pos_v), position=(150, pos_v),
maxwidth=100, maxwidth=100,
color=(0.6, 0.6, 0.7), color=(0.6, 0.6, 0.7),
@ -129,11 +129,11 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
h_align='left', h_align='left',
jitter=1.0).autoretain() jitter=1.0).autoretain()
def _show_team_score(self, pos_v: float, team: ba.SessionTeam, def _show_team_score(self, pos_v: float, sessionteam: ba.SessionTeam,
scored: bool, kill_delay: float, scored: bool, kill_delay: float,
shiftdelay: float) -> None: shiftdelay: float) -> None:
del kill_delay # unused arg del kill_delay # Unused arg.
ZoomText(str(team.sessiondata['score']), ZoomText(str(sessionteam.customdata['score']),
position=(150, pos_v), position=(150, pos_v),
maxwidth=100, maxwidth=100,
color=(1.0, 0.9, 0.5) if scored else (0.6, 0.6, 0.7), color=(1.0, 0.9, 0.5) if scored else (0.6, 0.6, 0.7),

View File

@ -28,7 +28,7 @@ import ba
from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Dict, Optional, Set from typing import Any, Dict, Optional, Set, Tuple
class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
@ -60,13 +60,17 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
player_order_prev = list(self.players) player_order_prev = list(self.players)
player_order_prev.sort( player_order_prev.sort(
reverse=True, reverse=True,
key=lambda p: key=lambda p: (
(p.team.sessiondata['previous_score'], p.getname(full=True))) p.team.sessionteam.customdata['previous_score'],
p.getname(full=True),
))
player_order = list(self.players) player_order = list(self.players)
player_order.sort(reverse=True, player_order.sort(reverse=True,
key=lambda p: key=lambda p: (
(p.team.sessiondata['score'], p.team.sessiondata[ p.team.sessionteam.customdata['score'],
'score'], p.getname(full=True))) p.team.sessionteam.customdata['score'],
p.getname(full=True),
))
v_offs = -74.0 + spacing * len(player_order_prev) * 0.5 v_offs = -74.0 + spacing * len(player_order_prev) * 0.5
delay1 = 1.3 + 0.1 delay1 = 1.3 + 0.1
@ -202,8 +206,9 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
transtime2: ts_h_offs - (95.0 + slide_amt) * scale transtime2: ts_h_offs - (95.0 + slide_amt) * scale
})) }))
s_txt = _scoretxt(str(player.team.sessiondata['previous_score']), s_txt = _scoretxt(
80, 0, False, 0, 1.0) str(player.team.sessionteam.customdata['previous_score']), 80,
0, False, 0, 1.0)
ba.timer( ba.timer(
tdelay + delay2, tdelay + delay2,
ba.WeakCall( ba.WeakCall(
@ -219,8 +224,9 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
transtime2: ts_h_offs + (80.0 - slide_amt) * scale transtime2: ts_h_offs + (80.0 - slide_amt) * scale
})) }))
score_change = (player.team.sessiondata['score'] - score_change = (
player.team.sessiondata['previous_score']) player.team.sessionteam.customdata['score'] -
player.team.sessionteam.customdata['previous_score'])
if score_change > 0: if score_change > 0:
xval = 113 xval = 113
yval = 3.0 yval = 3.0
@ -257,12 +263,11 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
tdelay + delay1, tdelay + delay1,
ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1))) ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)))
for j in range(score_change): for j in range(score_change):
ba.timer( ba.timer((tdelay + delay1 + 0.15 * j),
(tdelay + delay1 + 0.15 * j), ba.Call(
ba.Call( _safesetattr, s_txt.node, 'text',
_safesetattr, s_txt.node, 'text', str(player.team.sessionteam.
str(player.team.sessiondata['previous_score'] + j + customdata['previous_score'] + j + 1)))
1)))
tfin = tdelay + delay1 + 0.15 * j tfin = tdelay + delay1 + 0.15 * j
if tfin not in sound_times: if tfin not in sound_times:
sound_times.add(tfin) sound_times.add(tfin)

View File

@ -121,8 +121,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
# Results is already sorted; just convert it into a list of # Results is already sorted; just convert it into a list of
# score-set-entries. # score-set-entries.
for winner in results.get_winners(): for winnergroup in results.winnergroups:
for team in winner.teams: for team in winnergroup.teams:
if len(team.players) == 1: if len(team.players) == 1:
player_entry = _get_player_score_set_entry( player_entry = _get_player_score_set_entry(
team.players[0]) team.players[0])
@ -172,8 +172,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
_txt(180, 4, ba.Lstr(resource='killsText')) _txt(180, 4, ba.Lstr(resource='killsText'))
_txt(280, 4, ba.Lstr(resource='deathsText'), maxwidth=100) _txt(280, 4, ba.Lstr(resource='deathsText'), maxwidth=100)
score_name = 'Score' if results is None else results.get_score_name() score_label = 'Score' if results is None else results.score_label
translated = ba.Lstr(translate=('scoreNames', score_name)) translated = ba.Lstr(translate=('scoreNames', score_label))
_txt(390, 0, translated) _txt(390, 0, translated)

View File

@ -79,7 +79,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
for _pkey, prec in self.stats.get_records().items(): for _pkey, prec in self.stats.get_records().items():
if prec.player.in_game: if prec.player.in_game:
player_entries.append( player_entries.append(
(prec.player.team.sessiondata['score'], (prec.player.sessionteam.customdata['score'],
prec.getname(full=True), prec)) prec.getname(full=True), prec))
player_entries.sort(reverse=True, key=lambda x: x[0]) player_entries.sort(reverse=True, key=lambda x: x[0])
else: else:
@ -145,8 +145,8 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
win_score = (session.get_series_length() - 1) // 2 + 1 win_score = (session.get_series_length() - 1) // 2 + 1
lose_score = 0 lose_score = 0
for team in self.teams: for team in self.teams:
if team.sessiondata['score'] != win_score: if team.sessionteam.customdata['score'] != win_score:
lose_score = team.sessiondata['score'] lose_score = team.sessionteam.customdata['score']
if not self._is_ffa: if not self._is_ffa:
Text(ba.Lstr(resource='gamesToText', Text(ba.Lstr(resource='gamesToText',
@ -309,7 +309,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
for _score, name, prec in player_entries: for _score, name, prec in player_entries:
tdelay -= 4 * t_incr tdelay -= 4 * t_incr
v_offs -= 40 v_offs -= 40
Text(str(prec.team.sessiondata['score']) Text(str(prec.team.customdata['score'])
if self._is_ffa else str(prec.score), if self._is_ffa else str(prec.score),
color=(0.5, 0.5, 0.5, 1.0), color=(0.5, 0.5, 0.5, 1.0),
position=(ts_h_offs + 230, ts_height / 2 + v_offs), position=(ts_h_offs + 230, ts_height / 2 + v_offs),

View File

@ -39,6 +39,9 @@ class RespawnIcon:
This is used to indicate that a ba.Player is waiting to respawn. This is used to indicate that a ba.Player is waiting to respawn.
""" """
_MASKTEXSTORENAME = ba.storagename('masktex')
_ICONSSTORENAME = ba.storagename('icons')
def __init__(self, player: ba.Player, respawn_time: float): def __init__(self, player: ba.Player, respawn_time: float):
"""Instantiate with a ba.Player and respawn_time (in seconds).""" """Instantiate with a ba.Player and respawn_time (in seconds)."""
self._visible = True self._visible = True
@ -46,10 +49,10 @@ class RespawnIcon:
on_right, offs_extra, respawn_icons = self._get_context(player) on_right, offs_extra, respawn_icons = self._get_context(player)
# Cache our mask tex on the team for easy access. # Cache our mask tex on the team for easy access.
mask_tex = player.team.gamedata.get('_spaz_respawn_icons_mask_tex') mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME)
if mask_tex is None: if mask_tex is None:
mask_tex = ba.gettexture('characterIconMask') mask_tex = ba.gettexture('characterIconMask')
player.team.gamedata['_spaz_respawn_icons_mask_tex'] = mask_tex player.team.customdata[self._MASKTEXSTORENAME] = mask_tex
assert isinstance(mask_tex, ba.Texture) assert isinstance(mask_tex, ba.Texture)
# Now find the first unused slot and use that. # Now find the first unused slot and use that.
@ -139,9 +142,9 @@ class RespawnIcon:
on_right = player.team.id % 2 == 1 on_right = player.team.id % 2 == 1
# Store a list of icons in the team. # Store a list of icons in the team.
icons = player.team.gamedata.get('_spaz_respawn_icons') icons = player.team.customdata.get(self._ICONSSTORENAME)
if icons is None: if icons is None:
player.team.gamedata['_spaz_respawn_icons'] = icons = {} player.team.customdata[self._ICONSSTORENAME] = icons = {}
assert isinstance(icons, dict) assert isinstance(icons, dict)
offs_extra = -20 offs_extra = -20
@ -149,9 +152,9 @@ class RespawnIcon:
on_right = False on_right = False
# Store a list of icons in the activity. # Store a list of icons in the activity.
icons = activity.gamedata.get('_spaz_respawn_icons') icons = activity.customdata.get(self._ICONSSTORENAME)
if icons is None: if icons is None:
activity.gamedata['_spaz_respawn_icons'] = icons = {} activity.customdata[self._ICONSSTORENAME] = icons = {}
assert isinstance(icons, dict) assert isinstance(icons, dict)
if isinstance(activity.session, ba.FreeForAllSession): if isinstance(activity.session, ba.FreeForAllSession):

View File

@ -343,6 +343,8 @@ class Scoreboard:
category: Gameplay Classes category: Gameplay Classes
""" """
_STORENAME = ba.storagename('entry')
def __init__(self, label: ba.Lstr = None, score_split: float = 0.7): def __init__(self, label: ba.Lstr = None, score_split: float = 0.7):
"""Instantiate a scoreboard. """Instantiate a scoreboard.
@ -382,8 +384,8 @@ class Scoreboard:
# Create a proxy in the team which will kill # Create a proxy in the team which will kill
# our entry when it dies (for convenience) # our entry when it dies (for convenience)
assert '_scoreboard_entry' not in team.gamedata assert self._STORENAME not in team.customdata
team.gamedata['_scoreboard_entry'] = _EntryProxy(self, team) team.customdata[self._STORENAME] = _EntryProxy(self, team)
# Now set the entry. # Now set the entry.
self._entries[team.id].set_value(score=score, self._entries[team.id].set_value(score=score,

View File

@ -61,23 +61,25 @@ class ConquestFlag(Flag):
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
"""Our player type for this game.""" """Our player type for this game."""
# FIXME: We shouldn't be using customdata here
# (but need to update respawn funcs accordingly first).
@property @property
def respawn_timer(self) -> Optional[ba.Timer]: def respawn_timer(self) -> Optional[ba.Timer]:
"""Type safe access to standard respawn timer.""" """Type safe access to standard respawn timer."""
return self.gamedata.get('respawn_timer', None) return self.customdata.get('respawn_timer', None)
@respawn_timer.setter @respawn_timer.setter
def respawn_timer(self, value: Optional[ba.Timer]) -> None: def respawn_timer(self, value: Optional[ba.Timer]) -> None:
self.gamedata['respawn_timer'] = value self.customdata['respawn_timer'] = value
@property @property
def respawn_icon(self) -> Optional[RespawnIcon]: def respawn_icon(self) -> Optional[RespawnIcon]:
"""Type safe access to standard respawn icon.""" """Type safe access to standard respawn icon."""
return self.gamedata.get('respawn_icon', None) return self.customdata.get('respawn_icon', None)
@respawn_icon.setter @respawn_icon.setter
def respawn_icon(self, value: Optional[RespawnIcon]) -> None: def respawn_icon(self, value: Optional[RespawnIcon]) -> None:
self.gamedata['respawn_icon'] = value self.customdata['respawn_icon'] = value
class Team(ba.Team[Player]): class Team(ba.Team[Player]):

View File

@ -203,8 +203,8 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
# Respawn them shortly. # Respawn them shortly.
player = msg.getplayer(Player) player = msg.getplayer(Player)
assert self.initial_player_info is not None assert self.initialplayerinfos is not None
respawn_time = 2.0 + len(self.initial_player_info) * 1.0 respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
player.respawn_timer = ba.Timer( player.respawn_timer = ba.Timer(
respawn_time, ba.Call(self.spawn_player_if_exists, player)) respawn_time, ba.Call(self.spawn_player_if_exists, player))
player.respawn_icon = RespawnIcon(player, respawn_time) player.respawn_icon = RespawnIcon(player, respawn_time)

View File

@ -441,7 +441,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
if ba.app.kiosk_mode: if ba.app.kiosk_mode:
controlsguide.ControlsGuide(delay=3.0, lifespan=10.0, controlsguide.ControlsGuide(delay=3.0, lifespan=10.0,
bright=True).autoretain() bright=True).autoretain()
assert self.initial_player_info is not None assert self.initialplayerinfos is not None
abot: Type[SpazBot] abot: Type[SpazBot]
bbot: Type[SpazBot] bbot: Type[SpazBot]
cbot: Type[SpazBot] cbot: Type[SpazBot]
@ -450,36 +450,36 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
self._have_tnt = False self._have_tnt = False
abot = (BrawlerBotLite abot = (BrawlerBotLite
if self._preset == 'rookie_easy' else BrawlerBot) if self._preset == 'rookie_easy' else BrawlerBot)
self._bot_types_initial = [abot] * len(self.initial_player_info) self._bot_types_initial = [abot] * len(self.initialplayerinfos)
bbot = (BomberBotLite bbot = (BomberBotLite
if self._preset == 'rookie_easy' else BomberBot) if self._preset == 'rookie_easy' else BomberBot)
self._bot_types_7 = ( self._bot_types_7 = (
[bbot] * (1 if len(self.initial_player_info) < 3 else 2)) [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot) cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot)
self._bot_types_14 = ( self._bot_types_14 = (
[cbot] * (1 if len(self.initial_player_info) < 3 else 2)) [cbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
elif self._preset == 'tournament': elif self._preset == 'tournament':
self._exclude_powerups = [] self._exclude_powerups = []
self._have_tnt = True self._have_tnt = True
self._bot_types_initial = ( self._bot_types_initial = (
[BrawlerBot] * (1 if len(self.initial_player_info) < 2 else 2)) [BrawlerBot] * (1 if len(self.initialplayerinfos) < 2 else 2))
self._bot_types_7 = ( self._bot_types_7 = (
[TriggerBot] * (1 if len(self.initial_player_info) < 3 else 2)) [TriggerBot] * (1 if len(self.initialplayerinfos) < 3 else 2))
self._bot_types_14 = ( self._bot_types_14 = (
[ChargerBot] * (1 if len(self.initial_player_info) < 4 else 2)) [ChargerBot] * (1 if len(self.initialplayerinfos) < 4 else 2))
elif self._preset in ['pro', 'pro_easy', 'tournament_pro']: elif self._preset in ['pro', 'pro_easy', 'tournament_pro']:
self._exclude_powerups = ['curse'] self._exclude_powerups = ['curse']
self._have_tnt = True self._have_tnt = True
self._bot_types_initial = [ChargerBot] * len( self._bot_types_initial = [ChargerBot] * len(
self.initial_player_info) self.initialplayerinfos)
abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite) abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite)
typed_bot_list: List[Type[SpazBot]] = [] typed_bot_list: List[Type[SpazBot]] = []
self._bot_types_7 = ( self._bot_types_7 = (
typed_bot_list + [abot] + [BomberBot] * typed_bot_list + [abot] + [BomberBot] *
(1 if len(self.initial_player_info) < 3 else 2)) (1 if len(self.initialplayerinfos) < 3 else 2))
bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot) bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot)
self._bot_types_14 = ( self._bot_types_14 = (
[bbot] * (1 if len(self.initial_player_info) < 3 else 2)) [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
elif self._preset in ['uber', 'uber_easy']: elif self._preset in ['uber', 'uber_easy']:
self._exclude_powerups = [] self._exclude_powerups = []
self._have_tnt = True self._have_tnt = True
@ -487,12 +487,11 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot) bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot)
typed_bot_list_2: List[Type[SpazBot]] = [] typed_bot_list_2: List[Type[SpazBot]] = []
self._bot_types_initial = (typed_bot_list_2 + [StickyBot] + self._bot_types_initial = (typed_bot_list_2 + [StickyBot] +
[abot] * len(self.initial_player_info)) [abot] * len(self.initialplayerinfos))
self._bot_types_7 = ( self._bot_types_7 = (
[bbot] * (1 if len(self.initial_player_info) < 3 else 2)) [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
self._bot_types_14 = ( self._bot_types_14 = (
[ExplodeyBot] * [ExplodeyBot] * (1 if len(self.initialplayerinfos) < 3 else 2))
(1 if len(self.initial_player_info) < 3 else 2))
else: else:
raise Exception() raise Exception()
@ -794,7 +793,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
'outcome': outcome, 'outcome': outcome,
'score': scoreval, 'score': scoreval,
'score_order': 'decreasing', 'score_order': 'decreasing',
'player_info': self.initial_player_info 'playerinfos': self.initialplayerinfos
}) })
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
@ -805,8 +804,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
# Respawn them shortly. # Respawn them shortly.
player = msg.getplayer(Player) player = msg.getplayer(Player)
assert self.initial_player_info is not None assert self.initialplayerinfos is not None
respawn_time = 2.0 + len(self.initial_player_info) * 1.0 respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
player.respawn_timer = ba.Timer( player.respawn_timer = ba.Timer(
respawn_time, ba.Call(self.spawn_player_if_exists, player)) respawn_time, ba.Call(self.spawn_player_if_exists, player))
player.respawn_icon = RespawnIcon(player, respawn_time) player.respawn_icon = RespawnIcon(player, respawn_time)

View File

@ -154,7 +154,6 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
ba.timer(1.0, self._tick, repeat=True) ba.timer(1.0, self._tick, repeat=True)
self._flag_state = FlagState.NEW self._flag_state = FlagState.NEW
Flag.project_stand(self._flag_pos) Flag.project_stand(self._flag_pos)
self._flag = Flag(position=self._flag_pos, self._flag = Flag(position=self._flag_pos,
touchable=False, touchable=False,
color=(1, 1, 1)) color=(1, 1, 1))

View File

@ -113,12 +113,12 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]):
ChargerBot, pos=(-5, 3, -2), spawn_time=3.0)) ChargerBot, pos=(-5, 3, -2), spawn_time=3.0))
# Add some extras for multiplayer or pro mode. # Add some extras for multiplayer or pro mode.
assert self.initial_player_info is not None assert self.initialplayerinfos is not None
if len(self.initial_player_info) > 2 or is_pro: if len(self.initialplayerinfos) > 2 or is_pro:
ba.timer( ba.timer(
5.0, lambda: self._bots.spawn_bot( 5.0, lambda: self._bots.spawn_bot(
ChargerBot, pos=(0, 3, -5), spawn_time=3.0)) ChargerBot, pos=(0, 3, -5), spawn_time=3.0))
if len(self.initial_player_info) > 3 or is_pro: if len(self.initialplayerinfos) > 3 or is_pro:
ba.timer( ba.timer(
6.0, lambda: self._bots.spawn_bot( 6.0, lambda: self._bots.spawn_bot(
ChargerBot, pos=(0, 3, 1), spawn_time=3.0)) ChargerBot, pos=(0, 3, 1), spawn_time=3.0))

View File

@ -757,7 +757,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
'outcome': outcome, 'outcome': outcome,
'score': score, 'score': score,
'fail_message': fail_message, 'fail_message': fail_message,
'player_info': self.initial_player_info 'playerinfos': self.initialplayerinfos
}, },
delay=delay) delay=delay)
@ -844,10 +844,10 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
for player in self.players: for player in self.players:
try: try:
if player.is_alive(): if player.is_alive():
assert self.initial_player_info is not None assert self.initialplayerinfos is not None
self.stats.player_scored( self.stats.player_scored(
player, player,
int(100 / len(self.initial_player_info)), int(100 / len(self.initialplayerinfos)),
scale=1.4, scale=1.4,
color=(0.6, 0.6, 1.0, 1.0), color=(0.6, 0.6, 1.0, 1.0),
title=ba.Lstr(resource='completionBonusText'), title=ba.Lstr(resource='completionBonusText'),

View File

@ -563,7 +563,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
'outcome': outcome, 'outcome': outcome,
'score': score, 'score': score,
'fail_message': fail_message, 'fail_message': fail_message,
'player_info': self.initial_player_info 'playerinfos': self.initialplayerinfos
}) })
def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None:
@ -1118,8 +1118,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
# Respawn them shortly. # Respawn them shortly.
player = msg.getplayer(Player) player = msg.getplayer(Player)
assert self.initial_player_info is not None assert self.initialplayerinfos is not None
respawn_time = 2.0 + len(self.initial_player_info) * 1.0 respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
player.respawn_timer = ba.Timer( player.respawn_timer = ba.Timer(
respawn_time, ba.Call(self.spawn_player_if_exists, player)) respawn_time, ba.Call(self.spawn_player_if_exists, player))
player.respawn_icon = RespawnIcon(player, respawn_time) player.respawn_icon = RespawnIcon(player, respawn_time)

View File

@ -196,7 +196,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
results={ results={
'outcome': outcome, 'outcome': outcome,
'score': self._score, 'score': self._score,
'player_info': self.initial_player_info 'playerinfos': self.initialplayerinfos
}) })
def _update_bots(self) -> None: def _update_bots(self) -> None:

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND --> <!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-06-01 for Ballistica version 1.5.0 build 20039</em></h4> <h4><em>last updated on 2020-06-02 for Ballistica version 1.5.0 build 20041</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module, <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> 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> <hr>
@ -92,6 +92,7 @@
<li><a href="#function_ba_screenmessage">ba.screenmessage()</a></li> <li><a href="#function_ba_screenmessage">ba.screenmessage()</a></li>
<li><a href="#function_ba_set_analytics_screen">ba.set_analytics_screen()</a></li> <li><a href="#function_ba_set_analytics_screen">ba.set_analytics_screen()</a></li>
<li><a href="#function_ba_setlanguage">ba.setlanguage()</a></li> <li><a href="#function_ba_setlanguage">ba.setlanguage()</a></li>
<li><a href="#function_ba_storagename">ba.storagename()</a></li>
<li><a href="#function_ba_time">ba.time()</a></li> <li><a href="#function_ba_time">ba.time()</a></li>
<li><a href="#function_ba_timer">ba.timer()</a></li> <li><a href="#function_ba_timer">ba.timer()</a></li>
<li><a href="#function_ba_timestring">ba.timestring()</a></li> <li><a href="#function_ba_timestring">ba.timestring()</a></li>
@ -343,8 +344,16 @@ actually award achievements.</p>
can overlap during transitions.</p> can overlap during transitions.</p>
<h3>Attributes:</h3> <h3>Attributes:</h3>
<h5><a href="#attr_ba_Activity__expired">expired</a>, <a href="#attr_ba_Activity__gamedata">gamedata</a>, <a href="#attr_ba_Activity__globalsnode">globalsnode</a>, <a href="#attr_ba_Activity__players">players</a>, <a href="#attr_ba_Activity__playertype">playertype</a>, <a href="#attr_ba_Activity__session">session</a>, <a href="#attr_ba_Activity__settings_raw">settings_raw</a>, <a href="#attr_ba_Activity__stats">stats</a>, <a href="#attr_ba_Activity__teams">teams</a>, <a href="#attr_ba_Activity__teamtype">teamtype</a></h5> <h5><a href="#attr_ba_Activity__customdata">customdata</a>, <a href="#attr_ba_Activity__expired">expired</a>, <a href="#attr_ba_Activity__globalsnode">globalsnode</a>, <a href="#attr_ba_Activity__players">players</a>, <a href="#attr_ba_Activity__playertype">playertype</a>, <a href="#attr_ba_Activity__session">session</a>, <a href="#attr_ba_Activity__settings_raw">settings_raw</a>, <a href="#attr_ba_Activity__stats">stats</a>, <a href="#attr_ba_Activity__teams">teams</a>, <a href="#attr_ba_Activity__teamtype">teamtype</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_Activity__customdata">customdata</a></h4></dt><dd>
<p><span>dict</span></p>
<p>Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired
activities.</p>
</dd>
<dt><h4><a name="attr_ba_Activity__expired">expired</a></h4></dt><dd> <dt><h4><a name="attr_ba_Activity__expired">expired</a></h4></dt><dd>
<p><span>bool</span></p> <p><span>bool</span></p>
<p>Whether the activity is expired.</p> <p>Whether the activity is expired.</p>
@ -353,14 +362,6 @@ actually award achievements.</p>
At this point no new nodes, timers, etc should be made, At this point no new nodes, timers, etc should be made,
run, etc, and the activity should be considered a 'zombie'.</p> run, etc, and the activity should be considered a 'zombie'.</p>
</dd>
<dt><h4><a name="attr_ba_Activity__gamedata">gamedata</a></h4></dt><dd>
<p><span>dict</span></p>
<p>Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired
activities.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_Activity__globalsnode">globalsnode</a></h4></dt><dd> <dt><h4><a name="attr_ba_Activity__globalsnode">globalsnode</a></h4></dt><dd>
<p><span><a href="#class_ba_Node">ba.Node</a></span></p> <p><span><a href="#class_ba_Node">ba.Node</a></span></p>
@ -1552,8 +1553,16 @@ start_long_action(callback_when_done=<a href="#class_ba_ContextCall">ba.ContextC
<h3>Attributes Inherited:</h3> <h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Activity__players">players</a>, <a href="#attr_ba_Activity__settings_raw">settings_raw</a>, <a href="#attr_ba_Activity__teams">teams</a></h5> <h5><a href="#attr_ba_Activity__players">players</a>, <a href="#attr_ba_Activity__settings_raw">settings_raw</a>, <a href="#attr_ba_Activity__teams">teams</a></h5>
<h3>Attributes Defined Here:</h3> <h3>Attributes Defined Here:</h3>
<h5><a href="#attr_ba_CoopGameActivity__expired">expired</a>, <a href="#attr_ba_CoopGameActivity__gamedata">gamedata</a>, <a href="#attr_ba_CoopGameActivity__globalsnode">globalsnode</a>, <a href="#attr_ba_CoopGameActivity__map">map</a>, <a href="#attr_ba_CoopGameActivity__playertype">playertype</a>, <a href="#attr_ba_CoopGameActivity__session">session</a>, <a href="#attr_ba_CoopGameActivity__stats">stats</a>, <a href="#attr_ba_CoopGameActivity__teamtype">teamtype</a></h5> <h5><a href="#attr_ba_CoopGameActivity__customdata">customdata</a>, <a href="#attr_ba_CoopGameActivity__expired">expired</a>, <a href="#attr_ba_CoopGameActivity__globalsnode">globalsnode</a>, <a href="#attr_ba_CoopGameActivity__map">map</a>, <a href="#attr_ba_CoopGameActivity__playertype">playertype</a>, <a href="#attr_ba_CoopGameActivity__session">session</a>, <a href="#attr_ba_CoopGameActivity__stats">stats</a>, <a href="#attr_ba_CoopGameActivity__teamtype">teamtype</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_CoopGameActivity__customdata">customdata</a></h4></dt><dd>
<p><span>dict</span></p>
<p>Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired
activities.</p>
</dd>
<dt><h4><a name="attr_ba_CoopGameActivity__expired">expired</a></h4></dt><dd> <dt><h4><a name="attr_ba_CoopGameActivity__expired">expired</a></h4></dt><dd>
<p><span>bool</span></p> <p><span>bool</span></p>
<p>Whether the activity is expired.</p> <p>Whether the activity is expired.</p>
@ -1562,14 +1571,6 @@ start_long_action(callback_when_done=<a href="#class_ba_ContextCall">ba.ContextC
At this point no new nodes, timers, etc should be made, At this point no new nodes, timers, etc should be made,
run, etc, and the activity should be considered a 'zombie'.</p> run, etc, and the activity should be considered a 'zombie'.</p>
</dd>
<dt><h4><a name="attr_ba_CoopGameActivity__gamedata">gamedata</a></h4></dt><dd>
<p><span>dict</span></p>
<p>Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired
activities.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_CoopGameActivity__globalsnode">globalsnode</a></h4></dt><dd> <dt><h4><a name="attr_ba_CoopGameActivity__globalsnode">globalsnode</a></h4></dt><dd>
<p><span><a href="#class_ba_Node">ba.Node</a></span></p> <p><span><a href="#class_ba_Node">ba.Node</a></span></p>
@ -2145,8 +2146,16 @@ its time with lingering corpses, sound effects, etc.</p>
<h3>Attributes Inherited:</h3> <h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Activity__players">players</a>, <a href="#attr_ba_Activity__settings_raw">settings_raw</a>, <a href="#attr_ba_Activity__teams">teams</a></h5> <h5><a href="#attr_ba_Activity__players">players</a>, <a href="#attr_ba_Activity__settings_raw">settings_raw</a>, <a href="#attr_ba_Activity__teams">teams</a></h5>
<h3>Attributes Defined Here:</h3> <h3>Attributes Defined Here:</h3>
<h5><a href="#attr_ba_GameActivity__expired">expired</a>, <a href="#attr_ba_GameActivity__gamedata">gamedata</a>, <a href="#attr_ba_GameActivity__globalsnode">globalsnode</a>, <a href="#attr_ba_GameActivity__map">map</a>, <a href="#attr_ba_GameActivity__playertype">playertype</a>, <a href="#attr_ba_GameActivity__session">session</a>, <a href="#attr_ba_GameActivity__stats">stats</a>, <a href="#attr_ba_GameActivity__teamtype">teamtype</a></h5> <h5><a href="#attr_ba_GameActivity__customdata">customdata</a>, <a href="#attr_ba_GameActivity__expired">expired</a>, <a href="#attr_ba_GameActivity__globalsnode">globalsnode</a>, <a href="#attr_ba_GameActivity__map">map</a>, <a href="#attr_ba_GameActivity__playertype">playertype</a>, <a href="#attr_ba_GameActivity__session">session</a>, <a href="#attr_ba_GameActivity__stats">stats</a>, <a href="#attr_ba_GameActivity__teamtype">teamtype</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_GameActivity__customdata">customdata</a></h4></dt><dd>
<p><span>dict</span></p>
<p>Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired
activities.</p>
</dd>
<dt><h4><a name="attr_ba_GameActivity__expired">expired</a></h4></dt><dd> <dt><h4><a name="attr_ba_GameActivity__expired">expired</a></h4></dt><dd>
<p><span>bool</span></p> <p><span>bool</span></p>
<p>Whether the activity is expired.</p> <p>Whether the activity is expired.</p>
@ -2155,14 +2164,6 @@ its time with lingering corpses, sound effects, etc.</p>
At this point no new nodes, timers, etc should be made, At this point no new nodes, timers, etc should be made,
run, etc, and the activity should be considered a 'zombie'.</p> run, etc, and the activity should be considered a 'zombie'.</p>
</dd>
<dt><h4><a name="attr_ba_GameActivity__gamedata">gamedata</a></h4></dt><dd>
<p><span>dict</span></p>
<p>Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired
activities.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_GameActivity__globalsnode">globalsnode</a></h4></dt><dd> <dt><h4><a name="attr_ba_GameActivity__globalsnode">globalsnode</a></h4></dt><dd>
<p><span><a href="#class_ba_Node">ba.Node</a></span></p> <p><span><a href="#class_ba_Node">ba.Node</a></span></p>
@ -3871,14 +3872,14 @@ even if myactor is set to None.</p>
own custom <a href="#class_ba_Player">ba.Player</a> types.</p> own custom <a href="#class_ba_Player">ba.Player</a> types.</p>
<h3>Attributes:</h3> <h3>Attributes:</h3>
<h5><a href="#attr_ba_Player__actor">actor</a>, <a href="#attr_ba_Player__gamedata">gamedata</a>, <a href="#attr_ba_Player__node">node</a>, <a href="#attr_ba_Player__position">position</a>, <a href="#attr_ba_Player__sessionplayer">sessionplayer</a>, <a href="#attr_ba_Player__team">team</a></h5> <h5><a href="#attr_ba_Player__actor">actor</a>, <a href="#attr_ba_Player__customdata">customdata</a>, <a href="#attr_ba_Player__node">node</a>, <a href="#attr_ba_Player__position">position</a>, <a href="#attr_ba_Player__sessionplayer">sessionplayer</a>, <a href="#attr_ba_Player__team">team</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_Player__actor">actor</a></h4></dt><dd> <dt><h4><a name="attr_ba_Player__actor">actor</a></h4></dt><dd>
<p><span>Optional[<a href="#class_ba_Actor">ba.Actor</a>]</span></p> <p><span>Optional[<a href="#class_ba_Actor">ba.Actor</a>]</span></p>
<p>The <a href="#class_ba_Actor">ba.Actor</a> associated with the player.</p> <p>The <a href="#class_ba_Actor">ba.Actor</a> associated with the player.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_Player__gamedata">gamedata</a></h4></dt><dd> <dt><h4><a name="attr_ba_Player__customdata">customdata</a></h4></dt><dd>
<p><span>dict</span></p> <p><span>dict</span></p>
<p>Arbitrary values associated with the player. <p>Arbitrary values associated with the player.
Though it is encouraged that most player values be properly defined Though it is encouraged that most player values be properly defined
@ -4111,9 +4112,9 @@ the type-checker properly identifies the returned value as one.</p>
</dd> </dd>
<dt><h4><a name="method_ba_PlayerRecord__associate_with_player">associate_with_player()</a></dt></h4><dd> <dt><h4><a name="method_ba_PlayerRecord__associate_with_player">associate_with_player()</a></dt></h4><dd>
<p><span>associate_with_player(self, player: <a href="#class_ba_SessionPlayer">ba.SessionPlayer</a>) -&gt; None</span></p> <p><span>associate_with_player(self, sessionplayer: <a href="#class_ba_SessionPlayer">ba.SessionPlayer</a>) -&gt; None</span></p>
<p>Associate this entry with a <a href="#class_ba_Player">ba.Player</a>.</p> <p>Associate this entry with a <a href="#class_ba_SessionPlayer">ba.SessionPlayer</a>.</p>
</dd> </dd>
<dt><h4><a name="method_ba_PlayerRecord__cancel_multi_kill_timer">cancel_multi_kill_timer()</a></dt></h4><dd> <dt><h4><a name="method_ba_PlayerRecord__cancel_multi_kill_timer">cancel_multi_kill_timer()</a></dt></h4><dd>
@ -4556,7 +4557,7 @@ that a SessionPlayer is still present if retaining references to one
for any length of time.</p> for any length of time.</p>
<h3>Attributes:</h3> <h3>Attributes:</h3>
<h5><a href="#attr_ba_SessionPlayer__character">character</a>, <a href="#attr_ba_SessionPlayer__color">color</a>, <a href="#attr_ba_SessionPlayer__gameplayer">gameplayer</a>, <a href="#attr_ba_SessionPlayer__highlight">highlight</a>, <a href="#attr_ba_SessionPlayer__id">id</a>, <a href="#attr_ba_SessionPlayer__in_game">in_game</a>, <a href="#attr_ba_SessionPlayer__inputdevice">inputdevice</a>, <a href="#attr_ba_SessionPlayer__sessiondata">sessiondata</a>, <a href="#attr_ba_SessionPlayer__team">team</a></h5> <h5><a href="#attr_ba_SessionPlayer__character">character</a>, <a href="#attr_ba_SessionPlayer__color">color</a>, <a href="#attr_ba_SessionPlayer__gameplayer">gameplayer</a>, <a href="#attr_ba_SessionPlayer__highlight">highlight</a>, <a href="#attr_ba_SessionPlayer__id">id</a>, <a href="#attr_ba_SessionPlayer__in_game">in_game</a>, <a href="#attr_ba_SessionPlayer__inputdevice">inputdevice</a>, <a href="#attr_ba_SessionPlayer__sessionteam">sessionteam</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_SessionPlayer__character">character</a></h4></dt><dd> <dt><h4><a name="attr_ba_SessionPlayer__character">character</a></h4></dt><dd>
<p><span> str</span></p> <p><span> str</span></p>
@ -4602,14 +4603,7 @@ any lobby character/team selection.</p>
<p>The input device associated with the player.</p> <p>The input device associated with the player.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_SessionPlayer__sessiondata">sessiondata</a></h4></dt><dd> <dt><h4><a name="attr_ba_SessionPlayer__sessionteam">sessionteam</a></h4></dt><dd>
<p><span> Dict</span></p>
<p>A dict for use by the current <a href="#class_ba_Session">ba.Session</a> for
storing data associated with this player.
This persists for the duration of the session.</p>
</dd>
<dt><h4><a name="attr_ba_SessionPlayer__team">team</a></h4></dt><dd>
<p><span> <a href="#class_ba_SessionTeam">ba.SessionTeam</a></span></p> <p><span> <a href="#class_ba_SessionTeam">ba.SessionTeam</a></span></p>
<p>The <a href="#class_ba_SessionTeam">ba.SessionTeam</a> this Player is on. If the SessionPlayer <p>The <a href="#class_ba_SessionTeam">ba.SessionTeam</a> this Player is on. If the SessionPlayer
is still in its lobby selecting a team/etc. then a is still in its lobby selecting a team/etc. then a
@ -4708,18 +4702,19 @@ other players.</p>
each SessionTeam consists of just one SessionPlayer.</p> each SessionTeam consists of just one SessionPlayer.</p>
<h3>Attributes:</h3> <h3>Attributes:</h3>
<h5><a href="#attr_ba_SessionTeam__color">color</a>, <a href="#attr_ba_SessionTeam__gamedata">gamedata</a>, <a href="#attr_ba_SessionTeam__id">id</a>, <a href="#attr_ba_SessionTeam__name">name</a>, <a href="#attr_ba_SessionTeam__players">players</a>, <a href="#attr_ba_SessionTeam__sessiondata">sessiondata</a></h5> <h5><a href="#attr_ba_SessionTeam__color">color</a>, <a href="#attr_ba_SessionTeam__customdata">customdata</a>, <a href="#attr_ba_SessionTeam__id">id</a>, <a href="#attr_ba_SessionTeam__name">name</a>, <a href="#attr_ba_SessionTeam__players">players</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_SessionTeam__color">color</a></h4></dt><dd> <dt><h4><a name="attr_ba_SessionTeam__color">color</a></h4></dt><dd>
<p><span>Tuple[float, ...]</span></p> <p><span>Tuple[float, ...]</span></p>
<p>The team's color.</p> <p>The team's color.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_SessionTeam__gamedata">gamedata</a></h4></dt><dd> <dt><h4><a name="attr_ba_SessionTeam__customdata">customdata</a></h4></dt><dd>
<p><span>Dict</span></p> <p><span>dict</span></p>
<p>A dict for use by the current <a href="#class_ba_Activity">ba.Activity</a> <p>A dict for use by the current <a href="#class_ba_Session">ba.Session</a> for
for storing data associated with this team. storing data associated with this team.
This gets cleared for each new <a href="#class_ba_Activity">ba.Activity</a>.</p> Unlike customdata, this persists for the duration
of the session.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_SessionTeam__id">id</a></h4></dt><dd> <dt><h4><a name="attr_ba_SessionTeam__id">id</a></h4></dt><dd>
@ -4736,14 +4731,6 @@ This gets cleared for each new <a href="#class_ba_Activity">ba.Activity</a>.</p>
<p><span>List[<a href="#class_ba_SessionPlayer">ba.SessionPlayer</a>]</span></p> <p><span>List[<a href="#class_ba_SessionPlayer">ba.SessionPlayer</a>]</span></p>
<p>The list of <a href="#class_ba_SessionPlayer">ba.SessionPlayers</a> on the team.</p> <p>The list of <a href="#class_ba_SessionPlayer">ba.SessionPlayers</a> on the team.</p>
</dd>
<dt><h4><a name="attr_ba_SessionTeam__sessiondata">sessiondata</a></h4></dt><dd>
<p><span>Dict</span></p>
<p>A dict for use by the current <a href="#class_ba_Session">ba.Session</a> for
storing data associated with this team.
Unlike gamedata, this persists for the duration
of the session.</p>
</dd> </dd>
</dl> </dl>
<h3>Methods:</h3> <h3>Methods:</h3>
@ -4952,7 +4939,7 @@ of the session.</p>
</p> </p>
<h3>Methods:</h3> <h3>Methods:</h3>
<h5><a href="#method_ba_Stats____init__">&lt;constructor&gt;</a>, <a href="#method_ba_Stats__get_records">get_records()</a>, <a href="#method_ba_Stats__getactivity">getactivity()</a>, <a href="#method_ba_Stats__player_got_hit">player_got_hit()</a>, <a href="#method_ba_Stats__player_scored">player_scored()</a>, <a href="#method_ba_Stats__player_was_killed">player_was_killed()</a>, <a href="#method_ba_Stats__register_player">register_player()</a>, <a href="#method_ba_Stats__reset">reset()</a>, <a href="#method_ba_Stats__reset_accum">reset_accum()</a>, <a href="#method_ba_Stats__setactivity">setactivity()</a></h5> <h5><a href="#method_ba_Stats____init__">&lt;constructor&gt;</a>, <a href="#method_ba_Stats__get_records">get_records()</a>, <a href="#method_ba_Stats__getactivity">getactivity()</a>, <a href="#method_ba_Stats__player_scored">player_scored()</a>, <a href="#method_ba_Stats__player_was_killed">player_was_killed()</a>, <a href="#method_ba_Stats__register_player">register_player()</a>, <a href="#method_ba_Stats__reset">reset()</a>, <a href="#method_ba_Stats__reset_accum">reset_accum()</a>, <a href="#method_ba_Stats__setactivity">setactivity()</a></h5>
<dl> <dl>
<dt><h4><a name="method_ba_Stats____init__">&lt;constructor&gt;</a></dt></h4><dd> <dt><h4><a name="method_ba_Stats____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.Stats()</span></p> <p><span>ba.Stats()</span></p>
@ -4971,12 +4958,6 @@ of the session.</p>
<p>May return None.</p> <p>May return None.</p>
</dd>
<dt><h4><a name="method_ba_Stats__player_got_hit">player_got_hit()</a></dt></h4><dd>
<p><span>player_got_hit(self, player: <a href="#class_ba_SessionPlayer">ba.SessionPlayer</a>) -&gt; None</span></p>
<p>Call this when a player got hit.</p>
</dd> </dd>
<dt><h4><a name="method_ba_Stats__player_scored">player_scored()</a></dt></h4><dd> <dt><h4><a name="method_ba_Stats__player_scored">player_scored()</a></dt></h4><dd>
<p><span>player_scored(self, player: <a href="#class_ba_Player">ba.Player</a>, base_points: int = 1, target: Sequence[float] = None, kill: bool = False, victim_player: <a href="#class_ba_Player">ba.Player</a> = None, scale: float = 1.0, color: Sequence[float] = None, title: Union[str, <a href="#class_ba_Lstr">ba.Lstr</a>] = None, screenmessage: bool = True, display: bool = True, importance: int = 1, showpoints: bool = True, big_message: bool = False) -&gt; int</span></p> <p><span>player_scored(self, player: <a href="#class_ba_Player">ba.Player</a>, base_points: int = 1, target: Sequence[float] = None, kill: bool = False, victim_player: <a href="#class_ba_Player">ba.Player</a> = None, scale: float = 1.0, color: Sequence[float] = None, title: Union[str, <a href="#class_ba_Lstr">ba.Lstr</a>] = None, screenmessage: bool = True, display: bool = True, importance: int = 1, showpoints: bool = True, big_message: bool = False) -&gt; int</span></p>
@ -5029,9 +5010,9 @@ of the session.</p>
</p> </p>
<h3>Attributes:</h3> <h3>Attributes:</h3>
<h5><a href="#attr_ba_Team__gamedata">gamedata</a>, <a href="#attr_ba_Team__sessionteam">sessionteam</a></h5> <h5><a href="#attr_ba_Team__customdata">customdata</a>, <a href="#attr_ba_Team__sessionteam">sessionteam</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_Team__gamedata">gamedata</a></h4></dt><dd> <dt><h4><a name="attr_ba_Team__customdata">customdata</a></h4></dt><dd>
<p><span>dict</span></p> <p><span>dict</span></p>
<p>Arbitrary values associated with the team. <p>Arbitrary values associated with the team.
Though it is encouraged that most player values be properly defined Though it is encouraged that most player values be properly defined
@ -5080,8 +5061,16 @@ of the session.</p>
<h3>Attributes Inherited:</h3> <h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Activity__players">players</a>, <a href="#attr_ba_Activity__settings_raw">settings_raw</a>, <a href="#attr_ba_Activity__teams">teams</a></h5> <h5><a href="#attr_ba_Activity__players">players</a>, <a href="#attr_ba_Activity__settings_raw">settings_raw</a>, <a href="#attr_ba_Activity__teams">teams</a></h5>
<h3>Attributes Defined Here:</h3> <h3>Attributes Defined Here:</h3>
<h5><a href="#attr_ba_TeamGameActivity__expired">expired</a>, <a href="#attr_ba_TeamGameActivity__gamedata">gamedata</a>, <a href="#attr_ba_TeamGameActivity__globalsnode">globalsnode</a>, <a href="#attr_ba_TeamGameActivity__map">map</a>, <a href="#attr_ba_TeamGameActivity__playertype">playertype</a>, <a href="#attr_ba_TeamGameActivity__session">session</a>, <a href="#attr_ba_TeamGameActivity__stats">stats</a>, <a href="#attr_ba_TeamGameActivity__teamtype">teamtype</a></h5> <h5><a href="#attr_ba_TeamGameActivity__customdata">customdata</a>, <a href="#attr_ba_TeamGameActivity__expired">expired</a>, <a href="#attr_ba_TeamGameActivity__globalsnode">globalsnode</a>, <a href="#attr_ba_TeamGameActivity__map">map</a>, <a href="#attr_ba_TeamGameActivity__playertype">playertype</a>, <a href="#attr_ba_TeamGameActivity__session">session</a>, <a href="#attr_ba_TeamGameActivity__stats">stats</a>, <a href="#attr_ba_TeamGameActivity__teamtype">teamtype</a></h5>
<dl> <dl>
<dt><h4><a name="attr_ba_TeamGameActivity__customdata">customdata</a></h4></dt><dd>
<p><span>dict</span></p>
<p>Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired
activities.</p>
</dd>
<dt><h4><a name="attr_ba_TeamGameActivity__expired">expired</a></h4></dt><dd> <dt><h4><a name="attr_ba_TeamGameActivity__expired">expired</a></h4></dt><dd>
<p><span>bool</span></p> <p><span>bool</span></p>
<p>Whether the activity is expired.</p> <p>Whether the activity is expired.</p>
@ -5090,14 +5079,6 @@ of the session.</p>
At this point no new nodes, timers, etc should be made, At this point no new nodes, timers, etc should be made,
run, etc, and the activity should be considered a 'zombie'.</p> run, etc, and the activity should be considered a 'zombie'.</p>
</dd>
<dt><h4><a name="attr_ba_TeamGameActivity__gamedata">gamedata</a></h4></dt><dd>
<p><span>dict</span></p>
<p>Entities needing to store simple data with an activity can put it
here. This dict will be deleted when the activity expires, so contained
objects generally do not need to worry about handling expired
activities.</p>
</dd> </dd>
<dt><h4><a name="attr_ba_TeamGameActivity__globalsnode">globalsnode</a></h4></dt><dd> <dt><h4><a name="attr_ba_TeamGameActivity__globalsnode">globalsnode</a></h4></dt><dd>
<p><span><a href="#class_ba_Node">ba.Node</a></span></p> <p><span><a href="#class_ba_Node">ba.Node</a></span></p>
@ -5208,43 +5189,58 @@ Results for a completed <a href="#class_ba_TeamGameActivity">ba.TeamGameActivity
<p>Upon completion, a game should fill one of these out and pass it to its <p>Upon completion, a game should fill one of these out and pass it to its
<a href="#method_ba_Activity__end">ba.Activity.end</a>() call.</p> <a href="#method_ba_Activity__end">ba.Activity.end</a>() call.</p>
<h3>Attributes:</h3>
<h5><a href="#attr_ba_TeamGameResults__lower_is_better">lower_is_better</a>, <a href="#attr_ba_TeamGameResults__playerinfos">playerinfos</a>, <a href="#attr_ba_TeamGameResults__score_label">score_label</a>, <a href="#attr_ba_TeamGameResults__scoretype">scoretype</a>, <a href="#attr_ba_TeamGameResults__sessionteams">sessionteams</a>, <a href="#attr_ba_TeamGameResults__winnergroups">winnergroups</a>, <a href="#attr_ba_TeamGameResults__winning_team">winning_team</a></h5>
<dl>
<dt><h4><a name="attr_ba_TeamGameResults__lower_is_better">lower_is_better</a></h4></dt><dd>
<p><span>bool</span></p>
<p>Whether lower scores are better.</p>
</dd>
<dt><h4><a name="attr_ba_TeamGameResults__playerinfos">playerinfos</a></h4></dt><dd>
<p><span>List[<a href="#class_ba_PlayerInfo">ba.PlayerInfo</a>]</span></p>
<p>Get info about the players represented by the results.</p>
</dd>
<dt><h4><a name="attr_ba_TeamGameResults__score_label">score_label</a></h4></dt><dd>
<p><span>str</span></p>
<p>The label associated with scores ('points', etc).</p>
</dd>
<dt><h4><a name="attr_ba_TeamGameResults__scoretype">scoretype</a></h4></dt><dd>
<p><span><a href="#class_ba_ScoreType">ba.ScoreType</a></span></p>
<p>The type of score.</p>
</dd>
<dt><h4><a name="attr_ba_TeamGameResults__sessionteams">sessionteams</a></h4></dt><dd>
<p><span>List[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>]</span></p>
<p>Return all <a href="#class_ba_SessionTeam">ba.SessionTeams</a> in the results.</p>
</dd>
<dt><h4><a name="attr_ba_TeamGameResults__winnergroups">winnergroups</a></h4></dt><dd>
<p><span>List[WinnerGroup]</span></p>
<p>Get an ordered list of winner groups.</p>
</dd>
<dt><h4><a name="attr_ba_TeamGameResults__winning_team">winning_team</a></h4></dt><dd>
<p><span>Optional[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>]</span></p>
<p>The winning <a href="#class_ba_SessionTeam">ba.SessionTeam</a> if there is exactly one, or else None.</p>
</dd>
</dl>
<h3>Methods:</h3> <h3>Methods:</h3>
<h5><a href="#method_ba_TeamGameResults____init__">&lt;constructor&gt;</a>, <a href="#method_ba_TeamGameResults__get_lower_is_better">get_lower_is_better()</a>, <a href="#method_ba_TeamGameResults__get_player_info">get_player_info()</a>, <a href="#method_ba_TeamGameResults__get_score_name">get_score_name()</a>, <a href="#method_ba_TeamGameResults__get_score_type">get_score_type()</a>, <a href="#method_ba_TeamGameResults__get_team_score">get_team_score()</a>, <a href="#method_ba_TeamGameResults__get_team_score_str">get_team_score_str()</a>, <a href="#method_ba_TeamGameResults__get_teams">get_teams()</a>, <a href="#method_ba_TeamGameResults__get_winners">get_winners()</a>, <a href="#method_ba_TeamGameResults__get_winning_team">get_winning_team()</a>, <a href="#method_ba_TeamGameResults__has_score_for_team">has_score_for_team()</a>, <a href="#method_ba_TeamGameResults__set_game">set_game()</a>, <a href="#method_ba_TeamGameResults__set_team_score">set_team_score()</a></h5> <h5><a href="#method_ba_TeamGameResults____init__">&lt;constructor&gt;</a>, <a href="#method_ba_TeamGameResults__get_team_score">get_team_score()</a>, <a href="#method_ba_TeamGameResults__get_team_score_str">get_team_score_str()</a>, <a href="#method_ba_TeamGameResults__has_score_for_team">has_score_for_team()</a>, <a href="#method_ba_TeamGameResults__set_game">set_game()</a>, <a href="#method_ba_TeamGameResults__set_team_score">set_team_score()</a></h5>
<dl> <dl>
<dt><h4><a name="method_ba_TeamGameResults____init__">&lt;constructor&gt;</a></dt></h4><dd> <dt><h4><a name="method_ba_TeamGameResults____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.TeamGameResults()</span></p> <p><span>ba.TeamGameResults()</span></p>
<p>Instantiate a results instance.</p> <p>Instantiate a results instance.</p>
</dd>
<dt><h4><a name="method_ba_TeamGameResults__get_lower_is_better">get_lower_is_better()</a></dt></h4><dd>
<p><span>get_lower_is_better(self) -&gt; bool</span></p>
<p>Return whether lower scores are better.</p>
</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[<a href="#class_ba_PlayerInfo">ba.PlayerInfo</a>]</span></p>
<p>Get info about the players represented by the results.</p>
</dd>
<dt><h4><a name="method_ba_TeamGameResults__get_score_name">get_score_name()</a></dt></h4><dd>
<p><span>get_score_name(self) -&gt; str</span></p>
<p>Get the name associated with scores ('points', etc).</p>
</dd>
<dt><h4><a name="method_ba_TeamGameResults__get_score_type">get_score_type()</a></dt></h4><dd>
<p><span>get_score_type(self) -&gt; <a href="#class_ba_ScoreType">ba.ScoreType</a></span></p>
<p>Get the type of score.</p>
</dd> </dd>
<dt><h4><a name="method_ba_TeamGameResults__get_team_score">get_team_score()</a></dt></h4><dd> <dt><h4><a name="method_ba_TeamGameResults__get_team_score">get_team_score()</a></dt></h4><dd>
<p><span>get_team_score(self, team: Union[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>, <a href="#class_ba_Team">ba.Team</a>]) -&gt; Optional[int]</span></p> <p><span>get_team_score(self, sessionteam: Union[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>]) -&gt; Optional[int]</span></p>
<p>Return the score for a given team.</p> <p>Return the score for a given <a href="#class_ba_SessionTeam">ba.SessionTeam</a>.</p>
</dd> </dd>
<dt><h4><a name="method_ba_TeamGameResults__get_team_score_str">get_team_score_str()</a></dt></h4><dd> <dt><h4><a name="method_ba_TeamGameResults__get_team_score_str">get_team_score_str()</a></dt></h4><dd>
@ -5254,24 +5250,6 @@ Results for a completed <a href="#class_ba_TeamGameActivity">ba.TeamGameActivity
<p>(properly formatted for the score type.)</p> <p>(properly formatted for the score type.)</p>
</dd>
<dt><h4><a name="method_ba_TeamGameResults__get_teams">get_teams()</a></dt></h4><dd>
<p><span>get_teams(self) -&gt; List[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>]</span></p>
<p>Return all <a href="#class_ba_SessionTeam">ba.SessionTeams</a> in the results.</p>
</dd>
<dt><h4><a name="method_ba_TeamGameResults__get_winners">get_winners()</a></dt></h4><dd>
<p><span>get_winners(self) -&gt; List[WinnerGroup]</span></p>
<p>Get an ordered list of winner groups.</p>
</dd>
<dt><h4><a name="method_ba_TeamGameResults__get_winning_team">get_winning_team()</a></dt></h4><dd>
<p><span>get_winning_team(self) -&gt; Optional[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>]</span></p>
<p>Get the winning <a href="#class_ba_Team">ba.Team</a> if there is exactly one; None otherwise.</p>
</dd> </dd>
<dt><h4><a name="method_ba_TeamGameResults__has_score_for_team">has_score_for_team()</a></dt></h4><dd> <dt><h4><a name="method_ba_TeamGameResults__has_score_for_team">has_score_for_team()</a></dt></h4><dd>
<p><span>has_score_for_team(self, sessionteam: <a href="#class_ba_SessionTeam">ba.SessionTeam</a>) -&gt; bool</span></p> <p><span>has_score_for_team(self, sessionteam: <a href="#class_ba_SessionTeam">ba.SessionTeam</a>) -&gt; bool</span></p>
@ -5286,7 +5264,7 @@ Results for a completed <a href="#class_ba_TeamGameActivity">ba.TeamGameActivity
</dd> </dd>
<dt><h4><a name="method_ba_TeamGameResults__set_team_score">set_team_score()</a></dt></h4><dd> <dt><h4><a name="method_ba_TeamGameResults__set_team_score">set_team_score()</a></dt></h4><dd>
<p><span>set_team_score(self, team: Union[<a href="#class_ba_SessionTeam">ba.SessionTeam</a>, <a href="#class_ba_Team">ba.Team</a>], score: Optional[int]) -&gt; None</span></p> <p><span>set_team_score(self, team: <a href="#class_ba_Team">ba.Team</a>, score: Optional[int]) -&gt; None</span></p>
<p>Set the score for a given <a href="#class_ba_Team">ba.Team</a>.</p> <p>Set the score for a given <a href="#class_ba_Team">ba.Team</a>.</p>
@ -6422,6 +6400,30 @@ playing, the playing track will not be restarted.</p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p> <p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>
<hr>
<h2><strong><a name="function_ba_storagename">ba.storagename()</a></strong></h3>
<p><span>storagename(basename: str) -&gt; str</span></p>
<p>Generate a (hopefully) unique name for storing things in public places.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>This consists of a leading underscore, the module path at the
call site with dots replaced by underscores, the class name, and
the provided suffix. When storing data in public places such as
'customdata' dicts, this minimizes the chance of collisions if a
module or class is duplicated or renamed.</p>
<pre><span><em><small># Example: generate a unique name for storage purposes:</small></em></span>
class MyThingie:</pre>
<pre><span><em><small> # This will give something like '_mymodule_submodule_mythingie_data'.</small></em></span>
_STORENAME = <a href="#function_ba_storagename">ba.storagename</a>('data')</pre>
<p> def __init__(self, activity):
# Store some data in the Activity we were passed
activity.customdata[self._STORENAME] = {}</p>
<hr> <hr>
<h2><strong><a name="function_ba_textwidget">ba.textwidget()</a></strong></h3> <h2><strong><a name="function_ba_textwidget">ba.textwidget()</a></strong></h3>
<p><span>textwidget(edit: Widget = None, parent: Widget = None, <p><span>textwidget(edit: Widget = None, parent: Widget = None,