Ejemplo n.º 1
0
    def add_actor_weak_ref(self, actor: ba.Actor) -> None:
        """Add a weak-reference to a ba.Actor to the ba.Activity.

        (called by the ba.Actor base class)
        """
        from ba import _actor as bsactor
        if not isinstance(actor, bsactor.Actor):
            raise TypeError('non-actor passed to add_actor_weak_ref')

        # Make sure our pruning is happening...
        assert (not self.has_transitioned_in()
                or _ba.time() - self._last_prune_dead_actors_time < 10.0)

        self._actor_weak_refs.append(weakref.ref(actor))
Ejemplo n.º 2
0
def show_post_purchase_message() -> None:
    """(internal)"""
    from ba._lang import Lstr
    from ba._enums import TimeType
    app = _ba.app
    cur_time = _ba.time(TimeType.REAL)
    if (app.last_post_purchase_message_time is None
            or cur_time - app.last_post_purchase_message_time > 3.0):
        app.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'))
Ejemplo n.º 3
0
    def add_actor_weak_ref(self, actor: ba.Actor) -> None:
        """Add a weak-reference to a ba.Actor to the ba.Activity.

        (called by the ba.Actor base class)
        """
        from ba import _actor as bsactor
        if not isinstance(actor, bsactor.Actor):
            raise TypeError('non-actor passed to add_actor_weak_ref')
        if (self.has_transitioned_in()
                and _ba.time() - self._last_prune_dead_actors_time > 10.0):
            print_error('it looks like nodes/actors are '
                        'not being pruned in your activity;'
                        ' did you call Activity.on_transition_in()'
                        ' from your subclass?; ' + str(self) + ' (loc. b)')
        self._actor_weak_refs.append(weakref.ref(actor))
Ejemplo n.º 4
0
 def __init__(self, settings: Dict[str, Any]):
     super().__init__(settings)
     self.transition_time = 0.5
     self.inherits_tint = True
     self.inherits_camera_vr_offset = True
     self.use_fixed_vr_overlay = True
     self.default_music: Optional[MusicType] = MusicType.SCORES
     self._birth_time = _ba.time()
     self._min_view_time = 5.0
     self._allow_server_restart = False
     self._background: Optional[ba.Actor] = None
     self._tips_text: Optional[ba.Actor] = None
     self._kicked_off_server_shutdown = False
     self._kicked_off_server_restart = False
     self._default_show_tips = True
     self._custom_continue_message: Optional[ba.Lstr] = None
Ejemplo n.º 5
0
    def retain_actor(self, actor: ba.Actor) -> None:
        """Add a strong-reference to a ba.Actor to this Activity.

        The reference will be lazily released once ba.Actor.exists()
        returns False for the Actor. The ba.Actor.autoretain() method
        is a convenient way to access this same functionality.
        """
        from ba import _actor as bsactor
        if not isinstance(actor, bsactor.Actor):
            raise TypeError('non-actor passed to retain_actor')

        # Make sure our pruning is happening...
        assert (not self.has_transitioned_in()
                or _ba.time() - self._last_prune_dead_actors_time < 10.0)

        self._actor_refs.append(actor)
Ejemplo n.º 6
0
    def retain_actor(self, actor: ba.Actor) -> None:
        """Add a strong-reference to a ba.Actor to this Activity.

        The reference will be lazily released once ba.Actor.exists()
        returns False for the Actor. The ba.Actor.autoretain() method
        is a convenient way to access this same functionality.
        """
        from ba import _actor as bsactor
        if not isinstance(actor, bsactor.Actor):
            raise TypeError('non-actor passed to retain_actor')
        if (self.has_transitioned_in()
                and _ba.time() - self._last_prune_dead_actors_time > 10.0):
            print_error('it looks like nodes/actors are not'
                        ' being pruned in your activity;'
                        ' did you call Activity.on_transition_in()'
                        ' from your subclass?; ' + str(self) + ' (loc. a)')
        self._actor_refs.append(actor)
