From 9e2fa821f31634baa06ebdc82ac1bddea3e4b4f0 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Sat, 10 Jul 2021 21:19:44 +0300 Subject: [PATCH 1/8] coop in headless builds stuff --- assets/src/ba_data/python/ba/_servermode.py | 13 +++ .../python/bastd/activity/coopscore.py | 93 ++++++++++++++++--- tools/bacommon/servermanager.py | 6 +- 3 files changed, 98 insertions(+), 14 deletions(-) diff --git a/assets/src/ba_data/python/ba/_servermode.py b/assets/src/ba_data/python/ba/_servermode.py index ac3bab8d..7c87af63 100644 --- a/assets/src/ba_data/python/ba/_servermode.py +++ b/assets/src/ba_data/python/ba/_servermode.py @@ -16,6 +16,7 @@ import _ba from ba._generated.enums import TimeType from ba._freeforallsession import FreeForAllSession from ba._dualteamsession import DualTeamSession +from ba._coopsession import CoopSession if TYPE_CHECKING: from typing import Optional, Dict, Any, Type @@ -97,6 +98,8 @@ class ServerController: self._playlist_fetch_got_response = False self._playlist_fetch_code = -1 + self._coop_game_name = self._config.coop_game_name + # Now sit around doing any pre-launch prep such as waiting for # account sign-in or fetching playlists; this will kick off the # session once done. @@ -289,6 +292,8 @@ class ServerController: return FreeForAllSession if self._config.session_type == 'teams': return DualTeamSession + if self._config.session_type == 'coop': + return CoopSession raise RuntimeError( f'Invalid session_type: "{self._config.session_type}"') @@ -311,6 +316,8 @@ class ServerController: ptypename = 'Free-for-All' elif sessiontype is DualTeamSession: ptypename = 'Team Tournament' + elif sessiontype is CoopSession: + ptypename = 'Coop' else: raise RuntimeError(f'Unknown session type {sessiontype}') @@ -340,6 +347,12 @@ class ServerController: appcfg['Team Tournament Playlist Selection'] = self._playlist_name appcfg['Team Tournament Playlist Randomize'] = ( self._config.playlist_shuffle) + elif sessiontype is CoopSession: + campaignname, levelname = self._coop_game_name.split(':') + app.coop_session_args = { + 'campaign': campaignname, + 'level': levelname, + } else: raise RuntimeError(f'Unknown session type {sessiontype}') diff --git a/assets/src/ba_data/python/bastd/activity/coopscore.py b/assets/src/ba_data/python/bastd/activity/coopscore.py index b8678cd0..a0a9d81f 100644 --- a/assets/src/ba_data/python/bastd/activity/coopscore.py +++ b/assets/src/ba_data/python/bastd/activity/coopscore.py @@ -118,6 +118,12 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._tournament_time_remaining_text: Optional[Text] = None self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None + # Stuff for activity skip by pressing button + self._birth_time = ba.time() + self._min_view_time = 5.0 + self._allow_server_transition = False + self._server_transitioning: Optional[bool] = True + self._playerinfos: List[ba.PlayerInfo] = settings['playerinfos'] assert isinstance(self._playerinfos, list) assert (isinstance(i, ba.PlayerInfo) for i in self._playerinfos) @@ -485,6 +491,51 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): if self._store_button_instance is not None: self._store_button_instance.set_position((pos_x + 100, pos_y)) + def _player_press(self) -> None: + # (Only for headless builds). + + print('HERE PLAYER PRESS') + + # If this activity is a good 'end point', ask server-mode just once if + # it wants to do anything special like switch sessions or kill the app. + if (self._allow_server_transition and _ba.app.server is not None + and self._server_transitioning is None): + self._server_transitioning = _ba.app.server.handle_transition() + assert isinstance(self._server_transitioning, bool) + + print('HERE IF PASSED') + + # If server-mode is handling this, don't do anything ourself. + if self._server_transitioning is True: + return + + print('HERE RESTARTING COOP') + # Otherwise restart current level. + print('HERE ui_restart() only for testing; replace after that') + self._ui_restart() + + def _safe_assign(self, player: EmptyPlayer) -> None: + # (Only for headless builds). + + # Just to be extra careful, don't assign if we're transitioning out. + # (though theoretically that should be ok). + if not self.is_transitioning_out() and player: + player.assigninput((ba.InputType.JUMP_PRESS, ba.InputType.PUNCH_PRESS, + ba.InputType.BOMB_PRESS, ba.InputType.PICK_UP_PRESS), + self._player_press) + + def on_player_join(self, player: PlayerType) -> None: + print('HERE ON PLAYER JOIN') + super().on_player_join(player) + + if ba.app.server is not None: + print('HERE INPUT ASSIGNED') + # Host can't press retry button, so anyone can do it instead. + time_till_assign = max( + 0, self._birth_time + self._min_view_time - _ba.time()) + + ba.timer(time_till_assign, ba.WeakCall(self._safe_assign, player)) + def on_begin(self) -> None: # FIXME: Clean this up. # pylint: disable=too-many-statements @@ -582,19 +633,35 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): color=(0.5, 0.7, 0.5, 1), position=(0, 230)).autoretain() - adisp = _ba.get_account_display_string() - txt = Text(ba.Lstr(resource='waitingForHostText', - subs=[('${HOST}', adisp)]), - maxwidth=300, - transition=Text.Transition.FADE_IN, - transition_delay=8.0, - scale=0.85, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - color=(1, 1, 0, 1), - position=(0, -230)).autoretain() - assert txt.node - txt.node.client_only = True + if ba.app.server is None: + # If we're running in normal non-headless build, show this text + # because only host can continue the game. + adisp = _ba.get_account_display_string() + txt = Text(ba.Lstr(resource='waitingForHostText', + subs=[('${HOST}', adisp)]), + maxwidth=300, + transition=Text.Transition.FADE_IN, + transition_delay=8.0, + scale=0.85, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + color=(1, 1, 0, 1), + position=(0, -230)).autoretain() + assert txt.node + txt.node.client_only = True + else: + # In headless build, anyone can continue the game. + sval = ba.Lstr(resource='pressAnyButtonPlayAgainText') + Text(sval, + v_attach=Text.VAttach.BOTTOM, + h_align=Text.HAlign.CENTER, + flash=True, + vr_depth=50, + position=(0, 60), + scale=0.8, + color=(0.5, 0.7, 0.5, 0.5), + transition=Text.Transition.IN_BOTTOM_SLOW, + transition_delay=self._min_view_time).autoretain() if self._score is not None: ba.timer(0.35, diff --git a/tools/bacommon/servermanager.py b/tools/bacommon/servermanager.py index c4a22464..128d5090 100644 --- a/tools/bacommon/servermanager.py +++ b/tools/bacommon/servermanager.py @@ -51,10 +51,14 @@ class ServerConfig: # exposed but I'll try to add that soon. max_party_size: int = 6 - # Options here are 'ffa' (free-for-all) and 'teams' + # Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative) # This value is ignored if you supply a playlist_code (see below). session_type: str = 'ffa' + # There are unavailable co-op playlists now, so if you want to host a co-op + # game, pass level name here. + coop_game_name: Optional[str] = None + # To host your own custom playlists, use the 'share' functionality in the # playlist editor in the regular version of the game. # This will give you a numeric code you can enter here to host that From fa0036f14cc1e66c223f200edcfedf80b06065a0 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Sat, 10 Jul 2021 22:27:48 +0300 Subject: [PATCH 2/8] got coop in headless builds working --- assets/src/ba_data/python/ba/_coopsession.py | 34 +++++++++++++++---- .../python/bastd/activity/coopscore.py | 21 +++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py index 6273f867..d0534e3c 100644 --- a/assets/src/ba_data/python/ba/_coopsession.py +++ b/assets/src/ba_data/python/ba/_coopsession.py @@ -156,8 +156,29 @@ class CoopSession(Session): from ba._general import WeakCall super().on_player_leave(sessionplayer) - # If all our players leave we wanna quit out of the session. - _ba.timer(2.0, WeakCall(self._end_session_if_empty)) + if _ba.app.server is not None: + # If we're in server mode, end game and show results. + activity = self.getactivity() + _ba.timer(2.0, WeakCall(self._end_activity_if_empty)) + else: + # Otherwise, if all our players leave + # we wanna quit out of the session. + _ba.timer(2.0, WeakCall(self._end_session_if_empty)) + + def _end_activity_if_empty(self) -> None: + activity = self.getactivity() + if activity is None: + return # Probably everything is already broken, why do something? + + if activity.players: + return + + with _ba.Context(activity): + from ba._gameactivity import GameActivity + + # FIXME: rewrite this, doesn't cover all cases + assert isinstance(activity, GameActivity) + activity.end_game() def _end_session_if_empty(self) -> None: activity = self.getactivity() @@ -250,10 +271,11 @@ class CoopSession(Session): # If at any point we have no in-game players, quit out of the session # (this can happen if someone leaves in the tutorial for instance). - active_players = [p for p in self.sessionplayers if p.in_game] - if not active_players: - self.end() - return + if isinstance(activity, TutorialActivity): + active_players = [p for p in self.sessionplayers if p.in_game] + if not active_players: + self.end() + return # If we're in a between-round activity or a restart-activity, # hop into a round. diff --git a/assets/src/ba_data/python/bastd/activity/coopscore.py b/assets/src/ba_data/python/bastd/activity/coopscore.py index a0a9d81f..fa4165db 100644 --- a/assets/src/ba_data/python/bastd/activity/coopscore.py +++ b/assets/src/ba_data/python/bastd/activity/coopscore.py @@ -122,7 +122,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._birth_time = ba.time() self._min_view_time = 5.0 self._allow_server_transition = False - self._server_transitioning: Optional[bool] = True + self._server_transitioning: Optional[bool] = None self._playerinfos: List[ba.PlayerInfo] = settings['playerinfos'] assert isinstance(self._playerinfos, list) @@ -494,8 +494,6 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): def _player_press(self) -> None: # (Only for headless builds). - print('HERE PLAYER PRESS') - # If this activity is a good 'end point', ask server-mode just once if # it wants to do anything special like switch sessions or kill the app. if (self._allow_server_transition and _ba.app.server is not None @@ -503,16 +501,14 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._server_transitioning = _ba.app.server.handle_transition() assert isinstance(self._server_transitioning, bool) - print('HERE IF PASSED') - # If server-mode is handling this, don't do anything ourself. if self._server_transitioning is True: return - print('HERE RESTARTING COOP') # Otherwise restart current level. - print('HERE ui_restart() only for testing; replace after that') - self._ui_restart() + self._campaign.set_selected_level(self._level_name) + with ba.Context(self): + self.end({'outcome': 'restart'}) def _safe_assign(self, player: EmptyPlayer) -> None: # (Only for headless builds). @@ -520,16 +516,15 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): # Just to be extra careful, don't assign if we're transitioning out. # (though theoretically that should be ok). if not self.is_transitioning_out() and player: - player.assigninput((ba.InputType.JUMP_PRESS, ba.InputType.PUNCH_PRESS, - ba.InputType.BOMB_PRESS, ba.InputType.PICK_UP_PRESS), - self._player_press) + player.assigninput( + (ba.InputType.JUMP_PRESS, ba.InputType.PUNCH_PRESS, + ba.InputType.BOMB_PRESS, ba.InputType.PICK_UP_PRESS), + self._player_press) def on_player_join(self, player: PlayerType) -> None: - print('HERE ON PLAYER JOIN') super().on_player_join(player) if ba.app.server is not None: - print('HERE INPUT ASSIGNED') # Host can't press retry button, so anyone can do it instead. time_till_assign = max( 0, self._birth_time + self._min_view_time - _ba.time()) From 5c3b7077103b71f095faaded746913647c4e2987 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Sat, 10 Jul 2021 22:47:40 +0300 Subject: [PATCH 3/8] tidying --- .idea/dictionaries/roman.xml | 1 + assets/src/ba_data/python/ba/_coopsession.py | 1 - assets/src/ba_data/python/ba/_servermode.py | 4 +++- assets/src/ba_data/python/bastd/activity/coopscore.py | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.idea/dictionaries/roman.xml b/.idea/dictionaries/roman.xml index 240cd39e..d594223f 100644 --- a/.idea/dictionaries/roman.xml +++ b/.idea/dictionaries/roman.xml @@ -1,6 +1,7 @@ + gamename maxlen pagename diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py index d0534e3c..3435569e 100644 --- a/assets/src/ba_data/python/ba/_coopsession.py +++ b/assets/src/ba_data/python/ba/_coopsession.py @@ -158,7 +158,6 @@ class CoopSession(Session): if _ba.app.server is not None: # If we're in server mode, end game and show results. - activity = self.getactivity() _ba.timer(2.0, WeakCall(self._end_activity_if_empty)) else: # Otherwise, if all our players leave diff --git a/assets/src/ba_data/python/ba/_servermode.py b/assets/src/ba_data/python/ba/_servermode.py index 7c87af63..0f03d4c5 100644 --- a/assets/src/ba_data/python/ba/_servermode.py +++ b/assets/src/ba_data/python/ba/_servermode.py @@ -299,6 +299,7 @@ class ServerController: def _launch_server_session(self) -> None: """Kick off a host-session based on the current server config.""" + # pylint: disable=too-many-branches app = _ba.app appcfg = app.config sessiontype = self._get_session_type() @@ -348,7 +349,8 @@ class ServerController: appcfg['Team Tournament Playlist Randomize'] = ( self._config.playlist_shuffle) elif sessiontype is CoopSession: - campaignname, levelname = self._coop_game_name.split(':') + gamename = self._coop_game_name or 'Default:Onslaught Training' + campaignname, levelname = gamename.split(':') app.coop_session_args = { 'campaign': campaignname, 'level': levelname, diff --git a/assets/src/ba_data/python/bastd/activity/coopscore.py b/assets/src/ba_data/python/bastd/activity/coopscore.py index fa4165db..dc201f55 100644 --- a/assets/src/ba_data/python/bastd/activity/coopscore.py +++ b/assets/src/ba_data/python/bastd/activity/coopscore.py @@ -510,7 +510,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): with ba.Context(self): self.end({'outcome': 'restart'}) - def _safe_assign(self, player: EmptyPlayer) -> None: + def _safe_assign(self, player: ba.Player) -> None: # (Only for headless builds). # Just to be extra careful, don't assign if we're transitioning out. @@ -521,7 +521,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): ba.InputType.BOMB_PRESS, ba.InputType.PICK_UP_PRESS), self._player_press) - def on_player_join(self, player: PlayerType) -> None: + def on_player_join(self, player: ba.Player) -> None: super().on_player_join(player) if ba.app.server is not None: From 2d39ad7121bfa2f7ca43e5b9b1c2d1ae6dd4ac03 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Tue, 21 Sep 2021 17:12:48 +0300 Subject: [PATCH 4/8] some cleanups; fixed trouble from PR message --- assets/src/ba_data/python/ba/_activity.py | 4 ++++ assets/src/ba_data/python/ba/_coopgame.py | 2 ++ assets/src/ba_data/python/ba/_coopsession.py | 9 ++++----- assets/src/ba_data/python/ba/_session.py | 8 +------- .../ba_data/python/bastd/game/elimination.py | 19 ++----------------- 5 files changed, 13 insertions(+), 29 deletions(-) diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py index 0060ab64..d06e3dc2 100644 --- a/assets/src/ba_data/python/ba/_activity.py +++ b/assets/src/ba_data/python/ba/_activity.py @@ -112,6 +112,10 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # transitions). inherits_tint = False + # Whether players should be allowed to join in the middle of + # activities. + allow_mid_activity_joins: bool = True + # If the activity fades or transitions in, it should set the length of # time here so that previous activities will be kept alive for that # long (avoiding 'holes' in the screen) diff --git a/assets/src/ba_data/python/ba/_coopgame.py b/assets/src/ba_data/python/ba/_coopgame.py index 1811bb05..f7c3dc07 100644 --- a/assets/src/ba_data/python/ba/_coopgame.py +++ b/assets/src/ba_data/python/ba/_coopgame.py @@ -27,6 +27,8 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): # We can assume our session is a CoopSession. session: ba.CoopSession + allow_mid_activity_joins = False + @classmethod def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: from ba._coopsession import CoopSession diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py index 3435569e..7d5b0ff4 100644 --- a/assets/src/ba_data/python/ba/_coopsession.py +++ b/assets/src/ba_data/python/ba/_coopsession.py @@ -158,13 +158,13 @@ class CoopSession(Session): if _ba.app.server is not None: # If we're in server mode, end game and show results. - _ba.timer(2.0, WeakCall(self._end_activity_if_empty)) + _ba.timer(2.0, WeakCall(self._end_game_if_empty)) else: # Otherwise, if all our players leave # we wanna quit out of the session. _ba.timer(2.0, WeakCall(self._end_session_if_empty)) - def _end_activity_if_empty(self) -> None: + def _end_game_if_empty(self) -> None: activity = self.getactivity() if activity is None: return # Probably everything is already broken, why do something? @@ -175,9 +175,8 @@ class CoopSession(Session): with _ba.Context(activity): from ba._gameactivity import GameActivity - # FIXME: rewrite this, doesn't cover all cases - assert isinstance(activity, GameActivity) - activity.end_game() + if isinstance(activity, GameActivity): + activity.end_game() def _end_session_if_empty(self) -> None: activity = self.getactivity() diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 7bb94288..6ff90754 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -63,10 +63,6 @@ class Session: team instead of their own profile colors. This only applies if use_teams is enabled. - allow_mid_activity_joins - Whether players should be allowed to join in the middle of - activities. - customdata A shared dictionary for objects to use as storage on this session. Ensure that keys here are unique to avoid collisions. @@ -74,7 +70,6 @@ class Session: """ use_teams: bool = False use_team_colors: bool = True - allow_mid_activity_joins: bool = True # Note: even though these are instance vars, we annotate them at the # class level so that docs generation can access their types. @@ -220,7 +215,6 @@ class Session: if _ba.app.stress_test_reset_timer is None: if len(self.sessionplayers) >= self.max_players: - # Print a rejection message *only* to the client trying to # join (prevents spamming everyone else in the game). _ba.playsound(_ba.getsound('error')) @@ -657,7 +651,7 @@ class Session: # However, if we're not allowing mid-game joins, don't actually pass; # just announce the arrival and say they'll partake next round. if pass_to_activity: - if not self.allow_mid_activity_joins: + if not activity.allow_mid_activity_joins: pass_to_activity = False with _ba.Context(self): _ba.screenmessage( diff --git a/assets/src/ba_data/python/bastd/game/elimination.py b/assets/src/ba_data/python/bastd/game/elimination.py index aac7d823..dd95a469 100644 --- a/assets/src/ba_data/python/bastd/game/elimination.py +++ b/assets/src/ba_data/python/bastd/game/elimination.py @@ -179,6 +179,8 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): # Show messages when players die since it's meaningful here. announce_player_deaths = True + allow_mid_activity_joins = False + @classmethod def get_available_settings( cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]: @@ -257,23 +259,6 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): self.session, ba.DualTeamSession) else 'last one standing wins' def on_player_join(self, player: Player) -> None: - - # No longer allowing mid-game joiners here; too easy to exploit. - if self.has_begun(): - - # Make sure their team has survival seconds set if they're all dead - # (otherwise blocked new ffa players are considered 'still alive' - # in score tallying). - if (self._get_total_team_lives(player.team) == 0 - and player.team.survival_seconds is None): - player.team.survival_seconds = 0 - ba.screenmessage( - ba.Lstr(resource='playerDelayedJoinText', - subs=[('${PLAYER}', player.getname(full=True))]), - color=(0, 1, 0), - ) - return - player.lives = self._lives_per_player if self._solo_mode: From 843d064bd2c9005e46493161f8a1f5d448b545e2 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Tue, 21 Sep 2021 17:53:58 +0300 Subject: [PATCH 5/8] fix --- assets/src/ba_data/python/ba/_coopsession.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py index 7d5b0ff4..c6674c61 100644 --- a/assets/src/ba_data/python/ba/_coopsession.py +++ b/assets/src/ba_data/python/ba/_coopsession.py @@ -156,20 +156,17 @@ class CoopSession(Session): from ba._general import WeakCall super().on_player_leave(sessionplayer) - if _ba.app.server is not None: - # If we're in server mode, end game and show results. - _ba.timer(2.0, WeakCall(self._end_game_if_empty)) - else: - # Otherwise, if all our players leave - # we wanna quit out of the session. - _ba.timer(2.0, WeakCall(self._end_session_if_empty)) + _ba.timer(2.0, WeakCall(self._check_end_game)) + + def _check_end_game(self) -> None: + if not _ba.app.server: + self._end_session_if_empty() - def _end_game_if_empty(self) -> None: activity = self.getactivity() if activity is None: return # Probably everything is already broken, why do something? - if activity.players: + if [player for player in activity.players if player.is_alive()]: return with _ba.Context(activity): From fed8ca9400960bd1e1cd55ed75dc0a57bca8a180 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Fri, 8 Oct 2021 19:26:05 +0300 Subject: [PATCH 6/8] Add should_allow_mid_activity_joins(activity: Activity) -> bool --- assets/src/ba_data/python/ba/_activity.py | 2 +- assets/src/ba_data/python/ba/_coopgame.py | 2 -- assets/src/ba_data/python/ba/_coopsession.py | 10 +++++++++ assets/src/ba_data/python/ba/_session.py | 11 +++++++++- .../ba_data/python/bastd/game/meteorshower.py | 22 +++++-------------- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py index d06e3dc2..b19a2e40 100644 --- a/assets/src/ba_data/python/ba/_activity.py +++ b/assets/src/ba_data/python/ba/_activity.py @@ -113,7 +113,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): inherits_tint = False # Whether players should be allowed to join in the middle of - # activities. + # activity. allow_mid_activity_joins: bool = True # If the activity fades or transitions in, it should set the length of diff --git a/assets/src/ba_data/python/ba/_coopgame.py b/assets/src/ba_data/python/ba/_coopgame.py index f7c3dc07..1811bb05 100644 --- a/assets/src/ba_data/python/ba/_coopgame.py +++ b/assets/src/ba_data/python/ba/_coopgame.py @@ -27,8 +27,6 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): # We can assume our session is a CoopSession. session: ba.CoopSession - allow_mid_activity_joins = False - @classmethod def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: from ba._coopsession import CoopSession diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py index c6674c61..fed532e9 100644 --- a/assets/src/ba_data/python/ba/_coopsession.py +++ b/assets/src/ba_data/python/ba/_coopsession.py @@ -90,6 +90,16 @@ class CoopSession(Session): """Get the game instance currently being played.""" return self._current_game_instance + def should_allow_mid_activity_joins(self, activity: Activity) -> bool: + # pylint: disable=cyclic-import + from ba._gameactivity import GameActivity + + # Disallow any joins in the middle of the game. + if isinstance(activity, GameActivity): + return False + + return True + def _update_on_deck_game_instances(self) -> None: # pylint: disable=cyclic-import from ba._gameactivity import GameActivity diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 6ff90754..2c7e6520 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -205,6 +205,14 @@ class Session: raise NodeNotFoundError() return node + def should_allow_mid_activity_joins(self, activity: Activity) -> bool: + """Returned value is used by the Session to determine + whether to allow players to join in the middle of activity. + + Activity.allow_mid_activity_joins is also required to allow these + joins.""" + return True + def on_player_request(self, player: ba.SessionPlayer) -> bool: """Called when a new ba.Player wants to join the Session. @@ -651,7 +659,8 @@ class Session: # However, if we're not allowing mid-game joins, don't actually pass; # just announce the arrival and say they'll partake next round. if pass_to_activity: - if not activity.allow_mid_activity_joins: + if not (activity.allow_mid_activity_joins + and self.should_allow_mid_activity_joins(activity)): pass_to_activity = False with _ba.Context(self): _ba.screenmessage( diff --git a/assets/src/ba_data/python/bastd/game/meteorshower.py b/assets/src/ba_data/python/bastd/game/meteorshower.py index 9be79902..32c62c0d 100644 --- a/assets/src/ba_data/python/bastd/game/meteorshower.py +++ b/assets/src/ba_data/python/bastd/game/meteorshower.py @@ -44,7 +44,11 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): # Print messages when players die (since its meaningful in this game). announce_player_deaths = True - # we're currently hard-coded for one map.. + # Don't allow joining after we start + # (would enable leave/rejoin tomfoolery). + allow_mid_activity_joins = False + + # We're currently hard-coded for one map. @classmethod def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: return ['Rampage'] @@ -93,22 +97,6 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): # Check for immediate end (if we've only got 1 player, etc). ba.timer(5.0, self._check_end_game) - def on_player_join(self, player: Player) -> None: - # Don't allow joining after we start - # (would enable leave/rejoin tomfoolery). - if self.has_begun(): - ba.screenmessage( - ba.Lstr(resource='playerDelayedJoinText', - subs=[('${PLAYER}', player.getname(full=True))]), - color=(0, 1, 0), - ) - # For score purposes, mark them as having died right as the - # game started. - assert self._timer is not None - player.death_time = self._timer.getstarttime() - return - self.spawn_player(player) - def on_player_leave(self, player: Player) -> None: # Augment default behavior. super().on_player_leave(player) From 4db6ec8d455eaa651639e691f40623ba774ad22b Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Fri, 8 Oct 2021 19:44:24 +0300 Subject: [PATCH 7/8] lint fixes --- assets/src/ba_data/python/ba/_coopsession.py | 2 +- assets/src/ba_data/python/ba/_session.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py index fed532e9..bb85a4b0 100644 --- a/assets/src/ba_data/python/ba/_coopsession.py +++ b/assets/src/ba_data/python/ba/_coopsession.py @@ -90,7 +90,7 @@ class CoopSession(Session): """Get the game instance currently being played.""" return self._current_game_instance - def should_allow_mid_activity_joins(self, activity: Activity) -> bool: + def should_allow_mid_activity_joins(self, activity: ba.Activity) -> bool: # pylint: disable=cyclic-import from ba._gameactivity import GameActivity diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 2c7e6520..9170d214 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -205,7 +205,7 @@ class Session: raise NodeNotFoundError() return node - def should_allow_mid_activity_joins(self, activity: Activity) -> bool: + def should_allow_mid_activity_joins(self, activity: ba.Activity) -> bool: """Returned value is used by the Session to determine whether to allow players to join in the middle of activity. From 7359173ff936df686d29b1db0656322d83d62e66 Mon Sep 17 00:00:00 2001 From: Roman Trapeznikov Date: Fri, 8 Oct 2021 19:49:46 +0300 Subject: [PATCH 8/8] more lint fixes --- assets/src/ba_data/python/ba/_session.py | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 9170d214..618e9d00 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -211,6 +211,7 @@ class Session: Activity.allow_mid_activity_joins is also required to allow these joins.""" + del activity # Unused. return True def on_player_request(self, player: ba.SessionPlayer) -> bool: