Exemple #1
0
    def spawn_player_spaz(self,
                          player: PlayerType,
                          position: Sequence[float] = (0, 0, 0),
                          angle: float = None) -> PlayerSpaz:
        """Create and wire up a ba.PlayerSpaz for the provided ba.Player."""
        # pylint: disable=too-many-locals
        # pylint: disable=cyclic-import
        from ba import _math
        from ba._gameutils import animate
        from ba._coopsession import CoopSession
        from bastd.actor.playerspaz import PlayerSpaz
        name = player.getname()
        color = player.color
        highlight = player.highlight

        light_color = _math.normalized_color(color)
        display_color = _ba.safecolor(color, target_intensity=0.75)
        spaz = PlayerSpaz(color=color,
                          highlight=highlight,
                          character=player.character,
                          player=player)

        player.actor = spaz
        assert spaz.node

        # If this is co-op and we're on Courtyard or Runaround, add the
        # material that allows us to collide with the player-walls.
        # FIXME: Need to generalize this.
        if isinstance(self.session, CoopSession) and self.map.getname() in [
                'Courtyard', 'Tower D'
        ]:
            mat = self.map.preloaddata['collide_with_wall_material']
            assert isinstance(spaz.node.materials, tuple)
            assert isinstance(spaz.node.roller_materials, tuple)
            spaz.node.materials += (mat, )
            spaz.node.roller_materials += (mat, )

        spaz.node.name = name
        spaz.node.name_color = display_color
        spaz.connect_controls_to_player()

        # Move to the stand position and add a flash of light.
        spaz.handlemessage(
            StandMessage(
                position,
                angle if angle is not None else random.uniform(0, 360)))
        _ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
        light = _ba.newnode('light', attrs={'color': light_color})
        spaz.node.connectattr('position', light, 'position')
        animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0})
        _ba.timer(0.5, light.delete)
        return spaz
def show_damage_count(damage: str, position: Sequence[float],
                      direction: Sequence[float]) -> None:
    """Pop up a damage count at a position in space.

    Category: Gameplay Functions
    """
    lifespan = 1.0
    app = _ba.app

    # FIXME: Should never vary game elements based on local config.
    #  (connected clients may have differing configs so they won't
    #  get the intended results).
    do_big = app.ui.uiscale is UIScale.SMALL or app.vr_mode
    txtnode = _ba.newnode('text',
                          attrs={
                              'text': damage,
                              'in_world': True,
                              'h_align': 'center',
                              'flatness': 1.0,
                              'shadow': 1.0 if do_big else 0.7,
                              'color': (1, 0.25, 0.25, 1),
                              'scale': 0.015 if do_big else 0.01
                          })
    # Translate upward.
    tcombine = _ba.newnode('combine', owner=txtnode, attrs={'size': 3})
    tcombine.connectattr('output', txtnode, 'position')
    v_vals = []
    pval = 0.0
    vval = 0.07
    count = 6
    for i in range(count):
        v_vals.append((float(i) / count, pval))
        pval += vval
        vval *= 0.5
    p_start = position[0]
    p_dir = direction[0]
    animate(tcombine, 'input0',
            {i[0] * lifespan: p_start + p_dir * i[1]
             for i in v_vals})
    p_start = position[1]
    p_dir = direction[1]
    animate(tcombine, 'input1',
            {i[0] * lifespan: p_start + p_dir * i[1]
             for i in v_vals})
    p_start = position[2]
    p_dir = direction[2]
    animate(tcombine, 'input2',
            {i[0] * lifespan: p_start + p_dir * i[1]
             for i in v_vals})
    animate(txtnode, 'opacity', {0.7 * lifespan: 1.0, lifespan: 0.0})
    _ba.timer(lifespan, txtnode.delete)
Exemple #3
0
def print_corrupt_file_error() -> None:
    """Print an error if a corrupt file is found."""
    from ba._general import Call
    from ba._generated.enums import TimeType
    _ba.timer(2.0,
              lambda: _ba.screenmessage(
                  _ba.app.lang.get_resource('internal.corruptFileText').
                  replace('${EMAIL}', '*****@*****.**'),
                  color=(1, 0, 0),
              ),
              timetype=TimeType.REAL)
    _ba.timer(2.0,
              Call(_ba.playsound, _ba.getsound('error')),
              timetype=TimeType.REAL)
def snowfall(self) -> None:
    if hasattr(self, '_snowfall'):
        delattr(self, '_snowfall')
    if getattr(self, 'map', None):
        bounds = self.map.get_def_bound_box('map_bounds')

        def emits() -> None:
            for i in range(int(bounds[3] * bounds[5])):
                def _emit() -> None:
                    _ba.emitfx(
                        position=(
                            random.uniform(bounds[0], bounds[3]),
                            random.uniform(bounds[4] * 1.15, bounds[4] * 1.45),
                            random.uniform(bounds[2], bounds[5])
                        ),
                        velocity=(0, 0, 0),
                        scale=random.uniform(0.5, 0.8),
                        count=random.randint(7, 18),
                        spread=random.uniform(0.05, 0.1),
                        chunk_type='spark'
                    )
                _ba.timer(random.uniform(0.02, 0.05) * (i + 1),
                          _emit)

        setattr(self, '_snowfall',
                _ba.timer(0.5, emits, repeat=True))
Exemple #5
0
    def _test(self) -> None:
        """For testing achievement animations."""
        from ba._generated.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 continue_or_end_game(self) -> None:
        """If continues are allowed, prompts the player to purchase a continue
        and calls either end_game or continue_game depending on the result"""
        # pylint: disable=too-many-nested-blocks
        # pylint: disable=cyclic-import
        from bastd.ui.continues import ContinuesWindow
        from ba._coopsession import CoopSession
        from ba._enums import TimeType

        try:
            if _ba.get_account_misc_read_val('enableContinues', False):
                session = self.session

                # We only support continuing in non-tournament games.
                tournament_id = session.tournament_id
                if tournament_id is None:

                    # We currently only support continuing in sequential
                    # co-op campaigns.
                    if isinstance(session, CoopSession):
                        assert session.campaign is not None
                        if session.campaign.sequential:
                            gnode = self.globalsnode

                            # Only attempt this if we're not currently paused
                            # and there appears to be no UI.
                            if (not gnode.paused
                                    and not _ba.app.ui.has_main_menu_window()):
                                self._is_waiting_for_continue = True
                                with _ba.Context('ui'):
                                    _ba.timer(
                                        0.5,
                                        lambda: ContinuesWindow(
                                            self,
                                            self._continue_cost,
                                            continue_call=WeakCall(
                                                self._continue_choice, True),
                                            cancel_call=WeakCall(
                                                self._continue_choice, False)),
                                        timetype=TimeType.REAL)
                                return

        except Exception:
            print_exception('Error handling continues.')

        self.end_game()
 def emits() -> None:
     for i in range(int(bounds[3] * bounds[5])):
         def _emit() -> None:
             _ba.emitfx(
                 position=(
                     random.uniform(bounds[0], bounds[3]),
                     random.uniform(bounds[4] * 1.15, bounds[4] * 1.45),
                     random.uniform(bounds[2], bounds[5])
                 ),
                 velocity=(0, 0, 0),
                 scale=random.uniform(0.5, 0.8),
                 count=random.randint(7, 18),
                 spread=random.uniform(0.05, 0.1),
                 chunk_type='spark'
             )
         _ba.timer(random.uniform(0.02, 0.05) * (i + 1),
                   _emit)