Ejemplo n.º 7
0
 def _launch_end_session_activity(self) -> None:
     """(internal)"""
     from ba._activitytypes import EndSessionActivity
     from ba._enums import TimeType
     with _ba.Context(self):
         curtime = _ba.time(TimeType.REAL)
         if self._ending:
             # Ignore repeats unless its been a while.
             assert self._launch_end_session_activity_time is not None
             since_last = (curtime - self._launch_end_session_activity_time)
             if since_last < 30.0:
                 return
             print_error(
                 '_launch_end_session_activity called twice (since_last=' +
                 str(since_last) + ')')
         self._launch_end_session_activity_time = curtime
         self.setactivity(_ba.newactivity(EndSessionActivity))
         self._wants_to_end = False
         self._ending = True  # Prevent further actions.
Ejemplo n.º 8
0
    def do_remove_in_game_ads_message(self) -> None:
        """(internal)"""
        from ba._lang import Lstr
        from ba._enums import TimeType

        # Print this message once every 10 minutes at most.
        tval = _ba.time(TimeType.REAL)
        if (self.last_in_game_ad_remove_message_show_time is None or
            (tval - self.last_in_game_ad_remove_message_show_time > 60 * 10)):
            self.last_in_game_ad_remove_message_show_time = tval
            with _ba.Context('ui'):
                _ba.timer(
                    1.0,
                    lambda: _ba.screenmessage(Lstr(
                        resource='removeInGameAdsText',
                        subs=[('${PRO}',
                               Lstr(resource='store.bombSquadProNameText')),
                              ('${APP_NAME}', Lstr(resource='titleText'))]),
                                              color=(1, 1, 0)),
                    timetype=TimeType.REAL)
Ejemplo n.º 9
0
    def __init__(self, settings: dict):
        """Creates an Activity in the current ba.Session.

        The activity will not be actually run until ba.Session.setactivity
        is called. 'settings' should be a dict of key/value pairs specific
        to the activity.

        Activities should preload as much of their media/etc as possible in
        their constructor, but none of it should actually be used until they
        are transitioned in.
        """
        super().__init__()

        # Create our internal engine data.
        self._activity_data = _ba.register_activity(self)

        assert isinstance(settings, dict)
        assert _ba.getactivity() is self

        self._globalsnode: Optional[ba.Node] = None

        # Player/Team types should have been specified as type args;
        # grab those.
        self._playertype: type[PlayerType]
        self._teamtype: type[TeamType]
        self._setup_player_and_team_types()

        # FIXME: Relocate or remove the need for this stuff.
        self.paused_text: Optional[ba.Actor] = None

        self._session = weakref.ref(_ba.getsession())

        # Preloaded data for actors, maps, etc; indexed by type.
        self.preloads: dict[type, Any] = {}

        # Hopefully can eventually kill this; activities should
        # validate/store whatever settings they need at init time
        # (in a more type-safe way).
        self.settings_raw = settings

        self._has_transitioned_in = False
        self._has_begun = False
        self._has_ended = False
        self._activity_death_check_timer: Optional[ba.Timer] = None
        self._expired = False
        self._delay_delete_players: list[PlayerType] = []
        self._delay_delete_teams: list[TeamType] = []
        self._players_that_left: list[weakref.ref[PlayerType]] = []
        self._teams_that_left: list[weakref.ref[TeamType]] = []
        self._transitioning_out = False

        # A handy place to put most actors; this list is pruned of dead
        # actors regularly and these actors are insta-killed as the activity
        # is dying.
        self._actor_refs: list[ba.Actor] = []
        self._actor_weak_refs: list[weakref.ref[ba.Actor]] = []
        self._last_prune_dead_actors_time = _ba.time()
        self._prune_dead_actors_timer: Optional[ba.Timer] = None

        self.teams = []
        self.players = []

        self.lobby = None
        self._stats: Optional[ba.Stats] = None
        self._customdata: Optional[dict] = {}
