X-Git-Url: https://vilimpoc.org/repos/bl2-split/blobdiff_plain/5765b872626e0ee9da7d0311906bcd91429b9e69..320d816d9d10f21981e28b0a82e4808eacb7845c:/bl2-splitscreen.sh?ds=sidebyside diff --git a/bl2-splitscreen.sh b/bl2-splitscreen.sh index ed4e040..86c3517 100755 --- a/bl2-splitscreen.sh +++ b/bl2-splitscreen.sh @@ -78,15 +78,14 @@ PROTONPATH="${PROTONPATH:-}" # experimental and regular) segfaults this binary; classic goldberg works. NATIVE_RUNTIME="${NATIVE_RUNTIME:-/home/max/.steam/steam/ubuntu12_32/steam-runtime/run.sh}" -# Seconds to wait before auto-tiling, i.e. how long the intro logos/movies take to -# clear on your machine. Bump it if the windows snap into place too early (while -# still on the splash). You can always re-run `./bl2-splitscreen.sh tile`. -SPLASH_WAIT="${SPLASH_WAIT:-45}" - -# Seconds to wait after launching Player 1 before launching Player 2. Giving P1 -# time to reach the menu and start hosting reduces a co-op-startup race that can -# crash the second (fragile Aspyr) instance. -STAGGER="${STAGGER:-25}" +# Seconds after launch before the windows are moved onto their screens (long enough +# for both to load past the resize-y splash). You can always re-run `.../tile`. +SPLASH_WAIT="${SPLASH_WAIT:-30}" + +# Seconds between launching Player 1 and Player 2. 0 = both at once (this machine is +# fast enough). Native mode can be crash-prone launching both together - set e.g. +# STAGGER=25 there if the 2nd instance dies on startup. +STAGGER="${STAGGER:-0}" # =========================================================================== c_red=$'\e[31m'; c_grn=$'\e[32m'; c_yel=$'\e[33m'; c_dim=$'\e[2m'; c_rst=$'\e[0m' @@ -94,6 +93,13 @@ say() { printf '%s\n' "$*"; } ok() { printf '%s✓%s %s\n' "$c_grn" "$c_rst" "$*"; } warn() { printf '%s!%s %s\n' "$c_yel" "$c_rst" "$*"; } die() { printf '%s✗ %s%s\n' "$c_red" "$*" "$c_rst" >&2; exit 1; } +# Loud, unmissable, bold-bright-yellow banner. Each arg is one line (keep it CAPS). +banner() { + local b=$'\e[1;93m' r=$'\e[0m' bar="############################################################" l + printf '\n%s%s%s\n' "$b" "$bar" "$r" + for l in "$@"; do printf '%s## %-54s##%s\n' "$b" "$l" "$r"; done + printf '%s%s%s\n\n' "$b" "$bar" "$r" +} # -------------------------------------------------------- mode detection --- WIN_EXE_REL="Binaries/Win32/Borderlands2.exe" @@ -297,8 +303,13 @@ patch_native_config() { # $1=player home $2=width $3=height }' "$f" > "$f.tmp" && mv "$f.tmp" "$f" } -# Proton keeps its config inside the Wine prefix; drop the intro movies there so -# the game goes straight to the menu (resolution is handled by -ResX/-ResY args). +# Proton keeps its config inside the Wine prefix. We patch WillowEngine.ini to: +# - drop the intro movies (straight to menu; resolution is set by -ResX/-ResY); +# - always keep RUNNING when unfocused (bPauseOnLossOfFocus=FALSE) - split-screen +# focuses only one window at a time, and the default TRUE freezes the unfocused +# instance (its load stalls until you nudge the mouse); +# - mute when unfocused (bMuteAudioWhenNotInFocus=TRUE) so audio follows the focused +# window instead of every instance playing at once. patch_proton_config() { # $1 = prefix dir local f f=$(find "$1/drive_c/users" -ipath "*Borderlands 2*Config/WillowEngine.ini" 2>/dev/null | head -1) @@ -308,6 +319,10 @@ patch_proton_config() { # $1 = prefix dir /^\[/ { sec=$0 } { l=$0 if (sec=="[FullScreenMovie]") { if (l ~ /^StartupMovies=/) next; else if (l ~ /^bForceNoMovies=/) l="bForceNoMovies=TRUE" } + else if (sec=="[Engine.Engine]") { + if (l ~ /^bPauseOnLossOfFocus=/) l="bPauseOnLossOfFocus=FALSE" + else if (l ~ /^bMuteAudioWhenNotInFocus=/) l="bMuteAudioWhenNotInFocus=TRUE" + } printf "%s%s\n", l, cr }' "$f" > "$f.tmp" && mv "$f.tmp" "$f" } @@ -326,6 +341,10 @@ launch_player() { # $1=n $2=pad-node $3=vidpid -> sets LAUNCHED_PID local n="$1" pad="$2" vidpid="$3" pg="$BASE/p$1/game" wd local rw="${GW[$n]}" rh="${GH[$n]}" # this player's render size local -a env cmd + # Run under gamemode if installed: it flips the CPU to the performance governor + # (system-wide) while playing, so instances load at full speed instead of stalling + # on a power-saving governor until you jiggle the mouse. No-op if not installed. + local -a gm=(); command -v gamemoderun >/dev/null && gm=(gamemoderun) # No gamescope in either mode: it doesn't pass raw controllers to nested clients, # so the game sees no pad. Run the game windowed and let KWin place it. if [ "$MODE" = native ]; then @@ -335,10 +354,10 @@ launch_player() { # $1=n $2=pad-node $3=vidpid -> sets LAUNCHED_PID "LD_LIBRARY_PATH=$pg:${LD_LIBRARY_PATH:-}" "SteamAppId=$APPID" "SteamGameId=$APPID" "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS=1" ) - cmd=( "$NATIVE_RUNTIME" "./$NATIVE_BIN" -windowed "-ResX=$rw" "-ResY=$rh" ) + cmd=( "${gm[@]}" "$NATIVE_RUNTIME" "./$NATIVE_BIN" -windowed "-ResX=$rw" "-ResY=$rh" ) else wd="$pg/Binaries/Win32" - patch_proton_config "$BASE/p$n/prefix" # skip intro movies (if prefix exists) + patch_proton_config "$BASE/p$n/prefix" # movies off, no focus-pause, mute-when-unfocused mkdir -p "$BASE/dxvk-cache" env=( "WINEPREFIX=$BASE/p$n/prefix" "GAMEID=0" "STORE=none" "UMU_RUNTIME_UPDATE=0" # skip umu's startup update check @@ -349,7 +368,7 @@ launch_player() { # $1=n $2=pad-node $3=vidpid -> sets LAUNCHED_PID # redundant AND can over-filter (it wrongly dropped the 360 pad), so skip it. [ "$ISOLATION" = sdl ] && env+=( "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT=$vidpid" ) [ -n "$PROTONPATH" ] && env+=( "PROTONPATH=$PROTONPATH" ) - cmd=( umu-run "$pg/$WIN_EXE_REL" -windowed "-ResX=$rw" "-ResY=$rh" ) + cmd=( "${gm[@]}" umu-run "$pg/$WIN_EXE_REL" -windowed "-ResX=$rw" "-ResY=$rh" ) fi # bwrap (if used) masks the other pad's /dev/input nodes for this instance. @@ -423,22 +442,26 @@ cmd_run() { say "${c_dim}P1 ${GW[1]}x${GH[1]}@${GX[1]},${GY[1]} P2 ${GW[2]}x${GH[2]}@${GX[2]},${GY[2]}${c_rst}" local pid1 pid2 launch_player 1 "$p1" "$P1_VIDPID"; pid1=$LAUNCHED_PID - sleep "$STAGGER" + [ "$STAGGER" -gt 0 ] 2>/dev/null && sleep "$STAGGER" launch_player 2 "$p2" "$P2_VIDPID"; pid2=$LAUNCHED_PID - # Place windows AFTER the splash (the game resizes itself during it). Fire a few - # times so both instances get caught once settled. + local where="THEIR MONITORS"; [ "$DISPLAY_MODE" = split ] && where="THEIR HALVES" + banner "WIGGLE YOUR MOUSE NOW -- KEEP IT MOVING" \ + "UNTIL BOTH GAMES REACH THE MAIN MENU." \ + "" \ + "DO NOT TOUCH THE WINDOWS: I WILL MOVE THEM" \ + "ONTO $where IN $SPLASH_WAIT SECONDS." + + # Move windows AFTER the splash - the game resizes itself during it, so tiling too + # early makes the window flicker. Fire a few times so both settle. ( sleep "$SPLASH_WAIT"; cmd_tile >/dev/null 2>&1 - sleep 10; cmd_tile >/dev/null 2>&1 - sleep 15; cmd_tile >/dev/null 2>&1 ) & + sleep 8; cmd_tile >/dev/null 2>&1 + sleep 12; cmd_tile >/dev/null 2>&1 ) & - say "" ok "Both instances launching (pids $pid1 / $pid2)." say "In-game: ${c_grn}Player 1${c_rst} → Play → host over LAN. ${c_grn}Player 2${c_rst} → Play → Join → pick the LAN game." - local where="their monitors"; [ "$DISPLAY_MODE" = split ] && where="their halves" - say "${c_dim}Windows settle onto $where ~${SPLASH_WAIT}s in (after the splash)." - say "Re-place any time with: ./bl2-splitscreen.sh tile${c_rst}" - say ""; say "Press Ctrl-C to kill both instances." + say "${c_dim}Re-place windows any time with: ./bl2-splitscreen.sh tile${c_rst}" + say "Press Ctrl-C to kill both instances." # Killing $pid1/$pid2 only hits the top of each tree; the game runs deeper inside # umu/pressure-vessel and survives. kill_instances tears down the whole tree. trap 'echo; kill_instances; exit 130' INT TERM @@ -466,7 +489,9 @@ cmd_kill() { pgrep 'Borderlands2' >/dev/null || { ok "No Borderlands 2 instances running."; return 0; } say "Killing Borderlands 2 instances..." kill_instances - local left; left=$(pgrep -c 'Borderlands2' 2>/dev/null || echo 0) + # pgrep -c prints "0" and exits non-zero on no match, so DON'T add `|| echo 0` + # (that would double it). Command substitution keeps the "0" regardless of exit. + local left; left=$(pgrep -c 'Borderlands2' 2>/dev/null); left=${left:-0} [ "$left" = 0 ] && ok "All instances killed." || warn "$left process(es) still lingering." }