Exemple #8
0
    def announce_game_results(self,
                              activity: ba.GameActivity,
                              results: ba.GameResults,
                              delay: float,
                              announce_winning_team: bool = True) -> None:
        """Show basic game result at the end of a game.

        (before transitioning to a score screen).
        This will include a zoom-text of 'BLUE WINS'
        or whatnot, along with a possible audio
        announcement of the same.
        """
        # pylint: disable=cyclic-import
        # pylint: disable=too-many-locals
        from ba._math import normalized_color
        from ba._general import Call
        from ba._gameutils import cameraflash
        from ba._language import Lstr
        from ba._freeforallsession import FreeForAllSession
        from ba._messages import CelebrateMessage
        _ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell')))

        if announce_winning_team:
            winning_sessionteam = results.winning_sessionteam
            if winning_sessionteam is not None:
                # Have all players celebrate.
                celebrate_msg = CelebrateMessage(duration=10.0)
                assert winning_sessionteam.activityteam is not None
                for player in winning_sessionteam.activityteam.players:
                    if player.actor:
                        player.actor.handlemessage(celebrate_msg)
                cameraflash()

                # Some languages say "FOO WINS" different for teams vs players.
                if isinstance(self, FreeForAllSession):
                    wins_resource = 'winsPlayerText'
                else:
                    wins_resource = 'winsTeamText'
                wins_text = Lstr(resource=wins_resource,
                                 subs=[('${NAME}', winning_sessionteam.name)])
                activity.show_zoom_message(
                    wins_text,
                    scale=0.85,
                    color=normalized_color(winning_sessionteam.color),
                )
Exemple #9
0
 def _execute_shutdown(self) -> None:
     from ba._lang import Lstr
     if self._executing_shutdown:
         return
     self._executing_shutdown = True
     timestrval = time.strftime('%c')
     if self._shutdown_reason is ShutdownReason.RESTARTING:
         _ba.screenmessage(Lstr(resource='internal.serverRestartingText'),
                           color=(1, 0.5, 0.0))
         print(f'{Clr.SBLU}Exiting for server-restart'
               f' at {timestrval}{Clr.RST}')
     else:
         _ba.screenmessage(Lstr(resource='internal.serverShuttingDownText'),
                           color=(1, 0.5, 0.0))
         print(f'{Clr.SBLU}Exiting for server-shutdown'
               f' at {timestrval}{Clr.RST}')
     with _ba.Context('ui'):
         _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
    def _handle_server_restarts(self) -> bool:
        """Handle automatic restarts/shutdowns in server mode.

        Returns True if an action was taken; otherwise default action
        should occur (starting next round, etc).
        """
        # pylint: disable=cyclic-import

        # FIXME: Move server stuff to its own module.
        if self._allow_server_restart and _ba.app.server_config_dirty:
            from ba import _server
            from ba._lang import Lstr
            from ba._general import Call
            from ba._enums import TimeType
            if _ba.app.server_config.get('quit', False):
                if not self._kicked_off_server_shutdown:
                    if _ba.app.server_config.get(
                            'quit_reason') == 'restarting':
                        # FIXME: Should add a server-screen-message call
                        #  or something.
                        _ba.chat_message(
                            Lstr(resource='internal.serverRestartingText').
                            evaluate())
                        print(('Exiting for server-restart at ' +
                               time.strftime('%c')))
                    else:
                        print(('Exiting for server-shutdown at ' +
                               time.strftime('%c')))
                    with _ba.Context('ui'):
                        _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
                    self._kicked_off_server_shutdown = True
                    return True
            else:
                if not self._kicked_off_server_restart:
                    print(('Running updated server config at ' +
                           time.strftime('%c')))
                    with _ba.Context('ui'):
                        _ba.timer(1.0,
                                  Call(_ba.pushcall,
                                       _server.launch_server_session),
                                  timetype=TimeType.REAL)
                    self._kicked_off_server_restart = True
                    return True
        return False
Exemple #11
0
    def end(  # type: ignore
            self,
            results: Any = None,
            announce_winning_team: bool = True,
            announce_delay: float = 0.1,
            force: bool = False) -> None:
        """
        End the game and announce the single winning team
        unless 'announce_winning_team' is False.
        (for results without a single most-important winner).
        """
        # pylint: disable=arguments-differ
        from ba._coopsession import CoopSession
        from ba._multiteamsession import MultiTeamSession
        from ba._general import Call

        # Announce win (but only for the first finish() call)
        # (also don't announce in co-op sessions; we leave that up to them).
        session = self.session
        if not isinstance(session, CoopSession):
            do_announce = not self.has_ended()
            super().end(results, delay=2.0 + announce_delay, force=force)

            # Need to do this *after* end end call so that results is valid.
            assert isinstance(results, TeamGameResults)
            if do_announce and isinstance(session, MultiTeamSession):
                session.announce_game_results(
                    self,
                    results,
                    delay=announce_delay,
                    announce_winning_team=announce_winning_team)

        # For co-op we just pass this up the chain with a delay added
        # (in most cases). Team games expect a delay for the announce
        # portion in teams/ffa mode so this keeps it consistent.
        else:
            # don't want delay on restarts..
            if (isinstance(results, dict) and 'outcome' in results
                    and results['outcome'] == 'restart'):
                delay = 0.0
            else:
                delay = 2.0
                _ba.timer(0.1, Call(_ba.playsound, _ba.getsound('boxingBell')))
            super().end(results, delay=delay, force=force)
    def on_begin(self) -> None:
        super().on_begin()

        # Show achievements remaining.
        if not _ba.app.kiosk_mode:
            _ba.timer(3.8, WeakCall(self._show_remaining_achievements))

        # Preload achievement images in case we get some.
        _ba.timer(2.0, WeakCall(self._preload_achievements))

        # Let's ask the server for a 'time-to-beat' value.
        levelname = self._get_coop_level_name()
        campaign = self.session.campaign
        assert campaign is not None
        config_str = (str(len(self.players)) + 'p' + campaign.getlevel(
            self.settings_raw['name']).get_score_version_string().replace(
                ' ', '_'))
        _ba.get_scores_to_beat(levelname, config_str,
                               WeakCall(self._on_got_scores_to_beat))
