Beispiel #1
0
 def show_post_purchase_message(self) -> None:
     """(internal)"""
     from ba._language import Lstr
     from ba._generated.enums import TimeType
     cur_time = _ba.time(TimeType.REAL)
     if (self.last_post_purchase_message_time is None
             or cur_time - self.last_post_purchase_message_time > 3.0):
         self.last_post_purchase_message_time = cur_time
         with _ba.Context('ui'):
             _ba.screenmessage(Lstr(resource='updatingAccountText',
                                    fallback_resource='purchasingText'),
                               color=(0, 1, 0))
             _ba.playsound(_ba.getsound('click01'))
Beispiel #2
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)
Beispiel #3
0
    def on_account_state_changed(self) -> None:
        """(internal)"""
        from ba._language import Lstr

        # Run any pending promo codes we had queued up while not signed in.
        if _ba.get_account_state() == 'signed_in' and self.pending_promo_codes:
            for code in self.pending_promo_codes:
                _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()
            self.pending_promo_codes = []
Beispiel #4
0
 def display_name(self) -> ba.Lstr:
     """Return a ba.Lstr for this Achievement's name."""
     from ba._language import Lstr
     name: Union[ba.Lstr, str]
     try:
         if self._level_name != '':
             from ba._campaign import getcampaign
             campaignname, campaign_level = self._level_name.split(':')
             name = getcampaign(campaignname).getlevel(
                 campaign_level).displayname
         else:
             name = ''
     except Exception:
         name = ''
         print_exception()
     return Lstr(resource='achievements.' + self._name + '.name',
                 subs=[('${LEVEL}', name)])
Beispiel #5
0
    def _getname(self, full: bool = False) -> str:
        name_raw = name = self._profilenames[self._profileindex]
        clamp = False
        if name == '_random':
            try:
                name = (
                    self._sessionplayer.inputdevice.get_default_player_name())
            except Exception:
                print_exception('Error getting _random chooser name.')
                name = 'Invalid'
            clamp = not full
        elif name == '__account__':
            try:
                name = self._sessionplayer.inputdevice.get_v1_account_name(
                    full)
            except Exception:
                print_exception('Error getting account name for chooser.')
                name = 'Invalid'
            clamp = not full
        elif name == '_edit':
            # Explicitly flattening this to a str; it's only relevant on
            # the host so that's ok.
            name = (Lstr(
                resource='createEditPlayerText',
                fallback_resource='editProfileWindow.titleNewText').evaluate())
        else:
            # If we have a regular profile marked as global with an icon,
            # use it (for full only).
            if full:
                try:
                    if self._profiles[name_raw].get('global', False):
                        icon = (self._profiles[name_raw]['icon']
                                if 'icon' in self._profiles[name_raw] else
                                _ba.charstr(SpecialChar.LOGO))
                        name = icon + name
                except Exception:
                    print_exception('Error applying global icon.')
            else:
                # We now clamp non-full versions of names so there's at
                # least some hope of reading them in-game.
                clamp = True

        if clamp:
            if len(name) > 10:
                name = name[:10] + '...'
        return name
Beispiel #6
0
 def _calc_map_name(self, settings: dict) -> str:
     map_name: str
     if 'map' in settings:
         map_name = settings['map']
     else:
         # If settings doesn't specify a map, pick a random one from the
         # list of supported ones.
         unowned_maps = _map.get_unowned_maps()
         valid_maps: List[str] = [
             m for m in self.get_supported_maps(type(self.session))
             if m not in unowned_maps
         ]
         if not valid_maps:
             _ba.screenmessage(Lstr(resource='noValidMapsErrorText'))
             raise Exception('No valid maps')
         map_name = valid_maps[random.randrange(len(valid_maps))]
     return map_name
Beispiel #7
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),
                )
        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[ba.Vec3] = None
            if self._sessionplayer:
                if self._sessionplayer.activityplayer is not None:
                    try:
                        our_pos = self._sessionplayer.activityplayer.position
                    except NotFoundError:
                        pass
            if our_pos is None:
                return

            # Jitter position a bit since these often come in clusters.
            our_pos = _ba.Vec3(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))
