From 9a48d6581aa93b6f2b9abed4b253a084ae25b728 Mon Sep 17 00:00:00 2001
From: Eric Froemling
Date: Wed, 3 Jun 2020 01:24:12 -0700
Subject: [PATCH] More general type cleanup
---
.efrocachemap | 24 +--
assets/src/ba_data/python/_ba.py | 2 +-
assets/src/ba_data/python/ba/__init__.py | 2 +-
assets/src/ba_data/python/ba/_activity.py | 13 +-
assets/src/ba_data/python/ba/_appdelegate.py | 4 +-
assets/src/ba_data/python/ba/_coopsession.py | 2 +-
assets/src/ba_data/python/ba/_gameactivity.py | 110 ++++----------
assets/src/ba_data/python/ba/_gameresults.py | 7 +-
assets/src/ba_data/python/ba/_gameutils.py | 12 ++
assets/src/ba_data/python/ba/_general.py | 2 +-
.../ba_data/python/ba/_multiteamsession.py | 2 +-
assets/src/ba_data/python/ba/_player.py | 8 +-
assets/src/ba_data/python/ba/_session.py | 8 +-
assets/src/ba_data/python/ba/_stats.py | 55 +++----
.../python/bastd/activity/multiteamscore.py | 2 +-
.../src/ba_data/python/bastd/appdelegate.py | 4 +-
.../ba_data/python/bastd/game/onslaught.py | 54 +++----
docs/ba_module.md | 138 +++++++-----------
18 files changed, 187 insertions(+), 262 deletions(-)
diff --git a/.efrocachemap b/.efrocachemap
index 9f34ff7b..f420e66d 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -4132,16 +4132,16 @@
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c",
"assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
- "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6d/a5/bc48ad0c1b5757913b8d354e4302",
- "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/94/e0/cd115dbd1ce795e9b6a2878e8912",
- "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/44/78/d3166e9e3f2f443c13838768b4ee",
- "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/79/af/4d26abbac53e9fc396d1fc5660ae",
- "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e0/85/8d8d8d74685d0823bc341942c31c",
- "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/34/81/06f6dff6c5686d1b2ffb1b44bb46",
- "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/34/96/f1d361405a41d118016a576ef517",
- "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/0c/7e/116fdd2bb269fd3c4c3826f526b9",
- "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/75/a6/320d0a4b79a1e0c0cb8fecbc69e2",
- "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/8a/de/f35f0be58d20cc58cf1ba078013a",
- "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/2b/23/849c8e6286a8de4f6140f249c59a",
- "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ba/fd/49fe8a41b0448e2fd81a462618cb"
+ "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4c/7f/785a205dbe19ee4099f427dd9ca2",
+ "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b3/10/7ab16ca078a401bbbe2310660b85",
+ "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ef/f5/be729c0261c6326c3ef92330b764",
+ "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/98/9d/98bc183187af679fbc6531e3ac04",
+ "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/17/07/aed82d9bc9c5b4dd3457fcea9a8a",
+ "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/71/5e/a837ebb98b829cd3bebf081e02b0",
+ "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/a0/3204f264abfd13f40f7f4f72aa65",
+ "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/68/ab/de21be17db5b93a7191c353dbce8",
+ "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/54/5a/e32b46e614ed37c7ebe42c73b03c",
+ "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/15/09/2245bdaf317da30c7d63d8559c82",
+ "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f6/45/6972dd74de4fc822a28e995a4a0d",
+ "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/34/74/e8ff832e808ceaa07a4015fb2a5f"
}
\ No newline at end of file
diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py
index e7c48f86..4fb9bba5 100644
--- a/assets/src/ba_data/python/_ba.py
+++ b/assets/src/ba_data/python/_ba.py
@@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand.
"""
# (hash we can use to see if this file is out of date)
-# SOURCES_HASH=289441941088504861465847265420796017643
+# SOURCES_HASH=102801821147286215106809880997032542189
# I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression
diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py
index 26bdf607..d257eca7 100644
--- a/assets/src/ba_data/python/ba/__init__.py
+++ b/assets/src/ba_data/python/ba/__init__.py
@@ -73,7 +73,7 @@ from ba._appconfig import AppConfig
from ba._appdelegate import AppDelegate
from ba._apputils import is_browser_likely_available
from ba._campaign import Campaign
-from ba._gameutils import (animate, animate_array, show_damage_count,
+from ba._gameutils import (GameTip, animate, animate_array, show_damage_count,
timestring, cameraflash)
from ba._general import (WeakCall, Call, existing, Existable,
verify_object_death, storagename)
diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py
index 0fa0d50e..08795ab9 100644
--- a/assets/src/ba_data/python/ba/_activity.py
+++ b/assets/src/ba_data/python/ba/_activity.py
@@ -169,12 +169,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
# FIXME: Relocate or remove the need for this stuff.
self.paused_text: Optional[ba.Actor] = None
- self.spaz_respawn_icons_right: Dict[int, RespawnIcon]
- session = _ba.getsession()
- if session is None:
- raise RuntimeError('No current session')
- self._session = weakref.ref(session)
+ self._session = weakref.ref(_ba.getsession())
# Preloaded data for actors, maps, etc; indexed by type.
self.preloads: Dict[Type, Any] = {}
@@ -193,12 +189,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self._delay_delete_teams: List[TeamType] = []
self._players_that_left: List[ReferenceType[PlayerType]] = []
self._teams_that_left: List[ReferenceType[TeamType]] = []
-
- # This gets set once another activity has begun transitioning in but
- # before this one is killed. The on_transition_out() method is also
- # called at this time. Make sure to not assign player inputs,
- # change music, or anything else with global implications once this
- # happens.
self._transitioning_out = False
# A handy place to put most actors; this list is pruned of dead
@@ -209,7 +199,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
self._last_prune_dead_actors_time = _ba.time()
self._prune_dead_actors_timer: Optional[ba.Timer] = None
- # This stuff gets filled in just before on_begin() is called.
self.teams = []
self.players = []
diff --git a/assets/src/ba_data/python/ba/_appdelegate.py b/assets/src/ba_data/python/ba/_appdelegate.py
index 148b5587..e1cf5431 100644
--- a/assets/src/ba_data/python/ba/_appdelegate.py
+++ b/assets/src/ba_data/python/ba/_appdelegate.py
@@ -36,14 +36,14 @@ class AppDelegate:
def create_default_game_settings_ui(
self, gameclass: Type[ba.GameActivity],
- sessionclass: Type[ba.Session], config: Optional[dict],
+ sessiontype: Type[ba.Session], settings: Optional[dict],
completion_call: Callable[[Optional[dict]], None]) -> None:
"""Launch a UI to configure the given game config.
It should manipulate the contents of config and call completion_call
when done.
"""
- del gameclass, sessionclass, config, completion_call # unused
+ del gameclass, sessiontype, settings, completion_call # Unused.
from ba import _error
_error.print_error(
"create_default_game_settings_ui needs to be overridden")
diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py
index 926d1cc9..8f57b9b8 100644
--- a/assets/src/ba_data/python/ba/_coopsession.py
+++ b/assets/src/ba_data/python/ba/_coopsession.py
@@ -312,7 +312,7 @@ class CoopSession(Session):
# Skip players that are still choosing a team.
if player.in_game:
- self.stats.register_player(player)
+ self.stats.register_sessionplayer(player)
self.stats.setactivity(next_game)
# Now flip the current activity.
diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py
index a6320fa8..c83e6ea6 100644
--- a/assets/src/ba_data/python/ba/_gameactivity.py
+++ b/assets/src/ba_data/python/ba/_gameactivity.py
@@ -54,7 +54,8 @@ class GameActivity(Activity[PlayerType, TeamType]):
"""
# pylint: disable=too-many-public-methods
- tips: List[Union[str, Dict[str, Any]]] = []
+ # Tips to be presented to the user at the start of the game.
+ tips: List[Union[str, ba.GameTip]] = []
# Default getname() will return this if not None.
name: Optional[str] = None
@@ -82,16 +83,16 @@ class GameActivity(Activity[PlayerType, TeamType]):
@classmethod
def create_settings_ui(
cls,
- sessionclass: Type[ba.Session],
+ sessiontype: Type[ba.Session],
settings: Optional[dict],
completion_call: Callable[[Optional[dict]], None],
) -> None:
"""Launch an in-game UI to configure settings for a game type.
- 'sessionclass' should be the ba.Session class the game will be used in.
+ 'sessiontype' should be the ba.Session class the game will be used in.
- 'config' should be an existing config dict (specifies 'edit' ui mode)
- or None (specifies 'add' ui mode).
+ 'settings' should be an existing settings dict (implies 'edit'
+ ui mode) or None (implies 'add' ui mode).
'completion_call' will be called with a filled-out settings dict on
success or None on cancel.
@@ -103,14 +104,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
"""
delegate = _ba.app.delegate
assert delegate is not None
- delegate.create_default_game_settings_ui(cls, sessionclass, settings,
+ delegate.create_default_game_settings_ui(cls, sessiontype, settings,
completion_call)
@classmethod
def getscoreconfig(cls) -> ba.ScoreConfig:
"""Return info about game scoring setup; can be overridden by games."""
- return cls.scoreconfig if cls.scoreconfig is not None else ScoreConfig(
- )
+ return (cls.scoreconfig
+ if cls.scoreconfig is not None else ScoreConfig())
@classmethod
def getname(cls) -> str:
@@ -170,62 +171,8 @@ class GameActivity(Activity[PlayerType, TeamType]):
@classmethod
def get_available_settings(
cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
- """
- Called by the default ba.GameActivity.create_settings_ui()
- implementation; should return a dict of config options to be presented
- to the user for the given ba.Session type.
-
- The format for settings is a list of 2-member tuples consisting
- of a name and a dict of options.
-
- Available Setting Options:
-
- 'default': This determines the default value as well as the
- type (int, float, or bool)
-
- 'min_value': Minimum value for int/float settings.
-
- 'max_value': Maximum value for int/float settings.
-
- 'choices': A list of name/value pairs the user can choose from by name.
-
- 'increment': Value increment for int/float settings.
-
- # example get_available_settings() for a capture-the-flag game:
- @classmethod
- def get_available_settings(cls, sessiontype):
- return [("Score to Win", {
- 'default': 3,
- 'min_value': 1
- }),
- ("Flag Touch Return Time", {
- 'default': 0,
- 'min_value': 0,
- 'increment': 1
- }),
- ("Flag Idle Return Time", {
- 'default': 30,
- 'min_value': 5,
- 'increment': 5
- }),
- ("Time Limit", {
- 'default': 0,
- 'choices': [
- ('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
- ('5 Minutes', 300), ('10 Minutes', 600),
- ('20 Minutes', 1200)
- ]
- }),
- ("Respawn Times", {
- 'default': 1.0,
- 'choices': [
- ('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
- ('Long', 2.0), ('Longer', 4.0)
- ]
- }),
- ("Epic Mode", {
- 'default': False
- })]
+ """Return a list of settings relevant to this game type when
+ running under the provided session type.
"""
del sessiontype # Unused arg.
return [] if cls.available_settings is None else cls.available_settings
@@ -403,7 +350,6 @@ class GameActivity(Activity[PlayerType, TeamType]):
return ''
def on_transition_in(self) -> None:
-
super().on_transition_in()
# Make our map.
@@ -575,8 +521,9 @@ class GameActivity(Activity[PlayerType, TeamType]):
victim_player=player,
importance=importance,
showpoints=self.show_kill_points)
- return None
- return super().handlemessage(msg)
+ else:
+ return super().handlemessage(msg)
+ return None
def _show_scoreboard_info(self) -> None:
"""Create the game info display.
@@ -599,7 +546,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
else:
sb_desc_l = sb_desc_in
if not isinstance(sb_desc_l[0], str):
- raise TypeError('Invalid format for instance description')
+ raise TypeError('Invalid format for instance description.')
is_empty = (sb_desc_l[0] == '')
subs = []
@@ -608,9 +555,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
translation = Lstr(translate=('gameDescriptions', sb_desc_l[0]),
subs=subs)
sb_desc = translation
-
vrmode = _ba.app.vr_mode
-
yval = -34 if is_empty else -20
yval -= 16
sbpos = ((15, yval) if isinstance(self.session, FreeForAllSession) else
@@ -727,7 +672,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
def _show_tip(self) -> None:
# pylint: disable=too-many-locals
- from ba._gameutils import animate
+ from ba._gameutils import animate, GameTip
from ba._enums import SpecialChar
# If there's any tips left on the list, display one.
@@ -735,14 +680,12 @@ class GameActivity(Activity[PlayerType, TeamType]):
tip = self.tips.pop(random.randrange(len(self.tips)))
tip_title = Lstr(value='${A}:',
subs=[('${A}', Lstr(resource='tipText'))])
- icon = None
- sound = None
- if isinstance(tip, dict):
- if 'icon' in tip:
- icon = tip['icon']
- if 'sound' in tip:
- sound = tip['sound']
- tip = tip['tip']
+ icon: Optional[ba.Texture] = None
+ sound: Optional[ba.Sound] = None
+ if isinstance(tip, GameTip):
+ icon = tip.icon
+ sound = tip.sound
+ tip = tip.text
assert isinstance(tip, str)
# Do a few substitutions.
@@ -844,12 +787,11 @@ class GameActivity(Activity[PlayerType, TeamType]):
super().end(results, delay, force)
def end_game(self) -> None:
- """
- Tells the game to wrap itself up and call ba.Activity.end()
- immediately. This method should be overridden by subclasses.
+ """Tell the game to wrap up and call ba.Activity.end() immediately.
- A game should always be prepared to end and deliver results, even if
- there is no 'winner' yet; this way things like the standard time-limit
+ This method should be overridden by subclasses. A game should always
+ be prepared to end and deliver results, even if there is no 'winner'
+ yet; this way things like the standard time-limit
(ba.GameActivity.setup_standard_time_limit()) will work with the game.
"""
print('WARNING: default end_game() implementation called;'
diff --git a/assets/src/ba_data/python/ba/_gameresults.py b/assets/src/ba_data/python/ba/_gameresults.py
index 8b850f80..1ffa78a8 100644
--- a/assets/src/ba_data/python/ba/_gameresults.py
+++ b/assets/src/ba_data/python/ba/_gameresults.py
@@ -84,8 +84,7 @@ class GameResults:
sessionteam = team.sessionteam
self._scores[sessionteam.id] = (weakref.ref(sessionteam), score)
- def get_team_score(self,
- sessionteam: Union[ba.SessionTeam]) -> Optional[int]:
+ def get_team_score(self, sessionteam: ba.SessionTeam) -> Optional[int]:
"""Return the score for a given ba.SessionTeam."""
for score in list(self._scores.values()):
if score[0]() is sessionteam:
@@ -111,7 +110,7 @@ class GameResults:
"""Return whether there is a score for a given team."""
return any(s[0]() is sessionteam for s in self._scores.values())
- def get_team_score_str(self, team: ba.Team) -> ba.Lstr:
+ def get_team_score_str(self, sessionteam: ba.SessionTeam) -> ba.Lstr:
"""Return the score for the given ba.Team as an Lstr.
(properly formatted for the score type.)
@@ -123,7 +122,7 @@ class GameResults:
if not self._game_set:
raise RuntimeError("Can't get team-score-str until game is set.")
for score in list(self._scores.values()):
- if score[0]() is team.sessionteam:
+ if score[0]() is sessionteam:
if score[1] is None:
return Lstr(value='-')
if self._scoretype is ScoreType.SECONDS:
diff --git a/assets/src/ba_data/python/ba/_gameutils.py b/assets/src/ba_data/python/ba/_gameutils.py
index 8c48ce76..7f4588fb 100644
--- a/assets/src/ba_data/python/ba/_gameutils.py
+++ b/assets/src/ba_data/python/ba/_gameutils.py
@@ -22,6 +22,7 @@
from __future__ import annotations
+from dataclasses import dataclass
from typing import TYPE_CHECKING
import _ba
@@ -42,6 +43,17 @@ TROPHY_CHARS = {
}
+@dataclass
+class GameTip:
+ """Defines a tip presentable to the user at the start of a game.
+
+ Category: Gameplay Classes
+ """
+ text: str
+ icon: Optional[ba.Texture] = None
+ sound: Optional[ba.Sound] = None
+
+
def get_trophy_string(trophy_id: str) -> str:
"""Given a trophy id, returns a string to visualize it."""
if trophy_id in TROPHY_CHARS:
diff --git a/assets/src/ba_data/python/ba/_general.py b/assets/src/ba_data/python/ba/_general.py
index bb1f3bb5..43eff04b 100644
--- a/assets/src/ba_data/python/ba/_general.py
+++ b/assets/src/ba_data/python/ba/_general.py
@@ -123,7 +123,7 @@ def json_prep(data: Any) -> Any:
def utf8_all(data: Any) -> Any:
- """Convert any unicode data in provided sequence(s)to utf8 bytes."""
+ """Convert any unicode data in provided sequence(s) to utf8 bytes."""
if isinstance(data, dict):
return dict((utf8_all(key), utf8_all(value))
for key, value in list(data.items()))
diff --git a/assets/src/ba_data/python/ba/_multiteamsession.py b/assets/src/ba_data/python/ba/_multiteamsession.py
index 90bd4b1f..9daedc9d 100644
--- a/assets/src/ba_data/python/ba/_multiteamsession.py
+++ b/assets/src/ba_data/python/ba/_multiteamsession.py
@@ -224,7 +224,7 @@ class MultiTeamSession(Session):
except NotFoundError:
has_team = False
if has_team:
- self.stats.register_player(player)
+ self.stats.register_sessionplayer(player)
self.stats.setactivity(next_game)
# Now flip the current activity.
diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py
index a07bc61d..6dbd6969 100644
--- a/assets/src/ba_data/python/ba/_player.py
+++ b/assets/src/ba_data/python/ba/_player.py
@@ -26,7 +26,8 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING, TypeVar, Generic, cast
import _ba
-from ba._error import SessionPlayerNotFoundError, print_exception
+from ba._error import (SessionPlayerNotFoundError, print_exception,
+ ActorNotFoundError)
from ba._messages import DeathType, DieMessage
if TYPE_CHECKING:
@@ -219,11 +220,12 @@ class Player(Generic[TeamType]):
def position(self) -> ba.Vec3:
"""The position of the player, as defined by its current ba.Actor.
- This value is undefined when the player has no Actor.
+ If the player currently has no actor, raises a ba.ActorNotFoundError.
"""
assert self._postinited
assert not self._expired
- assert self.actor is not None
+ if self.actor is None:
+ raise ActorNotFoundError
return _ba.Vec3(self.node.position)
def exists(self) -> bool:
diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py
index 2025cc42..72c56101 100644
--- a/assets/src/ba_data/python/ba/_session.py
+++ b/assets/src/ba_data/python/ba/_session.py
@@ -84,6 +84,10 @@ class Session:
Whether players should be allowed to join in the middle of
activities.
+ customdata
+ A shared dictionary for objects to use as storage on this session.
+ Ensure that keys here are unique to avoid collisions.
+
"""
use_teams: bool = False
use_team_colors: bool = True
@@ -95,6 +99,7 @@ class Session:
max_players: int
min_players: int
players: List[ba.SessionPlayer]
+ customdata: dict
teams: List[ba.SessionTeam]
def __init__(self,
@@ -166,6 +171,7 @@ class Session:
self.min_players = min_players
self.max_players = max_players
+ self.customdata = {}
self._in_set_activity = False
self._next_team_id = 0
self._activity_retained: Optional[ba.Activity] = None
@@ -695,7 +701,7 @@ class Session:
color=chooser.get_color(),
highlight=chooser.get_highlight())
- self.stats.register_player(sessionplayer)
+ self.stats.register_sessionplayer(sessionplayer)
if pass_to_activity:
activity.add_player(sessionplayer)
return sessionplayer
diff --git a/assets/src/ba_data/python/ba/_stats.py b/assets/src/ba_data/python/ba/_stats.py
index b92dc374..317e8c83 100644
--- a/assets/src/ba_data/python/ba/_stats.py
+++ b/assets/src/ba_data/python/ba/_stats.py
@@ -28,7 +28,7 @@ from dataclasses import dataclass
import _ba
from ba._error import (print_exception, print_error, SessionTeamNotFoundError,
- SessionPlayerNotFoundError)
+ SessionPlayerNotFoundError, NotFoundError)
if TYPE_CHECKING:
import ba
@@ -61,8 +61,8 @@ class PlayerRecord:
"""
character: str
- def __init__(self, name: str, name_full: str, player: ba.SessionPlayer,
- stats: ba.Stats):
+ def __init__(self, name: str, name_full: str,
+ sessionplayer: ba.SessionPlayer, stats: ba.Stats):
self.name = name
self.name_full = name_full
self.score = 0
@@ -75,10 +75,10 @@ class PlayerRecord:
self._multi_kill_count = 0
self._stats = weakref.ref(stats)
self._last_sessionplayer: Optional[ba.SessionPlayer] = None
- self._player: Optional[ba.SessionPlayer] = None
- self._team: Optional[ReferenceType[ba.SessionTeam]] = None
+ self._sessionplayer: Optional[ba.SessionPlayer] = None
+ self._sessionteam: Optional[ReferenceType[ba.SessionTeam]] = None
self.streak = 0
- self.associate_with_player(player)
+ self.associate_with_sessionplayer(sessionplayer)
@property
def team(self) -> ba.SessionTeam:
@@ -87,8 +87,8 @@ class PlayerRecord:
This can still return a valid result even if the player is gone.
Raises a ba.SessionTeamNotFoundError if the team no longer exists.
"""
- assert self._team is not None
- team = self._team()
+ assert self._sessionteam is not None
+ team = self._sessionteam()
if team is None:
raise SessionTeamNotFoundError()
return team
@@ -100,9 +100,9 @@ class PlayerRecord:
Raises a ba.SessionPlayerNotFoundError if the player
no longer exists.
"""
- if not self._player:
+ if not self._sessionplayer:
raise SessionPlayerNotFoundError()
- return self._player
+ return self._sessionplayer
def getname(self, full: bool = False) -> str:
"""Return the player entry's name."""
@@ -127,19 +127,20 @@ class PlayerRecord:
return stats.getactivity()
return None
- def associate_with_player(self, sessionplayer: ba.SessionPlayer) -> None:
+ def associate_with_sessionplayer(self,
+ sessionplayer: ba.SessionPlayer) -> None:
"""Associate this entry with a ba.SessionPlayer."""
- self._team = weakref.ref(sessionplayer.sessionteam)
+ self._sessionteam = weakref.ref(sessionplayer.sessionteam)
self.character = sessionplayer.character
self._last_sessionplayer = sessionplayer
- self._player = sessionplayer
+ self._sessionplayer = sessionplayer
self.streak = 0
def _end_multi_kill(self) -> None:
self._multi_kill_timer = None
self._multi_kill_count = 0
- def get_last_player(self) -> ba.SessionPlayer:
+ def get_last_sessionplayer(self) -> ba.SessionPlayer:
"""Return the last ba.Player we were associated with."""
assert self._last_sessionplayer is not None
return self._last_sessionplayer
@@ -204,18 +205,20 @@ class PlayerRecord:
# Only award this if they're still alive and we can get
# a current position for them.
- our_pos: Optional[Sequence[float]] = None
- if self._player is not None:
- if self._player.activityplayer is not None:
- if self._player.activityplayer.node:
- our_pos = self._player.activityplayer.node.position
+ our_pos: Optional[ba.Vec3] = None
+ if self._sessionplayer is not None:
+ if self._sessionplayer.activityplayer is not None:
+ try:
+ our_pos = self._sessionplayer.activityplayer.position
+ except NotFoundError:
+ pass
if our_pos is None:
return
# Jitter position a bit since these often come in clusters.
- our_pos = (our_pos[0] + (random.random() - 0.5) * 2.0,
- our_pos[1] + (random.random() - 0.5) * 2.0,
- our_pos[2] + (random.random() - 0.5) * 2.0)
+ our_pos = _ba.Vec3(our_pos[0] + (random.random() - 0.5) * 2.0,
+ our_pos[1] + (random.random() - 0.5) * 2.0,
+ our_pos[2] + (random.random() - 0.5) * 2.0)
activity = self.getactivity()
if activity is not None:
PopupText(Lstr(
@@ -305,14 +308,14 @@ class Stats:
s_player.accum_killed_count = 0
s_player.streak = 0
- def register_player(self, player: ba.SessionPlayer) -> None:
- """Register a player with this score-set."""
+ def register_sessionplayer(self, player: ba.SessionPlayer) -> None:
+ """Register a ba.SessionPlayer with this score-set."""
assert player.exists() # Invalid refs should never be passed to funcs.
name = player.getname()
if name in self._player_records:
# If the player already exists, update his character and such as
# it may have changed.
- self._player_records[name].associate_with_player(player)
+ self._player_records[name].associate_with_sessionplayer(player)
else:
name_full = player.getname(full=True)
self._player_records[name] = PlayerRecord(name, name_full, player,
@@ -325,7 +328,7 @@ class Stats:
# Go through our player records and return ones whose player id still
# corresponds to a player with that name.
for record_id, record in self._player_records.items():
- lastplayer = record.get_last_player()
+ lastplayer = record.get_last_sessionplayer()
if lastplayer and lastplayer.getname() == record_id:
records[record_id] = record
return records
diff --git a/assets/src/ba_data/python/bastd/activity/multiteamscore.py b/assets/src/ba_data/python/bastd/activity/multiteamscore.py
index c95266d1..68a4c3d8 100644
--- a/assets/src/ba_data/python/bastd/activity/multiteamscore.py
+++ b/assets/src/ba_data/python/bastd/activity/multiteamscore.py
@@ -97,7 +97,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
if is_free_for_all and results is not None:
assert isinstance(results, ba.GameResults)
assert p_rec.team.gameteam is not None
- val = results.get_team_score_str(p_rec.team.gameteam)
+ val = results.get_team_score_str(p_rec.team)
assert val is not None
return val
return str(p_rec.accumscore)
diff --git a/assets/src/ba_data/python/bastd/appdelegate.py b/assets/src/ba_data/python/bastd/appdelegate.py
index c3e19ee0..351a1984 100644
--- a/assets/src/ba_data/python/bastd/appdelegate.py
+++ b/assets/src/ba_data/python/bastd/appdelegate.py
@@ -34,7 +34,7 @@ class AppDelegate(ba.AppDelegate):
def create_default_game_settings_ui(
self, gameclass: Type[ba.GameActivity],
- sessionclass: Type[ba.Session], config: Optional[dict],
+ sessiontype: Type[ba.Session], settings: Optional[dict],
completion_call: Callable[[Optional[dict]], Any]) -> None:
"""(internal)"""
@@ -42,6 +42,6 @@ class AppDelegate(ba.AppDelegate):
from bastd.ui.playlist.editgame import PlaylistEditGameWindow
prev_window = ba.app.main_menu_window
ba.app.main_menu_window = (PlaylistEditGameWindow(
- gameclass, sessionclass, config,
+ gameclass, sessiontype, settings,
completion_call=completion_call).get_root_widget())
ba.containerwidget(edit=prev_window, transition='out_left')
diff --git a/assets/src/ba_data/python/bastd/game/onslaught.py b/assets/src/ba_data/python/bastd/game/onslaught.py
index 607ce182..cbb03d17 100644
--- a/assets/src/ba_data/python/bastd/game/onslaught.py
+++ b/assets/src/ba_data/python/bastd/game/onslaught.py
@@ -141,7 +141,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
name = 'Onslaught'
description = 'Defeat all enemies.'
- tips: List[Union[str, Dict[str, Any]]] = [
+ tips: List[Union[str, ba.GameTip]] = [
'Hold any button to run.'
' (Trigger buttons work well if you have them)',
'Try tricking enemies into killing eachother or running off cliffs.',
@@ -213,43 +213,45 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def on_transition_in(self) -> None:
super().on_transition_in()
- session = ba.getsession()
+ customdata = ba.getsession().customdata
# Show special landmine tip on rookie preset.
if self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}:
# Show once per session only (then we revert to regular tips).
- if not getattr(session, '_g_showed_onslaught_landmine_tip', False):
- setattr(session, '_g_showed_onslaught_landmine_tip', True)
- self.tips = [{
- 'tip': 'Land-mines are a good way'
- ' to stop speedy enemies.',
- 'icon': ba.gettexture('powerupLandMines'),
- 'sound': ba.getsound('ding')
- }]
+ if not customdata.get('_showed_onslaught_landmine_tip', False):
+ customdata['_showed_onslaught_landmine_tip'] = True
+ self.tips = [
+ ba.GameTip(
+ 'Land-mines are a good way to stop speedy enemies.',
+ icon=ba.gettexture('powerupLandMines'),
+ sound=ba.getsound('ding'))
+ ]
# Show special tnt tip on pro preset.
if self._preset in {Preset.PRO, Preset.PRO_EASY}:
# Show once per session only (then we revert to regular tips).
- if not getattr(session, '_g_showed_onslaught_tnt_tip', False):
- setattr(session, '_g_showed_onslaught_tnt_tip', True)
- self.tips = [{
- 'tip': 'Take out a group of enemies by\n'
- 'setting off a bomb near a TNT box.',
- 'icon': ba.gettexture('tnt'),
- 'sound': ba.getsound('ding')
- }]
+ if not customdata.get('_showed_onslaught_tnt_tip', False):
+ customdata['_showed_onslaught_tnt_tip'] = True
+ self.tips = [
+ ba.GameTip(
+ 'Take out a group of enemies by\n'
+ 'setting off a bomb near a TNT box.',
+ icon=ba.gettexture('tnt'),
+ sound=ba.getsound('ding'))
+ ]
# Show special curse tip on uber preset.
if self._preset in {Preset.UBER, Preset.UBER_EASY}:
# Show once per session only (then we revert to regular tips).
- if not getattr(session, '_g_showed_onslaught_curse_tip', False):
- setattr(session, '_g_showed_onslaught_curse_tip', True)
- self.tips = [{
- 'tip': 'Curse boxes turn you into a ticking time bomb.\n'
- 'The only cure is to quickly grab a health-pack.',
- 'icon': ba.gettexture('powerupCurse'),
- 'sound': ba.getsound('ding')
- }]
+ if not customdata.get('_showed_onslaught_curse_tip', False):
+ customdata['_showed_onslaught_curse_tip'] = True
+ self.tips = [
+ ba.GameTip(
+ 'Curse boxes turn you into a ticking time bomb.\n'
+ 'The only cure is to quickly grab a health-pack.',
+ icon=ba.gettexture('powerupCurse'),
+ sound=ba.getsound('ding'))
+ ]
self._spawn_info_text = ba.NodeActor(
ba.newnode('text',
diff --git a/docs/ba_module.md b/docs/ba_module.md
index c3fadfde..662e9975 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -1,5 +1,5 @@
-last updated on 2020-06-02 for Ballistica version 1.5.0 build 20043
+last updated on 2020-06-03 for Ballistica version 1.5.0 build 20044
This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!
@@ -21,6 +21,7 @@
ba.Chooser
ba.GameResults
+ ba.GameTip
ba.InputDevice
ba.Level
ba.Lobby
@@ -1100,7 +1101,7 @@ manually.
Methods:
-
-
create_default_game_settings_ui(self, gameclass: Type[ba.GameActivity], sessionclass: Type[ba.Session], config: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None
+create_default_game_settings_ui(self, gameclass: Type[ba.GameActivity], sessiontype: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None
Launch a UI to configure the given game config.
@@ -1733,7 +1734,7 @@ and it should begin its actual game logic.
high score lists.
Attributes Inherited:
-
+
Attributes Defined Here:
@@ -2091,7 +2092,7 @@ its time with lingering corpses, sound effects, etc.
Attributes Inherited:
-
+
Attributes Defined Here:
-
@@ -2156,7 +2157,7 @@ its time with lingering corpses, sound effects, etc.
ba.Vec3
The position of the player, as defined by its current ba.Actor.
- This value is undefined when the player has no Actor.
+ If the player currently has no actor, raises a ba.ActorNotFoundError.
-
@@ -2272,7 +2273,7 @@ its time with lingering corpses, sound effects, etc.
Attributes Inherited:
-
+
Attributes Defined Here:
-
@@ -2407,14 +2408,14 @@ and calls either end_game or continue_game depending on the result
-
<class method>
-create_settings_ui(sessionclass: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None
+create_settings_ui(sessiontype: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None
Launch an in-game UI to configure settings for a game type.
-'sessionclass' should be the ba.Session class the game will be used in.
+'sessiontype' should be the ba.Session class the game will be used in.
-'config' should be an existing config dict (specifies 'edit' ui mode)
- or None (specifies 'add' ui mode).
+'settings' should be an existing settings dict (implies 'edit'
+ ui mode) or None (implies 'add' ui mode).
'completion_call' will be called with a filled-out settings dict on
success or None on cancel.
@@ -2439,11 +2440,11 @@ will replace the old.
-
end_game(self) -> None
-Tells the game to wrap itself up and call ba.Activity.end()
-immediately. This method should be overridden by subclasses.
+Tell the game to wrap up and call ba.Activity.end() immediately.
-A game should always be prepared to end and deliver results, even if
-there is no 'winner' yet; this way things like the standard time-limit
+
This method should be overridden by subclasses. A game should always
+be prepared to end and deliver results, even if there is no 'winner'
+yet; this way things like the standard time-limit
(ba.GameActivity.setup_standard_time_limit()) will work with the game.
@@ -2451,61 +2452,8 @@ there is no 'winner' yet; this way things like the standard time-limit
<class method>
get_available_settings(sessiontype: Type[ba.Session]) -> List[ba.Setting]
-Called by the default ba.GameActivity.create_settings_ui()
-implementation; should return a dict of config options to be presented
-to the user for the given ba.Session type.
-
-The format for settings is a list of 2-member tuples consisting
-of a name and a dict of options.
-
-Available Setting Options:
-
-'default': This determines the default value as well as the
- type (int, float, or bool)
-
-'min_value': Minimum value for int/float settings.
-
-'max_value': Maximum value for int/float settings.
-
-'choices': A list of name/value pairs the user can choose from by name.
-
-'increment': Value increment for int/float settings.
-
-# example get_available_settings() for a capture-the-flag game:
-@classmethod
-def get_available_settings(cls, sessiontype):
- return [("Score to Win", {
- 'default': 3,
- 'min_value': 1
- }),
- ("Flag Touch Return Time", {
- 'default': 0,
- 'min_value': 0,
- 'increment': 1
- }),
- ("Flag Idle Return Time", {
- 'default': 30,
- 'min_value': 5,
- 'increment': 5
- }),
- ("Time Limit", {
- 'default': 0,
- 'choices': [
- ('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
- ('5 Minutes', 300), ('10 Minutes', 600),
- ('20 Minutes', 1200)
- ]
- }),
- ("Respawn Times", {
- 'default': 1.0,
- 'choices': [
- ('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
- ('Long', 2.0), ('Longer', 4.0)
- ]
- }),
- ("Epic Mode", {
- 'default': False
- })]
+Return a list of settings relevant to this game type when
+running under the provided session type.
-
@@ -2821,13 +2769,13 @@ Results for a completed game.
-
-
get_team_score(self, sessionteam: Union[ba.SessionTeam]) -> Optional[int]
+get_team_score(self, sessionteam: ba.SessionTeam) -> Optional[int]
Return the score for a given ba.SessionTeam.
-
-
get_team_score_str(self, team: ba.Team) -> ba.Lstr
+get_team_score_str(self, sessionteam: ba.SessionTeam) -> ba.Lstr
Return the score for the given ba.Team as an Lstr.
@@ -2854,6 +2802,22 @@ Results for a completed game.
This can be a number or None.
(see the none_is_winner arg in the constructor)
+
+
+
+<top level class>
+
+Defines a tip presentable to the user at the start of a game.
+
+Category: Gameplay Classes
+
+
+Methods:
+
+-
+
ba.GameTip(text: str, icon: Optional[ba.Texture] = None, sound: Optional[ba.Sound] = None)
+
@@ -3728,7 +3692,7 @@ Use ba.getmodel() to instantiate one.
Attributes Inherited:
-
+
Attributes Defined Here:
-
@@ -4248,7 +4212,7 @@ even if myactor is set to None.
ba.Vec3
The position of the player, as defined by its current ba.Actor.
- This value is undefined when the player has no Actor.
+ If the player currently has no actor, raises a ba.ActorNotFoundError.
-
@@ -4446,14 +4410,14 @@ the type-checker properly identifies the returned value as one.
Methods:
-
+
-
-
ba.PlayerRecord(name: str, name_full: str, player: ba.SessionPlayer, stats: ba.Stats)
+ba.PlayerRecord(name: str, name_full: str, sessionplayer: ba.SessionPlayer, stats: ba.Stats)
--
-
associate_with_player(self, sessionplayer: ba.SessionPlayer) -> None
+ -
+
associate_with_sessionplayer(self, sessionplayer: ba.SessionPlayer) -> None
Associate this entry with a ba.SessionPlayer.
@@ -4470,8 +4434,8 @@ the type-checker properly identifies the returned value as one.
Get the icon for this instance's player.
--
-
get_last_player(self) -> ba.SessionPlayer
+ -
+
get_last_sessionplayer(self) -> ba.SessionPlayer
Return the last ba.Player we were associated with.
@@ -4700,13 +4664,19 @@ Pass 0 or a negative number for no ban time.
maintaining state between them (players, teams, score tallies, etc).
Attributes:
-
+
-
bool
Whether players should be allowed to join in the middle of
activities.
+
+-
+
dict
+A shared dictionary for objects to use as storage on this session.
+Ensure that keys here are unique to avoid collisions.
+
-
ba.Lobby
@@ -5290,7 +5260,7 @@ of the session.
Methods:
-
+
-
ba.Stats()
@@ -5324,10 +5294,10 @@ of the session.
Should be called when a player is killed.
--
-
register_player(self, player: ba.SessionPlayer) -> None
+ -
+
register_sessionplayer(self, player: ba.SessionPlayer) -> None
-Register a player with this score-set.
+Register a ba.SessionPlayer with this score-set.
-