예제 #1
0
def start_stress_test(args: Dict[str, Any]) -> None:
    """(internal)"""
    from ba._general import Call
    from ba._dualteamsession import DualTeamSession
    from ba._freeforallsession import FreeForAllSession
    from ba._enums import TimeType, TimeFormat
    bs_config = _ba.app.config
    playlist_type = args['playlist_type']
    if playlist_type == 'Random':
        if random.random() < 0.5:
            playlist_type = 'Teams'
        else:
            playlist_type = 'Free-For-All'
    _ba.screenmessage('Running Stress Test (listType="' + playlist_type +
                      '", listName="' + args['playlist_name'] + '")...')
    if playlist_type == 'Teams':
        bs_config['Team Tournament Playlist Selection'] = args['playlist_name']
        bs_config['Team Tournament Playlist Randomize'] = 1
        _ba.timer(1.0,
                  Call(_ba.pushcall, Call(_ba.new_host_session,
                                          DualTeamSession)),
                  timetype=TimeType.REAL)
    else:
        bs_config['Free-for-All Playlist Selection'] = args['playlist_name']
        bs_config['Free-for-All Playlist Randomize'] = 1
        _ba.timer(1.0,
                  Call(_ba.pushcall,
                       Call(_ba.new_host_session, FreeForAllSession)),
                  timetype=TimeType.REAL)
    _ba.set_stress_testing(True, args['player_count'])
    _ba.app.stress_test_reset_timer = _ba.Timer(
        args['round_duration'] * 1000,
        Call(_reset_stress_test, args),
        timetype=TimeType.REAL,
        timeformat=TimeFormat.MILLISECONDS)
예제 #2
0
    def end_activity(self, activity: ba.Activity, results: Any, delay: float,
                     force: bool) -> None:
        """Commence shutdown of a ba.Activity (if not already occurring).

        'delay' is the time delay before the Activity actually ends
        (in seconds). Further calls to end() will be ignored up until
        this time, unless 'force' is True, in which case the new results
        will replace the old.
        """
        from ba._general import Call
        from ba._enums import TimeType

        # Only pay attention if this is coming from our current activity.
        if activity is not self._activity_retained:
            return

        # If this activity hasn't begun yet, just set it up to end immediately
        # once it does.
        if not activity.has_begun():
            # activity.set_immediate_end(results, delay, force)
            if not self._activity_should_end_immediately or force:
                self._activity_should_end_immediately = True
                self._activity_should_end_immediately_results = results
                self._activity_should_end_immediately_delay = delay

        # The activity has already begun; get ready to end it.
        else:
            if (not activity.has_ended()) or force:
                activity.set_has_ended(True)

                # Set a timer to set in motion this activity's demise.
                self._activity_end_timer = _ba.Timer(
                    delay,
                    Call(self._complete_end_activity, activity, results),
                    timetype=TimeType.BASE)
예제 #3
0
def _spaz_spawn_holding_node(self) -> Optional[ba.Node]:
    if not self.node:
        return
    self.delete_holding_node()
    t = self.node.position
    t = (t[0], t[1] + 1, t[2])
    self.holding_node = ba.newnode('prop',
                                   owner=self.node,
                                   delegate=self,
                                   attrs={
                                       'position': t,
                                       'body': 'box',
                                       'body_scale': 0.000001,
                                       'model': None,
                                       'model_scale': 0.000001,
                                       'color_texture': None,
                                       'max_speed': 0,
                                       'owner': self.node,
                                       'materials': []
                                   })
    self._combine = ba.newnode('combine',
                               owner=self.holding_node,
                               attrs={'size': 3})
    self._offset = [0, 0, 0]
    self._combine.input0, self._combine.input1, self._combine.input2 = t
    self._combine.connectattr('output', self.holding_node, 'position')

    self.move_holding_node('xyz')
    self.fly_timer = _ba.Timer(0.1,
                               ba.WeakCall(self.move_holding_node, 'xyz'),
                               repeat=True)
    return self.holding_node
예제 #4
0
    def __init__(self, config: ServerConfig) -> None:

        self._config = config
        self._playlist_name = '__default__'
        self._ran_access_check = False
        self._prep_timer: Optional[ba.Timer] = None
        self._next_stuck_login_warn_time = time.time() + 10.0
        self._first_run = True
        self._shutdown_reason: Optional[ShutdownReason] = None
        self._executing_shutdown = False

        # Make note if they want us to import a playlist;
        # we'll need to do that first if so.
        self._playlist_fetch_running = self._config.playlist_code is not None
        self._playlist_fetch_sent_request = False
        self._playlist_fetch_got_response = False
        self._playlist_fetch_code = -1

        # Now sit around doing any pre-launch prep such as waiting for
        # account sign-in or fetching playlists; this will kick off the
        # session once done.
        with _ba.Context('ui'):
            self._prep_timer = _ba.Timer(0.25,
                                         self._prepare_to_serve,
                                         timetype=TimeType.REAL,
                                         repeat=True)
예제 #5
0
    def _destroy(self) -> None:
        from ba._general import Call
        from ba._enums import TimeType

        # Create a real-timer that watches a weak-ref of this activity
        # and reports any lingering references keeping it alive.
        # We store the timer on the activity so as soon as the activity dies
        # it gets cleaned up.
        with _ba.Context('ui'):
            ref = weakref.ref(self)
            self._activity_death_check_timer = _ba.Timer(
                5.0,
                Call(self._check_activity_death, ref, [0]),
                repeat=True,
                timetype=TimeType.REAL)

        # Run _expire in an empty context; nothing should be happening in
        # there except deleting things which requires no context.
        # (plus, _expire() runs in the destructor for un-run activities
        # and we can't properly provide context in that situation anyway; might
        # as well be consistent).
        if not self._expired:
            with _ba.Context('empty'):
                self._expire()
        else:
            raise Exception("_destroy() called multiple times")
예제 #6
0
    def announce_completion(self, sound: bool = True) -> None:
        """Kick off an announcement for this achievement's completion."""
        from ba._enums import TimeType
        app = _ba.app

        # Even though there are technically achievements when we're not
        # signed in, lets not show them (otherwise we tend to get
        # confusing 'controller connected' achievements popping up while
        # waiting to log in which can be confusing).
        if _ba.get_account_state() != 'signed_in':
            return

        # If we're being freshly complete, display/report it and whatnot.
        if (self, sound) not in app.achievements_to_display:
            app.achievements_to_display.append((self, sound))

        # If there's no achievement display timer going, kick one off
        # (if one's already running it will pick this up before it dies).

        # Need to check last time too; its possible our timer wasn't able to
        # clear itself if an activity died and took it down with it.
        if ((app.achievement_display_timer is None or
             _ba.time(TimeType.REAL) - app.last_achievement_display_time > 2.0)
                and _ba.getactivity(doraise=False) is not None):
            app.achievement_display_timer = _ba.Timer(
                1.0,
                _display_next_achievement,
                repeat=True,
                timetype=TimeType.BASE)

            # Show the first immediately.
            _display_next_achievement()
