mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-08-03 15:52:41 +00:00
feat: slim skins support
This commit is contained in:
parent
60fd9485ea
commit
53e652b3b1
|
|
@ -47,7 +47,6 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
|
|||
const { updateMessage, clearUpdateMessage } = useUpdateCheck();
|
||||
|
||||
const configRaw = useAppConfig();
|
||||
const skinSync = useSkinSync();
|
||||
const gameRaw = useGameManager({
|
||||
profile: configRaw.profile,
|
||||
setProfile: configRaw.setProfile,
|
||||
|
|
@ -55,6 +54,7 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
|
|||
setCustomEditions: configRaw.setCustomEditions,
|
||||
keepLauncherOpen: configRaw.keepLauncherOpen,
|
||||
});
|
||||
const skinSync = useSkinSync({ profile: configRaw.profile, editions: gameRaw.editions });
|
||||
const audioRaw = useAudioController({
|
||||
musicVol: configRaw.musicVol,
|
||||
sfxVol: configRaw.sfxVol,
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ const BASE_EDITIONS = [
|
|||
desc: "Backporting the newer title updates back to the LCE",
|
||||
url: "https://codeberg.org/piebot/LegacyEvolved/releases/download/nightly/LCEWindows64.zip",
|
||||
titleImage: "/images/minecraft_title_LegacyEvolved.png",
|
||||
supportsSlimSkins: true,
|
||||
},
|
||||
{
|
||||
id: "revelations",
|
||||
name: "Legacy Revelations",
|
||||
desc: "QoL, performance, hardcore mode, & security features for LCE.",
|
||||
url: "https://github.com/itsRevela/MinecraftConsoles/releases/download/Nightly/LCREWindows64.zip",
|
||||
titleImage: "/images/minecraft_title_revelations.png"
|
||||
titleImage: "/images/minecraft_title_revelations.png",
|
||||
supportsSlimSkins: false,
|
||||
},
|
||||
{
|
||||
id: "vanilla_tu19",
|
||||
|
|
@ -25,6 +27,7 @@ const BASE_EDITIONS = [
|
|||
desc: "Leaked 4J Studios build. (smartcmd)",
|
||||
url: "https://github.com/smartcmd/MinecraftConsoles/releases/download/nightly/LCEWindows64.zip",
|
||||
titleImage: "/images/minecraft_title_tu19.png",
|
||||
supportsSlimSkins: false,
|
||||
},
|
||||
{
|
||||
id: "360revived",
|
||||
|
|
@ -32,6 +35,7 @@ const BASE_EDITIONS = [
|
|||
desc: "PC port of Xbox 360 Edition TU19",
|
||||
url: "https://github.com/BluTac10/360Revived/releases/download/nightly/LCEWindows64.zip",
|
||||
titleImage: "/images/minecraft_title_360revived.png",
|
||||
supportsSlimSkins: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -51,16 +55,27 @@ interface GameManagerProps {
|
|||
keepLauncherOpen: boolean;
|
||||
}
|
||||
|
||||
export function useGameManager({ profile, setProfile, customEditions, setCustomEditions, keepLauncherOpen }: GameManagerProps) {
|
||||
export function useGameManager({
|
||||
profile,
|
||||
setProfile,
|
||||
customEditions,
|
||||
setCustomEditions,
|
||||
keepLauncherOpen,
|
||||
}: GameManagerProps) {
|
||||
const [installs, setInstalls] = useState<string[]>([]);
|
||||
const [isGameRunning, setIsGameRunning] = useState(false);
|
||||
const [downloadProgress, setDownloadProgress] = useState<number | null>(null);
|
||||
const [downloadingId, setDownloadingId] = useState<string | null>(null);
|
||||
const [isRunnerDownloading, setIsRunnerDownloading] = useState(false);
|
||||
const [runnerDownloadProgress, setRunnerDownloadProgress] = useState<number | null>(null);
|
||||
const [runnerDownloadProgress, setRunnerDownloadProgress] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const editions = useMemo(() => [...BASE_EDITIONS, ...customEditions], [customEditions]);
|
||||
const editions = useMemo(
|
||||
() => [...BASE_EDITIONS, ...customEditions],
|
||||
[customEditions],
|
||||
);
|
||||
|
||||
const checkInstalls = useCallback(async () => {
|
||||
const results = await Promise.all(
|
||||
|
|
@ -74,56 +89,73 @@ export function useGameManager({ profile, setProfile, customEditions, setCustomE
|
|||
|
||||
useEffect(() => {
|
||||
checkInstalls();
|
||||
const unlistenDownload = TauriService.onDownloadProgress((p) => setDownloadProgress(p));
|
||||
const unlistenRunner = TauriService.onRunnerDownloadProgress((p) => setRunnerDownloadProgress(p));
|
||||
const unlistenDownload = TauriService.onDownloadProgress((p) =>
|
||||
setDownloadProgress(p),
|
||||
);
|
||||
const unlistenRunner = TauriService.onRunnerDownloadProgress((p) =>
|
||||
setRunnerDownloadProgress(p),
|
||||
);
|
||||
return () => {
|
||||
unlistenDownload.then((u) => u());
|
||||
unlistenRunner.then((u) => u());
|
||||
};
|
||||
}, [customEditions, checkInstalls]);
|
||||
|
||||
const downloadRunner = useCallback(async (name: string, url: string) => {
|
||||
if (isRunnerDownloading) return;
|
||||
setIsRunnerDownloading(true);
|
||||
setRunnerDownloadProgress(0);
|
||||
setError(null);
|
||||
try {
|
||||
await TauriService.downloadRunner(name, url);
|
||||
setRunnerDownloadProgress(null);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
setError(typeof e === 'string' ? e : e.message || "Failed to download runner");
|
||||
} finally {
|
||||
setIsRunnerDownloading(false);
|
||||
}
|
||||
}, [isRunnerDownloading]);
|
||||
const downloadRunner = useCallback(
|
||||
async (name: string, url: string) => {
|
||||
if (isRunnerDownloading) return;
|
||||
setIsRunnerDownloading(true);
|
||||
setRunnerDownloadProgress(0);
|
||||
setError(null);
|
||||
try {
|
||||
await TauriService.downloadRunner(name, url);
|
||||
setRunnerDownloadProgress(null);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
setError(
|
||||
typeof e === "string" ? e : e.message || "Failed to download runner",
|
||||
);
|
||||
} finally {
|
||||
setIsRunnerDownloading(false);
|
||||
}
|
||||
},
|
||||
[isRunnerDownloading],
|
||||
);
|
||||
|
||||
const toggleInstall = useCallback(async (id: string) => {
|
||||
if (downloadingId) return;
|
||||
const edition = editions.find((e) => e.id === id);
|
||||
if (!edition) return;
|
||||
setError(null);
|
||||
try {
|
||||
setDownloadingId(id);
|
||||
setDownloadProgress(0);
|
||||
await TauriService.downloadAndInstall(edition.url, id);
|
||||
await TauriService.syncDlc(id);
|
||||
const toggleInstall = useCallback(
|
||||
async (id: string) => {
|
||||
if (downloadingId) return;
|
||||
const edition = editions.find((e) => e.id === id);
|
||||
if (!edition) return;
|
||||
setError(null);
|
||||
try {
|
||||
setDownloadingId(id);
|
||||
setDownloadProgress(0);
|
||||
await TauriService.downloadAndInstall(edition.url, id);
|
||||
await TauriService.syncDlc(id);
|
||||
await checkInstalls();
|
||||
setProfile(id);
|
||||
setDownloadProgress(null);
|
||||
setDownloadingId(null);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
setError(
|
||||
typeof e === "string" ? e : e.message || "Failed to install version",
|
||||
);
|
||||
setDownloadProgress(null);
|
||||
setDownloadingId(null);
|
||||
}
|
||||
},
|
||||
[downloadingId, editions, checkInstalls, setProfile],
|
||||
);
|
||||
|
||||
const handleUninstall = useCallback(
|
||||
async (id: string) => {
|
||||
await TauriService.deleteInstance(id);
|
||||
await checkInstalls();
|
||||
setProfile(id);
|
||||
setDownloadProgress(null);
|
||||
setDownloadingId(null);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
setError(typeof e === 'string' ? e : e.message || "Failed to install version");
|
||||
setDownloadProgress(null);
|
||||
setDownloadingId(null);
|
||||
}
|
||||
}, [downloadingId, editions, checkInstalls, setProfile]);
|
||||
|
||||
const handleUninstall = useCallback(async (id: string) => {
|
||||
await TauriService.deleteInstance(id);
|
||||
await checkInstalls();
|
||||
}, [checkInstalls]);
|
||||
},
|
||||
[checkInstalls],
|
||||
);
|
||||
|
||||
const handleLaunch = useCallback(async () => {
|
||||
if (isGameRunning) return;
|
||||
|
|
@ -136,7 +168,9 @@ export function useGameManager({ profile, setProfile, customEditions, setCustomE
|
|||
await TauriService.launchGame(profile, PARTNERSHIP_SERVERS);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
setError(typeof e === 'string' ? e : e.message || "Failed to launch game");
|
||||
setError(
|
||||
typeof e === "string" ? e : e.message || "Failed to launch game",
|
||||
);
|
||||
} finally {
|
||||
setIsGameRunning(false);
|
||||
await appWindow.show();
|
||||
|
|
@ -154,21 +188,36 @@ export function useGameManager({ profile, setProfile, customEditions, setCustomE
|
|||
}
|
||||
}, [profile]);
|
||||
|
||||
const addCustomEdition = useCallback((edition: { name: string; desc: string; url: string }) => {
|
||||
const id = `custom_${Date.now()}`;
|
||||
const newEdition = { ...edition, id, titleImage: "/images/minecraft_title_tucustom.png" };
|
||||
setCustomEditions([...customEditions, newEdition]);
|
||||
return id;
|
||||
}, [customEditions, setCustomEditions]);
|
||||
const addCustomEdition = useCallback(
|
||||
(edition: { name: string; desc: string; url: string }) => {
|
||||
const id = `custom_${Date.now()}`;
|
||||
const newEdition = {
|
||||
...edition,
|
||||
id,
|
||||
titleImage: "/images/minecraft_title_tucustom.png",
|
||||
};
|
||||
setCustomEditions([...customEditions, newEdition]);
|
||||
return id;
|
||||
},
|
||||
[customEditions, setCustomEditions],
|
||||
);
|
||||
|
||||
const deleteCustomEdition = useCallback((id: string) => {
|
||||
setCustomEditions(customEditions.filter((e) => e.id !== id));
|
||||
TauriService.deleteInstance(id).catch(console.error);
|
||||
}, [customEditions, setCustomEditions]);
|
||||
const deleteCustomEdition = useCallback(
|
||||
(id: string) => {
|
||||
setCustomEditions(customEditions.filter((e) => e.id !== id));
|
||||
TauriService.deleteInstance(id).catch(console.error);
|
||||
},
|
||||
[customEditions, setCustomEditions],
|
||||
);
|
||||
|
||||
const updateCustomEdition = useCallback((id: string, updated: { name: string; desc: string; url: string }) => {
|
||||
setCustomEditions(customEditions.map((e) => e.id === id ? { ...e, ...updated } : e));
|
||||
}, [customEditions, setCustomEditions]);
|
||||
const updateCustomEdition = useCallback(
|
||||
(id: string, updated: { name: string; desc: string; url: string }) => {
|
||||
setCustomEditions(
|
||||
customEditions.map((e) => (e.id === id ? { ...e, ...updated } : e)),
|
||||
);
|
||||
},
|
||||
[customEditions, setCustomEditions],
|
||||
);
|
||||
|
||||
return {
|
||||
installs,
|
||||
|
|
|
|||
|
|
@ -1,33 +1,49 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useLocalStorage } from "./useLocalStorage";
|
||||
|
||||
export function useSkinSync() {
|
||||
interface Edition {
|
||||
id: string;
|
||||
supportsSlimSkins?: boolean;
|
||||
}
|
||||
|
||||
interface UseSkinSyncProps {
|
||||
profile: string;
|
||||
editions: Edition[];
|
||||
}
|
||||
|
||||
export function useSkinSync({ profile, editions }: UseSkinSyncProps) {
|
||||
const [skinUrl, setSkinUrl] = useLocalStorage("lce-skin", "/images/Default.png");
|
||||
const [skinBase64, setSkinBase64] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const syncSkin = async () => {
|
||||
if (!skinUrl) return;
|
||||
try {
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.onload = () => {
|
||||
const cvs = document.createElement("canvas");
|
||||
cvs.width = 64;
|
||||
cvs.height = 32;
|
||||
const ctx = cvs.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, 64, 32, 0, 0, 64, 32);
|
||||
setSkinBase64(cvs.toDataURL("image/png"));
|
||||
}
|
||||
};
|
||||
img.src = skinUrl;
|
||||
} catch (e) {
|
||||
console.error("Skin conversion failed:", e);
|
||||
const edition = editions.find((e) => e.id === profile);
|
||||
const supportsSlim = edition?.supportsSlimSkins ?? false;
|
||||
if (!supportsSlim) {
|
||||
try {
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.onload = () => {
|
||||
const cvs = document.createElement("canvas");
|
||||
cvs.width = 64;
|
||||
cvs.height = 32;
|
||||
const ctx = cvs.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, 64, 32, 0, 0, 64, 32);
|
||||
setSkinBase64(cvs.toDataURL("image/png"));
|
||||
}
|
||||
};
|
||||
img.src = skinUrl;
|
||||
} catch (e) {
|
||||
console.error("Skin conversion failed:", e);
|
||||
}
|
||||
} else {
|
||||
setSkinBase64(null);
|
||||
}
|
||||
};
|
||||
syncSkin();
|
||||
}, [skinUrl]);
|
||||
}, [skinUrl, profile, editions]);
|
||||
|
||||
return {
|
||||
skinUrl,
|
||||
|
|
|
|||
Loading…
Reference in a new issue