mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-07-25 18:47:10 +00:00
feat: game options editor
This commit is contained in:
parent
3f4a776600
commit
4a0a329f6b
BIN
public/images/tools/options.png
Normal file
BIN
public/images/tools/options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -14,7 +14,8 @@ const DEV_TOOLS: DevTool[] = [
|
|||
{ id: "arc", name: "ARC Editor", view: "arc-editor", comingSoon: false },
|
||||
{ id: "loc", name: "LOC Editor", view: "loc-editor", comingSoon: false },
|
||||
{ id: "grf", name: "GRF Editor", view: "grf-editor", comingSoon: false },
|
||||
{ id: "col", name: "COL Editor", view: "col-editor", comingSoon: false }
|
||||
{ id: "col", name: "COL Editor", view: "col-editor", comingSoon: false },
|
||||
{ id: "options", name: "Options Editor", view: "options-editor", comingSoon: false }
|
||||
];
|
||||
|
||||
export default function DevtoolsView() {
|
||||
|
|
|
|||
241
src/components/views/OptionsEditorView.tsx
Normal file
241
src/components/views/OptionsEditorView.tsx
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
import { useState, useRef } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useUI, useAudio, useConfig } from "../../context/LauncherContext";
|
||||
import { OptionsService } from "../../services/OptionsService";
|
||||
import { OptionsFile } from "../../types/options";
|
||||
const BUTTONS: Record<number, string> = {
|
||||
0x00: "NONE", 0x01: "A", 0x02: "B", 0x03: "X", 0x04: "Y",
|
||||
0x05: "D-Pad Left", 0x06: "D-Pad Right", 0x07: "D-Pad Up", 0x08: "D-Pad Down",
|
||||
0x09: "RB", 0x0A: "LB", 0x0B: "RT", 0x0C: "LT",
|
||||
0x0D: "RS", 0x0E: "LS"
|
||||
};
|
||||
|
||||
export default function OptionsEditorView() {
|
||||
const { setActiveView } = useUI();
|
||||
const { playPressSound, playBackSound } = useAudio();
|
||||
const { animationsEnabled } = useConfig();
|
||||
const [opt, setOpt] = useState<OptionsFile | null>(null);
|
||||
const [notification, setNotification] = useState<{ message: string, type: "success" | "error" } | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [activeTab, setActiveTab] = useState<"settings" | "skins" | "actions">("settings");
|
||||
const showNotification = (message: string, type: "success" | "error" = "success") => {
|
||||
setNotification({ message, type });
|
||||
setTimeout(() => setNotification(null), 3000);
|
||||
};
|
||||
|
||||
const handleFileLoad = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
playPressSound();
|
||||
const buffer = await file.arrayBuffer();
|
||||
try {
|
||||
const parsed = OptionsService.readOptions(buffer);
|
||||
setOpt(parsed);
|
||||
showNotification(`Loaded options.dat`);
|
||||
} catch (err: any) {
|
||||
console.error("Failed to parse Options", err);
|
||||
showNotification(err.message || "Failed to parse Options", "error");
|
||||
}
|
||||
e.target.value = "";
|
||||
};
|
||||
|
||||
const handleSaveOptions = () => {
|
||||
if (!opt) return;
|
||||
playPressSound();
|
||||
try {
|
||||
const buffer = OptionsService.serializeOptions(opt);
|
||||
const blob = new Blob([buffer]);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "options.dat";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
showNotification("Options Saved");
|
||||
} catch (err: any) {
|
||||
showNotification("Failed to save", "error");
|
||||
}
|
||||
};
|
||||
|
||||
const updateSetting = (field: keyof OptionsFile, value: any) => {
|
||||
if (!opt) return;
|
||||
setOpt({ ...opt, [field]: value });
|
||||
};
|
||||
|
||||
const updateAction = (field: keyof OptionsFile["actions"], value: number) => {
|
||||
if (!opt) return;
|
||||
setOpt({ ...opt, actions: { ...opt.actions, [field]: value } });
|
||||
};
|
||||
|
||||
const updateFavSkin = (idx: number, value: number) => {
|
||||
if (!opt) return;
|
||||
const newFav = [...opt.favoriteSkins];
|
||||
newFav[idx] = value;
|
||||
setOpt({ ...opt, favoriteSkins: newFav });
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ duration: animationsEnabled ? 0.3 : 0 }}
|
||||
className="flex flex-col w-full h-[85vh] max-w-7xl relative"
|
||||
>
|
||||
<input type="file" ref={fileInputRef} onChange={handleFileLoad} className="hidden" accept=".dat,.bin" />
|
||||
<div className="flex items-center justify-between mb-6 px-4">
|
||||
<div className="flex items-center gap-6">
|
||||
<h2 className="text-3xl text-white mc-text-shadow tracking-widest uppercase font-bold">Options Editor</h2>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="px-6 py-2 text-white mc-text-shadow text-lg"
|
||||
style={{ backgroundImage: "url('/images/Button_Background.png')", backgroundSize: "100% 100%" }}
|
||||
>
|
||||
Open settings.dat
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSaveOptions}
|
||||
disabled={!opt}
|
||||
className={`px-6 py-2 text-white mc-text-shadow text-lg ${!opt ? "opacity-50 grayscale" : ""}`}
|
||||
style={{ backgroundImage: "url('/images/Button_Background.png')", backgroundSize: "100% 100%" }}
|
||||
>
|
||||
Save settings.dat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!opt ? (
|
||||
<div className="flex-1 w-full flex flex-col items-center justify-center p-12"
|
||||
style={{ backgroundImage: "url('/images/frame_background.png')", backgroundSize: "100% 100%", imageRendering: "pixelated" }}>
|
||||
<h3 className="text-2xl text-white/40 mc-text-shadow italic">Open settings.dat to begin</h3>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 w-full flex flex-col overflow-hidden" style={{ backgroundImage: "url('/images/frame_background.png')", backgroundSize: "100% 100%", imageRendering: "pixelated" }}>
|
||||
<div className="flex gap-1 p-2 pt-4 border-b-2 border-[#373737]">
|
||||
{(["settings", "skins", "actions"] as const).map(tab => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => { playPressSound(); setActiveTab(tab); }}
|
||||
className={`flex items-center gap-3 px-6 py-2 transition-all mc-text-shadow ${activeTab === tab ? "text-[#FFFF55] opacity-100 scale-105" : "text-white opacity-40 hover:opacity-100"}`}
|
||||
>
|
||||
<span className="text-lg capitalize">{tab}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col p-6 overflow-y-auto custom-scrollbar">
|
||||
{activeTab === "settings" && (
|
||||
<div className="grid grid-cols-2 gap-x-12 gap-y-6">
|
||||
{Object.keys(opt).filter(k => k !== "actions" && k !== "rawData" && k !== "chosenSkin" && k !== "playerCape" && k !== "favoriteSkins" && k !== "endianness").map((k) => {
|
||||
const val = opt[k as keyof OptionsFile];
|
||||
if (typeof val === "boolean") {
|
||||
return (
|
||||
<label key={k} className="flex items-center justify-between cursor-pointer group">
|
||||
<span className="text-white/80 uppercase text-sm tracking-widest">{k.replace(/([A-Z])/g, ' $1').trim()}</span>
|
||||
<div className={`w-12 h-6 border-2 transition-colors ${val ? "bg-[#55FF55] border-[#55FF55]" : "bg-black/40 border-[#373737]"}`} onClick={() => { playPressSound(); updateSetting(k as keyof OptionsFile, !val); }} />
|
||||
</label>
|
||||
);
|
||||
} else if (typeof val === "number") {
|
||||
return (
|
||||
<div key={k} className="flex flex-col gap-2">
|
||||
<span className="text-white/80 uppercase text-sm tracking-widest">{k.replace(/([A-Z])/g, ' $1').trim()}: {val}</span>
|
||||
<input
|
||||
type="range" min="0" max={k.includes("Size") || k === "difficulty" ? 3 : 255}
|
||||
value={val}
|
||||
onChange={(e) => updateSetting(k as keyof OptionsFile, parseInt(e.target.value))}
|
||||
className="w-full accent-[#FFFF55]"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "skins" && (
|
||||
<div className="flex flex-col gap-8 max-w-xl mx-auto">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-white/80 uppercase tracking-widest">Chosen Skin ID</span>
|
||||
<input
|
||||
type="number" value={opt.chosenSkin} onChange={(e) => updateSetting("chosenSkin", parseInt(e.target.value) || 0)}
|
||||
className="bg-black/40 border border-[#373737] p-2 text-white outline-none focus:border-[#FFFF55] font-mono text-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-white/80 uppercase tracking-widest">Player Cape ID</span>
|
||||
<input
|
||||
type="number" value={opt.playerCape} onChange={(e) => updateSetting("playerCape", parseInt(e.target.value) || 0)}
|
||||
className="bg-black/40 border border-[#373737] p-2 text-white outline-none focus:border-[#FFFF55] font-mono text-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-white/80 uppercase tracking-widest">Favorite Skins</span>
|
||||
{opt.favoriteSkins.map((s, i) => (
|
||||
<div key={i} className="flex items-center gap-4">
|
||||
<span className="text-white/40 w-8">{i + 1}.</span>
|
||||
<input
|
||||
type="number" value={s} onChange={(e) => updateFavSkin(i, parseInt(e.target.value) || 0)}
|
||||
className="bg-black/40 flex-1 border border-[#373737] p-2 text-white outline-none focus:border-[#FFFF55] font-mono"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "actions" && (
|
||||
<div className="grid grid-cols-2 gap-x-12 gap-y-4">
|
||||
{Object.keys(opt.actions).map((k) => {
|
||||
const val = opt.actions[k as keyof typeof opt.actions];
|
||||
return (
|
||||
<div key={k} className="flex justify-between items-center group">
|
||||
<span className="text-white/80 uppercase text-sm tracking-widest">{k.replace(/([A-Z])/g, ' $1').trim()}</span>
|
||||
<select
|
||||
value={val}
|
||||
onChange={(e) => updateAction(k as keyof typeof opt.actions, parseInt(e.target.value))}
|
||||
className="bg-black/40 border border-[#373737] p-2 text-white outline-none focus:border-[#FFFF55] min-w-[120px] cursor-pointer"
|
||||
>
|
||||
{Object.entries(BUTTONS).map(([id, name]) => (
|
||||
<option key={id} value={id}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-center mt-6 h-14">
|
||||
<button
|
||||
onClick={() => { playBackSound(); setActiveView("devtools"); }}
|
||||
className="w-72 h-full flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none hover:text-[#FFFF55] text-white"
|
||||
style={{ backgroundImage: "url('/images/Button_Background.png')", backgroundSize: "100% 100%", imageRendering: "pixelated" }}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{notification && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -50, scale: 0.9 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: -50, scale: 0.9 }}
|
||||
className="fixed top-12 right-12 z-[100] p-6 flex flex-col items-center justify-center min-w-[240px]"
|
||||
style={{ backgroundImage: "url('/images/frame_background.png')", backgroundSize: "100% 100%", imageRendering: "pixelated" }}
|
||||
>
|
||||
<span className="text-white text-lg mc-text-shadow font-bold tracking-widest uppercase">
|
||||
{notification.message}
|
||||
</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import { ArcEditorView } from "../components/views/ArcEditorView";
|
|||
import LocEditorView from "../components/views/LocEditorView";
|
||||
import GrfEditorView from "../components/views/GrfEditorView";
|
||||
import ColEditorView from "../components/views/ColEditorView";
|
||||
import OptionsEditorView from "../components/views/OptionsEditorView";
|
||||
import ScreenshotsView from "../components/views/ScreenshotsView";
|
||||
import SkinViewer from "../components/common/SkinViewer";
|
||||
import TeamModal from "../components/modals/TeamModal";
|
||||
|
|
@ -360,6 +361,9 @@ export default function App() {
|
|||
{activeView === "col-editor" && (
|
||||
<ColEditorView key="col-editor-view" />
|
||||
)}
|
||||
{activeView === "options-editor" && (
|
||||
<OptionsEditorView key="options-editor-view" />
|
||||
)}
|
||||
{activeView === "skins" && <SkinsView key="skins-view" />}
|
||||
{activeView === "screenshots" && (
|
||||
<ScreenshotsView key="screenshots-view" />
|
||||
|
|
|
|||
167
src/services/OptionsService.ts
Normal file
167
src/services/OptionsService.ts
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import { OptionsFile } from "../types/options";
|
||||
|
||||
export class OptionsService {
|
||||
public static readOptions(buffer: ArrayBuffer, endianness: "little" | "big" = "little"): OptionsFile {
|
||||
const rawData = new Uint8Array(buffer).slice();
|
||||
const view = new DataView(buffer);
|
||||
const little = endianness === "little";
|
||||
|
||||
const opt: Partial<OptionsFile> = {
|
||||
endianness,
|
||||
rawData
|
||||
};
|
||||
|
||||
opt.musicVolume = view.getUint8(0x01);
|
||||
opt.soundVolume = view.getUint8(0x02);
|
||||
opt.gameSensitivity = view.getUint8(0x03);
|
||||
opt.gamma = view.getUint8(0x04);
|
||||
opt.interfaceSensitivity = view.getUint8(0x50);
|
||||
opt.interfaceOpacity = view.getUint8(0x51);
|
||||
const val06 = view.getUint16(0x06, little);
|
||||
opt.difficulty = val06 & 0x3;
|
||||
opt.viewBobbing = (val06 & (1 << 2)) !== 0;
|
||||
opt.inGameGamertags = (val06 & (1 << 3)) !== 0;
|
||||
opt.invertLook = (val06 & (1 << 6)) !== 0;
|
||||
opt.southpaw = (val06 & (1 << 7)) !== 0;
|
||||
opt.verticalSplitscreen = (val06 & (1 << 8)) !== 0;
|
||||
opt.splitscreenGamertags = (val06 & (1 << 9)) !== 0;
|
||||
opt.hints = (val06 & (1 << 10)) !== 0;
|
||||
opt.autosaveTimer = (val06 >> 11) & 0xF;
|
||||
opt.inGameTooltips = (val06 & (1 << 15)) !== 0;
|
||||
const val54 = view.getUint32(0x54, little);
|
||||
opt.renderClouds = (val54 & (1 << 0)) !== 0;
|
||||
opt.displayHud = (val54 & (1 << 7)) !== 0;
|
||||
opt.displayHand = (val54 & (1 << 8)) !== 0;
|
||||
opt.customSkinAnimation = (val54 & (1 << 9)) !== 0;
|
||||
opt.deathMessages = (val54 & (1 << 10)) !== 0;
|
||||
opt.hudSize = (val54 >> 11) & 0x3;
|
||||
opt.hudSizeSplitscreen = (val54 >> 13) & 0x3;
|
||||
opt.animatedCharacter = (val54 & (1 << 15)) !== 0;
|
||||
opt.classicCrafting = (val54 & (1 << 18)) !== 0;
|
||||
opt.caveSounds = (val54 & (1 << 19)) !== 0;
|
||||
opt.gameChat = (val54 & (1 << 20)) !== 0;
|
||||
opt.minecartSounds = (val54 & (1 << 21)) !== 0;
|
||||
opt.showGlideGhost = (val54 & (1 << 22)) !== 0;
|
||||
opt.autoJump = (val54 & (1 << 26)) !== 0;
|
||||
opt.displayGameMessages = (val54 & (1 << 28)) !== 0;
|
||||
opt.displaySaveIcon = (val54 & (1 << 29)) !== 0;
|
||||
opt.flyingViewRolling = (val54 & (1 << 30)) === 0;
|
||||
opt.showGlideGhostPath = (val54 & (1 << 31)) !== 0;
|
||||
opt.chosenSkin = view.getUint32(0x4C, little);
|
||||
opt.playerCape = view.getUint32(0x5C, little);
|
||||
opt.favoriteSkins = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
opt.favoriteSkins.push(view.getUint32(0x60 + i * 4, little));
|
||||
}
|
||||
|
||||
opt.actions = {
|
||||
jump: view.getUint8(0xA4),
|
||||
use: view.getUint8(0xA5),
|
||||
action: view.getUint8(0xA6),
|
||||
cycleHeldItemLeft: view.getUint8(0xA7),
|
||||
cycleHeldItemRight: view.getUint8(0xA8),
|
||||
inventory: view.getUint8(0xA9),
|
||||
drop: view.getUint8(0xAA),
|
||||
sneakDismount: view.getUint8(0xAB),
|
||||
crafting: view.getUint8(0xAC),
|
||||
changeCameraMode: view.getUint8(0xAD),
|
||||
flyLeft: view.getUint8(0xAE),
|
||||
flyRight: view.getUint8(0xAF),
|
||||
flyUp: view.getUint8(0xB0),
|
||||
flyDown: view.getUint8(0xB1),
|
||||
sprint: view.getUint8(0xB2),
|
||||
pickBlock: view.getUint8(0xB3),
|
||||
previousPlayer: view.getUint8(0xB4),
|
||||
nextPlayer: view.getUint8(0xB5),
|
||||
spectateNoise: view.getUint8(0xB6),
|
||||
cancelSpectating: view.getUint8(0xB7),
|
||||
confirmReady: view.getUint8(0xB8),
|
||||
vote: view.getUint8(0xB9),
|
||||
restartSection: view.getUint8(0xBA),
|
||||
restartRace: view.getUint8(0xBB),
|
||||
lookBehind: view.getUint8(0xBC)
|
||||
};
|
||||
|
||||
return opt as OptionsFile;
|
||||
}
|
||||
|
||||
public static serializeOptions(opt: OptionsFile): ArrayBuffer {
|
||||
const minSize = 0xBC + 1;
|
||||
const buffer = new Uint8Array(Math.max(opt.rawData.length, minSize));
|
||||
buffer.set(opt.rawData);
|
||||
const view = new DataView(buffer.buffer);
|
||||
const little = opt.endianness === "little";
|
||||
view.setUint8(0x01, opt.musicVolume);
|
||||
view.setUint8(0x02, opt.soundVolume);
|
||||
view.setUint8(0x03, opt.gameSensitivity);
|
||||
view.setUint8(0x04, opt.gamma);
|
||||
view.setUint8(0x50, opt.interfaceSensitivity);
|
||||
view.setUint8(0x51, opt.interfaceOpacity);
|
||||
let val06 = view.getUint16(0x06, little);
|
||||
val06 = (val06 & ~0x3) | (opt.difficulty & 0x3);
|
||||
val06 = (val06 & ~(1 << 2)) | (opt.viewBobbing ? (1 << 2) : 0);
|
||||
val06 = (val06 & ~(1 << 3)) | (opt.inGameGamertags ? (1 << 3) : 0);
|
||||
val06 = (val06 & ~(1 << 6)) | (opt.invertLook ? (1 << 6) : 0);
|
||||
val06 = (val06 & ~(1 << 7)) | (opt.southpaw ? (1 << 7) : 0);
|
||||
val06 = (val06 & ~(1 << 8)) | (opt.verticalSplitscreen ? (1 << 8) : 0);
|
||||
val06 = (val06 & ~(1 << 9)) | (opt.splitscreenGamertags ? (1 << 9) : 0);
|
||||
val06 = (val06 & ~(1 << 10)) | (opt.hints ? (1 << 10) : 0);
|
||||
val06 = (val06 & ~(0xF << 11)) | ((opt.autosaveTimer & 0xF) << 11);
|
||||
val06 = (val06 & ~(1 << 15)) | (opt.inGameTooltips ? (1 << 15) : 0);
|
||||
view.setUint16(0x06, val06, little);
|
||||
let val54 = view.getUint32(0x54, little);
|
||||
val54 = (val54 & ~(1 << 0)) | (opt.renderClouds ? (1 << 0) : 0);
|
||||
val54 = (val54 & ~(1 << 7)) | (opt.displayHud ? (1 << 7) : 0);
|
||||
val54 = (val54 & ~(1 << 8)) | (opt.displayHand ? (1 << 8) : 0);
|
||||
val54 = (val54 & ~(1 << 9)) | (opt.customSkinAnimation ? (1 << 9) : 0);
|
||||
val54 = (val54 & ~(1 << 10)) | (opt.deathMessages ? (1 << 10) : 0);
|
||||
val54 = (val54 & ~(0x3 << 11)) | ((opt.hudSize & 0x3) << 11);
|
||||
val54 = (val54 & ~(0x3 << 13)) | ((opt.hudSizeSplitscreen & 0x3) << 13);
|
||||
val54 = (val54 & ~(1 << 15)) | (opt.animatedCharacter ? (1 << 15) : 0);
|
||||
val54 = (val54 & ~(1 << 18)) | (opt.classicCrafting ? (1 << 18) : 0);
|
||||
val54 = (val54 & ~(1 << 19)) | (opt.caveSounds ? (1 << 19) : 0);
|
||||
val54 = (val54 & ~(1 << 20)) | (opt.gameChat ? (1 << 20) : 0);
|
||||
val54 = (val54 & ~(1 << 21)) | (opt.minecartSounds ? (1 << 21) : 0);
|
||||
val54 = (val54 & ~(1 << 22)) | (opt.showGlideGhost ? (1 << 22) : 0);
|
||||
val54 = (val54 & ~(1 << 26)) | (opt.autoJump ? (1 << 26) : 0);
|
||||
val54 = (val54 & ~(1 << 28)) | (opt.displayGameMessages ? (1 << 28) : 0);
|
||||
val54 = (val54 & ~(1 << 29)) | (opt.displaySaveIcon ? (1 << 29) : 0);
|
||||
val54 = (val54 & ~(1 << 30)) | (!opt.flyingViewRolling ? (1 << 30) : 0);
|
||||
val54 = (val54 & ~(1 << 31)) | (opt.showGlideGhostPath ? (1 << 31) : 0);
|
||||
view.setUint32(0x54, val54, little);
|
||||
view.setUint32(0x4C, opt.chosenSkin, little);
|
||||
view.setUint32(0x5C, opt.playerCape, little);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
if (opt.favoriteSkins[i] !== undefined) {
|
||||
view.setUint32(0x60 + i * 4, opt.favoriteSkins[i], little);
|
||||
}
|
||||
}
|
||||
|
||||
view.setUint8(0xA4, opt.actions.jump);
|
||||
view.setUint8(0xA5, opt.actions.use);
|
||||
view.setUint8(0xA6, opt.actions.action);
|
||||
view.setUint8(0xA7, opt.actions.cycleHeldItemLeft);
|
||||
view.setUint8(0xA8, opt.actions.cycleHeldItemRight);
|
||||
view.setUint8(0xA9, opt.actions.inventory);
|
||||
view.setUint8(0xAA, opt.actions.drop);
|
||||
view.setUint8(0xAB, opt.actions.sneakDismount);
|
||||
view.setUint8(0xAC, opt.actions.crafting);
|
||||
view.setUint8(0xAD, opt.actions.changeCameraMode);
|
||||
view.setUint8(0xAE, opt.actions.flyLeft);
|
||||
view.setUint8(0xAF, opt.actions.flyRight);
|
||||
view.setUint8(0xB0, opt.actions.flyUp);
|
||||
view.setUint8(0xB1, opt.actions.flyDown);
|
||||
view.setUint8(0xB2, opt.actions.sprint);
|
||||
view.setUint8(0xB3, opt.actions.pickBlock);
|
||||
view.setUint8(0xB4, opt.actions.previousPlayer);
|
||||
view.setUint8(0xB5, opt.actions.nextPlayer);
|
||||
view.setUint8(0xB6, opt.actions.spectateNoise);
|
||||
view.setUint8(0xB7, opt.actions.cancelSpectating);
|
||||
view.setUint8(0xB8, opt.actions.confirmReady);
|
||||
view.setUint8(0xB9, opt.actions.vote);
|
||||
view.setUint8(0xBA, opt.actions.restartSection);
|
||||
view.setUint8(0xBB, opt.actions.restartRace);
|
||||
view.setUint8(0xBC, opt.actions.lookBehind);
|
||||
return buffer.buffer;
|
||||
}
|
||||
}
|
||||
69
src/types/options.ts
Normal file
69
src/types/options.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
export interface OptionsFile {
|
||||
endianness: "little" | "big";
|
||||
musicVolume: number;
|
||||
soundVolume: number;
|
||||
gamma: number;
|
||||
gameSensitivity: number;
|
||||
interfaceSensitivity: number;
|
||||
interfaceOpacity: number;
|
||||
difficulty: number;
|
||||
viewBobbing: boolean;
|
||||
invertLook: boolean;
|
||||
southpaw: boolean;
|
||||
verticalSplitscreen: boolean;
|
||||
inGameGamertags: boolean;
|
||||
autosaveTimer: number;
|
||||
splitscreenGamertags: boolean;
|
||||
hints: boolean;
|
||||
inGameTooltips: boolean;
|
||||
renderClouds: boolean;
|
||||
displayHud: boolean;
|
||||
displayHand: boolean;
|
||||
customSkinAnimation: boolean;
|
||||
deathMessages: boolean;
|
||||
hudSize: number;
|
||||
hudSizeSplitscreen: number;
|
||||
animatedCharacter: boolean;
|
||||
classicCrafting: boolean;
|
||||
caveSounds: boolean;
|
||||
gameChat: boolean;
|
||||
minecartSounds: boolean;
|
||||
displayGameMessages: boolean;
|
||||
displaySaveIcon: boolean;
|
||||
flyingViewRolling: boolean;
|
||||
showGlideGhost: boolean;
|
||||
showGlideGhostPath: boolean;
|
||||
autoJump: boolean;
|
||||
chosenSkin: number;
|
||||
playerCape: number;
|
||||
favoriteSkins: number[];
|
||||
actions: {
|
||||
jump: number;
|
||||
use: number;
|
||||
action: number;
|
||||
cycleHeldItemLeft: number;
|
||||
cycleHeldItemRight: number;
|
||||
inventory: number;
|
||||
drop: number;
|
||||
sneakDismount: number;
|
||||
crafting: number;
|
||||
changeCameraMode: number;
|
||||
flyLeft: number;
|
||||
flyRight: number;
|
||||
flyUp: number;
|
||||
flyDown: number;
|
||||
sprint: number;
|
||||
pickBlock: number;
|
||||
previousPlayer: number;
|
||||
nextPlayer: number;
|
||||
spectateNoise: number;
|
||||
cancelSpectating: number;
|
||||
confirmReady: number;
|
||||
vote: number;
|
||||
restartSection: number;
|
||||
restartRace: number;
|
||||
lookBehind: number;
|
||||
};
|
||||
|
||||
rawData: Uint8Array;
|
||||
}
|
||||
Loading…
Reference in a new issue