def spawn_player_spaz(self, player: PlayerType, position: Sequence[float] = (0, 0, 0), angle: float = None) -> MyPlayerSpaz: """Create and wire up a ba.PlayerSpaz for the provided ba.Player.""" # pylint: disable=too-many-locals # pylint: disable=cyclic-import name = player.getname() color = player.color highlight = player.highlight light_color = _math.normalized_color(color) display_color = _ba.safecolor(color, target_intensity=0.75) spaz = MyPlayerSpaz(color=color, highlight=highlight, character=player.character, player=player) player.actor = spaz assert spaz.node if position is None: # In teams-mode get our team-start-location. if isinstance(self.session, DualTeamSession): position = (self.map.get_start_position(player.team.id)) else: # Otherwise do free-for-all spawn locations. position = self.map.get_ffa_start_position(self.players) # If this is co-op and we're on Courtyard or Runaround, add the # material that allows us to collide with the player-walls. # FIXME: Need to generalize this. if isinstance(self.session, CoopSession) and self.map.getname() in [ 'Courtyard', 'Tower D' ]: mat = self.map.preloaddata['collide_with_wall_material'] assert isinstance(spaz.node.materials, tuple) assert isinstance(spaz.node.roller_materials, tuple) spaz.node.materials += (mat, ) spaz.node.roller_materials += (mat, ) spaz.node.name = name spaz.node.name_color = display_color spaz.connect_controls_to_player() # Move to the stand position and add a flash of light. spaz.handlemessage( StandMessage( position, angle if angle is not None else random.uniform(0, 360))) _ba.playsound(self._spawn_sound, 1, position=spaz.node.position) light = _ba.newnode('light', attrs={'color': light_color}) spaz.node.connectattr('position', light, 'position') animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}) _ba.timer(0.5, light.delete) return spaz
def _standard_time_limit_tick(self) -> None: from ba._gameutils import animate assert self._standard_time_limit_time is not None self._standard_time_limit_time -= 1 if self._standard_time_limit_time <= 10: if self._standard_time_limit_time == 10: assert self._standard_time_limit_text is not None assert self._standard_time_limit_text.node self._standard_time_limit_text.node.scale = 1.3 self._standard_time_limit_text.node.position = (-30, -45) cnode = _ba.newnode('combine', owner=self._standard_time_limit_text.node, attrs={'size': 4}) cnode.connectattr('output', self._standard_time_limit_text.node, 'color') animate(cnode, 'input0', {0: 1, 0.15: 1}, loop=True) animate(cnode, 'input1', {0: 1, 0.15: 0.5}, loop=True) animate(cnode, 'input2', {0: 0.1, 0.15: 0.0}, loop=True) cnode.input3 = 1.0 _ba.playsound(_ba.getsound('tick')) if self._standard_time_limit_time <= 0: self._standard_time_limit_timer = None self.end_game() node = _ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': 'center', 'h_align': 'center', 'color': (1, 0.7, 0, 1), 'position': (0, -90), 'scale': 1.2, 'text': Lstr(resource='timeExpiredText') }) _ba.playsound(_ba.getsound('refWhistle')) animate(node, 'scale', {0.0: 0.0, 0.1: 1.4, 0.15: 1.2})
def fade_to_red(self) -> None: """Fade the screen to red; (such as when the good guys have lost).""" from ba import _gameutils c_existing = self.globalsnode.tint cnode = _ba.newnode('combine', attrs={ 'input0': c_existing[0], 'input1': c_existing[1], 'input2': c_existing[2], 'size': 3 }) _gameutils.animate(cnode, 'input1', {0: c_existing[1], 2.0: 0}) _gameutils.animate(cnode, 'input2', {0: c_existing[2], 2.0: 0}) cnode.connectattr('output', self.globalsnode, 'tint')
def _tournament_time_limit_tick(self) -> None: from ba._gameutils import animate assert self._tournament_time_limit is not None self._tournament_time_limit -= 1 if self._tournament_time_limit <= 10: if self._tournament_time_limit == 10: assert self._tournament_time_limit_title_text is not None assert self._tournament_time_limit_title_text.node assert self._tournament_time_limit_text is not None assert self._tournament_time_limit_text.node self._tournament_time_limit_title_text.node.scale = 1.0 self._tournament_time_limit_text.node.scale = 1.3 self._tournament_time_limit_title_text.node.position = (80, 85) self._tournament_time_limit_text.node.position = (80, 60) cnode = _ba.newnode( 'combine', owner=self._tournament_time_limit_text.node, attrs={'size': 4}) cnode.connectattr('output', self._tournament_time_limit_title_text.node, 'color') cnode.connectattr('output', self._tournament_time_limit_text.node, 'color') animate(cnode, 'input0', {0: 1, 0.15: 1}, loop=True) animate(cnode, 'input1', {0: 1, 0.15: 0.5}, loop=True) animate(cnode, 'input2', {0: 0.1, 0.15: 0.0}, loop=True) cnode.input3 = 1.0 _ba.playsound(_ba.getsound('tick')) if self._tournament_time_limit <= 0: self._tournament_time_limit_timer = None self.end_game() tval = Lstr(resource='tournamentTimeExpiredText', fallback_resource='timeExpiredText') node = _ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': 'center', 'h_align': 'center', 'color': (1, 0.7, 0, 1), 'position': (0, -200), 'scale': 1.6, 'text': tval }) _ba.playsound(_ba.getsound('refWhistle')) animate(node, 'scale', {0: 0.0, 0.1: 1.4, 0.15: 1.2}) # Normally we just connect this to time, but since this is a bit of a # funky setup we just update it manually once per second. assert self._tournament_time_limit_text_input is not None assert self._tournament_time_limit_text_input.node self._tournament_time_limit_text_input.node.time2 = ( self._tournament_time_limit * 1000)
def _show_standard_scores_to_beat_ui(self, scores: List[Dict[str, Any]]) -> None: from efro.util import asserttype from ba._gameutils import timestring, animate from ba._nodeactor import NodeActor from ba._enums import TimeFormat display_type = self.get_score_type() if scores is not None: # Sort by originating date so that the most recent is first. scores.sort(reverse=True, key=lambda s: asserttype(s['time'], int)) # Now make a display for the most recent challenge. for score in scores: if score['type'] == 'score_challenge': tval = (score['player'] + ': ' + timestring( int(score['value']) * 10, timeformat=TimeFormat.MILLISECONDS).evaluate() if display_type == 'time' else str(score['value'])) hattach = 'center' if display_type == 'time' else 'left' halign = 'center' if display_type == 'time' else 'left' pos = (20, -70) if display_type == 'time' else (20, -130) txt = NodeActor( _ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': hattach, 'h_align': halign, 'color': (0.7, 0.4, 1, 1), 'shadow': 0.5, 'flatness': 1.0, 'position': pos, 'scale': 0.6, 'text': tval })).autoretain() assert txt.node is not None animate(txt.node, 'scale', {1.0: 0.0, 1.1: 0.7, 1.2: 0.6}) break
def _show_tip(self) -> None: # pylint: disable=too-many-locals from ba._gameutils import animate, GameTip from ba._enums import SpecialChar # If there's any tips left on the list, display one. if self.tips: tip = self.tips.pop(random.randrange(len(self.tips))) tip_title = Lstr(value='${A}:', subs=[('${A}', Lstr(resource='tipText'))]) icon: Optional[ba.Texture] = None sound: Optional[ba.Sound] = None if isinstance(tip, GameTip): icon = tip.icon sound = tip.sound tip = tip.text assert isinstance(tip, str) # Do a few substitutions. tip_lstr = Lstr(translate=('tips', tip), subs=[('${PICKUP}', _ba.charstr(SpecialChar.TOP_BUTTON))]) base_position = (75, 50) tip_scale = 0.8 tip_title_scale = 1.2 vrmode = _ba.app.vr_mode t_offs = -350.0 tnode = _ba.newnode('text', attrs={ 'text': tip_lstr, 'scale': tip_scale, 'maxwidth': 900, 'position': (base_position[0] + t_offs, base_position[1]), 'h_align': 'left', 'vr_depth': 300, 'shadow': 1.0 if vrmode else 0.5, 'flatness': 1.0 if vrmode else 0.5, 'v_align': 'center', 'v_attach': 'bottom' }) t2pos = (base_position[0] + t_offs - (20 if icon is None else 82), base_position[1] + 2) t2node = _ba.newnode('text', owner=tnode, attrs={ 'text': tip_title, 'scale': tip_title_scale, 'position': t2pos, 'h_align': 'right', 'vr_depth': 300, 'shadow': 1.0 if vrmode else 0.5, 'flatness': 1.0 if vrmode else 0.5, 'maxwidth': 140, 'v_align': 'center', 'v_attach': 'bottom' }) if icon is not None: ipos = (base_position[0] + t_offs - 40, base_position[1] + 1) img = _ba.newnode('image', attrs={ 'texture': icon, 'position': ipos, 'scale': (50, 50), 'opacity': 1.0, 'vr_depth': 315, 'color': (1, 1, 1), 'absolute_scale': True, 'attach': 'bottomCenter' }) animate(img, 'opacity', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0}) _ba.timer(5.0, img.delete) if sound is not None: _ba.playsound(sound) combine = _ba.newnode('combine', owner=tnode, attrs={ 'input0': 1.0, 'input1': 0.8, 'input2': 1.0, 'size': 4 }) combine.connectattr('output', tnode, 'color') combine.connectattr('output', t2node, 'color') animate(combine, 'input3', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0}) _ba.timer(5.0, tnode.delete)
def _show_info(self) -> None: """Show the game description.""" from ba._gameutils import animate from bastd.actor.zoomtext import ZoomText name = self.get_instance_display_string() ZoomText(name, maxwidth=800, lifespan=2.5, jitter=2.0, position=(0, 180), flash=False, color=(0.93 * 1.25, 0.9 * 1.25, 1.0 * 1.25), trailcolor=(0.15, 0.05, 1.0, 0.0)).autoretain() _ba.timer(0.2, Call(_ba.playsound, _ba.getsound('gong'))) # The description can be either a string or a sequence with args # to swap in post-translation. desc_in = self.get_instance_description() desc_l: Sequence if isinstance(desc_in, str): desc_l = [desc_in] # handle simple string case else: desc_l = desc_in if not isinstance(desc_l[0], str): raise TypeError('Invalid format for instance description') subs = [] for i in range(len(desc_l) - 1): subs.append(('${ARG' + str(i + 1) + '}', str(desc_l[i + 1]))) translation = Lstr(translate=('gameDescriptions', desc_l[0]), subs=subs) # Do some standard filters (epic mode, etc). if self.settings_raw.get('Epic Mode', False): translation = Lstr(resource='epicDescriptionFilterText', subs=[('${DESCRIPTION}', translation)]) vrmode = _ba.app.vr_mode dnode = _ba.newnode('text', attrs={ 'v_attach': 'center', 'h_attach': 'center', 'h_align': 'center', 'color': (1, 1, 1, 1), 'shadow': 1.0 if vrmode else 0.5, 'flatness': 1.0 if vrmode else 0.5, 'vr_depth': -30, 'position': (0, 80), 'scale': 1.2, 'maxwidth': 700, 'text': translation }) cnode = _ba.newnode('combine', owner=dnode, attrs={ 'input0': 1.0, 'input1': 1.0, 'input2': 1.0, 'size': 4 }) cnode.connectattr('output', dnode, 'color') keys = {0.5: 0, 1.0: 1.0, 2.5: 1.0, 4.0: 0.0} animate(cnode, 'input3', keys) _ba.timer(4.0, dnode.delete)
def _show_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 })
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)
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 __init__(self, vpos: float, player: _ba.SessionPlayer, lobby: 'Lobby') -> None: # FIXME: Tidy up around here. # pylint: disable=too-many-branches # pylint: disable=too-many-statements from ba import _gameutils from ba import _profile from ba import _lang app = _ba.app 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._player = player 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) # Hmm does this need to be public? self.profiles: Dict[str, Dict[str, Any]] = {} # Load available 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; 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. try: input_device_id = self._player.get_input_device().id except Exception: from ba import _error _error.print_exception('Error getting device-id on chooser create') input_device_id = 0 if app.lobby_random_char_index_offset is None: # We want the first device that asks for a chooser to always get # spaz as a random character.. # scratch that.. we now kinda accomplish the same thing with # account profiles so lets just be fully random here. app.lobby_random_char_index_offset = (random.randrange(1000)) # To calc our random index we pick a random character 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 assert char_index_offset is not None self._random_character_index = ((input_device_id + char_index_offset) % len(self.character_names)) self._random_color, self._random_highlight = ( _profile.get_player_profile_colors(None)) # Attempt to pick an initial profile based on what's been stored # for this input device. input_device = self._player.get_input_device() try: name = input_device.name unique_id = input_device.unique_identifier self._profilename = ( app.config['Default Player Profiles'][name + ' ' + unique_id]) self._profileindex = self._profilenames.index(self._profilename) # If this one is __account__ and is local and we haven't marked # anyone as the account-profile device yet, mark this guy as it. # (prevents the next joiner from getting the account profile too). if (self._profilename == '__account__' and not input_device.is_remote_client and app.lobby_account_profile_device_id is None): app.lobby_account_profile_device_id = input_device_id # Well hmm that didn't work.. pick __account__, _random, or some # other random profile. except Exception: profilenames = self._profilenames # We want the first local input-device in the game to latch on to # the account profile. if (not input_device.is_remote_client and not input_device.is_controller_app): if (app.lobby_account_profile_device_id is None and '__account__' in profilenames): app.lobby_account_profile_device_id = input_device_id # If this is the designated account-profile-device, try to default # to the account profile. if (input_device_id == app.lobby_account_profile_device_id and '__account__' in profilenames): self._profileindex = profilenames.index('__account__') else: # If this is the controller app, it defaults to using a random # profile (since we can pull the random name from the app). if input_device.is_controller_app: self._profileindex = profilenames.index('_random') else: # If its a client connection, for now just force # the account profile if possible.. (need to provide a # way for clients to specify/remember their default # profile on remote servers that do not already know them). if (input_device.is_remote_client and '__account__' in profilenames): self._profileindex = profilenames.index('__account__') else: # Cycle through our non-random profiles once; after # that, everyone gets random. while (app.lobby_random_profile_index < len(profilenames) and profilenames[app.lobby_random_profile_index] in ('_random', '__account__', '_edit')): app.lobby_random_profile_index += 1 if (app.lobby_random_profile_index < len(profilenames)): self._profileindex = ( app.lobby_random_profile_index) app.lobby_random_profile_index += 1 else: self._profileindex = profilenames.index('_random') self._profilename = profilenames[self._profileindex] self.character_index = self._random_character_index self._color = self._random_color self._highlight = self._random_highlight 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' }) _gameutils.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' }) _gameutils.animate_array(self.icon, 'scale', 2, { 0: (0, 0), 0.1: (45, 45) }) self._set_ready(False) # Set our initial name to '<choosing player>' in case anyone asks. self._player.set_name( _lang.Lstr(resource='choosingPlayerText').evaluate(), real=False) self.update_from_player_profiles() self.update_position() self._inited = True