예제 #7
0
    def __init__(self, lobby: ba.Lobby):
        from ba._nodeactor import NodeActor
        from ba._general import WeakCall
        self._state = 0
        self._press_to_punch: Union[str,
                                    ba.Lstr] = ('C' if _ba.app.iircade_mode
                                                else _ba.charstr(
                                                    SpecialChar.LEFT_BUTTON))
        self._press_to_bomb: Union[str,
                                   ba.Lstr] = ('B' if _ba.app.iircade_mode else
                                               _ba.charstr(
                                                   SpecialChar.RIGHT_BUTTON))
        self._joinmsg = Lstr(resource='pressAnyButtonToJoinText')
        can_switch_teams = (len(lobby.sessionteams) > 1)

        # If we have a keyboard, grab keys for punch and pickup.
        # FIXME: This of course is only correct on the local device;
        #  Should change this for net games.
        keyboard = _ba.getinputdevice('Keyboard', '#1', doraise=False)
        if keyboard is not None:
            self._update_for_keyboard(keyboard)

        flatness = 1.0 if _ba.app.vr_mode else 0.0
        self._text = NodeActor(
            _ba.newnode('text',
                        attrs={
                            'position': (0, -40),
                            'h_attach': 'center',
                            'v_attach': 'top',
                            'h_align': 'center',
                            'color': (0.7, 0.7, 0.95, 1.0),
                            'flatness': flatness,
                            'text': self._joinmsg
                        }))

        if _ba.app.demo_mode or _ba.app.arcade_mode:
            self._messages = [self._joinmsg]
        else:
            msg1 = Lstr(resource='pressToSelectProfileText',
                        subs=[
                            ('${BUTTONS}', _ba.charstr(SpecialChar.UP_ARROW) +
                             ' ' + _ba.charstr(SpecialChar.DOWN_ARROW))
                        ])
            msg2 = Lstr(resource='pressToOverrideCharacterText',
                        subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))])
            msg3 = Lstr(value='${A} < ${B} >',
                        subs=[('${A}', msg2), ('${B}', self._press_to_bomb)])
            self._messages = (([
                Lstr(
                    resource='pressToSelectTeamText',
                    subs=[('${BUTTONS}', _ba.charstr(SpecialChar.LEFT_ARROW) +
                           ' ' + _ba.charstr(SpecialChar.RIGHT_ARROW))],
                )
            ] if can_switch_teams else []) + [msg1] + [msg3] + [self._joinmsg])

        self._timer = _ba.Timer(4.0, WeakCall(self._update), repeat=True)
예제 #8
0
    def transition_in(self, prev_globals: Optional[ba.Node]) -> None:
        """Called by Session to kick off transition-in.

        (internal)
        """
        assert not self._has_transitioned_in
        self._has_transitioned_in = True

        # Set up the globals node based on our settings.
        with _ba.Context(self):
            glb = self._globalsnode = _ba.newnode('globals')

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

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

            # Start pruning our various things periodically.
            self._prune_dead_actors()
            self._prune_dead_actors_timer = _ba.Timer(5.17,
                                                      self._prune_dead_actors,
                                                      repeat=True)

            _ba.timer(13.3, self._prune_delay_deletes, repeat=True)

            # Also start our low-level scene running.
            self._activity_data.start()

            try:
                self.on_transition_in()
            except Exception:
                print_exception(f'Error in on_transition_in for {self}.')

        # Tell the C++ layer that this activity is the main one, so it uses
        # settings from our globals, directs various events to us, etc.
        self._activity_data.make_foreground()
예제 #9
0
def setup_asyncio() -> asyncio.AbstractEventLoop:
    """Setup asyncio functionality for the logic thread."""
    # pylint: disable=global-statement

    import _ba
    import ba
    from ba._generated.enums import TimeType

    assert _ba.in_game_thread()

    # Create our event-loop. We don't expect there to be one
    # running on this thread before we do.
    try:
        asyncio.get_running_loop()
        print('Found running asyncio loop; unexpected.')
    except RuntimeError:
        pass

    global _asyncio_event_loop  # pylint: disable=invalid-name
    _asyncio_event_loop = asyncio.new_event_loop()
    _asyncio_event_loop.set_default_executor(ba.app.threadpool)

    # Ideally we should integrate asyncio into our C++ Thread class's
    # low level event loop so that asyncio timers/sockets/etc. could
    # be true first-class citizens. For now, though, we can explicitly
    # pump an asyncio loop periodically which gets us a decent
    # approximation of that, which should be good enough for
    # all but extremely time sensitive uses.
    # See https://stackoverflow.com/questions/29782377/
    # is-it-possible-to-run-only-a-single-step-of-the-asyncio-event-loop
    def run_cycle() -> None:
        assert _asyncio_event_loop is not None
        _asyncio_event_loop.call_soon(_asyncio_event_loop.stop)
        _asyncio_event_loop.run_forever()

    global _asyncio_timer  # pylint: disable=invalid-name
    _asyncio_timer = _ba.Timer(1.0 / 30.0,
                               run_cycle,
                               timetype=TimeType.REAL,
                               repeat=True)

    if bool(False):

        async def aio_test() -> None:
            print('TEST AIO TASK STARTING')
            assert _asyncio_event_loop is not None
            assert asyncio.get_running_loop() is _asyncio_event_loop
            await asyncio.sleep(2.0)
            print('TEST AIO TASK ENDING')

        _asyncio_event_loop.create_task(aio_test())

    return _asyncio_event_loop
 def setup_standard_powerup_drops(self, enable_tnt: bool = True) -> None:
     """Create standard powerup drops for the current map."""
     # pylint: disable=cyclic-import
     from bastd.actor.powerupbox import DEFAULT_POWERUP_INTERVAL
     self._powerup_drop_timer = _ba.Timer(DEFAULT_POWERUP_INTERVAL,
                                          WeakCall(
                                              self._standard_drop_powerups),
                                          repeat=True)
     self._standard_drop_powerups()
     if enable_tnt:
         self._tnt_spawners = {}
         self._setup_standard_tnt_drops()
