mirror of
https://github.com/Corfirean/coa-server-manager.git
synced 2026-11-11 03:51:01 +00:00
feat(client): install custom race patches in player mode
This commit is contained in:
1 parent
cfea54e203
commit
7c866b116d
9 files changed
+27
-5
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
area: manager
|
||||
type: added
|
||||
audience: players
|
||||
title: Players can install custom race patches when connecting to a friend
|
||||
---
|
||||
Install the custom race client patch directly from the Game client section without hosting a server.
|
||||
@@ -668,9 +668,9 @@ async fn module_set_enabled(state: State<'_, AppState>, id: String, module: Stri
|
||||
|
||||
#[tauri::command]
|
||||
async fn custom_races_status(state: State<'_, AppState>, id: String) -> std::result::Result<Option<coa_core::custom_races::ClientPatchStatus>, UiError> {
|
||||
let root = path_of(&state, &id)?;
|
||||
let ctx = client_context(&state, &id)?;
|
||||
blocking(move || {
|
||||
let client = linked_client(&root)?;
|
||||
let client = ctx.linked();
|
||||
Ok(client.as_deref().map(coa_core::custom_races::client_patch_status))
|
||||
})
|
||||
.await
|
||||
@@ -678,12 +678,12 @@ async fn custom_races_status(state: State<'_, AppState>, id: String) -> std::res
|
||||
|
||||
#[tauri::command]
|
||||
async fn custom_races_install(app: AppHandle, state: State<'_, AppState>, id: String) -> std::result::Result<(), UiError> {
|
||||
let root = path_of(&state, &id)?;
|
||||
let ctx = client_context(&state, &id)?;
|
||||
let _guard = BusyGuard::acquire(&state, &id)?;
|
||||
let cancel = begin_client_job(&state)?;
|
||||
let sid = id.clone();
|
||||
let result = blocking(move || {
|
||||
let client = linked_client(&root)?.ok_or_else(|| Error::Invalid("No game client is linked to this server.".into()))?;
|
||||
let client = ctx.require()?;
|
||||
coa_core::custom_races::install_client_patches(&client, &cancel, &|step, percent| {
|
||||
let _ = app.emit("custom-races-progress", serde_json::json!({
|
||||
"id": sid,
|
||||
@@ -691,7 +691,7 @@ async fn custom_races_install(app: AppHandle, state: State<'_, AppState>, id: St
|
||||
"percent": percent,
|
||||
}));
|
||||
})?;
|
||||
let enabled = coa_core::modules::list(&root).iter().any(|module| module.id == "custom-races" && module.enabled);
|
||||
let enabled = ctx.root.as_ref().map_or(true, |root| coa_core::modules::list(root).iter().any(|module| module.id == "custom-races" && module.enabled));
|
||||
coa_core::custom_races::set_client_patch_enabled(&client, enabled)
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -195,6 +195,7 @@ const de: Partial<Record<keyof typeof en, string>> = {
|
||||
"comp.saved": "Gespeichert. {n} Begleiter werden beim Serverstart erstellt.",
|
||||
|
||||
// Client
|
||||
"client.races.install": "Patch für benutzerdefinierte Völker installieren",
|
||||
"client.title": "Spielclient",
|
||||
"client.looking": "Suche …",
|
||||
"client.intro": "Sag dem Manager, wo dein Spiel installiert ist, um einen SPIELEN-Button zu erhalten. Deine Spieldateien werden nie geändert, außer der Serveradresse und dem Begleiter-Addon.",
|
||||
|
||||
@@ -238,6 +238,7 @@ const en = {
|
||||
"comp.saved": "Saved. {n} companions will be created when the server starts.",
|
||||
|
||||
// Client
|
||||
"client.races.install": "Install custom race patch",
|
||||
"client.title": "Game client",
|
||||
"client.looking": "Looking…",
|
||||
"client.intro": "Tell Manager where your game is installed to get a PLAY button. Your game files are never changed except the server address and the companion addon.",
|
||||
|
||||
@@ -195,6 +195,7 @@ const es: Partial<Record<keyof typeof en, string>> = {
|
||||
"comp.saved": "Guardado. Se crearán {n} compañeros cuando se inicie el servidor.",
|
||||
|
||||
// Client
|
||||
"client.races.install": "Instalar parche de razas personalizadas",
|
||||
"client.title": "Cliente del juego",
|
||||
"client.looking": "Buscando…",
|
||||
"client.intro": "Indica al Manager dónde está instalado el juego para obtener un botón JUGAR. Tus archivos del juego nunca se modifican, salvo la dirección del servidor y el addon de compañeros.",
|
||||
|
||||
@@ -195,6 +195,7 @@ const fr: Partial<Record<keyof typeof en, string>> = {
|
||||
"comp.saved": "Enregistré. {n} compagnons seront créés au démarrage du serveur.",
|
||||
|
||||
// Client
|
||||
"client.races.install": "Installer le patch des races personnalisées",
|
||||
"client.title": "Client du jeu",
|
||||
"client.looking": "Recherche…",
|
||||
"client.intro": "Indiquez au Manager où le jeu est installé pour obtenir un bouton JOUER. Vos fichiers de jeu ne sont jamais modifiés, sauf l’adresse du serveur et l’addon des compagnons.",
|
||||
|
||||
@@ -228,6 +228,7 @@ const ru: Partial<Record<keyof typeof en, string>> = {
|
||||
"comp.saved": "Сохранено. Компаньоны ({n}) будут созданы при запуске сервера.",
|
||||
|
||||
// Client
|
||||
"client.races.install": "Установить патч кастомных рас",
|
||||
"client.title": "Клиент игры",
|
||||
"client.looking": "Ищу…",
|
||||
"client.intro": "Укажите, где установлена игра, чтобы появилась кнопка «Играть». Файлы игры не изменяются, кроме адреса сервера и аддона компаньонов.",
|
||||
|
||||
@@ -207,6 +207,7 @@ const zh = {
|
||||
"comp.saved": "已保存。{n} 名随从将在服务器启动时创建。",
|
||||
|
||||
// Client
|
||||
"client.races.install": "安装自定义种族补丁",
|
||||
"client.title": "游戏客户端",
|
||||
"client.looking": "正在寻找…",
|
||||
"client.intro": "指定游戏客户端安装路径即可启用“开始游戏”按钮。除服务器地址和随从插件外,你的游戏文件绝不会被修改。",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { api, asUiError, type ClientInfo, type ClientStatus, type UiError } from "@/lib/api";
|
||||
import { checkClient } from "@/lib/clientUpdate";
|
||||
@@ -16,6 +17,7 @@ export function ClientCard({ serverId, remote = false }: { serverId: string; rem
|
||||
const [note, setNote] = useState<string | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [status, setStatus] = useState<ClientStatus | null>(null);
|
||||
const [patchProgress, setPatchProgress] = useState<string | null>(null);
|
||||
const [dialog, setDialog] = useState<ClientDialogMode | null>(null);
|
||||
const [playAfter, setPlayAfter] = useStartAfterUpdate("client", serverId);
|
||||
|
||||
@@ -25,6 +27,10 @@ export function ClientCard({ serverId, remote = false }: { serverId: string; rem
|
||||
};
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
const unlisten = listen<{ id: string; step: string; percent: number }>("custom-races-progress", e => {
|
||||
if (e.payload.id === serverId) setPatchProgress(`${e.payload.step} (${Math.round(e.payload.percent * 100)}%)`);
|
||||
});
|
||||
return () => { void unlisten.then(f => f()); };
|
||||
}, [serverId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
async function run(fn: () => Promise<string | void>) {
|
||||
@@ -40,6 +46,7 @@ export function ClientCard({ serverId, remote = false }: { serverId: string; rem
|
||||
setError(asUiError(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
setPatchProgress(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,11 +118,13 @@ export function ClientCard({ serverId, remote = false }: { serverId: string; rem
|
||||
{status && !status.managed && (
|
||||
<Button size="sm" disabled={busy} onClick={() => setDialog("track")}>{t("client.track.button")}</Button>
|
||||
)}
|
||||
{remote && <Button size="sm" disabled={busy} onClick={() => void run(() => api.customRacesInstall(serverId))}>{t("client.races.install")}</Button>}
|
||||
<Button size="sm" variant="ghost" disabled={busy} onClick={() => void choose()}>{t("client.changeFolder")}</Button>
|
||||
<Button size="sm" variant="ghost" disabled={busy} onClick={() => setDialog("setup")}>{t("client.downloadInstead")}</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{patchProgress && <p className="mt-3 text-sm text-muted" role="status">{patchProgress}</p>}
|
||||
{note && <p className="mt-3 text-sm text-ok" role="status">{note}</p>}
|
||||
{error && <p className="mt-3 text-sm text-bad" role="alert">{error.human.code === "unknown" ? error.technical : human(error.human).message}</p>}
|
||||
{dialog && (
|
||||
|
||||
Reference in new issue
Block a user