More modernizing

This commit is contained in:
Eric Froemling 2020-05-27 17:43:41 -07:00
parent 849bd11795
commit 08ea64bdc5
20 changed files with 261 additions and 148 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/ba/35/3b6bc5c5609b1dd37bd65c39df45", "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ca/db/9c7cfd4e4f4a1f7a7adc980bca42",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a6/bc/c2c7231dc6bf085eda15d6198554", "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4e/0b/231e38ff29d932df7552050891c5",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/fd/020ed9bb0e8c8a18b2d793fee8bd", "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f2/56/bb316ec28ee98ece5c0c3a04b77f",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/43/0b/78c8bacb215abaf50dcb3284eef7", "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ef/92/d787c99db6cc85f70b7131ff2c0c",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f8/e1/e0dc64b5c00661cce19530c0e836", "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/97/b9/9c6c3c90f10d319250a9f3d287b3",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e6/74/73a514993d626a6bc75717d185ef", "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f0/2a/60bdf1c4d4e13bdbb5f4df121e3e",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f0/7d/dbd2624759a1fdce2a20d53cab1a", "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/66/25/79ea606983dc91ac0cd79c1e7da6",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1c/01/4833ec215cc6c53f7e4ebf850608", "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/70/c6/0ab2cdf222ffcadade37dd3b8462",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/b1/57/b72500d2a568df5afa36556f89dd", "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/3c/65/450a67dab189c0832b6bf28a9e9c",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/48/ea/c83f97f44703b16eeec794d29da6", "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7e/a9/e1ab6defb8bcf536dff46d0c62b2",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a3/16/284b9953c7ef4a841ff907079cbd", "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ab/fc/d00336dae2b1c7323b31518b52aa",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/12/43/d0513cf8f8dac0712cbf42d4b94b" "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ed/98/dbea1af1da83bfa1a3283175b234"
} }

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=223083205204988067566025188831386474803 # SOURCES_HASH=265401783818737452594582363319036908124
# 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

View File

@ -73,7 +73,8 @@ from ba._apputils import is_browser_likely_available
from ba._campaign import Campaign 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,
sharedobj, timestring, cameraflash) sharedobj, timestring, cameraflash)
from ba._general import WeakCall, Call, existing, Existable from ba._general import (WeakCall, Call, existing, Existable,
verify_object_death)
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

@ -28,6 +28,7 @@ from ba._team import Team
from ba._player import Player from ba._player import Player
from ba._error import print_exception, print_error, SessionTeamNotFoundError from ba._error import print_exception, print_error, SessionTeamNotFoundError
from ba._dependency import DependencyComponent from ba._dependency import DependencyComponent
from ba._general import Call, verify_object_death
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
@ -217,7 +218,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self._stats: Optional[ba.Stats] = None self._stats: Optional[ba.Stats] = None
def __del__(self) -> None: def __del__(self) -> None:
from ba._apputils import garbage_collect, call_after_ad
# If the activity has been run then we should have already cleaned # If the activity has been run then we should have already cleaned
# it up, but we still need to run expire calls for un-run activities. # it up, but we still need to run expire calls for un-run activities.
@ -225,20 +225,13 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
with _ba.Context('empty'): with _ba.Context('empty'):
self._expire() self._expire()
# Since we're mostly between activities at this point, lets run a cycle # Inform our owner that we're officially kicking the bucket.
# of garbage collection; hopefully it won't cause hitches here.
garbage_collect(session_end=False)
# Now that our object is officially gonna be dead, tell the session it
# can fire up the next activity.
if self._transitioning_out: if self._transitioning_out:
session = self._session() session = self._session()
if session is not None: if session is not None:
with _ba.Context(session): _ba.pushcall(
if self.can_show_ad_on_death: Call(session.transitioning_out_activity_was_freed,
call_after_ad(session.begin_next_activity) self.can_show_ad_on_death))
else:
_ba.pushcall(session.begin_next_activity)
@property @property
def stats(self) -> ba.Stats: def stats(self) -> ba.Stats:
@ -304,7 +297,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
(internal) (internal)
""" """
from ba._general import Call
from ba._enums import TimeType from ba._enums import TimeType
# Create a real-timer that watches a weak-ref of this activity # Create a real-timer that watches a weak-ref of this activity
@ -628,6 +620,10 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self.players.remove(player) self.players.remove(player)
assert player not in self.players assert player not in self.players
# This should allow our ba.Player instance to die.
# Complain if that doesn't happen.
# verify_object_death(player)
with _ba.Context(self): with _ba.Context(self):
# Make a decent attempt to persevere if user code breaks. # Make a decent attempt to persevere if user code breaks.
try: try:
@ -670,6 +666,10 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self.teams.remove(team) self.teams.remove(team)
assert team not in self.teams assert team not in self.teams
# This should allow our ba.Team instance to die. Complain
# if that doesn't happen.
# verify_object_death(team)
with _ba.Context(self): with _ba.Context(self):
# Make a decent attempt to persevere if user code breaks. # Make a decent attempt to persevere if user code breaks.
try: try:
@ -680,6 +680,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
sessionteam.reset_gamedata() sessionteam.reset_gamedata()
except Exception: except Exception:
print_exception(f'Error in reset_gamedata for {self}') print_exception(f'Error in reset_gamedata for {self}')
sessionteam.gameteam = None sessionteam.gameteam = None
def _sanity_check_begin_call(self) -> None: def _sanity_check_begin_call(self) -> None:
@ -777,32 +778,45 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
for actor_ref in self._actor_weak_refs: for actor_ref in self._actor_weak_refs:
actor = actor_ref() actor = actor_ref()
if actor is not None: if actor is not None:
verify_object_death(actor)
try: try:
actor.on_expire() actor.on_expire()
except Exception: except Exception:
print_exception(f'Error expiring Actor {actor_ref()}') print_exception(f'Error in Actor.on_expire()'
f' for {actor_ref()}')
# Reset all Players. # Reset all Players.
# (releases any attached actors, clears game-data, etc) # (releases any attached actors, clears game-data, etc)
for player in self.players: for player in self.players:
if player: try:
try: # This should allow our ba.Player instance to die.
sessionplayer = player.sessionplayer # Complain if that doesn't happen.
player.reset() # verify_object_death(player)
sessionplayer.set_node(None) sessionplayer = player.sessionplayer
sessionplayer.set_activity(None) player.reset()
sessionplayer.gameplayer = None sessionplayer.set_node(None)
sessionplayer.reset() sessionplayer.set_activity(None)
except Exception:
print_exception(f'Error resetting Player {player}') sessionplayer.gameplayer = None
sessionplayer.reset()
except Exception:
print_exception(f'Error resetting Player {player}')
# Ditto with Teams. # Ditto with Teams.
for team in self.teams: for team in self.teams:
try: try:
sessionteam = team.sessionteam sessionteam = team.sessionteam
# This should allow our ba.Team instance to die.
# Complain if that doesn't happen.
# verify_object_death(sessionteam.gameteam)
sessionteam.gameteam = None sessionteam.gameteam = None
sessionteam.reset_gamedata() sessionteam.reset_gamedata()
except SessionTeamNotFoundError: except SessionTeamNotFoundError:
# It is expected that Team objects may last longer than
# the SessionTeam they came from (game objects may hold
# team references past the point at which the underlying
# player/team leaves)
pass pass
except Exception: except Exception:
print_exception(f'Error resetting Team {team}') print_exception(f'Error resetting Team {team}')