예제 #11
0
    def on_app_launch(self) -> None:
        """Should be run on app launch."""
        from ba.ui import UIController, ui_upkeep
        from ba._enums import TimeType

        self.controller = UIController()

        # Kick off our periodic UI upkeep.
        # FIXME: Can probably kill this if we do immediate UI death checks.
        self.upkeeptimer = _ba.Timer(2.6543,
                                     ui_upkeep,
                                     timetype=TimeType.REAL,
                                     repeat=True)
예제 #12
0
    def on_transition_in(self) -> None:
        """Called when the Activity is first becoming visible.

        Upon this call, the Activity should fade in backgrounds,
        start playing music, etc. It does not yet have access to ba.Players
        or ba.Teams, however. They remain owned by the previous Activity
        up until ba.Activity.on_begin() is called.
        """
        from ba._general import WeakCall

        self._called_activity_on_transition_in = True

        # Start pruning our transient actors periodically.
        self._prune_dead_objects_timer = _ba.Timer(
            5.17, WeakCall(self._prune_dead_objects), repeat=True)
        self._prune_dead_objects()

        # Also start our low-level scene-graph running.
        self._activity_data.start()
    def respawn_player(self,
                       player: PlayerType,
                       respawn_time: Optional[float] = None) -> None:
        """
        Given a ba.Player, sets up a standard respawn timer,
        along with the standard counter display, etc.
        At the end of the respawn period spawn_player() will
        be called if the Player still exists.
        An explicit 'respawn_time' can optionally be provided
        (in seconds).
        """
        # pylint: disable=cyclic-import

        assert player
        if respawn_time is None:
            teamsize = len(player.team.players)
            if teamsize == 1:
                respawn_time = 3.0
            elif teamsize == 2:
                respawn_time = 5.0
            elif teamsize == 3:
                respawn_time = 6.0
            else:
                respawn_time = 7.0

        # If this standard setting is present, factor it in.
        if 'Respawn Times' in self.settings_raw:
            respawn_time *= self.settings_raw['Respawn Times']

        # We want whole seconds.
        assert respawn_time is not None
        respawn_time = round(max(1.0, respawn_time), 0)

        if player.actor and not self.has_ended():
            from bastd.actor.respawnicon import RespawnIcon
            player.customdata['respawn_timer'] = _ba.Timer(
                respawn_time, WeakCall(self.spawn_player_if_exists, player))
            player.customdata['respawn_icon'] = RespawnIcon(
                player, respawn_time)
 def setup_standard_time_limit(self, duration: float) -> None:
     """
     Create a standard game time-limit given the provided
     duration in seconds.
     This will be displayed at the top of the screen.
     If the time-limit expires, end_game() will be called.
     """
     from ba._nodeactor import NodeActor
     if duration <= 0.0:
         return
     self._standard_time_limit_time = int(duration)
     self._standard_time_limit_timer = _ba.Timer(
         1.0, WeakCall(self._standard_time_limit_tick), repeat=True)
     self._standard_time_limit_text = NodeActor(
         _ba.newnode('text',
                     attrs={
                         'v_attach': 'top',
                         'h_attach': 'center',
                         'h_align': 'left',
                         'color': (1.0, 1.0, 1.0, 0.5),
                         'position': (-25, -30),
                         'flatness': 1.0,
                         'scale': 0.9
                     }))
     self._standard_time_limit_text_input = NodeActor(
         _ba.newnode('timedisplay',
                     attrs={
                         'time2': duration * 1000,
                         'timemin': 0
                     }))
     self.globalsnode.connectattr('time',
                                  self._standard_time_limit_text_input.node,
                                  'time1')
     assert self._standard_time_limit_text_input.node
     assert self._standard_time_limit_text.node
     self._standard_time_limit_text_input.node.connectattr(
         'output', self._standard_time_limit_text.node, 'text')
