def _handle_splat(self) -> None: node = ba.get_collision_info('opposing_node') if (node is not self.owner and ba.time() - self._last_sticky_sound_time > 1.0): self._last_sticky_sound_time = ba.time() assert self.node ba.playsound(get_factory().sticky_impact_sound, 2.0, position=self.node.position)
def _handle_splat(self) -> None: node = ba.getcollision().opposingnode if (node is not self.owner and ba.time() - self._last_sticky_sound_time > 1.0): self._last_sticky_sound_time = ba.time() assert self.node ba.playsound(BombFactory.get().sticky_impact_sound, 2.0, position=self.node.position)
def update_scores(self) -> None: """ update scoreboard and check for winners """ # FIXME: tidy this up # pylint: disable=too-many-nested-blocks have_scoring_team = False win_score = self._score_to_win for team in [self.teams[0], self._bot_team]: assert team is not None assert self._scoreboard is not None self._scoreboard.set_team_value(team, team.gamedata['score'], win_score) if team.gamedata['score'] >= win_score: if not have_scoring_team: self.scoring_team = team if team is self._bot_team: self.continue_or_end_game() else: ba.setmusic(ba.MusicType.VICTORY) # Completion achievements. assert self._bot_team is not None if self._preset in ['rookie', 'rookie_easy']: self._award_achievement('Rookie Football Victory', sound=False) if self._bot_team.gamedata['score'] == 0: self._award_achievement( 'Rookie Football Shutout', sound=False) elif self._preset in ['pro', 'pro_easy']: self._award_achievement('Pro Football Victory', sound=False) if self._bot_team.gamedata['score'] == 0: self._award_achievement('Pro Football Shutout', sound=False) elif self._preset in ['uber', 'uber_easy']: self._award_achievement('Uber Football Victory', sound=False) if self._bot_team.gamedata['score'] == 0: self._award_achievement( 'Uber Football Shutout', sound=False) if (not self._player_has_dropped_bomb and not self._player_has_punched): self._award_achievement('Got the Moves', sound=False) self._bots.stop_moving() self.show_zoom_message(ba.Lstr(resource='victoryText'), scale=1.0, duration=4.0) self.celebrate(10.0) assert self._starttime_ms is not None self._final_time_ms = int( ba.time(timeformat=ba.TimeFormat.MILLISECONDS) - self._starttime_ms) self._time_text_timer = None assert (self._time_text_input is not None and self._time_text_input.node) self._time_text_input.node.timemax = ( self._final_time_ms) # FIXME: Does this still need to be deferred? ba.pushcall(ba.Call(self.do_end, 'victory'))
def _drop_powerups(self, standard_points: bool = False, force_first: str = None) -> None: """Generic powerup drop.""" # If its been a minute since our last wave finished emerging, stop # giving out land-mine powerups. (prevents players from waiting # around for them on purpose and filling the map up) if ba.time() - self._last_wave_end_time > 60.0: extra_excludes = ['land_mines'] else: extra_excludes = [] if standard_points: points = self.map.powerup_spawn_points for i in range(len(points)): ba.timer( 1.0 + i * 0.5, ba.Call(self._drop_powerup, i, force_first if i == 0 else None)) else: pos = (self._powerup_center[0] + random.uniform( -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]), self._powerup_center[1], self._powerup_center[2] + random.uniform( -self._powerup_spread[1], self._powerup_spread[1])) # drop one random one somewhere.. assert self._exclude_powerups is not None PowerupBox( position=pos, poweruptype=PowerupBoxFactory.get().get_random_powerup_type( excludetypes=self._exclude_powerups + extra_excludes)).autoretain()
def on_punch_press(self) -> None: """ Called to 'press punch' on this spaz; used for player or AI connections. """ if not self.node or self.frozen or self.node.knockout > 0.0: return t_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(t_ms, int) if t_ms - self.last_punch_time_ms >= self._punch_cooldown: if self.punch_callback is not None: self.punch_callback(self) self._punched_nodes = set() # Reset this. self.last_punch_time_ms = t_ms self.node.color = ((0 + random.random() * 6.5), (0 + random.random() * 6.5), (0 + random.random() * 6.5)) self.node.highlight = ((0 + random.random() * 6.5), (0 + random.random() * 6.5), (0 + random.random() * 6.5)) ba.emitfx(position=self.node.position, velocity=self.node.velocity, count=35, scale=0.5, spread=2, chunk_type='spark') self.node.punch_pressed = True if not self.node.hold_node: ba.timer( 0.1, ba.WeakCall(self._safe_play_sound, SpazFactory.get().swish_sound, 0.8)) self._turbo_filter_add_press('punch')
def _update(self) -> None: """Periodic updating.""" now = ba.time(ba.TimeType.REAL) self._update_currency_ui() if self._state.sub_tab is SubTabType.HOST: # If we're not signed in, just refresh to show that. if (_ba.get_account_state() != 'signed_in' and self._showing_not_signed_in_screen): self._refresh_sub_tab() else: # Query an updated state periodically. if (self._last_hosting_state_query_time is None or now - self._last_hosting_state_query_time > 15.0): self._debug_server_comm('querying private party state') if _ba.get_account_state() == 'signed_in': _ba.add_transaction( {'type': 'PRIVATE_PARTY_QUERY'}, callback=ba.WeakCall( self._hosting_state_idle_response), ) _ba.run_transactions() else: self._hosting_state_idle_response(None) self._last_hosting_state_query_time = now
def stop(self, endtime: Union[int, float] = None, timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> None: """End the timer. If 'endtime' is not None, it is used when calculating the final display time; otherwise the current time is used. 'timeformat' applies to endtime and can be SECONDS or MILLISECONDS """ if endtime is None: endtime = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) timeformat = ba.TimeFormat.MILLISECONDS if self._starttime is None: print('Warning: OnScreenTimer.stop() called without start() first') else: endtime_ms: int if timeformat is ba.TimeFormat.SECONDS: endtime_ms = int(endtime * 1000) elif timeformat is ba.TimeFormat.MILLISECONDS: assert isinstance(endtime, int) endtime_ms = endtime else: raise ValueError(f'invalid timeformat: {timeformat}') self.inputnode.timemax = endtime_ms - self._starttime
def start(self) -> None: """Start the timer.""" tval = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(tval, int) self._starttime = tval self.inputnode.time1 = self._starttime ba.sharedobj('globals').connectattr('time', self.inputnode, 'time2')
def _ping_parties_periodically(self) -> None: now = ba.time(ba.TimeType.REAL) # Go through our existing public party entries firing off pings # for any that have timed out. for party in list(self._parties.values()): if party.next_ping_time <= now and ba.app.ping_thread_count < 15: # Crank the interval up for high-latency or non-responding # parties to save us some useless work. mult = 1 if party.ping_responses == 0: if party.ping_attempts > 4: mult = 10 elif party.ping_attempts > 2: mult = 5 if party.ping is not None: mult = (10 if party.ping > 300 else 5 if party.ping > 150 else 2) interval = party.ping_interval * mult if DEBUG_SERVER_COMMUNICATION: print(f'pinging #{party.index} cur={party.ping} ' f'interval={interval} ' f'({party.ping_responses}/{party.ping_attempts})') party.next_ping_time = now + party.ping_interval * mult party.ping_attempts += 1 PingThread(party.address, party.port, ba.WeakCall(self._ping_callback)).start()
def check(self): current = ba.time(ba.TimeType.REAL, timeformat=ba.TimeFormat.MILLISECONDS) for player in _ba.get_foreground_host_session().sessionplayers: last_input = int(player.inputdevice.get_last_input_time()) afk_time = int((current - last_input) / 1000) if afk_time in range(INGAME_TIME, INGAME_TIME + 20): self.warn_player( player.get_account_id(), "Press any button within " + str(INGAME_TIME + 20 - afk_time) + " secs") if afk_time > INGAME_TIME + 20: player.remove_from_game() if LOBBY_KICK: current_players = [] for player in _ba.get_game_roster(): if player['client_id'] != -1 and len(player['players']) == 0: current_players.append(player['client_id']) if player['client_id'] not in self.lobbies: self.lobbies[player['client_id']] = current lobby_afk = int( (current - self.lobbies[player['client_id']]) / 1000) if lobby_afk in range(INLOBBY_TIME, INLOBBY_TIME + 10): _ba.screenmessage("Join game within " + str(INLOBBY_TIME + 10 - lobby_afk) + " secs", color=(1, 0, 0), transient=True, clients=[player['client_id']]) if lobby_afk > INLOBBY_TIME + 10: _ba.disconnect_client(player['client_id'], 0) # clean the lobbies dict temp = self.lobbies.copy() for clid in temp: if clid not in current_players: del self.lobbies[clid]
def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ba.PlayerDiedMessage): # Augment standard behavior. super().handlemessage(msg) player: Player = msg.getplayer(Player) player.lives -= 1 if player.lives < 0: ba.print_error( "Got lives < 0 in Elim; this shouldn't happen. duo: True") player.lives = 0 # If we have any icons, update their state. for icon in player.icons: icon.handle_player_died() # Play big death sound on our last death # or for every one in solo mode. if player.lives == 0: ba.playsound(SpazFactory.get().single_player_death_sound) # If we hit zero lives, we're dead (and our team might be too). if player.lives == 0: # If the whole team is now dead, mark their survival time. if self._get_total_team_lives(player.team) == 0: assert self._start_time is not None player.team.survival_seconds = int(ba.time() - self._start_time) # In solo, put ourself at the back of the spawn order. if self._duo_mode: player.team.spawn_order.remove(player) player.team.spawn_order.append(player)
def _on_pay_with_ad_press(self) -> None: from ba.internal import show_ad_2 # If we're already entering, ignore. if self._entering: return if not self._have_valid_data: ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return # Deny if it looks like the tourney has ended. if self._seconds_remaining == 0: ba.screenmessage(ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return cur_time = ba.time(ba.TimeType.REAL) if cur_time - self._last_ad_press_time > 5.0: self._last_ad_press_time = cur_time show_ad_2('tournament_entry', on_completion_call=ba.WeakCall(self._on_ad_complete))
def handlemessage(self, msg: Any) -> Any: if isinstance(msg, PlayerSpazDeathMessage): # Augment standard behavior. super().handlemessage(msg) curtime = ba.time() # Record the player's moment of death. PlayerData.get(msg.spaz.player).death_time = curtime # In co-op mode, end the game the instant everyone dies # (more accurate looking). # In teams/ffa, allow a one-second fudge-factor so we can # get more draws if players die basically at the same time. if isinstance(self.session, ba.CoopSession): # Teams will still show up if we check now.. check in # the next cycle. ba.pushcall(self._check_end_game) # Also record this for a final setting of the clock. self._last_player_death_time = curtime else: ba.timer(1.0, self._check_end_game) else: # Default handler: super().handlemessage(msg)
def end_game(self) -> None: # Stop our on-screen timer so players can see what they got. assert self._timer is not None self._timer.stop() results = ba.TeamGameResults() # If we won, set our score to the elapsed time # (there should just be 1 team here since this is co-op). # ..if we didn't win, leave scores as default (None) which means # we lost. if self._won: curtime = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(curtime, int) starttime = self._timer.getstarttime( timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(starttime, int) elapsed_time_ms = curtime - starttime ba.cameraflash() ba.playsound(self._winsound) for team in self.teams: for player in team.players: if player.actor: player.actor.handlemessage(ba.CelebrateMessage()) results.set_team_score(team, elapsed_time_ms) # Ends the activity. self.end(results)
def __init__(self, settings: dict): settings['map'] = 'Tower D' super().__init__(settings) shared = SharedObjects.get() self._preset = Preset(settings.get('preset', 'pro')) self._player_death_sound = ba.getsound('playerDeath') self._new_wave_sound = ba.getsound('scoreHit01') self._winsound = ba.getsound('score') self._cashregistersound = ba.getsound('cashRegister') self._bad_guy_score_sound = ba.getsound('shieldDown') self._heart_tex = ba.gettexture('heart') self._heart_model_opaque = ba.getmodel('heartOpaque') self._heart_model_transparent = ba.getmodel('heartTransparent') self._a_player_has_been_killed = False self._spawn_center = self._map_type.defs.points['spawn1'][0:3] self._tntspawnpos = self._map_type.defs.points['tnt_loc'][0:3] self._powerup_center = self._map_type.defs.boxes['powerup_region'][0:3] self._powerup_spread = ( self._map_type.defs.boxes['powerup_region'][6] * 0.5, self._map_type.defs.boxes['powerup_region'][8] * 0.5) self._score_region_material = ba.Material() self._score_region_material.add_actions( conditions=('they_have_material', shared.player_material), actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_reached_end), )) self._last_wave_end_time = ba.time() self._player_has_picked_up_powerup = False self._scoreboard: Optional[Scoreboard] = None self._game_over = False self._wavenum = 0 self._can_end_wave = True self._score = 0 self._time_bonus = 0 self._score_region: Optional[ba.Actor] = None self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') self._exclude_powerups: Optional[List[str]] = None self._have_tnt: Optional[bool] = None self._waves: Optional[List[Wave]] = None self._bots = SpazBotSet() self._tntspawner: Optional[TNTSpawner] = None self._lives_bg: Optional[ba.NodeActor] = None self._start_lives = 10 self._lives = self._start_lives self._lives_text: Optional[ba.NodeActor] = None self._flawless = True self._time_bonus_timer: Optional[ba.Timer] = None self._time_bonus_text: Optional[ba.NodeActor] = None self._time_bonus_mult: Optional[float] = None self._wave_text: Optional[ba.NodeActor] = None self._flawless_bonus: Optional[int] = None self._wave_update_timer: Optional[ba.Timer] = None
def start(self) -> None: """Start the timer.""" tval = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(tval, int) self._starttime_ms = tval self.inputnode.time1 = self._starttime_ms ba.getactivity().globalsnode.connectattr('time', self.inputnode, 'time2')
def _update(self) -> None: remaining = int(round(self._respawn_time - ba.time())) if remaining > 0: assert self._text is not None if self._text.node: self._text.node.text = str(remaining) else: self._visible = False self._image = self._text = self._timer = self._name = None
def _handle_flag_entered_base(self, team: Team) -> None: try: flag = ba.getcollision().opposingnode.getdelegate( StickyStormCTFFlag, True) except ba.NotFoundError: # Don't think this should logically ever happen. print( 'Error getting StickyStormCTFFlag in entering-base callback.') return if flag.team is team: team.home_flag_at_base = True # If the enemy flag is already here, score! if team.enemy_flag_at_base: self._score(team) else: team.enemy_flag_at_base = True if team.home_flag_at_base: # Award points to whoever was carrying the enemy flag. player = flag.last_player_to_hold if player and player.team is team: assert self.stats self.stats.player_scored(player, 50, big_message=True) # Update score and reset flags. self._score(team) # If the home-team flag isn't here, print a message to that effect. else: # Don't want slo-mo affecting this curtime = ba.time(ba.TimeType.BASE) if curtime - self._last_home_flag_notice_print_time > 5.0: self._last_home_flag_notice_print_time = curtime bpos = team.base_pos tval = ba.Lstr(resource='ownFlagAtYourBaseWarning') tnode = ba.newnode('text', attrs={ 'text': tval, 'in_world': True, 'scale': 0.013, 'color': (1, 1, 0, 1), 'h_align': 'center', 'position': (bpos[0], bpos[1] + 3.2, bpos[2]) }) ba.timer(5.1, tnode.delete) ba.animate(tnode, 'scale', { 0.0: 0, 0.2: 0.013, 4.8: 0.013, 5.0: 0 })
def _handle_flag_entered_base(self, team: ba.Team) -> None: node = ba.get_collision_info('opposing_node') assert isinstance(node, (ba.Node, type(None))) flag = CTFFlag.from_node(node) if not flag: print('Unable to get flag in _handle_flag_entered_base') return if flag.team is team: team.gamedata['home_flag_at_base'] = True # If the enemy flag is already here, score! if team.gamedata['enemy_flag_at_base']: self._score(team) else: team.gamedata['enemy_flag_at_base'] = True if team.gamedata['home_flag_at_base']: # Award points to whoever was carrying the enemy flag. player = flag.last_player_to_hold if player and player.team is team: assert self.stats self.stats.player_scored(player, 50, big_message=True) # Update score and reset flags. self._score(team) # If the home-team flag isn't here, print a message to that effect. else: # Don't want slo-mo affecting this curtime = ba.time(ba.TimeType.BASE) if curtime - self._last_home_flag_notice_print_time > 5.0: self._last_home_flag_notice_print_time = curtime bpos = team.gamedata['base_pos'] tval = ba.Lstr(resource='ownFlagAtYourBaseWarning') tnode = ba.newnode('text', attrs={ 'text': tval, 'in_world': True, 'scale': 0.013, 'color': (1, 1, 0, 1), 'h_align': 'center', 'position': (bpos[0], bpos[1] + 3.2, bpos[2]) }) ba.timer(5.1, tnode.delete) ba.animate(tnode, 'scale', { 0.0: 0, 0.2: 0.013, 4.8: 0.013, 5.0: 0 })
def __init__(self, data: dict[str, Any]): self._dialog_id = data['dialogID'] txt = ba.Lstr(translate=('serverResponses', data['text']), subs=data.get('subs', [])).evaluate() txt = txt.strip() txt_scale = 1.5 txt_height = (_ba.get_string_height(txt, suppress_warning=True) * txt_scale) self._width = 500 self._height = 130 + min(200, txt_height) uiscale = ba.app.ui.uiscale super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition='in_scale', scale=(1.8 if uiscale is ba.UIScale.SMALL else 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) self._starttime = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) ba.playsound(ba.getsound('swish')) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, 70 + (self._height - 70) * 0.5), size=(0, 0), color=(1.0, 3.0, 1.0), scale=txt_scale, h_align='center', v_align='center', text=txt, maxwidth=self._width * 0.85, max_height=(self._height - 110)) show_cancel = data.get('showCancel', True) self._cancel_button: Optional[ba.Widget] if show_cancel: self._cancel_button = ba.buttonwidget( parent=self._root_widget, position=(30, 30), size=(160, 60), autoselect=True, label=ba.Lstr(resource='cancelText'), on_activate_call=self._cancel_press) else: self._cancel_button = None self._ok_button = ba.buttonwidget( parent=self._root_widget, position=((self._width - 182) if show_cancel else (self._width * 0.5 - 80), 30), size=(160, 60), autoselect=True, label=ba.Lstr(resource='okText'), on_activate_call=self._ok_press) ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button, start_button=self._ok_button, selected_child=self._ok_button)
def _ok_press(self) -> None: if ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - self._starttime < 1000: ba.playsound(ba.getsound('error')) return _ba.add_transaction({ 'type': 'DIALOG_RESPONSE', 'dialogID': self._dialog_id, 'response': 1 }) ba.containerwidget(edit=self._root_widget, transition='out_scale')
def _type_char(self, char: str) -> None: ba.playsound(self._click_sound) if char.isspace(): if (ba.time(ba.TimeType.REAL) - self._last_space_press < self._double_space_interval): self._last_space_press = 0 self._next_keyboard() self._del() # We typed unneeded space around 1s ago. return self._last_space_press = ba.time(ba.TimeType.REAL) # Operate in unicode so we don't do anything funky like chop utf-8 # chars in half. txt = cast(str, ba.textwidget(query=self._text_field)) txt += char ba.textwidget(edit=self._text_field, text=txt) # If we were caps, go back only if not Shift is pressed twice. if self._mode == 'caps' and not self._double_press_shift: self._mode = 'normal' self._refresh()
def wavedash(self) -> None: if not self.node: return isMoving = abs(self.node.move_up_down) >= 0.5 or abs( self.node.move_left_right) >= 0.5 if self._dead or not self.grounded or not isMoving: return if self.node.knockout > 0.0 or self.frozen or self.node.hold_node: return t_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(t_ms, int) if t_ms - self.last_wavedash_time_ms >= self._wavedash_cooldown: move = [self.node.move_left_right, -self.node.move_up_down] vel = [self.node.velocity[0], self.node.velocity[2]] move_length = math.hypot(move[0], move[1]) vel_length = math.hypot(vel[0], vel[1]) if vel_length < 1.25: return move_norm = [m / move_length for m in move] vel_norm = [v / vel_length for v in vel] dot = sum(x * y for x, y in zip(move_norm, vel_norm)) turn_power = min(round(math.acos(dot) / math.pi, 2) * 1.3, 1) if turn_power < 0.2: return boost_power = math.sqrt(math.pow(vel[0], 2) + math.pow(vel[1], 2)) * 1.2 boost_power = min(pow(boost_power, 4), 160) #print(boost_power * turn_power) self.last_wavedash_time_ms = t_ms # FX ba.emitfx(position=self.node.position, velocity=(vel[0] * 0.5, -1, vel[1] * 0.5), chunk_type='sweat', count=8, scale=boost_power / 160 * turn_power, spread=0.25) # Boost itself pos = self.node.position for i in range(6): self.node.handlemessage('impulse', pos[0], -0.1 + pos[1] + i * 0.1, pos[2], 0, 0, 0, boost_power * turn_power, boost_power * turn_power, 0, 0, move[0], 0, move[1])
def buy(self, item: str) -> None: """Attempt to purchase the provided item.""" from ba.internal import (get_available_sale_time, get_store_item_name_translated) from bastd.ui import account from bastd.ui.confirm import ConfirmWindow from bastd.ui import getcurrency # Prevent pressing buy within a few seconds of the last press # (gives the buttons time to disable themselves and whatnot). curtime = ba.time(ba.TimeType.REAL) if self._last_buy_time is not None and (curtime - self._last_buy_time) < 2.0: ba.playsound(ba.getsound('error')) else: if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() else: self._last_buy_time = curtime # Pro is an actual IAP; the rest are ticket purchases. if item == 'pro': ba.playsound(ba.getsound('click01')) # Purchase either pro or pro_sale depending on whether # there is a sale going on. self._do_purchase_check('pro' if get_available_sale_time( 'extras') is None else 'pro_sale') else: price = _ba.get_account_misc_read_val( 'price.' + item, None) our_tickets = _ba.get_account_ticket_count() if price is not None and our_tickets < price: ba.playsound(ba.getsound('error')) getcurrency.show_get_tickets_prompt() else: def do_it() -> None: self._do_purchase_check(item, is_ticket_purchase=True) ba.playsound(ba.getsound('swish')) ConfirmWindow( ba.Lstr(resource='store.purchaseConfirmText', subs=[ ('${ITEM}', get_store_item_name_translated(item)) ]), width=400, height=120, action=do_it, ok_text=ba.Lstr(resource='store.purchaseText', fallback_resource='okText'))
def end_game(self) -> None: cur_time = ba.time() assert self._timer is not None start_time = self._timer.getstarttime() # Mark death-time as now for any still-living players # and award players points for how long they lasted. # (these per-player scores are only meaningful in team-games) for team in self.teams: for player in team.players: playerdata = PlayerData.get(player) survived = False # Throw an extra fudge factor in so teams that # didn't die come out ahead of teams that did. if playerdata.death_time is None: survived = True playerdata.death_time = cur_time + 1 # Award a per-player score depending on how many seconds # they lasted (per-player scores only affect teams mode; # everywhere else just looks at the per-team score). score = int(playerdata.death_time - self._timer.getstarttime()) if survived: score += 50 # A bit extra for survivors. self.stats.player_scored(player, score, screenmessage=False) # Stop updating our time text, and set the final time to match # exactly when our last guy died. self._timer.stop(endtime=self._last_player_death_time) # Ok now calc game results: set a score for each team and then tell # the game to end. results = ba.TeamGameResults() # Remember that 'free-for-all' mode is simply a special form # of 'teams' mode where each player gets their own team, so we can # just always deal in teams and have all cases covered. for team in self.teams: # Set the team score to the max time survived by any player on # that team. longest_life = 0.0 for player in team.players: playerdata = PlayerData.get(player) assert playerdata.death_time is not None longest_life = max(longest_life, playerdata.death_time - start_time) # Submit the score value in milliseconds. results.set_team_score(team, int(1000.0 * longest_life)) self.end(results=results)
def _on_cancel(self) -> None: # Don't allow canceling for several seconds after poking an enter # button if it looks like we're waiting on a purchase or entering # the tournament. if ((ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - self._last_ticket_press_time < 6000) and (_ba.have_outstanding_transactions() or _ba.get_purchased(self._purchase_name) or self._entering)): ba.playsound(ba.getsound('error')) return self._transition_out()
def _on_pay_with_tickets_press(self) -> None: from bastd.ui import getcurrency # If we're already entering, ignore. if self._entering: return if not self._have_valid_data: ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return # If we don't have a price. if self._purchase_price is None: ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return # Deny if it looks like the tourney has ended. if self._seconds_remaining == 0: ba.screenmessage(ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return # Deny if we don't have enough tickets. ticket_count: Optional[int] try: ticket_count = _ba.get_account_ticket_count() except Exception: # FIXME: should add a ba.NotSignedInError we can use here. ticket_count = None ticket_cost = self._purchase_price if ticket_count is not None and ticket_count < ticket_cost: getcurrency.show_get_tickets_prompt() ba.playsound(ba.getsound('error')) return cur_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) self._last_ticket_press_time = cur_time assert isinstance(ticket_cost, int) _ba.in_game_purchase(self._purchase_name, ticket_cost) self._entering = True _ba.add_transaction({ 'type': 'ENTER_TOURNAMENT', 'fee': self._fee, 'tournamentID': self._tournament_id }) _ba.run_transactions() self._launch()
def updatepartylist(): self._internet_join_last_refresh_time = now self._first_public_party_list_rebuild_time = ba.time( ba.TimeType.REAL) + 1 app = ba.app _ba.add_transaction( { 'type': 'PUBLIC_PARTY_QUERY', 'proto': app.protocol_version, 'lang': app.language }, callback=ba.WeakCall(self._on_public_party_query_result)) _ba.run_transactions()
def _update(self): if not self.target: del self # commit suicide because we have no goal in our existing :( return d = ba.Vec3(self.target.position) - ba.Vec3(self.position) if d.length() < 0.1: self._blast() del self return d = d.normalized() * 0.04 from math import sin, cos self.position = (self.position[0] + d.x + sin(ba.time() * 2) * 0.03, self.position[1] + d.y, self.position[2] + d.z + cos(ba.time() * 2) * 0.03) self._sparkle() ba.timer(0.001, ba.WeakCall(self._update))
def _process_pending_party_infos(self) -> None: starttime = time.time() # We want to do this in small enough pieces to not cause UI hitches. chunksize = 30 parties_in = self._pending_party_infos[:chunksize] self._pending_party_infos = self._pending_party_infos[chunksize:] for party_in in parties_in: addr = party_in['a'] assert isinstance(addr, str) port = party_in['p'] assert isinstance(port, int) party_key = f'{addr}_{port}' party = self._parties.get(party_key) if party is None: # If this party is new to us, init it. party = PartyEntry(address=addr, next_ping_time=ba.time(ba.TimeType.REAL) + 0.001 * party_in['pd'], index=self._next_entry_index) self._parties[party_key] = party self._parties_sorted.append((party_key, party)) self._party_lists_dirty = True self._next_entry_index += 1 assert isinstance(party.address, str) assert isinstance(party.next_ping_time, float) # Now, new or not, update its values. party.queue = party_in.get('q') assert isinstance(party.queue, (str, type(None))) party.port = port party.name = party_in['n'] assert isinstance(party.name, str) party.size = party_in['s'] assert isinstance(party.size, int) party.size_max = party_in['sm'] assert isinstance(party.size_max, int) # Server provides this in milliseconds; we use seconds. party.ping_interval = 0.001 * party_in['pi'] assert isinstance(party.ping_interval, float) party.stats_addr = party_in['sa'] assert isinstance(party.stats_addr, (str, type(None))) # Make sure the party's UI gets updated. party.clean_display_index = None if DEBUG_PROCESSING and parties_in: print(f'Processed {len(parties_in)} raw party infos in' f' {time.time()-starttime:.5f}s.')