diff --git a/public/images/LCE Team.png b/public/images/LCE Team.png new file mode 100644 index 0000000..bcbd463 Binary files /dev/null and b/public/images/LCE Team.png differ diff --git a/public/images/esrb_warning.png b/public/images/esrb_warning.png new file mode 100644 index 0000000..c9f558f Binary files /dev/null and b/public/images/esrb_warning.png differ diff --git a/public/images/minecraft_title_neoLegacy.png b/public/images/minecraft_title_neoLegacy.png index 618c261..3d2cc52 100644 Binary files a/public/images/minecraft_title_neoLegacy.png and b/public/images/minecraft_title_neoLegacy.png differ diff --git a/public/images/wool_14.png b/public/images/wool_14.png deleted file mode 100644 index 09cff97..0000000 Binary files a/public/images/wool_14.png and /dev/null differ diff --git a/public/images/wool_5.png b/public/images/wool_5.png deleted file mode 100644 index 958e431..0000000 Binary files a/public/images/wool_5.png and /dev/null differ diff --git a/public/images/wool_8.png b/public/images/wool_8.png deleted file mode 100644 index a16ed67..0000000 Binary files a/public/images/wool_8.png and /dev/null differ diff --git a/public/music/Moog City 2.opus b/public/music/Moog City 2.opus new file mode 100644 index 0000000..7465f4e Binary files /dev/null and b/public/music/Moog City 2.opus differ diff --git a/public/sounds/in.ogg b/public/sounds/in.ogg new file mode 100644 index 0000000..5ec26ff Binary files /dev/null and b/public/sounds/in.ogg differ diff --git a/public/sounds/notification.ogg b/public/sounds/notification.ogg new file mode 100644 index 0000000..f097959 Binary files /dev/null and b/public/sounds/notification.ogg differ diff --git a/public/sounds/out.ogg b/public/sounds/out.ogg new file mode 100644 index 0000000..78d4b72 Binary files /dev/null and b/public/sounds/out.ogg differ diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index fb16c7c..b5295d6 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -25,6 +25,7 @@ pub fn load_config_raw(app: AppHandle) -> AppConfig { animations_enabled: Some(true), vfx_enabled: Some(true), rpc_enabled: Some(true), + start_fullscreen: Some(true), music_vol: Some(50), sfx_vol: Some(100), legacy_mode: Some(false), diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index af0247e..c7592bb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -97,6 +97,14 @@ pub fn run() { relay::join_game, ]) .setup(|app| { + let app_handle = app.handle().clone(); + let config = config::load_config_raw(app_handle.clone()); + if config.start_fullscreen.unwrap_or(true) { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.set_fullscreen(true); + } + } + let args: Vec = std::env::args().collect(); if args.len() > 1 && !args[1].starts_with('-') { let instance_id = args[1].clone(); diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index 44db3b7..ba3ea98 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -40,6 +40,7 @@ pub struct AppConfig { pub animations_enabled: Option, pub vfx_enabled: Option, pub rpc_enabled: Option, + pub start_fullscreen: Option, pub music_vol: Option, pub sfx_vol: Option, pub legacy_mode: Option, diff --git a/src/components/common/AchievementToast.tsx b/src/components/common/AchievementToast.tsx index ccaae5c..546ed5b 100644 --- a/src/components/common/AchievementToast.tsx +++ b/src/components/common/AchievementToast.tsx @@ -1,5 +1,6 @@ import { useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; +import { useAudio } from "../../context/LauncherContext"; interface AchievementToastProps { message: string | null; @@ -16,14 +17,24 @@ export function AchievementToast({ title = "Error!", variant = "error", }: AchievementToastProps) { + const { playSfx } = useAudio(); + useEffect(() => { if (message) { + if (variant === "update") { + playSfx("notification.ogg"); + } else { + playSfx("in.ogg"); + } const timer = setTimeout(() => { onClose(); }, 8000); - return () => clearTimeout(timer); + return () => { + clearTimeout(timer); + playSfx("out.ogg"); + }; } - }, [message, onClose]); + }, [message, onClose, variant, playSfx]); const getIcon = () => { if (variant === "update") { diff --git a/src/components/common/CinematicIntro.tsx b/src/components/common/CinematicIntro.tsx new file mode 100644 index 0000000..7933eef --- /dev/null +++ b/src/components/common/CinematicIntro.tsx @@ -0,0 +1,93 @@ +import { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; + +interface CinematicIntroProps { + onComplete: () => void; + startMusic: () => void; +} + +type Phase = "black" | "white-lceteam" | "white-esrb" | "out"; + +const TIMINGS = { + black: 800, + "white-lceteam": 3000, + "white-esrb": 3000, + out: 800, +}; + +export function CinematicIntro({ onComplete, startMusic }: CinematicIntroProps) { + const [phase, setPhase] = useState("black"); + + useEffect(() => { + startMusic(); + + const run = async () => { + await new Promise((r) => setTimeout(r, TIMINGS.black)); + setPhase("white-lceteam"); + + await new Promise((r) => setTimeout(r, TIMINGS["white-lceteam"])); + setPhase("white-esrb"); + + await new Promise((r) => setTimeout(r, TIMINGS["white-esrb"])); + setPhase("out"); + + await new Promise((r) => setTimeout(r, TIMINGS.out)); + onComplete(); + }; + + run(); + }, [onComplete, startMusic]); + + return ( +
+ + {phase === "black" && ( + + )} + + {(phase === "white-lceteam" || phase === "white-esrb" || phase === "out") && ( + + + {phase === "white-lceteam" && ( + + )} + + {(phase === "white-esrb" || phase === "out") && ( + + )} + + + )} + +
+ ); +} diff --git a/src/components/views/LceLiveView.tsx b/src/components/views/LceLiveView.tsx index 1a9d8d2..efc971d 100644 --- a/src/components/views/LceLiveView.tsx +++ b/src/components/views/LceLiveView.tsx @@ -803,16 +803,7 @@ const LceLiveView = memo(function LceLiveView() { )} -
+
{renderContent()}
diff --git a/src/components/views/SettingsView.tsx b/src/components/views/SettingsView.tsx index b8f0837..018d125 100644 --- a/src/components/views/SettingsView.tsx +++ b/src/components/views/SettingsView.tsx @@ -28,6 +28,8 @@ const SettingsView = memo(function SettingsView() { setPerfBoost, rpcEnabled, setRpcEnabled, + startFullscreen, + setStartFullscreen, legacyMode, setLegacyMode, mangohudEnabled, @@ -100,6 +102,11 @@ const SettingsView = memo(function SettingsView() { setRpcEnabled(!rpcEnabled); }; + const handleFullscreenToggle = () => { + playPressSound(); + setStartFullscreen(!startFullscreen); + }; + const handleLegacyToggle = () => { playPressSound(); setLegacyMode(!legacyMode); @@ -396,6 +403,12 @@ const SettingsView = memo(function SettingsView() { }, }); } else if (currentSubMenu === "launcher") { + items.push({ + id: "fullscreen", + label: `Start in Fullscreen: ${startFullscreen ? "ON" : "OFF"}`, + type: "button", + onClick: handleFullscreenToggle, + }); items.push({ id: "rpc", label: `Discord RPC: ${rpcEnabled ? "ON" : "OFF"}`, diff --git a/src/components/views/VersionsView.tsx b/src/components/views/VersionsView.tsx index 5e8c39b..d9d515a 100644 --- a/src/components/views/VersionsView.tsx +++ b/src/components/views/VersionsView.tsx @@ -299,31 +299,29 @@ const VersionsView = memo(function VersionsView() { onMouseEnter={() => !isComingSoon && setFocusIndex(i)} >
- {isComingSoon ? ( - Coming Soon - ) : isDownloading ? ( + {isDownloading ? ( {Math.floor(downloadProgress || 0)}% - ) : isInstalled ? ( - Installed + ) : edition.logo ? ( + edition.logo.startsWith("http") || + edition.logo.startsWith("/images") ? ( + + ) : ( + + ) ) : ( - Not installed +
)}
@@ -338,23 +336,6 @@ const VersionsView = memo(function VersionsView() { } ${isComingSoon ? "cursor-not-allowed" : ""}`} >
- {edition.logo && - (edition.logo.startsWith("http") || - edition.logo.startsWith("/images") ? ( - - ) : ( - - ))} -
- {ALL_TABS.map((tab) => { +
+ {CATEGORY_TABS.map((tab) => { + const isActive = tab === activeTab; + return ( + + ); + })} +
+ +
+ {SERVER_TABS.map((tab) => { + const isActive = tab === activeTab; + return ( + + ); + })} +
+ {UTILITY_TABS.map((tab) => { const isActive = tab === activeTab; const updateCount = tab === "Installed" @@ -518,16 +554,9 @@ const WorkshopView = memo(function WorkshopView() { key={tab} onClick={() => selectTab(tab)} className={` - relative h-10 px-6 text-lg mc-text-shadow tracking-widest border-none outline-none cursor-pointer transition-all - ${isActive ? "text-[#FFFF55] scale-105" : "text-white hover:text-[#FFFF55] hover:scale-105"} + relative h-8 px-4 text-xs mc-text-shadow tracking-widest border outline-none cursor-pointer transition-all + ${isActive ? "text-[#FFFF55] border-[#FFFF55] bg-black/40" : "text-[#A0A0A0] border-[#444] hover:text-white hover:border-[#888]"} `} - style={{ - backgroundImage: isActive - ? "url('/images/button_highlighted.png')" - : "url('/images/Button_Background.png')", - backgroundSize: "100% 100%", - imageRendering: "pixelated", - }} > {tab.toUpperCase()} {updateCount > 0 && ( @@ -540,16 +569,9 @@ const WorkshopView = memo(function WorkshopView() { })}
-
- - {showSearch ? ( - +
+ {showSearch ? ( +
)}
- - ) : loading ? ( - +
+ ) : loading ? ( +
Searching Archives... - - ) : error ? ( - +
+ ) : error ? ( +
{error} - - ) : ( - +
+ ) : ( +
{filteredItems.length === 0 ? (
@@ -805,9 +808,8 @@ const WorkshopView = memo(function WorkshopView() { ))}
)} - - )} - +
+ )}
@@ -1104,26 +1106,8 @@ function PackageModal({ : "REINSTALL"; return ( <> - - e.stopPropagation()} - className="flex flex-col w-[640px] max-h-[85vh] overflow-hidden font-['Mojangles'] border-2 border-[#555] rounded-sm" - style={{ - backgroundImage: "url('/images/frame_background.png')", - backgroundSize: "100% 100%", - imageRendering: "pixelated", - }} - > +
+
e.stopPropagation()} className="flex flex-col w-[640px] max-h-[85vh] overflow-hidden font-['Mojangles'] mc-options-bg">
{imgError ? (
@@ -1176,11 +1160,11 @@ function PackageModal({
{pkg.extended_description && pkg.extended_description.trim() !== "" && ( -
- - Plugin Description +
+ + Description -
+
{pkg.extended_description} @@ -1189,9 +1173,9 @@ function PackageModal({ )}
-
+
- + Metadata
@@ -1435,8 +1419,8 @@ function PackageModal({
- - +
+
{showInstall && ( diff --git a/src/context/LauncherContext.tsx b/src/context/LauncherContext.tsx index 31560d9..6438304 100644 --- a/src/context/LauncherContext.tsx +++ b/src/context/LauncherContext.tsx @@ -59,7 +59,6 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) { const audioRaw = useAudioController({ musicVol: configRaw.musicVol, sfxVol: configRaw.sfxVol, - showIntro, isGameRunning: gameRaw.isGameRunning, isWindowVisible, }); diff --git a/src/css/App.css b/src/css/App.css index 1d76bf5..c60b78b 100644 --- a/src/css/App.css +++ b/src/css/App.css @@ -1 +1,55 @@ -/* empty :P */ \ No newline at end of file +@keyframes splashPulse { + 0% { transform: scale(0.95) rotate(-20deg); } + 100% { transform: scale(1.08) rotate(-20deg); } +} + +.mc-splash { + animation: splashPulse 0.45s ease-in-out infinite alternate; + transform-origin: center; +} + +.mc-slider-custom { + -webkit-appearance: none; + appearance: none; + background: transparent; + height: 100%; + outline: none; + border: none; + margin: 0; + padding: 0; +} + +.mc-slider-custom::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 44px; + background: url('/images/Slider_Handle.png') no-repeat center; + background-size: 100% 100%; + cursor: pointer; + position: relative; + z-index: 30; +} + +*:focus { + outline: none !important; + box-shadow: none !important; +} + +button, input { + border-radius: 0 !important; + border: none !important; + outline: none !important; + box-shadow: none !important; +} + +.mc-sq-btn { + background: url('/images/Button_Square.png') no-repeat center; + background-size: 100% 100%; + image-rendering: pixelated; +} + +.mc-sq-btn:hover { + background: url('/images/Button_Square_Highlighted.png') no-repeat center; + background-size: 100% 100%; +} \ No newline at end of file diff --git a/src/data/splashes.ts b/src/data/splashes.ts new file mode 100644 index 0000000..25b9ff0 --- /dev/null +++ b/src/data/splashes.ts @@ -0,0 +1,156 @@ +export const SPLASHES = [ + "Legacy is back!", + "Pixelated goodness!", + "Console Edition vibe!", + "100% Not Microsoft!", + "Symmetry is key!", + "Does anyone even read these?", + "Task failed successfully.", + "Hardware accelerated!", + "It's a feature, not a bug.", + "Look behind you.", + "Works on my machine.", + "Now gluten-free!", + "Mom, get the camera!", + "Batteries not included.", + "May contain nuts.", + "Press Alt+F4 for diamonds!", + "Downloading more RAM...", + "Reinventing the wheel!", + "The cake is a lie.", + "Powered by copious amounts of coffee.", + "I'm running out of ideas.", + "That's no moon...", + "Now with 100% more nostalgia!", + "Legacy is the new modern.", + "No microtransactions!", + "As seen on TV!", + "Ironic, isn't it?", + "Creeper? Aww man.", + "Technoblade never dies!", + "is smartcmd dead ?", + "NO BUILT IN MS AUTH !", + "Mr_Anilex wasn't here!", + "Who's Jack ?", + "This text is blue!", + "Bonjour!", + "Salam!", + "Reverse engineering Wii U version", + "Don't try Valorant!", + "This could never be a sad place!", + "Made without microslop", + "Thank you C418", + "Bread is pain", + "From the star!", + "Never gonna give you up!", + "9+10=21", + ".party() was successful", + "Not Kogama", + "You can be proud of you!", + "Let's drink Orange Joe", + "Kirater is a great singer!", + "Mirkette My beloved", + "Started in Bordeaux", + "Oui Oui Baguette", + "Milk In The Microwave", + "8-3: DISINTEGRATION LOOP", + "Turn the light OFF", + "Not written by Mr_Anilex", + "The One Who's Running the Show!", + "Playing Forever", + "The World looks cubic!", + "huh?", + "Sybau", + "Available on Toaster", + "Try ArchLinux", + "69% Accurate", + "A molecule of meow", + "http://localhost:3000", + "uuhhhh...", + "Oyasumi", + "I don't want to set the world on fire", + "Directed by Michael Bay", + "We see you, Opal!", + "A Cool Cat in Town", + "Not BrainRotted!", + "Farting is Natural -Leon", + "93/100 on metacritic", + "Not (anymore) on Steam", + "Sudo apt install EmeraldLauncher", + "Sudo pacman -S EmeraldLauncher", + "Kay-Chan my beloved! <3", + "Peak!", + "OpenSource!", + "made by human with bone and flesh", + "Made with hate against microslop", + "Steelorse :fire:", + "It's Minecraft but i'm not sure", + "Look at you!", + "You're beautiful", + "Mr_Anilex has a big ego", + "Traduis-moi !", + "May contains Mr_Anilex", + "Neoapps didn't write this splash", + "Where's Kinger?", + "KayJann, Breakcore and code", + "Hey Goku!", + "Vegeta is a DZ mashallah", + "Bogos Binted? Vorp", + "YOU SHALL NOT PASS !", + "Bready, Steady, GO !", + "Not-so-Empty-house", + "We'll Meet Again", + "idk", + "wdym", + "Not making sense", + "Dw!", + "i forgor", + "Remember to be patient!", + "NOW'S YOUR CHANCE TO BE A.", + "BIG SHOT", + "A burning memory", + "FREE MONEY!", + "Can You Really Call This A Hotel. I didn't Reveive A Mint On My Pillow Or Anything", + "Try Indie Game", + "SHARK WITH LEGS!", + "it's a seal!", + "Shrimp.", + "Limited edition!", + "Fat free!", + "GOTY!", + "Water proof!", + "LALALA-LAVA", + "CHICHICHI-CHICKEN", + "Tasty ah hell", + "1% sugar!", + "150% hyperbole!", + "Hotter than the sun!", + "Woo, reddit!", + "piebot was here!", + "Legacy in an evolved manner.", + "neoapps is cool!", + "neoapps has put a self insert into this program!", + "Also try neoLegacy!", + "$20 is $20", + "no RenderDragon!", + "Iggy Jiggy", + "Arch, btw!", + "Bedrock bad!", + "Bedrock Linux not bad", + "Also try Terraria!", + "Also try LC Launcher!", + "Exclusively abandonware!", + "100% legal in all 3 states (of matter)", + "Herobrine has been confirmed we are fighting him please help", + "LGTM", + "git revert", + "We do not crypto mine on your computer.", + "100% legal!", + "Definitely not pirated", + "Long Live LCE!", + "For the community, by the community!", + "Not affiliated with Microslop", + "What is nether update", + "To the good old days", + "Also try : Minecraft Pocket Edition", +]; \ No newline at end of file diff --git a/src/hooks/useAppConfig.ts b/src/hooks/useAppConfig.ts index 2e1519d..a78f1b0 100644 --- a/src/hooks/useAppConfig.ts +++ b/src/hooks/useAppConfig.ts @@ -8,6 +8,7 @@ export function useAppConfig() { const [vfxEnabled, setVfxEnabled] = useLocalStorage("lce-vfx", true); const [animationsEnabled, setAnimationsEnabled] = useLocalStorage("lce-animations", true); const [rpcEnabled, setRpcEnabled] = useLocalStorage("discord-rpc", true); + const [startFullscreen, setStartFullscreen] = useLocalStorage("lce-fullscreen", true); const [musicVol, setMusicVol] = useLocalStorage("lce-music", 50); const [sfxVol, setSfxVol] = useLocalStorage("lce-sfx", 100); const [isDayTime, setIsDayTime] = useLocalStorage("lce-daytime", true); @@ -36,6 +37,7 @@ export function useAppConfig() { if (config.vfxEnabled !== undefined) setVfxEnabled(config.vfxEnabled); if (config.animationsEnabled !== undefined) setAnimationsEnabled(config.animationsEnabled); if (config.rpcEnabled !== undefined) setRpcEnabled(config.rpcEnabled); + if (config.startFullscreen !== undefined) setStartFullscreen(config.startFullscreen); if (config.musicVol !== undefined && config.musicVol !== null) setMusicVol(config.musicVol); if (config.sfxVol !== undefined && config.sfxVol !== null) setSfxVol(config.sfxVol); if (config.legacyMode !== undefined) setLegacyMode(config.legacyMode); @@ -60,6 +62,7 @@ export function useAppConfig() { animationsEnabled, vfxEnabled, rpcEnabled, + startFullscreen, musicVol, sfxVol, legacyMode, @@ -69,7 +72,7 @@ export function useAppConfig() { launchEnvVars, }).catch(console.error); } - }, [username, theme, linuxRunner, perfBoost, profile, customEditions, customizations, animationsEnabled, vfxEnabled, rpcEnabled, musicVol, sfxVol, legacyMode, mangohudEnabled, extraLaunchArgs, launchPrefix, launchEnvVars, isLoaded]); + }, [username, theme, linuxRunner, perfBoost, profile, customEditions, customizations, animationsEnabled, vfxEnabled, rpcEnabled, startFullscreen, musicVol, sfxVol, legacyMode, mangohudEnabled, extraLaunchArgs, launchPrefix, launchEnvVars, isLoaded]); return { username, @@ -84,6 +87,8 @@ export function useAppConfig() { setAnimationsEnabled, rpcEnabled, setRpcEnabled, + startFullscreen, + setStartFullscreen, musicVol: musicVol ?? 50, setMusicVol, sfxVol: sfxVol ?? 100, diff --git a/src/hooks/useAudioController.ts b/src/hooks/useAudioController.ts index bf6533b..f095d04 100644 --- a/src/hooks/useAudioController.ts +++ b/src/hooks/useAudioController.ts @@ -1,6 +1,8 @@ import { useState, useEffect, useRef, useCallback } from "react"; +import { SPLASHES } from "../data/splashes"; const TRACKS = [ + "music/Moog City 2.opus", "music/Blind Spots.ogg", "music/Key.ogg", "music/Living Mice.ogg", @@ -13,167 +15,10 @@ const resolveAudioUrl = (path: string) => { return new URL(relativePath, window.location.origin).href; }; -const SPLASHES = [ - "Legacy is back!", - "Pixelated goodness!", - "Console Edition vibe!", - "100% Not Microsoft!", - "Symmetry is key!", - "Does anyone even read these?", - "Task failed successfully.", - "Hardware accelerated!", - "It's a feature, not a bug.", - "Look behind you.", - "Works on my machine.", - "Now gluten-free!", - "Mom, get the camera!", - "Batteries not included.", - "May contain nuts.", - "Press Alt+F4 for diamonds!", - "Downloading more RAM...", - "Reinventing the wheel!", - "The cake is a lie.", - "Powered by copious amounts of coffee.", - "I'm running out of ideas.", - "That's no moon...", - "Now with 100% more nostalgia!", - "Legacy is the new modern.", - "No microtransactions!", - "As seen on TV!", - "Ironic, isn't it?", - "Creeper? Aww man.", - "Technoblade never dies!", - "is smartcmd dead ?", - "NO BUILT IN MS AUTH !", - "Mr_Anilex wasn't here!", - "Who's Jack ?", - "This text is blue!", - "Bonjour!", - "Salam!", - "Reverse engineering Wii U version", - "Don't try Valorant!", - "This could never be a sad place!", - "Made without microslop", - "Thank you C418", - "Bread is pain", - "From the star!", - "Never gonna give you up!", - "9+10=21", - ".party() was successful", - "Not Kogama", - "You can be proud of you!", - "Let's drink Orange Joe", - "Kirater is a great singer!", - "Mirkette My beloved", - "Started in Bordeaux", - "Oui Oui Baguette", - "Milk In The Microwave", - "8-3: DISINTEGRATION LOOP", - "Turn the light OFF", - "Not written by Mr_Anilex", - "The One Who's Running the Show!", - "Playing Forever", - "The World looks cubic!", - "huh?", - "Sybau", - "Available on Toaster", - "Try ArchLinux", - "69% Accurate", - "A molecule of meow", - "http://localhost:3000", - "uuhhhh...", - "Oyasumi", - "I don't want to set the world on fire", - "Directed by Michael Bay", - "We see you, Opal!", - "A Cool Cat in Town", - "Not BrainRotted!", - "Farting is Natural -Leon", - "93/100 on metacritic", - "Not (anymore) on Steam", - "Sudo apt install EmeraldLauncher", - "Sudo pacman -S EmeraldLauncher", - "Kay-Chan my beloved! <3", - "Peak!", - "OpenSource!", - "made by human with bone and flesh", - "Made with hate against microslop", - "Steelorse :fire:", - "It's Minecraft but i'm not sure", - "Look at you!", - "You're beautiful", - "Mr_Anilex has a big ego", - "Traduis-moi !", - "May contains Mr_Anilex", - "Neoapps didn't write this splash", - "Where's Kinger?", - "KayJann, Breakcore and code", - "Hey Goku!", - "Vegeta is a DZ mashallah", - "Bogos Binted? Vorp", - "YOU SHALL NOT PASS !", - "Bready, Steady, GO !", - "Not-so-Empty-house", - "We'll Meet Again", - "idk", - "wdym", - "Not making sense", - "Dw!", - "i forgor", - "Remember to be patient!", - "NOW'S YOUR CHANCE TO BE A.", - "BIG SHOT", - "A burning memory", - "FREE MONEY!", - "Can You Really Call This A Hotel. I didn't Reveive A Mint On My Pillow Or Anything", - "Try Indie Game", - "SHARK WITH LEGS!", - "it's a seal!", - "Shrimp.", - "Limited edition!", - "Fat free!", - "GOTY!", - "Water proof!", - "LALALA-LAVA", - "CHICHICHI-CHICKEN", - "Tasty ah hell", - "1% sugar!", - "150% hyperbole!", - "Hotter than the sun!", - "Woo, reddit!", - "piebot was here!", - "Legacy in an evolved manner.", - "neoapps is cool!", - "neoapps has put a self insert into this program!", - "Also try neoLegacy!", - "$20 is $20", - "no RenderDragon!", - "Iggy Jiggy", - "Arch, btw!", - "Bedrock bad!", - "Bedrock Linux not bad", - "Also try Terraria!", - "Also try LC Launcher!", - "Exclusively abandonware!", - "100% legal in all 3 states (of matter)", - "Herobrine has been confirmed we are fighting him please help", - "LGTM", - "git revert", - "We do not crypto mine on your computer.", - "100% legal!", - "Definitely not pirated", - "Long Live LCE!", - "For the community, by the community!", - "Not affiliated with Microslop", - "What is nether update", - "To the good old days", - "Also try : Minecraft Pocket Edition", -]; interface AudioControllerProps { musicVol: number; sfxVol: number; - showIntro: boolean; isGameRunning: boolean; isWindowVisible: boolean; } @@ -181,7 +26,6 @@ interface AudioControllerProps { export function useAudioController({ musicVol, sfxVol, - showIntro, isGameRunning, isWindowVisible, }: AudioControllerProps) { @@ -404,10 +248,18 @@ export function useAudioController({ setSplashIndex(newIndex); }, [playSplashSound, splashIndex]); + const [isMusicStarted, setIsMusicStarted] = useState(false); + + const startMusic = useCallback(() => { + setIsMusicStarted(true); + }, []); + useEffect(() => { - if (showIntro) return; + if (!isMusicStarted) return; const loadAndPlay = async () => { + await ensureAudioContextReady(); + let buffer = trackBuffersRef.current.get(currentTrack); if (!buffer) { buffer = await loadAudioBuffer(resolveAudioUrl(TRACKS[currentTrack])); @@ -416,35 +268,16 @@ export function useAudioController({ } } if (buffer) { - await playMusicBuffer(buffer); - } - }; - - loadAndPlay(); - - return () => { - stopMusic(); - }; - }, [showIntro, currentTrack, loadAudioBuffer, playMusicBuffer, stopMusic]); - - useEffect(() => { - if (!audioContextRef.current || showIntro) return; - - const loadAndPlay = async () => { - let buffer = trackBuffersRef.current.get(currentTrack); - if (!buffer) { - buffer = await loadAudioBuffer(resolveAudioUrl(TRACKS[currentTrack])); - if (buffer) { - trackBuffersRef.current.set(currentTrack, buffer); + if (currentTrack === 0) { + await playMusicBuffer(buffer); + } else { + await fadeInMusic(buffer, targetVolumeRef.current, 500); } } - if (buffer) { - await fadeInMusic(buffer, musicVol / 100, 500); - } }; loadAndPlay(); - }, [currentTrack, showIntro, musicVol, loadAudioBuffer, fadeInMusic]); + }, [isMusicStarted, currentTrack, loadAudioBuffer, playMusicBuffer, fadeInMusic, ensureAudioContextReady]); useEffect(() => { const shouldPause = isGameRunning || !isWindowVisible; @@ -517,5 +350,6 @@ export function useAudioController({ playSfx, tracks: TRACKS, splashes: SPLASHES, + startMusic, }; } diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 8f94ca9..e3eecd2 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -1,5 +1,6 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useMemo, useCallback } from "react"; import { motion, AnimatePresence, MotionConfig } from "framer-motion"; +import "../css/App.css"; import HomeView from "../components/views/HomeView"; import SettingsView from "../components/views/SettingsView"; import VersionsView from "../components/views/VersionsView"; @@ -20,6 +21,7 @@ import SkinViewer from "../components/common/SkinViewer"; import TeamModal from "../components/modals/TeamModal"; import PanoramaBackground from "../components/common/PanoramaBackground"; import { ClickParticles } from "../components/common/ClickParticles"; +import { CinematicIntro } from "../components/common/CinematicIntro"; import { DownloadOverlay } from "../components/layout/DownloadOverlay"; import { AchievementToast } from "../components/common/AchievementToast"; import { @@ -34,11 +36,10 @@ import { useLceLiveNotifications } from "../hooks/useLceLiveNotifications"; import type { Edition } from "../types/edition"; import pkg from "../../package.json"; export default function App() { + const ui = useUI(); const { showIntro, setShowIntro, - logoAnimDone, - setLogoAnimDone, activeView, setActiveView, isUiHidden, @@ -50,44 +51,44 @@ export default function App() { updateMessage, updateUrl, clearUpdateMessage, - } = useUI(); + connected, + } = ui; const config = useConfig(); const audio = useAudio(); const game = useGame(); - const { skinUrl, setSkinUrl, capeUrl } = useSkin(); + const skin = useSkin(); + const { skinUrl, setSkinUrl, capeUrl } = skin; + const notifications = useLceLiveNotifications(); const { friendRequestMessage, gameInviteMessage, clearFriendRequestMessage, clearGameInviteMessage, - } = useLceLiveNotifications(); - const [showSetup, setShowSetup] = useState(true); - const [displayIsDay, setDisplayIsDay] = useState(config.isDayTime); - useEffect(() => { - setDisplayIsDay(config.isDayTime); - }, [config.isDayTime]); + } = notifications; + const [showSetup, setShowSetup] = useState(false); + const [isSetupChecked, setIsSetupChecked] = useState(false); + const displayIsDay = config.isDayTime; - const selectedEdition = game.editions.find( - (e: Edition) => e.instanceId === config.profile, - ); - const selectedVersionName = selectedEdition?.name || ""; - const hasAnyInstall = game.installs.length > 0; - const titleImage = hasAnyInstall - ? selectedEdition?.titleImage || "/images/MenuTitle.png" - : "/images/MenuTitle.png"; + const clearError = useCallback(() => game.setError(null), [game]); + const clearGameUpdate = useCallback(() => game.setGameUpdateMessage(null), [game]); + const clearSteamSuccess = useCallback(() => game.setSteamSuccessMessage(null), [game]); + useEffect(() => { if (config.isLoaded) { - const setupCompleted = - localStorage.getItem("lce-setup-completed") === "true"; + const setupCompleted = localStorage.getItem("lce-setup-completed") === "true"; setShowSetup(!setupCompleted); + setIsSetupChecked(true); } }, [config.isLoaded]); - useEffect(() => { - setTimeout(() => setShowIntro(false), 2400); - setTimeout(() => setLogoAnimDone(true), 3400); - }, [showSetup]); + const selectedEdition = useMemo(() => + game.editions.find((e: Edition) => e.instanceId === config.profile), + [game.editions, config.profile] + ); + const selectedVersionName = selectedEdition?.name ?? ""; + const hasAnyInstall = game.installs.length > 0; + const titleImage = selectedEdition?.titleImage ?? "/images/MenuTitle.png"; useEffect(() => { const handleContextMenu = (e: MouseEvent) => e.preventDefault(); @@ -95,35 +96,56 @@ export default function App() { return () => document.removeEventListener("contextmenu", handleContextMenu); }, []); - const uiFade = { + const animDuration = config.animationsEnabled ? undefined : { duration: 0 }; + const uiFade = useMemo(() => ({ initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, - transition: { duration: config.animationsEnabled ? 0.5 : 0 }, - }; + transition: animDuration ?? { duration: 0.5 }, + }), [animDuration]); - const backgroundFade = { + const backgroundFade = useMemo(() => ({ initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, - transition: { duration: config.animationsEnabled ? 0.8 : 0 }, - }; + transition: animDuration ?? { duration: 0.8 }, + }), [animDuration]); + + if (!config.isLoaded || !isSetupChecked) { + return
; + } + + if (showSetup) { + return ( +
+ { + setShowSetup(false); + setShowIntro(true); + }} + /> +
+ ); + } + + if (showIntro) { + return ( +
+ { + setShowIntro(false); + }} + startMusic={audio.startMusic} + /> +
+ ); + } return (
-
@@ -139,6 +161,7 @@ export default function App() {
+ {config.vfxEnabled && } @@ -152,17 +175,15 @@ export default function App() { )} - - - + game.setError(null)} + onClose={clearError} /> game.setGameUpdateMessage(null)} + onClose={clearGameUpdate} onClick={() => { - game.setGameUpdateMessage(null); + clearGameUpdate(); setActiveView("versions"); }} title="Game Update Available!" @@ -191,280 +212,225 @@ export default function App() { game.setSteamSuccessMessage(null)} + onClose={clearSteamSuccess} title="Steam Integration" variant="steam" /> - - {showSetup ? ( - { - setShowSetup(false); - setShowIntro(true); - }} - /> - ) : showIntro ? ( + + {!config.legacyMode && ( - - - ) : ( - - - {logoAnimDone && ( - <> - {!config.legacyMode && ( - - - - )} - - {!config.legacyMode && ( - - - {displayIsDay ? "Day" : "Night"} - - - - )} - - {isUiHidden && - !displayIsDay && - activeView == "devtools" && ( - - - - )} - - )} - - -
-
- - - {logoAnimDone && ( - <> - -
- {audio.splashIndex === -1 - ? `Welcome ${config.username}!` - : audio.splashes[audio.splashIndex]} -
-
- {activeView === "main" && - hasAnyInstall && - titleImage === "/images/MenuTitle.png" && ( - - {selectedVersionName} - - )} - - )} -
-
-
- -
-
- - {activeView === "main" && ( - - )} - - -
- - {activeView === "main" && } - {activeView === "settings" && ( - - )} - {activeView === "versions" && ( - - )} - {activeView === "workshop" && ( - - )} - {activeView === "devtools" && ( - - )} - {activeView === "pck-editor" && ( - - )} - {activeView === "arc-editor" && ( - - )} - {activeView === "loc-editor" && ( - - )} - {activeView === "grf-editor" && ( - - )} - {activeView === "col-editor" && ( - - )} - {activeView === "options-editor" && ( - - )} - {activeView === "swf-editor" && ( - - )} - {activeView === "lcelive" && ( - - )} - {activeView === "skins" && } - {activeView === "screenshots" && ( - - )} - -
-
-
- - - {logoAnimDone && ( - -
- Version: {pkg.version} ({__BUILD_DATE__}) -
-
- Not affiliated with Mojang AB or Microsoft. "Minecraft" is - a trademark of Mojang Synergies AB. -
-
- {useUI().connected && "CONTROLLER CONNECTED"} -
-
- )} -
+
)} -
+ + {!config.legacyMode && ( + + + {displayIsDay ? "Day" : "Night"} + + + + )} + + {isUiHidden && !displayIsDay && activeView === "devtools" && ( + + + + )} + +
+
+ + +
+ {audio.splashIndex === -1 + ? `Welcome ${config.username}!` + : audio.splashes[audio.splashIndex]} +
+
+ {activeView === "main" && hasAnyInstall && titleImage === "/images/MenuTitle.png" && ( + + {selectedVersionName} + + )} +
+
+ +
+
+ + {activeView === "main" && ( + + )} + + +
+ + {activeView === "main" && } + {activeView === "settings" && ( + + )} + {activeView === "versions" && ( + + )} + {activeView === "workshop" && ( + + )} + {activeView === "devtools" && ( + + )} + {activeView === "pck-editor" && ( + + )} + {activeView === "arc-editor" && ( + + )} + {activeView === "loc-editor" && ( + + )} + {activeView === "grf-editor" && ( + + )} + {activeView === "col-editor" && ( + + )} + {activeView === "options-editor" && ( + + )} + {activeView === "swf-editor" && ( + + )} + {activeView === "lcelive" && ( + + )} + {activeView === "skins" && } + {activeView === "screenshots" && ( + + )} + +
+
+
+ + +
+ Version: {pkg.version} ({__BUILD_DATE__}) +
+
+ Not affiliated with Mojang AB or Microsoft. "Minecraft" is + a trademark of Mojang Synergies AB. +
+
+ {connected && "CONTROLLER CONNECTED"} +
+
+