def verify_object_death(obj: object) -> None:
    """Warn if an object does not get freed within a short period.

    Category: General Utility Functions

    This can be handy to detect and prevent memory/resource leaks.
    """
    try:
        ref = weakref.ref(obj)
    except Exception:
        print_exception('Unable to create weak-ref in verify_object_death')

    # Use a slight range for our checks so they don't all land at once
    # if we queue a lot of them.
    delay = random.uniform(2.0, 5.5)
    with _ba.Context('ui'):
        _ba.timer(delay,
                  lambda: _verify_object_death(ref),
                  timetype=TimeType.REAL)
Exemple #14
0
    def do_remove_in_game_ads_message(self) -> None:
        """(internal)"""
        from ba._lang import Lstr
        from ba._enums import TimeType

        # Print this message once every 10 minutes at most.
        tval = _ba.time(TimeType.REAL)
        if (self.last_in_game_ad_remove_message_show_time is None or
            (tval - self.last_in_game_ad_remove_message_show_time > 60 * 10)):
            self.last_in_game_ad_remove_message_show_time = tval
            with _ba.Context('ui'):
                _ba.timer(
                    1.0,
                    lambda: _ba.screenmessage(Lstr(
                        resource='removeInGameAdsText',
                        subs=[('${PRO}',
                               Lstr(resource='store.bombSquadProNameText')),
                              ('${APP_NAME}', Lstr(resource='titleText'))]),
                                              color=(1, 1, 0)),
                    timetype=TimeType.REAL)
    def on_begin(self) -> None:
        from ba._analytics import game_begin_analytics
        super().on_begin()

        game_begin_analytics()

        # We don't do this in on_transition_in because it may depend on
        # players/teams which aren't available until now.
        _ba.timer(0.001, self._show_scoreboard_info)
        _ba.timer(1.0, self._show_info)
        _ba.timer(2.5, self._show_tip)

        # Store some basic info about players present at start time.
        self.initialplayerinfos = [
            PlayerInfo(name=p.getname(full=True), character=p.character)
            for p in self.players
        ]

        # Sort this by name so high score lists/etc will be consistent
        # regardless of player join order.
        self.initialplayerinfos.sort(key=lambda x: x.name)

        # If this is a tournament, query info about it such as how much
        # time is left.
        tournament_id = self.session.tournament_id
        if tournament_id is not None:
            _ba.tournament_query(
                args={
                    'tournamentIDs': [tournament_id],
                    'source': 'in-game time remaining query'
                },
                callback=WeakCall(self._on_tournament_query_response),
            )
Exemple #16
0
    def set_main_menu_window(self, window: ba.Widget) -> None:
        """Set the current 'main' window, replacing any existing."""
        existing = self._main_menu_window
        from ba._generated.enums import TimeType
        from inspect import currentframe, getframeinfo

        # Let's grab the location where we were called from to report
        # if we have to force-kill the existing window (which normally
        # should not happen).
        frameline = None
        try:
            frame = currentframe()
            if frame is not None:
                frame = frame.f_back
            if frame is not None:
                frameinfo = getframeinfo(frame)
                frameline = f'{frameinfo.filename} {frameinfo.lineno}'
        except Exception:
            from ba._error import print_exception
            print_exception('Error calcing line for set_main_menu_window')

        # With our legacy main-menu system, the caller is responsible for
        # clearing out the old main menu window when assigning the new.
        # However there are corner cases where that doesn't happen and we get
        # old windows stuck under the new main one. So let's guard against
        # that. However, we can't simply delete the existing main window when
        # a new one is assigned because the user may transition the old out
        # *after* the assignment. Sigh. So, as a happy medium, let's check in
        # on the old after a short bit of time and kill it if its still alive.
        # That will be a bit ugly on screen but at least should un-break
        # things.
        def _delay_kill() -> None:
            import time
            if existing:
                print(f'Killing old main_menu_window'
                      f' when called at: {frameline} t={time.time():.3f}')
                existing.delete()

        _ba.timer(1.0, _delay_kill, timetype=TimeType.REAL)
        self._main_menu_window = window
Exemple #17
0
 def _execute_shutdown(self) -> None:
     from ba._lang import Lstr
     if self._executing_shutdown:
         return
     self._executing_shutdown = True
     timestrval = time.strftime('%c')
     if self._shutdown_reason is ShutdownReason.RESTARTING:
         # FIXME: Should add a server-screen-message call.
         # (so we could send this an an Lstr)
         _ba.chat_message(
             Lstr(resource='internal.serverRestartingText').evaluate())
         print(f'{Clr.SBLU}Exiting for server-restart'
               f' at {timestrval}{Clr.RST}')
     else:
         # FIXME: Should add a server-screen-message call.
         # (so we could send this an an Lstr)
         print(f'{Clr.SBLU}Exiting for server-shutdown'
               f' at {timestrval}{Clr.RST}')
         _ba.chat_message(
             Lstr(resource='internal.serverShuttingDownText').evaluate())
     with _ba.Context('ui'):
         _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
Exemple #18
0
    def handle_deep_link(self, url: str) -> None:
        """Handle a deep link URL."""
        from ba._lang import Lstr
        from ba._enums import TimeType
        appname = _ba.appname()
        if url.startswith(f'{appname}://code/'):
            code = url.replace(f'{appname}://code/', '')

            # If we're not signed in, queue up the code to run the next time we
            # are and issue a warning if we haven't signed in within the next
            # few seconds.
            if _ba.get_account_state() != 'signed_in':

                def check_pending_codes() -> None:
                    """(internal)"""

                    # If we're still not signed in and have pending codes,
                    # inform the user that they need to sign in to use them.
                    if _ba.app.pending_promo_codes:
                        _ba.screenmessage(
                            Lstr(resource='signInForPromoCodeText'),
                            color=(1, 0, 0))
                        _ba.playsound(_ba.getsound('error'))

                _ba.app.pending_promo_codes.append(code)
                _ba.timer(6.0, check_pending_codes, timetype=TimeType.REAL)
                return
            _ba.screenmessage(Lstr(resource='submittingPromoCodeText'),
                              color=(0, 1, 0))
            _ba.add_transaction({
                'type': 'PROMO_CODE',
                'expire_time': time.time() + 5,
                'code': code
            })
            _ba.run_transactions()
        else:
            _ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
            _ba.playsound(_ba.getsound('error'))
