From 6ff2b3093332a1c017a9c8df954f09975b47c621 Mon Sep 17 00:00:00 2001
From: Eric Froemling
Date: Fri, 8 Oct 2021 21:02:57 -0500
Subject: [PATCH] A bit of tidying for co-op server mode
---
.idea/dictionaries/ericf.xml | 1 +
CHANGELOG.md | 1 +
assets/src/ba_data/python/ba/_activity.py | 5 +-
assets/src/ba_data/python/ba/_coopsession.py | 62 +++++++++----------
assets/src/ba_data/python/ba/_level.py | 4 ++
assets/src/ba_data/python/ba/_servermode.py | 8 +--
assets/src/ba_data/python/ba/_session.py | 9 +--
.../ba_data/python/bastd/game/runaround.py | 2 +-
.../.idea/dictionaries/ericf.xml | 1 +
docs/ba_module.md | 16 ++---
tools/bacommon/servermanager.py | 14 +++--
tools/batools/build.py | 8 ++-
12 files changed, 70 insertions(+), 61 deletions(-)
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 18caaff6..b388fb62 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -1598,6 +1598,7 @@
outpath
outputter
outval
+ outvals
outvalue
ouya
overloadsigs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 723fde22..1e5b7d87 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,5 @@
### 1.6.5 (20388)
+- Added co-op support to server builds (thanks Dliwk!)
### 1.6.4 (20382)
- Some cleanups in the Favorites tab of the gather window.
diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py
index b19a2e40..4c9190ba 100644
--- a/assets/src/ba_data/python/ba/_activity.py
+++ b/assets/src/ba_data/python/ba/_activity.py
@@ -112,8 +112,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
# transitions).
inherits_tint = False
- # Whether players should be allowed to join in the middle of
- # activity.
+ # Whether players should be allowed to join in the middle of this
+ # activity. Note that Sessions may not allow mid-activity-joins even
+ # if the activity says its ok.
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/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py
index bb85a4b0..021142a9 100644
--- a/assets/src/ba_data/python/ba/_coopsession.py
+++ b/assets/src/ba_data/python/ba/_coopsession.py
@@ -166,51 +166,44 @@ class CoopSession(Session):
from ba._general import WeakCall
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:
- if not _ba.app.server:
- self._end_session_if_empty()
+ def _handle_empty_activity(self) -> None:
+ """Handle cases where all players have left the current activity."""
- activity = self.getactivity()
- 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:
+ from ba._gameactivity import GameActivity
activity = self.getactivity()
if activity is None:
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:
return
- # If there's *no* players left in the current activity but there *is*
- # in the session, restart the activity to pull them into the game
- # (or quit if they're just in the lobby).
+ # If there are *not* players in the current activity but there
+ # *are* in the session:
if not activity.players and self.sessionplayers:
- # Special exception for tourney games; don't auto-restart these.
- if self.tournament_id is not None:
- self.end()
- else:
- # Don't restart joining activities; this probably means there's
- # someone with a chooser up in that case.
- if not activity.is_joining_activity:
+ # If we're in a game, we should restart to pull in players
+ # currently waiting in the session.
+ if isinstance(activity, GameActivity):
+
+ # Never restart tourney games however; just end the session
+ # if all players are gone.
+ if self.tournament_id is not None:
+ self.end()
+ else:
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:
- 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(
self, resume_callback: Callable[[], Any]) -> None:
@@ -274,9 +267,10 @@ class CoopSession(Session):
else:
outcome = '' if results is None else results.get('outcome', '')
- # 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).
- if isinstance(activity, TutorialActivity):
+ # If we're running with a gui and 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).
+ if not _ba.app.headless_mode:
active_players = [p for p in self.sessionplayers if p.in_game]
if not active_players:
self.end()
diff --git a/assets/src/ba_data/python/ba/_level.py b/assets/src/ba_data/python/ba/_level.py
index c45f7156..ad764b15 100644
--- a/assets/src/ba_data/python/ba/_level.py
+++ b/assets/src/ba_data/python/ba/_level.py
@@ -36,6 +36,10 @@ class Level:
self._index: Optional[int] = None
self._score_version_string: Optional[str] = None
+ def __repr__(self) -> str:
+ cls = type(self)
+ return f"<{cls.__module__}.{cls.__name__} '{self._name}'>"
+
@property
def name(self) -> str:
"""The unique name for this Level."""
diff --git a/assets/src/ba_data/python/ba/_servermode.py b/assets/src/ba_data/python/ba/_servermode.py
index 0f03d4c5..7f06ddaa 100644
--- a/assets/src/ba_data/python/ba/_servermode.py
+++ b/assets/src/ba_data/python/ba/_servermode.py
@@ -98,8 +98,6 @@ 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.
@@ -349,11 +347,9 @@ class ServerController:
appcfg['Team Tournament Playlist Randomize'] = (
self._config.playlist_shuffle)
elif sessiontype is CoopSession:
- gamename = self._coop_game_name or 'Default:Onslaught Training'
- campaignname, levelname = gamename.split(':')
app.coop_session_args = {
- 'campaign': campaignname,
- 'level': levelname,
+ 'campaign': self._config.coop_campaign,
+ 'level': self._config.coop_level,
}
else:
raise RuntimeError(f'Unknown session type {sessiontype}')
diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py
index 618e9d00..c3f0c632 100644
--- a/assets/src/ba_data/python/ba/_session.py
+++ b/assets/src/ba_data/python/ba/_session.py
@@ -206,11 +206,12 @@ class Session:
return node
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.
+ """Ask ourself if we should allow joins during an Activity.
- Activity.allow_mid_activity_joins is also required to allow these
- joins."""
+ Note that for a join to be allowed, both the Session and Activity
+ have to be ok with it (via this function and the
+ Activity.allow_mid_activity_joins property.
+ """
del activity # Unused.
return True
diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py
index 711ee463..3607ecbd 100644
--- a/assets/src/ba_data/python/bastd/game/runaround.py
+++ b/assets/src/ba_data/python/bastd/game/runaround.py
@@ -561,7 +561,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
fail_message = None
else:
score = None
- fail_message = 'Reach wave 2 to rank.'
+ fail_message = ba.Lstr(resource='reachWave2Text')
self.end(delay=delay,
results={
diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
index 36dd41ab..b4e73a88 100644
--- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
@@ -766,6 +766,7 @@
outpath
outputter
outval
+ outvals
outvalue
ouya
ovld
diff --git a/docs/ba_module.md b/docs/ba_module.md
index f1ef308a..4d96634f 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -1837,11 +1837,11 @@ is pressed.
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.
+Ask ourself if we should allow joins during an Activity.
-Activity.allow_mid_activity_joins is also required to allow these
-joins.
+Note that for a join to be allowed, both the Session and Activity
+have to be ok with it (via this function and the
+Activity.allow_mid_activity_joins property.
@@ -5263,11 +5263,11 @@ session.setactivity(foo) and then ba.newnode(
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.
+Ask ourself if we should allow joins during an Activity.
-Activity.allow_mid_activity_joins is also required to allow these
-joins.
+Note that for a join to be allowed, both the Session and Activity
+have to be ok with it (via this function and the
+Activity.allow_mid_activity_joins property.
diff --git a/tools/bacommon/servermanager.py b/tools/bacommon/servermanager.py
index 128d5090..0ae3005c 100644
--- a/tools/bacommon/servermanager.py
+++ b/tools/bacommon/servermanager.py
@@ -55,10 +55,7 @@ class ServerConfig:
# 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
-
+ # Playlist-code for teams or free-for-all mode sessions.
# 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
@@ -76,6 +73,15 @@ class ServerConfig:
# (teams mode only).
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.
# IMPORTANT: This option is no longer available, as it was being used
# for exploits. Live access to the running server is still possible through
diff --git a/tools/batools/build.py b/tools/batools/build.py
index 18b3be97..7c914573 100644
--- a/tools/batools/build.py
+++ b/tools/batools/build.py
@@ -742,10 +742,14 @@ def _get_server_config_template_yaml(projroot: str) -> str:
continue
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(':')
vname = vname[:-1]
- assert veq == '='
+ # assert veq == '='
vval: Any
if vval_raw == 'field(default_factory=list)':
vval = []