Ejemplo n.º 10
0
    def __init__(self, settings: Dict[str, Any]):
        """Creates an activity in the current ba.Session.

        The activity will not be actually run until ba.Session.set_activity()
        is called. 'settings' should be a dict of key/value pairs specific
        to the activity.

        Activities should preload as much of their media/etc as possible in
        their constructor, but none of it should actually be used until they
        are transitioned in.
        """
        super().__init__()

        # FIXME: Relocate this stuff.
        self.sharedobjs: Dict[str, Any] = {}
        self.paused_text: Optional[ba.Actor] = None
        self.spaz_respawn_icons_right: Dict[int, RespawnIcon]

        # Create our internal engine data.
        self._activity_data = _ba.register_activity(self)

        session = _ba.getsession()
        if session is None:
            raise Exception("No current session")
        self._session = weakref.ref(session)

        # Preloaded data for actors, maps, etc; indexed by type.
        self.preloads: Dict[Type, Any] = {}

        if not isinstance(settings, dict):
            raise Exception("expected dict for settings")
        if _ba.getactivity(doraise=False) is not self:
            raise Exception('invalid context state')

        self.settings = settings

        self._has_transitioned_in = False
        self._has_begun = False
        self._has_ended = False
        self._should_end_immediately = False
        self._should_end_immediately_results: (
            Optional[ba.TeamGameResults]) = None
        self._should_end_immediately_delay = 0.0
        self._called_activity_on_transition_in = False
        self._called_activity_on_begin = False

        self._activity_death_check_timer: Optional[ba.Timer] = None
        self._expired = False

        # Whether to print every time a player dies. This can be pertinent
        # in games such as Death-Match but can be annoying in games where it
        # doesn't matter.
        self.announce_player_deaths = False

        # Joining activities are for waiting for initial player joins.
        # They are treated slightly differently than regular activities,
        # mainly in that all players are passed to the activity at once
        # instead of as each joins.
        self.is_joining_activity = False

        # Whether game-time should still progress when in menus/etc.
        self.allow_pausing = False

        # Whether idle players can potentially be kicked (should not happen in
        # menus/etc).
        self.allow_kick_idle_players = True

        # In vr mode, this determines whether overlay nodes (text, images, etc)
        # are created at a fixed position in space or one that moves based on
        # the current map. Generally this should be on for games and off for
        # transitions/score-screens/etc. that persist between maps.
        self.use_fixed_vr_overlay = False

        # If True, runs in slow motion and turns down sound pitch.
        self.slow_motion = False

        # Set this to True to inherit slow motion setting from previous
        # activity (useful for transitions to avoid hitches).
        self.inherits_slow_motion = False

        # Set this to True to keep playing the music from the previous activity
        # (without even restarting it).
        self.inherits_music = False

        # Set this to true to inherit VR camera offsets from the previous
        # activity (useful for preventing sporadic camera movement
        # during transitions).
        self.inherits_camera_vr_offset = False

        # Set this to true to inherit (non-fixed) VR overlay positioning from
        # the previous activity (useful for prevent sporadic overlay jostling
        # during transitions).
        self.inherits_vr_overlay_center = False

        # Set this to true to inherit screen tint/vignette colors from the
        # previous activity (useful to prevent sudden color changes during
        # transitions).
        self.inherits_tint = False

        # If the activity fades or transitions in, it should set the length of
        # time here so that previous activities will be kept alive for that
        # long (avoiding 'holes' in the screen)
        # This value is given in real-time seconds.
        self.transition_time = 0.0

        # Is it ok to show an ad after this activity ends before showing
        # the next activity?
        self.can_show_ad_on_death = False

        # This gets set once another activity has begun transitioning in but
        # before this one is killed. The on_transition_out() method is also
        # called at this time.  Make sure to not assign player inputs,
        # change music, or anything else with global implications once this
        # happens.
        self._transitioning_out = False

        # A handy place to put most actors; this list is pruned of dead
        # actors regularly and these actors are insta-killed as the activity
        # is dying.
        self._actor_refs: List[ba.Actor] = []
        self._actor_weak_refs: List[ReferenceType[ba.Actor]] = []
        self._last_dead_object_prune_time = _ba.time()

        # This stuff gets filled in just before on_begin() is called.
        self.teams = []
        self.players = []
        self._stats: Optional[ba.Stats] = None

        self.lobby = None
        self._prune_dead_objects_timer: Optional[ba.Timer] = None