View File

@ -21,16 +21,22 @@
"""Utility snippets applying to generic Python code.""" """Utility snippets applying to generic Python code."""
from __future__ import annotations from __future__ import annotations
import gc
import types import types
import weakref import weakref
import random
from typing import TYPE_CHECKING, TypeVar from typing import TYPE_CHECKING, TypeVar
from typing_extensions import Protocol from typing_extensions import Protocol
from efro.terminal import Clr
from ba._error import print_error, print_exception
from ba._enums import TimeType
import _ba import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Type, Optional from typing import Any, Type, Optional
from efro.call import Call as Call # 'as Call' so we re-export. from efro.call import Call as Call # 'as Call' so we re-export.
from weakref import ReferenceType
class Existable(Protocol): class Existable(Protocol):
@ -100,22 +106,18 @@ def json_prep(data: Any) -> Any:
if isinstance(data, list): if isinstance(data, list):
return [json_prep(element) for element in data] return [json_prep(element) for element in data]
if isinstance(data, tuple): if isinstance(data, tuple):
from ba import _error print_error('json_prep encountered tuple', once=True)
_error.print_error('json_prep encountered tuple', once=True)
return [json_prep(element) for element in data] return [json_prep(element) for element in data]
if isinstance(data, bytes): if isinstance(data, bytes):
try: try:
return data.decode(errors='ignore') return data.decode(errors='ignore')
except Exception: except Exception:
from ba import _error from ba import _error
_error.print_error('json_prep encountered utf-8 decode error', print_error('json_prep encountered utf-8 decode error', once=True)
once=True)
return data.decode(errors='ignore') return data.decode(errors='ignore')
if not isinstance(data, (str, float, bool, type(None), int)): if not isinstance(data, (str, float, bool, type(None), int)):
from ba import _error print_error('got unsupported type in json_prep:' + str(type(data)),
_error.print_error('got unsupported type in json_prep:' + once=True)
str(type(data)),
once=True)
return data return data
@ -135,7 +137,6 @@ def utf8_all(data: Any) -> Any:
def print_refs(obj: Any) -> None: def print_refs(obj: Any) -> None:
"""Print a list of known live references to an object.""" """Print a list of known live references to an object."""
import gc
# Hmmm; I just noticed that calling this on an object # Hmmm; I just noticed that calling this on an object
# seems to keep it alive. Should figure out why. # seems to keep it alive. Should figure out why.
@ -291,3 +292,47 @@ class WeakMethod:
def __str__(self) -> str: def __str__(self) -> str:
return '<ba.WeakMethod object; call=' + str(self._func) + '>' return '<ba.WeakMethod object; call=' + str(self._func) + '>'
def verify_object_death(obj: object) -> None:
"""Warn if an object does not get freed within a short period.
Category: General Utility Functions
This can be handy to detect and prevent memory/resource leaks.
"""
try:
ref = weakref.ref(obj)
except Exception:
print_exception('Unable to create weak-ref in verify_object_death')
# Use a slight range for our checks so they don't all land at once
# if we queue a lot of them.
delay = random.uniform(2.0, 5.5)
with _ba.Context('ui'):
_ba.timer(delay,
lambda: _verify_object_death(ref),
timetype=TimeType.REAL)
def _verify_object_death(wref: ReferenceType) -> None:
obj = wref()
if obj is None:
return
try:
name = type(obj).__name__
except Exception:
print(f'Note: unable to get type name for {obj}')
name = 'object'
print(f'{Clr.RED}Error: {name} not dying'
f' when expected to: {Clr.BLD}{obj}{Clr.RST}')
refs = list(gc.get_referrers(obj))
print(f'{Clr.YLW}Active References:{Clr.RST}')
i = 1
for ref in refs:
# if isinstance(ref, types.FrameType):
# continue
print(f'{Clr.YLW} reference {i}:{Clr.BLU} {ref}{Clr.RST}')
i += 1

View File