Exemple #19
0
    def on_app_launch(self) -> None:
        """Runs after the app finishes bootstrapping.

        (internal)"""
        # pylint: disable=too-many-locals
        # pylint: disable=cyclic-import
        from ba import _apputils
        from ba import _appconfig
        from ba import _achievement
        from ba import _map
        from ba import _campaign
        from bastd import appdelegate
        from bastd import maps as stdmaps
        from bastd.actor import spazappearance
        from ba._enums import TimeType

        cfg = self.config

        self.delegate = appdelegate.AppDelegate()

        self.ui.on_app_launch()

        spazappearance.register_appearances()
        _campaign.init_campaigns()

        # FIXME: This should not be hard-coded.
        for maptype in [
                stdmaps.HockeyStadium, stdmaps.FootballStadium,
                stdmaps.Bridgit, stdmaps.BigG, stdmaps.Roundabout,
                stdmaps.MonkeyFace, stdmaps.ZigZag, stdmaps.ThePad,
                stdmaps.DoomShroom, stdmaps.LakeFrigid, stdmaps.TipTop,
                stdmaps.CragCastle, stdmaps.TowerD, stdmaps.HappyThoughts,
                stdmaps.StepRightUp, stdmaps.Courtyard, stdmaps.Rampage
        ]:
            _map.register_map(maptype)

        # Non-test, non-debug builds should generally be blessed; warn if not.
        # (so I don't accidentally release a build that can't play tourneys)
        if (not self.debug_build and not self.test_build
                and not _ba.is_blessed()):
            _ba.screenmessage('WARNING: NON-BLESSED BUILD', color=(1, 0, 0))

        # If there's a leftover log file, attempt to upload it to the
        # master-server and/or get rid of it.
        _apputils.handle_leftover_log_file()

        # Only do this stuff if our config file is healthy so we don't
        # overwrite a broken one or whatnot and wipe out data.
        if not self.config_file_healthy:
            if self.platform in ('mac', 'linux', 'windows'):
                from bastd.ui import configerror
                configerror.ConfigErrorWindow()
                return

            # For now on other systems we just overwrite the bum config.
            # At this point settings are already set; lets just commit them
            # to disk.
            _appconfig.commit_app_config(force=True)

        self.music.on_app_launch()

        launch_count = cfg.get('launchCount', 0)
        launch_count += 1

        # So we know how many times we've run the game at various
        # version milestones.
        for key in ('lc14173', 'lc14292'):
            cfg.setdefault(key, launch_count)

        # Debugging - make note if we're using the local test server so we
        # don't accidentally leave it on in a release.
        # FIXME - should move this to the native layer.
        server_addr = _ba.get_master_server_address()
        if 'localhost' in server_addr:
            _ba.timer(2.0,
                      lambda: _ba.screenmessage(
                          'Note: using local server',
                          (1, 1, 0),
                          log=True,
                      ),
                      timetype=TimeType.REAL)
        elif 'test' in server_addr:
            _ba.timer(2.0,
                      lambda: _ba.screenmessage(
                          'Note: using test server-module',
                          (1, 1, 0),
                          log=True,
                      ),
                      timetype=TimeType.REAL)

        cfg['launchCount'] = launch_count
        cfg.commit()

        # Run a test in a few seconds to see if we should pop up an existing
        # pending special offer.
        def check_special_offer() -> None:
            from bastd.ui.specialoffer import show_offer
            config = self.config
            if ('pendingSpecialOffer' in config and _ba.get_public_login_id()
                    == config['pendingSpecialOffer']['a']):
                self.special_offer = config['pendingSpecialOffer']['o']
                show_offer()

        if not self.headless_mode:
            _ba.timer(3.0, check_special_offer, timetype=TimeType.REAL)

        self.meta.on_app_launch()
        self.accounts.on_app_launch()
        self.plugins.on_app_launch()

        self.state = self.State.RUNNING
Exemple #20
0
    def on_player_leave(self, sessionplayer: ba.SessionPlayer) -> None:
        from ba._general import WeakCall
        super().on_player_leave(sessionplayer)

        _ba.timer(2.0, WeakCall(self._handle_empty_activity))
Exemple #21
0
    def set_activity(self, activity: ba.Activity) -> None:
        """Assign a new current ba.Activity for the session.

        Note that this will not change the current context to the new
        Activity's. Code must be run in the new activity's methods
        (on_transition_in, etc) to get it. (so you can't do
        session.set_activity(foo) and then ba.newnode() to add a node to foo)
        """
        # pylint: disable=too-many-statements
        # pylint: disable=too-many-branches
        from ba import _error
        from ba._gameutils import sharedobj
        from ba._enums import TimeType

        # Sanity test: make sure this doesn't get called recursively.
        if self._in_set_activity:
            raise Exception(
                'Session.set_activity() cannot be called recursively.')

        if activity.session is not _ba.getsession():
            raise Exception("Provided Activity's Session is not current.")

        # Quietly ignore this if the whole session is going down.
        if self._ending:
            return

        if activity is self._activity_retained:
            _error.print_error('activity set to already-current activity')
            return

        if self._next_activity is not None:
            raise Exception('Activity switch already in progress (to ' +
                            str(self._next_activity) + ')')

        self._in_set_activity = True

        prev_activity = self._activity_retained

        if prev_activity is not None:
            with _ba.Context(prev_activity):
                gprev = sharedobj('globals')
        else:
            gprev = None

        with _ba.Context(activity):

            # Now that it's going to be front and center,
            # set some global values based on what the activity wants.
            glb = sharedobj('globals')
            glb.use_fixed_vr_overlay = activity.use_fixed_vr_overlay
            glb.allow_kick_idle_players = activity.allow_kick_idle_players
            if activity.inherits_slow_motion and gprev is not None:
                glb.slow_motion = gprev.slow_motion
            else:
                glb.slow_motion = activity.slow_motion
            if activity.inherits_music and gprev is not None:
                glb.music_continuous = True  # Prevent restarting same music.
                glb.music = gprev.music
                glb.music_count += 1
            if activity.inherits_camera_vr_offset and gprev is not None:
                glb.vr_camera_offset = gprev.vr_camera_offset
            if activity.inherits_vr_overlay_center and gprev is not None:
                glb.vr_overlay_center = gprev.vr_overlay_center
                glb.vr_overlay_center_enabled = gprev.vr_overlay_center_enabled

            # If they want to inherit tint from the previous activity.
            if activity.inherits_tint and gprev is not None:
                glb.tint = gprev.tint
                glb.vignette_outer = gprev.vignette_outer
                glb.vignette_inner = gprev.vignette_inner

            # Let the activity do its thing.
            activity.start_transition_in()

        self._next_activity = activity

        # If we have a current activity, tell it it's transitioning out;
        # the next one will become current once this one dies.
        if prev_activity is not None:
            # pylint: disable=protected-access
            prev_activity._transitioning_out = True
            # pylint: enable=protected-access

            # Activity will be None until the next one begins.
            with _ba.Context(prev_activity):
                prev_activity.on_transition_out()

            # Setting this to None should free up the old activity to die,
            # which will call begin_next_activity.
            # We can still access our old activity through
            # self._activity_weak() to keep it up to date on player
            # joins/departures/etc until it dies.
            self._activity_retained = None

        # There's no existing activity; lets just go ahead with the begin call.
        else:
            self.begin_next_activity()

        # Tell the C layer that this new activity is now 'foregrounded'.
        # This means that its globals node controls global stuff and stuff
        # like console operations, keyboard shortcuts, etc will run in it.
        # pylint: disable=protected-access
        # noinspection PyProtectedMember
        activity._activity_data.make_foreground()
        # pylint: enable=protected-access

        # We want to call _destroy() for the previous activity once it should
        # tear itself down, clear out any self-refs, etc.  If the new activity
        # has a transition-time, set it up to be called after that passes;
        # otherwise call it immediately. After this call the activity should
        # have no refs left to it and should die (which will trigger the next
        # activity to run).
        if prev_activity is not None:
            if activity.transition_time > 0.0:
                # FIXME: We should tweak the activity to not allow
                #  node-creation/etc when we call _destroy (or after).
                with _ba.Context('ui'):
                    # pylint: disable=protected-access
                    # noinspection PyProtectedMember
                    _ba.timer(activity.transition_time,
                              prev_activity._destroy,
                              timetype=TimeType.REAL)

            # Just run immediately.
            else:
                # noinspection PyProtectedMember
                prev_activity._destroy()  # pylint: disable=protected-access
        self._in_set_activity = False