예제 #15
0
    def on_app_launch(self) -> None:
        """Should be run on app launch."""
        from ba.ui import UIController, ui_upkeep
        from ba._generated.enums import TimeType

        # IMPORTANT: If tweaking UI stuff, make sure it behaves for small,
        # medium, and large UI modes. (doesn't run off screen, etc).
        # The overrides below can be used to test with different sizes.
        # Generally small is used on phones, medium is used on tablets/tvs,
        # and large is on desktop computers or perhaps large tablets. When
        # possible, run in windowed mode and resize the window to assure
        # this holds true at all aspect ratios.

        # UPDATE: A better way to test this is now by setting the environment
        # variable BA_UI_SCALE to "small", "medium", or "large".
        # This will affect system UIs not covered by the values below such
        # as screen-messages. The below values remain functional, however,
        # for cases such as Android where environment variables can't be set
        # easily.

        if bool(False):  # force-test ui scale
            self._uiscale = UIScale.SMALL
            with _ba.Context('ui'):
                _ba.pushcall(lambda: _ba.screenmessage(
                    f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
                    color=(1, 0, 1),
                    log=True))

        self.controller = UIController()

        # Kick off our periodic UI upkeep.
        # FIXME: Can probably kill this if we do immediate UI death checks.
        self.upkeeptimer = _ba.Timer(2.6543,
                                     ui_upkeep,
                                     timetype=TimeType.REAL,
                                     repeat=True)
예제 #16
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, UIScale

        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)

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

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

        # IMPORTANT: If tweaking UI stuff, make sure it behaves for small,
        # medium, and large UI modes. (doesn't run off screen, etc).
        # The overrides below can be used to test with different sizes.
        # Generally small is used on phones, medium is used on tablets/tvs,
        # and large is on desktop computers or perhaps large tablets. When
        # possible, run in windowed mode and resize the window to assure
        # this holds true at all aspect ratios.

        # UPDATE: A better way to test this is now by setting the environment
        # variable BA_FORCE_UI_SCALE to "small", "medium", or "large".
        # This will affect system UIs not covered by the values below such
        # as screen-messages. The below values remain functional, however,
        # for cases such as Android where environment variables can't be set
        # easily.

        if bool(False):  # force-test ui scale
            self._uiscale = UIScale.SMALL
            with _ba.Context('ui'):
                _ba.pushcall(lambda: _ba.screenmessage(
                    f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
                    color=(1, 0, 1),
                    log=True))

        # 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 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 or cfg.get('Auto Account State') == 'Local':
                _ba.sign_in('Local')

        _ba.pushcall(do_auto_sign_in)

        self.ran_on_app_launch = True