Ejemplo n.º 11
0
def get_available_sale_time(tab: str) -> Optional[int]:
    """(internal)"""
    # pylint: disable=too-many-branches
    # pylint: disable=too-many-nested-blocks
    # pylint: disable=too-many-locals
    try:
        import datetime
        from ba._generated.enums import TimeType, TimeFormat
        app = _ba.app
        sale_times: List[Optional[int]] = []

        # Calc time for our pro sale (old special case).
        if tab == 'extras':
            config = app.config
            if app.accounts.have_pro():
                return None

            # If we haven't calced/loaded start times yet.
            if app.pro_sale_start_time is None:

                # If we've got a time-remaining in our config, start there.
                if 'PSTR' in config:
                    app.pro_sale_start_time = int(
                        _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS))
                    app.pro_sale_start_val = config['PSTR']
                else:

                    # We start the timer once we get the duration from
                    # the server.
                    start_duration = _ba.get_account_misc_read_val(
                        'proSaleDurationMinutes', None)
                    if start_duration is not None:
                        app.pro_sale_start_time = int(
                            _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS))
                        app.pro_sale_start_val = (60000 * start_duration)

                    # If we haven't heard from the server yet, no sale..
                    else:
                        return None

            assert app.pro_sale_start_val is not None
            val: Optional[int] = max(
                0, app.pro_sale_start_val -
                (_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) -
                 app.pro_sale_start_time))

            # Keep the value in the config up to date. I suppose we should
            # write the config occasionally but it should happen often enough
            # for other reasons.
            config['PSTR'] = val
            if val == 0:
                val = None
            sale_times.append(val)

        # Now look for sales in this tab.
        sales_raw = _ba.get_account_misc_read_val('sales', {})
        store_layout = get_store_layout()
        for section in store_layout[tab]:
            for item in section['items']:
                if item in sales_raw:
                    if not _ba.get_purchased(item):
                        to_end = ((datetime.datetime.utcfromtimestamp(
                            sales_raw[item]['e']) -
                                   datetime.datetime.utcnow()).total_seconds())
                        if to_end > 0:
                            sale_times.append(int(to_end * 1000))

        # Return the smallest time I guess?
        sale_times_int = [t for t in sale_times if isinstance(t, int)]
        return min(sale_times_int) if sale_times_int else None

    except Exception:
        from ba import _error
        _error.print_exception('error calcing sale time')
        return None
Ejemplo n.º 12
0
    def __init__(self, settings: Dict[str, Any]):
        """Creates an Activity in the current ba.Session.

        The activity will not be actually run until ba.Session.set_activity()
        is called. 'settings' should be a dict of key/value pairs specific
        to the activity.

        Activities should preload as much of their media/etc as possible in
        their constructor, but none of it should actually be used until they
        are transitioned in.
        """
        super().__init__()

        # Create our internal engine data.
        self._activity_data = _ba.register_activity(self)

        # Player/Team types should have been specified as type args;
        # grab those.
        self._playertype: Type[PlayerType]
        self._teamtype: Type[TeamType]
        self._setup_player_and_team_types()

        # FIXME: Relocate or remove the need for this stuff.
        self.sharedobjs: Dict[str, Any] = {}
        self.paused_text: Optional[ba.Actor] = None
        self.spaz_respawn_icons_right: Dict[int, RespawnIcon]

        session = _ba.getsession()
        if session is None:
            raise RuntimeError('No current session')
        self._session = weakref.ref(session)

        # Preloaded data for actors, maps, etc; indexed by type.
        self.preloads: Dict[Type, Any] = {}

        if not isinstance(settings, dict):
            raise TypeError('expected dict for settings')
        if _ba.getactivity(doraise=False) is not self:
            raise Exception('invalid context state')

        # Hopefully can eventually kill this; activities should
        # validate/store whatever settings they need at init time
        # (in a more type-safe way).
        self.settings_raw = settings

        self._has_transitioned_in = False
        self._has_begun = False
        self._has_ended = False
        self._should_end_immediately = False
        self._should_end_immediately_results: (
            Optional[ba.TeamGameResults]) = None
        self._should_end_immediately_delay = 0.0
        self._called_activity_on_transition_in = False
        self._called_activity_on_begin = False

        self._activity_death_check_timer: Optional[ba.Timer] = None
        self._expired = False

        # This gets set once another activity has begun transitioning in but
        # before this one is killed. The on_transition_out() method is also
        # called at this time.  Make sure to not assign player inputs,
        # change music, or anything else with global implications once this
        # happens.
        self._transitioning_out = False

        # A handy place to put most actors; this list is pruned of dead
        # actors regularly and these actors are insta-killed as the activity
        # is dying.
        self._actor_refs: List[ba.Actor] = []
        self._actor_weak_refs: List[ReferenceType[ba.Actor]] = []
        self._last_prune_dead_actors_time = _ba.time()
        self._prune_dead_actors_timer: Optional[ba.Timer] = None

        # This stuff gets filled in just before on_begin() is called.
        self.teams = []
        self.players = []
        self.lobby = None
        self._stats: Optional[ba.Stats] = None