Exemple #22
0
    def submit_kill(self, showpoints: bool = True) -> None:
        """Submit a kill for this player entry."""
        # FIXME Clean this up.
        # pylint: disable=too-many-statements
        from ba._lang import Lstr
        from ba._general import Call
        self._multi_kill_count += 1
        stats = self._stats()
        assert stats
        if self._multi_kill_count == 1:
            score = 0
            name = None
            delay = 0.0
            color = (0.0, 0.0, 0.0, 1.0)
            scale = 1.0
            sound = None
        elif self._multi_kill_count == 2:
            score = 20
            name = Lstr(resource='twoKillText')
            color = (0.1, 1.0, 0.0, 1)
            scale = 1.0
            delay = 0.0
            sound = stats.orchestrahitsound1
        elif self._multi_kill_count == 3:
            score = 40
            name = Lstr(resource='threeKillText')
            color = (1.0, 0.7, 0.0, 1)
            scale = 1.1
            delay = 0.3
            sound = stats.orchestrahitsound2
        elif self._multi_kill_count == 4:
            score = 60
            name = Lstr(resource='fourKillText')
            color = (1.0, 1.0, 0.0, 1)
            scale = 1.2
            delay = 0.6
            sound = stats.orchestrahitsound3
        elif self._multi_kill_count == 5:
            score = 80
            name = Lstr(resource='fiveKillText')
            color = (1.0, 0.5, 0.0, 1)
            scale = 1.3
            delay = 0.9
            sound = stats.orchestrahitsound4
        else:
            score = 100
            name = Lstr(resource='multiKillText',
                        subs=[('${COUNT}', str(self._multi_kill_count))])
            color = (1.0, 0.5, 0.0, 1)
            scale = 1.3
            delay = 1.0
            sound = stats.orchestrahitsound4

        def _apply(name2: Lstr, score2: int, showpoints2: bool,
                   color2: Tuple[float, float, float, float], scale2: float,
                   sound2: Optional[ba.Sound]) -> None:
            from bastd.actor.popuptext import PopupText

            # Only award this if they're still alive and we can get
            # a current position for them.
            our_pos: Optional[Sequence[float]] = None
            if self._player is not None:
                if self._player.gameplayer is not None:
                    if self._player.gameplayer.node:
                        our_pos = self._player.gameplayer.node.position
            if our_pos is None:
                return

            # Jitter position a bit since these often come in clusters.
            our_pos = (our_pos[0] + (random.random() - 0.5) * 2.0,
                       our_pos[1] + (random.random() - 0.5) * 2.0,
                       our_pos[2] + (random.random() - 0.5) * 2.0)
            activity = self.getactivity()
            if activity is not None:
                PopupText(Lstr(
                    value=(('+' + str(score2) + ' ') if showpoints2 else '') +
                    '${N}',
                    subs=[('${N}', name2)]),
                          color=color2,
                          scale=scale2,
                          position=our_pos).autoretain()
            if sound2:
                _ba.playsound(sound2)

            self.score += score2
            self.accumscore += score2

            # Inform a running game of the score.
            if score2 != 0 and activity is not None:
                activity.handlemessage(PlayerScoredMessage(score=score2))

        if name is not None:
            _ba.timer(
                0.3 + delay,
                Call(_apply, name, score, showpoints, color, scale, sound))

        # Keep the tally rollin'...
        # set a timer for a bit in the future.
        self._multi_kill_timer = _ba.Timer(1.0, self._end_multi_kill)
Exemple #23
0
 def do_print() -> None:
     _ba.timer(1.0,
               Call(_ba.screenmessage, Lstr(resource='usingItunesText'),
                    (0, 1, 0)),
               timetype=TimeType.REAL)
Exemple #24
0
    def on_app_launch(self) -> None:
        """Runs after the app finishes bootstrapping.

        (internal)"""
        # FIXME: Break this up.
        # pylint: disable=too-many-statements
        # pylint: disable=too-many-locals
        # pylint: disable=cyclic-import
        from ba import _apputils
        from ba import _appconfig
        from ba.ui import UIController, ui_upkeep
        from ba import _achievement
        from ba import _map
        from ba import _meta
        from ba import _campaign
        from bastd import appdelegate
        from bastd import maps as stdmaps
        from bastd.actor import spazappearance
        from ba._enums import TimeType

        cfg = self.config

        self.delegate = appdelegate.AppDelegate()

        self.uicontroller = UIController()
        _achievement.init_achievements()
        spazappearance.register_appearances()
        _campaign.init_campaigns()

        # FIXME: This should not be hard-coded.
        for maptype in [
                stdmaps.HockeyStadium, stdmaps.FootballStadium,
                stdmaps.Bridgit, stdmaps.BigG, stdmaps.Roundabout,
                stdmaps.MonkeyFace, stdmaps.ZigZag, stdmaps.ThePad,
                stdmaps.DoomShroom, stdmaps.LakeFrigid, stdmaps.TipTop,
                stdmaps.CragCastle, stdmaps.TowerD, stdmaps.HappyThoughts,
                stdmaps.StepRightUp, stdmaps.Courtyard, stdmaps.Rampage
        ]:
            _map.register_map(maptype)

        if self.debug_build:
            _apputils.suppress_debug_reports()

        # IMPORTANT - if tweaking UI stuff, you need to make sure it behaves
        # for small, medium, and large UI modes. (doesn't run off screen, etc).
        # Set these to 1 to test with different sizes. Generally small is used
        # on phones, medium is used on tablets, and large is on desktops or
        # large tablets.

        # Kick off our periodic UI upkeep.
        # FIXME: Can probably kill this if we do immediate UI death checks.
        self.uiupkeeptimer = _ba.Timer(2.6543,
                                       ui_upkeep,
                                       timetype=TimeType.REAL,
                                       repeat=True)

        if bool(False):  # force-test small UI
            self.small_ui = True
            self.med_ui = False
            with _ba.Context('ui'):
                _ba.pushcall(lambda: _ba.screenmessage(
                    'FORCING SMALL UI FOR TESTING', color=(1, 0, 1), log=True))

        if bool(False):  # force-test medium UI
            self.small_ui = False
            self.med_ui = True
            with _ba.Context('ui'):
                _ba.pushcall(lambda: _ba.screenmessage(
                    'FORCING MEDIUM UI FOR TESTING', color=
                    (1, 0, 1), log=True))
        if bool(False):  # force-test large UI
            self.small_ui = False
            self.med_ui = False
            with _ba.Context('ui'):
                _ba.pushcall(lambda: _ba.screenmessage(
                    'FORCING LARGE UI FOR TESTING', color=(1, 0, 1), log=True))

        # If there's a leftover log file, attempt to upload
        # it to the server and/or get rid of it.
        _apputils.handle_leftover_log_file()
        try:
            _apputils.handle_leftover_log_file()
        except Exception:
            from ba import _error
            _error.print_exception('Error handling leftover log file')

        # Notify the user if we're using custom system scripts.
        # FIXME: This no longer works since sys-scripts is an absolute path;
        #  need to just add a proper call to query this.
        # if env['python_directory_ba'] != 'data/scripts':
        #     ba.screenmessage("Using custom system scripts...",
        #                     color=(0, 1, 0))

        # Only do this stuff if our config file is healthy so we don't
        # overwrite a broken one or whatnot and wipe out data.
        if not self.config_file_healthy:
            if self.platform in ('mac', 'linux', 'windows'):
                from bastd.ui import configerror
                configerror.ConfigErrorWindow()
                return

            # For now on other systems we just overwrite the bum config.
            # At this point settings are already set; lets just commit them
            # to disk.
            _appconfig.commit_app_config(force=True)

        self.music.on_app_launch()

        launch_count = cfg.get('launchCount', 0)
        launch_count += 1

        # So we know how many times we've run the game at various
        # version milestones.
        for key in ('lc14173', 'lc14292'):
            cfg.setdefault(key, launch_count)

        # Debugging - make note if we're using the local test server so we
        # don't accidentally leave it on in a release.
        # FIXME - move this to native layer.
        server_addr = _ba.get_master_server_address()
        if 'localhost' in server_addr:
            _ba.timer(2.0,
                      lambda: _ba.screenmessage('Note: using local server',
                                                (1, 1, 0),
                                                log=True),
                      timetype=TimeType.REAL)
        elif 'test' in server_addr:
            _ba.timer(
                2.0,
                lambda: _ba.screenmessage('Note: using test server-module',
                                          (1, 1, 0),
                                          log=True),
                timetype=TimeType.REAL)

        cfg['launchCount'] = launch_count
        cfg.commit()

        # Run a test in a few seconds to see if we should pop up an existing
        # pending special offer.
        def check_special_offer() -> None:
            from bastd.ui import specialoffer
            config = self.config
            if ('pendingSpecialOffer' in config and _ba.get_public_login_id()
                    == config['pendingSpecialOffer']['a']):
                self.special_offer = config['pendingSpecialOffer']['o']
                specialoffer.show_offer()

        if not self.headless_build:
            _ba.timer(3.0, check_special_offer, timetype=TimeType.REAL)

        # Start scanning for things exposed via ba_meta.
        _meta.start_scan()

        # Auto-sign-in to a local account in a moment if we're set to.
        def do_auto_sign_in() -> None:
            if self.headless_build:
                _ba.sign_in('Local')
            elif cfg.get('Auto Account State') == 'Local':
                _ba.sign_in('Local')

        _ba.pushcall(do_auto_sign_in)

        self.ran_on_app_launch = True
Exemple #25
0
    def on_begin(self) -> None:
        super().on_begin()

        # Die almost immediately.
        _ba.timer(0.1, self.end)
    def _show_info(self) -> None:
        """Show the game description."""
        from ba._gameutils import animate
        from bastd.actor.zoomtext import ZoomText
        name = self.get_instance_display_string()
        ZoomText(name,
                 maxwidth=800,
                 lifespan=2.5,
                 jitter=2.0,
                 position=(0, 180),
                 flash=False,
                 color=(0.93 * 1.25, 0.9 * 1.25, 1.0 * 1.25),
                 trailcolor=(0.15, 0.05, 1.0, 0.0)).autoretain()
        _ba.timer(0.2, Call(_ba.playsound, _ba.getsound('gong')))

        # The description can be either a string or a sequence with args
        # to swap in post-translation.
        desc_in = self.get_instance_description()
        desc_l: Sequence
        if isinstance(desc_in, str):
            desc_l = [desc_in]  # handle simple string case
        else:
            desc_l = desc_in
        if not isinstance(desc_l[0], str):
            raise TypeError('Invalid format for instance description')
        subs = []
        for i in range(len(desc_l) - 1):
            subs.append(('${ARG' + str(i + 1) + '}', str(desc_l[i + 1])))
        translation = Lstr(translate=('gameDescriptions', desc_l[0]),
                           subs=subs)

        # Do some standard filters (epic mode, etc).
        if self.settings_raw.get('Epic Mode', False):
            translation = Lstr(resource='epicDescriptionFilterText',
                               subs=[('${DESCRIPTION}', translation)])
        vrmode = _ba.app.vr_mode
        dnode = _ba.newnode('text',
                            attrs={
                                'v_attach': 'center',
                                'h_attach': 'center',
                                'h_align': 'center',
                                'color': (1, 1, 1, 1),
                                'shadow': 1.0 if vrmode else 0.5,
                                'flatness': 1.0 if vrmode else 0.5,
                                'vr_depth': -30,
                                'position': (0, 80),
                                'scale': 1.2,
                                'maxwidth': 700,
                                'text': translation
                            })
        cnode = _ba.newnode('combine',
                            owner=dnode,
                            attrs={
                                'input0': 1.0,
                                'input1': 1.0,
                                'input2': 1.0,
                                'size': 4
                            })
        cnode.connectattr('output', dnode, 'color')
        keys = {0.5: 0, 1.0: 1.0, 2.5: 1.0, 4.0: 0.0}
        animate(cnode, 'input3', keys)
        _ba.timer(4.0, dnode.delete)
    def _show_tip(self) -> None:
        # pylint: disable=too-many-locals
        from ba._gameutils import animate, GameTip
        from ba._enums import SpecialChar

        # If there's any tips left on the list, display one.
        if self.tips:
            tip = self.tips.pop(random.randrange(len(self.tips)))
            tip_title = Lstr(value='${A}:',
                             subs=[('${A}', Lstr(resource='tipText'))])
            icon: Optional[ba.Texture] = None
            sound: Optional[ba.Sound] = None
            if isinstance(tip, GameTip):
                icon = tip.icon
                sound = tip.sound
                tip = tip.text
                assert isinstance(tip, str)

            # Do a few substitutions.
            tip_lstr = Lstr(translate=('tips', tip),
                            subs=[('${PICKUP}',
                                   _ba.charstr(SpecialChar.TOP_BUTTON))])
            base_position = (75, 50)
            tip_scale = 0.8
            tip_title_scale = 1.2
            vrmode = _ba.app.vr_mode

            t_offs = -350.0
            tnode = _ba.newnode('text',
                                attrs={
                                    'text':
                                    tip_lstr,
                                    'scale':
                                    tip_scale,
                                    'maxwidth':
                                    900,
                                    'position': (base_position[0] + t_offs,
                                                 base_position[1]),
                                    'h_align':
                                    'left',
                                    'vr_depth':
                                    300,
                                    'shadow':
                                    1.0 if vrmode else 0.5,
                                    'flatness':
                                    1.0 if vrmode else 0.5,
                                    'v_align':
                                    'center',
                                    'v_attach':
                                    'bottom'
                                })
            t2pos = (base_position[0] + t_offs - (20 if icon is None else 82),
                     base_position[1] + 2)
            t2node = _ba.newnode('text',
                                 owner=tnode,
                                 attrs={
                                     'text': tip_title,
                                     'scale': tip_title_scale,
                                     'position': t2pos,
                                     'h_align': 'right',
                                     'vr_depth': 300,
                                     'shadow': 1.0 if vrmode else 0.5,
                                     'flatness': 1.0 if vrmode else 0.5,
                                     'maxwidth': 140,
                                     'v_align': 'center',
                                     'v_attach': 'bottom'
                                 })
            if icon is not None:
                ipos = (base_position[0] + t_offs - 40, base_position[1] + 1)
                img = _ba.newnode('image',
                                  attrs={
                                      'texture': icon,
                                      'position': ipos,
                                      'scale': (50, 50),
                                      'opacity': 1.0,
                                      'vr_depth': 315,
                                      'color': (1, 1, 1),
                                      'absolute_scale': True,
                                      'attach': 'bottomCenter'
                                  })
                animate(img, 'opacity', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0})
                _ba.timer(5.0, img.delete)
            if sound is not None:
                _ba.playsound(sound)

            combine = _ba.newnode('combine',
                                  owner=tnode,
                                  attrs={
                                      'input0': 1.0,
                                      'input1': 0.8,
                                      'input2': 1.0,
                                      'size': 4
                                  })
            combine.connectattr('output', tnode, 'color')
            combine.connectattr('output', t2node, 'color')
            animate(combine, 'input3', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0})
            _ba.timer(5.0, tnode.delete)
