def get_tournament_prize_strings(entry: Dict[str, Any]) -> List[str]: """Given a tournament entry, return strings for its prize levels.""" # pylint: disable=too-many-locals from ba._generated.enums import SpecialChar from ba._gameutils import get_trophy_string range1 = entry.get('prizeRange1') range2 = entry.get('prizeRange2') range3 = entry.get('prizeRange3') prize1 = entry.get('prize1') prize2 = entry.get('prize2') prize3 = entry.get('prize3') trophy_type_1 = entry.get('prizeTrophy1') trophy_type_2 = entry.get('prizeTrophy2') trophy_type_3 = entry.get('prizeTrophy3') out_vals = [] for rng, prize, trophy_type in ((range1, prize1, trophy_type_1), (range2, prize2, trophy_type_2), (range3, prize3, trophy_type_3)): prval = ('' if rng is None else ('#' + str(rng[0])) if (rng[0] == rng[1]) else ('#' + str(rng[0]) + '-' + str(rng[1]))) pvval = '' if trophy_type is not None: pvval += get_trophy_string(trophy_type) # If we've got trophies but not for this entry, throw some space # in to compensate so the ticket counts line up. if prize is not None: pvval = _ba.charstr( SpecialChar.TICKET_BACKING) + str(prize) + pvval out_vals.append(prval) out_vals.append(pvval) return out_vals
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)
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
def _getname(self, full: bool = False) -> str: name_raw = name = self._profilenames[self._profileindex] if name[0] in self._glowing: name = name[1:] clamp = False 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: clamp = True if clamp and len(name) > 10: name = name[:10] + '...' return name return self._getname_glowing(full)
def get_player_profile_icon(profilename: str) -> str: """Given a profile name, returns an icon string for it. (non-account profiles only) """ from ba._enums import SpecialChar bs_config = _ba.app.config icon: str try: is_global = bs_config['Player Profiles'][profilename]['global'] except KeyError: is_global = False if is_global: try: icon = bs_config['Player Profiles'][profilename]['icon'] except KeyError: icon = _ba.charstr(SpecialChar.LOGO) else: icon = '' return icon
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 get_store_items() -> Dict[str, Dict]: """Returns info about purchasable items. (internal) """ # pylint: disable=cyclic-import from ba._generated.enums import SpecialChar from bastd import maps if _ba.app.store_items is None: from bastd.game import ninjafight from bastd.game import meteorshower from bastd.game import targetpractice from bastd.game import easteregghunt # IMPORTANT - need to keep this synced with the master server. # (doing so manually for now) _ba.app.store_items = { 'characters.kronk': { 'character': 'Kronk' }, 'characters.zoe': { 'character': 'Zoe' }, 'characters.jackmorgan': { 'character': 'Jack Morgan' }, 'characters.mel': { 'character': 'Mel' }, 'characters.snakeshadow': { 'character': 'Snake Shadow' }, 'characters.bones': { 'character': 'Bones' }, 'characters.bernard': { 'character': 'Bernard', 'highlight': (0.6, 0.5, 0.8) }, 'characters.pixie': { 'character': 'Pixel' }, 'characters.wizard': { 'character': 'Grumbledorf' }, 'characters.frosty': { 'character': 'Frosty' }, 'characters.pascal': { 'character': 'Pascal' }, 'characters.cyborg': { 'character': 'B-9000' }, 'characters.agent': { 'character': 'Agent Johnson' }, 'characters.taobaomascot': { 'character': 'Taobao Mascot' }, 'characters.santa': { 'character': 'Santa Claus' }, 'characters.bunny': { 'character': 'Easter Bunny' }, 'pro': {}, 'maps.lake_frigid': { 'map_type': maps.LakeFrigid }, 'games.ninja_fight': { 'gametype': ninjafight.NinjaFightGame, 'previewTex': 'courtyardPreview' }, 'games.meteor_shower': { 'gametype': meteorshower.MeteorShowerGame, 'previewTex': 'rampagePreview' }, 'games.target_practice': { 'gametype': targetpractice.TargetPracticeGame, 'previewTex': 'doomShroomPreview' }, 'games.easter_egg_hunt': { 'gametype': easteregghunt.EasterEggHuntGame, 'previewTex': 'towerDPreview' }, 'icons.flag_us': { 'icon': _ba.charstr(SpecialChar.FLAG_UNITED_STATES) }, 'icons.flag_mexico': { 'icon': _ba.charstr(SpecialChar.FLAG_MEXICO) }, 'icons.flag_germany': { 'icon': _ba.charstr(SpecialChar.FLAG_GERMANY) }, 'icons.flag_brazil': { 'icon': _ba.charstr(SpecialChar.FLAG_BRAZIL) }, 'icons.flag_russia': { 'icon': _ba.charstr(SpecialChar.FLAG_RUSSIA) }, 'icons.flag_china': { 'icon': _ba.charstr(SpecialChar.FLAG_CHINA) }, 'icons.flag_uk': { 'icon': _ba.charstr(SpecialChar.FLAG_UNITED_KINGDOM) }, 'icons.flag_canada': { 'icon': _ba.charstr(SpecialChar.FLAG_CANADA) }, 'icons.flag_india': { 'icon': _ba.charstr(SpecialChar.FLAG_INDIA) }, 'icons.flag_japan': { 'icon': _ba.charstr(SpecialChar.FLAG_JAPAN) }, 'icons.flag_france': { 'icon': _ba.charstr(SpecialChar.FLAG_FRANCE) }, 'icons.flag_indonesia': { 'icon': _ba.charstr(SpecialChar.FLAG_INDONESIA) }, 'icons.flag_italy': { 'icon': _ba.charstr(SpecialChar.FLAG_ITALY) }, 'icons.flag_south_korea': { 'icon': _ba.charstr(SpecialChar.FLAG_SOUTH_KOREA) }, 'icons.flag_netherlands': { 'icon': _ba.charstr(SpecialChar.FLAG_NETHERLANDS) }, 'icons.flag_uae': { 'icon': _ba.charstr(SpecialChar.FLAG_UNITED_ARAB_EMIRATES) }, 'icons.flag_qatar': { 'icon': _ba.charstr(SpecialChar.FLAG_QATAR) }, 'icons.flag_egypt': { 'icon': _ba.charstr(SpecialChar.FLAG_EGYPT) }, 'icons.flag_kuwait': { 'icon': _ba.charstr(SpecialChar.FLAG_KUWAIT) }, 'icons.flag_algeria': { 'icon': _ba.charstr(SpecialChar.FLAG_ALGERIA) }, 'icons.flag_saudi_arabia': { 'icon': _ba.charstr(SpecialChar.FLAG_SAUDI_ARABIA) }, 'icons.flag_malaysia': { 'icon': _ba.charstr(SpecialChar.FLAG_MALAYSIA) }, 'icons.flag_czech_republic': { 'icon': _ba.charstr(SpecialChar.FLAG_CZECH_REPUBLIC) }, 'icons.flag_australia': { 'icon': _ba.charstr(SpecialChar.FLAG_AUSTRALIA) }, 'icons.flag_singapore': { 'icon': _ba.charstr(SpecialChar.FLAG_SINGAPORE) }, 'icons.flag_iran': { 'icon': _ba.charstr(SpecialChar.FLAG_IRAN) }, 'icons.flag_poland': { 'icon': _ba.charstr(SpecialChar.FLAG_POLAND) }, 'icons.flag_argentina': { 'icon': _ba.charstr(SpecialChar.FLAG_ARGENTINA) }, 'icons.flag_philippines': { 'icon': _ba.charstr(SpecialChar.FLAG_PHILIPPINES) }, 'icons.flag_chile': { 'icon': _ba.charstr(SpecialChar.FLAG_CHILE) }, 'icons.fedora': { 'icon': _ba.charstr(SpecialChar.FEDORA) }, 'icons.hal': { 'icon': _ba.charstr(SpecialChar.HAL) }, 'icons.crown': { 'icon': _ba.charstr(SpecialChar.CROWN) }, 'icons.yinyang': { 'icon': _ba.charstr(SpecialChar.YIN_YANG) }, 'icons.eyeball': { 'icon': _ba.charstr(SpecialChar.EYE_BALL) }, 'icons.skull': { 'icon': _ba.charstr(SpecialChar.SKULL) }, 'icons.heart': { 'icon': _ba.charstr(SpecialChar.HEART) }, 'icons.dragon': { 'icon': _ba.charstr(SpecialChar.DRAGON) }, 'icons.helmet': { 'icon': _ba.charstr(SpecialChar.HELMET) }, 'icons.mushroom': { 'icon': _ba.charstr(SpecialChar.MUSHROOM) }, 'icons.ninja_star': { 'icon': _ba.charstr(SpecialChar.NINJA_STAR) }, 'icons.viking_helmet': { 'icon': _ba.charstr(SpecialChar.VIKING_HELMET) }, 'icons.moon': { 'icon': _ba.charstr(SpecialChar.MOON) }, 'icons.spider': { 'icon': _ba.charstr(SpecialChar.SPIDER) }, 'icons.fireball': { 'icon': _ba.charstr(SpecialChar.FIREBALL) }, 'icons.mikirog': { 'icon': _ba.charstr(SpecialChar.MIKIROG) }, } store_items = _ba.app.store_items assert store_items is not None return store_items
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 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._lang import Lstr from ba._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: from ba import _error _error.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
def get_trophy_string(trophy_id: str) -> str: """Given a trophy id, returns a string to visualize it.""" if trophy_id in TROPHY_CHARS: return _ba.charstr(TROPHY_CHARS[trophy_id]) return '?'
def _build_host_tab(self) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-statements if _ba.get_account_state() != 'signed_in': ba.textwidget(parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=200, scale=0.8, color=(0.6, 0.56, 0.6), position=(self._c_width * 0.5, self._c_height * 0.5), text=ba.Lstr(resource='notSignedInErrorText')) self._showing_not_signed_in_screen = True return self._showing_not_signed_in_screen = False # At first we don't want to show anything until we've gotten a state. # Update: In this situation we now simply show our existing state # but give the start/stop button a loading message and disallow its # use. This keeps things a lot less jumpy looking and allows selecting # playlists/etc without having to wait for the server each time # back to the ui. if self._waiting_for_initial_state and bool(False): ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=200, scale=0.8, color=(0.6, 0.56, 0.6), position=(self._c_width * 0.5, self._c_height * 0.5), text=ba.Lstr( value='${A}...', subs=[('${A}', ba.Lstr(resource='store.loadingText'))], ), ) return # If we're not currently hosting and hosting requires tickets, # Show our count (possibly with a link to purchase more). if (not self._waiting_for_initial_state and self._hostingstate.party_code is None and self._hostingstate.tickets_to_host_now != 0): if not ba.app.ui.use_toolbars: if ba.app.allow_ticket_purchases: self._get_tickets_button = ba.buttonwidget( parent=self._container, position=(self._c_width - 210 + 125, self._c_height - 44), autoselect=True, scale=0.6, size=(120, 60), textcolor=(0.2, 1, 0.2), label=ba.charstr(ba.SpecialChar.TICKET), color=(0.65, 0.5, 0.8), on_activate_call=self._on_get_tickets_press) else: self._ticket_count_text = ba.textwidget( parent=self._container, scale=0.6, position=(self._c_width - 210 + 125, self._c_height - 44), color=(0.2, 1, 0.2), h_align='center', v_align='center') # Set initial ticket count. self._update_currency_ui() v = self._c_height - 90 if self._hostingstate.party_code is None: ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(0.5, 0.46, 0.5), position=(self._c_width * 0.5, v), text=ba.Lstr( resource='gatherWindow.privatePartyCloudDescriptionText')) v -= 100 if self._hostingstate.party_code is None: # We've got no current party running; show options to set one up. ba.textwidget(parent=self._container, size=(0, 0), h_align='right', v_align='center', maxwidth=200, scale=0.8, color=(0.6, 0.56, 0.6), position=(self._c_width * 0.5 - 210, v), text=ba.Lstr(resource='playlistText')) self._host_playlist_button = ba.buttonwidget( parent=self._container, size=(400, 70), color=(0.6, 0.5, 0.6), textcolor=(0.8, 0.75, 0.8), label=self._hostingconfig.playlist_name, on_activate_call=self._playlist_press, position=(self._c_width * 0.5 - 200, v - 35), up_widget=self._host_sub_tab_text, autoselect=True) # If it appears we're coming back from playlist selection, # re-select our playlist button. if ba.app.ui.selecting_private_party_playlist: ba.containerwidget(edit=self._container, selected_child=self._host_playlist_button) ba.app.ui.selecting_private_party_playlist = False else: # We've got a current party; show its info. ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=600, scale=0.9, color=(0.7, 0.64, 0.7), position=(self._c_width * 0.5, v + 90), text=ba.Lstr(resource='gatherWindow.partyServerRunningText')) ba.textwidget(parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=600, scale=0.7, color=(0.7, 0.64, 0.7), position=(self._c_width * 0.5, v + 50), text=ba.Lstr(resource='gatherWindow.partyCodeText')) ba.textwidget(parent=self._container, size=(0, 0), h_align='center', v_align='center', scale=2.0, color=(0.0, 1.0, 0.0), position=(self._c_width * 0.5, v + 10), text=self._hostingstate.party_code) # Also action buttons to copy it and connect to it. if ba.clipboard_is_supported(): cbtnoffs = 10 self._host_copy_button = ba.buttonwidget( parent=self._container, size=(140, 40), color=(0.6, 0.5, 0.6), textcolor=(0.8, 0.75, 0.8), label=ba.Lstr(resource='gatherWindow.copyCodeText'), on_activate_call=self._host_copy_press, position=(self._c_width * 0.5 - 150, v - 70), autoselect=True) else: cbtnoffs = -70 self._host_connect_button = ba.buttonwidget( parent=self._container, size=(140, 40), color=(0.6, 0.5, 0.6), textcolor=(0.8, 0.75, 0.8), label=ba.Lstr(resource='gatherWindow.manualConnectText'), on_activate_call=self._host_connect_press, position=(self._c_width * 0.5 + cbtnoffs, v - 70), autoselect=True) v -= 120 # Line above the main action button: # If we don't want to show anything until we get a state: if self._waiting_for_initial_state: pass elif self._hostingstate.unavailable_error is not None: # If hosting is unavailable, show the associated reason. ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(1.0, 0.0, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr(translate=('serverResponses', self._hostingstate.unavailable_error))) elif self._hostingstate.free_host_minutes_remaining is not None: # If we've been pre-approved to start/stop for free, show that. ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=((0.7, 0.64, 0.7) if self._hostingstate.party_code else (0.0, 1.0, 0.0)), position=(self._c_width * 0.5, v), text=ba.Lstr( resource='gatherWindow.startStopHostingMinutesText', subs=[( '${MINUTES}', f'{self._hostingstate.free_host_minutes_remaining:.0f}' )])) else: # Otherwise tell whether the free cloud server is available # or will be at some point. if self._hostingstate.party_code is None: if self._hostingstate.tickets_to_host_now == 0: ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(0.0, 1.0, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr( resource= 'gatherWindow.freeCloudServerAvailableNowText')) else: if self._hostingstate.minutes_until_free_host is None: ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(1.0, 0.6, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr( resource= 'gatherWindow.freeCloudServerNotAvailableText') ) else: availmins = self._hostingstate.minutes_until_free_host ba.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, color=(1.0, 0.6, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr(resource='gatherWindow.' 'freeCloudServerAvailableMinutesText', subs=[('${MINUTES}', f'{availmins:.0f}')])) v -= 100 if (self._waiting_for_start_stop_response or self._waiting_for_initial_state): btnlabel = ba.Lstr(resource='oneMomentText') else: if self._hostingstate.unavailable_error is not None: btnlabel = ba.Lstr( resource='gatherWindow.hostingUnavailableText') elif self._hostingstate.party_code is None: ticon = _ba.charstr(ba.SpecialChar.TICKET) nowtickets = self._hostingstate.tickets_to_host_now if nowtickets > 0: btnlabel = ba.Lstr( resource='gatherWindow.startHostingPaidText', subs=[('${COST}', f'{ticon}{nowtickets}')]) else: btnlabel = ba.Lstr( resource='gatherWindow.startHostingText') else: btnlabel = ba.Lstr(resource='gatherWindow.stopHostingText') disabled = (self._hostingstate.unavailable_error is not None or self._waiting_for_initial_state) waiting = self._waiting_for_start_stop_response self._host_start_stop_button = ba.buttonwidget( parent=self._container, size=(400, 80), color=((0.6, 0.6, 0.6) if disabled else (0.5, 1.0, 0.5) if waiting else None), enable_sound=False, label=btnlabel, textcolor=((0.7, 0.7, 0.7) if disabled else None), position=(self._c_width * 0.5 - 200, v), on_activate_call=self._start_stop_button_press, autoselect=True)
def _get_name(self, full: bool = False) -> str: # FIXME: Needs cleanup. # pylint: disable=too-many-branches from ba._lang import Lstr from ba._enums import SpecialChar name_raw = name = self._profilenames[self._profileindex] clamp = False if name == '_random': input_device: Optional[ba.InputDevice] try: input_device = self._player.get_input_device() except Exception: input_device = None if input_device is not None: name = input_device.get_default_player_name() else: name = 'Invalid' if not full: clamp = True elif name == '__account__': try: input_device = self._player.get_input_device() except Exception: input_device = None if input_device is not None: name = input_device.get_account_name(full) else: name = 'Invalid' if not full: clamp = True elif name == '_edit': # FIXME: This causes problems as an Lstr, but its ok to # explicitly translate for now since this is only shown on the # host. (also should elaborate; don't remember what problems this # caused) 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: from ba import _error _error.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
def __init__(self, lobby: ba.Lobby): # pylint: disable=too-many-locals from ba import _input from ba._lang import Lstr from ba._nodeactor import NodeActor from ba._general import WeakCall from ba._enums import SpecialChar can_switch_teams = (len(lobby.teams) > 1) self._state = 0 press_to_punch: Union[str, ba.Lstr] = _ba.charstr(SpecialChar.LEFT_BUTTON) press_to_bomb: Union[str, ba.Lstr] = _ba.charstr(SpecialChar.RIGHT_BUTTON) # If we have a keyboard, grab keys for punch and pickup. # FIXME: This of course is only correct on the local device; # Should change this for net games. keyboard: Optional[ba.InputDevice] = _ba.get_input_device( 'Keyboard', '#1', doraise=False) if keyboard is not None: punch_key = keyboard.get_button_name( _input.get_device_value(keyboard, 'buttonPunch')) press_to_punch = Lstr(resource='orText', subs=[('${A}', Lstr(value='\'${K}\'', subs=[('${K}', punch_key)])), ('${B}', press_to_punch)]) bomb_key = keyboard.get_button_name( _input.get_device_value(keyboard, 'buttonBomb')) press_to_bomb = Lstr(resource='orText', subs=[('${A}', Lstr(value='\'${K}\'', subs=[('${K}', bomb_key)])), ('${B}', press_to_bomb)]) join_str = Lstr(value='${A} < ${B} >', subs=[('${A}', Lstr(resource='pressPunchToJoinText')), ('${B}', press_to_punch)]) else: join_str = Lstr(resource='pressAnyButtonToJoinText') flatness = 1.0 if _ba.app.vr_mode else 0.0 self._text = NodeActor( _ba.newnode('text', attrs={ 'position': (0, -40), 'h_attach': 'center', 'v_attach': 'top', 'h_align': 'center', 'color': (0.7, 0.7, 0.95, 1.0), 'flatness': flatness, 'text': join_str })) if _ba.app.kiosk_mode: self._messages = [join_str] else: msg1 = Lstr(resource='pressToSelectProfileText', subs=[ ('${BUTTONS}', _ba.charstr(SpecialChar.UP_ARROW) + ' ' + _ba.charstr(SpecialChar.DOWN_ARROW)) ]) msg2 = Lstr(resource='pressToOverrideCharacterText', subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))]) msg3 = Lstr(value='${A} < ${B} >', subs=[('${A}', msg2), ('${B}', press_to_bomb)]) self._messages = (([ Lstr(resource='pressToSelectTeamText', subs=[('${BUTTONS}', _ba.charstr(SpecialChar.LEFT_ARROW) + ' ' + _ba.charstr(SpecialChar.RIGHT_ARROW))]) ] if can_switch_teams else []) + [msg1] + [msg3] + [join_str]) self._timer = _ba.Timer(4.0, WeakCall(self._update), repeat=True)