Consolidated achievement functionality into AchievementSubsystem obj at ba.app.ach

This commit is contained in:
Eric Froemling 2020-10-16 10:41:35 -07:00
parent 2d5c2dda9a
commit 0f9fe41542
20 changed files with 441 additions and 408 deletions

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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.

View File

@ -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:

View File

@ -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:

View File

@ -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()

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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(

View File

@ -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))])

View File

@ -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

View File

@ -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(

View File

@ -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,

View File

@ -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:

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-10-15 for Ballistica version 1.5.27 build 20218</em></h4>
<h4><em>last updated on 2020-10-16 for Ballistica version 1.5.27 build 20219</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>
@ -148,6 +148,7 @@
<h4><a name="class_category_App_Classes">App Classes</a></h4>
<ul>
<li><a href="#class_ba_Achievement">ba.Achievement</a></li>
<li><a href="#class_ba_AchievementSubsystem">ba.AchievementSubsystem</a></li>
<li><a href="#class_ba_App">ba.App</a></li>
<li><a href="#class_ba_AppConfig">ba.AppConfig</a></li>
<li><a href="#class_ba_AppDelegate">ba.AppDelegate</a></li>
@ -350,6 +351,43 @@ actually award achievements.</p>
<p>Create the banner/sound for an acquired achievement announcement.</p>
</dd>
</dl>
<hr>
<h2><strong><a name="class_ba_AchievementSubsystem">ba.AchievementSubsystem</a></strong></h3>
<p><em>&lt;top level class&gt;</em>
</p>
<p>Subsystem for achievement handling.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
<p> Access the single shared instance of this class at 'ba.app.ach'.
</p>
<h3>Methods:</h3>
<h5><a href="#method_ba_AchievementSubsystem____init__">&lt;constructor&gt;</a>, <a href="#method_ba_AchievementSubsystem__achievements_for_coop_level">achievements_for_coop_level()</a>, <a href="#method_ba_AchievementSubsystem__award_local_achievement">award_local_achievement()</a>, <a href="#method_ba_AchievementSubsystem__get_achievement">get_achievement()</a></h5>
<dl>
<dt><h4><a name="method_ba_AchievementSubsystem____init__">&lt;constructor&gt;</a></dt></h4><dd>
<p><span>ba.AchievementSubsystem()</span></p>
</dd>
<dt><h4><a name="method_ba_AchievementSubsystem__achievements_for_coop_level">achievements_for_coop_level()</a></dt></h4><dd>
<p><span>achievements_for_coop_level(self, level_name: str) -&gt; List[Achievement]</span></p>
<p>Given a level name, return achievements available for it.</p>
</dd>
<dt><h4><a name="method_ba_AchievementSubsystem__award_local_achievement">award_local_achievement()</a></dt></h4><dd>
<p><span>award_local_achievement(self, achname: str) -&gt; None</span></p>
<p>For non-game-based achievements such as controller-connection.</p>
</dd>
<dt><h4><a name="method_ba_AchievementSubsystem__get_achievement">get_achievement()</a></dt></h4><dd>
<p><span>get_achievement(self, name: str) -&gt; Achievement</span></p>
<p>Return an Achievement by name.</p>
</dd>
</dl>
<hr>
@ -3974,7 +4012,7 @@ signify that the default soundtrack should be used..</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
<p> To use this class, access the single instance of it at 'ba.app.music'.
<p> Access the single shared instance of this class at 'ba.app.music'.
</p>
<h3>Methods:</h3>

View File

@ -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.