@ -35,7 +35,7 @@ PLAYER_COLORS = [(1, 0.15, 0.15), (0.2, 1, 0.2), (0.1, 0.1, 1), (0.2, 1, 1),
(0.5, 0.25, 1.0), (1, 1, 0), (1, 0.5, 0), (1, 0.3, 0.5), (0.5, 0.25, 1.0), (1, 1, 0), (1, 0.5, 0), (1, 0.3, 0.5),
(0.1, 0.1, 0.5), (0.4, 0.2, 0.1), (0.1, 0.35, 0.1), (0.1, 0.1, 0.5), (0.4, 0.2, 0.1), (0.1, 0.35, 0.1),
(1, 0.8, 0.5), (0.4, 0.05, 0.05), (0.13, 0.13, 0.13), (1, 0.8, 0.5), (0.4, 0.05, 0.05), (0.13, 0.13, 0.13),
(0.5, 0.5, 0.5), (1, 1, 1)] # yapf: disable (0.5, 0.5, 0.5), (1, 1, 1)]
def get_player_colors() -> List[Tuple[float, float, float]]: def get_player_colors() -> List[Tuple[float, float, float]]:
@ -75,8 +75,8 @@ def get_player_profile_colors(
if profiles is None: if profiles is None:
profiles = bs_config['Player Profiles'] profiles = bs_config['Player Profiles']
# special case - when being asked for a random color in kiosk mode, # Special case: when being asked for a random color in kiosk mode,
# always return default purple # always return default purple.
if _ba.app.kiosk_mode and profilename is None: if _ba.app.kiosk_mode and profilename is None:
color = (0.5, 0.4, 1.0) color = (0.5, 0.4, 1.0)
highlight = (0.4, 0.4, 0.5) highlight = (0.4, 0.4, 0.5)
@ -85,22 +85,22 @@ def get_player_profile_colors(
assert profilename is not None assert profilename is not None
color = profiles[profilename]['color'] color = profiles[profilename]['color']
except (KeyError, AssertionError): except (KeyError, AssertionError):
# key off name if possible # Key off name if possible.
if profilename is None: if profilename is None:
# first 6 are bright-ish # First 6 are bright-ish.
color = PLAYER_COLORS[random.randrange(6)] color = PLAYER_COLORS[random.randrange(6)]
else: else:
# first 6 are bright-ish # First 6 are bright-ish.
color = PLAYER_COLORS[sum([ord(c) for c in profilename]) % 6] color = PLAYER_COLORS[sum([ord(c) for c in profilename]) % 6]
try: try:
assert profilename is not None assert profilename is not None
highlight = profiles[profilename]['highlight'] highlight = profiles[profilename]['highlight']
except (KeyError, AssertionError): except (KeyError, AssertionError):
# key off name if possible # Key off name if possible.
if profilename is None: if profilename is None:
# last 2 are grey and white; ignore those or we # Last 2 are grey and white; ignore those or we
# get lots of old-looking players # get lots of old-looking players.
highlight = PLAYER_COLORS[random.randrange( highlight = PLAYER_COLORS[random.randrange(
len(PLAYER_COLORS) - 2)] len(PLAYER_COLORS) - 2)]
else: else:

View File

@ -582,6 +582,21 @@ class Session:
self._add_chosen_player(chooser) self._add_chosen_player(chooser)
lobby.remove_chooser(chooser.getplayer()) lobby.remove_chooser(chooser.getplayer())
def transitioning_out_activity_was_freed(
self, can_show_ad_on_death: bool) -> None:
"""(internal)"""
from ba._apputils import garbage_collect, call_after_ad
# Since we're mostly between activities at this point, lets run a cycle
# of garbage collection; hopefully it won't cause hitches here.
garbage_collect(session_end=False)
with _ba.Context(self):
if can_show_ad_on_death:
call_after_ad(self.begin_next_activity)
else:
_ba.pushcall(self.begin_next_activity)
def _add_chosen_player(self, chooser: ba.Chooser) -> ba.SessionPlayer: def _add_chosen_player(self, chooser: ba.Chooser) -> ba.SessionPlayer:
from ba._team import SessionTeam from ba._team import SessionTeam
sessionplayer = chooser.getplayer() sessionplayer = chooser.getplayer()

View File

@ -40,18 +40,17 @@ class RespawnIcon:
""" """
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 given ba.Player and respawn_time (in seconds)
"""
self._visible = True self._visible = True
on_right, offs_extra, respawn_icons = self._get_context(player) on_right, offs_extra, respawn_icons = self._get_context(player)
try: # Cache our mask tex on the team for easy access.
mask_tex = (player.team.gamedata['_spaz_respawn_icons_mask_tex']) mask_tex = getattr(player.team, '_spaz_respawn_icons_mask_tex', None)
except Exception: if mask_tex is None:
mask_tex = player.team.gamedata['_spaz_respawn_icons_mask_tex'] = ( mask_tex = ba.gettexture('characterIconMask')
ba.gettexture('characterIconMask')) setattr(player.team, '_spaz_respawn_icons_mask_tex', mask_tex)
assert isinstance(mask_tex, ba.Texture)
# Now find the first unused slot and use that. # Now find the first unused slot and use that.
index = 0 index = 0
@ -139,12 +138,14 @@ 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.
try: respawn_icons = getattr(player.team, '_spaz_respawn_icons_right',
respawn_icons = ( None)
player.team.gamedata['_spaz_respawn_icons_right']) if respawn_icons is None:
except Exception: respawn_icons = {}
respawn_icons = ( setattr(player.team, '_spaz_respawn_icons_right',
player.team.gamedata['_spaz_respawn_icons_right']) = {} respawn_icons)
assert isinstance(respawn_icons, dict)
offs_extra = -20 offs_extra = -20
else: else:
on_right = False on_right = False

View File

@ -200,7 +200,8 @@ class _Entry:
def set_position(self, position: Sequence[float]) -> None: def set_position(self, position: Sequence[float]) -> None:
"""Set the entry's position.""" """Set the entry's position."""
# abort if we've been killed
# Abort if we've been killed
if not self._backing.node: if not self._backing.node:
return return
self._pos = tuple(position) self._pos = tuple(position)
@ -315,13 +316,15 @@ class _EntryProxy:
def __init__(self, scoreboard: Scoreboard, team: ba.Team): def __init__(self, scoreboard: Scoreboard, team: ba.Team):
self._scoreboard = weakref.ref(scoreboard) self._scoreboard = weakref.ref(scoreboard)
# have to store ID here instead of a weak-ref since the team will be
# dead when we die and need to remove it # Have to store ID here instead of a weak-ref since the team will be
# dead when we die and need to remove it.
self._team_id = team.id self._team_id = team.id
def __del__(self) -> None: def __del__(self) -> None:
scoreboard = self._scoreboard() scoreboard = self._scoreboard()
# remove our team from the scoreboard if its still around
# Remove our team from the scoreboard if its still around.
if scoreboard is not None: if scoreboard is not None:
scoreboard.remove_team(self._team_id) scoreboard.remove_team(self._team_id)
@ -343,7 +346,7 @@ class Scoreboard:
self._label = label self._label = label
self.score_split = score_split self.score_split = score_split
# for free-for-all we go simpler since we have one per player # For free-for-all we go simpler since we have one per player.
self._pos: Sequence[float] self._pos: Sequence[float]
if isinstance(ba.getsession(), ba.FreeForAllSession): if isinstance(ba.getsession(), ba.FreeForAllSession):
self._do_cover = False self._do_cover = False
@ -368,11 +371,11 @@ class Scoreboard:
"""Update the score-board display for the given ba.Team.""" """Update the score-board display for the given ba.Team."""
if not team.id in self._entries: if not team.id in self._entries:
self._add_team(team) self._add_team(team)
# 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)
if '_scoreboard_entry' in team.gamedata: assert not hasattr(team, '_scoreboard_entry')
raise Exception('existing _EntryProxy found') setattr(team, '_scoreboard_entry', _EntryProxy(self, team))
team.gamedata['_scoreboard_entry'] = _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,
@ -383,7 +386,7 @@ class Scoreboard:
def _add_team(self, team: ba.Team) -> None: def _add_team(self, team: ba.Team) -> None:
if team.id in self._entries: if team.id in self._entries:
raise Exception('Duplicate team add') raise RuntimeError('Duplicate team add')
self._entries[team.id] = _Entry(self, self._entries[team.id] = _Entry(self,
team, team,
do_cover=self._do_cover, do_cover=self._do_cover,

View File

@ -49,16 +49,16 @@ class SpazBotPunchedMessage:
Attributes: Attributes:
badguy spazbot
The ba.SpazBot that got punched. The ba.SpazBot that got punched.
damage damage
How much damage was done to the ba.SpazBot. How much damage was done to the ba.SpazBot.
""" """
def __init__(self, badguy: SpazBot, damage: int): def __init__(self, spazbot: SpazBot, damage: int):
"""Instantiate a message with the given values.""" """Instantiate a message with the given values."""
self.badguy = badguy self.spazbot = spazbot
self.damage = damage self.damage = damage
@ -69,7 +69,7 @@ class SpazBotDiedMessage:
Attributes: Attributes:
badguy spazbot
The ba.SpazBot that was killed. The ba.SpazBot that was killed.
killerplayer killerplayer
@ -79,10 +79,10 @@ class SpazBotDiedMessage:
The particular type of death. The particular type of death.
""" """
def __init__(self, badguy: SpazBot, killerplayer: Optional[ba.Player], def __init__(self, spazbot: SpazBot, killerplayer: Optional[ba.Player],
how: ba.DeathType): how: ba.DeathType):
"""Instantiate with given values.""" """Instantiate with given values."""
self.badguy = badguy self.spazbot = spazbot
self.killerplayer = killerplayer self.killerplayer = killerplayer
self.how = how self.how = how

View File

@ -216,8 +216,8 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
# Whenever our evil bunny dies, respawn him and spew some eggs. # Whenever our evil bunny dies, respawn him and spew some eggs.
elif isinstance(msg, SpazBotDiedMessage): elif isinstance(msg, SpazBotDiedMessage):
self._spawn_evil_bunny() self._spawn_evil_bunny()
assert msg.badguy.node assert msg.spazbot.node
pos = msg.badguy.node.position pos = msg.spazbot.node.position
for _i in range(6): for _i in range(6):
spread = 0.4 spread = 0.4
self._eggs.append( self._eggs.append(

View File

@ -816,7 +816,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
elif isinstance(msg, SpazBotDiedMessage): elif isinstance(msg, SpazBotDiedMessage):
# Every time a bad guy dies, spawn a new one. # Every time a bad guy dies, spawn a new one.
ba.timer(3.0, ba.Call(self._spawn_bot, (type(msg.badguy)))) ba.timer(3.0, ba.Call(self._spawn_bot, (type(msg.spazbot))))
elif isinstance(msg, SpazBotPunchedMessage): elif isinstance(msg, SpazBotPunchedMessage):
if self._preset in ['rookie', 'rookie_easy']: if self._preset in ['rookie', 'rookie_easy']:

View File

@ -36,7 +36,7 @@ if TYPE_CHECKING:
from typing import Any, Sequence, Dict, Type, List, Optional, Union from typing import Any, Sequence, Dict, Type, List, Optional, Union
class PuckDeathMessage: class PuckDiedMessage:
"""Inform something that a puck has died.""" """Inform something that a puck has died."""
def __init__(self, puck: Puck): def __init__(self, puck: Puck):
@ -78,7 +78,7 @@ class Puck(ba.Actor):
self.node.delete() self.node.delete()
activity = self._activity() activity = self._activity()
if activity and not msg.immediate: if activity and not msg.immediate:
activity.handlemessage(PuckDeathMessage(self)) activity.handlemessage(PuckDiedMessage(self))
# If we go out of bounds, move back to where we started. # If we go out of bounds, move back to where we started.
elif isinstance(msg, ba.OutOfBoundsMessage): elif isinstance(msg, ba.OutOfBoundsMessage):
@ -113,6 +113,9 @@ class Player(ba.Player['Team']):
class Team(ba.Team[Player]): class Team(ba.Team[Player]):
"""Our team type for this game.""" """Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game # ba_meta export game
class HockeyGame(ba.TeamGameActivity[Player, Team]): class HockeyGame(ba.TeamGameActivity[Player, Team]):
@ -196,26 +199,28 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
self._puck_spawn_pos: Optional[Sequence[float]] = None self._puck_spawn_pos: Optional[Sequence[float]] = None
self._score_regions: Optional[List[ba.NodeActor]] = None self._score_regions: Optional[List[ba.NodeActor]] = None
self._puck: Optional[Puck] = None self._puck: Optional[Puck] = None
self._score_to_win = int(settings['Score to Win'])
self._time_limit = float(settings['Time Limit'])
def get_instance_description(self) -> Union[str, Sequence]: def get_instance_description(self) -> Union[str, Sequence]:
if self.settings_raw['Score to Win'] == 1: if self._score_to_win == 1:
return 'Score a goal.' return 'Score a goal.'
return 'Score ${ARG1} goals.', self.settings_raw['Score to Win'] return 'Score ${ARG1} goals.', self._score_to_win
def get_instance_description_short(self) -> Union[str, Sequence]: def get_instance_description_short(self) -> Union[str, Sequence]:
if self.settings_raw['Score to Win'] == 1: if self._score_to_win == 1:
return 'score a goal' return 'score a goal'
return 'score ${ARG1} goals', self.settings_raw['Score to Win'] return 'score ${ARG1} goals', self._score_to_win
def on_begin(self) -> None: def on_begin(self) -> None:
super().on_begin() super().on_begin()
self.setup_standard_time_limit(self.settings_raw['Time Limit']) self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops() self.setup_standard_powerup_drops()
self._puck_spawn_pos = self.map.get_flag_position(None) self._puck_spawn_pos = self.map.get_flag_position(None)
self._spawn_puck() self._spawn_puck()
# set up the two score regions # Set up the two score regions.
defs = self.map.defs defs = self.map.defs
self._score_regions = [] self._score_regions = []
self._score_regions.append( self._score_regions.append(
@ -240,7 +245,6 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
ba.playsound(self._chant_sound) ba.playsound(self._chant_sound)
def on_team_join(self, team: Team) -> None: def on_team_join(self, team: Team) -> None:
team.gamedata['score'] = 0
self._update_scoreboard() self._update_scoreboard()
def _handle_puck_player_collide(self) -> None: def _handle_puck_player_collide(self) -> None:
@ -274,7 +278,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
for team in self.teams: for team in self.teams:
if team.id == index: if team.id == index:
scoring_team = team scoring_team = team
team.gamedata['score'] += 1 team.score += 1
# Tell all players to celebrate. # Tell all players to celebrate.
for player in team.players: for player in team.players:
@ -291,7 +295,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
big_message=True) big_message=True)
# End game if we won. # End game if we won.
if team.gamedata['score'] >= self.settings_raw['Score to Win']: if team.score >= self._score_to_win:
self.end_game() self.end_game()
ba.playsound(self._foghorn_sound) ba.playsound(self._foghorn_sound)
@ -317,14 +321,13 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
def end_game(self) -> None: def end_game(self) -> None:
results = ba.TeamGameResults() results = ba.TeamGameResults()
for team in self.teams: for team in self.teams:
results.set_team_score(team, team.gamedata['score']) results.set_team_score(team, team.score)
self.end(results=results) self.end(results=results)
def _update_scoreboard(self) -> None: def _update_scoreboard(self) -> None:
winscore = self.settings_raw['Score to Win'] winscore = self._score_to_win
for team in self.teams: for team in self.teams:
self._scoreboard.set_team_value(team, team.gamedata['score'], self._scoreboard.set_team_value(team, team.score, winscore)
winscore)
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
@ -335,7 +338,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
self.respawn_player(msg.getplayer(Player)) self.respawn_player(msg.getplayer(Player))
# Respawn dead pucks. # Respawn dead pucks.
elif isinstance(msg, PuckDeathMessage): elif isinstance(msg, PuckDiedMessage):
if not self.has_ended(): if not self.has_ended():
ba.timer(3.0, self._spawn_puck) ba.timer(3.0, self._spawn_puck)
else: else:

View File

@ -1220,13 +1220,13 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
ba.timer(0.1, self._checkroundover) ba.timer(0.1, self._checkroundover)
elif isinstance(msg, SpazBotDiedMessage): elif isinstance(msg, SpazBotDiedMessage):
pts, importance = msg.badguy.get_death_points(msg.how) pts, importance = msg.spazbot.get_death_points(msg.how)
if msg.killerplayer is not None: if msg.killerplayer is not None:
self._handle_kill_achievements(msg) self._handle_kill_achievements(msg)
target: Optional[Sequence[float]] target: Optional[Sequence[float]]
try: try:
assert msg.badguy.node assert msg.spazbot.node
target = msg.badguy.node.position target = msg.spazbot.node.position
except Exception: except Exception:
ba.print_exception() ba.print_exception()
target = None target = None
@ -1265,13 +1265,13 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _handle_uber_kill_achievements(self, msg: SpazBotDiedMessage) -> None: def _handle_uber_kill_achievements(self, msg: SpazBotDiedMessage) -> None:
# Uber mine achievement: # Uber mine achievement:
if msg.badguy.last_attacked_type == ('explosion', 'land_mine'): if msg.spazbot.last_attacked_type == ('explosion', 'land_mine'):
self._land_mine_kills += 1 self._land_mine_kills += 1
if self._land_mine_kills >= 6: if self._land_mine_kills >= 6:
self._award_achievement('Gold Miner') self._award_achievement('Gold Miner')
# Uber tnt achievement: # Uber tnt achievement:
if msg.badguy.last_attacked_type == ('explosion', 'tnt'): if msg.spazbot.last_attacked_type == ('explosion', 'tnt'):
self._tnt_kills += 1 self._tnt_kills += 1
if self._tnt_kills >= 6: if self._tnt_kills >= 6:
ba.timer(0.5, ba.WeakCall(self._award_achievement, ba.timer(0.5, ba.WeakCall(self._award_achievement,
@ -1280,7 +1280,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _handle_pro_kill_achievements(self, msg: SpazBotDiedMessage) -> None: def _handle_pro_kill_achievements(self, msg: SpazBotDiedMessage) -> None:
# TNT achievement: # TNT achievement:
if msg.badguy.last_attacked_type == ('explosion', 'tnt'): if msg.spazbot.last_attacked_type == ('explosion', 'tnt'):
self._tnt_kills += 1 self._tnt_kills += 1
if self._tnt_kills >= 3: if self._tnt_kills >= 3:
ba.timer( ba.timer(
@ -1291,7 +1291,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _handle_rookie_kill_achievements(self, def _handle_rookie_kill_achievements(self,
msg: SpazBotDiedMessage) -> None: msg: SpazBotDiedMessage) -> None:
# Land-mine achievement: # Land-mine achievement:
if msg.badguy.last_attacked_type == ('explosion', 'land_mine'): if msg.spazbot.last_attacked_type == ('explosion', 'land_mine'):
self._land_mine_kills += 1 self._land_mine_kills += 1
if self._land_mine_kills >= 3: if self._land_mine_kills >= 3:
self._award_achievement('Mine Games') self._award_achievement('Mine Games')
@ -1299,7 +1299,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _handle_training_kill_achievements(self, def _handle_training_kill_achievements(self,
msg: SpazBotDiedMessage) -> None: msg: SpazBotDiedMessage) -> None:
# Toss-off-map achievement: # Toss-off-map achievement:
if msg.badguy.last_attacked_type == ('picked_up', 'default'): if msg.spazbot.last_attacked_type == ('picked_up', 'default'):
self._throw_off_kills += 1 self._throw_off_kills += 1
if self._throw_off_kills >= 3: if self._throw_off_kills >= 3:
self._award_achievement('Off You Go Then') self._award_achievement('Off You Go Then')

View File

@ -180,18 +180,18 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
def get_instance_description(self) -> Union[str, Sequence]: def get_instance_description(self) -> Union[str, Sequence]:
if (isinstance(self.session, ba.DualTeamSession) if (isinstance(self.session, ba.DualTeamSession)
and self.settings_raw.get('Entire Team Must Finish', False)): and self._entire_team_must_finish):
t_str = ' Your entire team has to finish.' t_str = ' Your entire team has to finish.'
else: else:
t_str = '' t_str = ''
if self.settings_raw['Laps'] > 1: if self._laps > 1:
return 'Run ${ARG1} laps.' + t_str, self.settings_raw['Laps'] return 'Run ${ARG1} laps.' + t_str, self._laps
return 'Run 1 lap.' + t_str return 'Run 1 lap.' + t_str
def get_instance_description_short(self) -> Union[str, Sequence]: def get_instance_description_short(self) -> Union[str, Sequence]:
if self.settings_raw['Laps'] > 1: if self._laps > 1:
return 'run ${ARG1} laps', self.settings_raw['Laps'] return 'run ${ARG1} laps', self._laps
return 'run 1 lap' return 'run 1 lap'
def on_transition_in(self) -> None: def on_transition_in(self) -> None:
@ -715,8 +715,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage): if isinstance(msg, ba.PlayerDiedMessage):
# Augment default behavior. super().handlemessage(msg) # Augment default behavior.
super().handlemessage(msg)
player = msg.getplayer(Player) player = msg.getplayer(Player)
if not player.finished: if not player.finished:
self.respawn_player(player, respawn_time=1) self.respawn_player(player, respawn_time=1)

View File

@ -47,6 +47,10 @@ if TYPE_CHECKING:
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
"""Our player type for this game.""" """Our player type for this game."""
def __init__(self) -> None:
self.respawn_timer: Optional[ba.Timer] = None
self.respawn_icon: Optional[RespawnIcon] = None
class Team(ba.Team[Player]): class Team(ba.Team[Player]):
"""Our team type for this game.""" """Our team type for this game."""
@ -1113,20 +1117,20 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
# Respawn them shortly. # Respawn them shortly.
assert self.initial_player_info is not None assert self.initial_player_info is not None
respawn_time = 2.0 + len(self.initial_player_info) * 1.0 respawn_time = 2.0 + len(self.initial_player_info) * 1.0
player.gamedata['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.gamedata['respawn_icon'] = RespawnIcon(player, respawn_time) player.respawn_icon = RespawnIcon(player, respawn_time)
elif isinstance(msg, SpazBotDiedMessage): elif isinstance(msg, SpazBotDiedMessage):
if msg.how is ba.DeathType.REACHED_GOAL: if msg.how is ba.DeathType.REACHED_GOAL:
return return None
pts, importance = msg.badguy.get_death_points(msg.how) pts, importance = msg.spazbot.get_death_points(msg.how)
if msg.killerplayer is not None: if msg.killerplayer is not None:
target: Optional[Sequence[float]] target: Optional[Sequence[float]]
try: try:
assert msg.badguy is not None assert msg.spazbot is not None
assert msg.badguy.node assert msg.spazbot.node
target = msg.badguy.node.position target = msg.spazbot.node.position
except Exception: except Exception:
ba.print_exception() ba.print_exception()
target = None target = None
@ -1151,7 +1155,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._update_scores() self._update_scores()
else: else:
super().handlemessage(msg) return super().handlemessage(msg)
return None
def _get_bot_speed(self, bot_type: Type[SpazBot]) -> float: def _get_bot_speed(self, bot_type: Type[SpazBot]) -> float:
speed = self._bot_speed_map.get(bot_type) speed = self._bot_speed_map.get(bot_type)

View File

@ -272,11 +272,11 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
self._update_scores() self._update_scores()
elif isinstance(msg, SpazBotDiedMessage): elif isinstance(msg, SpazBotDiedMessage):
pts, importance = msg.badguy.get_death_points(msg.how) pts, importance = msg.spazbot.get_death_points(msg.how)
target: Optional[Sequence[float]] target: Optional[Sequence[float]]
if msg.killerplayer: if msg.killerplayer:
assert msg.badguy.node assert msg.spazbot.node
target = msg.badguy.node.position target = msg.spazbot.node.position
self.stats.player_scored(msg.killerplayer, self.stats.player_scored(msg.killerplayer,
pts, pts,
target=target, target=target,

View File

@ -28,8 +28,8 @@
# pylint: disable=missing-function-docstring, missing-class-docstring # pylint: disable=missing-function-docstring, missing-class-docstring
# pylint: disable=invalid-name # pylint: disable=invalid-name
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
# pylint: disable=unused-variable
# pylint: disable=unused-argument # pylint: disable=unused-argument
# pylint: disable=unused-variable
from __future__ import annotations from __future__ import annotations
@ -181,7 +181,21 @@ class ButtonRelease:
timeformat=ba.TimeFormat.MILLISECONDS) timeformat=ba.TimeFormat.MILLISECONDS)
class TutorialActivity(ba.Activity[ba.Player, ba.Team]): class Player(ba.Player['Team']):
"""Our player type for this game."""
def __init__(self) -> None:
self.pressed = False
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
pass
class TutorialActivity(ba.Activity[Player, Team]):
def __init__(self, settings: Dict[str, Any] = None): def __init__(self, settings: Dict[str, Any] = None):
from bastd.maps import Rampage from bastd.maps import Rampage
@ -462,6 +476,7 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
n.opacity = 0.0 n.opacity = 0.0
a.set_stick_image_position(0, 0) a.set_stick_image_position(0, 0)
# Can be used for debugging.
class SetSpeed: class SetSpeed:
def __init__(self, speed: int): def __init__(self, speed: int):
@ -2330,7 +2345,7 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
ba.WeakCall(self._read_entries)) ba.WeakCall(self._read_entries))
def _update_skip_votes(self) -> None: def _update_skip_votes(self) -> None:
count = sum(1 for player in self.players if player.gamedata['pressed']) count = sum(1 for player in self.players if player.pressed)
assert self._skip_count_text assert self._skip_count_text
self._skip_count_text.text = ba.Lstr( self._skip_count_text.text = ba.Lstr(
resource=self._r + '.skipVoteCountText', resource=self._r + '.skipVoteCountText',
@ -2349,7 +2364,7 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
self._skip_text.text = '' self._skip_text.text = ''
self.end() self.end()
def _player_pressed_button(self, player: ba.Player) -> None: def _player_pressed_button(self, player: Player) -> None:
# Special case: if there's only one player, we give them a # Special case: if there's only one player, we give them a
# warning on their first press (some players were thinking the # warning on their first press (some players were thinking the
@ -2363,7 +2378,7 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
self._skip_text.scale = 1.3 self._skip_text.scale = 1.3
incr = 50 incr = 50
t = incr t = incr
for i in range(6): for _i in range(6):
ba.timer(t, ba.timer(t,
ba.Call(setattr, self._skip_text, 'color', ba.Call(setattr, self._skip_text, 'color',
(1, 0.5, 0.1)), (1, 0.5, 0.1)),
@ -2376,7 +2391,7 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
ba.timer(6.0, ba.WeakCall(self._revert_confirm)) ba.timer(6.0, ba.WeakCall(self._revert_confirm))
return return
player.gamedata['pressed'] = True player.pressed = True
# test... # test...
if not all(self.players): if not all(self.players):
@ -2393,15 +2408,15 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
self._skip_text.color = (1, 1, 1) self._skip_text.color = (1, 1, 1)
self._issued_warning = False self._issued_warning = False
def on_player_join(self, player: ba.Player) -> None: def on_player_join(self, player: Player) -> None:
super().on_player_join(player) super().on_player_join(player)
player.gamedata['pressed'] = False
# we just wanna know if this player presses anything.. # We just wanna know if this player presses anything.
player.assign_input_call( player.assign_input_call(
('jumpPress', 'punchPress', 'bombPress', 'pickUpPress'), ('jumpPress', 'punchPress', 'bombPress', 'pickUpPress'),
ba.Call(self._player_pressed_button, player)) ba.Call(self._player_pressed_button, player))
def on_player_leave(self, player: ba.Player) -> None: def on_player_leave(self, player: Player) -> None:
if not all(self.players): if not all(self.players):
ba.print_error('Nonexistent player in on_player_leave: ' + ba.print_error('Nonexistent player in on_player_leave: ' +
str([str(p) for p in self.players]) + ': we are ' + str([str(p) for p in self.players]) + ': we are ' +

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-05-25 for Ballistica version 1.5.0 build 20029</em></h4> <h4><em>last updated on 2020-05-27 for Ballistica version 1.5.0 build 20030</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>
@ -97,6 +97,7 @@
<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>
<li><a href="#function_ba_vec3validate">ba.vec3validate()</a></li> <li><a href="#function_ba_vec3validate">ba.vec3validate()</a></li>
<li><a href="#function_ba_verify_object_death">ba.verify_object_death()</a></li>
</ul> </ul>
<h4><a name="class_category_Asset_Classes">Asset Classes</a></h4> <h4><a name="class_category_Asset_Classes">Asset Classes</a></h4>
<ul> <ul>
@ -1644,7 +1645,7 @@ and it should begin its actual game logic.</p>
<h3>Attributes Inherited:</h3> <h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5> <h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h3>Methods Inherited:</h3> <h3>Methods Inherited:</h3>
<h5><a href="#method_ba_Session__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_Session__end">end()</a>, <a href="#method_ba_Session__end_activity">end_activity()</a>, <a href="#method_ba_Session__getactivity">getactivity()</a>, <a href="#method_ba_Session__handlemessage">handlemessage()</a>, <a href="#method_ba_Session__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_Session__on_player_request">on_player_request()</a>, <a href="#method_ba_Session__on_team_join">on_team_join()</a>, <a href="#method_ba_Session__on_team_leave">on_team_leave()</a>, <a href="#method_ba_Session__set_activity">set_activity()</a></h5> <h5><a href="#method_ba_Session__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_Session__end">end()</a>, <a href="#method_ba_Session__end_activity">end_activity()</a>, <a href="#method_ba_Session__getactivity">getactivity()</a>, <a href="#method_ba_Session__handlemessage">handlemessage()</a>, <a href="#method_ba_Session__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_Session__on_player_request">on_player_request()</a>, <a href="#method_ba_Session__on_team_join">on_team_join()</a>, <a href="#method_ba_Session__on_team_leave">on_team_leave()</a>, <a href="#method_ba_Session__set_activity">set_activity()</a>, <a href="#method_ba_Session__transitioning_out_activity_was_freed">transitioning_out_activity_was_freed()</a></h5>
<h3>Methods Defined or Overridden:</h3> <h3>Methods Defined or Overridden:</h3>
<h5><a href="#method_ba_CoopSession____init__">&lt;constructor&gt;</a>, <a href="#method_ba_CoopSession__get_current_game_instance">get_current_game_instance()</a>, <a href="#method_ba_CoopSession__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_CoopSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_CoopSession__on_player_leave">on_player_leave()</a>, <a href="#method_ba_CoopSession__restart">restart()</a></h5> <h5><a href="#method_ba_CoopSession____init__">&lt;constructor&gt;</a>, <a href="#method_ba_CoopSession__get_current_game_instance">get_current_game_instance()</a>, <a href="#method_ba_CoopSession__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_CoopSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_CoopSession__on_player_leave">on_player_leave()</a>, <a href="#method_ba_CoopSession__restart">restart()</a></h5>
<dl> <dl>
@ -1987,7 +1988,7 @@ its time with lingering corpses, sound effects, etc.</p>
<h3>Attributes Inherited:</h3> <h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5> <h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h3>Methods Inherited:</h3> <h3>Methods Inherited:</h3>
<h5><a href="#method_ba_MultiTeamSession__announce_game_results">announce_game_results()</a>, <a href="#method_ba_MultiTeamSession__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_MultiTeamSession__end">end()</a>, <a href="#method_ba_MultiTeamSession__end_activity">end_activity()</a>, <a href="#method_ba_MultiTeamSession__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_MultiTeamSession__get_ffa_series_length">get_ffa_series_length()</a>, <a href="#method_ba_MultiTeamSession__get_game_number">get_game_number()</a>, <a href="#method_ba_MultiTeamSession__get_max_players">get_max_players()</a>, <a href="#method_ba_MultiTeamSession__get_next_game_description">get_next_game_description()</a>, <a href="#method_ba_MultiTeamSession__get_series_length">get_series_length()</a>, <a href="#method_ba_MultiTeamSession__getactivity">getactivity()</a>, <a href="#method_ba_MultiTeamSession__handlemessage">handlemessage()</a>, <a href="#method_ba_MultiTeamSession__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_MultiTeamSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_MultiTeamSession__on_player_leave">on_player_leave()</a>, <a href="#method_ba_MultiTeamSession__on_player_request">on_player_request()</a>, <a href="#method_ba_MultiTeamSession__on_team_join">on_team_join()</a>, <a href="#method_ba_MultiTeamSession__on_team_leave">on_team_leave()</a>, <a href="#method_ba_MultiTeamSession__set_activity">set_activity()</a></h5> <h5><a href="#method_ba_MultiTeamSession__announce_game_results">announce_game_results()</a>, <a href="#method_ba_MultiTeamSession__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_MultiTeamSession__end">end()</a>, <a href="#method_ba_MultiTeamSession__end_activity">end_activity()</a>, <a href="#method_ba_MultiTeamSession__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_MultiTeamSession__get_ffa_series_length">get_ffa_series_length()</a>, <a href="#method_ba_MultiTeamSession__get_game_number">get_game_number()</a>, <a href="#method_ba_MultiTeamSession__get_max_players">get_max_players()</a>, <a href="#method_ba_MultiTeamSession__get_next_game_description">get_next_game_description()</a>, <a href="#method_ba_MultiTeamSession__get_series_length">get_series_length()</a>, <a href="#method_ba_MultiTeamSession__getactivity">getactivity()</a>, <a href="#method_ba_MultiTeamSession__handlemessage">handlemessage()</a>, <a href="#method_ba_MultiTeamSession__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_MultiTeamSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_MultiTeamSession__on_player_leave">on_player_leave()</a>, <a href="#method_ba_MultiTeamSession__on_player_request">on_player_request()</a>, <a href="#method_ba_MultiTeamSession__on_team_join">on_team_join()</a>, <a href="#method_ba_MultiTeamSession__on_team_leave">on_team_leave()</a>, <a href="#method_ba_MultiTeamSession__set_activity">set_activity()</a>, <a href="#method_ba_MultiTeamSession__transitioning_out_activity_was_freed">transitioning_out_activity_was_freed()</a></h5>
<h3>Methods Defined or Overridden:</h3> <h3>Methods Defined or Overridden:</h3>
<dl> <dl>
<dt><h4><a name="method_ba_DualTeamSession____init__">&lt;constructor&gt;</a></dt></h4><dd> <dt><h4><a name="method_ba_DualTeamSession____init__">&lt;constructor&gt;</a></dt></h4><dd>
@ -2025,7 +2026,7 @@ its time with lingering corpses, sound effects, etc.</p>
<h3>Attributes Inherited:</h3> <h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5> <h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h3>Methods Inherited:</h3> <h3>Methods Inherited:</h3>
<h5><a href="#method_ba_MultiTeamSession__announce_game_results">announce_game_results()</a>, <a href="#method_ba_MultiTeamSession__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_MultiTeamSession__end">end()</a>, <a href="#method_ba_MultiTeamSession__end_activity">end_activity()</a>, <a href="#method_ba_MultiTeamSession__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_MultiTeamSession__get_ffa_series_length">get_ffa_series_length()</a>, <a href="#method_ba_MultiTeamSession__get_game_number">get_game_number()</a>, <a href="#method_ba_MultiTeamSession__get_max_players">get_max_players()</a>, <a href="#method_ba_MultiTeamSession__get_next_game_description">get_next_game_description()</a>, <a href="#method_ba_MultiTeamSession__get_series_length">get_series_length()</a>, <a href="#method_ba_MultiTeamSession__getactivity">getactivity()</a>, <a href="#method_ba_MultiTeamSession__handlemessage">handlemessage()</a>, <a href="#method_ba_MultiTeamSession__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_MultiTeamSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_MultiTeamSession__on_player_leave">on_player_leave()</a>, <a href="#method_ba_MultiTeamSession__on_player_request">on_player_request()</a>, <a href="#method_ba_MultiTeamSession__on_team_join">on_team_join()</a>, <a href="#method_ba_MultiTeamSession__on_team_leave">on_team_leave()</a>, <a href="#method_ba_MultiTeamSession__set_activity">set_activity()</a></h5> <h5><a href="#method_ba_MultiTeamSession__announce_game_results">announce_game_results()</a>, <a href="#method_ba_MultiTeamSession__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_MultiTeamSession__end">end()</a>, <a href="#method_ba_MultiTeamSession__end_activity">end_activity()</a>, <a href="#method_ba_MultiTeamSession__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_MultiTeamSession__get_ffa_series_length">get_ffa_series_length()</a>, <a href="#method_ba_MultiTeamSession__get_game_number">get_game_number()</a>, <a href="#method_ba_MultiTeamSession__get_max_players">get_max_players()</a>, <a href="#method_ba_MultiTeamSession__get_next_game_description">get_next_game_description()</a>, <a href="#method_ba_MultiTeamSession__get_series_length">get_series_length()</a>, <a href="#method_ba_MultiTeamSession__getactivity">getactivity()</a>, <a href="#method_ba_MultiTeamSession__handlemessage">handlemessage()</a>, <a href="#method_ba_MultiTeamSession__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_MultiTeamSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_MultiTeamSession__on_player_leave">on_player_leave()</a>, <a href="#method_ba_MultiTeamSession__on_player_request">on_player_request()</a>, <a href="#method_ba_MultiTeamSession__on_team_join">on_team_join()</a>, <a href="#method_ba_MultiTeamSession__on_team_leave">on_team_leave()</a>, <a href="#method_ba_MultiTeamSession__set_activity">set_activity()</a>, <a href="#method_ba_MultiTeamSession__transitioning_out_activity_was_freed">transitioning_out_activity_was_freed()</a></h5>
<h3>Methods Defined or Overridden:</h3> <h3>Methods Defined or Overridden:</h3>
<h5><a href="#method_ba_FreeForAllSession____init__">&lt;constructor&gt;</a>, <a href="#method_ba_FreeForAllSession__get_ffa_point_awards">get_ffa_point_awards()</a></h5> <h5><a href="#method_ba_FreeForAllSession____init__">&lt;constructor&gt;</a>, <a href="#method_ba_FreeForAllSession__get_ffa_point_awards">get_ffa_point_awards()</a></h5>
<dl> <dl>
@ -3329,7 +3330,7 @@ Use <a href="#function_ba_getmodel">ba.getmodel</a>() to instantiate one.</p>
<h3>Attributes Inherited:</h3> <h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5> <h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<h3>Methods Inherited:</h3> <h3>Methods Inherited:</h3>
<h5><a href="#method_ba_Session__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_Session__end">end()</a>, <a href="#method_ba_Session__end_activity">end_activity()</a>, <a href="#method_ba_Session__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_Session__getactivity">getactivity()</a>, <a href="#method_ba_Session__handlemessage">handlemessage()</a>, <a href="#method_ba_Session__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_Session__on_player_leave">on_player_leave()</a>, <a href="#method_ba_Session__on_player_request">on_player_request()</a>, <a href="#method_ba_Session__on_team_leave">on_team_leave()</a>, <a href="#method_ba_Session__set_activity">set_activity()</a></h5> <h5><a href="#method_ba_Session__begin_next_activity">begin_next_activity()</a>, <a href="#method_ba_Session__end">end()</a>, <a href="#method_ba_Session__end_activity">end_activity()</a>, <a href="#method_ba_Session__get_custom_menu_entries">get_custom_menu_entries()</a>, <a href="#method_ba_Session__getactivity">getactivity()</a>, <a href="#method_ba_Session__handlemessage">handlemessage()</a>, <a href="#method_ba_Session__launch_end_session_activity">launch_end_session_activity()</a>, <a href="#method_ba_Session__on_player_leave">on_player_leave()</a>, <a href="#method_ba_Session__on_player_request">on_player_request()</a>, <a href="#method_ba_Session__on_team_leave">on_team_leave()</a>, <a href="#method_ba_Session__set_activity">set_activity()</a>, <a href="#method_ba_Session__transitioning_out_activity_was_freed">transitioning_out_activity_was_freed()</a></h5>
<h3>Methods Defined or Overridden:</h3> <h3>Methods Defined or Overridden:</h3>
<h5><a href="#method_ba_MultiTeamSession____init__">&lt;constructor&gt;</a>, <a href="#method_ba_MultiTeamSession__announce_game_results">announce_game_results()</a>, <a href="#method_ba_MultiTeamSession__get_ffa_series_length">get_ffa_series_length()</a>, <a href="#method_ba_MultiTeamSession__get_game_number">get_game_number()</a>, <a href="#method_ba_MultiTeamSession__get_max_players">get_max_players()</a>, <a href="#method_ba_MultiTeamSession__get_next_game_description">get_next_game_description()</a>, <a href="#method_ba_MultiTeamSession__get_series_length">get_series_length()</a>, <a href="#method_ba_MultiTeamSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_MultiTeamSession__on_team_join">on_team_join()</a></h5> <h5><a href="#method_ba_MultiTeamSession____init__">&lt;constructor&gt;</a>, <a href="#method_ba_MultiTeamSession__announce_game_results">announce_game_results()</a>, <a href="#method_ba_MultiTeamSession__get_ffa_series_length">get_ffa_series_length()</a>, <a href="#method_ba_MultiTeamSession__get_game_number">get_game_number()</a>, <a href="#method_ba_MultiTeamSession__get_max_players">get_max_players()</a>, <a href="#method_ba_MultiTeamSession__get_next_game_description">get_next_game_description()</a>, <a href="#method_ba_MultiTeamSession__get_series_length">get_series_length()</a>, <a href="#method_ba_MultiTeamSession__on_activity_end">on_activity_end()</a>, <a href="#method_ba_MultiTeamSession__on_team_join">on_team_join()</a></h5>
<dl> <dl>
@ -6522,6 +6523,16 @@ so this can be used to disambiguate 'Any' types).
Generally this should be used in 'if __debug__' or assert clauses Generally this should be used in 'if __debug__' or assert clauses
to keep runtime overhead minimal.</p> to keep runtime overhead minimal.</p>
<hr>
<h2><strong><a name="function_ba_verify_object_death">ba.verify_object_death()</a></strong></h3>
<p><span>verify_object_death(obj: object) -&gt; None</span></p>
<p>Warn if an object does not get freed within a short period.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>This can be handy to detect and prevent memory/resource leaks.</p>
<hr> <hr>
<h2><strong><a name="function_ba_widget">ba.widget()</a></strong></h3> <h2><strong><a name="function_ba_widget">ba.widget()</a></strong></h3>
<p><span>widget(edit: <a href="#class_ba_Widget">ba.Widget</a> = None, up_widget: <a href="#class_ba_Widget">ba.Widget</a> = None, <p><span>widget(edit: <a href="#class_ba_Widget">ba.Widget</a> = None, up_widget: <a href="#class_ba_Widget">ba.Widget</a> = None,

View File

@ -26,6 +26,7 @@ import json
import os import os
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from efro.terminal import Clr
from efrotools import get_files_hash from efrotools import get_files_hash
if TYPE_CHECKING: if TYPE_CHECKING:
@ -104,8 +105,8 @@ class FileCache:
# if anything has been modified, don't write. # if anything has been modified, don't write.
for fname, mtime in self.mtimes.items(): for fname, mtime in self.mtimes.items():
if os.path.getmtime(fname) != mtime: if os.path.getmtime(fname) != mtime:
print('File changed during run: "' + fname + '";' + print(f'{Clr.YLW}File changed during run:'
' cache not updated.') f' "{fname}"; cache not updated.{Clr.RST}')
return return
out = json.dumps(self.entries) out = json.dumps(self.entries)
with open(self._path, 'w') as outfile: with open(self._path, 'w') as outfile: