A bit of tidying for co-op server mode

This commit is contained in:
Eric Froemling 2021-10-08 21:02:57 -05:00
parent 14ed8d24cb
commit 6ff2b30933
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
12 changed files with 70 additions and 61 deletions

View File

@ -1598,6 +1598,7 @@
<w>outpath</w> <w>outpath</w>
<w>outputter</w> <w>outputter</w>
<w>outval</w> <w>outval</w>
<w>outvals</w>
<w>outvalue</w> <w>outvalue</w>
<w>ouya</w> <w>ouya</w>
<w>overloadsigs</w> <w>overloadsigs</w>

View File

@ -1,4 +1,5 @@
### 1.6.5 (20388) ### 1.6.5 (20388)
- Added co-op support to server builds (thanks Dliwk!)
### 1.6.4 (20382) ### 1.6.4 (20382)
- Some cleanups in the Favorites tab of the gather window. - Some cleanups in the Favorites tab of the gather window.

View File

@ -112,8 +112,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
# transitions). # transitions).
inherits_tint = False inherits_tint = False
# Whether players should be allowed to join in the middle of # Whether players should be allowed to join in the middle of this
# activity. # activity. Note that Sessions may not allow mid-activity-joins even
# if the activity says its ok.
allow_mid_activity_joins: bool = True allow_mid_activity_joins: bool = True
# If the activity fades or transitions in, it should set the length of # If the activity fades or transitions in, it should set the length of

View File

@ -166,51 +166,44 @@ class CoopSession(Session):
from ba._general import WeakCall from ba._general import WeakCall
super().on_player_leave(sessionplayer) super().on_player_leave(sessionplayer)
_ba.timer(2.0, WeakCall(self._check_end_game)) _ba.timer(2.0, WeakCall(self._handle_empty_activity))
def _check_end_game(self) -> None: def _handle_empty_activity(self) -> None:
if not _ba.app.server: """Handle cases where all players have left the current activity."""
self._end_session_if_empty()
activity = self.getactivity() from ba._gameactivity import GameActivity
if activity is None:
return # Probably everything is already broken, why do something?
if [player for player in activity.players if player.is_alive()]:
return
with _ba.Context(activity):
from ba._gameactivity import GameActivity
if isinstance(activity, GameActivity):
activity.end_game()
def _end_session_if_empty(self) -> None:
activity = self.getactivity() activity = self.getactivity()
if activity is None: if activity is None:
return # Hmm what should we do in this case? return # Hmm what should we do in this case?
# If there's still players in the current activity, we're good. # If there are still players in the current activity, we're good.
if activity.players: if activity.players:
return return
# If there's *no* players left in the current activity but there *is* # If there are *not* players in the current activity but there
# in the session, restart the activity to pull them into the game # *are* in the session:
# (or quit if they're just in the lobby).
if not activity.players and self.sessionplayers: if not activity.players and self.sessionplayers:
# Special exception for tourney games; don't auto-restart these. # If we're in a game, we should restart to pull in players
if self.tournament_id is not None: # currently waiting in the session.
self.end() if isinstance(activity, GameActivity):
else:
# Don't restart joining activities; this probably means there's # Never restart tourney games however; just end the session
# someone with a chooser up in that case. # if all players are gone.
if not activity.is_joining_activity: if self.tournament_id is not None:
self.end()
else:
self.restart() self.restart()
# Hmm; no players anywhere. lets just end the session. # Hmm; no players anywhere. Let's end the entire session if we're
# running a GUI (or just the current game if we're running headless).
else: else:
self.end() if not _ba.app.headless_mode:
self.end()
else:
if isinstance(activity, GameActivity):
with _ba.Context(activity):
activity.end_game()
def _on_tournament_restart_menu_press( def _on_tournament_restart_menu_press(
self, resume_callback: Callable[[], Any]) -> None: self, resume_callback: Callable[[], Any]) -> None:
@ -274,9 +267,10 @@ class CoopSession(Session):
else: else:
outcome = '' if results is None else results.get('outcome', '') outcome = '' if results is None else results.get('outcome', '')
# If at any point we have no in-game players, quit out of the session # If we're running with a gui and at any point we have no
# (this can happen if someone leaves in the tutorial for instance). # in-game players, quit out of the session (this can happen if
if isinstance(activity, TutorialActivity): # someone leaves in the tutorial for instance).
if not _ba.app.headless_mode:
active_players = [p for p in self.sessionplayers if p.in_game] active_players = [p for p in self.sessionplayers if p.in_game]
if not active_players: if not active_players:
self.end() self.end()

View File

@ -36,6 +36,10 @@ class Level:
self._index: Optional[int] = None self._index: Optional[int] = None
self._score_version_string: Optional[str] = None self._score_version_string: Optional[str] = None
def __repr__(self) -> str:
cls = type(self)
return f"<{cls.__module__}.{cls.__name__} '{self._name}'>"
@property @property
def name(self) -> str: def name(self) -> str:
"""The unique name for this Level.""" """The unique name for this Level."""

View File

@ -98,8 +98,6 @@ class ServerController:
self._playlist_fetch_got_response = False self._playlist_fetch_got_response = False
self._playlist_fetch_code = -1 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 # Now sit around doing any pre-launch prep such as waiting for
# account sign-in or fetching playlists; this will kick off the # account sign-in or fetching playlists; this will kick off the
# session once done. # session once done.
@ -349,11 +347,9 @@ class ServerController:
appcfg['Team Tournament Playlist Randomize'] = ( appcfg['Team Tournament Playlist Randomize'] = (
self._config.playlist_shuffle) self._config.playlist_shuffle)
elif sessiontype is CoopSession: elif sessiontype is CoopSession:
gamename = self._coop_game_name or 'Default:Onslaught Training'
campaignname, levelname = gamename.split(':')
app.coop_session_args = { app.coop_session_args = {
'campaign': campaignname, 'campaign': self._config.coop_campaign,
'level': levelname, 'level': self._config.coop_level,
} }
else: else:
raise RuntimeError(f'Unknown session type {sessiontype}') raise RuntimeError(f'Unknown session type {sessiontype}')

View File

@ -206,11 +206,12 @@ class Session:
return node return node
def should_allow_mid_activity_joins(self, activity: ba.Activity) -> bool: def should_allow_mid_activity_joins(self, activity: ba.Activity) -> bool:
"""Returned value is used by the Session to determine """Ask ourself if we should allow joins during an Activity.
whether to allow players to join in the middle of activity.
Activity.allow_mid_activity_joins is also required to allow these Note that for a join to be allowed, both the Session and Activity
joins.""" have to be ok with it (via this function and the
Activity.allow_mid_activity_joins property.
"""
del activity # Unused. del activity # Unused.
return True return True

View File

@ -561,7 +561,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
fail_message = None fail_message = None
else: else:
score = None score = None
fail_message = 'Reach wave 2 to rank.' fail_message = ba.Lstr(resource='reachWave2Text')
self.end(delay=delay, self.end(delay=delay,
results={ results={

View File

@ -766,6 +766,7 @@
<w>outpath</w> <w>outpath</w>
<w>outputter</w> <w>outputter</w>
<w>outval</w> <w>outval</w>
<w>outvals</w>
<w>outvalue</w> <w>outvalue</w>
<w>ouya</w> <w>ouya</w>
<w>ovld</w> <w>ovld</w>

View File

@ -1837,11 +1837,11 @@ is pressed.</p>
<dt><h4><a name="method_ba_CoopSession__should_allow_mid_activity_joins">should_allow_mid_activity_joins()</a></dt></h4><dd> <dt><h4><a name="method_ba_CoopSession__should_allow_mid_activity_joins">should_allow_mid_activity_joins()</a></dt></h4><dd>
<p><span>should_allow_mid_activity_joins(self, activity: <a href="#class_ba_Activity">ba.Activity</a>) -&gt; bool</span></p> <p><span>should_allow_mid_activity_joins(self, activity: <a href="#class_ba_Activity">ba.Activity</a>) -&gt; bool</span></p>
<p>Returned value is used by the Session to determine <p>Ask ourself if we should allow joins during an Activity.</p>
whether to allow players to join in the middle of activity.</p>
<p>Activity.allow_mid_activity_joins is also required to allow these <p>Note that for a join to be allowed, both the Session and Activity
joins.</p> have to be ok with it (via this function and the
Activity.allow_mid_activity_joins property.</p>
</dd> </dd>
</dl> </dl>
@ -5263,11 +5263,11 @@ session.setactivity(foo) and then <a href="#function_ba_newnode">ba.newnode</a>(
<dt><h4><a name="method_ba_Session__should_allow_mid_activity_joins">should_allow_mid_activity_joins()</a></dt></h4><dd> <dt><h4><a name="method_ba_Session__should_allow_mid_activity_joins">should_allow_mid_activity_joins()</a></dt></h4><dd>
<p><span>should_allow_mid_activity_joins(self, activity: <a href="#class_ba_Activity">ba.Activity</a>) -&gt; bool</span></p> <p><span>should_allow_mid_activity_joins(self, activity: <a href="#class_ba_Activity">ba.Activity</a>) -&gt; bool</span></p>
<p>Returned value is used by the Session to determine <p>Ask ourself if we should allow joins during an Activity.</p>
whether to allow players to join in the middle of activity.</p>
<p>Activity.allow_mid_activity_joins is also required to allow these <p>Note that for a join to be allowed, both the Session and Activity
joins.</p> have to be ok with it (via this function and the
Activity.allow_mid_activity_joins property.</p>
</dd> </dd>
</dl> </dl>

View File

@ -55,10 +55,7 @@ class ServerConfig:
# This value is ignored if you supply a playlist_code (see below). # This value is ignored if you supply a playlist_code (see below).
session_type: str = 'ffa' session_type: str = 'ffa'
# There are unavailable co-op playlists now, so if you want to host a co-op # Playlist-code for teams or free-for-all mode sessions.
# game, pass level name here.
coop_game_name: Optional[str] = None
# To host your own custom playlists, use the 'share' functionality in the # To host your own custom playlists, use the 'share' functionality in the
# playlist editor in the regular version of the game. # playlist editor in the regular version of the game.
# This will give you a numeric code you can enter here to host that # This will give you a numeric code you can enter here to host that
@ -76,6 +73,15 @@ class ServerConfig:
# (teams mode only). # (teams mode only).
auto_balance_teams: bool = True auto_balance_teams: bool = True
# The campaign used when in co-op session mode.
# Do print(ba.app.campaigns) to see available campaign names.
coop_campaign: str = 'Easy'
# The level name within the campaign used in co-op session mode.
# For campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see
# available level names.
coop_level: str = 'Onslaught Training'
# Whether to enable telnet access. # Whether to enable telnet access.
# IMPORTANT: This option is no longer available, as it was being used # IMPORTANT: This option is no longer available, as it was being used
# for exploits. Live access to the running server is still possible through # for exploits. Live access to the running server is still possible through

View File

@ -742,10 +742,14 @@ def _get_server_config_template_yaml(projroot: str) -> str:
continue continue
if line != '' and not line.startswith('#'): if line != '' and not line.startswith('#'):
vname, _vtype, veq, vval_raw = line.split() before_equal_sign, vval_raw = line.split('=', 1)
before_equal_sign = before_equal_sign.strip()
vval_raw = vval_raw.strip()
# vname, _vtype, veq, vval_raw = line.split()
vname, _vtype = before_equal_sign.split()
assert vname.endswith(':') assert vname.endswith(':')
vname = vname[:-1] vname = vname[:-1]
assert veq == '=' # assert veq == '='
vval: Any vval: Any
if vval_raw == 'field(default_factory=list)': if vval_raw == 'field(default_factory=list)':
vval = [] vval = []