Exemple #28
0
    def show_completion_banner(self, sound: bool = True) -> None:
        """Create the banner/sound for an acquired achievement announcement."""
        from ba import _account
        from ba import _gameutils
        from bastd.actor.text import Text
        from bastd.actor.image import Image
        from ba._general import WeakCall
        from ba._lang import Lstr
        from ba._messages import DieMessage
        from ba._enums import TimeType, SpecialChar
        app = _ba.app
        app.last_achievement_display_time = _ba.time(TimeType.REAL)

        # Just piggy-back onto any current activity
        # (should we use the session instead?..)
        activity = _ba.getactivity(doraise=False)

        # If this gets called while this achievement is occupying a slot
        # already, ignore it. (probably should never happen in real
        # life but whatevs).
        if self._completion_banner_slot is not None:
            return

        if activity is None:
            print('show_completion_banner() called with no current activity!')
            return

        if sound:
            _ba.playsound(_ba.getsound('achievement'), host_only=True)
        else:
            _ba.timer(
                0.5,
                lambda: _ba.playsound(_ba.getsound('ding'), host_only=True))

        in_time = 0.300
        out_time = 3.5

        base_vr_depth = 200

        # 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)
                self._completion_banner_slot = i

                # Remove us from that slot when we close.
                # Use a real-timer in the UI context so the removal runs even
                # if our activity/session dies.
                with _ba.Context('ui'):
                    _ba.timer(in_time + out_time,
                              self._remove_banner_slot,
                              timetype=TimeType.REAL)
                break
            i += 1
        assert self._completion_banner_slot is not None
        y_offs = 110 * self._completion_banner_slot
        objs: List[ba.Actor] = []
        obj = Image(_ba.gettexture('shadow'),
                    position=(-30, 30 + y_offs),
                    front=True,
                    attach=Image.Attach.BOTTOM_CENTER,
                    transition=Image.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth - 100,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    color=(0.0, 0.1, 0, 1),
                    scale=(1000, 300)).autoretain()
        objs.append(obj)
        assert obj.node
        obj.node.host_only = True
        obj = Image(_ba.gettexture('light'),
                    position=(-180, 60 + y_offs),
                    front=True,
                    attach=Image.Attach.BOTTOM_CENTER,
                    vr_depth=base_vr_depth,
                    transition=Image.Transition.IN_BOTTOM,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    color=(1.8, 1.8, 1.0, 0.0),
                    scale=(40, 300)).autoretain()
        objs.append(obj)
        assert obj.node
        obj.node.host_only = True
        obj.node.premultiplied = True
        combine = _ba.newnode('combine', owner=obj.node, attrs={'size': 2})
        _gameutils.animate(
            combine, 'input0', {
                in_time: 0,
                in_time + 0.4: 30,
                in_time + 0.5: 40,
                in_time + 0.6: 30,
                in_time + 2.0: 0
            })
        _gameutils.animate(
            combine, 'input1', {
                in_time: 0,
                in_time + 0.4: 200,
                in_time + 0.5: 500,
                in_time + 0.6: 200,
                in_time + 2.0: 0
            })
        combine.connectattr('output', obj.node, 'scale')
        _gameutils.animate(obj.node,
                           'rotate', {
                               0: 0.0,
                               0.35: 360.0
                           },
                           loop=True)
        obj = Image(self.get_icon_texture(True),
                    position=(-180, 60 + y_offs),
                    attach=Image.Attach.BOTTOM_CENTER,
                    front=True,
                    vr_depth=base_vr_depth - 10,
                    transition=Image.Transition.IN_BOTTOM,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    scale=(100, 100)).autoretain()
        objs.append(obj)
        assert obj.node
        obj.node.host_only = True

        # Flash.
        color = self.get_icon_color(True)
        combine = _ba.newnode('combine', owner=obj.node, attrs={'size': 3})
        keys = {
            in_time: 1.0 * color[0],
            in_time + 0.4: 1.5 * color[0],
            in_time + 0.5: 6.0 * color[0],
            in_time + 0.6: 1.5 * color[0],
            in_time + 2.0: 1.0 * color[0]
        }
        _gameutils.animate(combine, 'input0', keys)
        keys = {
            in_time: 1.0 * color[1],
            in_time + 0.4: 1.5 * color[1],
            in_time + 0.5: 6.0 * color[1],
            in_time + 0.6: 1.5 * color[1],
            in_time + 2.0: 1.0 * color[1]
        }
        _gameutils.animate(combine, 'input1', keys)
        keys = {
            in_time: 1.0 * color[2],
            in_time + 0.4: 1.5 * color[2],
            in_time + 0.5: 6.0 * color[2],
            in_time + 0.6: 1.5 * color[2],
            in_time + 2.0: 1.0 * color[2]
        }
        _gameutils.animate(combine, 'input2', keys)
        combine.connectattr('output', obj.node, 'color')

        obj = Image(_ba.gettexture('achievementOutline'),
                    model_transparent=_ba.getmodel('achievementOutline'),
                    position=(-180, 60 + y_offs),
                    front=True,
                    attach=Image.Attach.BOTTOM_CENTER,
                    vr_depth=base_vr_depth,
                    transition=Image.Transition.IN_BOTTOM,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    scale=(100, 100)).autoretain()
        assert obj.node
        obj.node.host_only = True

        # Flash.
        color = (2, 1.4, 0.4, 1)
        combine = _ba.newnode('combine', owner=obj.node, attrs={'size': 3})
        keys = {
            in_time: 1.0 * color[0],
            in_time + 0.4: 1.5 * color[0],
            in_time + 0.5: 6.0 * color[0],
            in_time + 0.6: 1.5 * color[0],
            in_time + 2.0: 1.0 * color[0]
        }
        _gameutils.animate(combine, 'input0', keys)
        keys = {
            in_time: 1.0 * color[1],
            in_time + 0.4: 1.5 * color[1],
            in_time + 0.5: 6.0 * color[1],
            in_time + 0.6: 1.5 * color[1],
            in_time + 2.0: 1.0 * color[1]
        }
        _gameutils.animate(combine, 'input1', keys)
        keys = {
            in_time: 1.0 * color[2],
            in_time + 0.4: 1.5 * color[2],
            in_time + 0.5: 6.0 * color[2],
            in_time + 0.6: 1.5 * color[2],
            in_time + 2.0: 1.0 * color[2]
        }
        _gameutils.animate(combine, 'input2', keys)
        combine.connectattr('output', obj.node, 'color')
        objs.append(obj)

        objt = Text(Lstr(value='${A}:',
                         subs=[('${A}', Lstr(resource='achievementText'))]),
                    position=(-120, 91 + y_offs),
                    front=True,
                    v_attach=Text.VAttach.BOTTOM,
                    vr_depth=base_vr_depth - 10,
                    transition=Text.Transition.IN_BOTTOM,
                    flatness=0.5,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    color=(1, 1, 1, 0.8),
                    scale=0.65).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        objt = Text(self.display_name,
                    position=(-120, 50 + y_offs),
                    front=True,
                    v_attach=Text.VAttach.BOTTOM,
                    transition=Text.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth,
                    flatness=0.5,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    flash=True,
                    color=(1, 0.8, 0, 1.0),
                    scale=1.5).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        objt = Text(_ba.charstr(SpecialChar.TICKET),
                    position=(-120 - 170 + 5, 75 + y_offs - 20),
                    front=True,
                    v_attach=Text.VAttach.BOTTOM,
                    h_align=Text.HAlign.CENTER,
                    v_align=Text.VAlign.CENTER,
                    transition=Text.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    flash=True,
                    color=(0.5, 0.5, 0.5, 1),
                    scale=3.0).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        objt = Text('+' + str(self.get_award_ticket_value()),
                    position=(-120 - 180 + 5, 80 + y_offs - 20),
                    v_attach=Text.VAttach.BOTTOM,
                    front=True,
                    h_align=Text.HAlign.CENTER,
                    v_align=Text.VAlign.CENTER,
                    transition=Text.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth,
                    flatness=0.5,
                    shadow=1.0,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    flash=True,
                    color=(0, 1, 0, 1),
                    scale=1.5).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        # Add the 'x 2' if we've got pro.
        if _account.have_pro():
            objt = Text('x 2',
                        position=(-120 - 180 + 45, 80 + y_offs - 50),
                        v_attach=Text.VAttach.BOTTOM,
                        front=True,
                        h_align=Text.HAlign.CENTER,
                        v_align=Text.VAlign.CENTER,
                        transition=Text.Transition.IN_BOTTOM,
                        vr_depth=base_vr_depth,
                        flatness=0.5,
                        shadow=1.0,
                        transition_delay=in_time,
                        transition_out_delay=out_time,
                        flash=True,
                        color=(0.4, 0, 1, 1),
                        scale=0.9).autoretain()
            objs.append(objt)
            assert objt.node
            objt.node.host_only = True

        objt = Text(self.description_complete,
                    position=(-120, 30 + y_offs),
                    front=True,
                    v_attach=Text.VAttach.BOTTOM,
                    transition=Text.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth - 10,
                    flatness=0.5,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    color=(1.0, 0.7, 0.5, 1.0),
                    scale=0.8).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        for actor in objs:
            _ba.timer(out_time + 1.000,
                      WeakCall(actor.handlemessage, DieMessage()))
