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/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ba/35/3b6bc5c5609b1dd37bd65c39df45",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a6/bc/c2c7231dc6bf085eda15d6198554",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/fd/020ed9bb0e8c8a18b2d793fee8bd",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/43/0b/78c8bacb215abaf50dcb3284eef7",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f8/e1/e0dc64b5c00661cce19530c0e836",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e6/74/73a514993d626a6bc75717d185ef",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f0/7d/dbd2624759a1fdce2a20d53cab1a",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1c/01/4833ec215cc6c53f7e4ebf850608",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/b1/57/b72500d2a568df5afa36556f89dd",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/48/ea/c83f97f44703b16eeec794d29da6",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a3/16/284b9953c7ef4a841ff907079cbd",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/12/43/d0513cf8f8dac0712cbf42d4b94b"
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ca/db/9c7cfd4e4f4a1f7a7adc980bca42",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4e/0b/231e38ff29d932df7552050891c5",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f2/56/bb316ec28ee98ece5c0c3a04b77f",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ef/92/d787c99db6cc85f70b7131ff2c0c",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/97/b9/9c6c3c90f10d319250a9f3d287b3",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f0/2a/60bdf1c4d4e13bdbb5f4df121e3e",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/66/25/79ea606983dc91ac0cd79c1e7da6",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/70/c6/0ab2cdf222ffcadade37dd3b8462",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/3c/65/450a67dab189c0832b6bf28a9e9c",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7e/a9/e1ab6defb8bcf536dff46d0c62b2",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ab/fc/d00336dae2b1c7323b31518b52aa",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ed/98/dbea1af1da83bfa1a3283175b234"
}

View File

@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand.
"""
# (hash we can use to see if this file is out of date)
# SOURCES_HASH=223083205204988067566025188831386474803
# SOURCES_HASH=265401783818737452594582363319036908124
# I'm sorry Pylint. I know this file saddens you. Be strong.
# 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._gameutils import (animate, animate_array, show_damage_count,
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._lobby import Lobby, Chooser
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._error import print_exception, print_error, SessionTeamNotFoundError
from ba._dependency import DependencyComponent
from ba._general import Call, verify_object_death
import _ba
if TYPE_CHECKING:
@ -217,7 +218,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self._stats: Optional[ba.Stats] = 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
# 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'):
self._expire()
# 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)
# Now that our object is officially gonna be dead, tell the session it
# can fire up the next activity.
# Inform our owner that we're officially kicking the bucket.
if self._transitioning_out:
session = self._session()
if session is not None:
with _ba.Context(session):
if self.can_show_ad_on_death:
call_after_ad(session.begin_next_activity)
else:
_ba.pushcall(session.begin_next_activity)
_ba.pushcall(
Call(session.transitioning_out_activity_was_freed,
self.can_show_ad_on_death))
@property
def stats(self) -> ba.Stats:
@ -304,7 +297,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
(internal)
"""
from ba._general import Call
from ba._enums import TimeType
# 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)
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):
# Make a decent attempt to persevere if user code breaks.
try:
@ -670,6 +666,10 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self.teams.remove(team)
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):
# Make a decent attempt to persevere if user code breaks.
try:
@ -680,6 +680,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
sessionteam.reset_gamedata()
except Exception:
print_exception(f'Error in reset_gamedata for {self}')
sessionteam.gameteam = 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:
actor = actor_ref()
if actor is not None:
verify_object_death(actor)
try:
actor.on_expire()
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.
# (releases any attached actors, clears game-data, etc)
for player in self.players:
if player:
try:
sessionplayer = player.sessionplayer
player.reset()
sessionplayer.set_node(None)
sessionplayer.set_activity(None)
sessionplayer.gameplayer = None
sessionplayer.reset()
except Exception:
print_exception(f'Error resetting Player {player}')
try:
# This should allow our ba.Player instance to die.
# Complain if that doesn't happen.
# verify_object_death(player)
sessionplayer = player.sessionplayer
player.reset()
sessionplayer.set_node(None)
sessionplayer.set_activity(None)
sessionplayer.gameplayer = None
sessionplayer.reset()
except Exception:
print_exception(f'Error resetting Player {player}')
# Ditto with Teams.
for team in self.teams:
try:
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.reset_gamedata()
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
except Exception:
print_exception(f'Error resetting Team {team}')