Ejemplo n.º 13
0
    def show_completion_banner(self, sound: bool = True) -> None:
        """Create the banner/sound for an acquired achievement announcement."""
        from ba import _account
        from ba import _gameutils
        from bastd.actor.text import Text
        from bastd.actor.image import Image
        from ba._general import WeakCall
        from ba._lang import Lstr
        from ba._messages import DieMessage
        from ba._enums import TimeType, SpecialChar
        app = _ba.app
        app.last_achievement_display_time = _ba.time(TimeType.REAL)

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

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

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

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

        in_time = 0.300
        out_time = 3.5

        base_vr_depth = 200

        # Find the first free slot.
        i = 0
        while True:
            if i not in app.achievement_completion_banner_slots:
                app.achievement_completion_banner_slots.add(i)
                self._completion_banner_slot = i

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

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

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

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

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

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

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

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

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

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

        for actor in objs:
            _ba.timer(out_time + 1.000,
                      WeakCall(actor.handlemessage, DieMessage()))
def animate(node: ba.Node,
            attr: str,
            keys: dict[float, float],
            loop: bool = False,
            offset: float = 0,
            timetype: ba.TimeType = TimeType.SIM,
            timeformat: ba.TimeFormat = TimeFormat.SECONDS,
            suppress_format_warning: bool = False) -> ba.Node:
    """Animate values on a target ba.Node.

    Category: Gameplay Functions

    Creates an 'animcurve' node with the provided values and time as an input,
    connect it to the provided attribute, and set it to die with the target.
    Key values are provided as time:value dictionary pairs.  Time values are
    relative to the current time. By default, times are specified in seconds,
    but timeformat can also be set to MILLISECONDS to recreate the old behavior
    (prior to ba 1.5) of taking milliseconds. Returns the animcurve node.
    """
    if timetype is TimeType.SIM:
        driver = 'time'
    else:
        raise Exception('FIXME; only SIM timetype is supported currently.')
    items = list(keys.items())
    items.sort()

    # Temp sanity check while we transition from milliseconds to seconds
    # based time values.
    if __debug__:
        if not suppress_format_warning:
            for item in items:
                _ba.time_format_check(timeformat, item[0])

    curve = _ba.newnode('animcurve',
                        owner=node,
                        name='Driving ' + str(node) + ' \'' + attr + '\'')

    if timeformat is TimeFormat.SECONDS:
        mult = 1000
    elif timeformat is TimeFormat.MILLISECONDS:
        mult = 1
    else:
        raise ValueError(f'invalid timeformat value: {timeformat}')

    curve.times = [int(mult * time) for time, val in items]
    curve.offset = _ba.time(timeformat=TimeFormat.MILLISECONDS) + int(
        mult * offset)
    curve.values = [val for time, val in items]
    curve.loop = loop

    # If we're not looping, set a timer to kill this curve
    # after its done its job.
    # FIXME: Even if we are looping we should have a way to die once we
    #  get disconnected.
    if not loop:
        _ba.timer(int(mult * items[-1][0]) + 1000,
                  curve.delete,
                  timeformat=TimeFormat.MILLISECONDS)

    # Do the connects last so all our attrs are in place when we push initial
    # values through.

    # We operate in either activities or sessions..
    try:
        globalsnode = _ba.getactivity().globalsnode
    except ActivityNotFoundError:
        globalsnode = _ba.getsession().sessionglobalsnode

    globalsnode.connectattr(driver, curve, 'in')
    curve.connectattr('out', node, attr)
    return curve
