From 0f9fe41542d4be55ecd3e0552db342a18519d309 Mon Sep 17 00:00:00 2001
From: Eric Froemling
Date: Fri, 16 Oct 2020 10:41:35 -0700
Subject: [PATCH] Consolidated achievement functionality into
AchievementSubsystem obj at ba.app.ach
---
.efrocachemap | 40 +-
assets/src/ba_data/python/ba/__init__.py | 2 +-
assets/src/ba_data/python/ba/_account.py | 2 +-
assets/src/ba_data/python/ba/_achievement.py | 686 +++++++++---------
assets/src/ba_data/python/ba/_app.py | 19 +-
assets/src/ba_data/python/ba/_coopgame.py | 9 +-
assets/src/ba_data/python/ba/_hooks.py | 6 +-
assets/src/ba_data/python/ba/_music.py | 2 +-
assets/src/ba_data/python/ba/_teamgame.py | 7 +-
assets/src/ba_data/python/ba/internal.py | 3 -
.../ba_data/python/bastd/activity/coopjoin.py | 4 +-
.../python/bastd/activity/coopscore.py | 8 +-
assets/src/ba_data/python/bastd/mainmenu.py | 3 +-
.../python/bastd/ui/account/settings.py | 4 +-
.../ba_data/python/bastd/ui/account/viewer.py | 2 +-
.../ba_data/python/bastd/ui/achievements.py | 2 +-
.../python/bastd/ui/coop/gamebutton.py | 4 +-
.../python/bastd/ui/league/rankwindow.py | 2 +-
docs/ba_module.md | 42 +-
src/ballistica/ballistica.cc | 2 +-
20 files changed, 441 insertions(+), 408 deletions(-)
diff --git a/.efrocachemap b/.efrocachemap
index 1f3eee67..686b8d08 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -3932,24 +3932,24 @@
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
- "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/68/70/ecdec08c0236f5bebbf298c9f4cd",
- "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/85/7f/53fb1e3f1414b4865315d815b17a",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9e/ae/586229f233660b6b49ca655b7c1b",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e5/a5/c6b90e3629a8041a68827aafec3f",
- "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d3/66/82b2cab9b1438c30cfd33eefba5e",
- "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/52/37/253765404b79740cd4a7ac5fe325",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c7/69/ecdbcee9df40225475391beb4cb2",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/34/51/cb528ea6eb5ac853794922a0cc34",
- "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d4/b8/d3a690970ca379d805432bde8533",
- "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/4d/16/b9014f5d983481731b54f7045dd9",
- "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/1e/11/d3c478923937f376eecb25561ae9",
- "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/d6/b4/b7854676ec826e42aa3ecb394b49",
- "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/76/bf185c1ea65f3c25cfa63666511b",
- "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/49/30/5acd0d56b736a3729e7689cd4e18",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f7/e8/197874ac6d756c341628ea4bb518",
- "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/50/6f/140a3d77288e2355605c9d0c942a",
- "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1c/ca/62d29425e0ad0b17c145ecbc97fe",
- "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c0/fa/52a38fc153714bff3bcda077ed75",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/75/5b/e84edb24f5df313bb6ebafa91b19",
- "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/2e/af28c0026c0379e5441e789e9721"
+ "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/4c/74/73bd143107ea1bc1cc150cf1d9a0",
+ "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d8/1f/b71688ce17abe2f1750d3b98c1a3",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7b/f5/0fb038ef5e8c9cb22f8487213622",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a7/22/7582b2dc2eb28e8c9b05c7860c83",
+ "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/01/6a/6787038c5a2fe07be6dd08d6df5f",
+ "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/eb/c3/12a2c00732a90af63ae853b76a3b",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/63/e7/795fd31307cda51524ea0cfc4055",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/92/48/1bfcef97e05d641771a934e312f3",
+ "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/cf/25/4ef41f30fb1380505759a16a7302",
+ "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/78/eb/9ee7487ac9d633020c02bcdd475b",
+ "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/4e/59/bd1e3f68c9ccf0180187dbf60ce1",
+ "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/3c/f2/e0eb7217dba038dd146421b0bf5e",
+ "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/66/0f/34874cece602328e1a2b27efbe09",
+ "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ee/58/ccc75a3b2679f01a9e9b9f693ab0",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2a/82/a34db5a370d695fb52891b0a7c54",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1b/c4/0b7f73a301f24177650efb8cfa69",
+ "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e6/55/d4819d9a2e5dcd48c2b2641c57df",
+ "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/21/3f/8c9d1590314cd5172e944f64e41c",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/91/72/b037555786dfb8a5ebc36467e60f",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e3/e1/c9e61438f6458b9ce4ac4cfe66ed"
}
\ No newline at end of file
diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py
index d141478e..642f4643 100644
--- a/assets/src/ba_data/python/ba/__init__.py
+++ b/assets/src/ba_data/python/ba/__init__.py
@@ -52,7 +52,7 @@ from ba._stats import PlayerScoredMessage, PlayerRecord, Stats
from ba._team import SessionTeam, Team, EmptyTeam
from ba._teamgame import TeamGameActivity
from ba._dualteamsession import DualTeamSession
-from ba._achievement import Achievement
+from ba._achievement import Achievement, AchievementSubsystem
from ba._appconfig import AppConfig
from ba._appdelegate import AppDelegate
from ba._apputils import is_browser_likely_available
diff --git a/assets/src/ba_data/python/ba/_account.py b/assets/src/ba_data/python/ba/_account.py
index 5fee72c0..312770c2 100644
--- a/assets/src/ba_data/python/ba/_account.py
+++ b/assets/src/ba_data/python/ba/_account.py
@@ -47,7 +47,7 @@ def get_league_rank_points(data: Optional[Dict[str, Any]],
total_ach_value = data['at']
else:
total_ach_value = 0
- for ach in _ba.app.achievements:
+ for ach in _ba.app.ach.achievements:
if ach.complete:
total_ach_value += ach.power_ranking_value
diff --git a/assets/src/ba_data/python/ba/_achievement.py b/assets/src/ba_data/python/ba/_achievement.py
index 039fbe81..d8019e97 100644
--- a/assets/src/ba_data/python/ba/_achievement.py
+++ b/assets/src/ba_data/python/ba/_achievement.py
@@ -6,9 +6,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
+from ba._error import print_exception
if TYPE_CHECKING:
- from typing import Any, Sequence, List, Dict, Union, Optional
+ from typing import Any, Sequence, List, Dict, Union, Optional, Tuple, Set
import ba
# This could use some cleanup.
@@ -61,69 +62,346 @@ ACH_LEVEL_NAMES = {
}
-def award_local_achievement(achname: str) -> None:
- """For non-game-based achievements such as controller-connection ones."""
- try:
- ach = get_achievement(achname)
- if not ach.complete:
+class AchievementSubsystem:
+ """Subsystem for achievement handling.
- # Report new achievements to the game-service.
- _ba.report_achievement(achname)
+ Category: App Classes
- # And to our account.
- _ba.add_transaction({'type': 'ACHIEVEMENT', 'name': achname})
-
- # Now attempt to show a banner.
- display_achievement_banner(achname)
-
- except Exception:
- from ba import _error
- _error.print_exception()
-
-
-def display_achievement_banner(achname: str) -> None:
- """Display a completion banner for an achievement.
-
- Used for server-driven achievements.
+ Access the single shared instance of this class at 'ba.app.ach'.
"""
- try:
- # FIXME: Need to get these using the UI context or some other
- # purely local context somehow instead of trying to inject these
- # into whatever activity happens to be active
- # (since that won't work while in client mode).
- activity = _ba.get_foreground_host_activity()
- if activity is not None:
- with _ba.Context(activity):
- get_achievement(achname).announce_completion()
- except Exception:
- from ba import _error
- _error.print_exception('error showing server ach')
+ def __init__(self) -> None:
+ self.achievements: List[Achievement] = []
+ self.achievements_to_display: (List[Tuple[ba.Achievement, bool]]) = []
+ self.achievement_display_timer: Optional[_ba.Timer] = None
+ self.last_achievement_display_time: float = 0.0
+ self.achievement_completion_banner_slots: Set[int] = set()
+ self._init_achievements()
-# This gets called whenever game-center/game-circle/etc tells us which
-# achievements we currently have. We always defer to them, even if that
-# means we have to un-set an achievement we think we have
+ def _init_achievements(self) -> None:
+ """Fill in available achievements."""
+ achs = self.achievements
-def set_completed_achievements(achs: Sequence[str]) -> None:
- """Set the current state of completed achievements.
+ # 5
+ achs.append(
+ Achievement('In Control', 'achievementInControl', (1, 1, 1), '',
+ 5))
+ # 15
+ achs.append(
+ Achievement('Sharing is Caring', 'achievementSharingIsCaring',
+ (1, 1, 1), '', 15))
+ # 10
+ achs.append(
+ Achievement('Dual Wielding', 'achievementDualWielding', (1, 1, 1),
+ '', 10))
- All achievements not included here will be set incomplete.
- """
- cfg = _ba.app.config
- cfg['Achievements'] = {}
- for a_name in achs:
- get_achievement(a_name).set_complete(True)
- cfg.commit()
+ # 10
+ achs.append(
+ Achievement('Free Loader', 'achievementFreeLoader', (1, 1, 1), '',
+ 10))
+ # 20
+ achs.append(
+ Achievement('Team Player', 'achievementTeamPlayer', (1, 1, 1), '',
+ 20))
+ # 5
+ achs.append(
+ Achievement('Onslaught Training Victory', 'achievementOnslaught',
+ (1, 1, 1), 'Default:Onslaught Training', 5))
+ # 5
+ achs.append(
+ Achievement('Off You Go Then', 'achievementOffYouGo',
+ (1, 1.1, 1.3), 'Default:Onslaught Training', 5))
+ # 10
+ achs.append(
+ Achievement('Boxer',
+ 'achievementBoxer', (1, 0.6, 0.6),
+ 'Default:Onslaught Training',
+ 10,
+ hard_mode_only=True))
-def get_achievement(name: str) -> Achievement:
- """Return an Achievement by name."""
- achs = [a for a in _ba.app.achievements if a.name == name]
- assert len(achs) < 2
- if not achs:
- raise ValueError("Invalid achievement name: '" + name + "'")
- return achs[0]
+ # 10
+ achs.append(
+ Achievement('Rookie Onslaught Victory', 'achievementOnslaught',
+ (0.5, 1.4, 0.6), 'Default:Rookie Onslaught', 10))
+ # 10
+ achs.append(
+ Achievement('Mine Games', 'achievementMine', (1, 1, 1.4),
+ 'Default:Rookie Onslaught', 10))
+ # 15
+ achs.append(
+ Achievement('Flawless Victory',
+ 'achievementFlawlessVictory', (1, 1, 1),
+ 'Default:Rookie Onslaught',
+ 15,
+ hard_mode_only=True))
+
+ # 10
+ achs.append(
+ Achievement('Rookie Football Victory',
+ 'achievementFootballVictory', (1.0, 1, 0.6),
+ 'Default:Rookie Football', 10))
+ # 10
+ achs.append(
+ Achievement('Super Punch', 'achievementSuperPunch', (1, 1, 1.8),
+ 'Default:Rookie Football', 10))
+ # 15
+ achs.append(
+ Achievement('Rookie Football Shutout',
+ 'achievementFootballShutout', (1, 1, 1),
+ 'Default:Rookie Football',
+ 15,
+ hard_mode_only=True))
+
+ # 15
+ achs.append(
+ Achievement('Pro Onslaught Victory', 'achievementOnslaught',
+ (0.3, 1, 2.0), 'Default:Pro Onslaught', 15))
+ # 15
+ achs.append(
+ Achievement('Boom Goes the Dynamite', 'achievementTNT',
+ (1.4, 1.2, 0.8), 'Default:Pro Onslaught', 15))
+ # 20
+ achs.append(
+ Achievement('Pro Boxer',
+ 'achievementBoxer', (2, 2, 0),
+ 'Default:Pro Onslaught',
+ 20,
+ hard_mode_only=True))
+
+ # 15
+ achs.append(
+ Achievement('Pro Football Victory', 'achievementFootballVictory',
+ (1.3, 1.3, 2.0), 'Default:Pro Football', 15))
+ # 15
+ achs.append(
+ Achievement('Super Mega Punch', 'achievementSuperPunch',
+ (2, 1, 0.6), 'Default:Pro Football', 15))
+ # 20
+ achs.append(
+ Achievement('Pro Football Shutout',
+ 'achievementFootballShutout', (0.7, 0.7, 2.0),
+ 'Default:Pro Football',
+ 20,
+ hard_mode_only=True))
+
+ # 15
+ achs.append(
+ Achievement('Pro Runaround Victory', 'achievementRunaround',
+ (1, 1, 1), 'Default:Pro Runaround', 15))
+ # 20
+ achs.append(
+ Achievement('Precision Bombing',
+ 'achievementCrossHair', (1, 1, 1.3),
+ 'Default:Pro Runaround',
+ 20,
+ hard_mode_only=True))
+ # 25
+ achs.append(
+ Achievement('The Wall',
+ 'achievementWall', (1, 0.7, 0.7),
+ 'Default:Pro Runaround',
+ 25,
+ hard_mode_only=True))
+
+ # 30
+ achs.append(
+ Achievement('Uber Onslaught Victory', 'achievementOnslaught',
+ (2, 2, 1), 'Default:Uber Onslaught', 30))
+ # 30
+ achs.append(
+ Achievement('Gold Miner',
+ 'achievementMine', (2, 1.6, 0.2),
+ 'Default:Uber Onslaught',
+ 30,
+ hard_mode_only=True))
+ # 30
+ achs.append(
+ Achievement('TNT Terror',
+ 'achievementTNT', (2, 1.8, 0.3),
+ 'Default:Uber Onslaught',
+ 30,
+ hard_mode_only=True))
+
+ # 30
+ achs.append(
+ Achievement('Uber Football Victory', 'achievementFootballVictory',
+ (1.8, 1.4, 0.3), 'Default:Uber Football', 30))
+ # 30
+ achs.append(
+ Achievement('Got the Moves',
+ 'achievementGotTheMoves', (2, 1, 0),
+ 'Default:Uber Football',
+ 30,
+ hard_mode_only=True))
+ # 40
+ achs.append(
+ Achievement('Uber Football Shutout',
+ 'achievementFootballShutout', (2, 2, 0),
+ 'Default:Uber Football',
+ 40,
+ hard_mode_only=True))
+
+ # 30
+ achs.append(
+ Achievement('Uber Runaround Victory', 'achievementRunaround',
+ (1.5, 1.2, 0.2), 'Default:Uber Runaround', 30))
+ # 40
+ achs.append(
+ Achievement('The Great Wall',
+ 'achievementWall', (2, 1.7, 0.4),
+ 'Default:Uber Runaround',
+ 40,
+ hard_mode_only=True))
+ # 40
+ achs.append(
+ Achievement('Stayin\' Alive',
+ 'achievementStayinAlive', (2, 2, 1),
+ 'Default:Uber Runaround',
+ 40,
+ hard_mode_only=True))
+
+ # 20
+ achs.append(
+ Achievement('Last Stand Master',
+ 'achievementMedalSmall', (2, 1.5, 0.3),
+ 'Default:The Last Stand',
+ 20,
+ hard_mode_only=True))
+ # 40
+ achs.append(
+ Achievement('Last Stand Wizard',
+ 'achievementMedalMedium', (2, 1.5, 0.3),
+ 'Default:The Last Stand',
+ 40,
+ hard_mode_only=True))
+ # 60
+ achs.append(
+ Achievement('Last Stand God',
+ 'achievementMedalLarge', (2, 1.5, 0.3),
+ 'Default:The Last Stand',
+ 60,
+ hard_mode_only=True))
+
+ # 5
+ achs.append(
+ Achievement('Onslaught Master', 'achievementMedalSmall',
+ (0.7, 1, 0.7), 'Challenges:Infinite Onslaught', 5))
+ # 15
+ achs.append(
+ Achievement('Onslaught Wizard', 'achievementMedalMedium',
+ (0.7, 1.0, 0.7), 'Challenges:Infinite Onslaught', 15))
+ # 30
+ achs.append(
+ Achievement('Onslaught God', 'achievementMedalLarge',
+ (0.7, 1.0, 0.7), 'Challenges:Infinite Onslaught', 30))
+
+ # 5
+ achs.append(
+ Achievement('Runaround Master', 'achievementMedalSmall',
+ (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 5))
+ # 15
+ achs.append(
+ Achievement('Runaround Wizard', 'achievementMedalMedium',
+ (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 15))
+ # 30
+ achs.append(
+ Achievement('Runaround God', 'achievementMedalLarge',
+ (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 30))
+
+ def award_local_achievement(self, achname: str) -> None:
+ """For non-game-based achievements such as controller-connection."""
+ try:
+ ach = self.get_achievement(achname)
+ if not ach.complete:
+
+ # Report new achievements to the game-service.
+ _ba.report_achievement(achname)
+
+ # And to our account.
+ _ba.add_transaction({'type': 'ACHIEVEMENT', 'name': achname})
+
+ # Now attempt to show a banner.
+ self.display_achievement_banner(achname)
+
+ except Exception:
+ print_exception()
+
+ def display_achievement_banner(self, achname: str) -> None:
+ """Display a completion banner for an achievement.
+
+ (internal)
+
+ Used for server-driven achievements.
+ """
+ try:
+ # FIXME: Need to get these using the UI context or some other
+ # purely local context somehow instead of trying to inject these
+ # into whatever activity happens to be active
+ # (since that won't work while in client mode).
+ activity = _ba.get_foreground_host_activity()
+ if activity is not None:
+ with _ba.Context(activity):
+ self.get_achievement(achname).announce_completion()
+ except Exception:
+ print_exception('error showing server ach')
+
+ def set_completed_achievements(self, achs: Sequence[str]) -> None:
+ """Set the current state of completed achievements.
+
+ (internal)
+
+ All achievements not included here will be set incomplete.
+ """
+
+ # Note: This gets called whenever game-center/game-circle/etc tells
+ # us which achievements we currently have. We always defer to them,
+ # even if that means we have to un-set an achievement we think we have.
+
+ cfg = _ba.app.config
+ cfg['Achievements'] = {}
+ for a_name in achs:
+ self.get_achievement(a_name).set_complete(True)
+ cfg.commit()
+
+ def get_achievement(self, name: str) -> Achievement:
+ """Return an Achievement by name."""
+ achs = [a for a in self.achievements if a.name == name]
+ assert len(achs) < 2
+ if not achs:
+ raise ValueError("Invalid achievement name: '" + name + "'")
+ return achs[0]
+
+ def achievements_for_coop_level(self,
+ level_name: str) -> List[Achievement]:
+ """Given a level name, return achievements available for it."""
+
+ # For the Easy campaign we return achievements for the Default
+ # campaign too. (want the user to see what achievements are part of the
+ # level even if they can't unlock them all on easy mode).
+ return [
+ a for a in self.achievements
+ if a.level_name in (level_name,
+ level_name.replace('Easy', 'Default'))
+ ]
+
+ def _test(self) -> None:
+ """For testing achievement animations."""
+ from ba._enums import TimeType
+
+ def testcall1() -> None:
+ self.achievements[0].announce_completion()
+ self.achievements[1].announce_completion()
+ self.achievements[2].announce_completion()
+
+ def testcall2() -> None:
+ self.achievements[3].announce_completion()
+ self.achievements[4].announce_completion()
+ self.achievements[5].announce_completion()
+
+ _ba.timer(3.0, testcall1, timetype=TimeType.BASE)
+ _ba.timer(7.0, testcall2, timetype=TimeType.BASE)
def _get_ach_mult(include_pro_bonus: bool = False) -> int:
@@ -139,34 +417,21 @@ def _get_ach_mult(include_pro_bonus: bool = False) -> int:
return val
-def get_achievements_for_coop_level(level_name: str) -> List[Achievement]:
- """Given a level name, return achievements available for it."""
-
- # For the Easy campaign we return achievements for the Default
- # campaign too. (want the user to see what achievements are part of the
- # level even if they can't unlock them all on easy mode).
- return [
- a for a in _ba.app.achievements
- if a.level_name in (level_name, level_name.replace('Easy', 'Default'))
- ]
-
-
def _display_next_achievement() -> None:
# Pull the first achievement off the list and display it, or kill the
# display-timer if the list is empty.
app = _ba.app
- if app.achievements_to_display:
+ if app.ach.achievements_to_display:
try:
- ach, sound = app.achievements_to_display.pop(0)
+ ach, sound = app.ach.achievements_to_display.pop(0)
ach.show_completion_banner(sound)
except Exception:
- from ba import _error
- _error.print_exception('error showing next achievement')
- app.achievements_to_display = []
- app.achievement_display_timer = None
+ print_exception('error showing next achievement')
+ app.ach.achievements_to_display = []
+ app.ach.achievement_display_timer = None
else:
- app.achievement_display_timer = None
+ app.ach.achievement_display_timer = None
class Achievement:
@@ -236,18 +501,18 @@ class Achievement:
return
# If we're being freshly complete, display/report it and whatnot.
- if (self, sound) not in app.achievements_to_display:
- app.achievements_to_display.append((self, sound))
+ if (self, sound) not in app.ach.achievements_to_display:
+ app.ach.achievements_to_display.append((self, sound))
# If there's no achievement display timer going, kick one off
# (if one's already running it will pick this up before it dies).
# Need to check last time too; its possible our timer wasn't able to
# clear itself if an activity died and took it down with it.
- if ((app.achievement_display_timer is None or
- _ba.time(TimeType.REAL) - app.last_achievement_display_time > 2.0)
- and _ba.getactivity(doraise=False) is not None):
- app.achievement_display_timer = _ba.Timer(
+ if ((app.ach.achievement_display_timer is None
+ or _ba.time(TimeType.REAL) - app.ach.last_achievement_display_time
+ > 2.0) and _ba.getactivity(doraise=False) is not None):
+ app.ach.achievement_display_timer = _ba.Timer(
1.0,
_display_next_achievement,
repeat=True,
@@ -280,9 +545,8 @@ class Achievement:
else:
name = ''
except Exception:
- from ba import _error
name = ''
- _error.print_exception()
+ print_exception()
return Lstr(resource='achievements.' + self._name + '.name',
subs=[('${LEVEL}', name)])
@@ -397,8 +661,7 @@ class Achievement:
else:
hmo = False
except Exception:
- from ba import _error
- _error.print_exception('Error determining campaign')
+ print_exception('Error determining campaign.')
hmo = False
objs: List[ba.Actor]
@@ -648,7 +911,7 @@ class Achievement:
def _remove_banner_slot(self) -> None:
assert self._completion_banner_slot is not None
- _ba.app.achievement_completion_banner_slots.remove(
+ _ba.app.ach.achievement_completion_banner_slots.remove(
self._completion_banner_slot)
self._completion_banner_slot = None
@@ -663,7 +926,7 @@ class Achievement:
from ba._messages import DieMessage
from ba._enums import TimeType, SpecialChar
app = _ba.app
- app.last_achievement_display_time = _ba.time(TimeType.REAL)
+ app.ach.last_achievement_display_time = _ba.time(TimeType.REAL)
# Just piggy-back onto any current activity
# (should we use the session instead?..)
@@ -694,8 +957,8 @@ class Achievement:
# Find the first free slot.
i = 0
while True:
- if i not in app.achievement_completion_banner_slots:
- app.achievement_completion_banner_slots.add(i)
+ if i not in app.ach.achievement_completion_banner_slots:
+ app.ach.achievement_completion_banner_slots.add(i)
self._completion_banner_slot = i
# Remove us from that slot when we close.
@@ -953,252 +1216,3 @@ class Achievement:
for actor in objs:
_ba.timer(out_time + 1.000,
WeakCall(actor.handlemessage, DieMessage()))
-
-
-def init_achievements() -> None:
- """Fill in available achievements."""
-
- achs = _ba.app.achievements
-
- # 5
- achs.append(
- Achievement('In Control', 'achievementInControl', (1, 1, 1), '', 5))
- # 15
- achs.append(
- Achievement('Sharing is Caring', 'achievementSharingIsCaring',
- (1, 1, 1), '', 15))
- # 10
- achs.append(
- Achievement('Dual Wielding', 'achievementDualWielding', (1, 1, 1), '',
- 10))
-
- # 10
- achs.append(
- Achievement('Free Loader', 'achievementFreeLoader', (1, 1, 1), '', 10))
- # 20
- achs.append(
- Achievement('Team Player', 'achievementTeamPlayer', (1, 1, 1), '', 20))
-
- # 5
- achs.append(
- Achievement('Onslaught Training Victory', 'achievementOnslaught',
- (1, 1, 1), 'Default:Onslaught Training', 5))
- # 5
- achs.append(
- Achievement('Off You Go Then', 'achievementOffYouGo', (1, 1.1, 1.3),
- 'Default:Onslaught Training', 5))
- # 10
- achs.append(
- Achievement('Boxer',
- 'achievementBoxer', (1, 0.6, 0.6),
- 'Default:Onslaught Training',
- 10,
- hard_mode_only=True))
-
- # 10
- achs.append(
- Achievement('Rookie Onslaught Victory', 'achievementOnslaught',
- (0.5, 1.4, 0.6), 'Default:Rookie Onslaught', 10))
- # 10
- achs.append(
- Achievement('Mine Games', 'achievementMine', (1, 1, 1.4),
- 'Default:Rookie Onslaught', 10))
- # 15
- achs.append(
- Achievement('Flawless Victory',
- 'achievementFlawlessVictory', (1, 1, 1),
- 'Default:Rookie Onslaught',
- 15,
- hard_mode_only=True))
-
- # 10
- achs.append(
- Achievement('Rookie Football Victory', 'achievementFootballVictory',
- (1.0, 1, 0.6), 'Default:Rookie Football', 10))
- # 10
- achs.append(
- Achievement('Super Punch', 'achievementSuperPunch', (1, 1, 1.8),
- 'Default:Rookie Football', 10))
- # 15
- achs.append(
- Achievement('Rookie Football Shutout',
- 'achievementFootballShutout', (1, 1, 1),
- 'Default:Rookie Football',
- 15,
- hard_mode_only=True))
-
- # 15
- achs.append(
- Achievement('Pro Onslaught Victory', 'achievementOnslaught',
- (0.3, 1, 2.0), 'Default:Pro Onslaught', 15))
- # 15
- achs.append(
- Achievement('Boom Goes the Dynamite', 'achievementTNT',
- (1.4, 1.2, 0.8), 'Default:Pro Onslaught', 15))
- # 20
- achs.append(
- Achievement('Pro Boxer',
- 'achievementBoxer', (2, 2, 0),
- 'Default:Pro Onslaught',
- 20,
- hard_mode_only=True))
-
- # 15
- achs.append(
- Achievement('Pro Football Victory', 'achievementFootballVictory',
- (1.3, 1.3, 2.0), 'Default:Pro Football', 15))
- # 15
- achs.append(
- Achievement('Super Mega Punch', 'achievementSuperPunch', (2, 1, 0.6),
- 'Default:Pro Football', 15))
- # 20
- achs.append(
- Achievement('Pro Football Shutout',
- 'achievementFootballShutout', (0.7, 0.7, 2.0),
- 'Default:Pro Football',
- 20,
- hard_mode_only=True))
-
- # 15
- achs.append(
- Achievement('Pro Runaround Victory', 'achievementRunaround', (1, 1, 1),
- 'Default:Pro Runaround', 15))
- # 20
- achs.append(
- Achievement('Precision Bombing',
- 'achievementCrossHair', (1, 1, 1.3),
- 'Default:Pro Runaround',
- 20,
- hard_mode_only=True))
- # 25
- achs.append(
- Achievement('The Wall',
- 'achievementWall', (1, 0.7, 0.7),
- 'Default:Pro Runaround',
- 25,
- hard_mode_only=True))
-
- # 30
- achs.append(
- Achievement('Uber Onslaught Victory', 'achievementOnslaught',
- (2, 2, 1), 'Default:Uber Onslaught', 30))
- # 30
- achs.append(
- Achievement('Gold Miner',
- 'achievementMine', (2, 1.6, 0.2),
- 'Default:Uber Onslaught',
- 30,
- hard_mode_only=True))
- # 30
- achs.append(
- Achievement('TNT Terror',
- 'achievementTNT', (2, 1.8, 0.3),
- 'Default:Uber Onslaught',
- 30,
- hard_mode_only=True))
-
- # 30
- achs.append(
- Achievement('Uber Football Victory', 'achievementFootballVictory',
- (1.8, 1.4, 0.3), 'Default:Uber Football', 30))
- # 30
- achs.append(
- Achievement('Got the Moves',
- 'achievementGotTheMoves', (2, 1, 0),
- 'Default:Uber Football',
- 30,
- hard_mode_only=True))
- # 40
- achs.append(
- Achievement('Uber Football Shutout',
- 'achievementFootballShutout', (2, 2, 0),
- 'Default:Uber Football',
- 40,
- hard_mode_only=True))
-
- # 30
- achs.append(
- Achievement('Uber Runaround Victory', 'achievementRunaround',
- (1.5, 1.2, 0.2), 'Default:Uber Runaround', 30))
- # 40
- achs.append(
- Achievement('The Great Wall',
- 'achievementWall', (2, 1.7, 0.4),
- 'Default:Uber Runaround',
- 40,
- hard_mode_only=True))
- # 40
- achs.append(
- Achievement('Stayin\' Alive',
- 'achievementStayinAlive', (2, 2, 1),
- 'Default:Uber Runaround',
- 40,
- hard_mode_only=True))
-
- # 20
- achs.append(
- Achievement('Last Stand Master',
- 'achievementMedalSmall', (2, 1.5, 0.3),
- 'Default:The Last Stand',
- 20,
- hard_mode_only=True))
- # 40
- achs.append(
- Achievement('Last Stand Wizard',
- 'achievementMedalMedium', (2, 1.5, 0.3),
- 'Default:The Last Stand',
- 40,
- hard_mode_only=True))
- # 60
- achs.append(
- Achievement('Last Stand God',
- 'achievementMedalLarge', (2, 1.5, 0.3),
- 'Default:The Last Stand',
- 60,
- hard_mode_only=True))
-
- # 5
- achs.append(
- Achievement('Onslaught Master', 'achievementMedalSmall', (0.7, 1, 0.7),
- 'Challenges:Infinite Onslaught', 5))
- # 15
- achs.append(
- Achievement('Onslaught Wizard', 'achievementMedalMedium',
- (0.7, 1.0, 0.7), 'Challenges:Infinite Onslaught', 15))
- # 30
- achs.append(
- Achievement('Onslaught God', 'achievementMedalLarge', (0.7, 1.0, 0.7),
- 'Challenges:Infinite Onslaught', 30))
-
- # 5
- achs.append(
- Achievement('Runaround Master', 'achievementMedalSmall',
- (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 5))
- # 15
- achs.append(
- Achievement('Runaround Wizard', 'achievementMedalMedium',
- (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 15))
- # 30
- achs.append(
- Achievement('Runaround God', 'achievementMedalLarge', (1.0, 1.0, 1.2),
- 'Challenges:Infinite Runaround', 30))
-
-
-def _test() -> None:
- """For testing achievement animations."""
- from ba._enums import TimeType
-
- def testcall1() -> None:
- app = _ba.app
- app.achievements[0].announce_completion()
- app.achievements[1].announce_completion()
- app.achievements[2].announce_completion()
-
- def testcall2() -> None:
- app = _ba.app
- app.achievements[3].announce_completion()
- app.achievements[4].announce_completion()
- app.achievements[5].announce_completion()
-
- _ba.timer(3.0, testcall1, timetype=TimeType.BASE)
- _ba.timer(7.0, testcall2, timetype=TimeType.BASE)
diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py
index 7c0de6d6..e5b84b90 100644
--- a/assets/src/ba_data/python/ba/_app.py
+++ b/assets/src/ba_data/python/ba/_app.py
@@ -99,7 +99,6 @@ class App:
"""Path where the app looks for its bundled scripts."""
assert isinstance(self._env['python_directory_app'], str)
return self._env['python_directory_app']
- # return self._python_directory_app
@property
def python_directory_app_site(self) -> str:
@@ -175,6 +174,7 @@ class App:
from ba._music import MusicSubsystem
from ba._language import LanguageSubsystem
from ba._ui import UISubsystem
+ from ba._achievement import AchievementSubsystem
# Config.
self.config_file_healthy = False
@@ -241,18 +241,10 @@ class App:
self.last_ad_purpose = 'invalid'
self.attempted_first_ad = False
- # Music.
self.music = MusicSubsystem()
-
- # Language.
self.lang = LanguageSubsystem()
-
- # Achievements.
- self.achievements: List[ba.Achievement] = []
- self.achievements_to_display: (List[Tuple[ba.Achievement, bool]]) = []
- self.achievement_display_timer: Optional[_ba.Timer] = None
- self.last_achievement_display_time: float = 0.0
- self.achievement_completion_banner_slots: Set[int] = set()
+ self.ach = AchievementSubsystem()
+ self.ui = UISubsystem()
# Lobby.
self.lobby_random_profile_index: int = 1
@@ -275,9 +267,6 @@ class App:
self.ffa_series_length = 24
self.coop_session_args: Dict = {}
- # UI.
- self.ui = UISubsystem()
-
self.value_test_defaults: dict = {}
self.first_main_menu = True # FIXME: Move to mainmenu class.
self.did_menu_intro = False # FIXME: Move to mainmenu class.
@@ -320,7 +309,7 @@ class App:
self.ui.on_app_launch()
- _achievement.init_achievements()
+ # _achievement.init_achievements()
spazappearance.register_appearances()
_campaign.init_campaigns()
diff --git a/assets/src/ba_data/python/ba/_coopgame.py b/assets/src/ba_data/python/ba/_coopgame.py
index adc64ad4..ae7adca9 100644
--- a/assets/src/ba_data/python/ba/_coopgame.py
+++ b/assets/src/ba_data/python/ba/_coopgame.py
@@ -130,21 +130,19 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
player.actor.handlemessage(CelebrateMessage(duration))
def _preload_achievements(self) -> None:
- from ba import _achievement
- achievements = _achievement.get_achievements_for_coop_level(
+ achievements = _ba.app.ach.achievements_for_coop_level(
self._get_coop_level_name())
for ach in achievements:
ach.get_icon_texture(True)
def _show_remaining_achievements(self) -> None:
# pylint: disable=cyclic-import
- from ba._achievement import get_achievements_for_coop_level
from ba._language import Lstr
from bastd.actor.text import Text
ts_h_offs = 30
v_offs = -200
achievements = [
- a for a in get_achievements_for_coop_level(
+ a for a in _ba.app.ach.achievements_for_coop_level(
self._get_coop_level_name()) if not a.complete
]
vrmode = _ba.app.vr_mode
@@ -193,12 +191,11 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
Returns True if a banner will be shown;
False otherwise
"""
- from ba._achievement import get_achievement
if achievement_name in self._achievements_awarded:
return
- ach = get_achievement(achievement_name)
+ ach = _ba.app.ach.get_achievement(achievement_name)
# If we're in the easy campaign and this achievement is hard-mode-only,
# ignore it.
diff --git a/assets/src/ba_data/python/ba/_hooks.py b/assets/src/ba_data/python/ba/_hooks.py
index c1e10c8f..eed60f95 100644
--- a/assets/src/ba_data/python/ba/_hooks.py
+++ b/assets/src/ba_data/python/ba/_hooks.py
@@ -138,13 +138,11 @@ def language_test_toggle() -> None:
def award_in_control_achievement() -> None:
- from ba._achievement import award_local_achievement
- award_local_achievement('In Control')
+ _ba.app.ach.award_local_achievement('In Control')
def award_dual_wielding_achievement() -> None:
- from ba._achievement import award_local_achievement
- award_local_achievement('Dual Wielding')
+ _ba.app.ach.award_local_achievement('Dual Wielding')
def play_gong_sound() -> None:
diff --git a/assets/src/ba_data/python/ba/_music.py b/assets/src/ba_data/python/ba/_music.py
index fda0630f..0bab82de 100644
--- a/assets/src/ba_data/python/ba/_music.py
+++ b/assets/src/ba_data/python/ba/_music.py
@@ -121,7 +121,7 @@ class MusicSubsystem:
Category: App Classes
- To use this class, access the single instance of it at 'ba.app.music'.
+ Access the single shared instance of this class at 'ba.app.music'.
"""
def __init__(self) -> None:
diff --git a/assets/src/ba_data/python/ba/_teamgame.py b/assets/src/ba_data/python/ba/_teamgame.py
index aa5563cf..afa8519e 100644
--- a/assets/src/ba_data/python/ba/_teamgame.py
+++ b/assets/src/ba_data/python/ba/_teamgame.py
@@ -6,11 +6,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar
+import _ba
from ba._freeforallsession import FreeForAllSession
from ba._gameactivity import GameActivity
from ba._gameresults import GameResults
from ba._dualteamsession import DualTeamSession
-import _ba
if TYPE_CHECKING:
from typing import Any, Dict, Type, Sequence
@@ -78,12 +78,11 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]):
# Award a few achievements.
if isinstance(self.session, FreeForAllSession):
if len(self.players) >= 2:
- from ba import _achievement
- _achievement.award_local_achievement('Free Loader')
+ _ba.app.ach.award_local_achievement('Free Loader')
elif isinstance(self.session, DualTeamSession):
if len(self.players) >= 4:
from ba import _achievement
- _achievement.award_local_achievement('Team Player')
+ _ba.app.ach.award_local_achievement('Team Player')
except Exception:
from ba import _error
_error.print_exception()
diff --git a/assets/src/ba_data/python/ba/internal.py b/assets/src/ba_data/python/ba/internal.py
index dd354fb5..9bf9568d 100644
--- a/assets/src/ba_data/python/ba/internal.py
+++ b/assets/src/ba_data/python/ba/internal.py
@@ -23,9 +23,6 @@ from ba._account import (on_account_state_changed,
get_purchased_icons, get_cached_league_rank_data,
get_league_rank_points, cache_league_rank_data)
from ba._activitytypes import JoinActivity, ScoreScreenActivity
-from ba._achievement import (get_achievement, set_completed_achievements,
- display_achievement_banner,
- get_achievements_for_coop_level)
from ba._apputils import (is_browser_likely_available, get_remote_app_name,
should_submit_debug_info, show_ad, show_ad_2)
from ba._benchmark import (run_gpu_benchmark, run_cpu_benchmark,
diff --git a/assets/src/ba_data/python/bastd/activity/coopjoin.py b/assets/src/ba_data/python/bastd/activity/coopjoin.py
index 3a62e68b..1bb614a4 100644
--- a/assets/src/ba_data/python/bastd/activity/coopjoin.py
+++ b/assets/src/ba_data/python/bastd/activity/coopjoin.py
@@ -59,7 +59,6 @@ class CoopJoinActivity(JoinActivity):
# pylint: disable=too-many-statements
from efro.util import asserttype
from bastd.actor.text import Text
- from ba.internal import get_achievements_for_coop_level
# Sort by originating date so that the most recent is first.
if scores is not None:
@@ -159,7 +158,8 @@ class CoopJoinActivity(JoinActivity):
if not (ba.app.demo_mode or ba.app.arcade_mode):
achievements = [
- a for a in get_achievements_for_coop_level(levelname)
+ a
+ for a in ba.app.ach.achievements_for_coop_level(levelname)
if not a.complete
]
have_achievements = bool(achievements)
diff --git a/assets/src/ba_data/python/bastd/activity/coopscore.py b/assets/src/ba_data/python/bastd/activity/coopscore.py
index a870f8f9..1c9e8cfe 100644
--- a/assets/src/ba_data/python/bastd/activity/coopscore.py
+++ b/assets/src/ba_data/python/bastd/activity/coopscore.py
@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING
import _ba
import ba
-from ba.internal import get_achievements_for_coop_level
from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText
@@ -50,8 +49,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._campaign: ba.Campaign = settings['campaign']
self._have_achievements = bool(
- get_achievements_for_coop_level(self._campaign.name + ':' +
- settings['level']))
+ ba.app.ach.achievements_for_coop_level(self._campaign.name + ':' +
+ settings['level']))
self._account_type = (_ba.get_account_type() if
_ba.get_account_state() == 'signed_in' else None)
@@ -847,7 +846,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
transition_delay=2.8).autoretain()
assert self._game_name_str is not None
- achievements = get_achievements_for_coop_level(self._game_name_str)
+ achievements = ba.app.ach.achievements_for_coop_level(
+ self._game_name_str)
hval = -455
vval = -100
tdelay = 0.0
diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py
index 44808147..9697e521 100644
--- a/assets/src/ba_data/python/bastd/mainmenu.py
+++ b/assets/src/ba_data/python/bastd/mainmenu.py
@@ -319,7 +319,8 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
transition_out_delay=self._message_duration
).autoretain()
achs = [
- a for a in app.achievements if not a.complete
+ a for a in app.ach.achievements
+ if not a.complete
]
if achs:
ach = achs.pop(
diff --git a/assets/src/ba_data/python/bastd/ui/account/settings.py b/assets/src/ba_data/python/bastd/ui/account/settings.py
index 30599418..a87e7a9a 100644
--- a/assets/src/ba_data/python/bastd/ui/account/settings.py
+++ b/assets/src/ba_data/python/bastd/ui/account/settings.py
@@ -995,8 +995,8 @@ class AccountSettingsWindow(ba.Window):
if (self._achievements_text is None
and self._achievements_button is None):
return
- complete = sum(1 if a.complete else 0 for a in ba.app.achievements)
- total = len(ba.app.achievements)
+ complete = sum(1 if a.complete else 0 for a in ba.app.ach.achievements)
+ total = len(ba.app.ach.achievements)
txt_final = ba.Lstr(resource=self._r + '.achievementProgressText',
subs=[('${COUNT}', str(complete)),
('${TOTAL}', str(total))])
diff --git a/assets/src/ba_data/python/bastd/ui/account/viewer.py b/assets/src/ba_data/python/bastd/ui/account/viewer.py
index 3b72aef0..a8ea39af 100644
--- a/assets/src/ba_data/python/bastd/ui/account/viewer.py
+++ b/assets/src/ba_data/python/bastd/ui/account/viewer.py
@@ -426,7 +426,7 @@ class AccountViewerWindow(popup.PopupWindow):
v_align='center',
scale=0.55,
text=str(data['achievementsCompleted']) + ' / ' +
- str(len(ba.app.achievements)),
+ str(len(ba.app.ach.achievements)),
maxwidth=sub_width * maxwidth_scale)
v -= 25
diff --git a/assets/src/ba_data/python/bastd/ui/achievements.py b/assets/src/ba_data/python/bastd/ui/achievements.py
index 66cb5b0b..ca56c436 100644
--- a/assets/src/ba_data/python/bastd/ui/achievements.py
+++ b/assets/src/ba_data/python/bastd/ui/achievements.py
@@ -47,7 +47,7 @@ class AchievementsWindow(popup.PopupWindow):
icon=ba.gettexture('crossOut'),
iconscale=1.2)
- achievements = ba.app.achievements
+ achievements = ba.app.ach.achievements
num_complete = len([a for a in achievements if a.complete])
txt_final = ba.Lstr(
diff --git a/assets/src/ba_data/python/bastd/ui/coop/gamebutton.py b/assets/src/ba_data/python/bastd/ui/coop/gamebutton.py
index 304d681e..6c6e8f07 100644
--- a/assets/src/ba_data/python/bastd/ui/coop/gamebutton.py
+++ b/assets/src/ba_data/python/bastd/ui/coop/gamebutton.py
@@ -22,7 +22,7 @@ class GameButton:
x: float, y: float, select: bool, row: str):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
- from ba.internal import (get_achievements_for_coop_level, getcampaign)
+ from ba.internal import getcampaign
self._game = game
sclx = 195.0
scly = 195.0
@@ -81,7 +81,7 @@ class GameButton:
mask_texture=ba.gettexture('mapPreviewMask'))
translated = campaign.getlevel(levelname).displayname
- self._achievements = (get_achievements_for_coop_level(game))
+ self._achievements = ba.app.ach.achievements_for_coop_level(game)
self._name_widget = ba.textwidget(parent=parent,
draw_controller=btn,
diff --git a/assets/src/ba_data/python/bastd/ui/league/rankwindow.py b/assets/src/ba_data/python/bastd/ui/league/rankwindow.py
index 43df0935..e0c7230b 100644
--- a/assets/src/ba_data/python/bastd/ui/league/rankwindow.py
+++ b/assets/src/ba_data/python/bastd/ui/league/rankwindow.py
@@ -808,7 +808,7 @@ class LeagueRankWindow(ba.Window):
# for the achievement value, use the number they gave us for
# non-current seasons; otherwise calc our own
total_ach_value = 0
- for ach in ba.app.achievements:
+ for ach in ba.app.ach.achievements:
if ach.complete:
total_ach_value += ach.power_ranking_value
if self._season != 'a' and not self._is_current_season:
diff --git a/docs/ba_module.md b/docs/ba_module.md
index a25ce291..3fb8309b 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -1,5 +1,5 @@
-last updated on 2020-10-15 for Ballistica version 1.5.27 build 20218
+last updated on 2020-10-16 for Ballistica version 1.5.27 build 20219
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!
@@ -148,6 +148,7 @@
Create the banner/sound for an acquired achievement announcement.
+
+
+
+<top level class>
+
+Subsystem for achievement handling.
+
+Category: App Classes
+
+ Access the single shared instance of this class at 'ba.app.ach'.
+
+
+Methods:
+
+
+-
+
ba.AchievementSubsystem()
+
+
+-
+
achievements_for_coop_level(self, level_name: str) -> List[Achievement]
+
+Given a level name, return achievements available for it.
+
+
+-
+
award_local_achievement(self, achname: str) -> None
+
+For non-game-based achievements such as controller-connection.
+
+
+-
+
get_achievement(self, name: str) -> Achievement
+
+Return an Achievement by name.
+
@@ -3974,7 +4012,7 @@ signify that the default soundtrack should be used..
Category: App Classes
- To use this class, access the single instance of it at 'ba.app.music'.
+
Access the single shared instance of this class at 'ba.app.music'.
Methods:
diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc
index 23ffe9c2..d1081461 100644
--- a/src/ballistica/ballistica.cc
+++ b/src/ballistica/ballistica.cc
@@ -29,7 +29,7 @@
namespace ballistica {
// These are set automatically via script; don't change here.
-const int kAppBuildNumber = 20218;
+const int kAppBuildNumber = 20219;
const char* kAppVersion = "1.5.27";
// Our standalone globals.