def _switch_to_score_screen(self, results: ba.GameResults) -> None: # pylint: disable=cyclic-import from efro.util import asserttype from bastd.activity.drawscore import DrawScoreScreenActivity from bastd.activity.multiteamvictory import ( TeamSeriesVictoryScoreScreenActivity) from bastd.activity.freeforallvictory import ( FreeForAllVictoryScoreScreenActivity) winners = results.winnergroups # If there's multiple players and everyone has the same score, # call it a draw. if len(self.sessionplayers) > 1 and len(winners) < 2: self.setactivity( _ba.newactivity(DrawScoreScreenActivity, {'results': results})) else: # Award different point amounts based on number of players. point_awards = self.get_ffa_point_awards() for i, winner in enumerate(winners): for team in winner.teams: points = (point_awards[i] if i in point_awards else 0) team.customdata['previous_score'] = ( team.customdata['score']) team.customdata['score'] += points series_winners = [ team for team in self.sessionteams if team.customdata['score'] >= self._ffa_series_length ] series_winners.sort( reverse=True, key=lambda t: asserttype(t.customdata['score'], int)) if (len(series_winners) == 1 or (len(series_winners) > 1 and series_winners[0].customdata['score'] != series_winners[1].customdata['score'])): self.setactivity( _ba.newactivity(TeamSeriesVictoryScoreScreenActivity, {'winner': series_winners[0]})) else: self.setactivity( _ba.newactivity(FreeForAllVictoryScoreScreenActivity, {'results': results}))
def winnergroups(self) -> list[WinnerGroup]: """Get an ordered list of winner groups.""" if not self._game_set: raise RuntimeError("Can't get winners until game is set.") # Group by best scoring teams. winners: dict[int, list[ba.SessionTeam]] = {} scores = [ score for score in self._scores.values() if score[0]() is not None and score[1] is not None ] for score in scores: assert score[1] is not None sval = winners.setdefault(score[1], []) team = score[0]() assert team is not None sval.append(team) results: list[tuple[Optional[int], list[ba.SessionTeam]]] = list(winners.items()) results.sort(reverse=not self._lower_is_better, key=lambda x: asserttype(x[0], int)) # Also group the 'None' scores. none_sessionteams: list[ba.SessionTeam] = [] for score in self._scores.values(): scoreteam = score[0]() if scoreteam is not None and score[1] is None: none_sessionteams.append(scoreteam) # Add the Nones to the list (either as winners or losers # depending on the rules). if none_sessionteams: nones: list[tuple[Optional[int], list[ba.SessionTeam]]] = [ (None, none_sessionteams) ] if self._none_is_winner: results = nones + results else: results = results + nones return [WinnerGroup(score, team) for score, team in results]
def _show_standard_scores_to_beat_ui(self, scores: List[Dict[str, Any]]) -> None: from efro.util import asserttype from ba._gameutils import timestring, animate from ba._nodeactor import NodeActor from ba._enums import TimeFormat display_type = self.get_score_type() if scores is not None: # Sort by originating date so that the most recent is first. scores.sort(reverse=True, key=lambda s: asserttype(s['time'], int)) # Now make a display for the most recent challenge. for score in scores: if score['type'] == 'score_challenge': tval = (score['player'] + ': ' + timestring( int(score['value']) * 10, timeformat=TimeFormat.MILLISECONDS).evaluate() if display_type == 'time' else str(score['value'])) hattach = 'center' if display_type == 'time' else 'left' halign = 'center' if display_type == 'time' else 'left' pos = (20, -70) if display_type == 'time' else (20, -130) txt = NodeActor( _ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': hattach, 'h_align': halign, 'color': (0.7, 0.4, 1, 1), 'shadow': 0.5, 'flatness': 1.0, 'position': pos, 'scale': 0.6, 'text': tval })).autoretain() assert txt.node is not None animate(txt.node, 'scale', {1.0: 0.0, 1.1: 0.7, 1.2: 0.6}) break
def _refresh(self) -> None: # pylint: disable=too-many-locals from efro.util import asserttype from ba.internal import (PlayerProfilesChangedMessage, get_player_profile_colors, get_player_profile_icon) old_selection = self._selected_profile # Delete old. while self._profile_widgets: self._profile_widgets.pop().delete() self._profiles = ba.app.config.get('Player Profiles', {}) assert self._profiles is not None items = list(self._profiles.items()) items.sort(key=lambda x: asserttype(x[0], str).lower()) index = 0 account_name: Optional[str] if _ba.get_account_state() == 'signed_in': account_name = _ba.get_account_display_string() else: account_name = None widget_to_select = None for p_name, _ in items: if p_name == '__account__' and account_name is None: continue color, _highlight = get_player_profile_colors(p_name) scl = 1.1 tval = (account_name if p_name == '__account__' else get_player_profile_icon(p_name) + p_name) assert isinstance(tval, str) txtw = ba.textwidget( parent=self._columnwidget, position=(0, 32), size=((self._width - 40) / scl, 28), text=ba.Lstr(value=tval), h_align='left', v_align='center', on_select_call=ba.WeakCall(self._select, p_name, index), maxwidth=self._scroll_width * 0.92, corner_scale=scl, color=ba.safecolor(color, 0.4), always_highlight=True, on_activate_call=ba.Call(self._edit_button.activate), selectable=True) if index == 0: ba.widget(edit=txtw, up_widget=self._back_button) ba.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) self._profile_widgets.append(txtw) # Select/show this one if it was previously selected # (but defer till after this loop since our height is # still changing). if p_name == old_selection: widget_to_select = txtw index += 1 if widget_to_select is not None: ba.columnwidget(edit=self._columnwidget, selected_child=widget_to_select, visible_child=widget_to_select) # If there's a team-chooser in existence, tell it the profile-list # has probably changed. session = _ba.get_foreground_host_session() if session is not None: session.handlemessage(PlayerProfilesChangedMessage())
def _refresh(self, select_soundtrack: str = None) -> None: from efro.util import asserttype self._allow_changing_soundtracks = False old_selection = self._selected_soundtrack # If there was no prev selection, look in prefs. if old_selection is None: old_selection = ba.app.config.get('Soundtrack') old_selection_index = self._selected_soundtrack_index # Delete old. while self._soundtrack_widgets: self._soundtrack_widgets.pop().delete() self._soundtracks = ba.app.config.get('Soundtracks', {}) assert self._soundtracks is not None items = list(self._soundtracks.items()) items.sort(key=lambda x: asserttype(x[0], str).lower()) items = [('__default__', None)] + items # default is always first index = 0 for pname, _pval in items: assert pname is not None txtw = ba.textwidget( parent=self._col, size=(self._width - 40, 24), text=self._get_soundtrack_display_name(pname), h_align='left', v_align='center', maxwidth=self._width - 110, always_highlight=True, on_select_call=ba.WeakCall(self._select, pname, index), on_activate_call=self._edit_soundtrack_with_sound, selectable=True) if index == 0: ba.widget(edit=txtw, up_widget=self._back_button) self._soundtrack_widgets.append(txtw) # Select this one if the user requested it if select_soundtrack is not None: if pname == select_soundtrack: ba.columnwidget(edit=self._col, selected_child=txtw, visible_child=txtw) else: # Select this one if it was previously selected. # Go by index if there's one. if old_selection_index is not None: if index == old_selection_index: ba.columnwidget(edit=self._col, selected_child=txtw, visible_child=txtw) else: # Otherwise look by name. if pname == old_selection: ba.columnwidget(edit=self._col, selected_child=txtw, visible_child=txtw) index += 1 # Explicitly run select callback on current one and re-enable # callbacks. # Eww need to run this in a timer so it happens after our select # callbacks. With a small-enough time sometimes it happens before # anyway. Ew. need a way to just schedule a callable i guess. ba.timer(0.1, ba.WeakCall(self._set_allow_changing), timetype=ba.TimeType.REAL)
def _refresh(self, select_playlist: str = None) -> None: from efro.util import asserttype old_selection = self._selected_playlist_name # If there was no prev selection, look in prefs. if old_selection is None: old_selection = ba.app.config.get(self._pvars.config_name + ' Playlist Selection') old_selection_index = self._selected_playlist_index # Delete old. while self._playlist_widgets: self._playlist_widgets.pop().delete() items = list(ba.app.config[self._config_name_full].items()) # Make sure everything is unicode now. items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i for i in items] items.sort(key=lambda x: asserttype(x[0], str).lower()) items = [['__default__', None]] + items # Default is always first. index = 0 for pname, _ in items: assert pname is not None txtw = ba.textwidget( parent=self._columnwidget, size=(self._width - 40, 30), maxwidth=self._width - 110, text=self._get_playlist_display_name(pname), h_align='left', v_align='center', color=(0.6, 0.6, 0.7, 1.0) if pname == '__default__' else (0.85, 0.85, 0.85, 1), always_highlight=True, on_select_call=ba.Call(self._select, pname, index), on_activate_call=ba.Call(self._edit_button.activate), selectable=True) ba.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50) # Hitting up from top widget should jump to 'back' if index == 0: ba.widget(edit=txtw, up_widget=self._back_button) self._playlist_widgets.append(txtw) # Select this one if the user requested it. if select_playlist is not None: if pname == select_playlist: ba.columnwidget(edit=self._columnwidget, selected_child=txtw, visible_child=txtw) else: # Select this one if it was previously selected. # Go by index if there's one. if old_selection_index is not None: if index == old_selection_index: ba.columnwidget(edit=self._columnwidget, selected_child=txtw, visible_child=txtw) else: # Otherwise look by name. if pname == old_selection: ba.columnwidget(edit=self._columnwidget, selected_child=txtw, visible_child=txtw) index += 1
def _on_got_scores_to_beat(self, scores: Optional[List[Dict[str, Any]]]) -> None: # pylint: disable=too-many-locals # pylint: disable=too-many-statements from efro.util import asserttype from bastd.actor.text import Text # Sort by originating date so that the most recent is first. if scores is not None: scores.sort(reverse=True, key=lambda score: asserttype(score['time'], int)) # We only show achievements and challenges for CoopGameActivities. session = self.session assert isinstance(session, ba.CoopSession) gameinstance = session.get_current_game_instance() if isinstance(gameinstance, ba.CoopGameActivity): score_type = gameinstance.get_score_type() if scores is not None: achievement_challenges = [ a for a in scores if a['type'] == 'achievement_challenge' ] score_challenges = [ a for a in scores if a['type'] == 'score_challenge' ] else: achievement_challenges = score_challenges = [] delay = 1.0 vpos = -140.0 spacing = 25 delay_inc = 0.1 def _add_t( text: Union[str, ba.Lstr], h_offs: float = 0.0, scale: float = 1.0, color: Sequence[float] = (1.0, 1.0, 1.0, 0.46) ) -> None: Text(text, scale=scale * 0.76, h_align=Text.HAlign.LEFT, h_attach=Text.HAttach.LEFT, v_attach=Text.VAttach.TOP, transition=Text.Transition.FADE_IN, transition_delay=delay, color=color, position=(60 + h_offs, vpos)).autoretain() if score_challenges: _add_t(ba.Lstr(value='${A}:', subs=[('${A}', ba.Lstr(resource='scoreChallengesText')) ]), scale=1.1) delay += delay_inc vpos -= spacing for chal in score_challenges: _add_t(str(chal['value'] if score_type == 'points' else ba. timestring(int(chal['value']) * 10, timeformat=ba.TimeFormat.MILLISECONDS ).evaluate()) + ' (1 player)', h_offs=30, color=(0.9, 0.7, 1.0, 0.8)) delay += delay_inc vpos -= 0.6 * spacing _add_t(chal['player'], h_offs=40, color=(0.8, 1, 0.8, 0.6), scale=0.8) delay += delay_inc vpos -= 1.2 * spacing vpos -= 0.5 * spacing if achievement_challenges: _add_t(ba.Lstr( value='${A}:', subs=[('${A}', ba.Lstr(resource='achievementChallengesText'))]), scale=1.1) delay += delay_inc vpos -= spacing for chal in achievement_challenges: _add_t(str(chal['value']), h_offs=30, color=(0.9, 0.7, 1.0, 0.8)) delay += delay_inc vpos -= 0.6 * spacing _add_t(chal['player'], h_offs=40, color=(0.8, 1, 0.8, 0.6), scale=0.8) delay += delay_inc vpos -= 1.2 * spacing vpos -= 0.5 * spacing # Now list our remaining achievements for this level. assert self.session.campaign is not None assert isinstance(self.session, ba.CoopSession) levelname = (self.session.campaign.name + ':' + self.session.campaign_level_name) ts_h_offs = 60 if not (ba.app.demo_mode or ba.app.arcade_mode): achievements = [ a for a in ba.app.ach.achievements_for_coop_level(levelname) if not a.complete ] have_achievements = bool(achievements) achievements = [a for a in achievements if not a.complete] vrmode = ba.app.vr_mode if have_achievements: Text(ba.Lstr(resource='achievementsRemainingText'), host_only=True, position=(ts_h_offs - 10, vpos), transition=Text.Transition.FADE_IN, scale=1.1 * 0.76, h_attach=Text.HAttach.LEFT, v_attach=Text.VAttach.TOP, color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1, 1), shadow=1.0, flatness=1.0 if vrmode else 0.6, transition_delay=delay).autoretain() hval = ts_h_offs + 50 vpos -= 35 for ach in achievements: delay += 0.05 ach.create_display(hval, vpos, delay, style='in_game') vpos -= 55 if not achievements: Text(ba.Lstr(resource='noAchievementsRemainingText'), host_only=True, position=(ts_h_offs + 15, vpos + 10), transition=Text.Transition.FADE_IN, scale=0.7, h_attach=Text.HAttach.LEFT, v_attach=Text.VAttach.TOP, color=(1, 1, 1, 0.5), transition_delay=delay + 0.5).autoretain()
def _refresh(self) -> None: # FIXME: Should tidy this up. # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-nested-blocks from efro.util import asserttype from ba.internal import (get_map_class, get_default_free_for_all_playlist, get_default_teams_playlist, filter_playlist) if not self._root_widget: return if self._subcontainer is not None: self._save_state() self._subcontainer.delete() # Make sure config exists. if self._config_name_full not in ba.app.config: ba.app.config[self._config_name_full] = {} items = list(ba.app.config[self._config_name_full].items()) # Make sure everything is unicode. items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i for i in items] items.sort(key=lambda x2: asserttype(x2[0], str).lower()) items = [['__default__', None]] + items # default is always first count = len(items) columns = 3 rows = int(math.ceil(float(count) / columns)) button_width = 230 button_height = 230 button_buffer_h = -3 button_buffer_v = 0 self._sub_width = self._scroll_width self._sub_height = 40 + rows * (button_height + 2 * button_buffer_v) + 90 assert self._sub_width is not None assert self._sub_height is not None self._subcontainer = ba.containerwidget(parent=self._scrollwidget, size=(self._sub_width, self._sub_height), background=False) children = self._subcontainer.get_children() for child in children: child.delete() ba.textwidget(parent=self._subcontainer, text=ba.Lstr(resource='playlistsText'), position=(40, self._sub_height - 26), size=(0, 0), scale=1.0, maxwidth=400, color=ba.app.ui.title_color, h_align='left', v_align='center') index = 0 appconfig = ba.app.config model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') h_offs = 225 if count == 1 else 115 if count == 2 else 0 h_offs_bottom = 0 uiscale = ba.app.ui.uiscale for y in range(rows): for x in range(columns): name = items[index][0] assert name is not None pos = (x * (button_width + 2 * button_buffer_h) + button_buffer_h + 8 + h_offs, self._sub_height - 47 - (y + 1) * (button_height + 2 * button_buffer_v)) btn = ba.buttonwidget(parent=self._subcontainer, button_type='square', size=(button_width, button_height), autoselect=True, label='', position=pos) if (x == 0 and ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL): ba.widget( edit=btn, left_widget=_ba.get_special_widget('back_button')) if (x == columns - 1 and ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL): ba.widget( edit=btn, right_widget=_ba.get_special_widget('party_button')) ba.buttonwidget( edit=btn, on_activate_call=ba.Call(self._on_playlist_press, btn, name), on_select_call=ba.Call(self._on_playlist_select, name)) ba.widget(edit=btn, show_buffer_top=50, show_buffer_bottom=50) if self._selected_playlist == name: ba.containerwidget(edit=self._subcontainer, selected_child=btn, visible_child=btn) if self._back_button is not None: if y == 0: ba.widget(edit=btn, up_widget=self._back_button) if x == 0: ba.widget(edit=btn, left_widget=self._back_button) print_name: Optional[Union[str, ba.Lstr]] if name == '__default__': print_name = self._pvars.default_list_name else: print_name = name ba.textwidget(parent=self._subcontainer, text=print_name, position=(pos[0] + button_width * 0.5, pos[1] + button_height * 0.79), size=(0, 0), scale=button_width * 0.003, maxwidth=button_width * 0.7, draw_controller=btn, h_align='center', v_align='center') # Poke into this playlist and see if we can display some of # its maps. map_images = [] try: map_textures = [] map_texture_entries = [] if name == '__default__': if self._sessiontype is ba.FreeForAllSession: playlist = (get_default_free_for_all_playlist()) elif self._sessiontype is ba.DualTeamSession: playlist = get_default_teams_playlist() else: raise Exception('unrecognized session-type: ' + str(self._sessiontype)) else: if name not in appconfig[self._pvars.config_name + ' Playlists']: print( 'NOT FOUND ERR', appconfig[self._pvars.config_name + ' Playlists']) playlist = appconfig[self._pvars.config_name + ' Playlists'][name] playlist = filter_playlist(playlist, self._sessiontype, remove_unowned=False, mark_unowned=True) for entry in playlist: mapname = entry['settings']['map'] maptype: Optional[Type[ba.Map]] try: maptype = get_map_class(mapname) except ba.NotFoundError: maptype = None if maptype is not None: tex_name = maptype.get_preview_texture_name() if tex_name is not None: map_textures.append(tex_name) map_texture_entries.append(entry) if len(map_textures) >= 6: break if len(map_textures) > 4: img_rows = 3 img_columns = 2 scl = 0.33 h_offs_img = 30 v_offs_img = 126 elif len(map_textures) > 2: img_rows = 2 img_columns = 2 scl = 0.35 h_offs_img = 24 v_offs_img = 110 elif len(map_textures) > 1: img_rows = 2 img_columns = 1 scl = 0.5 h_offs_img = 47 v_offs_img = 105 else: img_rows = 1 img_columns = 1 scl = 0.75 h_offs_img = 20 v_offs_img = 65 v = None for row in range(img_rows): for col in range(img_columns): tex_index = row * img_columns + col if tex_index < len(map_textures): entry = map_texture_entries[tex_index] owned = not (('is_unowned_map' in entry and entry['is_unowned_map']) or ('is_unowned_game' in entry and entry['is_unowned_game'])) tex_name = map_textures[tex_index] h = pos[0] + h_offs_img + scl * 250 * col v = pos[1] + v_offs_img - scl * 130 * row map_images.append( ba.imagewidget( parent=self._subcontainer, size=(scl * 250.0, scl * 125.0), position=(h, v), texture=ba.gettexture(tex_name), opacity=1.0 if owned else 0.25, draw_controller=btn, model_opaque=model_opaque, model_transparent=model_transparent, mask_texture=mask_tex)) if not owned: ba.imagewidget( parent=self._subcontainer, size=(scl * 100.0, scl * 100.0), position=(h + scl * 75, v + scl * 10), texture=ba.gettexture('lock'), draw_controller=btn) if v is not None: v -= scl * 130.0 except Exception: ba.print_exception('Error listing playlist maps.') if not map_images: ba.textwidget(parent=self._subcontainer, text='???', scale=1.5, size=(0, 0), color=(1, 1, 1, 0.5), h_align='center', v_align='center', draw_controller=btn, position=(pos[0] + button_width * 0.5, pos[1] + button_height * 0.5)) index += 1 if index >= count: break if index >= count: break self._customize_button = btn = ba.buttonwidget( parent=self._subcontainer, size=(100, 30), position=(34 + h_offs_bottom, 50), text_scale=0.6, label=ba.Lstr(resource='customizeText'), on_activate_call=self._on_customize_press, color=(0.54, 0.52, 0.67), textcolor=(0.7, 0.65, 0.7), autoselect=True) ba.widget(edit=btn, show_buffer_top=22, show_buffer_bottom=28) self._restore_state()