예제 #17
0
    def __init__(self, lobby: ba.Lobby):
        # pylint: disable=too-many-locals
        from ba import _input
        from ba._lang import Lstr
        from ba._nodeactor import NodeActor
        from ba._general import WeakCall
        from ba._enums import SpecialChar
        can_switch_teams = (len(lobby.teams) > 1)
        self._state = 0
        press_to_punch: Union[str,
                              ba.Lstr] = _ba.charstr(SpecialChar.LEFT_BUTTON)
        press_to_bomb: Union[str,
                             ba.Lstr] = _ba.charstr(SpecialChar.RIGHT_BUTTON)

        # If we have a keyboard, grab keys for punch and pickup.
        # FIXME: This of course is only correct on the local device;
        #  Should change this for net games.
        keyboard: Optional[ba.InputDevice] = _ba.get_input_device(
            'Keyboard', '#1', doraise=False)
        if keyboard is not None:
            punch_key = keyboard.get_button_name(
                _input.get_device_value(keyboard, 'buttonPunch'))
            press_to_punch = Lstr(resource='orText',
                                  subs=[('${A}',
                                         Lstr(value='\'${K}\'',
                                              subs=[('${K}', punch_key)])),
                                        ('${B}', press_to_punch)])
            bomb_key = keyboard.get_button_name(
                _input.get_device_value(keyboard, 'buttonBomb'))
            press_to_bomb = Lstr(resource='orText',
                                 subs=[('${A}',
                                        Lstr(value='\'${K}\'',
                                             subs=[('${K}', bomb_key)])),
                                       ('${B}', press_to_bomb)])
            join_str = Lstr(value='${A} < ${B} >',
                            subs=[('${A}',
                                   Lstr(resource='pressPunchToJoinText')),
                                  ('${B}', press_to_punch)])
        else:
            join_str = Lstr(resource='pressAnyButtonToJoinText')

        flatness = 1.0 if _ba.app.vr_mode else 0.0
        self._text = NodeActor(
            _ba.newnode('text',
                        attrs={
                            'position': (0, -40),
                            'h_attach': 'center',
                            'v_attach': 'top',
                            'h_align': 'center',
                            'color': (0.7, 0.7, 0.95, 1.0),
                            'flatness': flatness,
                            'text': join_str
                        }))

        if _ba.app.kiosk_mode:
            self._messages = [join_str]
        else:
            msg1 = Lstr(resource='pressToSelectProfileText',
                        subs=[
                            ('${BUTTONS}', _ba.charstr(SpecialChar.UP_ARROW) +
                             ' ' + _ba.charstr(SpecialChar.DOWN_ARROW))
                        ])
            msg2 = Lstr(resource='pressToOverrideCharacterText',
                        subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))])
            msg3 = Lstr(value='${A} < ${B} >',
                        subs=[('${A}', msg2), ('${B}', press_to_bomb)])
            self._messages = (([
                Lstr(resource='pressToSelectTeamText',
                     subs=[('${BUTTONS}', _ba.charstr(SpecialChar.LEFT_ARROW) +
                            ' ' + _ba.charstr(SpecialChar.RIGHT_ARROW))])
            ] if can_switch_teams else []) + [msg1] + [msg3] + [join_str])

        self._timer = _ba.Timer(4.0, WeakCall(self._update), repeat=True)
    def _setup_tournament_time_limit(self, duration: float) -> None:
        """
        Create a tournament game time-limit given the provided
        duration in seconds.
        This will be displayed at the top of the screen.
        If the time-limit expires, end_game() will be called.
        """
        from ba._nodeactor import NodeActor
        from ba._enums import TimeType
        if duration <= 0.0:
            return
        self._tournament_time_limit = int(duration)

        # We want this timer to match the server's time as close as possible,
        # so lets go with base-time. Theoretically we should do real-time but
        # then we have to mess with contexts and whatnot since its currently
        # not available in activity contexts. :-/
        self._tournament_time_limit_timer = _ba.Timer(
            1.0,
            WeakCall(self._tournament_time_limit_tick),
            repeat=True,
            timetype=TimeType.BASE)
        self._tournament_time_limit_title_text = NodeActor(
            _ba.newnode('text',
                        attrs={
                            'v_attach': 'bottom',
                            'h_attach': 'left',
                            'h_align': 'center',
                            'v_align': 'center',
                            'vr_depth': 300,
                            'maxwidth': 100,
                            'color': (1.0, 1.0, 1.0, 0.5),
                            'position': (60, 50),
                            'flatness': 1.0,
                            'scale': 0.5,
                            'text': Lstr(resource='tournamentText')
                        }))
        self._tournament_time_limit_text = NodeActor(
            _ba.newnode('text',
                        attrs={
                            'v_attach': 'bottom',
                            'h_attach': 'left',
                            'h_align': 'center',
                            'v_align': 'center',
                            'vr_depth': 300,
                            'maxwidth': 100,
                            'color': (1.0, 1.0, 1.0, 0.5),
                            'position': (60, 30),
                            'flatness': 1.0,
                            'scale': 0.9
                        }))
        self._tournament_time_limit_text_input = NodeActor(
            _ba.newnode('timedisplay',
                        attrs={
                            'timemin': 0,
                            'time2': self._tournament_time_limit * 1000
                        }))
        assert self._tournament_time_limit_text.node
        assert self._tournament_time_limit_text_input.node
        self._tournament_time_limit_text_input.node.connectattr(
            'output', self._tournament_time_limit_text.node, 'text')