def animate_array(node: ba.Node,
                  attr: str,
                  size: int,
                  keys: dict[float, Sequence[float]],
                  loop: bool = False,
                  offset: float = 0,
                  timetype: ba.TimeType = TimeType.SIM,
                  timeformat: ba.TimeFormat = TimeFormat.SECONDS,
                  suppress_format_warning: bool = False) -> None:
    """Animate an array of values on a target ba.Node.

    Category: Gameplay Functions

    Like ba.animate(), but operates on array attributes.
    """
    # pylint: disable=too-many-locals
    combine = _ba.newnode('combine', owner=node, attrs={'size': size})
    if timetype is TimeType.SIM:
        driver = 'time'
    else:
        raise Exception('FIXME: Only SIM timetype is supported currently.')
    items = list(keys.items())
    items.sort()

    # Temp sanity check while we transition from milliseconds to seconds
    # based time values.
    if __debug__:
        if not suppress_format_warning:
            for item in items:
                # (PyCharm seems to think item is a float, not a tuple)
                _ba.time_format_check(timeformat, item[0])

    if timeformat is TimeFormat.SECONDS:
        mult = 1000
    elif timeformat is TimeFormat.MILLISECONDS:
        mult = 1
    else:
        raise ValueError('invalid timeformat value: "' + str(timeformat) + '"')

    # We operate in either activities or sessions..
    try:
        globalsnode = _ba.getactivity().globalsnode
    except ActivityNotFoundError:
        globalsnode = _ba.getsession().sessionglobalsnode

    for i in range(size):
        curve = _ba.newnode('animcurve',
                            owner=node,
                            name=('Driving ' + str(node) + ' \'' + attr +
                                  '\' member ' + str(i)))
        globalsnode.connectattr(driver, curve, 'in')
        curve.times = [int(mult * time) for time, val in items]
        curve.values = [val[i] for time, val in items]
        curve.offset = _ba.time(timeformat=TimeFormat.MILLISECONDS) + int(
            mult * offset)
        curve.loop = loop
        curve.connectattr('out', combine, 'input' + str(i))

        # If we're not looping, set a timer to kill this
        # curve after its done its job.
        if not loop:
            # (PyCharm seems to think item is a float, not a tuple)
            _ba.timer(int(mult * items[-1][0]) + 1000,
                      curve.delete,
                      timeformat=TimeFormat.MILLISECONDS)
    combine.connectattr('output', node, attr)

    # If we're not looping, set a timer to kill the combine once
    # the job is done.
    # FIXME: Even if we are looping we should have a way to die
    #  once we get disconnected.
    if not loop:
        # (PyCharm seems to think item is a float, not a tuple)
        _ba.timer(int(mult * items[-1][0]) + 1000,
                  combine.delete,
                  timeformat=TimeFormat.MILLISECONDS)
