def postinit(self, sessionplayer: ba.SessionPlayer) -> None: """Wire up a newly created player. (internal) """ from ba._nodeactor import NodeActor # Sanity check; if a dataclass is created that inherits from us, # it will define an equality operator by default which will break # internal game logic. So complain loudly if we find one. if type(self).__eq__ is not object.__eq__: raise RuntimeError( f'Player class {type(self)} defines an equality' f' operator (__eq__) which will break internal' f' logic. Please remove it.\n' f'For dataclasses you can do "dataclass(eq=False)"' f' in the class decorator.') self.actor = None self.character = '' self._nodeactor: Optional[ba.NodeActor] = None self._sessionplayer = sessionplayer self.character = sessionplayer.character self.color = sessionplayer.color self.highlight = sessionplayer.highlight self._team = cast(TeamType, sessionplayer.sessionteam.activityteam) assert self._team is not None self._customdata = {} self._expired = False self._postinited = True node = _ba.newnode('player', attrs={'playerID': sessionplayer.id}) self._nodeactor = NodeActor(node) sessionplayer.setnode(node)
def pause(self) -> None: """Pause the game due to a user request or menu popping up. If there's a foreground host-activity that says it's pausable, tell it to pause ..we now no longer pause if there are connected clients. """ activity: Optional[ba.Activity] = _ba.get_foreground_host_activity() if (activity is not None and activity.allow_pausing and not _ba.have_connected_clients()): from ba import _gameutils from ba._language import Lstr from ba._nodeactor import NodeActor # FIXME: Shouldn't be touching scene stuff here; # should just pass the request on to the host-session. with _ba.Context(activity): globs = activity.globalsnode if not globs.paused: _ba.playsound(_ba.getsound('refWhistle')) globs.paused = True # FIXME: This should not be an attr on Actor. activity.paused_text = NodeActor( _ba.newnode('text', attrs={ 'text': Lstr(resource='pausedByHostText'), 'client_only': True, 'flatness': 1.0, 'h_align': 'center' }))
def create_player_node(self, player: ba.Player) -> ba.Node: """Create the 'player' node associated with the provided ba.Player.""" from ba._nodeactor import NodeActor with _ba.Context(self): node = _ba.newnode('player', attrs={'playerID': player.get_id()}) # FIXME: Should add a dedicated slot for this on ba.Player # instead of cluttering up their gamedata dict. player.gamedata['_playernode'] = NodeActor(node) return node
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 setup_standard_time_limit(self, duration: float) -> None: """ Create a standard game time-limit given the provided duration in seconds. This will be displayed at the top of the screen. If the time-limit expires, end_game() will be called. """ from ba._nodeactor import NodeActor if duration <= 0.0: return self._standard_time_limit_time = int(duration) self._standard_time_limit_timer = _ba.Timer( 1.0, WeakCall(self._standard_time_limit_tick), repeat=True) self._standard_time_limit_text = NodeActor( _ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': 'center', 'h_align': 'left', 'color': (1.0, 1.0, 1.0, 0.5), 'position': (-25, -30), 'flatness': 1.0, 'scale': 0.9 })) self._standard_time_limit_text_input = NodeActor( _ba.newnode('timedisplay', attrs={ 'time2': duration * 1000, 'timemin': 0 })) self.globalsnode.connectattr('time', self._standard_time_limit_text_input.node, 'time1') assert self._standard_time_limit_text_input.node assert self._standard_time_limit_text.node self._standard_time_limit_text_input.node.connectattr( 'output', self._standard_time_limit_text.node, 'text')
def _update_life_warning(self) -> None: # Beep continuously if anyone is close to death. should_beep = False for player in self.players: if player.is_alive(): # FIXME: Should abstract this instead of # reading hitpoints directly. if getattr(player.actor, 'hitpoints', 999) < 200: should_beep = True break if should_beep and self._life_warning_beep is None: from ba._nodeactor import NodeActor self._life_warning_beep = NodeActor( _ba.newnode('sound', attrs={ 'sound': self._warn_beeps_sound, 'positional': False, 'loop': True })) if self._life_warning_beep is not None and not should_beep: self._life_warning_beep = None
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 postinit(self, sessionplayer: ba.SessionPlayer) -> None: """Wire up a newly created player. (internal) """ from ba._nodeactor import NodeActor # Sanity check; if a dataclass is created that inherits from us, # it will define an equality operator by default which will break # internal game logic. So complain loudly if we find one. if type(self).__eq__ is not object.__eq__: raise RuntimeError( f'Player class {type(self)} defines an equality' f' operator (__eq__) which will break internal' f' logic. Please remove it.\n' f'For dataclasses you can do "dataclass(eq=False)"' f' in the class decorator.') self.actor = None self.character = '' self._nodeactor: Optional[ba.NodeActor] = None self._sessionplayer = sessionplayer self.character = sessionplayer.character self.color = sessionplayer.color self.highlight = sessionplayer.highlight self.team = sessionplayer.team.gameteam # type: ignore assert self.team is not None self.sessiondata = sessionplayer.sessiondata self.gamedata = sessionplayer.gamedata # Create our player node in the current activity. # Note: do we want to save a few cycles here by managing our player # node manually instead of wrapping it in a NodeActor? node = _ba.newnode('player', attrs={'playerID': sessionplayer.id}) self._nodeactor = NodeActor(node) sessionplayer.set_node(node)
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 _setup_tournament_time_limit(self, duration: float) -> None: """ Create a tournament game time-limit given the provided duration in seconds. This will be displayed at the top of the screen. If the time-limit expires, end_game() will be called. """ from ba._nodeactor import NodeActor from ba._enums import TimeType if duration <= 0.0: return self._tournament_time_limit = int(duration) # We want this timer to match the server's time as close as possible, # so lets go with base-time. Theoretically we should do real-time but # then we have to mess with contexts and whatnot since its currently # not available in activity contexts. :-/ self._tournament_time_limit_timer = _ba.Timer( 1.0, WeakCall(self._tournament_time_limit_tick), repeat=True, timetype=TimeType.BASE) self._tournament_time_limit_title_text = NodeActor( _ba.newnode('text', attrs={ 'v_attach': 'bottom', 'h_attach': 'left', 'h_align': 'center', 'v_align': 'center', 'vr_depth': 300, 'maxwidth': 100, 'color': (1.0, 1.0, 1.0, 0.5), 'position': (60, 50), 'flatness': 1.0, 'scale': 0.5, 'text': Lstr(resource='tournamentText') })) self._tournament_time_limit_text = NodeActor( _ba.newnode('text', attrs={ 'v_attach': 'bottom', 'h_attach': 'left', 'h_align': 'center', 'v_align': 'center', 'vr_depth': 300, 'maxwidth': 100, 'color': (1.0, 1.0, 1.0, 0.5), 'position': (60, 30), 'flatness': 1.0, 'scale': 0.9 })) self._tournament_time_limit_text_input = NodeActor( _ba.newnode('timedisplay', attrs={ 'timemin': 0, 'time2': self._tournament_time_limit * 1000 })) assert self._tournament_time_limit_text.node assert self._tournament_time_limit_text_input.node self._tournament_time_limit_text_input.node.connectattr( 'output', self._tournament_time_limit_text.node, 'text')
def cameraflash(duration: float = 999.0) -> None: """Create a strobing camera flash effect. Category: Gameplay Functions (as seen when a team wins a game) Duration is in seconds. """ # pylint: disable=too-many-locals import random from ba._nodeactor import NodeActor x_spread = 10 y_spread = 5 positions = [[-x_spread, -y_spread], [0, -y_spread], [0, y_spread], [x_spread, -y_spread], [x_spread, y_spread], [-x_spread, y_spread]] times = [0, 2700, 1000, 1800, 500, 1400] # Store this on the current activity so we only have one at a time. # FIXME: Need a type safe way to do this. activity = _ba.getactivity() activity.camera_flash_data = [] # type: ignore for i in range(6): light = NodeActor( _ba.newnode('light', attrs={ 'position': (positions[i][0], 0, positions[i][1]), 'radius': 1.0, 'lights_volumes': False, 'height_attenuated': False, 'color': (0.2, 0.2, 0.8) })) sval = 1.87 iscale = 1.3 tcombine = _ba.newnode('combine', owner=light.node, attrs={ 'size': 3, 'input0': positions[i][0], 'input1': 0, 'input2': positions[i][1] }) assert light.node tcombine.connectattr('output', light.node, 'position') xval = positions[i][0] yval = positions[i][1] spd = 0.5 + random.random() spd2 = 0.5 + random.random() animate(tcombine, 'input0', { 0.0: xval + 0, 0.069 * spd: xval + 10.0, 0.143 * spd: xval - 10.0, 0.201 * spd: xval + 0 }, loop=True) animate(tcombine, 'input2', { 0.0: yval + 0, 0.15 * spd2: yval + 10.0, 0.287 * spd2: yval - 10.0, 0.398 * spd2: yval + 0 }, loop=True) animate(light.node, 'intensity', { 0.0: 0, 0.02 * sval: 0, 0.05 * sval: 0.8 * iscale, 0.08 * sval: 0, 0.1 * sval: 0 }, loop=True, offset=times[i]) _ba.timer((times[i] + random.randint(1, int(duration)) * 40 * sval), light.node.delete, timeformat=TimeFormat.MILLISECONDS) activity.camera_flash_data.append(light) # type: ignore
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)