Merge branch 'master' into pubsync

This commit is contained in:
Eric Froemling 2020-04-10 00:47:01 -07:00
commit 967d0f19d8
7 changed files with 72 additions and 83 deletions

View File

@ -1556,6 +1556,7 @@
<w>spammy</w> <w>spammy</w>
<w>sparx</w> <w>sparx</w>
<w>spawner</w> <w>spawner</w>
<w>spawners</w>
<w>spawnpoints</w> <w>spawnpoints</w>
<w>spawnpt</w> <w>spawnpt</w>
<w>spawntype</w> <w>spawntype</w>

View File

@ -34,6 +34,7 @@ if TYPE_CHECKING:
from typing import (List, Optional, Dict, Type, Any, Callable, Sequence, from typing import (List, Optional, Dict, Type, Any, Callable, Sequence,
Tuple, Union) Tuple, Union)
from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.bomb import TNTSpawner
import ba import ba
@ -330,7 +331,7 @@ class GameActivity(Activity):
self._map_type.preload() self._map_type.preload()
self._map: Optional[ba.Map] = None self._map: Optional[ba.Map] = None
self._powerup_drop_timer: Optional[ba.Timer] = None self._powerup_drop_timer: Optional[ba.Timer] = None
self._tnt_objs: Optional[Dict[int, Any]] = None self._tnt_spawners: Optional[Dict[int, TNTSpawner]] = None
self._tnt_drop_timer: Optional[ba.Timer] = None self._tnt_drop_timer: Optional[ba.Timer] = None
self.initial_player_info: Optional[List[Dict[str, Any]]] = None self.initial_player_info: Optional[List[Dict[str, Any]]] = None
self._game_scoreboard_name_text: Optional[ba.Actor] = None self._game_scoreboard_name_text: Optional[ba.Actor] = None
@ -1111,12 +1112,8 @@ class GameActivity(Activity):
repeat=True) repeat=True)
self._standard_drop_powerups() self._standard_drop_powerups()
if enable_tnt: if enable_tnt:
self._tnt_objs = {} self._tnt_spawners = {}
self._tnt_drop_timer = _ba.Timer(5.5, self._setup_standard_tnt_drops()
_general.WeakCall(
self._standard_drop_tnt),
repeat=True)
self._standard_drop_tnt()
def _standard_drop_powerup(self, index: int, expire: bool = True) -> None: def _standard_drop_powerup(self, index: int, expire: bool = True) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
@ -1136,24 +1133,15 @@ class GameActivity(Activity):
_ba.timer(i * 0.4, _general.WeakCall(self._standard_drop_powerup, _ba.timer(i * 0.4, _general.WeakCall(self._standard_drop_powerup,
i)) i))
def _standard_drop_tnt(self) -> None: def _setup_standard_tnt_drops(self) -> None:
"""Standard tnt drop.""" """Standard tnt drop."""
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bastd.actor import bomb from bastd.actor.bomb import TNTSpawner
# Drop TNT on the map for any tnt location with no existing tnt box.
for i, point in enumerate(self.map.tnt_points): for i, point in enumerate(self.map.tnt_points):
assert self._tnt_objs is not None assert self._tnt_spawners is not None
if i not in self._tnt_objs: if self._tnt_spawners.get(i) is None:
self._tnt_objs[i] = {'absent_ticks': 9999, 'obj': None} self._tnt_spawners[i] = TNTSpawner(point)
tnt_obj = self._tnt_objs[i]
# Respawn once its been dead for a while.
if not tnt_obj['obj']:
tnt_obj['absent_ticks'] += 1
if tnt_obj['absent_ticks'] > 3:
tnt_obj['obj'] = bomb.Bomb(position=point, bomb_type='tnt')
tnt_obj['absent_ticks'] = 0
def setup_standard_time_limit(self, duration: float) -> None: def setup_standard_time_limit(self, duration: float) -> None:
""" """

View File

