feat: launch offline (WIP))

This commit is contained in:
aclist committed 2026-06-07 20:17:58 +09:00
1 parent 0070fe4061
commit 831bd9145d
9 files changed
+82 -6

No files matched your search

+17 -1
View File
@@ -146,7 +146,7 @@ def connect(client: str, addr: str, appid: int, name: str, mods: list[str]) -> i
return proc.returncode return proc.returncode
def load_to_menu(client: str, addr: str, appid: int, name: str, mods: list[str]) -> int: def load_to_menu(client: str, appid: int, name: str, mods: list[str]) -> int:
"""Loads to the menu screen with the selected mods; used for AFK/pre-joining""" """Loads to the menu screen with the selected mods; used for AFK/pre-joining"""
concat = concat_mods(mods) concat = concat_mods(mods)
client_args = concat_bash_args(client) client_args = concat_bash_args(client)
@@ -162,6 +162,22 @@ def load_to_menu(client: str, addr: str, appid: int, name: str, mods: list[str])
proc = subprocess.run([*client_args, *params]) proc = subprocess.run([*client_args, *params])
return proc.returncode return proc.returncode
def launch_offline(client: str, addr: str, appid: int, name: str, mods: list[str], mission: str) -> int:
"""Launch offline with specific mods/missions"""
concat = concat_mods(mods)
client_args = concat_bash_args(client)
params = [
"-applaunch",
str(appid),
"-nolauncher",
"-nosplash",
"-skipintro",
f"-name={name}",
f"-mod={concat}",
f"-mission={mission}"
]
proc = subprocess.run([*client_args, *params])
return proc.returncode
def find_user_id(path: Path) -> str | None: def find_user_id(path: Path) -> str | None:
resolved_path = path / "config" / "loginusers.vdf" resolved_path = path / "config" / "loginusers.vdf"
+1
View File
@@ -103,6 +103,7 @@ class Preferences(EnumWithAttrs):
class NotebookPage(EnumWithAttrs): class NotebookPage(EnumWithAttrs):
OFFLINE = {"crumbs": strings.crumbs.offline, "statusbar": True}
CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": True} CHANGELOG = {"crumbs": strings.crumbs.changelog, "statusbar": True}
DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False} DEVELOPERS = {"crumbs": strings.crumbs.developers, "statusbar": False}
HELP = {"crumbs": strings.crumbs._help, "statusbar": True} HELP = {"crumbs": strings.crumbs._help, "statusbar": True}
+11 -1
View File
@@ -26,7 +26,7 @@ from dzgui.model.servers import ServerModelManager
from dzgui.util.diag import write_diagnostic from dzgui.util.diag import write_diagnostic
from dzgui.util.format import format_player_count from dzgui.util.format import format_player_count
from dzgui.util.open_links import open_user_workshop, open_workshop_page from dzgui.util.open_links import open_user_workshop, open_workshop_page
from dzgui.views.dialogs.filepicker import FilePicker from dzgui.views.dialogs.filepicker import FilePicker, FolderPicker
from dzgui.views.dialogs.generic import ExceptionDialog from dzgui.views.dialogs.generic import ExceptionDialog
import gi import gi
@@ -47,6 +47,7 @@ if TYPE_CHECKING:
from dzgui.views.components.filter_panel import FilterPanel from dzgui.views.components.filter_panel import FilterPanel
from dzgui.views.components.right_panel import RightPanel from dzgui.views.components.right_panel import RightPanel
from dzgui.views.components.statusbar import Statusbar from dzgui.views.components.statusbar import Statusbar
from dzgui.views.pages.offline import OfflineLoader
from dzgui.views.pages.options import Options from dzgui.views.pages.options import Options
from dzgui.views.pages.preconnect import PreConnectionAssistant from dzgui.views.pages.preconnect import PreConnectionAssistant
from dzgui.views.pages.servers import ServerNotebook from dzgui.views.pages.servers import ServerNotebook
@@ -72,6 +73,7 @@ class AppNavigation:
filters: "FilterPanel" filters: "FilterPanel"
preconnect: "PreConnectionAssistant" preconnect: "PreConnectionAssistant"
options: "Options" options: "Options"
offline_loader: "OfflineLoader"
class Controller(GObject.GObject): class Controller(GObject.GObject):
@@ -288,6 +290,10 @@ class Controller(GObject.GObject):
dialog = ExceptionDialog(self, str(e)) dialog = ExceptionDialog(self, str(e))
dialog.run() dialog.run()
def set_custom_folder(self) -> None:
picker = FolderPicker(self.mediator.window)
return picker.pick_folder()
def update_api_key(self, key: Preferences, text: str) -> None: def update_api_key(self, key: Preferences, text: str) -> None:
self.config_man.update_api_key(key, text) self.config_man.update_api_key(key, text)
@@ -533,3 +539,7 @@ class Controller(GObject.GObject):
self.clear_cancel_event() self.clear_cancel_event()
return True return True
return False return False
def open_offline(self, mods) -> None:
self.open_page(NotebookPage.OFFLINE)
self.mediator.offline_loader.populate(mods)
+1 -1
View File
@@ -267,7 +267,7 @@ class ConnectionManager:
if menu_only: if menu_only:
rc = load_to_menu(client, addr, self.appid, playername, self.remote_mod_ids) rc = load_to_menu(client, addr, self.appid, playername, self.remote_mod_ids)
else: else:
rc = connect(client, addr, self.appid, playername, self.remote_mod_ids) rc = connect(client, self.appid, playername, self.remote_mod_ids)
if rc != 0: if rc != 0:
# TODO: log/pop the error # TODO: log/pop the error
func = StoredFunc(self.controller.update_status) func = StoredFunc(self.controller.update_status)
+2
View File
@@ -273,6 +273,7 @@ class Crumbs:
thanks: str thanks: str
developers: str developers: str
default: str default: str
offline: str
@dataclass(slots=True, frozen=True) @dataclass(slots=True, frozen=True)
@@ -462,6 +463,7 @@ crumbs = Crumbs(
thanks="Help > Special thanks", thanks="Help > Special thanks",
developers="Options > Developers", developers="Options > Developers",
default="Servers > ", default="Servers > ",
offline="Mods > Play offline"
) )
no_mods = "No local mods found." no_mods = "No local mods found."
+5
View File
@@ -110,8 +110,13 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
self.connection = PreConnectionAssistant(MainController) self.connection = PreConnectionAssistant(MainController)
from dzgui.views.pages.offline import OfflineLoader
self.offline = OfflineLoader(MainController)
self.pages = { self.pages = {
self.help: NotebookPage.HELP, self.help: NotebookPage.HELP,
self.offline: NotebookPage.OFFLINE,
self.clog: NotebookPage.CHANGELOG, self.clog: NotebookPage.CHANGELOG,
self.keys: NotebookPage.KEYS, self.keys: NotebookPage.KEYS,
self.settings: NotebookPage.OPTIONS, self.settings: NotebookPage.OPTIONS,
+1 -1
View File
@@ -1,5 +1,4 @@
from typing import Self, Union, TYPE_CHECKING from typing import Self, Union, TYPE_CHECKING
from warnings import deprecated
from dzgui.const.enum import NotebookPage, ServerTab from dzgui.const.enum import NotebookPage, ServerTab
from dzgui.util.strings import esc_to_return, question_to_return from dzgui.util.strings import esc_to_return, question_to_return
@@ -70,6 +69,7 @@ class Statusbar(Gtk.Grid):
NotebookPage.CHANGELOG, NotebookPage.CHANGELOG,
NotebookPage.LOG, NotebookPage.LOG,
NotebookPage.CONNECTION, NotebookPage.CONNECTION,
NotebookPage.OFFLINE,
): ):
self.set_by_context(enum, esc_to_return) self.set_by_context(enum, esc_to_return)
return return
+25
View File
@@ -33,3 +33,28 @@ class FilePicker(Gtk.FileChooserDialog):
self.destroy() self.destroy()
return Path(file) return Path(file)
return None return None
class FolderPicker(Gtk.FileChooserDialog):
def __init__(self, parent: Gtk.Window) -> None:
super().__init__(
title=picker.title,
action=Gtk.FileChooserAction.SELECT_FOLDER,
parent=parent,
resizable=True,
)
self.add_buttons("_Cancel", Gtk.ResponseType.CANCEL)
self.add_buttons("_OK", Gtk.ResponseType.OK)
self.set_default_response(Gtk.ResponseType.OK)
def pick_folder(self) -> Path | None:
res = self.run()
if res in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
self.destroy()
return None
if res == Gtk.ResponseType.OK:
folder = self.get_current_folder()
if folder is not None:
self.destroy()
return Path(folder)
return None
+19 -2
View File
@@ -11,21 +11,38 @@ if TYPE_CHECKING:
from dzgui.controllers.mc import Controller from dzgui.controllers.mc import Controller
class Mods(NoOverlayScrolledWindow): class Mods(NoOverlayScrolledWindow):
def __init__(self, controller: "Controller") -> None: def __init__(self, controller: "Controller") -> None:
super().__init__() super().__init__()
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.controller = controller self.controller = controller
self.emitter = controller.get_emitter() self.emitter = controller.get_emitter()
self.tree = ModTreeView(controller) self.tree = ModTreeView(controller)
self.tree.set_vexpand(True)
self.add(self.tree) self.offline_button = Gtk.Button(
label="Play offline",
halign=Gtk.Align.START,
margin_top=10,
margin_bottom=10,
)
self.offline_button.connect("clicked", self._on_offline_clicked)
self.box.add(self.offline_button)
self.box.add(self.tree)
self.add(self.box)
self.controller.register_widget("modtreeview", self.tree) self.controller.register_widget("modtreeview", self.tree)
self.connect("map", self._on_map) self.connect("map", self._on_map)
self.connect("unmap", self._on_unmap) self.connect("unmap", self._on_unmap)
def _on_offline_clicked(self, button: Gtk.Button) -> None:
self.controller.open_offline(self.tree.get_model())
# TODO: add offline page
def _on_unmap(self, widget: Self) -> None: def _on_unmap(self, widget: Self) -> None:
self.emitter.emit("mod_page_toggled", False) self.emitter.emit("mod_page_toggled", False)