Beispiel #9
0
    def _on_player_ready(self, chooser: ba.Chooser) -> None:
        """Called when a ba.Player has checked themself ready."""
        lobby = chooser.lobby
        activity = self._activity_weak()

        # This happens sometimes. That seems like it shouldn't be happening;
        # when would we have a session and a chooser with players but no
        # active activity?
        if activity is None:
            print('_on_player_ready called with no activity.')
            return

        # In joining-activities, we wait till all choosers are ready
        # and then create all players at once.
        if activity.is_joining_activity:
            if not lobby.check_all_ready():
                return
            choosers = lobby.get_choosers()
            min_players = self.min_players
            if len(choosers) >= min_players:
                for lch in lobby.get_choosers():
                    self._add_chosen_player(lch)
                lobby.remove_all_choosers()

                # Get our next activity going.
                self._complete_end_activity(activity, {})
            else:
                _ba.screenmessage(
                    Lstr(resource='notEnoughPlayersText',
                         subs=[('${COUNT}', str(min_players))]),
                    color=(1, 1, 0),
                )
                _ba.playsound(_ba.getsound('error'))

        # Otherwise just add players on the fly.
        else:
            self._add_chosen_player(chooser)
            lobby.remove_chooser(chooser.getplayer())
    def on_player_request(self, player: ba.SessionPlayer) -> bool:
        """Called when a new ba.Player wants to join the Session.

        This should return True or False to accept/reject.
        """

        # Limit player counts *unless* we're in a stress test.
        if _ba.app.stress_test_reset_timer is None:

            if len(self.sessionplayers) >= self.max_players:
                # Print a rejection message *only* to the client trying to
                # join (prevents spamming everyone else in the game).
                _ba.playsound(_ba.getsound('error'))
                _ba.screenmessage(Lstr(resource='playerLimitReachedText',
                                       subs=[('${COUNT}',
                                              str(self.max_players))]),
                                  color=(0.8, 0.0, 0.0),
                                  clients=[player.inputdevice.client_id],
                                  transient=True)
                return False

        _ba.playsound(_ba.getsound('dripity'))
        return True
Beispiel #11
0
def _update_text(self) -> None:
    assert self._text_node is not None
    if self._ready:

        # Once we're ready, we've saved the name, so lets ask the system
        # for it so we get appended numbers and stuff.
        text = Lstr(value=self._sessionplayer.getname(full=True))
        if self.characterchooser:
            text = Lstr(
                value='${A}\n${B}',
                subs=[('${A}', text),
                      ('${B}',
                       Lstr(value="" +
                            self._character_names[self._character_index]))])
            self._text_node.scale = 0.8
        else:
            text = Lstr(value='${A} (${B})',
                        subs=[('${A}', text),
                              ('${B}', Lstr(resource='readyText'))])
    else:
        text = Lstr(value=self._getname(full=True))
        self._text_node.scale = 1.0

    can_switch_teams = len(self.lobby.sessionteams) > 1

    # Flash as we're coming in.
    fin_color = _ba.safecolor(self.get_color()) + (1, )
    if not self._inited:
        animate_array(self._text_node, 'color', 4, {
            0.15: fin_color,
            0.25: (2, 2, 2, 1),
            0.35: fin_color
        })
    else:

        # Blend if we're in teams mode; switch instantly otherwise.
        if can_switch_teams:
            animate_array(self._text_node, 'color', 4, {
                0: self._text_node.color,
                0.1: fin_color
            })
        else:
            self._text_node.color = fin_color

    self._text_node.text = text
Beispiel #12
0
 def _show_remaining_achievements(self) -> None:
     # pylint: disable=cyclic-import
     from ba._language import Lstr
     from bastd.actor.text import Text
     ts_h_offs = 30
     v_offs = -200
     achievements = [
         a for a in _ba.app.ach.achievements_for_coop_level(
             self._get_coop_level_name()) if not a.complete
     ]
     vrmode = _ba.app.vr_mode
     if achievements:
         Text(Lstr(resource='achievementsRemainingText'),
              host_only=True,
              position=(ts_h_offs - 10 + 40, v_offs - 10),
              transition=Text.Transition.FADE_IN,
              scale=1.1,
              h_attach=Text.HAttach.LEFT,
              v_attach=Text.VAttach.TOP,
              color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1.0, 1.0),
              flatness=1.0 if vrmode else 0.6,
              shadow=1.0 if vrmode else 0.5,
              transition_delay=0.0,
              transition_out_delay=1.3
              if self.slow_motion else 4.0).autoretain()
         hval = 70
         vval = -50
         tdelay = 0.0
         for ach in achievements:
             tdelay += 0.05
             ach.create_display(hval + 40,
                                vval + v_offs,
                                0 + tdelay,
                                outdelay=1.3 if self.slow_motion else 4.0,
                                style='in_game')
             vval -= 55
Beispiel #13
0
 def _update_for_keyboard(self, keyboard: ba.InputDevice) -> None:
     from ba import _input
     punch_key = keyboard.get_button_name(
         _input.get_device_value(keyboard, 'buttonPunch'))
     self._press_to_punch = Lstr(resource='orText',
                                 subs=[('${A}',
                                        Lstr(value='\'${K}\'',
                                             subs=[('${K}', punch_key)])),
                                       ('${B}', self._press_to_punch)])
     bomb_key = keyboard.get_button_name(
         _input.get_device_value(keyboard, 'buttonBomb'))
     self._press_to_bomb = Lstr(resource='orText',
                                subs=[('${A}',
                                       Lstr(value='\'${K}\'',
                                            subs=[('${K}', bomb_key)])),
                                      ('${B}', self._press_to_bomb)])
     self._joinmsg = Lstr(value='${A} < ${B} >',
                          subs=[('${A}',
                                 Lstr(resource='pressPunchToJoinText')),
                                ('${B}', self._press_to_punch)])