Exemple #29
0
    def on_player_leave(self, player: ba.Player) -> None:
        from ba._general import WeakCall
        super().on_player_leave(player)

        # If all our players leave we wanna quit out of the session.
        _ba.timer(2.0, WeakCall(self._end_session_if_empty))
Exemple #30
0
    def setactivity(self, activity: ba.Activity) -> None:
        """Assign a new current ba.Activity for the session.

        Note that this will not change the current context to the new
        Activity's. Code must be run in the new activity's methods
        (on_transition_in, etc) to get it. (so you can't do
        session.setactivity(foo) and then ba.newnode() to add a node to foo)
        """
        from ba._enums import TimeType

        # Make sure we don't get called recursively.
        _rlock = self._SetActivityScopedLock(self)

        if activity.session is not _ba.getsession():
            raise RuntimeError("Provided Activity's Session is not current.")

        # Quietly ignore this if the whole session is going down.
        if self._ending:
            return

        if activity is self._activity_retained:
            print_error('Activity set to already-current activity.')
            return

        if self._next_activity is not None:
            raise RuntimeError('Activity switch already in progress (to ' +
                               str(self._next_activity) + ')')

        prev_activity = self._activity_retained
        prev_globals = (prev_activity.globalsnode
                        if prev_activity is not None else None)

        # Let the activity do its thing.
        activity.transition_in(prev_globals)

        self._next_activity = activity

        # If we have a current activity, tell it it's transitioning out;
        # the next one will become current once this one dies.
        if prev_activity is not None:
            prev_activity.transition_out()

            # Setting this to None should free up the old activity to die,
            # which will call begin_next_activity.
            # We can still access our old activity through
            # self._activity_weak() to keep it up to date on player
            # joins/departures/etc until it dies.
            self._activity_retained = None

        # There's no existing activity; lets just go ahead with the begin call.
        else:
            self.begin_next_activity()

        # We want to call destroy() for the previous activity once it should
        # tear itself down, clear out any self-refs, etc. After this call
        # the activity should have no refs left to it and should die (which
        # will trigger the next activity to run).
        if prev_activity is not None:
            with _ba.Context('ui'):
                _ba.timer(max(0.0, activity.transition_time),
                          prev_activity.expire,
                          timetype=TimeType.REAL)
        self._in_set_activity = False