@ -87,16 +87,18 @@ class ServerCallThread(threading.Thread):
activity) if activity is not None else None activity) if activity is not None else None
def _run_callback(self, arg: Union[None, Dict[str, Any]]) -> None: def _run_callback(self, arg: Union[None, Dict[str, Any]]) -> None:
# If we were created in an activity context and that activity has # If we were created in an activity context and that activity has
# since died, do nothing (hmm should we be using a context-call # since died, do nothing.
# instead of doing this manually?). # FIXME: Should we just be using a ContextCall instead of doing
activity = None if self._activity is None else self._activity() # this check manually?
if activity is None or activity.is_expired(): if self._activity is not None:
return activity = self._activity()
if activity is None or activity.is_expired():
return
# Technically we could do the same check for session contexts, # Technically we could do the same check for session contexts,
# but not gonna worry about it for now. # but not gonna worry about it for now.
assert self._context is not None
assert self._callback is not None assert self._callback is not None
with self._context: with self._context:
self._callback(arg) self._callback(arg)

View File

@ -29,6 +29,8 @@ from typing import TYPE_CHECKING
import _ba import _ba
import ba import ba
from ba.internal import get_achievements_for_coop_level from ba.internal import get_achievements_for_coop_level
from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Tuple, List, Dict, Any, Sequence from typing import Optional, Tuple, List, Dict, Any, Sequence
@ -50,9 +52,6 @@ class CoopScoreScreen(ba.Activity):
self.inherits_music = True self.inherits_music = True
self.use_fixed_vr_overlay = True self.use_fixed_vr_overlay = True
self._tournament_time_remaining = None
self._tournament_time_remaining_text = None
self._do_new_rating: bool = self.session.tournament_id is not None self._do_new_rating: bool = self.session.tournament_id is not None
self._score_display_sound = ba.getsound("scoreHit01") self._score_display_sound = ba.getsound("scoreHit01")
@ -134,6 +133,8 @@ class CoopScoreScreen(ba.Activity):
self._name_str: Optional[str] = None self._name_str: Optional[str] = None
self._friends_loading_status: Optional[ba.Actor] = None self._friends_loading_status: Optional[ba.Actor] = None
self._score_loading_status: Optional[ba.Actor] = None self._score_loading_status: Optional[ba.Actor] = None
self._tournament_time_remaining: Optional[float] = None
self._tournament_time_remaining_text: Optional[Text] = None
self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None
self._player_info = settings['player_info'] self._player_info = settings['player_info']
@ -288,7 +289,6 @@ class CoopScoreScreen(ba.Activity):
ba.open_url(self._score_link) ba.open_url(self._score_link)
def _ui_error(self) -> None: def _ui_error(self) -> None:
from bastd.actor.text import Text
with ba.Context(self): with ba.Context(self):
self._next_level_error = Text( self._next_level_error = Text(
ba.Lstr(resource='completeThisLevelToProceedText'), ba.Lstr(resource='completeThisLevelToProceedText'),
@ -515,8 +515,6 @@ class CoopScoreScreen(ba.Activity):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText
super().on_begin() super().on_begin()
# Calc whether the level is complete and other stuff. # Calc whether the level is complete and other stuff.
@ -893,7 +891,6 @@ class CoopScoreScreen(ba.Activity):
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# delay a bit if results come in too fast # delay a bit if results come in too fast
from bastd.actor.text import Text
base_delay = max(0, 1.9 - (ba.time() - self._begin_time)) base_delay = max(0, 1.9 - (ba.time() - self._begin_time))
ts_height = 300 ts_height = 300
ts_h_offs = -550 ts_h_offs = -550
@ -1009,7 +1006,6 @@ class CoopScoreScreen(ba.Activity):
# We need to manually run this in the context of our activity # We need to manually run this in the context of our activity
# and only if we aren't shutting down. # and only if we aren't shutting down.
# (really should make the submit_score call handle that stuff itself) # (really should make the submit_score call handle that stuff itself)
from bastd.actor.text import Text
if self.is_expired(): if self.is_expired():
return return
with ba.Context(self): with ba.Context(self):
@ -1169,10 +1165,9 @@ class CoopScoreScreen(ba.Activity):
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
from ba.internal import get_tournament_prize_strings from ba.internal import get_tournament_prize_strings
from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText
assert self._show_info is not None assert self._show_info is not None
available = (self._show_info['results'] is not None) available = (self._show_info['results'] is not None)
if available: if available:
error = (self._show_info['results']['error'] error = (self._show_info['results']['error']
if 'error' in self._show_info['results'] else None) if 'error' in self._show_info['results'] else None)
@ -1193,22 +1188,22 @@ class CoopScoreScreen(ba.Activity):
Text(ba.Lstr(resource='coopSelectWindow.timeRemainingText'), Text(ba.Lstr(resource='coopSelectWindow.timeRemainingText'),
position=(-360, -70 - 100), position=(-360, -70 - 100),
color=(1, 1, 1, 0.7), color=(1, 1, 1, 0.7),
h_align='center', h_align=Text.HAlign.CENTER,
v_align='center', v_align=Text.VAlign.CENTER,
transition='fade_in', transition=Text.Transition.FADE_IN,
scale=0.8, scale=0.8,
maxwidth=300, maxwidth=300,
transition_delay=2.0).autoretain() transition_delay=2.0).autoretain()
self._tournament_time_remaining_text = Text('', self._tournament_time_remaining_text = Text(
position=(-360, '',
-110 - 100), position=(-360, -110 - 100),
color=(1, 1, 1, 0.7), color=(1, 1, 1, 0.7),
h_align='center', h_align=Text.HAlign.CENTER,
v_align='center', v_align=Text.VAlign.CENTER,
transition='fade_in', transition=Text.Transition.FADE_IN,
scale=1.6, scale=1.6,
maxwidth=150, maxwidth=150,
transition_delay=2.0) transition_delay=2.0)
# If we're a tournament, show prizes. # If we're a tournament, show prizes.
try: try:
@ -1439,8 +1434,6 @@ class CoopScoreScreen(ba.Activity):
ba.timer(0.35, ba.Call(ba.playsound, self.cymbal_sound)) ba.timer(0.35, ba.Call(ba.playsound, self.cymbal_sound))
def _show_fail(self) -> None: def _show_fail(self) -> None:
from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText
ZoomText(ba.Lstr(resource='failText'), ZoomText(ba.Lstr(resource='failText'),
maxwidth=300, maxwidth=300,
flash=False, flash=False,
@ -1460,8 +1453,6 @@ class CoopScoreScreen(ba.Activity):
ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound)) ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound))
def _show_score_val(self, offs_x: float) -> None: def _show_score_val(self, offs_x: float) -> None:
from bastd.actor.text import Text
from bastd.actor.zoomtext import ZoomText
assert self._score_type is not None assert self._score_type is not None
assert self._score is not None assert self._score is not None
ZoomText((str(self._score) if self._score_type == 'points' else ZoomText((str(self._score) if self._score_type == 'points' else

View File

@ -1047,23 +1047,24 @@ class TNTSpawner:
category: Gameplay Classes category: Gameplay Classes
""" """
def __init__(self, position: Sequence[float], respawn_time: float = 30.0): def __init__(self, position: Sequence[float], respawn_time: float = 20.0):
"""Instantiate with given position and respawn_time (in seconds).""" """Instantiate with given position and respawn_time (in seconds)."""
self._position = position self._position = position
self._tnt: Optional[Bomb] = None self._tnt: Optional[Bomb] = None
self._respawn_time = random.uniform(0.8, 1.2) * respawn_time
self._wait_time = 0.0
self._update() self._update()
# (go with slightly more than 1 second to avoid timer stacking)
# Go with slightly more than 1 second to avoid timer stacking.
self._update_timer = ba.Timer(1.1, self._update_timer = ba.Timer(1.1,
ba.WeakCall(self._update), ba.WeakCall(self._update),
repeat=True) repeat=True)
self._respawn_time = random.uniform(0.8, 1.2) * respawn_time
self._wait_time = 0.0
def _update(self) -> None: def _update(self) -> None:
tnt_alive = self._tnt is not None and self._tnt.node tnt_alive = self._tnt is not None and self._tnt.node
if not tnt_alive: if not tnt_alive:
# respawn if its been long enough.. otherwise just increment our # Respawn if its been long enough.. otherwise just increment our
# how-long-since-we-died value # how-long-since-we-died value.
if self._tnt is None or self._wait_time >= self._respawn_time: if self._tnt is None or self._wait_time >= self._respawn_time:
self._tnt = Bomb(position=self._position, bomb_type='tnt') self._tnt = Bomb(position=self._position, bomb_type='tnt')
self._wait_time = 0.0 self._wait_time = 0.0

View File

@ -164,7 +164,7 @@ class MeteorShowerGame(ba.TeamGameActivity):
# Augment standard behavior. # Augment standard behavior.
super().handlemessage(msg) super().handlemessage(msg)
death_time = ba.time() death_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
# Record the player's moment of death. # Record the player's moment of death.
msg.spaz.player.gamedata['death_time'] = death_time msg.spaz.player.gamedata['death_time'] = death_time
@ -240,8 +240,10 @@ class MeteorShowerGame(ba.TeamGameActivity):
self._meteor_time = max(0.01, self._meteor_time * 0.9) self._meteor_time = max(0.01, self._meteor_time * 0.9)
def end_game(self) -> None: def end_game(self) -> None:
cur_time = ba.time() cur_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
assert self._timer is not None assert self._timer is not None
start_time = self._timer.getstarttime(
timeformat=ba.TimeFormat.MILLISECONDS)
# Mark 'death-time' as now for any still-living players # Mark 'death-time' as now for any still-living players
# and award players points for how long they lasted. # and award players points for how long they lasted.
@ -252,13 +254,14 @@ class MeteorShowerGame(ba.TeamGameActivity):
# Throw an extra fudge factor in so teams that # Throw an extra fudge factor in so teams that
# didn't die come out ahead of teams that did. # didn't die come out ahead of teams that did.
if 'death_time' not in player.gamedata: if 'death_time' not in player.gamedata:
player.gamedata['death_time'] = cur_time + 0.001 player.gamedata['death_time'] = cur_time + 1
# Award a per-player score depending on how many seconds # Award a per-player score depending on how many seconds
# they lasted (per-player scores only affect teams mode; # they lasted (per-player scores only affect teams mode;
# everywhere else just looks at the per-team score). # everywhere else just looks at the per-team score).
score = int(player.gamedata['death_time'] - score = int(player.gamedata['death_time'] -
self._timer.getstarttime()) self._timer.getstarttime(
timeformat=ba.TimeFormat.MILLISECONDS))
if 'death_time' not in player.gamedata: if 'death_time' not in player.gamedata:
score += 50 # a bit extra for survivors score += 50 # a bit extra for survivors
self.stats.player_scored(player, score, screenmessage=False) self.stats.player_scored(player, score, screenmessage=False)
@ -281,8 +284,7 @@ class MeteorShowerGame(ba.TeamGameActivity):
longest_life = 0 longest_life = 0
for player in team.players: for player in team.players:
longest_life = max(longest_life, longest_life = max(longest_life,
(player.gamedata['death_time'] - player.gamedata['death_time'] - start_time)
self._timer.getstarttime()))
results.set_team_score(team, longest_life) results.set_team_score(team, longest_life)
self.end(results=results) self.end(results=results)

View File

@ -88,7 +88,7 @@ class StoreBrowserWindow(ba.Window):
self._request: Any = None self._request: Any = None
self._r = 'store' self._r = 'store'
self._last_buy_time = None self._last_buy_time: Optional[float] = None
super().__init__(root_widget=ba.containerwidget( super().__init__(root_widget=ba.containerwidget(
size=(self._width, self._height + extra_top), size=(self._width, self._height + extra_top),
@ -179,7 +179,7 @@ class StoreBrowserWindow(ba.Window):
('minigames', ba.Lstr(resource=self._r + '.miniGamesText')), ('minigames', ba.Lstr(resource=self._r + '.miniGamesText')),
('characters', ba.Lstr(resource=self._r + '.charactersText')), ('characters', ba.Lstr(resource=self._r + '.charactersText')),
('icons', ba.Lstr(resource=self._r + '.iconsText')), ('icons', ba.Lstr(resource=self._r + '.iconsText')),
] # yapf : disable ]
tab_results = tabs.create_tab_buttons(self._root_widget, tab_results = tabs.create_tab_buttons(self._root_widget,
tabs_def, tabs_def,
@ -454,16 +454,19 @@ class StoreBrowserWindow(ba.Window):
# purchase this. Better to fail now than after we've # purchase this. Better to fail now than after we've
# paid locally. # paid locally.
app = ba.app app = ba.app
serverget('bsAccountPurchaseCheck', { serverget(
'item': item, 'bsAccountPurchaseCheck',
'platform': app.platform, {
'subplatform': app.subplatform, 'item': item,
'version': app.version, 'platform': app.platform,
'buildNumber': app.build_number, 'subplatform': app.subplatform,
'purchaseType': 'ticket' if is_ticket_purchase else 'real' 'version': app.version,
}, 'buildNumber': app.build_number,
callback=ba.WeakCall(self._purchase_check_result, item, 'purchaseType': 'ticket' if is_ticket_purchase else 'real'
is_ticket_purchase)) },
callback=ba.WeakCall(self._purchase_check_result, item,
is_ticket_purchase),
)
def buy(self, item: str) -> None: def buy(self, item: str) -> None:
"""Attempt to purchase the provided item.""" """Attempt to purchase the provided item."""
@ -476,7 +479,8 @@ class StoreBrowserWindow(ba.Window):
# Prevent pressing buy within a few seconds of the last press # Prevent pressing buy within a few seconds of the last press
# (gives the buttons time to disable themselves and whatnot). # (gives the buttons time to disable themselves and whatnot).
curtime = ba.time(ba.TimeType.REAL) curtime = ba.time(ba.TimeType.REAL)
if self._last_buy_time is None or curtime - self._last_buy_time < 2.0: if self._last_buy_time is not None and (curtime -
self._last_buy_time) < 2.0:
ba.playsound(ba.getsound('error')) ba.playsound(ba.getsound('error'))
else: else:
if _ba.get_account_state() != 'signed_in': if _ba.get_account_state() != 'signed_in':
@ -537,8 +541,7 @@ class StoreBrowserWindow(ba.Window):
sales_raw = _ba.get_account_misc_read_val('sales', {}) sales_raw = _ba.get_account_misc_read_val('sales', {})
sales = {} sales = {}
try: try:
# look at the current set of sales; filter any with # Look at the current set of sales; filter any with time remaining.
# time remaining..
for sale_item, sale_info in list(sales_raw.items()): for sale_item, sale_info in list(sales_raw.items()):
to_end = (datetime.datetime.utcfromtimestamp(sale_info['e']) - to_end = (datetime.datetime.utcfromtimestamp(sale_info['e']) -
datetime.datetime.utcnow()).total_seconds() datetime.datetime.utcnow()).total_seconds()
@ -548,7 +551,7 @@ class StoreBrowserWindow(ba.Window):
'original_price': sale_info['op'] 'original_price': sale_info['op']
} }
except Exception: except Exception:
ba.print_exception("Error parsing sales") ba.print_exception("Error parsing sales.")
assert self.button_infos is not None assert self.button_infos is not None
for b_type, b_info in self.button_infos.items(): for b_type, b_info in self.button_infos.items():
@ -602,7 +605,8 @@ class StoreBrowserWindow(ba.Window):
price_text_right = '' price_text_right = ''
else: else:
price = _ba.get_account_misc_read_val('price.' + b_type, 0) price = _ba.get_account_misc_read_val('price.' + b_type, 0)
# color button differently if we cant afford this
# Color the button differently if we cant afford this.
if _ba.get_account_state() == 'signed_in': if _ba.get_account_state() == 'signed_in':
if _ba.get_account_ticket_count() < price: if _ba.get_account_ticket_count() < price:
color = (0.6, 0.61, 0.6) color = (0.6, 0.61, 0.6)