예제 #19
0
def config_server(config_file: str = None) -> None:
    """Run the game in server mode with the provided server config file."""

    from ba._enums import TimeType

    app = _ba.app

    # Read and store the provided server config and then delete the file it
    # came from.
    if config_file is not None:
        with open(config_file) as infile:
            app.server_config = json.loads(infile.read())
        os.remove(config_file)
    else:
        app.server_config = {}

    # Make note if they want us to import a playlist;
    # we'll need to do that first if so.
    playlist_code = app.server_config.get('playlist_code')
    if playlist_code is not None:
        app.server_playlist_fetch = {
            'sent_request': False,
            'got_response': False,
            'playlist_code': str(playlist_code)
        }

    # Apply config stuff that can take effect immediately (party name, etc).
    _config_server()

    # Launch the server only the first time through;
    # after that it will be self-sustaining.
    if not app.launched_server:

        # Now sit around until we're signed in and then kick off the server.
        with _ba.Context('ui'):

            def do_it() -> None:
                if _ba.get_account_state() == 'signed_in':
                    can_launch = False

                    # If we're trying to fetch a playlist, we do that first.
                    if app.server_playlist_fetch is not None:

                        # Send request if we haven't.
                        if not app.server_playlist_fetch['sent_request']:

                            def on_playlist_fetch_response(
                                    result: Optional[Dict[str, Any]]) -> None:
                                if result is None:
                                    print('Error fetching playlist; aborting.')
                                    sys.exit(-1)

                                # Once we get here we simply modify our
                                # config to use this playlist.
                                type_name = ('teams' if result['playlistType']
                                             == 'Team Tournament' else
                                             'ffa' if result['playlistType']
                                             == 'Free-for-All' else '??')
                                print(('Playlist \'' + result['playlistName'] +
                                       '\' (' + type_name +
                                       ') downloaded; running...'))
                                assert app.server_playlist_fetch is not None
                                app.server_playlist_fetch['got_response'] = (
                                    True)
                                app.server_config['session_type'] = type_name
                                app.server_config['playlist_name'] = (
                                    result['playlistName'])

                            print(('Requesting shared-playlist ' + str(
                                app.server_playlist_fetch['playlist_code']) +
                                   '...'))
                            app.server_playlist_fetch['sent_request'] = True
                            _ba.add_transaction(
                                {
                                    'type':
                                    'IMPORT_PLAYLIST',
                                    'code':
                                    app.server_playlist_fetch['playlist_code'],
                                    'overwrite':
                                    True
                                },
                                callback=on_playlist_fetch_response)
                            _ba.run_transactions()

                        # If we got a valid result, forget the fetch ever
                        # existed and move on.
                        if app.server_playlist_fetch['got_response']:
                            app.server_playlist_fetch = None
                            can_launch = True
                    else:
                        can_launch = True
                    if can_launch:
                        app.run_server_wait_timer = None
                        _ba.pushcall(launch_server_session)

            app.run_server_wait_timer = _ba.Timer(0.25,
                                                  do_it,
                                                  timetype=TimeType.REAL,
                                                  repeat=True)
        app.launched_server = True
예제 #20
0
 def setup_low_life_warning_sound(self) -> None:
     """Set up a beeping noise to play when any players are near death."""
     self._life_warning_beep = None
     self._life_warning_beep_timer = _ba.Timer(
         1.0, WeakCall(self._update_life_warning), repeat=True)
예제 #21
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)
예제 #22
0
파일: _app.py 프로젝트: zwl1671/ballistica
    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