Ejemplo n.º 16
0
def call_after_ad(call: Callable[[], Any]) -> None:
    """Run a call after potentially showing an ad."""
    # pylint: disable=too-many-statements
    # pylint: disable=too-many-branches
    # pylint: disable=too-many-locals
    from ba._account import have_pro
    from ba._enums import TimeType
    import time
    app = _ba.app
    show = True

    # No ads without net-connections, etc.
    if not _ba.can_show_ad():
        show = False
    if have_pro():
        show = False  # Pro disables interstitials.
    try:
        session = _ba.get_foreground_host_session()
        assert session is not None
        is_tournament = session.tournament_id is not None
    except Exception:
        is_tournament = False
    if is_tournament:
        show = False  # Never show ads during tournaments.

    if show:
        interval: Optional[float]
        launch_count = app.config.get('launchCount', 0)

        # If we're seeing short ads we may want to space them differently.
        interval_mult = (_ba.get_account_misc_read_val(
            'ads.shortIntervalMult', 1.0) if app.last_ad_was_short else 1.0)
        if app.ad_amt is None:
            if launch_count <= 1:
                app.ad_amt = _ba.get_account_misc_read_val(
                    'ads.startVal1', 0.99)
            else:
                app.ad_amt = _ba.get_account_misc_read_val(
                    'ads.startVal2', 1.0)
            interval = None
        else:
            # So far we're cleared to show; now calc our ad-show-threshold and
            # see if we should *actually* show (we reach our threshold faster
            # the longer we've been playing).
            base = 'ads' if _ba.has_video_ads() else 'ads2'
            min_lc = _ba.get_account_misc_read_val(base + '.minLC', 0.0)
            max_lc = _ba.get_account_misc_read_val(base + '.maxLC', 5.0)
            min_lc_scale = (_ba.get_account_misc_read_val(
                base + '.minLCScale', 0.25))
            max_lc_scale = (_ba.get_account_misc_read_val(
                base + '.maxLCScale', 0.34))
            min_lc_interval = (_ba.get_account_misc_read_val(
                base + '.minLCInterval', 360))
            max_lc_interval = (_ba.get_account_misc_read_val(
                base + '.maxLCInterval', 300))
            if launch_count < min_lc:
                lc_amt = 0.0
            elif launch_count > max_lc:
                lc_amt = 1.0
            else:
                lc_amt = ((float(launch_count) - min_lc) / (max_lc - min_lc))
            incr = (1.0 - lc_amt) * min_lc_scale + lc_amt * max_lc_scale
            interval = ((1.0 - lc_amt) * min_lc_interval +
                        lc_amt * max_lc_interval)
            app.ad_amt += incr
        assert app.ad_amt is not None
        if app.ad_amt >= 1.0:
            app.ad_amt = app.ad_amt % 1.0
            app.attempted_first_ad = True

        # After we've reached the traditional show-threshold once,
        # try again whenever its been INTERVAL since our last successful show.
        elif (app.attempted_first_ad
              and (app.last_ad_completion_time is None or
                   (interval is not None
                    and _ba.time(TimeType.REAL) - app.last_ad_completion_time >
                    (interval * interval_mult)))):
            # Reset our other counter too in this case.
            app.ad_amt = 0.0
        else:
            show = False

    # If we're *still* cleared to show, actually tell the system to show.
    if show:
        # As a safety-check, set up an object that will run
        # the completion callback if we've returned and sat for 10 seconds
        # (in case some random ad network doesn't properly deliver its
        # completion callback).
        class _Payload:
            def __init__(self, pcall: Callable[[], Any]):
                self._call = pcall
                self._ran = False

            def run(self, fallback: bool = False) -> None:
                """Run the fallback call (and issues a warning about it)."""
                if not self._ran:
                    if fallback:
                        print((
                            'ERROR: relying on fallback ad-callback! '
                            'last network: ' + app.last_ad_network + ' (set ' +
                            str(int(time.time() -
                                    app.last_ad_network_set_time)) +
                            's ago); purpose=' + app.last_ad_purpose))
                    _ba.pushcall(self._call)
                    self._ran = True

        payload = _Payload(call)
        with _ba.Context('ui'):
            _ba.timer(5.0,
                      lambda: payload.run(fallback=True),
                      timetype=TimeType.REAL)
        show_ad('between_game', on_completion_call=payload.run)
    else:
        _ba.pushcall(call)  # Just run the callback without the ad.
Ejemplo n.º 17
0
 def _prune_dead_objects(self) -> None:
     self._actor_refs = [a for a in self._actor_refs if a]
     self._actor_weak_refs = [a for a in self._actor_weak_refs if a()]
     self._last_dead_object_prune_time = _ba.time()