Added server status and server selection dropdown.
This commit is contained in:
@@ -7,19 +7,22 @@ from datetime import datetime
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path, PurePosixPath
|
||||||
import platform
|
import platform
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from typing import Mapping, Sequence
|
from typing import Mapping, Sequence
|
||||||
|
|
||||||
from .command import CommandOutcome, CommandRunner, OutputLineFilter
|
from .command import CommandOutcome, CommandRunner, OutputLineFilter
|
||||||
|
from .depot import DEFAULT_DEPOT_START_OPTION, DepotStartOption
|
||||||
from .gamebox import build_gamebox_command
|
from .gamebox import build_gamebox_command
|
||||||
from .paths import AppPaths
|
from .paths import AppPaths
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
|
|
||||||
|
|
||||||
WINDOWS_GAME_EXE = r"C:\Program Files (x86)\Pristontale EU\Game.exe"
|
WINDOWS_GAME_DIRECTORY = r"C:\Program Files (x86)\Pristontale EU"
|
||||||
|
WINDOWS_GAME_EXE = rf"{WINDOWS_GAME_DIRECTORY}\Game.exe"
|
||||||
NATIVE_D3D43_DLLS = ("d3dx9_43", "d3dcompiler_43")
|
NATIVE_D3D43_DLLS = ("d3dx9_43", "d3dcompiler_43")
|
||||||
DXVK_D3D9_OVERRIDE = "native,builtin"
|
DXVK_D3D9_OVERRIDE = "native,builtin"
|
||||||
DLL_OVERRIDES_KEY = r"HKCU\Software\Wine\DllOverrides"
|
DLL_OVERRIDES_KEY = r"HKCU\Software\Wine\DllOverrides"
|
||||||
@@ -293,6 +296,7 @@ def run_game(
|
|||||||
selection: RuntimeSelection,
|
selection: RuntimeSelection,
|
||||||
runner: CommandRunner,
|
runner: CommandRunner,
|
||||||
*,
|
*,
|
||||||
|
start_option: DepotStartOption = DEFAULT_DEPOT_START_OPTION,
|
||||||
use_gamebox: bool = True,
|
use_gamebox: bool = True,
|
||||||
timeout_seconds: float = 12 * 60 * 60,
|
timeout_seconds: float = 12 * 60 * 60,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
@@ -301,11 +305,15 @@ def run_game(
|
|||||||
) -> CommandOutcome:
|
) -> CommandOutcome:
|
||||||
if not paths.runtime_view.is_dir() and not runner.dry_run:
|
if not paths.runtime_view.is_dir() and not runner.dry_run:
|
||||||
raise RuntimeErrorPT("runtime view is missing; prepare the game first")
|
raise RuntimeErrorPT("runtime view is missing; prepare the game first")
|
||||||
|
native_executable, windows_executable, game_arguments = _game_start_command(
|
||||||
|
paths,
|
||||||
|
start_option,
|
||||||
|
)
|
||||||
if selection.kind == "umu":
|
if selection.kind == "umu":
|
||||||
game_command = [
|
game_command = [
|
||||||
selection.executable,
|
selection.executable,
|
||||||
str(paths.runtime_view / "Game.exe"),
|
native_executable,
|
||||||
"PTEU_REBORN",
|
*game_arguments,
|
||||||
]
|
]
|
||||||
environment = _umu_environment(paths)
|
environment = _umu_environment(paths)
|
||||||
elif selection.kind == "proton7-container":
|
elif selection.kind == "proton7-container":
|
||||||
@@ -313,8 +321,8 @@ def run_game(
|
|||||||
raise RuntimeErrorPT("Proton script path is missing")
|
raise RuntimeErrorPT("Proton script path is missing")
|
||||||
game_command = [
|
game_command = [
|
||||||
selection.executable,
|
selection.executable,
|
||||||
str(paths.runtime_view / "Game.exe"),
|
native_executable,
|
||||||
"PTEU_REBORN",
|
*game_arguments,
|
||||||
]
|
]
|
||||||
environment = _umu_proton7_environment(
|
environment = _umu_proton7_environment(
|
||||||
paths,
|
paths,
|
||||||
@@ -330,8 +338,8 @@ def run_game(
|
|||||||
"ptlinux.proton_compat",
|
"ptlinux.proton_compat",
|
||||||
selection.proton_path,
|
selection.proton_path,
|
||||||
"runinprefix",
|
"runinprefix",
|
||||||
WINDOWS_GAME_EXE,
|
windows_executable,
|
||||||
"PTEU_REBORN",
|
*game_arguments,
|
||||||
]
|
]
|
||||||
environment = _proton_environment(
|
environment = _proton_environment(
|
||||||
paths,
|
paths,
|
||||||
@@ -356,6 +364,57 @@ def run_game(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _game_start_command(
|
||||||
|
paths: AppPaths,
|
||||||
|
option: DepotStartOption,
|
||||||
|
) -> tuple[str, str, tuple[str, ...]]:
|
||||||
|
relative = PurePosixPath(option.path)
|
||||||
|
if (
|
||||||
|
relative.is_absolute()
|
||||||
|
or not relative.parts
|
||||||
|
or any(part in {"", ".", ".."} for part in relative.parts)
|
||||||
|
or relative.suffix.casefold() != ".exe"
|
||||||
|
):
|
||||||
|
raise RuntimeErrorPT(f"invalid realm executable path: {option.path!r}")
|
||||||
|
try:
|
||||||
|
arguments = tuple(shlex.split(option.arguments))
|
||||||
|
except ValueError as exc:
|
||||||
|
raise RuntimeErrorPT(
|
||||||
|
f"invalid launch arguments for realm {option.name}: {exc}"
|
||||||
|
) from exc
|
||||||
|
if not arguments:
|
||||||
|
raise RuntimeErrorPT(f"realm {option.name} has no launch arguments")
|
||||||
|
native = str(_resolve_runtime_path_case(paths.runtime_view, relative))
|
||||||
|
windows_relative = "\\".join(relative.parts)
|
||||||
|
return native, rf"{WINDOWS_GAME_DIRECTORY}\{windows_relative}", arguments
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_runtime_path_case(root: Path, relative: PurePosixPath) -> Path:
|
||||||
|
"""Match Windows manifest casing against the case-sensitive runtime view."""
|
||||||
|
|
||||||
|
direct = root.joinpath(*relative.parts)
|
||||||
|
if direct.exists():
|
||||||
|
return direct
|
||||||
|
cursor = root
|
||||||
|
for part in relative.parts:
|
||||||
|
if not cursor.is_dir():
|
||||||
|
return direct
|
||||||
|
try:
|
||||||
|
matches = tuple(
|
||||||
|
entry for entry in cursor.iterdir() if entry.name.casefold() == part.casefold()
|
||||||
|
)
|
||||||
|
except OSError:
|
||||||
|
return direct
|
||||||
|
if not matches:
|
||||||
|
return direct
|
||||||
|
if len(matches) > 1:
|
||||||
|
raise RuntimeErrorPT(
|
||||||
|
f"realm executable path has ambiguous casing: {relative.as_posix()}"
|
||||||
|
)
|
||||||
|
cursor = matches[0]
|
||||||
|
return cursor
|
||||||
|
|
||||||
|
|
||||||
def run_proton_prefix_command(
|
def run_proton_prefix_command(
|
||||||
paths: AppPaths,
|
paths: AppPaths,
|
||||||
selection: RuntimeSelection,
|
selection: RuntimeSelection,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ SETTINGS_SCHEMA = 1
|
|||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Settings:
|
class Settings:
|
||||||
|
realm: str = "Babel"
|
||||||
width: int = 1600
|
width: int = 1600
|
||||||
height: int = 900
|
height: int = 900
|
||||||
output_width: int = 1600
|
output_width: int = 1600
|
||||||
@@ -133,6 +134,7 @@ def _read_settings_object(path: Path) -> dict[str, object] | None:
|
|||||||
|
|
||||||
def _settings_from_object(value: dict[str, object]) -> Settings:
|
def _settings_from_object(value: dict[str, object]) -> Settings:
|
||||||
settings = Settings(
|
settings = Settings(
|
||||||
|
realm=_string(value, "realm", "Babel").strip(),
|
||||||
width=_integer(value, "width", 1600),
|
width=_integer(value, "width", 1600),
|
||||||
height=_integer(value, "height", 900),
|
height=_integer(value, "height", 900),
|
||||||
output_width=_integer(value, "output_width", 1600),
|
output_width=_integer(value, "output_width", 1600),
|
||||||
@@ -215,6 +217,12 @@ def _fsync_directory(path: Path) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _validate(settings: Settings) -> None:
|
def _validate(settings: Settings) -> None:
|
||||||
|
if (
|
||||||
|
len(settings.realm) > 64
|
||||||
|
or not settings.realm.strip()
|
||||||
|
or any(ord(character) < 32 for character in settings.realm)
|
||||||
|
):
|
||||||
|
raise ValueError("realm must be a non-empty display name")
|
||||||
for name, value in (
|
for name, value in (
|
||||||
("width", settings.width),
|
("width", settings.width),
|
||||||
("height", settings.height),
|
("height", settings.height),
|
||||||
|
|||||||
480
ptlinux/ui.py
480
ptlinux/ui.py
@@ -17,7 +17,16 @@ import webbrowser
|
|||||||
from typing import Callable, Mapping
|
from typing import Callable, Mapping
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .depot import DepotClient, DepotError, DepotSnapshot
|
from .depot import (
|
||||||
|
DEFAULT_DEPOT_START_OPTION,
|
||||||
|
DepotClient,
|
||||||
|
DepotError,
|
||||||
|
DepotSnapshot,
|
||||||
|
DepotStartOption,
|
||||||
|
cache_depot_launch_catalog,
|
||||||
|
launch_catalog_from_snapshot,
|
||||||
|
select_depot_start_option,
|
||||||
|
)
|
||||||
from .desktop_identity import (
|
from .desktop_identity import (
|
||||||
APPLICATION_NAME,
|
APPLICATION_NAME,
|
||||||
APPLICATION_WM_CLASS,
|
APPLICATION_WM_CLASS,
|
||||||
@@ -48,6 +57,11 @@ from .self_update import (
|
|||||||
check_for_launcher_update,
|
check_for_launcher_update,
|
||||||
read_cached_launcher_update_check,
|
read_cached_launcher_update_check,
|
||||||
)
|
)
|
||||||
|
from .server_status import (
|
||||||
|
RealmServerStatus,
|
||||||
|
ServerStatusError,
|
||||||
|
fetch_server_status,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
MAX_NEWS_CARDS = 3
|
MAX_NEWS_CARDS = 3
|
||||||
@@ -70,6 +84,7 @@ DASHBOARD_PRELOADER_TIMEOUT_MILLISECONDS = 10_000
|
|||||||
DASHBOARD_TRANSITION_STEP_MILLISECONDS = 16
|
DASHBOARD_TRANSITION_STEP_MILLISECONDS = 16
|
||||||
DASHBOARD_TRANSITION_STEPS = 9
|
DASHBOARD_TRANSITION_STEPS = 9
|
||||||
DASHBOARD_TRANSITION_MINIMUM_OPACITY = 0.18
|
DASHBOARD_TRANSITION_MINIMUM_OPACITY = 0.18
|
||||||
|
PRELOADER_STATUS_CHARACTERS = 64
|
||||||
DASHBOARD_STARTUP_COMPONENTS = frozenset(
|
DASHBOARD_STARTUP_COMPONENTS = frozenset(
|
||||||
{
|
{
|
||||||
"broadcast",
|
"broadcast",
|
||||||
@@ -78,6 +93,7 @@ DASHBOARD_STARTUP_COMPONENTS = frozenset(
|
|||||||
"logo",
|
"logo",
|
||||||
"news",
|
"news",
|
||||||
"news_artwork",
|
"news_artwork",
|
||||||
|
"server_status",
|
||||||
"version",
|
"version",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -92,6 +108,22 @@ class LauncherUpdatePresentation:
|
|||||||
badge_text: str | None
|
badge_text: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class GameSyncPresentation:
|
||||||
|
"""User-facing wording for one managed-game synchronization."""
|
||||||
|
|
||||||
|
operation_label: str
|
||||||
|
file_progress_verb: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ServerStatusPresentation:
|
||||||
|
"""Text and Tk style for one official realm-health result."""
|
||||||
|
|
||||||
|
text: str
|
||||||
|
style: str
|
||||||
|
|
||||||
|
|
||||||
class DashboardError(RuntimeError):
|
class DashboardError(RuntimeError):
|
||||||
"""Raised when the native dashboard cannot be displayed."""
|
"""Raised when the native dashboard cannot be displayed."""
|
||||||
|
|
||||||
@@ -148,6 +180,61 @@ def game_client_activity_text(running_count: int) -> str:
|
|||||||
return f"{running_count} {label} running"
|
return f"{running_count} {label} running"
|
||||||
|
|
||||||
|
|
||||||
|
def game_sync_presentation(
|
||||||
|
*,
|
||||||
|
installed: bool,
|
||||||
|
repair: bool = False,
|
||||||
|
) -> GameSyncPresentation:
|
||||||
|
"""Distinguish a fresh download from an update or repair."""
|
||||||
|
|
||||||
|
if not installed:
|
||||||
|
return GameSyncPresentation(
|
||||||
|
operation_label=(
|
||||||
|
"Installing and verifying official game files…"
|
||||||
|
if repair
|
||||||
|
else "Installing official game files…"
|
||||||
|
),
|
||||||
|
file_progress_verb="Downloading",
|
||||||
|
)
|
||||||
|
if repair:
|
||||||
|
return GameSyncPresentation(
|
||||||
|
operation_label="Checking every game file against the official depot…",
|
||||||
|
file_progress_verb="Repairing",
|
||||||
|
)
|
||||||
|
return GameSyncPresentation(
|
||||||
|
operation_label="Synchronizing official game files…",
|
||||||
|
file_progress_verb="Updating",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def game_file_progress_status(current_file: str, verb: str) -> str:
|
||||||
|
"""Render the current depot asset with the operation's exact intent."""
|
||||||
|
|
||||||
|
return f"{verb} {current_file}"
|
||||||
|
|
||||||
|
|
||||||
|
def server_status_presentation(status: RealmServerStatus) -> ServerStatusPresentation:
|
||||||
|
if status.online is True:
|
||||||
|
players = (
|
||||||
|
f" · {status.players_online} online"
|
||||||
|
if status.players_online is not None
|
||||||
|
else " · online"
|
||||||
|
)
|
||||||
|
return ServerStatusPresentation(
|
||||||
|
text=f"● {status.name}{players}",
|
||||||
|
style="ServerOnline.TLabel",
|
||||||
|
)
|
||||||
|
if status.online is False:
|
||||||
|
return ServerStatusPresentation(
|
||||||
|
text=f"● {status.name} · offline",
|
||||||
|
style="ServerOffline.TLabel",
|
||||||
|
)
|
||||||
|
return ServerStatusPresentation(
|
||||||
|
text=f"○ {status.name} · status unknown",
|
||||||
|
style="ServerUnknown.TLabel",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def launcher_update_presentation(
|
def launcher_update_presentation(
|
||||||
check: LauncherUpdateCheck,
|
check: LauncherUpdateCheck,
|
||||||
) -> LauncherUpdatePresentation:
|
) -> LauncherUpdatePresentation:
|
||||||
@@ -200,6 +287,29 @@ def dashboard_transition_opacity(step: int, *, fading_in: bool) -> float:
|
|||||||
return 1.0 - opacity_range * progress
|
return 1.0 - opacity_range * progress
|
||||||
|
|
||||||
|
|
||||||
|
def preloader_status_text(
|
||||||
|
value: str,
|
||||||
|
step: int | None = None,
|
||||||
|
total: int | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Render one launch stage without letting its widget change shape."""
|
||||||
|
|
||||||
|
if step is None or total is None:
|
||||||
|
return value
|
||||||
|
return f"Step {step} of {total} · {value}"
|
||||||
|
|
||||||
|
|
||||||
|
def freeze_preloader_geometry(frame: tk.Misc) -> tuple[int, int]:
|
||||||
|
"""Lock a measured preloader frame so later status text cannot reflow it."""
|
||||||
|
|
||||||
|
frame.update_idletasks()
|
||||||
|
width = max(1, frame.winfo_reqwidth())
|
||||||
|
height = max(1, frame.winfo_reqheight())
|
||||||
|
frame.configure(width=width, height=height)
|
||||||
|
frame.pack_propagate(False)
|
||||||
|
return width, height
|
||||||
|
|
||||||
|
|
||||||
def launcher_restart_spec(
|
def launcher_restart_spec(
|
||||||
paths: AppPaths,
|
paths: AppPaths,
|
||||||
environment: Mapping[str, str] | None = None,
|
environment: Mapping[str, str] | None = None,
|
||||||
@@ -228,6 +338,12 @@ def launcher_restart_spec(
|
|||||||
return [sys.executable, "-B", "-P", "-m", "ptlinux", "gui"], env
|
return [sys.executable, "-B", "-P", "-m", "ptlinux", "gui"], env
|
||||||
|
|
||||||
|
|
||||||
|
def launcher_child_command(*arguments: str) -> list[str]:
|
||||||
|
"""Build a child command without writing bytecode into the active release."""
|
||||||
|
|
||||||
|
return [sys.executable, "-B", "-m", "ptlinux", *arguments]
|
||||||
|
|
||||||
|
|
||||||
class ProgressCoalescer:
|
class ProgressCoalescer:
|
||||||
"""Keep only the latest cross-thread progress event for the Tk loop.
|
"""Keep only the latest cross-thread progress event for the Tk loop.
|
||||||
|
|
||||||
@@ -347,7 +463,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
root.configure(background="#0c0908")
|
root.configure(background="#0c0908")
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
try:
|
try:
|
||||||
ensure_settings(paths)
|
startup_settings = ensure_settings(paths)
|
||||||
except (OSError, ValueError) as startup_error:
|
except (OSError, ValueError) as startup_error:
|
||||||
root.deiconify()
|
root.deiconify()
|
||||||
messagebox.showerror(
|
messagebox.showerror(
|
||||||
@@ -425,6 +541,37 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
foreground="#aa9a8b",
|
foreground="#aa9a8b",
|
||||||
font=("Sans", 8),
|
font=("Sans", 8),
|
||||||
)
|
)
|
||||||
|
style.configure(
|
||||||
|
"ServerHeading.TLabel",
|
||||||
|
background="#191311",
|
||||||
|
foreground="#d8aa61",
|
||||||
|
font=("Sans", 8, "bold"),
|
||||||
|
padding=(0, 3),
|
||||||
|
)
|
||||||
|
style.configure(
|
||||||
|
"ServerOnline.TLabel",
|
||||||
|
background="#102719",
|
||||||
|
foreground="#58e27a",
|
||||||
|
font=("Sans", 8, "bold"),
|
||||||
|
padding=(8, 3),
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
|
style.configure(
|
||||||
|
"ServerOffline.TLabel",
|
||||||
|
background="#301514",
|
||||||
|
foreground="#ff6258",
|
||||||
|
font=("Sans", 8, "bold"),
|
||||||
|
padding=(8, 3),
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
|
style.configure(
|
||||||
|
"ServerUnknown.TLabel",
|
||||||
|
background="#211916",
|
||||||
|
foreground="#b8a99a",
|
||||||
|
font=("Sans", 8),
|
||||||
|
padding=(8, 3),
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
style.configure(
|
style.configure(
|
||||||
"Speed.TLabel",
|
"Speed.TLabel",
|
||||||
background="#191311",
|
background="#191311",
|
||||||
@@ -494,6 +641,56 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
fieldbackground=[("readonly", "#100d0c")],
|
fieldbackground=[("readonly", "#100d0c")],
|
||||||
foreground=[("readonly", "#f8ead2")],
|
foreground=[("readonly", "#f8ead2")],
|
||||||
)
|
)
|
||||||
|
style.configure(
|
||||||
|
"Realm.TCombobox",
|
||||||
|
fieldbackground="#211916",
|
||||||
|
background="#211916",
|
||||||
|
foreground="#fff0d2",
|
||||||
|
arrowcolor="#d8aa61",
|
||||||
|
bordercolor="#4a3528",
|
||||||
|
lightcolor="#4a3528",
|
||||||
|
darkcolor="#4a3528",
|
||||||
|
font=("Sans", 10, "bold"),
|
||||||
|
padding=(10, 7),
|
||||||
|
relief="flat",
|
||||||
|
)
|
||||||
|
style.map(
|
||||||
|
"Realm.TCombobox",
|
||||||
|
fieldbackground=[
|
||||||
|
("disabled", "#171412"),
|
||||||
|
("readonly", "#211916"),
|
||||||
|
],
|
||||||
|
background=[
|
||||||
|
("disabled", "#171412"),
|
||||||
|
("active", "#2a201c"),
|
||||||
|
("readonly", "#211916"),
|
||||||
|
],
|
||||||
|
foreground=[
|
||||||
|
("disabled", "#887a70"),
|
||||||
|
("readonly", "#fff0d2"),
|
||||||
|
],
|
||||||
|
arrowcolor=[
|
||||||
|
("disabled", "#887a70"),
|
||||||
|
("active", "#f0d7a2"),
|
||||||
|
("readonly", "#d8aa61"),
|
||||||
|
],
|
||||||
|
bordercolor=[
|
||||||
|
("disabled", "#4a3528"),
|
||||||
|
("focus", "#6a4d38"),
|
||||||
|
("readonly", "#4a3528"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
root.option_add("*realm_selector*Listbox.background", "#211916")
|
||||||
|
root.option_add("*realm_selector*Listbox.foreground", "#fff0d2")
|
||||||
|
root.option_add("*realm_selector*Listbox.selectBackground", "#2a201c")
|
||||||
|
root.option_add("*realm_selector*Listbox.selectForeground", "#f0d7a2")
|
||||||
|
root.option_add("*realm_selector*Listbox.font", "Sans 10 bold")
|
||||||
|
root.option_add("*realm_selector*Listbox.justify", "center")
|
||||||
|
root.option_add("*realm_selector*Listbox.selectBorderWidth", 4)
|
||||||
|
root.option_add("*realm_selector*Listbox.borderWidth", 1)
|
||||||
|
root.option_add("*realm_selector*Listbox.highlightThickness", 0)
|
||||||
|
root.option_add("*realm_selector*Listbox.relief", "flat")
|
||||||
|
root.option_add("*realm_selector*Listbox.activeStyle", "none")
|
||||||
style.configure(
|
style.configure(
|
||||||
"Settings.TCheckbutton",
|
"Settings.TCheckbutton",
|
||||||
background="#191311",
|
background="#191311",
|
||||||
@@ -665,11 +862,14 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
text="NATIVE LINUX LAUNCHER",
|
text="NATIVE LINUX LAUNCHER",
|
||||||
style="SplashTagline.TLabel",
|
style="SplashTagline.TLabel",
|
||||||
).pack(anchor="w", pady=(2, 17))
|
).pack(anchor="w", pady=(2, 17))
|
||||||
ttk.Label(
|
startup_status_label = ttk.Label(
|
||||||
startup_shell,
|
startup_shell,
|
||||||
textvariable=startup_status,
|
textvariable=startup_status,
|
||||||
style="SplashStatus.TLabel",
|
style="SplashStatus.TLabel",
|
||||||
).pack(anchor="w", pady=(0, 8))
|
width=PRELOADER_STATUS_CHARACTERS,
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
|
startup_status_label.pack(fill="x", pady=(0, 8))
|
||||||
startup_progress = ttk.Progressbar(
|
startup_progress = ttk.Progressbar(
|
||||||
startup_shell,
|
startup_shell,
|
||||||
style="Depot.Horizontal.TProgressbar",
|
style="Depot.Horizontal.TProgressbar",
|
||||||
@@ -677,6 +877,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
length=420,
|
length=420,
|
||||||
)
|
)
|
||||||
startup_progress.pack(fill="x")
|
startup_progress.pack(fill="x")
|
||||||
|
freeze_preloader_geometry(startup_border)
|
||||||
startup_progress.start(12)
|
startup_progress.start(12)
|
||||||
startup_ready_components: set[str] = set()
|
startup_ready_components: set[str] = set()
|
||||||
startup_transition_started = False
|
startup_transition_started = False
|
||||||
@@ -921,8 +1122,25 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
|
|
||||||
activity_panel = ttk.Frame(frame, style="Surface.TFrame", padding=(10, 7))
|
activity_panel = ttk.Frame(frame, style="Surface.TFrame", padding=(10, 7))
|
||||||
activity_panel.pack(fill="x", pady=(0, 8))
|
activity_panel.pack(fill="x", pady=(0, 8))
|
||||||
|
realm_value = tk.StringVar(value=DEFAULT_DEPOT_START_OPTION.name)
|
||||||
|
activity_action = ttk.Frame(activity_panel, style="Surface.TFrame")
|
||||||
|
activity_action.pack(side="right", fill="y", padx=(12, 0))
|
||||||
|
play_button = ttk.Button(activity_action, text="PLAY", style="Play.TButton")
|
||||||
|
play_button.pack(fill="both", expand=True)
|
||||||
|
realm_selector = ttk.Combobox(
|
||||||
|
activity_action,
|
||||||
|
name="realm_selector",
|
||||||
|
textvariable=realm_value,
|
||||||
|
values=(DEFAULT_DEPOT_START_OPTION.name,),
|
||||||
|
state="readonly",
|
||||||
|
style="Realm.TCombobox",
|
||||||
|
font=("Sans", 10, "bold"),
|
||||||
|
justify="center",
|
||||||
|
width=20,
|
||||||
|
)
|
||||||
|
realm_selector.pack(fill="x", pady=(5, 0))
|
||||||
activity_copy = ttk.Frame(activity_panel, style="Surface.TFrame")
|
activity_copy = ttk.Frame(activity_panel, style="Surface.TFrame")
|
||||||
activity_copy.pack(side="left", fill="x", expand=True, padx=(0, 12))
|
activity_copy.pack(side="left", fill="both", expand=True)
|
||||||
status = tk.StringVar(value="Ready")
|
status = tk.StringVar(value="Ready")
|
||||||
progress_text = tk.StringVar(value="Click Play to start your journey")
|
progress_text = tk.StringVar(value="Click Play to start your journey")
|
||||||
speed_text = tk.StringVar(value="")
|
speed_text = tk.StringVar(value="")
|
||||||
@@ -962,8 +1180,20 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
style="Muted.TLabel",
|
style="Muted.TLabel",
|
||||||
anchor="w",
|
anchor="w",
|
||||||
).pack(side="left", fill="x", expand=True)
|
).pack(side="left", fill="x", expand=True)
|
||||||
play_button = ttk.Button(activity_panel, text="PLAY", style="Play.TButton")
|
server_status_row = ttk.Frame(activity_copy, style="Surface.TFrame")
|
||||||
play_button.pack(side="right", fill="y")
|
server_status_row.pack(fill="x", pady=(5, 1))
|
||||||
|
ttk.Label(
|
||||||
|
server_status_row,
|
||||||
|
text="SERVERS",
|
||||||
|
style="ServerHeading.TLabel",
|
||||||
|
).pack(side="left", fill="y", padx=(0, 8))
|
||||||
|
server_status_content = ttk.Frame(server_status_row, style="Surface.TFrame")
|
||||||
|
server_status_content.pack(side="left", fill="y", anchor="w")
|
||||||
|
ttk.Label(
|
||||||
|
server_status_content,
|
||||||
|
text="Checking official server status…",
|
||||||
|
style="ServerUnknown.TLabel",
|
||||||
|
).pack(side="left", anchor="w")
|
||||||
|
|
||||||
news_panel = ttk.Frame(frame, style="Surface.TFrame", padding=(10, 8))
|
news_panel = ttk.Frame(frame, style="Surface.TFrame", padding=(10, 8))
|
||||||
news_panel.pack(fill="x", pady=(0, 8), before=controls)
|
news_panel.pack(fill="x", pady=(0, 8), before=controls)
|
||||||
@@ -1054,8 +1284,13 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
operation_stage_total = 0
|
operation_stage_total = 0
|
||||||
operation_message = ""
|
operation_message = ""
|
||||||
operation_refreshes_compatibility = False
|
operation_refreshes_compatibility = False
|
||||||
|
operation_file_progress_verb = "Updating"
|
||||||
last_elapsed_second = -1
|
last_elapsed_second = -1
|
||||||
official_snapshot: DepotSnapshot | None = None
|
official_snapshot: DepotSnapshot | None = None
|
||||||
|
available_start_options: tuple[DepotStartOption, ...] = (DEFAULT_DEPOT_START_OPTION,)
|
||||||
|
preferred_realm = startup_settings.realm
|
||||||
|
latest_server_statuses: tuple[RealmServerStatus, ...] | None = None
|
||||||
|
server_status_placeholder = "Checking official server status…"
|
||||||
launcher_update_check: LauncherUpdateCheck | None = None
|
launcher_update_check: LauncherUpdateCheck | None = None
|
||||||
runtime_compatibility: RuntimeCompatibility | None = None
|
runtime_compatibility: RuntimeCompatibility | None = None
|
||||||
compatibility_check_generation = 0
|
compatibility_check_generation = 0
|
||||||
@@ -1106,6 +1341,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
new_state = "disabled" if value else "normal"
|
new_state = "disabled" if value else "normal"
|
||||||
for button in buttons:
|
for button in buttons:
|
||||||
button.configure(state=new_state)
|
button.configure(state=new_state)
|
||||||
|
realm_selector.configure(state="disabled" if value else "readonly")
|
||||||
if not value and game_clients.running_count:
|
if not value and game_clients.running_count:
|
||||||
for button in game_mutation_buttons:
|
for button in game_mutation_buttons:
|
||||||
button.configure(state="disabled")
|
button.configure(state="disabled")
|
||||||
@@ -1143,7 +1379,9 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
transfer_rate.reset()
|
transfer_rate.reset()
|
||||||
speed_text.set("")
|
speed_text.set("")
|
||||||
if isinstance(current_file, str) and current_file:
|
if isinstance(current_file, str) and current_file:
|
||||||
status.set(f"Updating {current_file}")
|
status.set(
|
||||||
|
game_file_progress_status(current_file, operation_file_progress_verb)
|
||||||
|
)
|
||||||
elif phase == "complete":
|
elif phase == "complete":
|
||||||
status.set("Game files are up to date and verified.")
|
status.set("Game files are up to date and verified.")
|
||||||
|
|
||||||
@@ -1214,8 +1452,15 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
text="Please wait while the game prepares its window.",
|
text="Please wait while the game prepares its window.",
|
||||||
style="PreloaderStatus.TLabel",
|
style="PreloaderStatus.TLabel",
|
||||||
).pack(anchor="w")
|
).pack(anchor="w")
|
||||||
ttk.Label(content, textvariable=status_variable, style="PreloaderMuted.TLabel").pack(
|
status_label = ttk.Label(
|
||||||
|
content,
|
||||||
|
textvariable=status_variable,
|
||||||
|
style="PreloaderMuted.TLabel",
|
||||||
|
width=PRELOADER_STATUS_CHARACTERS,
|
||||||
anchor="w",
|
anchor="w",
|
||||||
|
)
|
||||||
|
status_label.pack(
|
||||||
|
fill="x",
|
||||||
pady=(4, 10),
|
pady=(4, 10),
|
||||||
)
|
)
|
||||||
progress_widget = ttk.Progressbar(
|
progress_widget = ttk.Progressbar(
|
||||||
@@ -1227,6 +1472,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
)
|
)
|
||||||
preloader_progress = progress_widget
|
preloader_progress = progress_widget
|
||||||
progress_widget.pack(fill="x")
|
progress_widget.pack(fill="x")
|
||||||
|
freeze_preloader_geometry(content)
|
||||||
window.update_idletasks()
|
window.update_idletasks()
|
||||||
width = window.winfo_reqwidth()
|
width = window.winfo_reqwidth()
|
||||||
height = window.winfo_reqheight()
|
height = window.winfo_reqheight()
|
||||||
@@ -1237,10 +1483,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
|
|
||||||
def set_preloader_status(value: str, step: int | None = None, total: int | None = None) -> None:
|
def set_preloader_status(value: str, step: int | None = None, total: int | None = None) -> None:
|
||||||
if preloader_status is not None:
|
if preloader_status is not None:
|
||||||
if step is not None and total is not None:
|
preloader_status.set(preloader_status_text(value, step, total))
|
||||||
preloader_status.set(f"Step {step} of {total} · {value}")
|
|
||||||
else:
|
|
||||||
preloader_status.set(value)
|
|
||||||
if preloader_progress is not None and step is not None and total is not None:
|
if preloader_progress is not None and step is not None and total is not None:
|
||||||
preloader_progress.configure(maximum=total, value=step)
|
preloader_progress.configure(maximum=total, value=step)
|
||||||
|
|
||||||
@@ -1259,6 +1502,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
show_preloader: bool = False,
|
show_preloader: bool = False,
|
||||||
restart_on_success: bool = False,
|
restart_on_success: bool = False,
|
||||||
game_files_may_change: bool = False,
|
game_files_may_change: bool = False,
|
||||||
|
file_progress_verb: str = "Updating",
|
||||||
game_client_id: int | None = None,
|
game_client_id: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
nonlocal operation_active, game_window_active
|
nonlocal operation_active, game_window_active
|
||||||
@@ -1267,6 +1511,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
nonlocal operation_stage_total
|
nonlocal operation_stage_total
|
||||||
nonlocal operation_message
|
nonlocal operation_message
|
||||||
nonlocal operation_refreshes_compatibility
|
nonlocal operation_refreshes_compatibility
|
||||||
|
nonlocal operation_file_progress_verb
|
||||||
nonlocal last_elapsed_second
|
nonlocal last_elapsed_second
|
||||||
operation_active = True
|
operation_active = True
|
||||||
game_window_active = game_clients.running_count > 0
|
game_window_active = game_clients.running_count > 0
|
||||||
@@ -1275,6 +1520,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
operation_stage_total = 0
|
operation_stage_total = 0
|
||||||
operation_message = ""
|
operation_message = ""
|
||||||
operation_refreshes_compatibility = game_files_may_change
|
operation_refreshes_compatibility = game_files_may_change
|
||||||
|
operation_file_progress_verb = file_progress_verb
|
||||||
last_elapsed_second = -1
|
last_elapsed_second = -1
|
||||||
progress_updates.clear()
|
progress_updates.clear()
|
||||||
transfer_rate.reset()
|
transfer_rate.reset()
|
||||||
@@ -1658,6 +1904,102 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
|
|
||||||
threading.Thread(target=worker, name="ptlinux-logo", daemon=True).start()
|
threading.Thread(target=worker, name="ptlinux-logo", daemon=True).start()
|
||||||
|
|
||||||
|
def render_server_status() -> None:
|
||||||
|
for child in server_status_content.winfo_children():
|
||||||
|
child.destroy()
|
||||||
|
if latest_server_statuses is None:
|
||||||
|
ttk.Label(
|
||||||
|
server_status_content,
|
||||||
|
text=server_status_placeholder,
|
||||||
|
style="ServerUnknown.TLabel",
|
||||||
|
).pack(side="left", anchor="w")
|
||||||
|
return
|
||||||
|
statuses_by_name = {
|
||||||
|
item.name.casefold(): item for item in latest_server_statuses
|
||||||
|
}
|
||||||
|
visible = [
|
||||||
|
statuses_by_name[option.name.casefold()]
|
||||||
|
for option in available_start_options
|
||||||
|
if option.name.casefold() in statuses_by_name
|
||||||
|
]
|
||||||
|
if not visible:
|
||||||
|
ttk.Label(
|
||||||
|
server_status_content,
|
||||||
|
text="Official status is unavailable for this realm.",
|
||||||
|
style="ServerUnknown.TLabel",
|
||||||
|
).pack(side="left", anchor="w")
|
||||||
|
return
|
||||||
|
for index, server in enumerate(visible):
|
||||||
|
presentation = server_status_presentation(server)
|
||||||
|
ttk.Label(
|
||||||
|
server_status_content,
|
||||||
|
text=presentation.text,
|
||||||
|
style=presentation.style,
|
||||||
|
).pack(
|
||||||
|
side="left",
|
||||||
|
anchor="w",
|
||||||
|
padx=(0 if index == 0 else 6, 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_server_status(
|
||||||
|
statuses: tuple[RealmServerStatus, ...] | None,
|
||||||
|
*,
|
||||||
|
placeholder: str = "Server status unavailable.",
|
||||||
|
) -> None:
|
||||||
|
nonlocal latest_server_statuses, server_status_placeholder
|
||||||
|
latest_server_statuses = statuses
|
||||||
|
server_status_placeholder = placeholder
|
||||||
|
render_server_status()
|
||||||
|
|
||||||
|
def install_realm_options(snapshot: DepotSnapshot) -> None:
|
||||||
|
nonlocal available_start_options, preferred_realm
|
||||||
|
catalog = launch_catalog_from_snapshot(snapshot)
|
||||||
|
available_start_options = catalog.start_options
|
||||||
|
selected = select_depot_start_option(catalog, preferred_realm)
|
||||||
|
realm_selector.configure(
|
||||||
|
values=tuple(option.name for option in available_start_options),
|
||||||
|
state="disabled" if operation_active else "readonly",
|
||||||
|
)
|
||||||
|
realm_value.set(selected.name)
|
||||||
|
if selected.name != preferred_realm:
|
||||||
|
preferred_realm = selected.name
|
||||||
|
try:
|
||||||
|
update_settings(paths, realm=selected.name)
|
||||||
|
except (OSError, ValueError) as save_error:
|
||||||
|
append_log(f"Could not save the selected realm: {save_error}")
|
||||||
|
render_server_status()
|
||||||
|
|
||||||
|
def on_realm_selected(_event: object | None = None) -> None:
|
||||||
|
nonlocal preferred_realm
|
||||||
|
selected_name = realm_value.get()
|
||||||
|
selected = next(
|
||||||
|
(
|
||||||
|
option
|
||||||
|
for option in available_start_options
|
||||||
|
if option.name.casefold() == selected_name.casefold()
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if selected is None:
|
||||||
|
fallback = select_depot_start_option(
|
||||||
|
launch_catalog_from_snapshot(official_snapshot)
|
||||||
|
if official_snapshot is not None
|
||||||
|
else None,
|
||||||
|
preferred_realm,
|
||||||
|
)
|
||||||
|
realm_value.set(fallback.name)
|
||||||
|
return
|
||||||
|
realm_value.set(selected.name)
|
||||||
|
if preferred_realm == selected.name:
|
||||||
|
return
|
||||||
|
preferred_realm = selected.name
|
||||||
|
try:
|
||||||
|
update_settings(paths, realm=selected.name)
|
||||||
|
except (OSError, ValueError) as save_error:
|
||||||
|
append_log(f"Could not save the selected realm: {save_error}")
|
||||||
|
|
||||||
|
realm_selector.bind("<<ComboboxSelected>>", on_realm_selected)
|
||||||
|
|
||||||
def refresh_primary_action(
|
def refresh_primary_action(
|
||||||
snapshot: DepotSnapshot | None = None,
|
snapshot: DepotSnapshot | None = None,
|
||||||
*,
|
*,
|
||||||
@@ -1802,16 +2144,38 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
def news_worker() -> None:
|
def news_worker() -> None:
|
||||||
events.put(("news", fetch_news(paths.news_cache)))
|
events.put(("news", fetch_news(paths.news_cache)))
|
||||||
|
|
||||||
|
def server_status_worker() -> None:
|
||||||
|
try:
|
||||||
|
statuses = fetch_server_status()
|
||||||
|
except ServerStatusError as status_error:
|
||||||
|
events.put(("server_status_error", str(status_error)))
|
||||||
|
else:
|
||||||
|
events.put(("server_status", statuses))
|
||||||
|
|
||||||
def version_worker() -> None:
|
def version_worker() -> None:
|
||||||
try:
|
try:
|
||||||
snapshot = DepotClient().fetch_snapshot()
|
snapshot = DepotClient().fetch_snapshot()
|
||||||
except DepotError as remote_error:
|
except DepotError as remote_error:
|
||||||
events.put(("version_error", str(remote_error)))
|
events.put(("version_error", str(remote_error)))
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
|
cache_depot_launch_catalog(paths.depot_launch_catalog_cache, snapshot)
|
||||||
|
except OSError as cache_error:
|
||||||
|
events.put(
|
||||||
|
(
|
||||||
|
"output",
|
||||||
|
f"Could not cache official realm choices: {cache_error}\n",
|
||||||
|
)
|
||||||
|
)
|
||||||
events.put(("version", snapshot))
|
events.put(("version", snapshot))
|
||||||
|
|
||||||
threading.Thread(target=broadcast_worker, name="ptlinux-broadcast", daemon=True).start()
|
threading.Thread(target=broadcast_worker, name="ptlinux-broadcast", daemon=True).start()
|
||||||
threading.Thread(target=news_worker, name="ptlinux-news", daemon=True).start()
|
threading.Thread(target=news_worker, name="ptlinux-news", daemon=True).start()
|
||||||
|
threading.Thread(
|
||||||
|
target=server_status_worker,
|
||||||
|
name="ptlinux-server-status",
|
||||||
|
daemon=True,
|
||||||
|
).start()
|
||||||
threading.Thread(target=version_worker, name="ptlinux-version", daemon=True).start()
|
threading.Thread(target=version_worker, name="ptlinux-version", daemon=True).start()
|
||||||
refresh_launcher_update_check()
|
refresh_launcher_update_check()
|
||||||
|
|
||||||
@@ -1968,6 +2332,23 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
"broadcast",
|
"broadcast",
|
||||||
"Broadcast ready",
|
"Broadcast ready",
|
||||||
)
|
)
|
||||||
|
elif (
|
||||||
|
kind == "server_status"
|
||||||
|
and isinstance(value, tuple)
|
||||||
|
and all(isinstance(item, RealmServerStatus) for item in value)
|
||||||
|
):
|
||||||
|
show_server_status(value)
|
||||||
|
mark_startup_component_ready(
|
||||||
|
"server_status",
|
||||||
|
"Server status ready",
|
||||||
|
)
|
||||||
|
elif kind == "server_status_error" and isinstance(value, str):
|
||||||
|
show_server_status(None)
|
||||||
|
append_log(f"Server status check unavailable: {value}")
|
||||||
|
mark_startup_component_ready(
|
||||||
|
"server_status",
|
||||||
|
"Server status check complete",
|
||||||
|
)
|
||||||
elif kind == "news" and isinstance(value, tuple):
|
elif kind == "news" and isinstance(value, tuple):
|
||||||
show_news(value)
|
show_news(value)
|
||||||
mark_startup_component_ready(
|
mark_startup_component_ready(
|
||||||
@@ -2013,6 +2394,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
version.set(
|
version.set(
|
||||||
f"Launcher {__version__} · {value.version} · depot {value.build}"
|
f"Launcher {__version__} · {value.version} · depot {value.build}"
|
||||||
)
|
)
|
||||||
|
install_realm_options(value)
|
||||||
refresh_primary_action(value)
|
refresh_primary_action(value)
|
||||||
mark_startup_component_ready(
|
mark_startup_component_ready(
|
||||||
"version",
|
"version",
|
||||||
@@ -2074,7 +2456,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
root.after(poll_delay, poll_events)
|
root.after(poll_delay, poll_events)
|
||||||
|
|
||||||
def command_for(*arguments: str) -> list[str]:
|
def command_for(*arguments: str) -> list[str]:
|
||||||
return [sys.executable, "-m", "ptlinux", *arguments]
|
return launcher_child_command(*arguments)
|
||||||
|
|
||||||
def run_launcher_update(required_reason: str | None = None) -> None:
|
def run_launcher_update(required_reason: str | None = None) -> None:
|
||||||
check = launcher_update_check
|
check = launcher_update_check
|
||||||
@@ -2128,15 +2510,51 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
|
|
||||||
def start_game_client() -> None:
|
def start_game_client() -> None:
|
||||||
nonlocal active_game_launch_id
|
nonlocal active_game_launch_id
|
||||||
|
if official_snapshot is not None:
|
||||||
|
try:
|
||||||
|
cache_depot_launch_catalog(
|
||||||
|
paths.depot_launch_catalog_cache,
|
||||||
|
official_snapshot,
|
||||||
|
)
|
||||||
|
except OSError as cache_error:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Realm selection",
|
||||||
|
"The launcher could not save the official realm selection:\n\n"
|
||||||
|
f"{cache_error}",
|
||||||
|
parent=root,
|
||||||
|
)
|
||||||
|
return
|
||||||
client_id = game_clients.begin()
|
client_id = game_clients.begin()
|
||||||
active_game_launch_id = client_id
|
active_game_launch_id = client_id
|
||||||
run_operation(
|
run_operation(
|
||||||
f"Starting PristonTale EU client {client_id}…",
|
f"Starting PristonTale EU {realm_value.get()} client {client_id}…",
|
||||||
command_for("play"),
|
command_for("play", "--realm", realm_value.get()),
|
||||||
show_preloader=True,
|
show_preloader=True,
|
||||||
game_client_id=client_id,
|
game_client_id=client_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def synchronize_game_files(
|
||||||
|
installation: GameInstallationStatus | None = None,
|
||||||
|
*,
|
||||||
|
repair: bool = False,
|
||||||
|
) -> None:
|
||||||
|
current = installation or refresh_primary_action(update_activity=False)
|
||||||
|
presentation = game_sync_presentation(
|
||||||
|
installed=current.installed,
|
||||||
|
repair=repair,
|
||||||
|
)
|
||||||
|
command = (
|
||||||
|
command_for("update", "--no-import", "--repair")
|
||||||
|
if repair
|
||||||
|
else command_for("update")
|
||||||
|
)
|
||||||
|
run_operation(
|
||||||
|
presentation.operation_label,
|
||||||
|
command,
|
||||||
|
game_files_may_change=True,
|
||||||
|
file_progress_verb=presentation.file_progress_verb,
|
||||||
|
)
|
||||||
|
|
||||||
def run_primary_action() -> None:
|
def run_primary_action() -> None:
|
||||||
installation = refresh_primary_action(update_activity=False)
|
installation = refresh_primary_action(update_activity=False)
|
||||||
if (
|
if (
|
||||||
@@ -2144,18 +2562,8 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
or (game_clients.running_count > 0 and installation.installed)
|
or (game_clients.running_count > 0 and installation.installed)
|
||||||
):
|
):
|
||||||
start_game_client()
|
start_game_client()
|
||||||
elif installation.action is PrimaryAction.INSTALL:
|
elif installation.action in {PrimaryAction.INSTALL, PrimaryAction.UPDATE}:
|
||||||
run_operation(
|
synchronize_game_files(installation)
|
||||||
"Installing official game files…",
|
|
||||||
command_for("update"),
|
|
||||||
game_files_may_change=True,
|
|
||||||
)
|
|
||||||
elif installation.action is PrimaryAction.UPDATE:
|
|
||||||
run_operation(
|
|
||||||
"Synchronizing official game files…",
|
|
||||||
command_for("update"),
|
|
||||||
game_files_may_change=True,
|
|
||||||
)
|
|
||||||
elif installation.action is PrimaryAction.LAUNCHER_UPDATE:
|
elif installation.action is PrimaryAction.LAUNCHER_UPDATE:
|
||||||
run_launcher_update(
|
run_launcher_update(
|
||||||
runtime_compatibility.reason
|
runtime_compatibility.reason
|
||||||
@@ -2164,20 +2572,8 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
play_button.configure(command=run_primary_action)
|
play_button.configure(command=run_primary_action)
|
||||||
update_button.configure(
|
update_button.configure(command=synchronize_game_files)
|
||||||
command=lambda: run_operation(
|
repair_button.configure(command=lambda: synchronize_game_files(repair=True))
|
||||||
"Synchronizing official game files…",
|
|
||||||
command_for("update"),
|
|
||||||
game_files_may_change=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
repair_button.configure(
|
|
||||||
command=lambda: run_operation(
|
|
||||||
"Checking every game file against the official depot…",
|
|
||||||
command_for("update", "--no-import", "--repair"),
|
|
||||||
game_files_may_change=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
doctor_button.configure(
|
doctor_button.configure(
|
||||||
command=lambda: run_operation("Checking the system…", command_for("doctor"))
|
command=lambda: run_operation("Checking the system…", command_for("doctor"))
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user