Update #52 - Fixed various issues with the client

This commit is contained in:
lax1dude
2025-06-15 21:43:43 -07:00
parent 325a6826bf
commit f3281c037f
94 changed files with 882 additions and 506 deletions

View File

@ -24,6 +24,7 @@ import java.util.Date;
import org.teavm.interop.Import;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.browser.Storage;
@ -43,6 +44,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.buffer.MemoryStack;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.ClientMain;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.TeaVMUtils;
public class PlatformApplication {
@ -174,7 +176,23 @@ public class PlatformApplication {
}
}
public static final void displayFileChooser(String mime, String ext) {
public static void setResetSettingsCallbackWASM() {
setResetSettingsCallbackWASM0().call(ClientMain::resetSettings);
}
@JSFunctor
private static interface JSWASMResetSettingsCallback extends JSObject {
void callback();
}
private static interface JSWASMResetSettingsCallbackInterface extends JSObject {
void call(JSWASMResetSettingsCallback callback);
}
@Import(module = "platformApplication", name = "setResetSettingsCallback")
private static native JSWASMResetSettingsCallbackInterface setResetSettingsCallbackWASM0();
public static void displayFileChooser(String mime, String ext) {
displayFileChooser0(BetterJSStringConverter.stringToJS(mime), BetterJSStringConverter.stringToJS(ext));
}

View File

@ -94,11 +94,26 @@ public class PlatformAudio {
logger.error("OGG file support detected as false! Using embedded JOrbis OGG decoder");
}
}
if(((WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isKeepAliveHackTeaVM()) {
byte[] silenceFile = PlatformAssets.getResourceBytes("/assets/eagler/silence_loop.wav");
if (silenceFile != null) {
MemoryStack.push();
try {
initKeepAliveHack(WASMGCDirectArrayConverter.byteArrayToStackU8Array(silenceFile));
}finally {
MemoryStack.pop();
}
}
}
}
@Import(module = "platformAudio", name = "getContext")
private static native AudioContext getContext();
@Import(module = "platformAudio", name = "initKeepAliveHack")
private static native void initKeepAliveHack(Uint8Array array);
protected static class BrowserAudioResource implements IAudioResource {
protected AudioBuffer buffer;
@ -356,11 +371,8 @@ public class PlatformAudio {
return audioctx != null;
}
@JSBody(params = { "node" }, script = "node.distanceModel = \"linear\";")
static native void setDistanceModelLinearFast(PannerNode node) ;
@JSBody(params = { "node" }, script = "node.panningModel = \"HRTF\";")
static native void setPanningModelHRTFFast(PannerNode node) ;
@Import(module = "platformAudio", name = "setupPanner")
static native void setupPanner(PannerNode node, float maxDist, float x, float y, float z);
public static IAudioHandle beginPlayback(IAudioResource track, float x, float y, float z,
float volume, float pitch, boolean repeat) {
@ -373,17 +385,10 @@ public class PlatformAudio {
src.setLoop(repeat);
PannerNode panner = audioctx.createPanner();
panner.setPosition(x, y, z);
float v1 = volume * 16.0f;
if(v1 < 16.0f) v1 = 16.0f;
panner.setMaxDistance(v1);
panner.setRolloffFactor(1.0f);
setDistanceModelLinearFast(panner);
setPanningModelHRTFFast(panner);
panner.setConeInnerAngle(360.0f);
panner.setConeOuterAngle(0.0f);
panner.setConeOuterGain(0.0f);
panner.setOrientation(0.0f, 1.0f, 0.0f);
setupPanner(panner, v1, x, y, z);
GainNode gain = audioctx.createGain();
float v2 = volume;
@ -396,7 +401,7 @@ public class PlatformAudio {
if(gameRecGain != null) {
gain.connect(gameRecGain);
}
src.start();
BrowserAudioHandle ret = new BrowserAudioHandle(internalTrack, src, panner, gain, pitch, repeat);

View File

@ -113,6 +113,7 @@ public class PlatformInput {
static boolean vsync = true;
static boolean vsyncSupport = false;
static boolean finish = true;
private static Map<String, LegacyKeycodeTranslator.LegacyKeycode> keyCodeTranslatorMap = null;
@ -125,6 +126,7 @@ public class PlatformInput {
fullscreenSupported = supportsFullscreen0();
vsyncSupport = isVSyncSupported0();
WASMGCClientConfigAdapter conf = (WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter();
finish = conf.isFinishOnSwapTeaVM();
useVisualViewport = conf.isUseVisualViewportTeaVM() && isVisualViewport0();
lastWasResizedWindowWidth = -2;
lastWasResizedWindowHeight = -2;
@ -656,7 +658,7 @@ public class PlatformInput {
PlatformScreenRecord.captureFrameHook();
}
updatePlatformAndSleep(fpsLimit, vsync && vsyncSupport);// && vsync
updatePlatformAndSleep(fpsLimit, vsync && vsyncSupport, finish);
PlatformRuntime.pollJSEventsAfterSleep();
}
@ -664,7 +666,7 @@ public class PlatformInput {
private static native void updateCanvasSize(int width, int height);
@Import(module = "platformInput", name = "updatePlatformAndSleep")
private static native void updatePlatformAndSleep(int fpsLimit, boolean vsync);
private static native void updatePlatformAndSleep(int fpsLimit, boolean vsync, boolean finish);
public static boolean isVSyncSupported() {
return vsyncSupport;

View File

@ -79,6 +79,7 @@ public class PlatformRuntime {
canvas = getCanvasElement();
printMemoryStackAddrWASMGC();
PlatformApplication.setMCServerWindowGlobal(null);
PlatformApplication.setResetSettingsCallbackWASM();
PlatformOpenGL.initContext();
PlatformInput.initContext(win, parent, canvas);

View File

@ -215,16 +215,8 @@ public class PlatformVoiceClient {
audioNode.disconnect(gain);
}
panner = PlatformAudio.audioctx.createPanner();
panner.setRolloffFactor(1f);
PlatformAudio.setDistanceModelLinearFast(panner);
PlatformAudio.setPanningModelHRTFFast(panner);
panner.setConeInnerAngle(360f);
panner.setConeOuterAngle(0f);
panner.setConeOuterGain(0f);
panner.setOrientation(0f, 1f, 0f);
panner.setPosition(0, 0, 0);
float vol = VoiceClientController.getVoiceListenVolume();
panner.setMaxDistance(vol * 2 * VoiceClientController.getVoiceProximity() + 0.1f);
PlatformAudio.setupPanner(panner, vol * 2 * VoiceClientController.getVoiceProximity() + 0.1f, 0, 0, 0);
gain = PlatformAudio.audioctx.createGain();
gain.getGain().setValue(vol);
audioNode.connect(gain);

View File

@ -30,22 +30,26 @@ public class BetterJSStringConverter {
@Unmanaged
public static JSString stringToJS(String input) {
if (input == null) return null;
return (JSString) JS.wrap(input);
}
@Unmanaged
@SuppressWarnings("unchecked")
public static JSArray<JSString> stringArrayToJS(String[] input) {
if (input == null) return null;
return (JSArray<JSString>) JS.wrap(input);
}
@Unmanaged
public static String stringFromJS(JSString input) {
if (input == null) return null;
return JS.unwrapString(input);
}
@Unmanaged
public static String[] stringArrayFromJS(JSArray<JSString> input) {
if (input == null) return null;
return JS.unwrapStringArray(input);
}

View File

@ -24,6 +24,7 @@ import org.teavm.jso.browser.Window;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.ContextLostError;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsRoot;
import net.minecraft.client.main.Main;
@ -98,6 +99,25 @@ public class ClientMain {
}
}
/**
* Defined here to match the JS runtime
*/
public static void resetSettings() {
boolean y = false;
if (Window.confirm("Do you want to reset client settings?")) {
PlatformApplication.setLocalStorage("g", null);
PlatformApplication.setLocalStorage("p", null);
y = true;
}
if (Window.confirm("Do you want to reset servers and relays?")) {
PlatformApplication.setLocalStorage("r", null);
PlatformApplication.setLocalStorage("s", null);
y = true;
}
if (y) {
Window.alert("Settings reset.");
}
}
@Import(module = "platformRuntime", name = "getEaglercraftXOpts")
private static native JSObject getEaglerXOpts();

View File

@ -16,6 +16,7 @@
package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@ -24,16 +25,17 @@ import com.jcraft.jzlib.CRC32;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.InflaterInputStream;
import net.lax1dude.eaglercraft.v1_8.IOUtils;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.EaglerBufferInputStream;
public class EPKLoader {
public static final void loadEPK(ByteBuffer epkFile, Map<String, byte[]> loadedFiles) throws IOException {
public static void loadEPK(ByteBuffer epkFile, Map<String, byte[]> loadedFiles) throws IOException {
loadEPK(epkFile, "", loadedFiles);
}
public static final void loadEPK(ByteBuffer epkFile, String path, Map<String, byte[]> loadedFiles) throws IOException {
public static void loadEPK(ByteBuffer epkFile, String path, Map<String, byte[]> loadedFiles) throws IOException {
int byteLength = epkFile.remaining();
int l = byteLength - 16;
if(l < 1) {
@ -43,7 +45,7 @@ public class EPKLoader {
EaglerBufferInputStream is = new EaglerBufferInputStream(epkFile);
byte[] header = new byte[8];
is.read(header);
IOUtils.readFully(is, header);
String type = readASCII(header);
if(!"EAGPKG$$".equals(type)) {
@ -65,13 +67,13 @@ public class EPKLoader {
throw new IOException("Unknown or invalid EPK version: " + vers);
}
is.skip(is.read()); // skip filename
is.skip(loadShort(is)); // skip comment
is.skip(8); // skip millis date
IOUtils.skipFully(is, loadByte(is)); // skip filename
IOUtils.skipFully(is, loadShort(is)); // skip comment
IOUtils.skipFully(is, 8); // skip millis date
int numFiles = loadInt(is);
char compressionType = (char)is.read();
char compressionType = (char)loadByte(is);
InputStream zis;
switch(compressionType) {
@ -112,11 +114,11 @@ public class EPKLoader {
if(i == 0) {
if(blockType == blockHead) {
byte[] readType = new byte[len];
zis.read(readType);
IOUtils.readFully(zis, readType);
if(!"file-type".equals(name) || !"epk/resources".equals(readASCII(readType))) {
throw new IOException("EPK is not of file-type 'epk/resources'!");
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
continue;
@ -133,7 +135,7 @@ public class EPKLoader {
int expectedCRC = loadInt(zis);
byte[] load = new byte[len - 5];
zis.read(load);
IOUtils.readFully(zis, load);
if(len > 5) {
crc32.reset();
@ -143,16 +145,16 @@ public class EPKLoader {
}
}
if(zis.read() != ':') {
if(loadByte(zis) != ':') {
throw new IOException("File '" + name + "' is incomplete");
}
loadedFiles.put(path + name, load);
}else {
zis.skip(len);
IOUtils.skipFully(zis, len);
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
}
@ -163,28 +165,36 @@ public class EPKLoader {
zis.close();
}
private static final int loadShort(InputStream is) throws IOException {
return (is.read() << 8) | is.read();
private static int loadByte(InputStream is) throws IOException {
int i = is.read();
if (i < 0) {
throw new EOFException();
}
return i;
}
private static final int loadInt(InputStream is) throws IOException {
return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | is.read();
private static int loadShort(InputStream is) throws IOException {
return (loadByte(is) << 8) | loadByte(is);
}
private static final String readASCII(byte[] bytesIn) throws IOException {
private static int loadInt(InputStream is) throws IOException {
return (loadByte(is) << 24) | (loadByte(is) << 16) | (loadByte(is) << 8) | loadByte(is);
}
private static String readASCII(byte[] bytesIn) throws IOException {
char[] charIn = new char[bytesIn.length];
for(int i = 0; i < bytesIn.length; ++i) {
charIn[i] = (char)((int)bytesIn[i] & 0xFF);
}
return new String(charIn);
}
private static final String readASCII(InputStream bytesIn) throws IOException {
int len = bytesIn.read();
private static String readASCII(InputStream bytesIn) throws IOException {
int len = loadByte(bytesIn);
char[] charIn = new char[len];
for(int i = 0; i < len; ++i) {
charIn[i] = (char)(bytesIn.read() & 0xFF);
charIn[i] = (char)loadByte(bytesIn);
}
return new String(charIn);
}

View File

@ -82,6 +82,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
private boolean ramdiskMode = false;
private boolean singleThreadMode = false;
private boolean enforceVSync = true;
private boolean keepAliveHack = true;
private boolean finishOnSwap = true;
public void loadNative(JSObject jsObject) {
JSEaglercraftXOptsRoot eaglercraftXOpts = (JSEaglercraftXOptsRoot)jsObject;
@ -124,6 +126,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
ramdiskMode = eaglercraftXOpts.getRamdiskMode(false);
singleThreadMode = eaglercraftXOpts.getSingleThreadMode(false);
enforceVSync = eaglercraftXOpts.getEnforceVSync(true);
keepAliveHack = eaglercraftXOpts.getKeepAliveHack(true);
finishOnSwap = eaglercraftXOpts.getFinishOnSwap(true);
JSEaglercraftXOptsHooks hooksObj = eaglercraftXOpts.getHooks();
if(hooksObj != null) {
hooks.loadHooks(hooksObj);
@ -400,6 +404,14 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
return enforceVSync;
}
public boolean isKeepAliveHackTeaVM() {
return keepAliveHack;
}
public boolean isFinishOnSwapTeaVM() {
return finishOnSwap;
}
@Override
public IClientConfigAdapterHooks getHooks() {
return hooks;
@ -446,6 +458,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
jsonObject.put("ramdiskMode", ramdiskMode);
jsonObject.put("singleThreadMode", singleThreadMode);
jsonObject.put("enforceVSync", enforceVSync);
jsonObject.put("keepAliveHack", keepAliveHack);
jsonObject.put("finishOnSwap", finishOnSwap);
JSONArray serversArr = new JSONArray();
for(int i = 0, l = defaultServers.size(); i < l; ++i) {
DefaultServer srv = defaultServers.get(i);

View File

@ -114,10 +114,14 @@ public class WASMGCClientConfigAdapterHooks implements IClientConfigAdapterHooks
}
public void loadHooks(JSEaglercraftXOptsHooks hooks) {
saveHook = (LocalStorageSaveHook)hooks.getLocalStorageSavedHook();
loadHook = (LocalStorageLoadHook)hooks.getLocalStorageLoadedHook();
crashHook = (CrashReportHook)hooks.getCrashReportHook();
screenChangedHook = (ScreenChangeHook)hooks.getScreenChangedHook();
JSObject obj = hooks.getLocalStorageSavedHook();
saveHook = obj != null ? (LocalStorageSaveHook) obj : null;
obj = hooks.getLocalStorageLoadedHook();
loadHook = obj != null ? (LocalStorageLoadHook) obj : null;
obj = hooks.getCrashReportHook();
crashHook = obj != null ? (CrashReportHook) obj : null;
obj = hooks.getScreenChangedHook();
screenChangedHook = obj != null ? (ScreenChangeHook) obj : null;
}
}

View File

@ -172,4 +172,10 @@ public interface JSEaglercraftXOptsRoot extends JSObject {
@JSBody(params = { "def" }, script = "return (typeof this.enableEPKVersionCheck === \"boolean\") ? this.enableEPKVersionCheck : def;")
boolean getEnableEPKVersionCheck(boolean deobfStackTraces);
@JSBody(params = { "def" }, script = "return (typeof this.keepAliveHack === \"boolean\") ? this.keepAliveHack : def;")
boolean getKeepAliveHack(boolean keepAliveHack);
@JSBody(params = { "def" }, script = "return (typeof this.finishOnSwap === \"boolean\") ? this.finishOnSwap : def;")
boolean getFinishOnSwap(boolean finishOnSwap);
}