View File

@ -21,16 +21,22 @@
"""Utility snippets applying to generic Python code."""
from __future__ import annotations
import gc
import types
import weakref
import random
from typing import TYPE_CHECKING, TypeVar
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
if TYPE_CHECKING:
from typing import Any, Type, Optional
from efro.call import Call as Call # 'as Call' so we re-export.
from weakref import ReferenceType
class Existable(Protocol):
@ -100,22 +106,18 @@ def json_prep(data: Any) -> Any:
if isinstance(data, list):
return [json_prep(element) for element in data]
if isinstance(data, tuple):
from ba import _error
_error.print_error('json_prep encountered tuple', once=True)
print_error('json_prep encountered tuple', once=True)
return [json_prep(element) for element in data]
if isinstance(data, bytes):
try:
return data.decode(errors='ignore')
except Exception:
from ba import _error
_error.print_error('json_prep encountered utf-8 decode error',
once=True)
print_error('json_prep encountered utf-8 decode error', once=True)
return data.decode(errors='ignore')
if not isinstance(data, (str, float, bool, type(None), int)):
from ba import _error
_error.print_error('got unsupported type in json_prep:' +
str(type(data)),
once=True)
print_error('got unsupported type in json_prep:' + str(type(data)),
once=True)
return data
@ -135,7 +137,6 @@ def utf8_all(data: Any) -> Any:
def print_refs(obj: Any) -> None:
"""Print a list of known live references to an object."""
import gc
# Hmmm; I just noticed that calling this on an object
# seems to keep it alive. Should figure out why.
@ -291,3 +292,47 @@ class WeakMethod:
def __str__(self) -> str:
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.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),
(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]]:
@ -75,8 +75,8 @@ def get_player_profile_colors(
if profiles is None:
profiles = bs_config['Player Profiles']
# special case - when being asked for a random color in kiosk mode,
# always return default purple
# Special case: when being asked for a random color in kiosk mode,
# always return default purple.
if _ba.app.kiosk_mode and profilename is None:
color = (0.5, 0.4, 1.0)
highlight = (0.4, 0.4, 0.5)
@ -85,22 +85,22 @@ def get_player_profile_colors(
assert profilename is not None
color = profiles[profilename]['color']
except (KeyError, AssertionError):
# key off name if possible
# Key off name if possible.
if profilename is None:
# first 6 are bright-ish
# First 6 are bright-ish.
color = PLAYER_COLORS[random.randrange(6)]
else:
# first 6 are bright-ish
# First 6 are bright-ish.
color = PLAYER_COLORS[sum([ord(c) for c in profilename]) % 6]
try:
assert profilename is not None
highlight = profiles[profilename]['highlight']
except (KeyError, AssertionError):
# key off name if possible
# Key off name if possible.
if profilename is None:
# last 2 are grey and white; ignore those or we
# get lots of old-looking players
# Last 2 are grey and white; ignore those or we
# get lots of old-looking players.
highlight = PLAYER_COLORS[random.randrange(
len(PLAYER_COLORS) - 2)]
else:

View File

@ -582,6 +582,21 @@ class Session:
self._add_chosen_player(chooser)
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:
from ba._team import SessionTeam
sessionplayer = chooser.getplayer()

View File

@ -40,18 +40,17 @@ class RespawnIcon:
"""
def __init__(self, player: ba.Player, respawn_time: float):
"""
Instantiate with a given ba.Player and respawn_time (in seconds)
"""
"""Instantiate with a ba.Player and respawn_time (in seconds)."""
self._visible = True
on_right, offs_extra, respawn_icons = self._get_context(player)
try:
mask_tex = (player.team.gamedata['_spaz_respawn_icons_mask_tex'])
except Exception:
mask_tex = player.team.gamedata['_spaz_respawn_icons_mask_tex'] = (
ba.gettexture('characterIconMask'))
# Cache our mask tex on the team for easy access.
mask_tex = getattr(player.team, '_spaz_respawn_icons_mask_tex', None)
if mask_tex is None:
mask_tex = 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.
index = 0
@ -139,12 +138,14 @@ class RespawnIcon:
on_right = player.team.id % 2 == 1
# Store a list of icons in the team.
try:
respawn_icons = (
player.team.gamedata['_spaz_respawn_icons_right'])
except Exception:
respawn_icons = (
player.team.gamedata['_spaz_respawn_icons_right']) = {}
respawn_icons = getattr(player.team, '_spaz_respawn_icons_right',
None)
if respawn_icons is None:
respawn_icons = {}
setattr(player.team, '_spaz_respawn_icons_right',
respawn_icons)
assert isinstance(respawn_icons, dict)
offs_extra = -20
else:
on_right = False

View File

@ -200,7 +200,8 @@ class _Entry:
def set_position(self, position: Sequence[float]) -> None:
"""Set the entry's position."""
# abort if we've been killed
# Abort if we've been killed
if not self._backing.node:
return
self._pos = tuple(position)
@ -315,13 +316,15 @@ class _EntryProxy:
def __init__(self, scoreboard: Scoreboard, team: ba.Team):
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
def __del__(self) -> None:
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:
scoreboard.remove_team(self._team_id)
@ -343,7 +346,7 @@ class Scoreboard:
self._label = label
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]
if isinstance(ba.getsession(), ba.FreeForAllSession):
self._do_cover = False
@ -368,11 +371,11 @@ class Scoreboard:
"""Update the score-board display for the given ba.Team."""
if not team.id in self._entries:
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)
if '_scoreboard_entry' in team.gamedata:
raise Exception('existing _EntryProxy found')
team.gamedata['_scoreboard_entry'] = _EntryProxy(self, team)
assert not hasattr(team, '_scoreboard_entry')
setattr(team, '_scoreboard_entry', _EntryProxy(self, team))
# Now set the entry.
self._entries[team.id].set_value(score=score,
@ -383,7 +386,7 @@ class Scoreboard:
def _add_team(self, team: ba.Team) -> None:
if team.id in self._entries:
raise Exception('Duplicate team add')
raise RuntimeError('Duplicate team add')
self._entries[team.id] = _Entry(self,
team,
do_cover=self._do_cover,

View File

@ -49,16 +49,16 @@ class SpazBotPunchedMessage:
Attributes:
badguy
spazbot
The ba.SpazBot that got punched.
damage
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."""
self.badguy = badguy
self.spazbot = spazbot
self.damage = damage
@ -69,7 +69,7 @@ class SpazBotDiedMessage:
Attributes:
badguy
spazbot
The ba.SpazBot that was killed.
killerplayer
@ -79,10 +79,10 @@ class SpazBotDiedMessage:
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):
"""Instantiate with given values."""
self.badguy = badguy
self.spazbot = spazbot
self.killerplayer = killerplayer
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.
elif isinstance(msg, SpazBotDiedMessage):
self._spawn_evil_bunny()
assert msg.badguy.node
pos = msg.badguy.node.position
assert msg.spazbot.node
pos = msg.spazbot.node.position
for _i in range(6):
spread = 0.4
self._eggs.append(

View File

@ -816,7 +816,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
elif isinstance(msg, SpazBotDiedMessage):
# 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):
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
class PuckDeathMessage:
class PuckDiedMessage:
"""Inform something that a puck has died."""
def __init__(self, puck: Puck):
@ -78,7 +78,7 @@ class Puck(ba.Actor):
self.node.delete()
activity = self._activity()
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.
elif isinstance(msg, ba.OutOfBoundsMessage):
@ -113,6 +113,9 @@ class Player(ba.Player['Team']):
class Team(ba.Team[Player]):
"""Our team type for this game."""
def __init__(self) -> None:
self.score = 0
# ba_meta export game
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._score_regions: Optional[List[ba.NodeActor]] = 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]:
if self.settings_raw['Score to Win'] == 1:
if self._score_to_win == 1:
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]:
if self.settings_raw['Score to Win'] == 1:
if self._score_to_win == 1:
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:
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._puck_spawn_pos = self.map.get_flag_position(None)
self._spawn_puck()
# set up the two score regions
# Set up the two score regions.
defs = self.map.defs
self._score_regions = []
self._score_regions.append(
@ -240,7 +245,6 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
ba.playsound(self._chant_sound)
def on_team_join(self, team: Team) -> None:
team.gamedata['score'] = 0
self._update_scoreboard()
def _handle_puck_player_collide(self) -> None:
@ -274,7 +278,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
for team in self.teams:
if team.id == index:
scoring_team = team
team.gamedata['score'] += 1
team.score += 1
# Tell all players to celebrate.
for player in team.players:
@ -291,7 +295,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
big_message=True)
# 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()
ba.playsound(self._foghorn_sound)
@ -317,14 +321,13 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
def end_game(self) -> None:
results = ba.TeamGameResults()
for team in self.teams:
results.set_team_score(team, team.gamedata['score'])
results.set_team_score(team, team.score)
self.end(results=results)
def _update_scoreboard(self) -> None:
winscore = self.settings_raw['Score to Win']
winscore = self._score_to_win
for team in self.teams:
self._scoreboard.set_team_value(team, team.gamedata['score'],
winscore)
self._scoreboard.set_team_value(team, team.score, winscore)
def handlemessage(self, msg: Any) -> Any:
@ -335,7 +338,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
self.respawn_player(msg.getplayer(Player))
# Respawn dead pucks.
elif isinstance(msg, PuckDeathMessage):
elif isinstance(msg, PuckDiedMessage):
if not self.has_ended():
ba.timer(3.0, self._spawn_puck)
else:

View File

@ -1220,13 +1220,13 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
ba.timer(0.1, self._checkroundover)
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:
self._handle_kill_achievements(msg)
target: Optional[Sequence[float]]
try:
assert msg.badguy.node
target = msg.badguy.node.position
assert msg.spazbot.node
target = msg.spazbot.node.position
except Exception:
ba.print_exception()
target = None
@ -1265,13 +1265,13 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _handle_uber_kill_achievements(self, msg: SpazBotDiedMessage) -> None:
# 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
if self._land_mine_kills >= 6:
self._award_achievement('Gold Miner')
# Uber tnt achievement:
if msg.badguy.last_attacked_type == ('explosion', 'tnt'):
if msg.spazbot.last_attacked_type == ('explosion', 'tnt'):
self._tnt_kills += 1
if self._tnt_kills >= 6:
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:
# TNT achievement:
if msg.badguy.last_attacked_type == ('explosion', 'tnt'):
if msg.spazbot.last_attacked_type == ('explosion', 'tnt'):
self._tnt_kills += 1
if self._tnt_kills >= 3:
ba.timer(
@ -1291,7 +1291,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _handle_rookie_kill_achievements(self,
msg: SpazBotDiedMessage) -> None:
# 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
if self._land_mine_kills >= 3:
self._award_achievement('Mine Games')
@ -1299,7 +1299,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _handle_training_kill_achievements(self,
msg: SpazBotDiedMessage) -> None:
# 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
if self._throw_off_kills >= 3:
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]:
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.'
else:
t_str = ''
if self.settings_raw['Laps'] > 1:
return 'Run ${ARG1} laps.' + t_str, self.settings_raw['Laps']
if self._laps > 1:
return 'Run ${ARG1} laps.' + t_str, self._laps
return 'Run 1 lap.' + t_str
def get_instance_description_short(self) -> Union[str, Sequence]:
if self.settings_raw['Laps'] > 1:
return 'run ${ARG1} laps', self.settings_raw['Laps']
if self._laps > 1:
return 'run ${ARG1} laps', self._laps
return 'run 1 lap'
def on_transition_in(self) -> None:
@ -715,8 +715,7 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
# Augment default behavior.
super().handlemessage(msg)
super().handlemessage(msg) # Augment default behavior.
player = msg.getplayer(Player)
if not player.finished:
self.respawn_player(player, respawn_time=1)

View File

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

View File

@ -272,11 +272,11 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
self._update_scores()
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]]
if msg.killerplayer:
assert msg.badguy.node
target = msg.badguy.node.position
assert msg.spazbot.node
target = msg.spazbot.node.position
self.stats.player_scored(msg.killerplayer,
pts,
target=target,

View File

@ -28,8 +28,8 @@
# pylint: disable=missing-function-docstring, missing-class-docstring
# pylint: disable=invalid-name
# pylint: disable=too-many-locals
# pylint: disable=unused-variable
# pylint: disable=unused-argument
# pylint: disable=unused-variable
from __future__ import annotations
@ -181,7 +181,21 @@ class ButtonRelease:
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):
from bastd.maps import Rampage
@ -462,6 +476,7 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
n.opacity = 0.0
a.set_stick_image_position(0, 0)
# Can be used for debugging.
class SetSpeed:
def __init__(self, speed: int):
@ -2330,7 +2345,7 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
ba.WeakCall(self._read_entries))
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
self._skip_count_text.text = ba.Lstr(
resource=self._r + '.skipVoteCountText',
@ -2349,7 +2364,7 @@ class TutorialActivity(ba.Activity[ba.Player, ba.Team]):
self._skip_text.text = ''
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
# 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
incr = 50
t = incr
for i in range(6):
for _i in range(6):
ba.timer(t,
ba.Call(setattr, self._skip_text, 'color',
(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))
return
player.gamedata['pressed'] = True
player.pressed = True
# test...
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._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)
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(
('jumpPress', 'punchPress', 'bombPress', 'pickUpPress'),
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):
ba.print_error('Nonexistent player in on_player_leave: ' +
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 -->
<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,
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>
@ -97,6 +97,7 @@
<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_vec3validate">ba.vec3validate()</a></li>
<li><a href="#function_ba_verify_object_death">ba.verify_object_death()</a></li>
</ul>
<h4><a name="class_category_Asset_Classes">Asset Classes</a></h4>
<ul>
@ -1644,7 +1645,7 @@ and it should begin its actual game logic.</p>
<h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<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>
<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>
@ -1987,7 +1988,7 @@ its time with lingering corpses, sound effects, etc.</p>
<h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<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>
<dl>
<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>
<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>
<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>
<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>
@ -3329,7 +3330,7 @@ Use <a href="#function_ba_getmodel">ba.getmodel</a>() to instantiate one.</p>
<h3>Attributes Inherited:</h3>
<h5><a href="#attr_ba_Session__allow_mid_activity_joins">allow_mid_activity_joins</a>, <a href="#attr_ba_Session__campaign">campaign</a>, <a href="#attr_ba_Session__lobby">lobby</a>, <a href="#attr_ba_Session__max_players">max_players</a>, <a href="#attr_ba_Session__min_players">min_players</a>, <a href="#attr_ba_Session__players">players</a>, <a href="#attr_ba_Session__teams">teams</a>, <a href="#attr_ba_Session__use_team_colors">use_team_colors</a>, <a href="#attr_ba_Session__use_teams">use_teams</a></h5>
<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>
<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>
@ -6522,6 +6523,16 @@ so this can be used to disambiguate 'Any' types).
Generally this should be used in 'if __debug__' or assert clauses
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>
<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,

View File

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