Beispiel #14
0
    def show_completion_banner(self, sound: bool = True) -> None:
        """Create the banner/sound for an acquired achievement announcement."""
        from ba import _gameutils
        from bastd.actor.text import Text
        from bastd.actor.image import Image
        from ba._general import WeakCall
        from ba._language import Lstr
        from ba._messages import DieMessage
        from ba._generated.enums import TimeType, SpecialChar
        app = _ba.app
        app.ach.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.ach.achievement_completion_banner_slots:
                app.ach.achievement_completion_banner_slots.add(i)
                self._completion_banner_slot = i

                # Remove us from that slot when we close.
                # 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 app.accounts.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()))
Beispiel #15
0
    def create_display(self,
                       x: float,
                       y: float,
                       delay: float,
                       outdelay: float = None,
                       color: Sequence[float] = None,
                       style: str = 'post_game') -> list[ba.Actor]:
        """Create a display for the Achievement.

        Shows the Achievement icon, name, and description.
        """
        # pylint: disable=cyclic-import
        from ba._language import Lstr
        from ba._generated.enums import SpecialChar
        from ba._coopsession import CoopSession
        from bastd.actor.image import Image
        from bastd.actor.text import Text

        # Yeah this needs cleaning up.
        if style == 'post_game':
            in_game_colors = False
            in_main_menu = False
            h_attach = Text.HAttach.CENTER
            v_attach = Text.VAttach.CENTER
            attach = Image.Attach.CENTER
        elif style == 'in_game':
            in_game_colors = True
            in_main_menu = False
            h_attach = Text.HAttach.LEFT
            v_attach = Text.VAttach.TOP
            attach = Image.Attach.TOP_LEFT
        elif style == 'news':
            in_game_colors = True
            in_main_menu = True
            h_attach = Text.HAttach.CENTER
            v_attach = Text.VAttach.TOP
            attach = Image.Attach.TOP_CENTER
        else:
            raise ValueError('invalid style "' + style + '"')

        # Attempt to determine what campaign we're in
        # (so we know whether to show "hard mode only").
        if in_main_menu:
            hmo = False
        else:
            try:
                session = _ba.getsession()
                if isinstance(session, CoopSession):
                    campaign = session.campaign
                    assert campaign is not None
                    hmo = (self._hard_mode_only and campaign.name == 'Easy')
                else:
                    hmo = False
            except Exception:
                print_exception('Error determining campaign.')
                hmo = False

        objs: list[ba.Actor]

        if in_game_colors:
            objs = []
            out_delay_fin = (delay +
                             outdelay) if outdelay is not None else None
            if color is not None:
                cl1 = (2.0 * color[0], 2.0 * color[1], 2.0 * color[2],
                       color[3])
                cl2 = color
            else:
                cl1 = (1.5, 1.5, 2, 1.0)
                cl2 = (0.8, 0.8, 1.0, 1.0)

            if hmo:
                cl1 = (cl1[0], cl1[1], cl1[2], cl1[3] * 0.6)
                cl2 = (cl2[0], cl2[1], cl2[2], cl2[3] * 0.2)

            objs.append(
                Image(self.get_icon_texture(False),
                      host_only=True,
                      color=cl1,
                      position=(x - 25, y + 5),
                      attach=attach,
                      transition=Image.Transition.FADE_IN,
                      transition_delay=delay,
                      vr_depth=4,
                      transition_out_delay=out_delay_fin,
                      scale=(40, 40)).autoretain())
            txt = self.display_name
            txt_s = 0.85
            txt_max_w = 300
            objs.append(
                Text(txt,
                     host_only=True,
                     maxwidth=txt_max_w,
                     position=(x, y + 2),
                     transition=Text.Transition.FADE_IN,
                     scale=txt_s,
                     flatness=0.6,
                     shadow=0.5,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     color=cl2,
                     transition_delay=delay + 0.05,
                     transition_out_delay=out_delay_fin).autoretain())
            txt2_s = 0.62
            txt2_max_w = 400
            objs.append(
                Text(self.description_full
                     if in_main_menu else self.description,
                     host_only=True,
                     maxwidth=txt2_max_w,
                     position=(x, y - 14),
                     transition=Text.Transition.FADE_IN,
                     vr_depth=-5,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     scale=txt2_s,
                     flatness=1.0,
                     shadow=0.5,
                     color=cl2,
                     transition_delay=delay + 0.1,
                     transition_out_delay=out_delay_fin).autoretain())

            if hmo:
                txtactor = Text(
                    Lstr(resource='difficultyHardOnlyText'),
                    host_only=True,
                    maxwidth=txt2_max_w * 0.7,
                    position=(x + 60, y + 5),
                    transition=Text.Transition.FADE_IN,
                    vr_depth=-5,
                    h_attach=h_attach,
                    v_attach=v_attach,
                    h_align=Text.HAlign.CENTER,
                    v_align=Text.VAlign.CENTER,
                    scale=txt_s * 0.8,
                    flatness=1.0,
                    shadow=0.5,
                    color=(1, 1, 0.6, 1),
                    transition_delay=delay + 0.1,
                    transition_out_delay=out_delay_fin).autoretain()
                txtactor.node.rotate = 10
                objs.append(txtactor)

            # Ticket-award.
            award_x = -100
            objs.append(
                Text(_ba.charstr(SpecialChar.TICKET),
                     host_only=True,
                     position=(x + award_x + 33, y + 7),
                     transition=Text.Transition.FADE_IN,
                     scale=1.5,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     h_align=Text.HAlign.CENTER,
                     v_align=Text.VAlign.CENTER,
                     color=(1, 1, 1, 0.2 if hmo else 0.4),
                     transition_delay=delay + 0.05,
                     transition_out_delay=out_delay_fin).autoretain())
            objs.append(
                Text('+' + str(self.get_award_ticket_value()),
                     host_only=True,
                     position=(x + award_x + 28, y + 16),
                     transition=Text.Transition.FADE_IN,
                     scale=0.7,
                     flatness=1,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     h_align=Text.HAlign.CENTER,
                     v_align=Text.VAlign.CENTER,
                     color=cl2,
                     transition_delay=delay + 0.05,
                     transition_out_delay=out_delay_fin).autoretain())

        else:
            complete = self.complete
            objs = []
            c_icon = self.get_icon_color(complete)
            if hmo and not complete:
                c_icon = (c_icon[0], c_icon[1], c_icon[2], c_icon[3] * 0.3)
            objs.append(
                Image(self.get_icon_texture(complete),
                      host_only=True,
                      color=c_icon,
                      position=(x - 25, y + 5),
                      attach=attach,
                      vr_depth=4,
                      transition=Image.Transition.IN_RIGHT,
                      transition_delay=delay,
                      transition_out_delay=None,
                      scale=(40, 40)).autoretain())
            if complete:
                objs.append(
                    Image(_ba.gettexture('achievementOutline'),
                          host_only=True,
                          model_transparent=_ba.getmodel('achievementOutline'),
                          color=(2, 1.4, 0.4, 1),
                          vr_depth=8,
                          position=(x - 25, y + 5),
                          attach=attach,
                          transition=Image.Transition.IN_RIGHT,
                          transition_delay=delay,
                          transition_out_delay=None,
                          scale=(40, 40)).autoretain())
            else:
                if not complete:
                    award_x = -100
                    objs.append(
                        Text(_ba.charstr(SpecialChar.TICKET),
                             host_only=True,
                             position=(x + award_x + 33, y + 7),
                             transition=Text.Transition.IN_RIGHT,
                             scale=1.5,
                             h_attach=h_attach,
                             v_attach=v_attach,
                             h_align=Text.HAlign.CENTER,
                             v_align=Text.VAlign.CENTER,
                             color=(1, 1, 1, 0.4) if complete else
                             (1, 1, 1, (0.1 if hmo else 0.2)),
                             transition_delay=delay + 0.05,
                             transition_out_delay=None).autoretain())
                    objs.append(
                        Text('+' + str(self.get_award_ticket_value()),
                             host_only=True,
                             position=(x + award_x + 28, y + 16),
                             transition=Text.Transition.IN_RIGHT,
                             scale=0.7,
                             flatness=1,
                             h_attach=h_attach,
                             v_attach=v_attach,
                             h_align=Text.HAlign.CENTER,
                             v_align=Text.VAlign.CENTER,
                             color=((0.8, 0.93, 0.8, 1.0) if complete else
                                    (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
                             transition_delay=delay + 0.05,
                             transition_out_delay=None).autoretain())

                    # Show 'hard-mode-only' only over incomplete achievements
                    # when that's the case.
                    if hmo:
                        txtactor = Text(
                            Lstr(resource='difficultyHardOnlyText'),
                            host_only=True,
                            maxwidth=300 * 0.7,
                            position=(x + 60, y + 5),
                            transition=Text.Transition.FADE_IN,
                            vr_depth=-5,
                            h_attach=h_attach,
                            v_attach=v_attach,
                            h_align=Text.HAlign.CENTER,
                            v_align=Text.VAlign.CENTER,
                            scale=0.85 * 0.8,
                            flatness=1.0,
                            shadow=0.5,
                            color=(1, 1, 0.6, 1),
                            transition_delay=delay + 0.05,
                            transition_out_delay=None).autoretain()
                        assert txtactor.node
                        txtactor.node.rotate = 10
                        objs.append(txtactor)

            objs.append(
                Text(self.display_name,
                     host_only=True,
                     maxwidth=300,
                     position=(x, y + 2),
                     transition=Text.Transition.IN_RIGHT,
                     scale=0.85,
                     flatness=0.6,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     color=((0.8, 0.93, 0.8, 1.0) if complete else
                            (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
                     transition_delay=delay + 0.05,
                     transition_out_delay=None).autoretain())
            objs.append(
                Text(self.description_complete
                     if complete else self.description,
                     host_only=True,
                     maxwidth=400,
                     position=(x, y - 14),
                     transition=Text.Transition.IN_RIGHT,
                     vr_depth=-5,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     scale=0.62,
                     flatness=1.0,
                     color=((0.6, 0.6, 0.6, 1.0) if complete else
                            (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
                     transition_delay=delay + 0.1,
                     transition_out_delay=None).autoretain())
        return objs
Beispiel #16
0
def connection_failed_message() -> None:
    from ba._language import Lstr
    _ba.screenmessage(Lstr(resource='internal.connectionFailedText'),
                      color=(1, 0.5, 0))
    def player_scored(self,
                      player: ba.Player,
                      base_points: int = 1,
                      target: Sequence[float] = None,
                      kill: bool = False,
                      victim_player: ba.Player = None,
                      scale: float = 1.0,
                      color: Sequence[float] = None,
                      title: Union[str, ba.Lstr] = None,
                      screenmessage: bool = True,
                      display: bool = True,
                      importance: int = 1,
                      showpoints: bool = True,
                      big_message: bool = False) -> int:
        """Register a score for the player.

        Return value is actual score with multipliers and such factored in.
        """
        # FIXME: Tidy this up.
        # pylint: disable=cyclic-import
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-statements
        from bastd.actor.popuptext import PopupText
        from ba import _math
        from ba._gameactivity import GameActivity
        from ba._language import Lstr
        del victim_player  # Currently unused.
        name = player.getname()
        s_player = self._player_records[name]

        if kill:
            s_player.submit_kill(showpoints=showpoints)

        display_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0)

        if color is not None:
            display_color = color
        elif importance != 1:
            display_color = (1.0, 1.0, 0.4, 1.0)
        points = base_points

        # If they want a big announcement, throw a zoom-text up there.
        if display and big_message:
            try:
                assert self._activity is not None
                activity = self._activity()
                if isinstance(activity, GameActivity):
                    name_full = player.getname(full=True, icon=False)
                    activity.show_zoom_message(
                        Lstr(resource='nameScoresText',
                             subs=[('${NAME}', name_full)]),
                        color=_math.normalized_color(player.team.color))
            except Exception:
                print_exception('error showing big_message')

        # If we currently have a actor, pop up a score over it.
        if display and showpoints:
            our_pos = player.node.position if player.node else None
            if our_pos is not None:
                if target is None:
                    target = our_pos

                # If display-pos is *way* lower than us, raise it up
                # (so we can still see scores from dudes that fell off cliffs).
                display_pos = (target[0], max(target[1], our_pos[1] - 2.0),
                               min(target[2], our_pos[2] + 2.0))
                activity = self.getactivity()
                if activity is not None:
                    if title is not None:
                        sval = Lstr(value='+${A} ${B}',
                                    subs=[('${A}', str(points)),
                                          ('${B}', title)])
                    else:
                        sval = Lstr(value='+${A}',
                                    subs=[('${A}', str(points))])
                    PopupText(sval,
                              color=display_color,
                              scale=1.2 * scale,
                              position=display_pos).autoretain()

        # Tally kills.
        if kill:
            s_player.accum_kill_count += 1
            s_player.kill_count += 1

        # Report non-kill scorings.
        try:
            if screenmessage and not kill:
                _ba.screenmessage(Lstr(resource='nameScoresText',
                                       subs=[('${NAME}', name)]),
                                  top=True,
                                  color=player.color,
                                  image=player.get_icon())
        except Exception:
            print_exception('error announcing score')

        s_player.score += points
        s_player.accumscore += points

        # Inform a running game of the score.
        if points != 0:
            activity = self._activity() if self._activity is not None else None
            if activity is not None:
                activity.handlemessage(PlayerScoredMessage(score=points))

        return points
Beispiel #18
0
def temporarily_unavailable_message() -> None:
    from ba._language import Lstr
    _ba.playsound(_ba.getsound('error'))
    _ba.screenmessage(
        Lstr(resource='getTicketsWindow.unavailableTemporarilyText'),
        color=(1, 0, 0))
Beispiel #19
0
def purchase_not_valid_error() -> None:
    from ba._language import Lstr
    _ba.playsound(_ba.getsound('error'))
    _ba.screenmessage(Lstr(resource='store.purchaseNotValidError',
                           subs=[('${EMAIL}', '*****@*****.**')]),
                      color=(1, 0, 0))
Beispiel #20
0
    def __init__(self, vpos: float, sessionplayer: _ba.SessionPlayer,
                 lobby: 'Lobby') -> None:
        self._deek_sound = _ba.getsound('deek')
        self._click_sound = _ba.getsound('click01')
        self._punchsound = _ba.getsound('punch01')
        self._swish_sound = _ba.getsound('punchSwish')
        self._errorsound = _ba.getsound('error')
        self._mask_texture = _ba.gettexture('characterIconMask')
        self._vpos = vpos
        self._lobby = weakref.ref(lobby)
        self._sessionplayer = sessionplayer
        self._inited = False
        self._dead = False
        self._text_node: Optional[ba.Node] = None
        self._profilename = ''
        self._profilenames: List[str] = []
        self._ready: bool = False
        self._character_names: List[str] = []
        self._last_change: Sequence[Union[float, int]] = (0, 0)
        self._profiles: Dict[str, Dict[str, Any]] = {}

        app = _ba.app

        # Load available player profiles either from the local config or
        # from the remote device.
        self.reload_profiles()

        # Note: this is just our local index out of available teams; *not*
        # the team-id!
        self._selected_team_index: int = self.lobby.next_add_team

        # Store a persistent random character index and colors; we'll use this
        # for the '_random' profile. Let's use their input_device id to seed
        # it. This will give a persistent character for them between games
        # and will distribute characters nicely if everyone is random.
        self._random_color, self._random_highlight = (
            get_player_profile_colors(None))

        # To calc our random character we pick a random one out of our
        # unlocked list and then locate that character's index in the full
        # list.
        char_index_offset = app.lobby_random_char_index_offset
        self._random_character_index = (
            (sessionplayer.inputdevice.id + char_index_offset) %
            len(self._character_names))

        # Attempt to set an initial profile based on what was used previously
        # for this input-device, etc.
        self._profileindex = self._select_initial_profile()
        self._profilename = self._profilenames[self._profileindex]

        self._text_node = _ba.newnode('text',
                                      delegate=self,
                                      attrs={
                                          'position': (-100, self._vpos),
                                          'maxwidth': 160,
                                          'shadow': 0.5,
                                          'vr_depth': -20,
                                          'h_align': 'left',
                                          'v_align': 'center',
                                          'v_attach': 'top'
                                      })
        animate(self._text_node, 'scale', {0: 0, 0.1: 1.0})
        self.icon = _ba.newnode('image',
                                owner=self._text_node,
                                attrs={
                                    'position': (-130, self._vpos + 20),
                                    'mask_texture': self._mask_texture,
                                    'vr_depth': -10,
                                    'attach': 'topCenter'
                                })

        animate_array(self.icon, 'scale', 2, {0: (0, 0), 0.1: (45, 45)})

        # Set our initial name to '<choosing player>' in case anyone asks.
        self._sessionplayer.setname(
            Lstr(resource='choosingPlayerText').evaluate(), real=False)

        # Init these to our rando but they should get switched to the
        # selected profile (if any) right after.
        self._character_index = self._random_character_index
        self._color = self._random_color
        self._highlight = self._random_highlight

        self.update_from_profile()
        self.update_position()
        self._inited = True

        self._set_ready(False)
Beispiel #21
0
    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)
Beispiel #22
0
    def _show_scoreboard_info(self) -> None:
        """Create the game info display.

        This is the thing in the top left corner showing the name
        and short description of the game.
        """
        # pylint: disable=too-many-locals
        from ba._freeforallsession import FreeForAllSession
        from ba._gameutils import animate
        from ba._nodeactor import NodeActor
        sb_name = self.get_instance_scoreboard_display_string()

        # The description can be either a string or a sequence with args
        # to swap in post-translation.
        sb_desc_in = self.get_instance_description_short()
        sb_desc_l: Sequence
        if isinstance(sb_desc_in, str):
            sb_desc_l = [sb_desc_in]  # handle simple string case
        else:
            sb_desc_l = sb_desc_in
        if not isinstance(sb_desc_l[0], str):
            raise TypeError('Invalid format for instance description.')

        is_empty = (sb_desc_l[0] == '')
        subs = []
        for i in range(len(sb_desc_l) - 1):
            subs.append(('${ARG' + str(i + 1) + '}', str(sb_desc_l[i + 1])))
        translation = Lstr(translate=('gameDescriptions', sb_desc_l[0]),
                           subs=subs)
        sb_desc = translation
        vrmode = _ba.app.vr_mode
        yval = -34 if is_empty else -20
        yval -= 16
        sbpos = ((15, yval) if isinstance(self.session, FreeForAllSession) else
                 (15, yval))
        self._game_scoreboard_name_text = NodeActor(
            _ba.newnode('text',
                        attrs={
                            'text': sb_name,
                            'maxwidth': 300,
                            'position': sbpos,
                            'h_attach': 'left',
                            'vr_depth': 10,
                            'v_attach': 'top',
                            'v_align': 'bottom',
                            'color': (1.0, 1.0, 1.0, 1.0),
                            'shadow': 1.0 if vrmode else 0.6,
                            'flatness': 1.0 if vrmode else 0.5,
                            'scale': 1.1
                        }))

        assert self._game_scoreboard_name_text.node
        animate(self._game_scoreboard_name_text.node, 'opacity', {
            0: 0.0,
            1.0: 1.0
        })

        descpos = (((17, -44 +
                     10) if isinstance(self.session, FreeForAllSession) else
                    (17, -44 + 10)))
        self._game_scoreboard_description_text = NodeActor(
            _ba.newnode('text',
                        attrs={
                            'text':
                            sb_desc,
                            'maxwidth':
                            480,
                            'position':
                            descpos,
                            'scale':
                            0.7,
                            'h_attach':
                            'left',
                            'v_attach':
                            'top',
                            'v_align':
                            'top',
                            'shadow':
                            1.0 if vrmode else 0.7,
                            'flatness':
                            1.0 if vrmode else 0.8,
                            'color': (1, 1, 1, 1) if vrmode else
                            (0.9, 0.9, 0.9, 1.0)
                        }))

        assert self._game_scoreboard_description_text.node
        animate(self._game_scoreboard_description_text.node, 'opacity', {
            0: 0.0,
            1.0: 1.0
        })
Beispiel #23
0
 def get_team_display_string(cls, name: str) -> ba.Lstr:
     """Given a team name, returns a localized version of it."""
     return Lstr(translate=('teamNames', name))
Beispiel #24
0
def in_progress_message() -> None:
    from ba._language import Lstr
    _ba.playsound(_ba.getsound('error'))
    _ba.screenmessage(Lstr(resource='getTicketsWindow.inProgressText'),
                      color=(1, 0, 0))
Beispiel #25
0
    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._generated.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')
Beispiel #26
0
    def on_activity_end(self, activity: ba.Activity, results: Any) -> None:
        """Method override for co-op sessions.

        Jumps between co-op games and score screens.
        """
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-statements
        # pylint: disable=cyclic-import
        from ba._activitytypes import JoinActivity, TransitionActivity
        from ba._language import Lstr
        from ba._general import WeakCall
        from ba._coopgame import CoopGameActivity
        from ba._gameresults import GameResults
        from ba._score import ScoreType
        from ba._player import PlayerInfo
        from bastd.tutorial import TutorialActivity
        from bastd.activity.coopscore import CoopScoreScreen

        app = _ba.app

        # If we're running a TeamGameActivity we'll have a GameResults
        # as results. Otherwise its an old CoopGameActivity so its giving
        # us a dict of random stuff.
        if isinstance(results, GameResults):
            outcome = 'defeat'  # This can't be 'beaten'.
        else:
            outcome = '' if results is None else results.get('outcome', '')

        # If we're running with a gui and at any point we have no
        # in-game players, quit out of the session (this can happen if
        # someone leaves in the tutorial for instance).
        if not _ba.app.headless_mode:
            active_players = [p for p in self.sessionplayers if p.in_game]
            if not active_players:
                self.end()
                return

        # If we're in a between-round activity or a restart-activity,
        # hop into a round.
        if (isinstance(activity,
                       (JoinActivity, CoopScoreScreen, TransitionActivity))):

            if outcome == 'next_level':
                if self._next_game_instance is None:
                    raise RuntimeError()
                assert self._next_game_level_name is not None
                self.campaign_level_name = self._next_game_level_name
                next_game = self._next_game_instance
            else:
                next_game = self._current_game_instance

            # Special case: if we're coming from a joining-activity
            # and will be going into onslaught-training, show the
            # tutorial first.
            if (isinstance(activity, JoinActivity)
                    and self.campaign_level_name == 'Onslaught Training'
                    and not (app.demo_mode or app.arcade_mode)):
                if self._tutorial_activity is None:
                    raise RuntimeError('Tutorial not preloaded properly.')
                self.setactivity(self._tutorial_activity)
                self._tutorial_activity = None
                self._ran_tutorial_activity = True
                self._custom_menu_ui = []

            # Normal case; launch the next round.
            else:

                # Reset stats for the new activity.
                self.stats.reset()
                for player in self.sessionplayers:

                    # Skip players that are still choosing a team.
                    if player.in_game:
                        self.stats.register_sessionplayer(player)
                self.stats.setactivity(next_game)

                # Now flip the current activity..
                self.setactivity(next_game)

                if not (app.demo_mode or app.arcade_mode):
                    if self.tournament_id is not None:
                        self._custom_menu_ui = [{
                            'label':
                            Lstr(resource='restartText'),
                            'resume_on_call':
                            False,
                            'call':
                            WeakCall(self._on_tournament_restart_menu_press)
                        }]
                    else:
                        self._custom_menu_ui = [{
                            'label':
                            Lstr(resource='restartText'),
                            'call':
                            WeakCall(self.restart)
                        }]

        # If we were in a tutorial, just pop a transition to get to the
        # actual round.
        elif isinstance(activity, TutorialActivity):
            self.setactivity(_ba.newactivity(TransitionActivity))
        else:

            playerinfos: list[ba.PlayerInfo]

            # Generic team games.
            if isinstance(results, GameResults):
                playerinfos = results.playerinfos
                score = results.get_sessionteam_score(results.sessionteams[0])
                fail_message = None
                score_order = ('decreasing'
                               if results.lower_is_better else 'increasing')
                if results.scoretype in (ScoreType.SECONDS,
                                         ScoreType.MILLISECONDS):
                    scoretype = 'time'

                    # ScoreScreen wants hundredths of a second.
                    if score is not None:
                        if results.scoretype is ScoreType.SECONDS:
                            score *= 100
                        elif results.scoretype is ScoreType.MILLISECONDS:
                            score //= 10
                        else:
                            raise RuntimeError('FIXME')
                else:
                    if results.scoretype is not ScoreType.POINTS:
                        print(f'Unknown ScoreType:' f' "{results.scoretype}"')
                    scoretype = 'points'

            # Old coop-game-specific results; should migrate away from these.
            else:
                playerinfos = results.get('playerinfos')
                score = results['score'] if 'score' in results else None
                fail_message = (results['fail_message']
                                if 'fail_message' in results else None)
                score_order = (results['score_order']
                               if 'score_order' in results else 'increasing')
                activity_score_type = (activity.get_score_type() if isinstance(
                    activity, CoopGameActivity) else None)
                assert activity_score_type is not None
                scoretype = activity_score_type

            # Validate types.
            if playerinfos is not None:
                assert isinstance(playerinfos, list)
                assert (isinstance(i, PlayerInfo) for i in playerinfos)

            # Looks like we were in a round - check the outcome and
            # go from there.
            if outcome == 'restart':

                # This will pop up back in the same round.
                self.setactivity(_ba.newactivity(TransitionActivity))
            else:
                self.setactivity(
                    _ba.newactivity(
                        CoopScoreScreen, {
                            'playerinfos': playerinfos,
                            'score': score,
                            'fail_message': fail_message,
                            'score_order': score_order,
                            'score_type': scoretype,
                            'outcome': outcome,
                            'campaign': self.campaign,
                            'level': self.campaign_level_name
                        }))

        # No matter what, get the next 2 levels ready to go.
        self._update_on_deck_game_instances()
Beispiel #27
0
    def _add_chosen_player(self, chooser: ba.Chooser) -> ba.SessionPlayer:
        from ba._team import SessionTeam
        sessionplayer = chooser.getplayer()
        assert sessionplayer in self.sessionplayers, (
            'SessionPlayer not found in session '
            'player-list after chooser selection.')

        activity = self._activity_weak()
        assert activity is not None

        # Reset the player's input here, as it is probably
        # referencing the chooser which could inadvertently keep it alive.
        sessionplayer.resetinput()

        # We can pass it to the current activity if it has already begun
        # (otherwise it'll get passed once begin is called).
        pass_to_activity = (activity.has_begun()
                            and not activity.is_joining_activity)

        # However, if we're not allowing mid-game joins, don't actually pass;
        # just announce the arrival and say they'll partake next round.
        if pass_to_activity:
            if not self.allow_mid_activity_joins:
                pass_to_activity = False
                with _ba.Context(self):
                    _ba.screenmessage(
                        Lstr(resource='playerDelayedJoinText',
                             subs=[('${PLAYER}',
                                    sessionplayer.getname(full=True))]),
                        color=(0, 1, 0),
                    )

        # If we're a non-team session, each player gets their own team.
        # (keeps mini-game coding simpler if we can always deal with teams).
        if self.use_teams:
            sessionteam = chooser.sessionteam
        else:
            our_team_id = self._next_team_id
            self._next_team_id += 1
            sessionteam = SessionTeam(
                team_id=our_team_id,
                color=chooser.get_color(),
                name=chooser.getplayer().getname(full=True, icon=False),
            )

            # Add player's team to the Session.
            self.sessionteams.append(sessionteam)

            with _ba.Context(self):
                try:
                    self.on_team_join(sessionteam)
                except Exception:
                    print_exception(f'Error in on_team_join for {self}.')

            # Add player's team to the Activity.
            if pass_to_activity:
                activity.add_team(sessionteam)

        assert sessionplayer not in sessionteam.players
        sessionteam.players.append(sessionplayer)
        sessionplayer.setdata(team=sessionteam,
                              character=chooser.get_character_name(),
                              color=chooser.get_color(),
                              highlight=chooser.get_highlight())

        self.stats.register_sessionplayer(sessionplayer)
        if pass_to_activity:
            activity.add_player(sessionplayer)
        return sessionplayer
Beispiel #28
0
    def _show_tip(self) -> None:
        # pylint: disable=too-many-locals
        from ba._gameutils import animate, GameTip
        from ba._generated.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)
    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._language 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[ba.Vec3] = None
            if self._sessionplayer:
                if self._sessionplayer.activityplayer is not None:
                    try:
                        our_pos = self._sessionplayer.activityplayer.position
                    except NotFoundError:
                        pass
            if our_pos is None:
                return

            # Jitter position a bit since these often come in clusters.
            our_pos = _ba.Vec3(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)
Beispiel #30
0
def error_message() -> None:
    from ba._language import Lstr
    _ba.playsound(_ba.getsound('error'))
    _ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))