diff --git a/CREDITS b/CREDITS index f4bfcb9..7f9c384 100644 --- a/CREDITS +++ b/CREDITS @@ -11,13 +11,15 @@ - Made the integrated PBR resource pack - Wrote all desktop emulation code - Wrote EaglercraftXBungee - - Wrote WebRTC Relay Server + - Wrote WebRTC relay server + - Wrote voice chat server - Wrote the patch and build system ayunami2000: - Many bug fixes - WebRTC LAN worlds + - WebRTC voice chat - Added resource packs - Added screen recording - Added seamless fullscreen diff --git a/README.md b/README.md index d532e8f..89d7447 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,11 @@ If you would like to host your own relay, the JAR file and instructions can be d ## PBR Shaders -EaglercraftX 1.8 includes a deferred physically-based renderer modeled after the GTA V rendering engine with many new improvements and a novel raytracing technique for fast realistic reflections. It can be enabled in the "Shaders" menu in the game's options screen. Shader packs in EaglercraftX are just a component of resource packs, so any custom shaders you install will be in the form of a resource pack. EaglercraftX also comes with a very well optimized built-in PBR shader pack and also a concise built-in PBR material texture pack to give all blocks and items in the game realistic lighting and materials that looks better than most vanilla Minecraft shader packs. The default shader and texture packs were created from scratch by lax1dude, shaders packs made for vanilla Minecraft will not work in EaglercraftX and no shaders in EaglercraftX were taken from vanilla Minecraft shader packs. +EaglercraftX 1.8 includes a deferred physically-based renderer modeled after the GTA V rendering engine with many new improvements and a novel raytracing technique for fast realistic reflections. It can be enabled in the "Shaders" menu in the game's options screen. Shader packs in EaglercraftX are just a component of resource packs, so any custom shaders you install will be in the form of a resource pack. EaglercraftX also comes with a very well optimized built-in PBR shader pack and also a built-in PBR material texture pack to give all blocks and items in the game realistic lighting and materials that looks better than most vanilla Minecraft shader packs. The default shader and texture packs were created from scratch by lax1dude, shaders packs made for vanilla Minecraft will not work in EaglercraftX and no shaders in EaglercraftX were taken from vanilla Minecraft shader packs. + +## Voice Chat + +EaglercraftX 1.8 includes an integrated voice-chat service that can be used in shared worlds and also on multiplayer servers when it is enabled by the server owner. This feature also uses WebRTC like shared worlds, so be careful that you don't leak your IP address accidentally by using it on a public server. If you own a website and don't want people to use voice chat on it, edit the `eaglercraftXOpts` variable in your index.html and add `allowVoiceClient: false`. ## Making a Server @@ -96,6 +100,14 @@ By setting `online_mode` to `false` in the BungeeCord `config.yml` the authentic When configuring the EaglercraftXBungee `listeners.yml` file, every listener includes an `http_server` section that can be used to configure the listener to also behave like a regular HTTP server when the websocket address is entered into a browser. If this is disabled people will get the normal "404 Websocket Upgrade Failure" instead when they accidentally type your server address into their browser. `root` defines the path to the folder containing index.html and the other files you want to host, relative to the `plugins/EaglercraftXBungee` folder. This can be useful for hosting the client if the offline download doesn't work for some reason but might slow your BungeeCord server down if lots of people are loading it all the time. +### Enabling Voice Chat + +Voice chat is disabled by default in EaglercraftXBungee because it is not recommended for use on public servers. To enable it, add or change `allow_voice: true` to your EaglercraftXBungee `listeners.yml` file. The main difference between Eaglercraft 1.5.2 and EaglercraftX 1.8's voice chat feature is that the "Global" channel now only includes other players on the same server as you instead of every single player connected to the same bungeecord proxy. If you would like to disable voice chat on certain servers, add the names of the servers to the `disable_voice_chat_on_servers` list in the EaglercraftXBungee `settings.yml` file. You may have to add this property to the YML file manually if you've upgraded your server from an older version of EaglercraftXBungee. + +### Disabling FNAW Skins + +Players are known to complain about the high-poly Five Nights At Winstons character skins making PVP harder because of the belief that they change a player's hitbox. If you would like to disable those skins in your PVP worlds you can either set `disable_fnaw_skins_everywhere: true` in your EaglercraftXBungee `settings.yml` file to disable them for all players on your whole BungeeCord proxy, or you can disable them on specific servers by adding the names of the servers to the `disable_fnaw_skins_on_servers` list also in `settings.yml` like with disabling voice chat. + ## Launch Options The EaglercraftX 1.8 client is configured primarily through a variable called `window.eaglercraftXOpts` that must be set before the client starts up. @@ -140,6 +152,7 @@ The default eaglercraftXOpts values is this: - `logInvalidCerts:` print update certificates with invalid signatures to console - `enableSignatureBadge:` show a badge on the title screen indicating if digital signature is valid - `checkRelaysForUpdates:` proprietary feature used in offline downloads +- `allowVoiceClient:` can be used to disable the voice chat feature ## Developing a Client diff --git a/client_version b/client_version index ce6e011..17a60b9 100644 --- a/client_version +++ b/client_version @@ -1 +1 @@ -u27 \ No newline at end of file +u28 \ No newline at end of file diff --git a/patches/minecraft/net/minecraft/block/BlockRedstoneWire.edit.java b/patches/minecraft/net/minecraft/block/BlockRedstoneWire.edit.java index 33edfc7..c0e3dec 100644 --- a/patches/minecraft/net/minecraft/block/BlockRedstoneWire.edit.java +++ b/patches/minecraft/net/minecraft/block/BlockRedstoneWire.edit.java @@ -44,7 +44,7 @@ ~ BlockPos posTmp = new BlockPos(0, 0, 0); ~ Block block = worldIn.getBlockState(blockpos).getBlock(); ~ if (!canConnectTo(worldIn.getBlockState(blockpos), direction) && (block.isBlockNormalCube() -~ || !canConnectUpwardsTo(worldIn.getBlockState(blockpos.offsetEvenFaster(EnumFacing.UP, posTmp))))) { +~ || !canConnectUpwardsTo(worldIn.getBlockState(blockpos.offsetEvenFaster(EnumFacing.DOWN, posTmp))))) { > CHANGE 2 : 3 @ 2 : 3 @@ -74,8 +74,19 @@ ~ BlockPos blockpos = pos1.offsetEvenFaster(enumfacing, tmp); ~ boolean flag = blockpos.x != pos2.x || blockpos.z != pos2.z; -> CHANGE 35 : 37 @ 35 : 37 +> CHANGE 7 : 9 @ 7 : 8 +~ ++blockpos.y; +~ l = this.getMaxCurrentStrength(worldIn, blockpos, l); + +> CHANGE 3 : 5 @ 3 : 4 + +~ --blockpos.y; +~ l = this.getMaxCurrentStrength(worldIn, blockpos, l); + +> CHANGE 23 : 26 @ 23 : 25 + +~ facings = EnumFacing._VALUES; ~ for (int m = 0; m < facings.length; ++m) { ~ this.blocksNeedingUpdate.add(pos1.offset(facings[m])); diff --git a/patches/minecraft/net/minecraft/client/Minecraft.edit.java b/patches/minecraft/net/minecraft/client/Minecraft.edit.java index eba857f..e31364f 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.edit.java +++ b/patches/minecraft/net/minecraft/client/Minecraft.edit.java @@ -20,7 +20,7 @@ > DELETE 1 @ 1 : 4 -> CHANGE 1 : 52 @ 1 : 4 +> CHANGE 1 : 55 @ 1 : 4 ~ ~ import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput; @@ -46,6 +46,7 @@ ~ import net.lax1dude.eaglercraft.v1_8.log4j.Logger; ~ import net.lax1dude.eaglercraft.v1_8.minecraft.EaglerFolderResourcePack; ~ import net.lax1dude.eaglercraft.v1_8.minecraft.EaglerFontRenderer; +~ import net.lax1dude.eaglercraft.v1_8.opengl.EaglerMeshLoader; ~ import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU; ~ import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; ~ import net.lax1dude.eaglercraft.v1_8.opengl.ImageData; @@ -73,6 +74,8 @@ ~ import net.lax1dude.eaglercraft.v1_8.sp.gui.GuiScreenSingleplayerConnecting; ~ import net.lax1dude.eaglercraft.v1_8.sp.lan.LANServerController; ~ import net.lax1dude.eaglercraft.v1_8.update.RelayUpdateChecker; +~ import net.lax1dude.eaglercraft.v1_8.voice.GuiVoiceOverlay; +~ import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController; > DELETE 2 @ 2 : 4 @@ -165,10 +168,16 @@ + public int bungeeOutdatedMsgTimer = 0; + private boolean isLANOpen = false; -> INSERT 1 : 3 @ 1 +> INSERT 1 : 9 @ 1 + public SkullCommand eagskullCommand; + ++ public GuiVoiceOverlay voiceOverlay; ++ ++ public float startZoomValue = 18.0f; ++ public float adjustedZoomValue = 18.0f; ++ public boolean isZoomKey = false; ++ > CHANGE 2 : 3 @ 2 : 5 @@ -248,7 +257,7 @@ ~ this.standardGalacticFontRenderer = new EaglerFontRenderer(this.gameSettings, -> INSERT 5 : 11 @ 5 +> INSERT 5 : 12 @ 5 + this.mcResourceManager.registerReloadListener(new ShaderPackInfoReloadListener()); + this.mcResourceManager.registerReloadListener(PBRTextureMapUtils.blockMaterialConstants); @@ -256,6 +265,7 @@ + this.mcResourceManager.registerReloadListener(new MetalsLUT()); + this.mcResourceManager.registerReloadListener(new EmissiveItems()); + this.mcResourceManager.registerReloadListener(new BlockVertexIDs()); ++ this.mcResourceManager.registerReloadListener(new EaglerMeshLoader()); > CHANGE 3 : 4 @ 3 : 4 @@ -273,9 +283,12 @@ + SkinPreviewRenderer.initialize(); -> INSERT 2 : 11 @ 2 +> INSERT 2 : 14 @ 2 + this.eagskullCommand = new SkullCommand(this); ++ this.voiceOverlay = new GuiVoiceOverlay(this); ++ ScaledResolution voiceRes = new ScaledResolution(this); ++ this.voiceOverlay.setResolution(voiceRes.getScaledWidth(), voiceRes.getScaledHeight()); + + ServerList.initServerList(this); + EaglerProfile.read(); @@ -490,7 +503,11 @@ + Mouse.tickCursorShape(); -> DELETE 39 @ 39 : 57 +> INSERT 5 : 6 @ 5 + ++ Display.setVSync(this.gameSettings.enableVsync); + +> DELETE 34 @ 34 : 52 > CHANGE 39 : 40 @ 39 : 40 @@ -518,11 +535,19 @@ ~ Display.toggleFullscreen(); -> DELETE 11 @ 11 : 12 +> INSERT 5 : 6 @ 5 -> DELETE 2 @ 2 : 10 ++ ScaledResolution scaledresolution = new ScaledResolution(this); -> INSERT 9 : 28 @ 9 +> DELETE 1 @ 1 : 2 + +> DELETE 4 @ 4 : 6 + +> CHANGE 1 : 2 @ 1 : 7 + +~ this.voiceOverlay.setResolution(scaledresolution.getScaledWidth(), scaledresolution.getScaledHeight()); + +> INSERT 11 : 30 @ 11 + RateLimitTracker.tick(); + @@ -544,7 +569,13 @@ + RelayUpdateChecker.runTick(); + -> INSERT 15 : 16 @ 15 +> INSERT 5 : 8 @ 5 + ++ this.mcProfiler.endStartSection("eaglerVoice"); ++ VoiceClientController.tickVoiceClient(this); ++ + +> INSERT 10 : 11 @ 10 + GlStateManager.viewport(0, 0, displayWidth, displayHeight); // to be safe @@ -573,7 +604,13 @@ ~ return Minecraft.this.currentScreen.getClass().getName(); -> CHANGE 40 : 42 @ 40 : 41 +> CHANGE 25 : 28 @ 25 : 26 + +~ if (this.isZoomKey) { +~ this.adjustedZoomValue = MathHelper.clamp_float(adjustedZoomValue - j * 4.0f, 5.0f, 32.0f); +~ } else if (this.thePlayer.isSpectator()) { + +> CHANGE 14 : 16 @ 14 : 15 ~ if ((!this.inGameHasFocus || !Mouse.isActuallyGrabbed()) && Mouse.getEventButtonState()) { ~ this.inGameHasFocus = false; @@ -613,7 +650,16 @@ + GlStateManager.recompileShaders(); -> INSERT 163 : 164 @ 163 +> INSERT 71 : 77 @ 71 + ++ boolean zoomKey = this.gameSettings.keyBindZoomCamera.isKeyDown(); ++ if (zoomKey != isZoomKey) { ++ adjustedZoomValue = startZoomValue; ++ isZoomKey = zoomKey; ++ } ++ + +> INSERT 92 : 93 @ 92 + this.eagskullCommand.tick(); @@ -664,8 +710,9 @@ + } + -> CHANGE 6 : 16 @ 6 : 54 +> CHANGE 6 : 17 @ 6 : 54 +~ Minecraft.getMinecraft().getRenderManager().setEnableFNAWSkins(this.gameSettings.enableFNAWSkins); ~ session.reset(); ~ SingleplayerServerController.launchEaglercraftServer(folderName, gameSettings.difficulty.getDifficultyId(), ~ Math.max(gameSettings.renderDistanceChunks, 2), worldSettingsIn); @@ -799,7 +846,7 @@ > DELETE 26 @ 26 : 34 -> INSERT 7 : 27 @ 7 +> INSERT 7 : 35 @ 7 + + public static int getGLMaximumTextureSize() { @@ -821,5 +868,13 @@ + public void clearTitles() { + ingameGUI.displayTitle(null, null, -1, -1, -1); + } ++ ++ public boolean getEnableFNAWSkins() { ++ boolean ret = this.gameSettings.enableFNAWSkins; ++ if (this.thePlayer != null) { ++ ret &= this.thePlayer.sendQueue.currentFNAWSkinAllowedState; ++ } ++ return ret; ++ } > EOF diff --git a/patches/minecraft/net/minecraft/client/entity/AbstractClientPlayer.edit.java b/patches/minecraft/net/minecraft/client/entity/AbstractClientPlayer.edit.java index 4df33f3..7a5051d 100644 --- a/patches/minecraft/net/minecraft/client/entity/AbstractClientPlayer.edit.java +++ b/patches/minecraft/net/minecraft/client/entity/AbstractClientPlayer.edit.java @@ -5,14 +5,34 @@ # Version: 1.0 # Author: lax1dude -> CHANGE 2 : 3 @ 2 : 4 +> CHANGE 2 : 4 @ 2 : 4 ~ import net.lax1dude.eaglercraft.v1_8.mojang.authlib.GameProfile; +~ import net.lax1dude.eaglercraft.v1_8.profile.SkinModel; > DELETE 2 @ 2 : 6 > DELETE 6 @ 6 : 7 -> DELETE 44 @ 44 : 62 +> INSERT 6 : 14 @ 6 + ++ public long eaglerHighPolyAnimationTick = System.currentTimeMillis(); ++ public float eaglerHighPolyAnimationFloat1 = 0.0f; ++ public float eaglerHighPolyAnimationFloat2 = 0.0f; ++ public float eaglerHighPolyAnimationFloat3 = 0.0f; ++ public float eaglerHighPolyAnimationFloat4 = 0.0f; ++ public float eaglerHighPolyAnimationFloat5 = 0.0f; ++ public float eaglerHighPolyAnimationFloat6 = 0.0f; ++ + +> DELETE 38 @ 38 : 56 + +> INSERT 6 : 11 @ 6 + ++ public SkinModel getEaglerSkinModel() { ++ NetworkPlayerInfo networkplayerinfo = this.getPlayerInfo(); ++ return networkplayerinfo == null ? SkinModel.STEVE : networkplayerinfo.getEaglerSkinModel(); ++ } ++ > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/GuiButton.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiButton.edit.java index b992b7f..5f7489b 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiButton.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiButton.edit.java @@ -13,7 +13,12 @@ > DELETE 3 @ 3 : 6 -> INSERT 13 : 14 @ 13 +> CHANGE 4 : 6 @ 4 : 6 + +~ public int width; +~ public int height; + +> INSERT 7 : 8 @ 7 + public float fontScale = 1.0f; diff --git a/patches/minecraft/net/minecraft/client/gui/GuiChat.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiChat.edit.java index 84fa26e..6b77b96 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiChat.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiChat.edit.java @@ -117,4 +117,11 @@ ~ for (int i = 0; i < parArrayOfString.length; ++i) { ~ String s = parArrayOfString[i]; +> INSERT 24 : 28 @ 24 + ++ ++ public boolean blockPTTKey() { ++ return true; ++ } + > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/GuiCommandBlock.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiCommandBlock.edit.java index f09a2a3..b90ff79 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiCommandBlock.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiCommandBlock.edit.java @@ -26,4 +26,11 @@ ~ protected void mouseClicked(int parInt1, int parInt2, int parInt3) { +> INSERT 46 : 50 @ 46 + ++ ++ public boolean blockPTTKey() { ++ return commandTextField.isFocused(); ++ } + > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/GuiCustomizeSkin.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiCustomizeSkin.edit.java index 5697dbb..66f4b38 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiCustomizeSkin.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiCustomizeSkin.edit.java @@ -7,17 +7,37 @@ > DELETE 2 @ 2 : 5 -> CHANGE 15 : 18 @ 15 : 16 +> INSERT 7 : 9 @ 7 + ++ private GuiButton enableFNAWSkinsButton; ++ + +> CHANGE 8 : 11 @ 8 : 9 ~ EnumPlayerModelParts[] parts = EnumPlayerModelParts._VALUES; ~ for (int k = 0; k < parts.length; ++k) { ~ EnumPlayerModelParts enumplayermodelparts = parts[k]; -> CHANGE 14 : 15 @ 14 : 15 +> CHANGE 10 : 14 @ 10 : 11 + +~ this.buttonList.add(enableFNAWSkinsButton = new GuiButton(201, this.width / 2 - 100, +~ this.height / 6 + 10 + 24 * (i >> 1), I18n.format("options.skinCustomisation.enableFNAWSkins") + ": " +~ + I18n.format(mc.gameSettings.enableFNAWSkins ? "options.on" : "options.off"))); +~ this.buttonList.add(new GuiButton(200, this.width / 2 - 100, this.height / 6 + 40 + 24 * (i >> 1), + +> CHANGE 3 : 4 @ 3 : 4 ~ protected void actionPerformed(GuiButton parGuiButton) { -> CHANGE 27 : 32 @ 27 : 28 +> INSERT 4 : 9 @ 4 + ++ } else if (parGuiButton.id == 201) { ++ mc.gameSettings.enableFNAWSkins = !mc.gameSettings.enableFNAWSkins; ++ mc.getRenderManager().setEnableFNAWSkins(mc.getEnableFNAWSkins()); ++ enableFNAWSkinsButton.displayString = I18n.format("options.skinCustomisation.enableFNAWSkins") + ": " ++ + I18n.format(mc.gameSettings.enableFNAWSkins ? "options.on" : "options.off"); + +> CHANGE 23 : 28 @ 23 : 24 ~ /* ~ * TODO: I changed this to getUnformattedText() from getFormattedText() because diff --git a/patches/minecraft/net/minecraft/client/gui/GuiIngame.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiIngame.edit.java index 8d6f610..15edf0f 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiIngame.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiIngame.edit.java @@ -54,7 +54,14 @@ ~ this.overlayDebug.renderDebugInfo(scaledresolution); -> INSERT 87 : 90 @ 87 +> INSERT 83 : 87 @ 83 + ++ if (this.mc.currentScreen == null) { ++ this.mc.voiceOverlay.drawOverlay(); ++ } ++ + +> INSERT 4 : 7 @ 4 + if (this.mc.gameSettings.hudWorld && (mc.currentScreen == null || !(mc.currentScreen instanceof GuiChat))) { + j -= 10; diff --git a/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.edit.java index 7dd1a99..dcf9da2 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiIngameMenu.edit.java @@ -5,8 +5,9 @@ # Version: 1.0 # Author: lax1dude -> CHANGE 2 : 11 @ 2 : 9 +> CHANGE 2 : 14 @ 2 : 9 +~ import net.lax1dude.eaglercraft.v1_8.EagRuntime; ~ import net.lax1dude.eaglercraft.v1_8.Mouse; ~ import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; ~ import net.lax1dude.eaglercraft.v1_8.sp.SingleplayerServerController; @@ -15,6 +16,8 @@ ~ import net.lax1dude.eaglercraft.v1_8.sp.gui.GuiShareToLan; ~ import net.lax1dude.eaglercraft.v1_8.sp.lan.LANServerController; ~ import net.lax1dude.eaglercraft.v1_8.update.GuiUpdateCheckerOverlay; +~ import net.lax1dude.eaglercraft.v1_8.voice.GuiVoiceMenu; +~ import net.minecraft.client.Minecraft; ~ import net.minecraft.client.audio.PositionedSoundRecord; > CHANGE 4 : 7 @ 4 : 5 @@ -25,16 +28,20 @@ > DELETE 2 @ 2 : 4 -> INSERT 1 : 11 @ 1 +> INSERT 1 : 15 @ 1 + private GuiButton lanButton; + + boolean hasSentAutoSave = !SingleplayerServerController.isWorldRunning(); + + private GuiUpdateCheckerOverlay updateCheckerOverlay; ++ private GuiVoiceMenu voiceMenu; + + public GuiIngameMenu() { + updateCheckerOverlay = new GuiUpdateCheckerOverlay(true, this); ++ if (EagRuntime.getConfiguration().isAllowVoiceClient()) { ++ voiceMenu = new GuiVoiceMenu(this); ++ } + } + @@ -90,8 +97,12 @@ ~ } ~ break; -> CHANGE 6 : 9 @ 6 : 7 +> CHANGE 6 : 13 @ 6 : 7 +~ if (EagRuntime.getConfiguration().isAllowVoiceClient() +~ && (!mc.isSingleplayer() || LANServerController.isHostingLAN())) { +~ voiceMenu.updateScreen(); +~ } ~ if (Mouse.isActuallyGrabbed()) { ~ Mouse.setGrabbed(false); ~ } @@ -100,45 +111,76 @@ ~ this.drawCenteredString(this.fontRendererObj, I18n.format("menu.game", new Object[0]), this.width / 2, 20, -> INSERT 1 : 35 @ 1 +> CHANGE 1 : 55 @ 1 : 2 + +~ +~ this.updateCheckerOverlay.drawScreen(i, j, f); +~ +~ if (LANServerController.isLANOpen()) { +~ int offset = this.updateCheckerOverlay.getSharedWorldInfoYOffset(); +~ String str = I18n.format("lanServer.pauseMenu0"); +~ drawString(fontRendererObj, str, 6, 10 + offset, 0xFFFF55); +~ +~ if (mc.gameSettings.hideJoinCode) { +~ GlStateManager.pushMatrix(); +~ GlStateManager.translate(7.0f, 25.0f + offset, 0.0f); +~ GlStateManager.scale(0.75f, 0.75f, 0.75f); +~ str = I18n.format("lanServer.showCode"); +~ int w = fontRendererObj.getStringWidth(str); +~ boolean hover = i > 4 && i < 8 + w * 3 / 4 && j > 24 + offset && j < 25 + offset + 8; +~ drawString(fontRendererObj, EnumChatFormatting.UNDERLINE + str, 0, 0, hover ? 0xEEEEAA : 0xCCCC55); +~ GlStateManager.popMatrix(); +~ } else { +~ int w = fontRendererObj.getStringWidth(str); +~ GlStateManager.pushMatrix(); +~ GlStateManager.translate(6 + w + 3, 11 + offset, 0.0f); +~ GlStateManager.scale(0.75f, 0.75f, 0.75f); +~ str = I18n.format("lanServer.hideCode"); +~ int w2 = fontRendererObj.getStringWidth(str); +~ boolean hover = i > 6 + w + 2 && i < 6 + w + 3 + w2 * 3 / 4 && j > 11 + offset - 1 +~ && j < 11 + offset + 6; +~ drawString(fontRendererObj, EnumChatFormatting.UNDERLINE + str, 0, 0, hover ? 0xEEEEAA : 0xCCCC55); +~ GlStateManager.popMatrix(); +~ +~ drawString( +~ fontRendererObj, EnumChatFormatting.GRAY + I18n.format("lanServer.pauseMenu1") + " " +~ + EnumChatFormatting.RESET + LANServerController.getCurrentURI(), +~ 6, 25 + offset, 0xFFFFFF); +~ drawString( +~ fontRendererObj, EnumChatFormatting.GRAY + I18n.format("lanServer.pauseMenu2") + " " +~ + EnumChatFormatting.RESET + LANServerController.getCurrentCode(), +~ 6, 35 + offset, 0xFFFFFF); +~ } +~ } +~ +~ try { +~ if (EagRuntime.getConfiguration().isAllowVoiceClient() +~ && (!mc.isSingleplayer() || LANServerController.isHostingLAN())) { +~ if (voiceMenu.isBlockingInput()) { +~ super.drawScreen(0, 0, f); +~ } else { +~ super.drawScreen(i, j, f); +~ } +~ voiceMenu.drawScreen(i, j, f); +~ } else { +~ super.drawScreen(i, j, f); +~ } +~ } catch (GuiVoiceMenu.AbortedException ex) { +~ } + +> INSERT 1 : 80 @ 1 + -+ this.updateCheckerOverlay.drawScreen(i, j, f); -+ -+ if (LANServerController.isLANOpen()) { -+ String str = I18n.format("lanServer.pauseMenu0"); -+ drawString(fontRendererObj, str, 6, 32, 0xFFFF55); -+ -+ if (mc.gameSettings.hideJoinCode) { -+ GlStateManager.pushMatrix(); -+ GlStateManager.translate(7.0f, 47.0f, 0.0f); -+ GlStateManager.scale(0.75f, 0.75f, 0.75f); -+ str = I18n.format("lanServer.showCode"); -+ int w = fontRendererObj.getStringWidth(str); -+ boolean hover = i > 6 && i < 8 + w * 3 / 4 && j > 46 && j < 47 + 8; -+ drawString(fontRendererObj, EnumChatFormatting.UNDERLINE + str, 0, 0, hover ? 0xEEEEAA : 0xCCCC55); -+ GlStateManager.popMatrix(); -+ } else { -+ int w = fontRendererObj.getStringWidth(str); -+ GlStateManager.pushMatrix(); -+ GlStateManager.translate(6 + w + 3, 33, 0.0f); -+ GlStateManager.scale(0.75f, 0.75f, 0.75f); -+ str = I18n.format("lanServer.hideCode"); -+ int w2 = fontRendererObj.getStringWidth(str); -+ boolean hover = i > 6 + w + 2 && i < 6 + w + 3 + w2 * 3 / 4 && j > 33 - 1 && j < 33 + 6; -+ drawString(fontRendererObj, EnumChatFormatting.UNDERLINE + str, 0, 0, hover ? 0xEEEEAA : 0xCCCC55); -+ GlStateManager.popMatrix(); -+ -+ drawString(fontRendererObj, EnumChatFormatting.GRAY + I18n.format("lanServer.pauseMenu1") + " " -+ + EnumChatFormatting.RESET + LANServerController.getCurrentURI(), 6, 47, 0xFFFFFF); -+ drawString(fontRendererObj, EnumChatFormatting.GRAY + I18n.format("lanServer.pauseMenu2") + " " -+ + EnumChatFormatting.RESET + LANServerController.getCurrentCode(), 6, 57, 0xFFFFFF); ++ protected void keyTyped(char par1, int par2) { ++ try { ++ if (EagRuntime.getConfiguration().isAllowVoiceClient() ++ && (!mc.isSingleplayer() || LANServerController.isHostingLAN())) { ++ voiceMenu.keyTyped(par1, par2); + } ++ super.keyTyped(par1, par2); ++ } catch (GuiVoiceMenu.AbortedException ex) { + } -+ - -> INSERT 2 : 42 @ 2 - ++ } + + public void confirmClicked(boolean par1, int par2) { + mc.displayGuiScreen(this); @@ -152,11 +194,20 @@ + } + + protected void mouseClicked(int par1, int par2, int par3) { ++ try { ++ if (EagRuntime.getConfiguration().isAllowVoiceClient() ++ && (!mc.isSingleplayer() || LANServerController.isHostingLAN())) { ++ voiceMenu.mouseClicked(par1, par2, par3); ++ } ++ } catch (GuiVoiceMenu.AbortedException ex) { ++ return; ++ } + if (par3 == 0) { ++ int offset = this.updateCheckerOverlay.getSharedWorldInfoYOffset(); + if (mc.gameSettings.hideJoinCode) { + String str = I18n.format("lanServer.showCode"); + int w = fontRendererObj.getStringWidth(str); -+ if (par1 > 6 && par1 < 8 + w * 3 / 4 && par2 > 46 && par2 < 47 + 8) { ++ if (par1 > 4 && par1 < 8 + w * 3 / 4 && par2 > 24 + offset && par2 < 25 + offset + 8) { + mc.gameSettings.hideJoinCode = false; + this.mc.getSoundHandler() + .playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); @@ -167,7 +218,8 @@ + int w = fontRendererObj.getStringWidth(str); + str = I18n.format("lanServer.hideCode"); + int w2 = fontRendererObj.getStringWidth(str); -+ if (par1 > 6 + w + 2 && par1 < 6 + w + 3 + w2 * 3 / 4 && par2 > 33 - 1 && par2 < 33 + 6) { ++ if (par1 > 6 + w + 2 && par1 < 6 + w + 3 + w2 * 3 / 4 && par2 > 11 + offset - 1 ++ && par2 < 11 + offset + 6) { + mc.gameSettings.hideJoinCode = true; + this.mc.getSoundHandler() + .playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); @@ -179,5 +231,23 @@ + this.updateCheckerOverlay.mouseClicked(par1, par2, par3); + super.mouseClicked(par1, par2, par3); + } ++ ++ public void setWorldAndResolution(Minecraft par1Minecraft, int par2, int par3) { ++ super.setWorldAndResolution(par1Minecraft, par2, par3); ++ if (EagRuntime.getConfiguration().isAllowVoiceClient()) { ++ voiceMenu.setResolution(par1Minecraft, par2, par3); ++ } ++ } ++ ++ protected void mouseReleased(int par1, int par2, int par3) { ++ try { ++ if (EagRuntime.getConfiguration().isAllowVoiceClient() ++ && (!mc.isSingleplayer() || LANServerController.isHostingLAN())) { ++ voiceMenu.mouseReleased(par1, par2, par3); ++ } ++ super.mouseReleased(par1, par2, par3); ++ } catch (GuiVoiceMenu.AbortedException ex) { ++ } ++ } > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/GuiOptions.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiOptions.edit.java index b818f0a..726b582 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiOptions.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiOptions.edit.java @@ -59,7 +59,11 @@ ~ I18n.format("options.debugConsoleButton", new Object[0]))); ~ btn.enabled = EagRuntime.getPlatformType() != EnumPlatformType.DESKTOP; -> INSERT 17 : 18 @ 17 +> CHANGE 10 : 11 @ 10 : 11 + +~ return chatcomponenttext.getUnformattedText(); + +> INSERT 6 : 7 @ 6 + SingleplayerServerController.setDifficulty(-1); diff --git a/patches/minecraft/net/minecraft/client/gui/GuiRepair.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiRepair.edit.java index c1eb6f3..e57581c 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiRepair.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiRepair.edit.java @@ -28,4 +28,11 @@ ~ protected void mouseClicked(int parInt1, int parInt2, int parInt3) { +> INSERT 46 : 50 @ 46 + ++ ++ public boolean blockPTTKey() { ++ return nameField.isFocused(); ++ } + > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/GuiScreen.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiScreen.edit.java index 1ea2001..2b51878 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiScreen.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiScreen.edit.java @@ -178,11 +178,15 @@ ~ private void openWebLink(String parURI) { ~ EagRuntime.openLink(parURI); -> INSERT 34 : 38 @ 34 +> INSERT 34 : 42 @ 34 + + public boolean shouldHangupIntegratedServer() { + return true; + } ++ ++ public boolean blockPTTKey() { ++ return false; ++ } > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/GuiScreenBook.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiScreenBook.edit.java index 40a4c80..9fde905 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiScreenBook.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiScreenBook.edit.java @@ -42,4 +42,11 @@ ~ protected void mouseClicked(int parInt1, int parInt2, int parInt3) { +> INSERT 102 : 106 @ 102 + ++ ++ public boolean blockPTTKey() { ++ return this.bookIsUnsigned; ++ } + > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/GuiVideoSettings.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiVideoSettings.edit.java index 28eff5b..4575877 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiVideoSettings.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiVideoSettings.edit.java @@ -9,11 +9,20 @@ ~ -> CHANGE 12 : 17 @ 12 : 15 +> INSERT 8 : 11 @ 8 -~ GameSettings.Options.PARTICLES, GameSettings.Options.FXAA, GameSettings.Options.MIPMAP_LEVELS, -~ GameSettings.Options.BLOCK_ALTERNATIVES, GameSettings.Options.ENTITY_SHADOWS, GameSettings.Options.FOG, -~ GameSettings.Options.FULLSCREEN, GameSettings.Options.HUD_FPS, GameSettings.Options.HUD_COORDS, ++ /** ++ * + An array of all of GameSettings.Options's video options. ++ */ + +> CHANGE 2 : 10 @ 2 : 7 + +~ GameSettings.Options.FRAMERATE_LIMIT, GameSettings.Options.EAGLER_VSYNC, GameSettings.Options.ANAGLYPH, +~ GameSettings.Options.VIEW_BOBBING, GameSettings.Options.GUI_SCALE, GameSettings.Options.GAMMA, +~ GameSettings.Options.RENDER_CLOUDS, GameSettings.Options.PARTICLES, GameSettings.Options.FXAA, +~ GameSettings.Options.MIPMAP_LEVELS, GameSettings.Options.BLOCK_ALTERNATIVES, +~ GameSettings.Options.ENTITY_SHADOWS, GameSettings.Options.FOG, GameSettings.Options.FULLSCREEN, +~ GameSettings.Options.FNAW_SKINS, GameSettings.Options.HUD_FPS, GameSettings.Options.HUD_COORDS, ~ GameSettings.Options.HUD_PLAYER, GameSettings.Options.HUD_STATS, GameSettings.Options.HUD_WORLD, ~ GameSettings.Options.HUD_24H, GameSettings.Options.CHUNK_FIX }; @@ -30,4 +39,8 @@ ~ protected void mouseClicked(int parInt1, int parInt2, int parInt3) { +> INSERT 8 : 9 @ 8 + ++ this.mc.voiceOverlay.setResolution(j, k); + > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/inventory/GuiContainerCreative.edit.java b/patches/minecraft/net/minecraft/client/gui/inventory/GuiContainerCreative.edit.java index e7539fc..80af564 100644 --- a/patches/minecraft/net/minecraft/client/gui/inventory/GuiContainerCreative.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/inventory/GuiContainerCreative.edit.java @@ -84,4 +84,11 @@ ~ protected void actionPerformed(GuiButton parGuiButton) { +> INSERT 139 : 143 @ 139 + ++ ++ public boolean blockPTTKey() { ++ return searchField.isFocused(); ++ } + > EOF diff --git a/patches/minecraft/net/minecraft/client/gui/inventory/GuiEditSign.edit.java b/patches/minecraft/net/minecraft/client/gui/inventory/GuiEditSign.edit.java index 81413ab..91e4dea 100644 --- a/patches/minecraft/net/minecraft/client/gui/inventory/GuiEditSign.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/inventory/GuiEditSign.edit.java @@ -22,4 +22,11 @@ ~ protected void keyTyped(char parChar1, int parInt1) { +> INSERT 68 : 72 @ 68 + ++ ++ public boolean blockPTTKey() { ++ return true; ++ } + > EOF diff --git a/patches/minecraft/net/minecraft/client/model/ModelPlayer.edit.java b/patches/minecraft/net/minecraft/client/model/ModelPlayer.edit.java index 5dd3c40..6fef1af 100644 --- a/patches/minecraft/net/minecraft/client/model/ModelPlayer.edit.java +++ b/patches/minecraft/net/minecraft/client/model/ModelPlayer.edit.java @@ -13,7 +13,20 @@ ~ if (entity != null && entity.isSneaking()) { -> CHANGE 31 : 32 @ 31 : 32 +> INSERT 21 : 25 @ 21 + ++ GlStateManager.matrixMode(5890); ++ GlStateManager.pushMatrix(); ++ GlStateManager.scale(2.0f, 1.0f, 1.0f); ++ GlStateManager.matrixMode(5888); + +> INSERT 1 : 4 @ 1 + ++ GlStateManager.matrixMode(5890); ++ GlStateManager.popMatrix(); ++ GlStateManager.matrixMode(5888); + +> CHANGE 9 : 10 @ 9 : 10 ~ if (entity != null && entity.isSneaking()) { diff --git a/patches/minecraft/net/minecraft/client/multiplayer/PlayerControllerMP.edit.java b/patches/minecraft/net/minecraft/client/multiplayer/PlayerControllerMP.edit.java index 66a8814..979b68a 100644 --- a/patches/minecraft/net/minecraft/client/multiplayer/PlayerControllerMP.edit.java +++ b/patches/minecraft/net/minecraft/client/multiplayer/PlayerControllerMP.edit.java @@ -17,7 +17,7 @@ + import net.minecraft.util.ChatComponentText; -> CHANGE 228 : 239 @ 228 : 229 +> CHANGE 228 : 240 @ 228 : 229 ~ try { ~ this.netClientHandler.getNetworkManager().processReceivedPackets(); @@ -30,6 +30,7 @@ ~ .closeChannel(new ChatComponentText("Exception thrown: " + ex.toString())); ~ } ~ this.netClientHandler.getSkinCache().flush(); +~ this.netClientHandler.getCapeCache().flush(); > CHANGE 96 : 98 @ 96 : 98 diff --git a/patches/minecraft/net/minecraft/client/network/NetHandlerPlayClient.edit.java b/patches/minecraft/net/minecraft/client/network/NetHandlerPlayClient.edit.java index ffb4316..7e1f3ba 100644 --- a/patches/minecraft/net/minecraft/client/network/NetHandlerPlayClient.edit.java +++ b/patches/minecraft/net/minecraft/client/network/NetHandlerPlayClient.edit.java @@ -9,7 +9,7 @@ > DELETE 4 @ 4 : 6 -> INSERT 1 : 19 @ 1 +> INSERT 1 : 22 @ 1 + + import net.lax1dude.eaglercraft.v1_8.EagRuntime; @@ -19,12 +19,15 @@ + import com.google.common.collect.Maps; + + import net.lax1dude.eaglercraft.v1_8.netty.Unpooled; ++ import net.lax1dude.eaglercraft.v1_8.profile.CapePackets; ++ import net.lax1dude.eaglercraft.v1_8.profile.ServerCapeCache; + import net.lax1dude.eaglercraft.v1_8.profile.ServerSkinCache; + import net.lax1dude.eaglercraft.v1_8.profile.SkinPackets; + import net.lax1dude.eaglercraft.v1_8.socket.EaglercraftNetworkManager; + import net.lax1dude.eaglercraft.v1_8.sp.lan.LANClientNetworkManager; + import net.lax1dude.eaglercraft.v1_8.sp.socket.ClientIntegratedServerNetworkManager; + import net.lax1dude.eaglercraft.v1_8.update.UpdateService; ++ import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController; + import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; + import net.lax1dude.eaglercraft.v1_8.log4j.Logger; + import net.lax1dude.eaglercraft.v1_8.minecraft.EaglerFolderResourcePack; @@ -64,36 +67,51 @@ ~ private final Map playerInfoMap = Maps.newHashMap(); -> CHANGE 2 : 5 @ 2 : 3 +> CHANGE 2 : 7 @ 2 : 3 ~ private boolean isIntegratedServer = false; ~ private final EaglercraftRandom avRandomizer = new EaglercraftRandom(); ~ private final ServerSkinCache skinCache; +~ private final ServerCapeCache capeCache; +~ public boolean currentFNAWSkinAllowedState = true; > CHANGE 1 : 2 @ 1 : 2 ~ public NetHandlerPlayClient(Minecraft mcIn, GuiScreen parGuiScreen, EaglercraftNetworkManager parNetworkManager, -> INSERT 5 : 8 @ 5 +> INSERT 5 : 9 @ 5 + this.skinCache = new ServerSkinCache(parNetworkManager, mcIn.getTextureManager()); ++ this.capeCache = new ServerCapeCache(parNetworkManager, mcIn.getTextureManager()); + this.isIntegratedServer = (parNetworkManager instanceof ClientIntegratedServerNetworkManager) + || (parNetworkManager instanceof LANClientNetworkManager); -> INSERT 4 : 5 @ 4 +> INSERT 4 : 6 @ 4 + this.skinCache.destroy(); ++ this.capeCache.destroy(); -> INSERT 2 : 6 @ 2 +> INSERT 2 : 10 @ 2 + public ServerSkinCache getSkinCache() { + return this.skinCache; + } + ++ public ServerCapeCache getCapeCache() { ++ return this.capeCache; ++ } ++ > DELETE 1 @ 1 : 2 -> DELETE 19 @ 19 : 20 +> INSERT 16 : 20 @ 16 + ++ if (VoiceClientController.isClientSupported()) { ++ VoiceClientController.initializeVoiceClient((pkt) -> this.netManager ++ .sendPacket(new C17PacketCustomPayload(VoiceClientController.SIGNAL_CHANNEL, pkt))); ++ } + +> DELETE 3 @ 3 : 4 > DELETE 105 @ 105 : 106 @@ -129,8 +147,11 @@ > DELETE 22 @ 22 : 23 -> CHANGE 8 : 11 @ 8 : 9 +> CHANGE 8 : 14 @ 8 : 9 +~ VoiceClientController.handleServerDisconnect(); +~ Minecraft.getMinecraft().getRenderManager() +~ .setEnableFNAWSkins(this.gameController.gameSettings.enableFNAWSkins); ~ if (this.gameController.theWorld != null) { ~ this.gameController.loadWorld((WorldClient) null); ~ } @@ -241,11 +262,12 @@ ~ for (int i = 0, l = lst.size(); i < l; ++i) { ~ S38PacketPlayerListItem.AddPlayerData s38packetplayerlistitem$addplayerdata = lst.get(i); -> CHANGE 1 : 4 @ 1 : 2 +> CHANGE 1 : 5 @ 1 : 2 ~ EaglercraftUUID uuid = s38packetplayerlistitem$addplayerdata.getProfile().getId(); ~ this.playerInfoMap.remove(uuid); ~ this.skinCache.evictSkin(uuid); +~ this.capeCache.evictCape(uuid); > DELETE 34 @ 34 : 35 @@ -335,7 +357,7 @@ > DELETE 11 @ 11 : 13 -> INSERT 9 : 28 @ 9 +> INSERT 9 : 43 @ 9 + } else if ("EAG|Skins-1.8".equals(packetIn.getChannelName())) { + try { @@ -344,6 +366,13 @@ + logger.error("Couldn't read EAG|Skins-1.8 packet!"); + logger.error(e); + } ++ } else if ("EAG|Capes-1.8".equals(packetIn.getChannelName())) { ++ try { ++ CapePackets.readPluginMessage(packetIn.getBufferData(), capeCache); ++ } catch (IOException e) { ++ logger.error("Couldn't read EAG|Capes-1.8 packet!"); ++ logger.error(e); ++ } + } else if ("EAG|UpdateCert-1.8".equals(packetIn.getChannelName())) { + if (EagRuntime.getConfiguration().allowUpdateSvc()) { + try { @@ -356,8 +385,18 @@ + logger.error(e); + } + } ++ } else if (VoiceClientController.SIGNAL_CHANNEL.equals(packetIn.getChannelName())) { ++ if (VoiceClientController.isClientSupported()) { ++ VoiceClientController.handleVoiceSignalPacket(packetIn.getBufferData()); ++ } ++ } else if ("EAG|FNAWSEn-1.8".equals(packetIn.getChannelName())) { ++ this.currentFNAWSkinAllowedState = packetIn.getBufferData().readBoolean(); ++ Minecraft.getMinecraft().getRenderManager().setEnableFNAWSkins( ++ this.currentFNAWSkinAllowedState && Minecraft.getMinecraft().gameSettings.enableFNAWSkins); -> DELETE 5 @ 5 : 6 +> DELETE 1 @ 1 : 2 + +> DELETE 3 @ 3 : 4 > DELETE 19 @ 19 : 20 diff --git a/patches/minecraft/net/minecraft/client/network/NetworkPlayerInfo.edit.java b/patches/minecraft/net/minecraft/client/network/NetworkPlayerInfo.edit.java index ef7c3f7..41f6714 100644 --- a/patches/minecraft/net/minecraft/client/network/NetworkPlayerInfo.edit.java +++ b/patches/minecraft/net/minecraft/client/network/NetworkPlayerInfo.edit.java @@ -5,9 +5,10 @@ # Version: 1.0 # Author: lax1dude -> CHANGE 2 : 3 @ 2 : 6 +> CHANGE 2 : 4 @ 2 : 6 ~ import net.lax1dude.eaglercraft.v1_8.mojang.authlib.GameProfile; +~ import net.lax1dude.eaglercraft.v1_8.profile.SkinModel; > DELETE 1 @ 1 : 3 @@ -22,13 +23,21 @@ ~ return Minecraft.getMinecraft().getNetHandler().getSkinCache().getSkin(this.gameProfile) ~ .getSkinModel().profileSkinType; -> CHANGE 3 : 4 @ 3 : 9 +> CHANGE 2 : 5 @ 2 : 6 +~ public SkinModel getEaglerSkinModel() { +~ return Minecraft.getMinecraft().getNetHandler().getSkinCache().getSkin(this.gameProfile).getSkinModel(); +~ } + +> CHANGE 1 : 3 @ 1 : 3 + +~ public ResourceLocation getLocationSkin() { ~ return Minecraft.getMinecraft().getNetHandler().getSkinCache().getSkin(this.gameProfile).getResourceLocation(); -> CHANGE 3 : 4 @ 3 : 8 +> CHANGE 3 : 5 @ 3 : 8 -~ return null; +~ return Minecraft.getMinecraft().getNetHandler().getCapeCache().getCape(this.gameProfile.getId()) +~ .getResourceLocation(); > DELETE 6 @ 6 : 33 diff --git a/patches/minecraft/net/minecraft/client/renderer/EntityRenderer.edit.java b/patches/minecraft/net/minecraft/client/renderer/EntityRenderer.edit.java index 065a909..6fdaeda 100644 --- a/patches/minecraft/net/minecraft/client/renderer/EntityRenderer.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/EntityRenderer.edit.java @@ -16,7 +16,7 @@ ~ import net.lax1dude.eaglercraft.v1_8.EaglercraftRandom; ~ import net.lax1dude.eaglercraft.v1_8.HString; -> INSERT 1 : 27 @ 1 +> INSERT 1 : 28 @ 1 + + import com.google.common.base.Predicate; @@ -43,6 +43,7 @@ + import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.gui.GuiShaderConfig; + import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.texture.EmissiveItems; + import net.lax1dude.eaglercraft.v1_8.vector.Vector4f; ++ import net.lax1dude.eaglercraft.v1_8.voice.VoiceTagRenderer; + import net.lax1dude.eaglercraft.v1_8.vector.Matrix4f; > CHANGE 10 : 13 @ 10 : 20 @@ -84,7 +85,12 @@ + private GameOverlayFramebuffer overlayFramebuffer; + private float eagPartialTicks = 0.0f; -> DELETE 2 @ 2 : 3 +> INSERT 1 : 3 @ 1 + ++ public float currentProjMatrixFOV = 0.0f; ++ + +> DELETE 1 @ 1 : 2 > CHANGE 9 : 10 @ 9 : 10 @@ -136,12 +142,12 @@ > CHANGE 6 : 7 @ 6 : 7 -~ f = this.mc.gameSettings.keyBindZoomCamera.isKeyDown() ? 17.0f : this.mc.gameSettings.fovSetting; +~ f = this.mc.isZoomKey ? this.mc.adjustedZoomValue : this.mc.gameSettings.fovSetting; > CHANGE 169 : 173 @ 169 : 172 ~ float farPlane = this.farPlaneDistance * 2.0f * MathHelper.SQRT_2; -~ GlStateManager.gluPerspective(this.getFOVModifier(partialTicks, true), +~ GlStateManager.gluPerspective(currentProjMatrixFOV = this.getFOVModifier(partialTicks, true), ~ (float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, farPlane); ~ DeferredStateManager.setGBufferNearFarPlanes(0.05f, farPlane); @@ -253,13 +259,18 @@ ~ return HString.format("Scaled: (%d, %d). Absolute: (%d, %d). Scale factor of %d", -> DELETE 15 @ 15 : 17 +> INSERT 9 : 11 @ 9 + ++ ++ this.mc.voiceOverlay.drawOverlay(); + +> DELETE 6 @ 6 : 8 > CHANGE 32 : 33 @ 32 : 33 ~ EaglercraftGPU.glLineWidth(1.0F); -> INSERT 25 : 33 @ 25 +> INSERT 25 : 35 @ 25 + + boolean fxaa = !this.mc.gameSettings.shaders @@ -269,6 +280,8 @@ + EffectPipelineFXAA.begin(this.mc.displayWidth, this.mc.displayHeight); + } + ++ VoiceTagRenderer.clearTagsDrawnSet(); ++ > CHANGE 4 : 5 @ 4 : 5 @@ -302,16 +315,17 @@ > DELETE 15 @ 15 : 17 -> CHANGE 12 : 14 @ 12 : 14 +> CHANGE 12 : 15 @ 12 : 14 -~ GlStateManager.gluPerspective(this.getFOVModifier(partialTicks, true), -~ (float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, this.farPlaneDistance * 4.0F); +~ float vigg = this.getFOVModifier(partialTicks, true); +~ GlStateManager.gluPerspective(vigg, (float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, +~ this.farPlaneDistance * 4.0F); -> CHANGE 4 : 5 @ 4 : 5 +> CHANGE 4 : 5 @ 4 : 6 -~ GlStateManager.gluPerspective(this.getFOVModifier(partialTicks, true), +~ GlStateManager.gluPerspective(vigg, (float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, -> INSERT 27 : 28 @ 27 +> INSERT 26 : 27 @ 26 + GlStateManager.disableBlend(); diff --git a/patches/minecraft/net/minecraft/client/renderer/RenderGlobal.edit.java b/patches/minecraft/net/minecraft/client/renderer/RenderGlobal.edit.java index 2ed0af3..128721b 100644 --- a/patches/minecraft/net/minecraft/client/renderer/RenderGlobal.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/RenderGlobal.edit.java @@ -64,8 +64,9 @@ ~ private final EaglerTextureAtlasSprite[] destroyBlockIcons = new EaglerTextureAtlasSprite[10]; -> CHANGE 11 : 12 @ 11 : 12 +> CHANGE 11 : 13 @ 11 : 12 +~ private float lastViewProjMatrixFOV = Float.MIN_VALUE; ~ private final ChunkUpdateManager renderDispatcher = new ChunkUpdateManager(); > CHANGE 22 : 24 @ 22 : 24 @@ -418,7 +419,16 @@ ~ return HString.format("C: %d/%d %sD: %d, %s", -> CHANGE 115 : 118 @ 115 : 117 +> CHANGE 53 : 55 @ 53 : 54 + +~ || (double) viewEntity.rotationYaw != this.lastViewEntityYaw +~ || this.mc.entityRenderer.currentProjMatrixFOV != this.lastViewProjMatrixFOV; + +> INSERT 5 : 6 @ 5 + ++ this.lastViewProjMatrixFOV = this.mc.entityRenderer.currentProjMatrixFOV; + +> CHANGE 56 : 59 @ 56 : 58 ~ EnumFacing[] facings = EnumFacing._VALUES; ~ for (int i = 0; i < facings.length; ++i) { diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/Render.edit.java b/patches/minecraft/net/minecraft/client/renderer/entity/Render.edit.java index da5b4b8..331e426 100644 --- a/patches/minecraft/net/minecraft/client/renderer/entity/Render.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/entity/Render.edit.java @@ -5,7 +5,7 @@ # Version: 1.0 # Author: lax1dude -> INSERT 2 : 8 @ 2 +> INSERT 2 : 11 @ 2 + import net.lax1dude.eaglercraft.v1_8.minecraft.EaglerTextureAtlasSprite; + import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU; @@ -13,8 +13,15 @@ + import net.lax1dude.eaglercraft.v1_8.opengl.WorldRenderer; + import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.DeferredStateManager; + import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.NameTagRenderer; ++ import net.lax1dude.eaglercraft.v1_8.voice.EnumVoiceChannelStatus; ++ import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController; ++ import net.lax1dude.eaglercraft.v1_8.voice.VoiceTagRenderer; -> DELETE 3 @ 3 : 4 +> INSERT 2 : 3 @ 2 + ++ import net.minecraft.client.entity.EntityOtherPlayerMP; + +> DELETE 1 @ 1 : 2 > DELETE 1 @ 1 : 2 @@ -35,7 +42,13 @@ + } + -> CHANGE 28 : 30 @ 28 : 30 +> INSERT 26 : 29 @ 26 + ++ if (entity.width == 0 || entity.height == 0) { ++ return; ++ } + +> CHANGE 2 : 4 @ 2 : 4 ~ EaglerTextureAtlasSprite textureatlassprite = texturemap.getAtlasSprite("minecraft:blocks/fire_layer_0"); ~ EaglerTextureAtlasSprite textureatlassprite1 = texturemap.getAtlasSprite("minecraft:blocks/fire_layer_1"); @@ -64,4 +77,14 @@ ~ EaglercraftGPU.glNormal3f(0.0F, 1.0F, 0.0F); +> INSERT 31 : 38 @ 31 + ++ ++ if (entityIn instanceof EntityOtherPlayerMP) { ++ if (VoiceClientController.getVoiceStatus() == EnumVoiceChannelStatus.CONNECTED) { ++ VoiceTagRenderer.renderVoiceNameTag(Minecraft.getMinecraft(), (EntityOtherPlayerMP) entityIn, b0); ++ } ++ } ++ + > EOF diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/RenderManager.edit.java b/patches/minecraft/net/minecraft/client/renderer/entity/RenderManager.edit.java index 51a4245..1dfb3d3 100644 --- a/patches/minecraft/net/minecraft/client/renderer/entity/RenderManager.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/entity/RenderManager.edit.java @@ -7,7 +7,7 @@ > DELETE 2 @ 2 : 3 -> INSERT 1 : 8 @ 1 +> INSERT 1 : 9 @ 1 + + import com.google.common.collect.Maps; @@ -16,19 +16,39 @@ + import net.lax1dude.eaglercraft.v1_8.opengl.OpenGlHelper; + import net.lax1dude.eaglercraft.v1_8.opengl.WorldRenderer; + import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.DeferredStateManager; ++ import net.lax1dude.eaglercraft.v1_8.profile.RenderHighPoly; -> DELETE 16 @ 16 : 18 +> INSERT 3 : 4 @ 3 + ++ import net.minecraft.client.Minecraft; + +> DELETE 13 @ 13 : 15 > DELETE 2 @ 2 : 3 > DELETE 1 @ 1 : 55 -> CHANGE 163 : 165 @ 163 : 164 +> INSERT 81 : 82 @ 81 + ++ private RenderPlayer eaglerRenderer; + +> CHANGE 82 : 88 @ 82 : 83 ~ this.skinMap.put("slim", new RenderPlayer(this, true, false)); ~ this.skinMap.put("zombie", new RenderPlayer(this, false, true)); +~ this.eaglerRenderer = new RenderHighPoly(this, this.playerRenderer.getMainModel(), +~ this.playerRenderer.shadowSize); +~ this.skinMap.put("eagler", +~ Minecraft.getMinecraft().gameSettings.enableFNAWSkins ? this.eaglerRenderer : this.playerRenderer); -> CHANGE 11 : 12 @ 11 : 12 +> INSERT 2 : 6 @ 2 + ++ public void setEnableFNAWSkins(boolean en) { ++ this.skinMap.put("eagler", en ? this.eaglerRenderer : this.playerRenderer); ++ } ++ + +> CHANGE 9 : 10 @ 9 : 10 ~ render = this.getEntityClassRenderObject((Class) parClass1.getSuperclass()); diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/RenderPlayer.edit.java b/patches/minecraft/net/minecraft/client/renderer/entity/RenderPlayer.edit.java index 3e69897..e02a7be 100644 --- a/patches/minecraft/net/minecraft/client/renderer/entity/RenderPlayer.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/entity/RenderPlayer.edit.java @@ -9,8 +9,9 @@ + import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; -> INSERT 2 : 3 @ 2 +> INSERT 2 : 4 @ 2 ++ import net.minecraft.client.model.ModelBase; + import net.minecraft.client.model.ModelBiped; > CHANGE 1 : 2 @ 1 : 4 @@ -36,10 +37,17 @@ > CHANGE 8 : 10 @ 8 : 10 -~ public ModelBiped getMainModel() { -~ return (ModelBiped) super.getMainModel(); +~ protected RenderPlayer(RenderManager renderManager, ModelBase modelBase, float size) { +~ super(renderManager, modelBase, size); -> CHANGE 16 : 17 @ 16 : 17 +> INSERT 2 : 6 @ 2 + ++ public ModelBiped getMainModel() { ++ return (ModelBiped) super.getMainModel(); ++ } ++ + +> CHANGE 14 : 15 @ 14 : 15 ~ ModelBiped modelplayer = this.getMainModel(); diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/RendererLivingEntity.edit.java b/patches/minecraft/net/minecraft/client/renderer/entity/RendererLivingEntity.edit.java index 300aeeb..ef2d7a8 100644 --- a/patches/minecraft/net/minecraft/client/renderer/entity/RendererLivingEntity.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/entity/RendererLivingEntity.edit.java @@ -41,7 +41,12 @@ ~ return this.layerRenderers.add((LayerRenderer) layer); -> DELETE 115 @ 115 : 116 +> CHANGE 104 : 106 @ 104 : 105 + +~ logger.error("Couldn\'t render entity"); +~ logger.error(exception); + +> DELETE 10 @ 10 : 11 > CHANGE 36 : 42 @ 36 : 37 diff --git a/patches/minecraft/net/minecraft/client/renderer/texture/TextureManager.edit.java b/patches/minecraft/net/minecraft/client/renderer/texture/TextureManager.edit.java index 587ce9e..1a5a73d 100644 --- a/patches/minecraft/net/minecraft/client/renderer/texture/TextureManager.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/texture/TextureManager.edit.java @@ -28,8 +28,8 @@ > CHANGE 13 : 22 @ 13 : 18 ~ int glTex; -~ if (resource.cachedPointer != null) { -~ TextureUtil.bindTexture(glTex = ((ITextureObject) resource.cachedPointer).getGlTextureId()); // unsafe, lol +~ if (resource.cachedPointerType == ResourceLocation.CACHED_POINTER_TEXTURE) { +~ TextureUtil.bindTexture(glTex = ((ITextureObject) resource.cachedPointer).getGlTextureId()); ~ } else { ~ Object object = (ITextureObject) this.mapTextureObjects.get(resource); ~ if (object == null) { @@ -37,9 +37,10 @@ ~ this.loadTexture(resource, (ITextureObject) object); ~ } -> CHANGE 1 : 15 @ 1 : 2 +> CHANGE 1 : 16 @ 1 : 2 ~ resource.cachedPointer = object; +~ resource.cachedPointerType = ResourceLocation.CACHED_POINTER_TEXTURE; ~ TextureUtil.bindTexture(glTex = ((ITextureObject) object).getGlTextureId()); ~ } ~ if (DeferredStateManager.isInDeferredPass()) { @@ -66,15 +67,17 @@ ~ return textureObj2.getClass().getName(); -> INSERT 5 : 6 @ 5 +> INSERT 5 : 7 @ 5 ++ textureLocation.cachedPointerType = ResourceLocation.CACHED_POINTER_TEXTURE; + textureLocation.cachedPointer = textureObj; -> CHANGE 5 : 10 @ 5 : 6 +> CHANGE 5 : 11 @ 5 : 6 -~ if (textureLocation.cachedPointer != null) { +~ if (textureLocation.cachedPointerType == ResourceLocation.CACHED_POINTER_TEXTURE) { ~ return (ITextureObject) textureLocation.cachedPointer; ~ } else { +~ textureLocation.cachedPointerType = ResourceLocation.CACHED_POINTER_TEXTURE; ~ return (ITextureObject) (textureLocation.cachedPointer = this.mapTextureObjects.get(textureLocation)); ~ } diff --git a/patches/minecraft/net/minecraft/client/settings/GameSettings.edit.java b/patches/minecraft/net/minecraft/client/settings/GameSettings.edit.java index 4e4720e..0c9ab50 100644 --- a/patches/minecraft/net/minecraft/client/settings/GameSettings.edit.java +++ b/patches/minecraft/net/minecraft/client/settings/GameSettings.edit.java @@ -14,10 +14,12 @@ > DELETE 1 @ 1 : 3 -> INSERT 3 : 24 @ 3 +> INSERT 3 : 27 @ 3 + + import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayManager; ++ import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController; ++ + import org.json.JSONArray; + + import com.google.common.collect.ImmutableSet; @@ -33,6 +35,7 @@ + import net.lax1dude.eaglercraft.v1_8.HString; + import net.lax1dude.eaglercraft.v1_8.Keyboard; + import net.lax1dude.eaglercraft.v1_8.Mouse; ++ import net.lax1dude.eaglercraft.v1_8.internal.EnumPlatformType; + import net.lax1dude.eaglercraft.v1_8.internal.KeyboardConstants; + import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; + import net.lax1dude.eaglercraft.v1_8.log4j.Logger; @@ -53,9 +56,9 @@ ~ public boolean fancyGraphics = false; ~ public int ambientOcclusion = 0; -> DELETE 8 @ 8 : 9 +> CHANGE 8 : 9 @ 8 : 11 -> DELETE 1 @ 1 : 2 +~ public boolean enableVsync = EagRuntime.getPlatformType() != EnumPlatformType.DESKTOP; > CHANGE 5 : 6 @ 5 : 6 @@ -88,7 +91,7 @@ ~ public int guiScale = 3; -> INSERT 3 : 16 @ 3 +> INSERT 3 : 17 @ 3 + public boolean hudFps = true; + public boolean hudCoords = true; @@ -103,9 +106,15 @@ + public boolean shadersAODisable = false; + public EaglerDeferredConfig deferredShaderConf = new EaglerDeferredConfig(); + public boolean enableUpdateSvc = true; ++ public boolean enableFNAWSkins = true; -> CHANGE 1 : 2 @ 1 : 2 +> CHANGE 1 : 7 @ 1 : 2 +~ public int voiceListenRadius = 16; +~ public float voiceListenVolume = 0.5f; +~ public float voiceSpeakVolume = 0.5f; +~ public int voicePTTKey = 47; // V +~ ~ public GameSettings(Minecraft mcIn) { > CHANGE 4 : 6 @ 4 : 7 @@ -148,7 +157,7 @@ > DELETE 20 @ 20 : 37 -> INSERT 13 : 53 @ 13 +> INSERT 13 : 62 @ 13 + if (parOptions == GameSettings.Options.HUD_FPS) { + this.hudFps = !this.hudFps; @@ -190,6 +199,15 @@ + this.mc.toggleFullscreen(); + } + ++ if (parOptions == GameSettings.Options.FNAW_SKINS) { ++ this.enableFNAWSkins = !this.enableFNAWSkins; ++ this.mc.getRenderManager().setEnableFNAWSkins(this.mc.getEnableFNAWSkins()); ++ } ++ ++ if (parOptions == GameSettings.Options.EAGLER_VSYNC) { ++ this.enableVsync = !this.enableVsync; ++ } ++ > CHANGE 23 : 24 @ 23 : 34 @@ -199,7 +217,7 @@ > DELETE 2 @ 2 : 4 -> INSERT 8 : 26 @ 8 +> INSERT 8 : 30 @ 8 + case HUD_COORDS: + return this.hudCoords; @@ -219,6 +237,10 @@ + return this.fog; + case FULLSCREEN: + return this.mc.isFullScreen(); ++ case FNAW_SKINS: ++ return this.enableFNAWSkins; ++ case EAGLER_VSYNC: ++ return this.enableVsync; > CHANGE 43 : 46 @ 43 : 47 @@ -318,9 +340,11 @@ ~ } ~ } -> DELETE 38 @ 38 : 42 +> CHANGE 38 : 39 @ 38 : 43 -> DELETE 4 @ 4 : 8 +~ if (astring[0].equals("enableVsyncEag")) { + +> DELETE 3 @ 3 : 7 > CHANGE 52 : 54 @ 52 : 54 @@ -395,7 +419,7 @@ > DELETE 2 @ 2 : 10 -> CHANGE 6 : 17 @ 6 : 7 +> CHANGE 6 : 31 @ 6 : 7 ~ if (astring[0].equals("shaders")) { ~ this.shaders = astring[1].equals("true"); @@ -405,7 +429,21 @@ ~ this.enableUpdateSvc = astring[1].equals("true"); ~ } ~ -~ Keyboard.setFunctionKeyModifier(keyBindFunction.getKeyCode()); +~ if (astring[0].equals("voiceListenRadius")) { +~ voiceListenRadius = Integer.parseInt(astring[1]); +~ } +~ +~ if (astring[0].equals("voiceListenVolume")) { +~ voiceListenVolume = this.parseFloat(astring[1]); +~ } +~ +~ if (astring[0].equals("voiceSpeakVolume")) { +~ voiceSpeakVolume = this.parseFloat(astring[1]); +~ } +~ +~ if (astring[0].equals("voicePTTKey")) { +~ voicePTTKey = Integer.parseInt(astring[1]); +~ } ~ ~ for (SoundCategory soundcategory : SoundCategory._VALUES) { @@ -413,14 +451,31 @@ ~ for (EnumPlayerModelParts enumplayermodelparts : EnumPlayerModelParts._VALUES) { -> INSERT 4 : 6 @ 4 +> INSERT 4 : 10 @ 4 ++ ++ if (astring[0].equals("enableFNAWSkins")) { ++ this.enableFNAWSkins = astring[1].equals("true"); ++ } + + deferredShaderConf.readOption(astring[0], astring[1]); -> DELETE 6 @ 6 : 7 +> CHANGE 6 : 13 @ 6 : 7 -> INSERT 11 : 20 @ 11 +~ +~ Keyboard.setFunctionKeyModifier(keyBindFunction.getKeyCode()); +~ VoiceClientController.setVoiceListenVolume(voiceListenVolume); +~ VoiceClientController.setVoiceSpeakVolume(voiceSpeakVolume); +~ VoiceClientController.setVoiceProximity(voiceListenRadius); +~ if (this.mc.getRenderManager() != null) +~ this.mc.getRenderManager().setEnableFNAWSkins(this.enableFNAWSkins); + +> CHANGE 1 : 3 @ 1 : 2 + +~ logger.error("Failed to load options"); +~ logger.error(exception); + +> INSERT 9 : 18 @ 9 + byte[] data = writeOptions(); + if (data != null) { @@ -448,13 +503,13 @@ ~ printwriter.println("resourcePacks:" + toJSONArray(this.resourcePacks)); ~ printwriter.println("incompatibleResourcePacks:" + toJSONArray(this.field_183018_l)); -> DELETE 8 @ 8 : 9 +> CHANGE 8 : 9 @ 8 : 11 -> DELETE 1 @ 1 : 2 +~ printwriter.println("enableVsyncEag:" + this.enableVsync); > DELETE 13 @ 13 : 24 -> INSERT 5 : 16 @ 5 +> INSERT 5 : 21 @ 5 + printwriter.println("hudFps:" + this.hudFps); + printwriter.println("hudWorld:" + this.hudWorld); @@ -467,6 +522,11 @@ + printwriter.println("fxaa:" + this.fxaa); + printwriter.println("shaders:" + this.shaders); + printwriter.println("enableUpdateSvc:" + this.enableUpdateSvc); ++ printwriter.println("voiceListenRadius:" + this.voiceListenRadius); ++ printwriter.println("voiceListenVolume:" + this.voiceListenVolume); ++ printwriter.println("voiceSpeakVolume:" + this.voiceSpeakVolume); ++ printwriter.println("voicePTTKey:" + this.voicePTTKey); ++ printwriter.println("enableFNAWSkins:" + this.enableFNAWSkins); > CHANGE 5 : 8 @ 5 : 6 @@ -487,9 +547,11 @@ + return bao.toByteArray(); -> INSERT 2 : 3 @ 2 +> CHANGE 1 : 4 @ 1 : 2 -+ return null; +~ logger.error("Failed to save options"); +~ logger.error(exception); +~ return null; > DELETE 2 @ 2 : 3 @@ -521,13 +583,15 @@ ~ TOUCHSCREEN("options.touchscreen", false, true), CHAT_SCALE("options.chat.scale", true, false), ~ CHAT_WIDTH("options.chat.width", true, false), CHAT_HEIGHT_FOCUSED("options.chat.height.focused", true, false), -> CHANGE 14 : 20 @ 14 : 15 +> CHANGE 14 : 22 @ 14 : 15 ~ ENTITY_SHADOWS("options.entityShadows", false, true), HUD_FPS("options.hud.fps", false, true), ~ HUD_COORDS("options.hud.coords", false, true), HUD_STATS("options.hud.stats", false, true), ~ HUD_WORLD("options.hud.world", false, true), HUD_PLAYER("options.hud.player", false, true), ~ HUD_24H("options.hud.24h", false, true), CHUNK_FIX("options.chunkFix", false, true), ~ FOG("options.fog", false, true), FXAA("options.fxaa", false, false), -~ FULLSCREEN("options.fullscreen", false, true), FAST_MATH("options.fastMath", false, false); +~ FULLSCREEN("options.fullscreen", false, true), +~ FNAW_SKINS("options.skinCustomisation.enableFNAWSkins", false, true), +~ EAGLER_VSYNC("options.vsync", false, true); > EOF diff --git a/patches/minecraft/net/minecraft/network/NetHandlerPlayServer.edit.java b/patches/minecraft/net/minecraft/network/NetHandlerPlayServer.edit.java index b39cfe1..be1699a 100644 --- a/patches/minecraft/net/minecraft/network/NetHandlerPlayServer.edit.java +++ b/patches/minecraft/net/minecraft/network/NetHandlerPlayServer.edit.java @@ -26,9 +26,10 @@ > DELETE 2 @ 2 : 3 -> INSERT 16 : 18 @ 16 +> INSERT 16 : 19 @ 16 + import net.lax1dude.eaglercraft.v1_8.sp.server.socket.IntegratedServerPlayerNetworkManager; ++ import net.lax1dude.eaglercraft.v1_8.sp.server.voice.IntegratedVoiceService; + > CHANGE 1 : 3 @ 1 : 3 @@ -186,12 +187,21 @@ + s = net.minecraft.util.StringUtils.translateControlCodesAlternate(s); + } -> INSERT 5 : 24 @ 5 +> INSERT 5 : 33 @ 5 + } else if ("EAG|Skins-1.8".equals(c17packetcustompayload.getChannelName())) { + byte[] r = new byte[c17packetcustompayload.getBufferData().readableBytes()]; + c17packetcustompayload.getBufferData().readBytes(r); + ((EaglerMinecraftServer) serverController).getSkinService().processPacket(r, playerEntity); ++ } else if ("EAG|Capes-1.8".equals(c17packetcustompayload.getChannelName())) { ++ byte[] r = new byte[c17packetcustompayload.getBufferData().readableBytes()]; ++ c17packetcustompayload.getBufferData().readBytes(r); ++ ((EaglerMinecraftServer) serverController).getCapeService().processPacket(r, playerEntity); ++ } else if ("EAG|Voice-1.8".equals(c17packetcustompayload.getChannelName())) { ++ IntegratedVoiceService vcs = ((EaglerMinecraftServer) serverController).getVoiceService(); ++ if (vcs != null) { ++ vcs.processPacket(c17packetcustompayload.getBufferData(), playerEntity); ++ } + } else if ("EAG|MyUpdCert-1.8".equals(c17packetcustompayload.getChannelName())) { + if (playerEntity.updateCertificate == null) { + PacketBuffer pb = c17packetcustompayload.getBufferData(); diff --git a/patches/minecraft/net/minecraft/network/login/client/C00PacketLoginStart.edit.java b/patches/minecraft/net/minecraft/network/login/client/C00PacketLoginStart.edit.java index bc905f1..2bcf0dd 100644 --- a/patches/minecraft/net/minecraft/network/login/client/C00PacketLoginStart.edit.java +++ b/patches/minecraft/net/minecraft/network/login/client/C00PacketLoginStart.edit.java @@ -13,32 +13,40 @@ ~ import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; -> INSERT 6 : 7 @ 6 +> INSERT 6 : 8 @ 6 + private byte[] skin; ++ private byte[] cape; > CHANGE 4 : 5 @ 4 : 5 -~ public C00PacketLoginStart(GameProfile profileIn, byte[] skin) { +~ public C00PacketLoginStart(GameProfile profileIn, byte[] skin, byte[] cape) { -> INSERT 1 : 2 @ 1 +> INSERT 1 : 3 @ 1 + this.skin = skin; ++ this.cape = cape; -> CHANGE 3 : 5 @ 3 : 4 +> CHANGE 3 : 6 @ 3 : 4 ~ this.profile = new GameProfile((EaglercraftUUID) null, parPacketBuffer.readStringFromBuffer(16)); ~ this.skin = parPacketBuffer.readByteArray(); +~ this.cape = parPacketBuffer.readableBytes() > 0 ? parPacketBuffer.readByteArray() : null; -> INSERT 4 : 5 @ 4 +> INSERT 4 : 6 @ 4 + parPacketBuffer.writeByteArray(this.skin); ++ parPacketBuffer.writeByteArray(this.cape); -> INSERT 9 : 13 @ 9 +> INSERT 9 : 17 @ 9 + + public byte[] getSkin() { + return this.skin; + } ++ ++ public byte[] getCape() { ++ return this.cape; ++ } > EOF diff --git a/patches/minecraft/net/minecraft/server/management/ServerConfigurationManager.edit.java b/patches/minecraft/net/minecraft/server/management/ServerConfigurationManager.edit.java index 760bd88..99168d7 100644 --- a/patches/minecraft/net/minecraft/server/management/ServerConfigurationManager.edit.java +++ b/patches/minecraft/net/minecraft/server/management/ServerConfigurationManager.edit.java @@ -26,9 +26,11 @@ + import net.minecraft.util.ChatComponentText; -> CHANGE 12 : 15 @ 12 : 14 +> CHANGE 12 : 17 @ 12 : 14 +~ import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerMinecraftServer; ~ import net.lax1dude.eaglercraft.v1_8.sp.server.socket.IntegratedServerPlayerNetworkManager; +~ import net.lax1dude.eaglercraft.v1_8.sp.server.voice.IntegratedVoiceService; ~ import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; ~ import net.lax1dude.eaglercraft.v1_8.log4j.Logger; @@ -119,7 +121,17 @@ ~ this.playerStatFiles.remove(entityplayermp.getName()); -> CHANGE 6 : 13 @ 6 : 13 +> INSERT 2 : 9 @ 2 + ++ ((EaglerMinecraftServer) mcServer).getSkinService().unregisterPlayer(uuid); ++ ((EaglerMinecraftServer) mcServer).getCapeService().unregisterPlayer(uuid); ++ IntegratedVoiceService vcs = ((EaglerMinecraftServer) mcServer).getVoiceService(); ++ if (vcs != null) { ++ vcs.handlePlayerLoggedOut(playerIn); ++ } ++ + +> CHANGE 4 : 11 @ 4 : 11 ~ public String allowUserToConnect(GameProfile gameprofile) { ~ return this.playerEntityList.size() >= this.maxPlayers && !this.func_183023_f(gameprofile) diff --git a/patches/minecraft/net/minecraft/server/network/NetHandlerLoginServer.edit.java b/patches/minecraft/net/minecraft/server/network/NetHandlerLoginServer.edit.java index 9d6def6..fc78fa7 100644 --- a/patches/minecraft/net/minecraft/server/network/NetHandlerLoginServer.edit.java +++ b/patches/minecraft/net/minecraft/server/network/NetHandlerLoginServer.edit.java @@ -22,9 +22,10 @@ > DELETE 2 @ 2 : 3 -> INSERT 2 : 4 @ 2 +> INSERT 2 : 5 @ 2 + import net.lax1dude.eaglercraft.v1_8.sp.server.socket.IntegratedServerPlayerNetworkManager; ++ import net.lax1dude.eaglercraft.v1_8.sp.server.voice.IntegratedVoiceService; + > CHANGE 1 : 3 @ 1 : 3 @@ -42,9 +43,10 @@ ~ public final IntegratedServerPlayerNetworkManager networkManager; -> INSERT 3 : 4 @ 3 +> INSERT 3 : 5 @ 3 + private byte[] loginSkinPacket; ++ private byte[] loginCapePacket; > DELETE 1 @ 1 : 2 @@ -53,10 +55,18 @@ ~ public NetHandlerLoginServer(MinecraftServer parMinecraftServer, ~ IntegratedServerPlayerNetworkManager parNetworkManager) { -> INSERT 15 : 17 @ 15 +> INSERT 15 : 25 @ 15 + ((EaglerMinecraftServer) field_181025_l.mcServer).getSkinService() + .processLoginPacket(this.loginSkinPacket, field_181025_l); ++ if (this.loginCapePacket != null) { ++ ((EaglerMinecraftServer) field_181025_l.mcServer).getCapeService() ++ .processLoginPacket(this.loginCapePacket, field_181025_l); ++ } ++ IntegratedVoiceService svc = ((EaglerMinecraftServer) field_181025_l.mcServer).getVoiceService(); ++ if (svc != null) { ++ svc.handlePlayerLoggedIn(entityplayermp); ++ } > CHANGE 23 : 24 @ 23 : 29 @@ -68,22 +78,31 @@ + this.networkManager.setConnectionState(EnumConnectionState.PLAY); -> CHANGE 6 : 10 @ 6 : 8 +> CHANGE 6 : 18 @ 6 : 8 ~ entityplayermp = this.server.getConfigurationManager().createPlayerForUser(this.loginGameProfile); ~ this.server.getConfigurationManager().initializeConnectionToPlayer(this.networkManager, entityplayermp); ~ ((EaglerMinecraftServer) entityplayermp.mcServer).getSkinService() ~ .processLoginPacket(this.loginSkinPacket, entityplayermp); +~ if (this.loginCapePacket != null) { +~ ((EaglerMinecraftServer) entityplayermp.mcServer).getCapeService() +~ .processLoginPacket(this.loginCapePacket, entityplayermp); +~ } +~ IntegratedVoiceService svc = ((EaglerMinecraftServer) entityplayermp.mcServer).getVoiceService(); +~ if (svc != null) { +~ svc.handlePlayerLoggedIn(entityplayermp); +~ } > CHANGE 11 : 13 @ 11 : 13 ~ ? this.loginGameProfile.toString() + " (channel:" + this.networkManager.playerChannel + ")" ~ : ("channel:" + this.networkManager.playerChannel); -> CHANGE 5 : 8 @ 5 : 14 +> CHANGE 5 : 9 @ 5 : 14 ~ this.loginGameProfile = this.getOfflineProfile(c00packetloginstart.getProfile()); ~ this.loginSkinPacket = c00packetloginstart.getSkin(); +~ this.loginCapePacket = c00packetloginstart.getCape(); ~ this.currentLoginState = NetHandlerLoginServer.LoginState.READY_TO_ACCEPT; > DELETE 3 @ 3 : 15 diff --git a/patches/minecraft/net/minecraft/util/ResourceLocation.edit.java b/patches/minecraft/net/minecraft/util/ResourceLocation.edit.java index b2ee3b0..176a479 100644 --- a/patches/minecraft/net/minecraft/util/ResourceLocation.edit.java +++ b/patches/minecraft/net/minecraft/util/ResourceLocation.edit.java @@ -5,9 +5,14 @@ # Version: 1.0 # Author: lax1dude -> INSERT 8 : 10 @ 8 +> INSERT 8 : 15 @ 8 + public Object cachedPointer = null; ++ public int cachedPointerType = 0; ++ ++ public static final int CACHED_POINTER_NONE = 0; ++ public static final int CACHED_POINTER_TEXTURE = 1; ++ public static final int CACHED_POINTER_EAGLER_MESH = 2; + > EOF diff --git a/patches/minecraft/net/minecraft/world/World.edit.java b/patches/minecraft/net/minecraft/world/World.edit.java index d813e51..3db4ee2 100644 --- a/patches/minecraft/net/minecraft/world/World.edit.java +++ b/patches/minecraft/net/minecraft/world/World.edit.java @@ -113,13 +113,15 @@ ~ for (int j = 0, l = this.loadedEntityList.size(); j < l; ++j) { ~ Entity entity = this.loadedEntityList.get(j); -> CHANGE 102 : 105 @ 102 : 103 +> CHANGE 102 : 107 @ 102 : 104 ~ EnumFacing[] facings = EnumFacing._VALUES; +~ BlockPos tmp = new BlockPos(0, 0, 0); ~ for (int k = 0; k < facings.length; ++k) { ~ EnumFacing enumfacing = facings[k]; +~ int j = this.getRedstonePower(pos.offsetEvenFaster(enumfacing, tmp), enumfacing); -> CHANGE 60 : 61 @ 60 : 61 +> CHANGE 59 : 60 @ 59 : 60 ~ public EntityPlayer getPlayerEntityByUUID(EaglercraftUUID uuid) { diff --git a/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.edit.java b/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.edit.java index 865eadb..9405f6c 100644 --- a/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.edit.java +++ b/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.edit.java @@ -32,11 +32,31 @@ + } else { + ++EaglerMinecraftServer.counterChunkRead; -> INSERT 54 : 55 @ 54 +> CHANGE 32 : 34 @ 32 : 33 + +~ logger.error("Couldn\'t load chunk"); +~ logger.error(exception); + +> CHANGE 10 : 12 @ 10 : 11 + +~ logger.error("Couldn\'t save entities"); +~ logger.error(exception); + +> INSERT 10 : 11 @ 10 + ++EaglerMinecraftServer.counterChunkWrite; -> CHANGE 36 : 37 @ 36 : 37 +> CHANGE 1 : 3 @ 1 : 2 + +~ logger.error("Couldn\'t save chunk"); +~ logger.error(ioexception); + +> CHANGE 1 : 3 @ 1 : 3 + +~ logger.error("Couldn\'t save chunk; already in use by another instance of Minecraft?"); +~ logger.error(minecraftexception); + +> CHANGE 31 : 32 @ 31 : 32 ~ for (int j = 0, l = arraylist.size(); j < l; ++j) { diff --git a/patches/resources/assets/minecraft/lang/en_US.edit.lang b/patches/resources/assets/minecraft/lang/en_US.edit.lang index 24aa5e3..6639281 100644 --- a/patches/resources/assets/minecraft/lang/en_US.edit.lang +++ b/patches/resources/assets/minecraft/lang/en_US.edit.lang @@ -10,9 +10,9 @@ ~ eaglercraft.recording.unsupported=Recording Unsupported! ~ eaglercraft.recording.stop=Stop Recording ~ eaglercraft.recording.start=Record Screen... -~ eaglercraft.soundCategory.voice=Voice +~ eaglercraft.soundCategory.voice=Recording Voice -> INSERT 1 : 230 @ 1 +> INSERT 1 : 238 @ 1 + eaglercraft.resourcePack.prompt.title=What do you want to do with '%s'? + eaglercraft.resourcePack.prompt.text=Tip: Hold Shift to skip this screen when selecting a resource pack! @@ -37,6 +37,14 @@ + eaglercraft.editProfile.playerSkin=Player Skin + eaglercraft.editProfile.addSkin=Add Skin + eaglercraft.editProfile.clearSkin=Clear List ++ eaglercraft.editProfile.capes=Capes ++ eaglercraft.editProfile.disableFNAW=(Note: go to 'Options...' > 'Skin Customization' to disable FNAW skins) ++ eaglercraft.editProfile.enableFNAW=(Note: go to 'Options...' > 'Skin Customization' to enable FNAW skins) ++ ++ eaglercraft.editCape.title=Edit Cape ++ eaglercraft.editCape.playerCape=Player Cape ++ eaglercraft.editCape.addCape=Add Cape ++ eaglercraft.editCape.clearCape=Clear List + + eaglercraft.editProfile.importExport=Import/Export + @@ -244,7 +252,7 @@ + eaglercraft.command.clientStub=This command is client side! + -> INSERT 163 : 350 @ 163 +> INSERT 163 : 404 @ 163 + eaglercraft.singleplayer.busy.killTask=Cancel Task + eaglercraft.singleplayer.busy.cancelWarning=Are you sure? @@ -433,6 +441,60 @@ + eaglercraft.updateList.note.0=Note: Updates are digitally signed, EaglercraftX will block any + eaglercraft.updateList.note.1=updates that were not created by lax1dude or ayunami2000 + ++ eaglercraft.voice.title=Voice Channel ++ eaglercraft.voice.titleNoVoice=Voice is disabled on this server ++ eaglercraft.voice.titleVoiceUnavailable=Voice is unavailable ++ eaglercraft.voice.titleVoiceBrowserError=(browser issue) ++ eaglercraft.voice.ptt=Press '%s' to speak ++ eaglercraft.voice.pttChangeDesc=(Press Any Key) ++ eaglercraft.voice.changeKey=Change ++ eaglercraft.voice.off=OFF ++ eaglercraft.voice.radius=NEARBY ++ eaglercraft.voice.global=GLOBAL ++ eaglercraft.voice.volumeTitle=Change Volume ++ eaglercraft.voice.volumeListen=Speakers Volume: ++ eaglercraft.voice.volumeSpeak=Microphone Volume: ++ eaglercraft.voice.radiusTitle=Change Listener Radius ++ eaglercraft.voice.radiusLabel=Players Within: ++ eaglercraft.voice.radiusChange=change ++ eaglercraft.voice.notConnected=Not Connected ++ eaglercraft.voice.connecting=Connecting... ++ eaglercraft.voice.unavailable=Could not connect! ++ eaglercraft.voice.connectedGlobal=Connected - Global ++ eaglercraft.voice.connectedRadius=Connected - $f$Within $radius$m ++ eaglercraft.voice.playersListening=Players Listening: ++ eaglercraft.voice.muted=Players Muted: ++ eaglercraft.voice.unmute=unmute ++ eaglercraft.voice.mute=mute ++ eaglercraft.voice.apply=Apply ++ eaglercraft.voice.volumeSpeakerLabel=Speakers: ++ eaglercraft.voice.volumeMicrophoneLabel=Microphone: ++ ++ eaglercraft.voice.unsupportedWarning1=Voice Warning ++ eaglercraft.voice.unsupportedWarning2=Your network's firewall may not support ++ eaglercraft.voice.unsupportedWarning3=eaglercraft's voice chat. ++ eaglercraft.voice.unsupportedWarning4=If your game doesn't work it's your issue ++ eaglercraft.voice.unsupportedWarning5=to solve, not ayunami2000's or lax1dude's. ++ eaglercraft.voice.unsupportedWarning6=Don't ask them to 'fix' it for you because ++ eaglercraft.voice.unsupportedWarning7=they won't help you fix a problem that only ++ eaglercraft.voice.unsupportedWarning8=you or your network's administrator has the ++ eaglercraft.voice.unsupportedWarning9=ability to correctly resolve. ++ eaglercraft.voice.unsupportedWarning10=Continue ++ eaglercraft.voice.unsupportedWarning11=Cancel ++ ++ eaglercraft.voice.ipGrabWarning1=IP Logger Warning ++ eaglercraft.voice.ipGrabWarning2=Using Eaglercraft voice chat may allow your ++ eaglercraft.voice.ipGrabWarning3=IP address to be logged by other players ++ eaglercraft.voice.ipGrabWarning4=also using voice on the server. ++ eaglercraft.voice.ipGrabWarning5=This issue will not be fixed, it is an ++ eaglercraft.voice.ipGrabWarning6=internal browser issue, not a mistake in the ++ eaglercraft.voice.ipGrabWarning7=game. Fortunately, this can only be done if ++ eaglercraft.voice.ipGrabWarning8=the other player uses a hacked web browser ++ eaglercraft.voice.ipGrabWarning9=or has Wireshark to capture the voice ++ eaglercraft.voice.ipGrabWarning10=packets, as there exists no real javascript ++ eaglercraft.voice.ipGrabWarning11=method to log IPs using a normal skidded ++ eaglercraft.voice.ipGrabWarning12=eaglercraft hacked client. ++ > CHANGE 22 : 23 @ 22 : 23 @@ -442,7 +504,11 @@ ~ lanServer.start=Start Shared World -> CHANGE 280 : 281 @ 280 : 281 +> INSERT 175 : 176 @ 175 + ++ options.skinCustomisation.enableFNAWSkins=Show FNAW Skins + +> CHANGE 105 : 106 @ 105 : 106 ~ resourcePack.openFolder=Open resource pack diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAssets.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAssets.java index ba16a63..03785ec 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAssets.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAssets.java @@ -60,6 +60,9 @@ public class PlatformAssets { public static final ImageData loadImageFile(InputStream data) { try { BufferedImage img = ImageIO.read(data); + if(img == null) { + throw new IOException("Data is not a supported image format!"); + } int w = img.getWidth(); int h = img.getHeight(); boolean a = img.getColorModel().hasAlpha(); diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformBufferFunctions.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformBufferFunctions.java index 9adf74c..6cab7cc 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformBufferFunctions.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformBufferFunctions.java @@ -21,10 +21,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer; public class PlatformBufferFunctions { public static void put(ByteBuffer newBuffer, ByteBuffer flip) { - int len = flip.remaining(); - for(int i = 0; i < len; ++i) { - newBuffer.put(flip.get()); - } + newBuffer.put(flip); } public static void put(IntBuffer intBuffer, int index, int[] data) { diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java index ece3fff..e8ae9da 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java @@ -1,13 +1,11 @@ package net.lax1dude.eaglercraft.v1_8.internal; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; -import net.lax1dude.eaglercraft.v1_8.internal.vfs2.EaglerFileSystemException; -import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFSIterator2.BreakLoop; +import net.lax1dude.eaglercraft.v1_8.internal.lwjgl.DebugFilesystem; +import net.lax1dude.eaglercraft.v1_8.internal.lwjgl.JDBCFilesystem; +import net.lax1dude.eaglercraft.v1_8.internal.lwjgl.JDBCFilesystemConverter; import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; import net.lax1dude.eaglercraft.v1_8.log4j.Logger; @@ -30,183 +28,104 @@ public class PlatformFilesystem { public static final Logger logger = LogManager.getLogger("PlatformFilesystem"); - public static final File filesystemRoot = (new File("filesystem/sp")).getAbsoluteFile(); + public static final File debugFilesystemRoot = (new File("filesystem/sp")).getAbsoluteFile(); + + private static IFilesystemProvider provider = null; + + public static String jdbcUri = null; + public static String jdbcDriver = null; public static void initialize() { - if(!filesystemRoot.isDirectory() && !filesystemRoot.mkdirs()) { - throw new EaglerFileSystemException("Could not create directory for virtual filesystem: " + filesystemRoot.getAbsolutePath()); + if(provider == null) { + if(jdbcUri != null && jdbcDriver != null) { + provider = JDBCFilesystem.initialize(jdbcUri, jdbcDriver); + if(((JDBCFilesystem)provider).isNewFilesystem() && debugFilesystemRoot.isDirectory() && debugFilesystemRoot.list().length > 0) { + JDBCFilesystemConverter.convertFilesystem("Converting filesystem, please wait...", debugFilesystemRoot, provider, true); + } + }else { + provider = DebugFilesystem.initialize(debugFilesystemRoot); + } } } + public static void setUseJDBC(String uri) { + jdbcUri = uri; + } + + public static void setJDBCDriverClass(String driver) { + jdbcDriver = driver; + } + + public static interface IFilesystemProvider { + + boolean eaglerDelete(String pathName); + + ByteBuffer eaglerRead(String pathName); + + void eaglerWrite(String pathName, ByteBuffer data); + + boolean eaglerExists(String pathName); + + boolean eaglerMove(String pathNameOld, String pathNameNew); + + int eaglerCopy(String pathNameOld, String pathNameNew); + + int eaglerSize(String pathName); + + void eaglerIterate(String pathName, VFSFilenameIterator itr, boolean recursive); + + } + + private static void throwNotInitialized() { + throw new UnsupportedOperationException("Filesystem has not been initialized!"); + } + public static boolean eaglerDelete(String pathName) { - File f = getJREFile(pathName); - if(!f.exists()) { - logger.warn("Tried to delete file that doesn't exist: \"{}\"", pathName); - return false; - } - if(f.delete()) { - deleteParentIfEmpty(f); - return true; - } - return false; + if(provider == null) throwNotInitialized(); + return provider.eaglerDelete(pathName); } public static ByteBuffer eaglerRead(String pathName) { - File f = getJREFile(pathName); - if(f.isFile()) { - long fileSize = f.length(); - if(fileSize > 2147483647L) throw new EaglerFileSystemException("Too large: " + fileSize + " @ " + f.getAbsolutePath()); - ByteBuffer buf = PlatformRuntime.allocateByteBuffer((int)fileSize); - try(FileInputStream is = new FileInputStream(f)) { - byte[] copyBuffer = new byte[4096]; - int i; - while((i = is.read(copyBuffer, 0, copyBuffer.length)) != -1) { - buf.put(copyBuffer, 0, i); - } - if(buf.remaining() > 0) { - throw new EaglerFileSystemException("ERROR: " + buf.remaining() + " bytes are remaining after reading: " + f.getAbsolutePath()); - } - buf.flip(); - ByteBuffer tmp = buf; - buf = null; - return tmp; - }catch (IOException e) { - throw new EaglerFileSystemException("Failed to read: " + f.getAbsolutePath(), e); - }catch(ArrayIndexOutOfBoundsException ex) { - throw new EaglerFileSystemException("ERROR: Expected " + fileSize + " bytes, buffer overflow reading: " + f.getAbsolutePath(), ex); - }finally { - if(buf != null) { - PlatformRuntime.freeByteBuffer(buf); - } - } - }else { - logger.warn("Tried to read file that doesn't exist: \"{}\"", f.getAbsolutePath()); - return null; - } + if(provider == null) throwNotInitialized(); + return provider.eaglerRead(pathName); } public static void eaglerWrite(String pathName, ByteBuffer data) { - File f = getJREFile(pathName); - File p = f.getParentFile(); - if(!p.isDirectory()) { - if(!p.mkdirs()) { - throw new EaglerFileSystemException("Could not create parent directory: " + p.getAbsolutePath()); - } - } - try(FileOutputStream fos = new FileOutputStream(f)) { - byte[] copyBuffer = new byte[Math.min(4096, data.remaining())]; - int i; - while((i = data.remaining()) > 0) { - if(i > copyBuffer.length) { - i = copyBuffer.length; - } - data.get(copyBuffer, 0, i); - fos.write(copyBuffer, 0, i); - } - }catch (IOException e) { - throw new EaglerFileSystemException("Failed to write: " + f.getAbsolutePath(), e); - } + if(provider == null) throwNotInitialized(); + provider.eaglerWrite(pathName, data); } public static boolean eaglerExists(String pathName) { - return getJREFile(pathName).isFile(); + if(provider == null) throwNotInitialized(); + return provider.eaglerExists(pathName); } public static boolean eaglerMove(String pathNameOld, String pathNameNew) { - File f1 = getJREFile(pathNameOld); - File f2 = getJREFile(pathNameNew); - if(f2.exists()) { - logger.warn("Tried to rename file \"{}\" to \"{}\" which already exists! File will be replaced"); - if(!f2.delete()) { - return false; - } - } - if(f1.renameTo(f2)) { - deleteParentIfEmpty(f1); - return true; - } - return false; + if(provider == null) throwNotInitialized(); + return provider.eaglerMove(pathNameOld, pathNameNew); } public static int eaglerCopy(String pathNameOld, String pathNameNew) { - File f1 = getJREFile(pathNameOld); - File f2 = getJREFile(pathNameNew); - if(!f1.isFile()) { - return -1; - } - if(f2.isDirectory()) { - throw new EaglerFileSystemException("Destination file is a directory: " + f2.getAbsolutePath()); - } - File p = f2.getParentFile(); - if(!p.isDirectory()) { - if(!p.mkdirs()) { - throw new EaglerFileSystemException("Could not create parent directory: " + p.getAbsolutePath()); - } - } - int sz = 0; - try(FileInputStream is = new FileInputStream(f1)) { - try(FileOutputStream os = new FileOutputStream(f2)) { - byte[] copyBuffer = new byte[4096]; - int i; - while((i = is.read(copyBuffer, 0, copyBuffer.length)) != -1) { - os.write(copyBuffer, 0, i); - sz += i; - } - } - }catch (IOException e) { - throw new EaglerFileSystemException("Failed to copy \"" + f1.getAbsolutePath() + "\" to file \"" + f2.getAbsolutePath() + "\"", e); - } - return sz; + if(provider == null) throwNotInitialized(); + return provider.eaglerCopy(pathNameOld, pathNameNew); } public static int eaglerSize(String pathName) { - File f = getJREFile(pathName); - if(f.isFile()) { - long fileSize = f.length(); - if(fileSize > 2147483647L) throw new EaglerFileSystemException("Too large: " + fileSize + " @ " + f.getAbsolutePath()); - return (int)fileSize; - }else { - return -1; - } + if(provider == null) throwNotInitialized(); + return provider.eaglerSize(pathName); } public static void eaglerIterate(String pathName, VFSFilenameIterator itr, boolean recursive) { - try { - iterateFile(pathName, getJREFile(pathName), itr, recursive); - }catch(BreakLoop ex) { - } + if(provider == null) throwNotInitialized(); + provider.eaglerIterate(pathName, itr, recursive); } - private static void iterateFile(String pathName, File f, VFSFilenameIterator itr, boolean recursive) { - if(!f.exists()) { - return; - } - if(!f.isDirectory()) { - itr.next(pathName); - return; - } - File[] fa = f.listFiles(); - for(int i = 0; i < fa.length; ++i) { - File ff = fa[i]; - String fn = pathName + "/" + ff.getName(); - if(ff.isDirectory()) { - if(recursive) { - iterateFile(fn, ff, itr, true); - } - }else { - itr.next(fn); + public static void platformShutdown() { + if(provider != null) { + if(provider instanceof JDBCFilesystem) { + ((JDBCFilesystem)provider).shutdown(); } - } - } - - private static File getJREFile(String path) { - return new File(filesystemRoot, path); - } - - private static void deleteParentIfEmpty(File f) { - String[] s; - while((f = f.getParentFile()) != null && (s = f.list()) != null && s.length == 0) { - f.delete(); + provider = null; } } } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java index 074cace..d15647e 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java @@ -57,7 +57,10 @@ public class PlatformInput { public static boolean lockKeys = false; private static final List keyboardCharList = new LinkedList(); - + + private static boolean vsync = true; + private static boolean glfwVSyncState = false; + private static class KeyboardEvent { protected final int key; @@ -214,8 +217,16 @@ public class PlatformInput { return glfwWindowShouldClose(win); } + public static void setVSync(boolean enable) { + vsync = enable; + } + public static void update() { glfwPollEvents(); + if(vsync != glfwVSyncState) { + glfwSwapInterval(vsync ? 1 : 0); + glfwVSyncState = vsync; + } glfwSwapBuffers(win); } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java index 4abaaf7..7fffdcf 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java @@ -7,6 +7,8 @@ import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer; import static org.lwjgl.opengles.GLES30.*; +import org.lwjgl.opengles.GLESCapabilities; + /** * Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved. * @@ -24,6 +26,12 @@ import static org.lwjgl.opengles.GLES30.*; */ public class PlatformOpenGL { + private static boolean hasLinearHDR32FSupport = false; + + static void setCurrentContext(GLESCapabilities caps) { + hasLinearHDR32FSupport = caps.GL_OES_texture_float_linear; + } + public static final void _wglEnable(int glEnum) { glEnable(glEnum); } @@ -269,6 +277,12 @@ public class PlatformOpenGL { data == null ? 0l : EaglerLWJGLAllocator.getAddress(data)); } + public static final void _wglTexImage2Df32(int target, int level, int internalFormat, int width, int height, + int border, int format, int type, ByteBuffer data) { + nglTexImage2D(target, level, internalFormat, width, height, border, format, type, + data == null ? 0l : EaglerLWJGLAllocator.getAddress(data)); + } + public static final void _wglTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data) { nglTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, @@ -523,4 +537,7 @@ public class PlatformOpenGL { return true; } + public static final boolean checkLinearHDR32FSupport() { + return hasLinearHDR32FSupport; + } } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java index 702e60a..5d108c5 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java @@ -171,7 +171,7 @@ public class PlatformRuntime { EGL.createDisplayCapabilities(glfw_eglHandle, major[0], minor[0]); glfwMakeContextCurrent(windowHandle); - GLES.createCapabilities(); + PlatformOpenGL.setCurrentContext(GLES.createCapabilities()); logger.info("OpenGL Version: {}", (glVersion = GLES30.glGetString(GLES30.GL_VERSION))); logger.info("OpenGL Renderer: {}", (glRenderer = GLES30.glGetString(GLES30.GL_RENDERER))); @@ -245,6 +245,7 @@ public class PlatformRuntime { public static void destroy() { PlatformAudio.platformShutdown(); + PlatformFilesystem.platformShutdown(); GLES.destroy(); EGL.destroy(); glfwDestroyWindow(windowHandle); @@ -340,15 +341,27 @@ public class PlatformRuntime { public static class NativeNIO { public static java.nio.ByteBuffer allocateByteBuffer(int length) { - return MemoryUtil.memByteBuffer(JEmalloc.nje_malloc(length), length); + long ret = JEmalloc.nje_malloc(length); + if(ret == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + return MemoryUtil.memByteBuffer(ret, length); } public static java.nio.IntBuffer allocateIntBuffer(int length) { - return MemoryUtil.memIntBuffer(JEmalloc.nje_malloc(length << 2), length); + long ret = JEmalloc.nje_malloc(length << 2); + if(ret == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + return MemoryUtil.memIntBuffer(ret, length); } public static java.nio.FloatBuffer allocateFloatBuffer(int length) { - return MemoryUtil.memFloatBuffer(JEmalloc.nje_malloc(length << 2), length); + long ret = JEmalloc.nje_malloc(length << 2); + if(ret == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + return MemoryUtil.memFloatBuffer(ret, length); } public static java.nio.IntBuffer getIntBuffer(java.nio.ByteBuffer byteBuffer) { diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java new file mode 100644 index 0000000..5020d8f --- /dev/null +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java @@ -0,0 +1,116 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.voice.EnumVoiceChannelPeerState; +import net.lax1dude.eaglercraft.v1_8.voice.EnumVoiceChannelReadyState; + +/** + * Copyright (c) 2022-2024 ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class PlatformVoiceClient { + + public static void initialize() { + + } + + public static void initializeDevices() { + + } + + public static boolean isSupported() { + return false; + } + + public static void setVoiceListenVolume(float f) { + + } + + public static void setVoiceSpeakVolume(float f) { + + } + + public static void activateVoice(boolean talk) { + + } + + public static void setICEServers(String[] servs) { + + } + + public static void signalConnect(EaglercraftUUID user, boolean offer) { + + } + + public static void signalDisconnect(EaglercraftUUID user, boolean b) { + + } + + public static void signalICECandidate(EaglercraftUUID user, String ice) { + + } + + public static void signalDescription(EaglercraftUUID user, String desc) { + + } + + public static void tickVoiceClient() { + + } + + public static void updateVoicePosition(EaglercraftUUID uuid, double x, double y, double z) { + + } + + public static void resetPeerStates() { + + } + + public static void setVoiceProximity(int prox) { + + } + + public static void setMicVolume(float f) { + + } + + public static void mutePeer(EaglercraftUUID uuid, boolean mute) { + + } + + public static EnumVoiceChannelPeerState getPeerState() { + return EnumVoiceChannelPeerState.LOADING; + } + + public static EnumVoiceChannelReadyState getReadyState() { + return EnumVoiceChannelReadyState.NONE; + } + + public static EnumVoiceChannelPeerState getPeerStateConnect() { + return EnumVoiceChannelPeerState.LOADING; + } + + public static EnumVoiceChannelPeerState getPeerStateInitial() { + return EnumVoiceChannelPeerState.LOADING; + } + + public static EnumVoiceChannelPeerState getPeerStateDesc() { + return EnumVoiceChannelPeerState.LOADING; + } + + public static EnumVoiceChannelPeerState getPeerStateIce() { + return EnumVoiceChannelPeerState.LOADING; + } + +} diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLAllocator.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLAllocator.java index 8167730..e531404 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLAllocator.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLAllocator.java @@ -3,7 +3,7 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; import org.lwjgl.system.jemalloc.JEmalloc; /** - * Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -26,19 +26,35 @@ public class EaglerLWJGLAllocator { } public static ByteBuffer allocByteBuffer(int len) { - return new EaglerLWJGLByteBuffer(JEmalloc.nje_malloc(len), len, true); + long ret = JEmalloc.nje_malloc(len); + if(ret == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + return new EaglerLWJGLByteBuffer(ret, len, true); } public static ShortBuffer allocShortBuffer(int len) { - return new EaglerLWJGLShortBuffer(JEmalloc.nje_malloc(len << 1), len, true); + long ret = JEmalloc.nje_malloc(len << 1); + if(ret == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + return new EaglerLWJGLShortBuffer(ret, len, true); } public static IntBuffer allocIntBuffer(int len) { - return new EaglerLWJGLIntBuffer(JEmalloc.nje_malloc(len << 2), len, true); + long ret = JEmalloc.nje_malloc(len << 2); + if(ret == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + return new EaglerLWJGLIntBuffer(ret, len, true); } public static FloatBuffer allocFloatBuffer(int len) { - return new EaglerLWJGLFloatBuffer(JEmalloc.nje_malloc(len << 2), len, true); + long ret = JEmalloc.nje_malloc(len << 2); + if(ret == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + return new EaglerLWJGLFloatBuffer(ret, len, true); } public static void freeByteBuffer(ByteBuffer buffer) { diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLByteBuffer.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLByteBuffer.java index d21926f..5cb1142 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLByteBuffer.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLByteBuffer.java @@ -1,10 +1,12 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.jemalloc.JEmalloc; +import net.lax1dude.unsafememcpy.UnsafeMemcpy; +import net.lax1dude.unsafememcpy.UnsafeUtils; + /** - * Copyright (c) 2022 lax1dude. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -104,35 +106,33 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public byte get() { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - return MemoryUtil.memGetByte(address + position++); + return UnsafeUtils.getMemByte(address + position++); } @Override public ByteBuffer put(byte b) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutByte(address + position++, b); + UnsafeUtils.setMemByte(address + position++, b); return this; } @Override public byte get(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetByte(address + index); + return UnsafeUtils.getMemByte(address + index); } @Override public ByteBuffer put(int index, byte b) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutByte(address + index, b); + UnsafeUtils.setMemByte(address + index, b); return this; } @Override public ByteBuffer get(byte[] dst, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dst[offset + i] = MemoryUtil.memGetByte(address + position + i); - } + UnsafeMemcpy.memcpy(dst, offset, address + position, length); position += length; return this; } @@ -140,9 +140,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public ByteBuffer get(byte[] dst) { if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1); - for(int i = 0; i < dst.length; ++i) { - dst[position + i] = MemoryUtil.memGetByte(address + position + i); - } + UnsafeMemcpy.memcpy(dst, 0, address + position, dst.length); position += dst.length; return this; } @@ -153,14 +151,14 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { EaglerLWJGLByteBuffer c = (EaglerLWJGLByteBuffer)src; int l = c.limit - c.position; if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); - MemoryUtil.memCopy(c.address + c.position, address + position, l); + UnsafeMemcpy.memcpy(address + position, c.address + c.position, l); position += l; c.position += l; }else { int l = src.remaining(); if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); for(int i = 0; i < l; ++i) { - MemoryUtil.memPutByte(address + position + l, src.get()); + UnsafeUtils.setMemByte(address + position + l, src.get()); } position += l; } @@ -170,9 +168,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public ByteBuffer put(byte[] src, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - MemoryUtil.memPutByte(address + position + i, src[offset + i]); - } + UnsafeMemcpy.memcpy(address + position, src, offset, length); position += length; return this; } @@ -180,9 +176,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public ByteBuffer put(byte[] src) { if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1); - for(int i = 0; i < src.length; ++i) { - MemoryUtil.memPutByte(address + position + i, src[i]); - } + UnsafeMemcpy.memcpy(address + position, src, 0, src.length); position += src.length; return this; } @@ -203,7 +197,10 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { int newLen = limit - position; long newAlloc = JEmalloc.nje_malloc(newLen); - MemoryUtil.memCopy(address + position, newAlloc, newLen); + if(newAlloc == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + UnsafeMemcpy.memcpy(newAlloc, address + position, newLen); return new EaglerLWJGLByteBuffer(newAlloc, newLen, true); } @@ -211,7 +208,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public char getChar() { if(position + 2 > limit) throw new ArrayIndexOutOfBoundsException(position); - char c = (char)MemoryUtil.memGetShort(address + position); + char c = UnsafeUtils.getMemChar(address + position); position += 2; return c; } @@ -219,7 +216,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public ByteBuffer putChar(char value) { if(position + 2 > limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutShort(address + position, (short)value); + UnsafeUtils.setMemChar(address + position, value); position += 2; return this; } @@ -227,20 +224,20 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public char getChar(int index) { if(index + 2 > limit) throw new ArrayIndexOutOfBoundsException(index); - return (char)MemoryUtil.memGetShort(address + index); + return UnsafeUtils.getMemChar(address + index); } @Override public ByteBuffer putChar(int index, char value) { if(index + 2 > limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutShort(address + index, (short)value); + UnsafeUtils.setMemChar(address + index, value); return this; } @Override public short getShort() { if(position + 2 > limit) throw new ArrayIndexOutOfBoundsException(position); - short s = MemoryUtil.memGetShort(address + position); + short s = UnsafeUtils.getMemShort(address + position); position += 2; return s; } @@ -248,7 +245,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public ByteBuffer putShort(short value) { if(position + 2 > limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutShort(address + position, value); + UnsafeUtils.setMemShort(address + position, value); position += 2; return this; } @@ -256,13 +253,13 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public short getShort(int index) { if(index + 2 > limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetShort(address + index); + return UnsafeUtils.getMemShort(address + index); } @Override public ByteBuffer putShort(int index, short value) { if(index + 2 > limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutShort(address + index, value); + UnsafeUtils.setMemShort(address + index, value); return this; } @@ -274,7 +271,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public int getInt() { if(position + 4 > limit) throw new ArrayIndexOutOfBoundsException(position); - int i = MemoryUtil.memGetInt(address + position); + int i = UnsafeUtils.getMemInt(address + position); position += 4; return i; } @@ -282,7 +279,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public ByteBuffer putInt(int value) { if(position + 4 > limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutInt(address + position, value); + UnsafeUtils.setMemInt(address + position, value); position += 4; return this; } @@ -290,13 +287,13 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public int getInt(int index) { if(index + 4 > limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetInt(address + index); + return UnsafeUtils.getMemInt(address + index); } @Override public ByteBuffer putInt(int index, int value) { if(index + 4 > limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutInt(address + index, value); + UnsafeUtils.setMemInt(address + index, value); return this; } @@ -308,7 +305,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public long getLong() { if(position + 8 > limit) throw new ArrayIndexOutOfBoundsException(position); - long l = MemoryUtil.memGetLong(address + position); + long l = UnsafeUtils.getMemLong(address + position); position += 8; return l; } @@ -316,7 +313,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public ByteBuffer putLong(long value) { if(position + 8 > limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutLong(address + position, value); + UnsafeUtils.setMemLong(address + position, value); position += 8; return this; } @@ -324,20 +321,20 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public long getLong(int index) { if(index + 8 > limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetLong(address + index); + return UnsafeUtils.getMemLong(address + index); } @Override public ByteBuffer putLong(int index, long value) { if(index + 8 > limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutLong(address + index, value); + UnsafeUtils.setMemLong(address + index, value); return this; } @Override public float getFloat() { if(position + 4 > limit) throw new ArrayIndexOutOfBoundsException(position); - float f = MemoryUtil.memGetFloat(address + position); + float f = UnsafeUtils.getMemFloat(address + position); position += 4; return f; } @@ -345,7 +342,7 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public ByteBuffer putFloat(float value) { if(position + 4 > limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutFloat(address + position, value); + UnsafeUtils.setMemFloat(address + position, value); position += 4; return this; } @@ -353,13 +350,13 @@ public class EaglerLWJGLByteBuffer implements ByteBuffer { @Override public float getFloat(int index) { if(index + 4 > limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetFloat(address + index); + return UnsafeUtils.getMemFloat(address + index); } @Override public ByteBuffer putFloat(int index, float value) { if(index + 4 > limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutFloat(address + index, value); + UnsafeUtils.setMemFloat(address + index, value); return this; } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLFloatBuffer.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLFloatBuffer.java index 5110c17..a163fe7 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLFloatBuffer.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLFloatBuffer.java @@ -1,10 +1,12 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.jemalloc.JEmalloc; +import net.lax1dude.unsafememcpy.UnsafeMemcpy; +import net.lax1dude.unsafememcpy.UnsafeUtils; + /** - * Copyright (c) 2022 lax1dude. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -106,47 +108,45 @@ public class EaglerLWJGLFloatBuffer implements FloatBuffer { @Override public float get() { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - return MemoryUtil.memGetFloat(address + ((position++) << SHIFT)); + return UnsafeUtils.getMemFloat(address + ((position++) << SHIFT)); } @Override public FloatBuffer put(float b) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutFloat(address + ((position++) << SHIFT), b); + UnsafeUtils.setMemFloat(address + ((position++) << SHIFT), b); return this; } @Override public float get(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetFloat(address + (index << SHIFT)); + return UnsafeUtils.getMemFloat(address + (index << SHIFT)); } @Override public FloatBuffer put(int index, float b) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutFloat(address + (index << SHIFT), b); + UnsafeUtils.setMemFloat(address + (index << SHIFT), b); return this; } @Override public float getElement(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetFloat(address + (index << SHIFT)); + return UnsafeUtils.getMemFloat(address + (index << SHIFT)); } @Override public void putElement(int index, float value) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutFloat(address + ((position++) << SHIFT), value); + UnsafeUtils.setMemFloat(address + ((position++) << SHIFT), value); } @Override public FloatBuffer get(float[] dst, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dst[offset + i] = MemoryUtil.memGetFloat(address + ((position + i) << SHIFT)); - } + UnsafeMemcpy.memcpyAlignDst(dst, offset << SHIFT, address + (position << SHIFT), length); position += length; return this; } @@ -154,9 +154,7 @@ public class EaglerLWJGLFloatBuffer implements FloatBuffer { @Override public FloatBuffer get(float[] dst) { if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1); - for(int i = 0; i < dst.length; ++i) { - dst[i] = MemoryUtil.memGetFloat(address + ((position + i) << SHIFT)); - } + UnsafeMemcpy.memcpyAlignDst(dst, 0, address + (position << SHIFT), dst.length); position += dst.length; return this; } @@ -167,14 +165,14 @@ public class EaglerLWJGLFloatBuffer implements FloatBuffer { EaglerLWJGLFloatBuffer c = (EaglerLWJGLFloatBuffer)src; int l = c.limit - c.position; if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); - MemoryUtil.memCopy(c.address + (c.position << SHIFT), address + (position << SHIFT), l << SHIFT); + UnsafeMemcpy.memcpy(address + (position << SHIFT), c.address + (c.position << SHIFT), l << SHIFT); position += l; c.position += l; }else { int l = src.remaining(); if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); for(int i = 0; i < l; ++i) { - MemoryUtil.memPutFloat(address + ((position + l) << SHIFT), src.get()); + UnsafeUtils.setMemFloat(address + ((position + l) << SHIFT), src.get()); } position += l; } @@ -184,9 +182,7 @@ public class EaglerLWJGLFloatBuffer implements FloatBuffer { @Override public FloatBuffer put(float[] src, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - MemoryUtil.memPutFloat(address + ((position + i) << SHIFT), src[offset + i]); - } + UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset << SHIFT, length); position += length; return this; } @@ -194,9 +190,7 @@ public class EaglerLWJGLFloatBuffer implements FloatBuffer { @Override public FloatBuffer put(float[] src) { if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1); - for(int i = 0; i < src.length; ++i) { - MemoryUtil.memPutFloat(address + ((position + i) << SHIFT), src[i]); - } + UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, 0, src.length); position += src.length; return this; } @@ -217,7 +211,10 @@ public class EaglerLWJGLFloatBuffer implements FloatBuffer { int newLen = limit - position; long newAlloc = JEmalloc.nje_malloc(newLen); - MemoryUtil.memCopy(address + (position << SHIFT), newAlloc, newLen << SHIFT); + if(newAlloc == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + UnsafeMemcpy.memcpy(newAlloc, address + (position << SHIFT), newLen << SHIFT); return new EaglerLWJGLFloatBuffer(newAlloc, newLen, true); } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLIntBuffer.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLIntBuffer.java index 6471eed..bf306a2 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLIntBuffer.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLIntBuffer.java @@ -1,10 +1,12 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.jemalloc.JEmalloc; +import net.lax1dude.unsafememcpy.UnsafeMemcpy; +import net.lax1dude.unsafememcpy.UnsafeUtils; + /** - * Copyright (c) 2022 lax1dude. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -106,47 +108,45 @@ public class EaglerLWJGLIntBuffer implements IntBuffer { @Override public int get() { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - return MemoryUtil.memGetInt(address + ((position++) << SHIFT)); + return UnsafeUtils.getMemInt(address + ((position++) << SHIFT)); } @Override public IntBuffer put(int b) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutInt(address + ((position++) << SHIFT), b); + UnsafeUtils.setMemInt(address + ((position++) << SHIFT), b); return this; } @Override public int get(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetInt(address + (index << SHIFT)); + return UnsafeUtils.getMemInt(address + (index << SHIFT)); } @Override public IntBuffer put(int index, int b) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutInt(address + (index << SHIFT), b); + UnsafeUtils.setMemInt(address + (index << SHIFT), b); return this; } @Override public int getElement(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetInt(address + (index << SHIFT)); + return UnsafeUtils.getMemInt(address + (index << SHIFT)); } @Override public void putElement(int index, int value) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutInt(address + (index << SHIFT), value); + UnsafeUtils.setMemInt(address + (index << SHIFT), value); } @Override public IntBuffer get(int[] dst, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dst[offset + i] = MemoryUtil.memGetInt(address + ((position + i) << SHIFT)); - } + UnsafeMemcpy.memcpyAlignDst(dst, offset << SHIFT, address + (position << SHIFT), length); position += length; return this; } @@ -154,9 +154,7 @@ public class EaglerLWJGLIntBuffer implements IntBuffer { @Override public IntBuffer get(int[] dst) { if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1); - for(int i = 0; i < dst.length; ++i) { - dst[i] = MemoryUtil.memGetInt(address + ((position + i) << SHIFT)); - } + UnsafeMemcpy.memcpyAlignDst(dst, 0, address + (position << SHIFT), dst.length); position += dst.length; return this; } @@ -167,14 +165,14 @@ public class EaglerLWJGLIntBuffer implements IntBuffer { EaglerLWJGLIntBuffer c = (EaglerLWJGLIntBuffer)src; int l = c.limit - c.position; if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); - MemoryUtil.memCopy(c.address + (c.position << SHIFT), address + (position << SHIFT), l << SHIFT); + UnsafeMemcpy.memcpy(address + (position << SHIFT), c.address + (c.position << SHIFT), l << SHIFT); position += l; c.position += l; }else { int l = src.remaining(); if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); for(int i = 0; i < l; ++i) { - MemoryUtil.memPutInt(address + ((position + l) << SHIFT), src.get()); + UnsafeUtils.setMemInt(address + ((position + l) << SHIFT), src.get()); } position += l; } @@ -184,9 +182,7 @@ public class EaglerLWJGLIntBuffer implements IntBuffer { @Override public IntBuffer put(int[] src, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - MemoryUtil.memPutInt(address + ((position + i) << SHIFT), src[offset + i]); - } + UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset << SHIFT, length); position += length; return this; } @@ -194,9 +190,7 @@ public class EaglerLWJGLIntBuffer implements IntBuffer { @Override public IntBuffer put(int[] src) { if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1); - for(int i = 0; i < src.length; ++i) { - MemoryUtil.memPutInt(address + ((position + i) << SHIFT), src[i]); - } + UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, 0, src.length); position += src.length; return this; } @@ -217,7 +211,10 @@ public class EaglerLWJGLIntBuffer implements IntBuffer { int newLen = limit - position; long newAlloc = JEmalloc.nje_malloc(newLen); - MemoryUtil.memCopy(address + (position << SHIFT), newAlloc, newLen << SHIFT); + if(newAlloc == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + UnsafeMemcpy.memcpy(newAlloc, address + (position << SHIFT), newLen << SHIFT); return new EaglerLWJGLIntBuffer(newAlloc, newLen, true); } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLShortBuffer.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLShortBuffer.java index 01e0198..eba363d 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLShortBuffer.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerLWJGLShortBuffer.java @@ -1,10 +1,12 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.jemalloc.JEmalloc; +import net.lax1dude.unsafememcpy.UnsafeMemcpy; +import net.lax1dude.unsafememcpy.UnsafeUtils; + /** - * Copyright (c) 2022 lax1dude. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -106,47 +108,45 @@ public class EaglerLWJGLShortBuffer implements ShortBuffer { @Override public short get() { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - return MemoryUtil.memGetShort(address + ((position++) << SHIFT)); + return UnsafeUtils.getMemShort(address + ((position++) << SHIFT)); } @Override public ShortBuffer put(short b) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - MemoryUtil.memPutShort(address + ((position++) << SHIFT), b); + UnsafeUtils.setMemShort(address + ((position++) << SHIFT), b); return this; } @Override public short get(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetShort(address + (index << SHIFT)); + return UnsafeUtils.getMemShort(address + (index << SHIFT)); } @Override public ShortBuffer put(int index, short b) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutShort(address + (index << SHIFT), b); + UnsafeUtils.setMemShort(address + (index << SHIFT), b); return this; } @Override public short getElement(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return MemoryUtil.memGetShort(address + (index << SHIFT)); + return UnsafeUtils.getMemShort(address + (index << SHIFT)); } @Override public void putElement(int index, short value) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - MemoryUtil.memPutShort(address + (index << SHIFT), value); + UnsafeUtils.setMemShort(address + (index << SHIFT), value); } @Override public ShortBuffer get(short[] dst, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dst[offset + i] = MemoryUtil.memGetShort(address + ((position + i) << SHIFT)); - } + UnsafeMemcpy.memcpyAlignDst(dst, offset << SHIFT, address + (position << SHIFT), length); position += length; return this; } @@ -154,9 +154,7 @@ public class EaglerLWJGLShortBuffer implements ShortBuffer { @Override public ShortBuffer get(short[] dst) { if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1); - for(int i = 0; i < dst.length; ++i) { - dst[i] = MemoryUtil.memGetShort(address + ((position + i) << SHIFT)); - } + UnsafeMemcpy.memcpyAlignDst(dst, 0, address + (position << SHIFT), dst.length); position += dst.length; return this; } @@ -167,14 +165,14 @@ public class EaglerLWJGLShortBuffer implements ShortBuffer { EaglerLWJGLShortBuffer c = (EaglerLWJGLShortBuffer)src; int l = c.limit - c.position; if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); - MemoryUtil.memCopy(c.address + (c.position << SHIFT), address + (position << SHIFT), l << SHIFT); + UnsafeMemcpy.memcpy(address + (position << SHIFT), c.address + (c.position << SHIFT), l << SHIFT); position += l; c.position += l; }else { int l = src.remaining(); if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); for(int i = 0; i < l; ++i) { - MemoryUtil.memPutShort(address + ((position + l) << SHIFT), src.get()); + UnsafeUtils.setMemInt(address + ((position + l) << SHIFT), src.get()); } position += l; } @@ -184,9 +182,7 @@ public class EaglerLWJGLShortBuffer implements ShortBuffer { @Override public ShortBuffer put(short[] src, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - MemoryUtil.memPutShort(address + ((position + i) << SHIFT), src[offset + i]); - } + UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, offset << SHIFT, length); position += length; return this; } @@ -194,9 +190,7 @@ public class EaglerLWJGLShortBuffer implements ShortBuffer { @Override public ShortBuffer put(short[] src) { if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1); - for(int i = 0; i < src.length; ++i) { - MemoryUtil.memPutShort(address + ((position + i) << SHIFT), src[i]); - } + UnsafeMemcpy.memcpyAlignSrc(address + (position << SHIFT), src, 0, src.length); position += src.length; return this; } @@ -217,7 +211,10 @@ public class EaglerLWJGLShortBuffer implements ShortBuffer { int newLen = limit - position; long newAlloc = JEmalloc.nje_malloc(newLen); - MemoryUtil.memCopy(address + (position << SHIFT), newAlloc, newLen << SHIFT); + if(newAlloc == 0l) { + throw new OutOfMemoryError("Native je_malloc call returned null pointer!"); + } + UnsafeMemcpy.memcpy(newAlloc, address + (position << SHIFT), newLen << SHIFT); return new EaglerLWJGLShortBuffer(newAlloc, newLen, true); } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DebugFilesystem.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DebugFilesystem.java new file mode 100644 index 0000000..a5ee8df --- /dev/null +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DebugFilesystem.java @@ -0,0 +1,224 @@ +package net.lax1dude.eaglercraft.v1_8.internal.lwjgl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.VFSFilenameIterator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.EaglerFileSystemException; +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFSIterator2.BreakLoop; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class DebugFilesystem implements PlatformFilesystem.IFilesystemProvider { + + public static DebugFilesystem initialize(File filesystemRoot) { + if(!filesystemRoot.isDirectory() && !filesystemRoot.mkdirs()) { + throw new EaglerFileSystemException("Could not create directory for virtual filesystem: " + filesystemRoot.getAbsolutePath()); + } + return new DebugFilesystem(filesystemRoot); + } + + private final File filesystemRoot; + + private DebugFilesystem(File root) { + this.filesystemRoot = root; + } + + @Override + public boolean eaglerDelete(String pathName) { + File f = getJREFile(pathName); + if(!f.exists()) { + PlatformFilesystem.logger.warn("Tried to delete file that doesn't exist: \"{}\"", pathName); + return false; + } + if(f.delete()) { + deleteParentIfEmpty(f); + return true; + } + return false; + } + + @Override + public ByteBuffer eaglerRead(String pathName) { + File f = getJREFile(pathName); + if(f.isFile()) { + long fileSize = f.length(); + if(fileSize > 2147483647L) throw new EaglerFileSystemException("Too large: " + fileSize + " @ " + f.getAbsolutePath()); + ByteBuffer buf = PlatformRuntime.allocateByteBuffer((int)fileSize); + try(FileInputStream is = new FileInputStream(f)) { + byte[] copyBuffer = new byte[4096]; + int i; + while((i = is.read(copyBuffer, 0, copyBuffer.length)) != -1) { + buf.put(copyBuffer, 0, i); + } + if(buf.remaining() > 0) { + throw new EaglerFileSystemException("ERROR: " + buf.remaining() + " bytes are remaining after reading: " + f.getAbsolutePath()); + } + buf.flip(); + ByteBuffer tmp = buf; + buf = null; + return tmp; + }catch (IOException e) { + throw new EaglerFileSystemException("Failed to read: " + f.getAbsolutePath(), e); + }catch(ArrayIndexOutOfBoundsException ex) { + throw new EaglerFileSystemException("ERROR: Expected " + fileSize + " bytes, buffer overflow reading: " + f.getAbsolutePath(), ex); + }finally { + if(buf != null) { + PlatformRuntime.freeByteBuffer(buf); + } + } + }else { + PlatformFilesystem.logger.warn("Tried to read file that doesn't exist: \"{}\"", f.getAbsolutePath()); + return null; + } + } + + @Override + public void eaglerWrite(String pathName, ByteBuffer data) { + File f = getJREFile(pathName); + File p = f.getParentFile(); + if(!p.isDirectory()) { + if(!p.mkdirs()) { + throw new EaglerFileSystemException("Could not create parent directory: " + p.getAbsolutePath()); + } + } + try(FileOutputStream fos = new FileOutputStream(f)) { + byte[] copyBuffer = new byte[Math.min(4096, data.remaining())]; + int i; + while((i = data.remaining()) > 0) { + if(i > copyBuffer.length) { + i = copyBuffer.length; + } + data.get(copyBuffer, 0, i); + fos.write(copyBuffer, 0, i); + } + }catch (IOException e) { + throw new EaglerFileSystemException("Failed to write: " + f.getAbsolutePath(), e); + } + } + + @Override + public boolean eaglerExists(String pathName) { + return getJREFile(pathName).isFile(); + } + + @Override + public boolean eaglerMove(String pathNameOld, String pathNameNew) { + File f1 = getJREFile(pathNameOld); + File f2 = getJREFile(pathNameNew); + if(f2.exists()) { + PlatformFilesystem.logger.warn("Tried to rename file \"{}\" to \"{}\" which already exists! File will be replaced"); + if(!f2.delete()) { + return false; + } + } + if(f1.renameTo(f2)) { + deleteParentIfEmpty(f1); + return true; + } + return false; + } + + @Override + public int eaglerCopy(String pathNameOld, String pathNameNew) { + File f1 = getJREFile(pathNameOld); + File f2 = getJREFile(pathNameNew); + if(!f1.isFile()) { + return -1; + } + if(f2.isDirectory()) { + throw new EaglerFileSystemException("Destination file is a directory: " + f2.getAbsolutePath()); + } + File p = f2.getParentFile(); + if(!p.isDirectory()) { + if(!p.mkdirs()) { + throw new EaglerFileSystemException("Could not create parent directory: " + p.getAbsolutePath()); + } + } + int sz = 0; + try(FileInputStream is = new FileInputStream(f1)) { + try(FileOutputStream os = new FileOutputStream(f2)) { + byte[] copyBuffer = new byte[4096]; + int i; + while((i = is.read(copyBuffer, 0, copyBuffer.length)) != -1) { + os.write(copyBuffer, 0, i); + sz += i; + } + } + }catch (IOException e) { + throw new EaglerFileSystemException("Failed to copy \"" + f1.getAbsolutePath() + "\" to file \"" + f2.getAbsolutePath() + "\"", e); + } + return sz; + } + + @Override + public int eaglerSize(String pathName) { + File f = getJREFile(pathName); + if(f.isFile()) { + long fileSize = f.length(); + if(fileSize > 2147483647L) throw new EaglerFileSystemException("Too large: " + fileSize + " @ " + f.getAbsolutePath()); + return (int)fileSize; + }else { + return -1; + } + } + + @Override + public void eaglerIterate(String pathName, VFSFilenameIterator itr, boolean recursive) { + try { + iterateFile(pathName, getJREFile(pathName), itr, recursive); + }catch(BreakLoop ex) { + } + } + + private void iterateFile(String pathName, File f, VFSFilenameIterator itr, boolean recursive) { + if(!f.exists()) { + return; + } + if(!f.isDirectory()) { + itr.next(pathName); + return; + } + File[] fa = f.listFiles(); + for(int i = 0; i < fa.length; ++i) { + File ff = fa[i]; + String fn = pathName + "/" + ff.getName(); + if(ff.isDirectory()) { + if(recursive) { + iterateFile(fn, ff, itr, true); + } + }else { + itr.next(fn); + } + } + } + + private File getJREFile(String path) { + return new File(filesystemRoot, path); + } + + private void deleteParentIfEmpty(File f) { + String[] s; + while((f = f.getParentFile()) != null && (s = f.list()) != null && s.length == 0) { + f.delete(); + } + } +} diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DesktopClientConfigAdapter.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DesktopClientConfigAdapter.java index 26b1be6..3ce88b5 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DesktopClientConfigAdapter.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DesktopClientConfigAdapter.java @@ -124,4 +124,9 @@ public class DesktopClientConfigAdapter implements IClientConfigAdapter { return false; } + @Override + public boolean isAllowVoiceClient() { + return false; + } + } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/FilesystemConvertingDialog.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/FilesystemConvertingDialog.java new file mode 100644 index 0000000..4b23727 --- /dev/null +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/FilesystemConvertingDialog.java @@ -0,0 +1,78 @@ +package net.lax1dude.eaglercraft.v1_8.internal.lwjgl; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import java.awt.Color; +import java.awt.BorderLayout; +import javax.swing.JProgressBar; +import java.awt.Dimension; +import java.awt.Toolkit; + +import javax.swing.JLabel; +import javax.swing.SwingConstants; +import javax.swing.UIManager; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class FilesystemConvertingDialog extends JFrame { + + private static final long serialVersionUID = 1L; + private JPanel contentPane; + private JProgressBar progressBar; + + public FilesystemConvertingDialog(String title) { + setIconImage(Toolkit.getDefaultToolkit().getImage("icon32.png")); + setResizable(false); + setAlwaysOnTop(true); + setTitle("EaglercraftX 1.8"); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + setBounds(100, 100, 420, 100); + contentPane = new JPanel(); + contentPane.setBackground(new Color(255, 255, 255)); + contentPane.setBorder(null); + + setContentPane(contentPane); + contentPane.setLayout(new BorderLayout(0, 0)); + + JPanel panel = new JPanel(); + panel.setBorder(new EmptyBorder(10, 10, 10, 10)); + panel.setBackground(new Color(255, 255, 255)); + contentPane.add(panel, BorderLayout.SOUTH); + panel.setLayout(new BorderLayout(0, 0)); + + progressBar = new JProgressBar(); + progressBar.setIndeterminate(true); + progressBar.setPreferredSize(new Dimension(146, 20)); + progressBar.setMinimum(0); + progressBar.setMaximum(512); + panel.add(progressBar, BorderLayout.CENTER); + + JLabel lblNewLabel = new JLabel(title); + lblNewLabel.setFont(UIManager.getFont("PopupMenu.font")); + lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER); + contentPane.add(lblNewLabel, BorderLayout.CENTER); + } + + public void setProgressIndeterminate(boolean itr) { + progressBar.setIndeterminate(itr); + } + + public void setProgressValue(int val) { + progressBar.setValue(val); + } + +} diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/JDBCFilesystem.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/JDBCFilesystem.java new file mode 100644 index 0000000..c6aae92 --- /dev/null +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/JDBCFilesystem.java @@ -0,0 +1,441 @@ +package net.lax1dude.eaglercraft.v1_8.internal.lwjgl; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.Map.Entry; +import java.util.Properties; + +import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem.IFilesystemProvider; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem; +import net.lax1dude.eaglercraft.v1_8.internal.VFSFilenameIterator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.EaglerFileSystemException; +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFSIterator2; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class JDBCFilesystem implements IFilesystemProvider { + + public static final Logger logger = LogManager.getLogger("JDBCFilesystem"); + + private boolean newFilesystem = true; + + private static volatile boolean cleanupThreadStarted = false; + private static final Collection jdbcFilesystems = new LinkedList(); + + private final String jdbcUri; + private final String jdbcDriver; + + private final Connection conn; + private final PreparedStatement createStatement; + private final PreparedStatement updateStatement; + private final PreparedStatement readStatement; + private final PreparedStatement existsStatement; + private final PreparedStatement sizeStatement; + private final PreparedStatement deleteStatement; + private final PreparedStatement renameStatement; + private final PreparedStatement iterateNonRecursive; + private final PreparedStatement iterateRecursive; + private boolean hasClosed = false; + + private final Object mutex = new Object(); + + public static IFilesystemProvider initialize(String jdbcUri, String jdbcDriver) { + Class driver; + try { + driver = Class.forName(jdbcDriver); + } catch (ClassNotFoundException e) { + throw new EaglerFileSystemException("JDBC driver class not found in JRE: " + jdbcDriver, e); + } + Driver driverObj = null; + Enumeration registeredDriversItr = DriverManager.getDrivers(); + while(registeredDriversItr.hasMoreElements()) { + Driver drv = registeredDriversItr.nextElement(); + if(drv.getClass().equals(driver)) { + driverObj = drv; + break; + } + } + if(driverObj == null) { + logger.warn("The class \"{}\" is not a registered JDBC driver, eaglercraft will try all registered drivers...", jdbcDriver); + } + Properties props = new Properties(); + for(Entry etr : System.getProperties().entrySet()) { + if(etr.getKey() instanceof String) { + String str = (String)etr.getKey(); + if(str.startsWith("eagler.jdbc.opts.")) { + props.put(str.substring(17), etr.getValue()); + } + } + } + logger.info("Connecting to database: \"{}\"", jdbcUri); + Connection conn; + try { + if(driverObj != null) { + conn = driverObj.connect(jdbcUri, props); + }else { + conn = DriverManager.getConnection(jdbcUri, props); + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("Failed to connect to database: \"" + jdbcUri + "\"", ex); + } + try { + return new JDBCFilesystem(conn, jdbcUri, jdbcDriver); + } catch (SQLException ex) { + try { + conn.close(); + }catch(SQLException ex2) { + } + throw new EaglerFileSystemException("Failed to initialize database: \"" + jdbcUri + "\"", ex); + } + } + + private JDBCFilesystem(Connection conn, String jdbcUri, String jdbcDriver) throws SQLException { + this.conn = conn; + this.jdbcUri = jdbcUri; + this.jdbcDriver = jdbcDriver; + try(Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE IF NOT EXISTS " + + "\"eaglercraft_desktop_runtime_filesystem\" (" + + "\"FileName\" VARCHAR(1024) NOT NULL," + + "\"FileSize\" INT NOT NULL," + + "\"FileData\" BLOB NOT NULL," + + "PRIMARY KEY(\"FileName\"))"); + + int totalFiles = 0; + try(ResultSet resultSet = stmt.executeQuery("SELECT COUNT(*) AS total_files FROM eaglercraft_desktop_runtime_filesystem")) { + if(resultSet.next()) { + totalFiles = resultSet.getInt(1); + } + } + logger.info("Loaded JDBC filesystem with {} files: \"{}\"", totalFiles, jdbcUri); + if(totalFiles > 0) { + newFilesystem = false; + } + } + this.createStatement = conn.prepareStatement("INSERT INTO eaglercraft_desktop_runtime_filesystem (FileName, FileSize, FileData) VALUES(?,?,?)"); + this.updateStatement = conn.prepareStatement("UPDATE eaglercraft_desktop_runtime_filesystem SET FileSize = ?, FileData = ? WHERE FileName = ?"); + this.readStatement = conn.prepareStatement("SELECT FileData FROM eaglercraft_desktop_runtime_filesystem WHERE FileName = ? LIMIT 1"); + this.existsStatement = conn.prepareStatement("SELECT COUNT(FileName) AS has_object FROM eaglercraft_desktop_runtime_filesystem WHERE FileName = ? LIMIT 1"); + this.sizeStatement = conn.prepareStatement("SELECT FileSize FROM eaglercraft_desktop_runtime_filesystem WHERE FileName = ? LIMIT 1"); + this.deleteStatement = conn.prepareStatement("DELETE FROM eaglercraft_desktop_runtime_filesystem WHERE FileName = ?"); + this.renameStatement = conn.prepareStatement("UPDATE eaglercraft_desktop_runtime_filesystem SET FileName = ? WHERE FileName = ?"); + this.iterateNonRecursive = conn.prepareStatement("SELECT FileName FROM eaglercraft_desktop_runtime_filesystem WHERE FileName LIKE ? AND NOT FileName LIKE ?"); + this.iterateRecursive = conn.prepareStatement("SELECT FileName FROM eaglercraft_desktop_runtime_filesystem WHERE FileName LIKE ?"); + startCleanupThread(); + synchronized(jdbcFilesystems) { + jdbcFilesystems.add(this); + } + } + + public boolean isNewFilesystem() { + return newFilesystem; + } + + private static void startCleanupThread() { + if(!cleanupThreadStarted) { + cleanupThreadStarted = true; + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + synchronized(jdbcFilesystems) { + if(!jdbcFilesystems.isEmpty()) { + for(JDBCFilesystem fs : jdbcFilesystems) { + fs.shutdown0(); + } + jdbcFilesystems.clear(); + } + } + }, "JDBCFilesystemCleanup")); + } + } + + public void shutdown() { + shutdown0(); + synchronized(jdbcFilesystems) { + jdbcFilesystems.remove(this); + } + } + + private void shutdown0() { + synchronized(mutex) { + if(!hasClosed) { + hasClosed = true; + logger.info("Disconnecting from database: \"{}\"", jdbcUri); + try { + shutdown1(); + }catch(Throwable t) { + logger.error("Failed to disconnect from database: \"{}\""); + logger.error(t); + } + } + } + } + + private void shutdown1() throws SQLException { + if(!conn.isClosed()) { + quietClose(createStatement); + quietClose(updateStatement); + quietClose(readStatement); + quietClose(existsStatement); + quietClose(sizeStatement); + quietClose(deleteStatement); + quietClose(renameStatement); + quietClose(iterateNonRecursive); + quietClose(iterateRecursive); + conn.close(); + } + } + + private static void quietClose(Statement stmt) { + try { + stmt.close(); + }catch(Throwable t) { + } + } + + @Override + public boolean eaglerDelete(String pathName) { + try { + synchronized(mutex) { + if(hasClosed || conn.isClosed()) { + throw new SQLException("Filesystem database connection is closed!"); + } + deleteStatement.setString(1, pathName); + int ret = deleteStatement.executeUpdate(); + if(ret == 0) { + PlatformFilesystem.logger.warn("Tried to delete file that doesn't exist: \"{}\"", pathName); + } + return ret > 0; + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("JDBC exception thrown while executing delete!", ex); + } + } + + @Override + public ByteBuffer eaglerRead(String pathName) { + try { + synchronized(mutex) { + if(hasClosed || conn.isClosed()) { + throw new SQLException("Filesystem database connection is closed!"); + } + readStatement.setString(1, pathName); + byte[] has = null; + try(ResultSet resultSet = readStatement.executeQuery()) { + if(resultSet.next()) { + has = resultSet.getBytes(1); + } + } + if(has == null) { + PlatformFilesystem.logger.warn("Tried to read file that doesn't exist: \"{}\"", pathName); + return null; + } + ByteBuffer byteBuf = PlatformRuntime.allocateByteBuffer(has.length); + byteBuf.put(has); + byteBuf.flip(); + return byteBuf; + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("JDBC exception thrown while executing read!", ex); + } + } + + @Override + public void eaglerWrite(String pathName, ByteBuffer data) { + try { + synchronized(mutex) { + if(hasClosed || conn.isClosed()) { + throw new SQLException("Filesystem database connection is closed!"); + } + existsStatement.setString(1, pathName); + boolean exists; + try(ResultSet resultSet = existsStatement.executeQuery()) { + if(resultSet.next()) { + exists = resultSet.getInt(1) > 0; + }else { + exists = false; + } + } + byte[] cp = new byte[data.remaining()]; + data.get(cp); + if(exists) { + updateStatement.setInt(1, cp.length); + updateStatement.setBytes(2, cp); + updateStatement.setString(3, pathName); + if(updateStatement.executeUpdate() == 0) { + throw new EaglerFileSystemException("SQL file update query did not update any rows!"); + } + }else { + createStatement.setString(1, pathName); + createStatement.setInt(2, cp.length); + createStatement.setBytes(3, cp); + createStatement.executeUpdate(); + } + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("JDBC exception thrown while executing write!", ex); + } + } + + @Override + public boolean eaglerExists(String pathName) { + try { + synchronized(mutex) { + if(hasClosed || conn.isClosed()) { + throw new SQLException("Filesystem database connection is closed!"); + } + existsStatement.setString(1, pathName); + try(ResultSet resultSet = existsStatement.executeQuery()) { + if(resultSet.next()) { + return resultSet.getInt(1) > 0; + }else { + return false; + } + } + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("JDBC exception thrown while executing exists!", ex); + } + } + + @Override + public boolean eaglerMove(String pathNameOld, String pathNameNew) { + try { + synchronized(mutex) { + if(hasClosed || conn.isClosed()) { + throw new SQLException("Filesystem database connection is closed!"); + } + renameStatement.setString(1, pathNameNew); + renameStatement.setString(2, pathNameOld); + return renameStatement.executeUpdate() > 0; + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("JDBC exception thrown while executing move!", ex); + } + } + + @Override + public int eaglerCopy(String pathNameOld, String pathNameNew) { + try { + synchronized(mutex) { + if(hasClosed || conn.isClosed()) { + throw new SQLException("Filesystem database connection is closed!"); + } + readStatement.setString(1, pathNameOld); + try(ResultSet resultSet = readStatement.executeQuery()) { + byte[] has = null; + if(resultSet.next()) { + has = resultSet.getBytes(1); + } + if(has == null) { + return -1; + } + existsStatement.setString(1, pathNameNew); + boolean exists; + try(ResultSet resultSet2 = existsStatement.executeQuery()) { + if(resultSet2.next()) { + exists = resultSet2.getInt(1) > 0; + }else { + exists = false; + } + } + if(exists) { + updateStatement.setInt(1, has.length); + updateStatement.setBytes(2, has); + updateStatement.setString(3, pathNameNew); + if(updateStatement.executeUpdate() == 0) { + throw new EaglerFileSystemException("SQL file update query did not update any rows!"); + } + }else { + createStatement.setString(1, pathNameNew); + createStatement.setInt(2, has.length); + createStatement.setBytes(3, has); + createStatement.executeUpdate(); + } + return has.length; + } + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("JDBC exception thrown while executing copy!", ex); + } + } + + @Override + public int eaglerSize(String pathName) { + try { + synchronized(mutex) { + if(hasClosed || conn.isClosed()) { + throw new SQLException("Filesystem database connection is closed!"); + } + sizeStatement.setString(1, pathName); + try(ResultSet resultSet = sizeStatement.executeQuery()) { + if(resultSet.next()) { + return resultSet.getInt(1); + }else { + return -1; + } + } + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("JDBC exception thrown while executing size!", ex); + } + } + + @Override + public void eaglerIterate(String pathName, VFSFilenameIterator itr, boolean recursive) { + try { + synchronized(mutex) { + if(hasClosed || conn.isClosed()) { + throw new SQLException("Filesystem database connection is closed!"); + } + PreparedStatement stmt; + if(recursive) { + stmt = iterateRecursive; + stmt.setString(1, pathName + (!pathName.endsWith("/") ? "/%" : "%"));; + }else { + stmt = iterateNonRecursive; + if(!pathName.endsWith("/")) { + pathName += "/"; + } + stmt.setString(1, pathName + "%"); + stmt.setString(2, pathName + "%/%"); + } + try(ResultSet resultSet = stmt.executeQuery()) { + while(resultSet.next()) { + try { + itr.next(resultSet.getString(1)); + }catch(VFSIterator2.BreakLoop exx) { + break; + } + } + } + } + }catch(SQLException ex) { + throw new EaglerFileSystemException("JDBC exception thrown while executing iterate!", ex); + } + } + +} diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/JDBCFilesystemConverter.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/JDBCFilesystemConverter.java new file mode 100644 index 0000000..62d19b2 --- /dev/null +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/JDBCFilesystemConverter.java @@ -0,0 +1,130 @@ +package net.lax1dude.eaglercraft.v1_8.internal.lwjgl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem.IFilesystemProvider; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.EaglerFileSystemException; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class JDBCFilesystemConverter { + + private static final Logger logger = LogManager.getLogger("JDBCFilesystemConverter"); + + public static void convertFilesystem(String title, File oldFS, IFilesystemProvider newFS, boolean deleteOld) { + FilesystemConvertingDialog progressDialog = new FilesystemConvertingDialog(title); + try { + progressDialog.setProgressIndeterminate(true); + progressDialog.setLocationRelativeTo(null); + progressDialog.setVisible(true); + + String slug = oldFS.getAbsolutePath(); + List filesToCopy = new ArrayList(); + logger.info("Discovering files to convert..."); + iterateFolder(slug.length(), oldFS, filesToCopy); + logger.info("Found {} files in the old directory", filesToCopy.size()); + + progressDialog.setProgressIndeterminate(false); + progressDialog.setProgressValue(0); + + int progCounter = 0; + int lastProgUpdate = 0; + byte[] copyArray = new byte[4096]; + + int l = filesToCopy.size(); + for(int i = 0; i < l; ++i) { + String str = filesToCopy.get(i); + File f = new File(oldFS, str); + try(InputStream is = new FileInputStream(f)) { + ByteBuffer copyBuffer = PlatformRuntime.allocateByteBuffer((int)f.length()); + try { + int j; + while(copyBuffer.hasRemaining() && (j = is.read(copyArray, 0, copyArray.length)) != -1) { + copyBuffer.put(copyArray, 0, j); + } + copyBuffer.flip(); + progCounter += copyBuffer.remaining(); + newFS.eaglerWrite(str, copyBuffer); + }finally { + PlatformRuntime.freeByteBuffer(copyBuffer); + } + if(progCounter - lastProgUpdate > 25000) { + lastProgUpdate = progCounter; + logger.info("Converted {}/{} files, {} bytes to JDBC format...", (i + 1), l, progCounter); + } + }catch(IOException ex) { + throw new EaglerFileSystemException("Failed to convert file: \"" + f.getAbsolutePath() + "\"", ex); + } + progressDialog.setProgressValue(i * 512 / (l - 1)); + } + + logger.info("Converted {}/{} files successfully!", l, l); + + if(deleteOld) { + logger.info("Deleting old filesystem..."); + progressDialog.setProgressIndeterminate(true); + deleteOldFolder(oldFS); + logger.info("Delete complete!"); + } + }finally { + progressDialog.setVisible(false); + progressDialog.dispose(); + } + } + + private static void iterateFolder(int slug, File file, List ret) { + File[] f = file.listFiles(); + if(f == null) { + return; + } + for(int i = 0; i < f.length; ++i) { + File ff = f[i]; + if(ff.isDirectory()) { + iterateFolder(slug, ff, ret); + }else { + String str = ff.getAbsolutePath(); + if(str.length() > slug) { + str = str.substring(slug).replace('\\', '/'); + if(str.startsWith("/")) { + str = str.substring(1); + } + ret.add(str); + } + } + } + } + + private static void deleteOldFolder(File file) { + File[] f = file.listFiles(); + for(int i = 0; i < f.length; ++i) { + if(f[i].isDirectory()) { + deleteOldFolder(f[i]); + }else { + f[i].delete(); + } + } + file.delete(); + } +} diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/LWJGLEntryPoint.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/LWJGLEntryPoint.java index 23af40b..849e836 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/LWJGLEntryPoint.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/LWJGLEntryPoint.java @@ -6,6 +6,7 @@ import javax.swing.UnsupportedLookAndFeelException; import net.lax1dude.eaglercraft.v1_8.EagRuntime; import net.lax1dude.eaglercraft.v1_8.EagUtils; import net.lax1dude.eaglercraft.v1_8.internal.EnumPlatformANGLE; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem; import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput; import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.program.ShaderSource; @@ -43,15 +44,9 @@ public class LWJGLEntryPoint { boolean hideRenderDocDialog = false; for(int i = 0; i < args.length; ++i) { - if(args[i].equalsIgnoreCase("highp")) { - ShaderSource.setHighP(true); - } if(args[i].equalsIgnoreCase("hide-renderdoc")) { hideRenderDocDialog = true; } - if(args[i].equalsIgnoreCase("fullscreen")) { - PlatformInput.setStartupFullscreen(true); - } } if(!hideRenderDocDialog) { @@ -66,7 +61,7 @@ public class LWJGLEntryPoint { lr.dispose(); } - getANGLEPlatformFromArgs(args); + getPlatformOptionsFromArgs(args); RelayManager.relayManager.load(EagRuntime.getStorage("r")); @@ -81,12 +76,22 @@ public class LWJGLEntryPoint { } - private static void getANGLEPlatformFromArgs(String[] args) { + private static void getPlatformOptionsFromArgs(String[] args) { for(int i = 0; i < args.length; ++i) { - EnumPlatformANGLE angle = EnumPlatformANGLE.fromId(args[i]); - if(angle != EnumPlatformANGLE.DEFAULT) { - PlatformRuntime.requestANGLE(angle); - break; + if(args[i].equalsIgnoreCase("fullscreen")) { + PlatformInput.setStartupFullscreen(true); + }else if(args[i].equalsIgnoreCase("highp")) { + ShaderSource.setHighP(true); + }else if(args[i].startsWith("jdbc:")) { + if(i < args.length - 1) { + PlatformFilesystem.setUseJDBC(args[i]); + PlatformFilesystem.setJDBCDriverClass(args[++i]); + } + }else { + EnumPlatformANGLE angle = EnumPlatformANGLE.fromId(args[i]); + if(angle != EnumPlatformANGLE.DEFAULT) { + PlatformRuntime.requestANGLE(angle); + } } } } diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/TestProgram.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/TestProgram.java deleted file mode 100644 index 26f348e..0000000 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/TestProgram.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.lax1dude.eaglercraft.v1_8.internal.lwjgl; - -import net.lax1dude.eaglercraft.v1_8.Display; -import net.lax1dude.eaglercraft.v1_8.EagRuntime; -import net.lax1dude.eaglercraft.v1_8.EagUtils; -import net.lax1dude.eaglercraft.v1_8.Keyboard; -import net.lax1dude.eaglercraft.v1_8.Mouse; -import net.lax1dude.eaglercraft.v1_8.internal.KeyboardConstants; - -public class TestProgram { - - private static boolean grab = false; - - public static void main_(String[] args) { - - while(!Display.isCloseRequested()) { - - Keyboard.enableRepeatEvents(true); - Display.update(); - - while(Keyboard.next()) { - if(Keyboard.getEventKey() == KeyboardConstants.KEY_E && Keyboard.getEventKeyState()) { - grab = !grab; - Mouse.setGrabbed(grab); - } - } - - System.out.println("" + Mouse.getDX() + ", " + Mouse.getDY()); - - EagUtils.sleep(100l); - } - - EagRuntime.destroy(); - - } - -} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/Display.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/Display.java index 2f46e14..6f40077 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/Display.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/Display.java @@ -45,6 +45,10 @@ public class Display { return PlatformInput.isCloseRequested(); } + public static void setVSync(boolean enable) { + PlatformInput.setVSync(enable); + } + public static void update() { PlatformInput.update(); } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java index 3e65516..aed0804 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java @@ -10,7 +10,7 @@ public class EaglercraftVersion { /// Customize these to fit your fork: public static final String projectForkName = "EaglercraftX"; - public static final String projectForkVersion = "u27"; + public static final String projectForkVersion = "u28"; public static final String projectForkVendor = "lax1dude"; public static final String projectForkURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; @@ -20,7 +20,7 @@ public class EaglercraftVersion { public static final String projectOriginName = "EaglercraftX"; public static final String projectOriginAuthor = "lax1dude"; public static final String projectOriginRevision = "1.8"; - public static final String projectOriginVersion = "u27"; + public static final String projectOriginVersion = "u28"; public static final String projectOriginURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; // rest in peace @@ -31,7 +31,7 @@ public class EaglercraftVersion { public static final boolean enableUpdateService = true; public static final String updateBundlePackageName = "net.lax1dude.eaglercraft.v1_8.client"; - public static final int updateBundlePackageVersionInt = 27; + public static final int updateBundlePackageVersionInt = 28; public static final String updateLatestLocalStorageKey = "latestUpdate_" + updateBundlePackageName; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/IClientConfigAdapter.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/IClientConfigAdapter.java index dc519b1..684275c 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/IClientConfigAdapter.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/IClientConfigAdapter.java @@ -67,4 +67,6 @@ public interface IClientConfigAdapter { boolean isCheckRelaysForUpdates(); boolean isEnableSignatureBadge(); + + boolean isAllowVoiceClient(); } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglerMeshLoader.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglerMeshLoader.java new file mode 100644 index 0000000..e4c6f11 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglerMeshLoader.java @@ -0,0 +1,172 @@ +package net.lax1dude.eaglercraft.v1_8.opengl; + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import net.lax1dude.eaglercraft.v1_8.EagRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.IResourceManagerReloadListener; +import net.minecraft.util.ResourceLocation; + +import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; +import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class EaglerMeshLoader implements IResourceManagerReloadListener { + + private static final Logger logger = LogManager.getLogger("EaglerMeshLoader"); + + private static final Map meshCache = new HashMap(); + + public static HighPolyMesh getEaglerMesh(ResourceLocation meshLoc) { + if(meshLoc.cachedPointerType == ResourceLocation.CACHED_POINTER_EAGLER_MESH) { + return (HighPolyMesh)meshLoc.cachedPointer; + } + HighPolyMesh theMesh = meshCache.get(meshLoc); + if(theMesh == null) { + theMesh = new HighPolyMesh(); + reloadMesh(meshLoc, theMesh, Minecraft.getMinecraft().getResourceManager()); + } + meshLoc.cachedPointerType = ResourceLocation.CACHED_POINTER_EAGLER_MESH; + meshLoc.cachedPointer = theMesh; + return theMesh; + } + + private static void reloadMesh(ResourceLocation meshLoc, HighPolyMesh meshStruct, IResourceManager resourceManager) { + IntBuffer up1 = null; + try { + int intsOfVertex, intsOfIndex, intsTotal, stride; + try(DataInputStream dis = new DataInputStream(resourceManager.getResource(meshLoc).getInputStream())) { + byte[] header = new byte[8]; + dis.read(header); + if(!Arrays.equals(header, new byte[] { (byte) 33, (byte) 69, (byte) 65, (byte) 71, (byte) 36, + (byte) 109, (byte) 100, (byte) 108 })) { + throw new IOException("File is not an eaglercraft high-poly mesh!"); + } + + char CT = (char)dis.read(); + + if(CT == 'C') { + meshStruct.hasTexture = false; + }else if(CT == 'T') { + meshStruct.hasTexture = true; + }else { + throw new IOException("Unsupported mesh type '" + CT + "'!"); + } + + dis.skipBytes(dis.readUnsignedShort()); + + meshStruct.vertexCount = dis.readInt(); + meshStruct.indexCount = dis.readInt(); + int byteIndexCount = meshStruct.indexCount; + if(byteIndexCount % 2 != 0) { // must round up to int + byteIndexCount += 1; + } + stride = meshStruct.hasTexture ? 24 : 16; + + intsOfVertex = meshStruct.vertexCount * stride / 4; + intsOfIndex = byteIndexCount / 2; + intsTotal = intsOfIndex + intsOfVertex; + up1 = EagRuntime.allocateIntBuffer(intsTotal); + + for(int i = 0; i < intsTotal; ++i) { + int ch1 = dis.read(); + int ch2 = dis.read(); + int ch3 = dis.read(); + int ch4 = dis.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException(); // rip + up1.put((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0)); + } + + } + + if(meshStruct.vertexArray == null) { + meshStruct.vertexArray = _wglGenVertexArrays(); + } + if(meshStruct.vertexBuffer == null) { + meshStruct.vertexBuffer = _wglGenBuffers(); + } + if(meshStruct.indexBuffer == null) { + meshStruct.indexBuffer = _wglGenBuffers(); + } + + up1.position(0).limit(intsOfVertex); + + EaglercraftGPU.bindGLArrayBuffer(meshStruct.vertexBuffer); + _wglBufferData(GL_ARRAY_BUFFER, up1, GL_STATIC_DRAW); + + EaglercraftGPU.bindGLBufferArray(meshStruct.vertexArray); + + up1.position(intsOfVertex).limit(intsTotal); + + _wglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshStruct.indexBuffer); + _wglBufferData(GL_ELEMENT_ARRAY_BUFFER, up1, GL_STATIC_DRAW); + + _wglEnableVertexAttribArray(0); + _wglVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0); + + if(meshStruct.hasTexture) { + _wglEnableVertexAttribArray(1); + _wglVertexAttribPointer(1, 2, GL_FLOAT, false, stride, 16); + } + + _wglEnableVertexAttribArray(meshStruct.hasTexture ? 2 : 1); + _wglVertexAttribPointer(meshStruct.hasTexture ? 2 : 1, 4, GL_BYTE, true, stride, 12); + }catch(Throwable ex) { + if(meshStruct.vertexArray != null) { + _wglDeleteVertexArrays(meshStruct.vertexArray); + meshStruct.vertexArray = null; + } + if(meshStruct.vertexBuffer != null) { + _wglDeleteBuffers(meshStruct.vertexBuffer); + meshStruct.vertexBuffer = null; + } + if(meshStruct.indexBuffer != null) { + _wglDeleteBuffers(meshStruct.indexBuffer); + meshStruct.indexBuffer = null; + } + + meshStruct.vertexCount = 0; + meshStruct.indexCount = 0; + meshStruct.hasTexture = false; + + logger.error("Failed to load eaglercraft high-poly mesh: \"{}\"", meshLoc); + logger.error(ex); + }finally { + if(up1 != null) { + EagRuntime.freeIntBuffer(up1); + } + } + } + + @Override + public void onResourceManagerReload(IResourceManager var1) { + for(Entry meshEntry : meshCache.entrySet()) { + reloadMesh(meshEntry.getKey(), meshEntry.getValue(), var1); + } + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglercraftGPU.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglercraftGPU.java index 7922549..10b6bee 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglercraftGPU.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglercraftGPU.java @@ -23,7 +23,7 @@ import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*; /** - * Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -152,7 +152,7 @@ public class EaglercraftGPU { currentList = null; } - public static void glCallList(int displayList) { + public static final void glCallList(int displayList) { DisplayList dp = mapDisplayListsGL.get(displayList); if(dp == null) { throw new NullPointerException("Tried to call a display list that does not exist: " + displayList); @@ -488,18 +488,28 @@ public class EaglercraftGPU { return mapTexturesGL.get(tex); } + public static final void drawHighPoly(HighPolyMesh mesh) { + if(mesh.vertexCount == 0 || mesh.indexCount == 0 || mesh.vertexArray == null) { + return; + } + FixedFunctionPipeline p = FixedFunctionPipeline.setupRenderDisplayList(mesh.getAttribBits()).update(); + EaglercraftGPU.bindGLBufferArray(mesh.vertexArray); + p.drawElements(GL_TRIANGLES, mesh.indexCount, GL_UNSIGNED_SHORT, 0); + } + static boolean hasFramebufferHDR16FSupport = false; static boolean hasFramebufferHDR32FSupport = false; + static boolean hasLinearHDR32FSupport = false; - public static void createFramebufferHDR16FTexture(int target, int level, int w, int h, int format, boolean allow32bitFallback) { + public static final void createFramebufferHDR16FTexture(int target, int level, int w, int h, int format, boolean allow32bitFallback) { createFramebufferHDR16FTexture(target, level, w, h, format, allow32bitFallback, null); } - public static void createFramebufferHDR16FTexture(int target, int level, int w, int h, int format, ByteBuffer pixelData) { + public static final void createFramebufferHDR16FTexture(int target, int level, int w, int h, int format, ByteBuffer pixelData) { createFramebufferHDR16FTexture(target, level, w, h, format, false, pixelData); } - private static void createFramebufferHDR16FTexture(int target, int level, int w, int h, int format, boolean allow32bitFallback, ByteBuffer pixelData) { + private static final void createFramebufferHDR16FTexture(int target, int level, int w, int h, int format, boolean allow32bitFallback, ByteBuffer pixelData) { if(hasFramebufferHDR16FSupport) { int internalFormat; switch(format) { @@ -530,15 +540,15 @@ public class EaglercraftGPU { } } - public static void createFramebufferHDR32FTexture(int target, int level, int w, int h, int format, boolean allow16bitFallback) { + public static final void createFramebufferHDR32FTexture(int target, int level, int w, int h, int format, boolean allow16bitFallback) { createFramebufferHDR32FTexture(target, level, w, h, format, allow16bitFallback, null); } - public static void createFramebufferHDR32FTexture(int target, int level, int w, int h, int format, ByteBuffer pixelData) { + public static final void createFramebufferHDR32FTexture(int target, int level, int w, int h, int format, ByteBuffer pixelData) { createFramebufferHDR32FTexture(target, level, w, h, format, false, pixelData); } - private static void createFramebufferHDR32FTexture(int target, int level, int w, int h, int format, boolean allow16bitFallback, ByteBuffer pixelData) { + private static final void createFramebufferHDR32FTexture(int target, int level, int w, int h, int format, boolean allow16bitFallback, ByteBuffer pixelData) { if(hasFramebufferHDR32FSupport) { int internalFormat; switch(format) { @@ -555,7 +565,7 @@ public class EaglercraftGPU { default: throw new UnsupportedOperationException("Unknown format: " + format); } - _wglTexImage2D(target, level, internalFormat, w, h, 0, format, GL_FLOAT, pixelData); + _wglTexImage2Df32(target, level, internalFormat, w, h, 0, format, GL_FLOAT, pixelData); }else { if(allow16bitFallback) { if(hasFramebufferHDR16FSupport) { @@ -585,7 +595,13 @@ public class EaglercraftGPU { }else { logger.error("32-bit HDR render target support: false"); } - if(!checkHasHDRFramebufferSupport()) { + hasLinearHDR32FSupport = PlatformOpenGL.checkLinearHDR32FSupport(); + if(hasLinearHDR32FSupport) { + logger.info("32-bit HDR linear filter support: true"); + }else { + logger.error("32-bit HDR linear filter support: false"); + } + if(!checkHasHDRFramebufferSupportWithFilter()) { logger.error("No HDR render target support was detected! Shaders will be disabled."); } DrawUtils.init(); @@ -612,4 +628,12 @@ public class EaglercraftGPU { public static final boolean checkHasHDRFramebufferSupport() { return hasFramebufferHDR16FSupport || hasFramebufferHDR32FSupport; } + + public static final boolean checkHasHDRFramebufferSupportWithFilter() { + return hasFramebufferHDR16FSupport || (hasFramebufferHDR32FSupport && hasLinearHDR32FSupport); + } + + public static final boolean checkLinearHDR32FSupport() { + return hasLinearHDR32FSupport; + } } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/GlStateManager.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/GlStateManager.java index 28051cc..8886072 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/GlStateManager.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/GlStateManager.java @@ -11,7 +11,7 @@ import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*; /** - * Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -983,6 +983,30 @@ public class GlStateManager { Matrix4f.mul(modeMatrix, paramMatrix, modeMatrix); } + public static final void multMatrix(Matrix4f matrix) { + Matrix4f modeMatrix; + + switch(stateMatrixMode) { + case GL_MODELVIEW: + default: + modeMatrix = modelMatrixStack[modelMatrixStackPointer]; + modelMatrixStackAccessSerial[modelMatrixStackPointer] = ++modelMatrixAccessSerial; + break; + case GL_PROJECTION: + modeMatrix = projectionMatrixStack[projectionMatrixStackPointer]; + projectionMatrixStackAccessSerial[projectionMatrixStackPointer] = ++projectionMatrixAccessSerial; + break; + case GL_TEXTURE: + int ptr = textureMatrixStackPointer[activeTexture]; + modeMatrix = textureMatrixStack[activeTexture][ptr]; + textureMatrixStackAccessSerial[activeTexture][textureMatrixStackPointer[activeTexture]] = + ++textureMatrixAccessSerial[activeTexture]; + break; + } + + Matrix4f.mul(modeMatrix, matrix, modeMatrix); + } + public static final void color(float colorRed, float colorGreen, float colorBlue, float colorAlpha) { stateColorR = colorRed; stateColorG = colorGreen; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/HighPolyMesh.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/HighPolyMesh.java new file mode 100644 index 0000000..5dd7c5c --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/HighPolyMesh.java @@ -0,0 +1,66 @@ +package net.lax1dude.eaglercraft.v1_8.opengl; + +import net.lax1dude.eaglercraft.v1_8.internal.IBufferArrayGL; +import net.lax1dude.eaglercraft.v1_8.internal.IBufferGL; +import net.lax1dude.eaglercraft.v1_8.opengl.FixedFunctionShader.FixedFunctionState; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class HighPolyMesh { + + IBufferArrayGL vertexArray; + IBufferGL vertexBuffer; + IBufferGL indexBuffer; + + int vertexCount; + int indexCount; + + boolean hasTexture; + + public HighPolyMesh(IBufferArrayGL vertexArray, IBufferGL vertexBuffer, IBufferGL indexBuffer, int vertexCount, + int indexCount, boolean hasTexture) { + this.vertexArray = vertexArray; + this.vertexBuffer = vertexBuffer; + this.indexBuffer = indexBuffer; + this.vertexCount = vertexCount; + this.indexCount = indexCount; + this.hasTexture = hasTexture; + } + + HighPolyMesh() { + + } + + public boolean isNull() { + return vertexArray == null; + } + + public int getVertexCount() { + return vertexCount; + } + + public int getIndexCount() { + return indexCount; + } + + public boolean getHasTexture() { + return hasTexture; + } + + public int getAttribBits() { + return hasTexture ? (FixedFunctionState.STATE_HAS_ATTRIB_TEXTURE | FixedFunctionState.STATE_HAS_ATTRIB_NORMAL) : FixedFunctionState.STATE_HAS_ATTRIB_NORMAL; + } +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/deferred/EaglerDeferredPipeline.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/deferred/EaglerDeferredPipeline.java index a1c2254..f08117f 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/deferred/EaglerDeferredPipeline.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/deferred/EaglerDeferredPipeline.java @@ -3984,11 +3984,11 @@ public class EaglerDeferredPipeline { } public static final boolean isSupported() { - return EaglercraftGPU.checkHasHDRFramebufferSupport(); + return EaglercraftGPU.checkHasHDRFramebufferSupportWithFilter(); } public static final String getReasonUnsupported() { - if(!EaglercraftGPU.checkHasHDRFramebufferSupport()) { + if(!EaglercraftGPU.checkHasHDRFramebufferSupportWithFilter()) { return I18n.format("shaders.gui.unsupported.reason.hdrFramebuffer"); }else { return null; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/CapePackets.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/CapePackets.java new file mode 100644 index 0000000..581176d --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/CapePackets.java @@ -0,0 +1,83 @@ +package net.lax1dude.eaglercraft.v1_8.profile; + +import java.io.IOException; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.netty.Unpooled; +import net.minecraft.network.PacketBuffer; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class CapePackets { + + public static final int PACKET_MY_CAPE_PRESET = 0x01; + public static final int PACKET_MY_CAPE_CUSTOM = 0x02; + public static final int PACKET_GET_OTHER_CAPE = 0x03; + public static final int PACKET_OTHER_CAPE_PRESET = 0x04; + public static final int PACKET_OTHER_CAPE_CUSTOM = 0x05; + + public static void readPluginMessage(PacketBuffer buffer, ServerCapeCache capeCache) throws IOException { + try { + int type = (int)buffer.readByte() & 0xFF; + switch(type) { + case PACKET_OTHER_CAPE_PRESET: { + EaglercraftUUID responseUUID = buffer.readUuid(); + int responsePreset = buffer.readInt(); + if(buffer.isReadable()) { + throw new IOException("PACKET_OTHER_CAPE_PRESET had " + buffer.readableBytes() + " remaining bytes!"); + } + capeCache.cacheCapePreset(responseUUID, responsePreset); + break; + } + case PACKET_OTHER_CAPE_CUSTOM: { + EaglercraftUUID responseUUID = buffer.readUuid(); + byte[] readCape = new byte[1173]; + buffer.readBytes(readCape); + if(buffer.isReadable()) { + throw new IOException("PACKET_OTHER_CAPE_CUSTOM had " + buffer.readableBytes() + " remaining bytes!"); + } + capeCache.cacheCapeCustom(responseUUID, readCape); + break; + } + default: + throw new IOException("Unknown skin packet type: " + type); + } + }catch(IOException ex) { + throw ex; + }catch(Throwable t) { + throw new IOException("Failed to parse cape packet!", t); + } + } + + public static byte[] writeMyCapePreset(int capeId) { + return new byte[] { (byte) PACKET_MY_CAPE_PRESET, (byte) (capeId >> 24), (byte) (capeId >> 16), + (byte) (capeId >> 8), (byte) (capeId & 0xFF) }; + } + + public static byte[] writeMyCapeCustom(CustomCape customCape) { + byte[] packet = new byte[1 + customCape.texture.length]; + packet[0] = (byte) PACKET_MY_CAPE_CUSTOM; + System.arraycopy(customCape.texture, 0, packet, 1, customCape.texture.length); + return packet; + } + + public static PacketBuffer writeGetOtherCape(EaglercraftUUID playerId) throws IOException { + PacketBuffer ret = new PacketBuffer(Unpooled.buffer(17, 17)); + ret.writeByte(PACKET_GET_OTHER_CAPE); + ret.writeUuid(playerId); + return ret; + } +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/CustomCape.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/CustomCape.java new file mode 100644 index 0000000..12fac3d --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/CustomCape.java @@ -0,0 +1,58 @@ +package net.lax1dude.eaglercraft.v1_8.profile; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.ResourceLocation; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class CustomCape { + + public final String name; + public final byte[] texture; + + private EaglerSkinTexture textureInstance; + private ResourceLocation resourceLocation; + + private static int texId = 0; + + public CustomCape(String name, byte[] texture) { + this.name = name; + this.texture = texture; + byte[] texture2 = new byte[4096]; + SkinConverter.convertCape23x17RGBto32x32RGBA(texture, texture2); + this.textureInstance = new EaglerSkinTexture(texture2, 32, 32); + this.resourceLocation = null; + } + + public void load() { + if(resourceLocation == null) { + resourceLocation = new ResourceLocation("eagler:capes/custom/tex_" + texId++); + Minecraft.getMinecraft().getTextureManager().loadTexture(resourceLocation, textureInstance); + } + } + + public ResourceLocation getResource() { + return resourceLocation; + } + + public void delete() { + if(resourceLocation != null) { + Minecraft.getMinecraft().getTextureManager().deleteTexture(resourceLocation); + resourceLocation = null; + } + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/DefaultCapes.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/DefaultCapes.java new file mode 100644 index 0000000..562d006 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/DefaultCapes.java @@ -0,0 +1,75 @@ +package net.lax1dude.eaglercraft.v1_8.profile; + +import net.minecraft.util.ResourceLocation; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public enum DefaultCapes { + + NO_CAPE(0, "No Cape", null), + MINECON_2011(1, "Minecon 2011", new ResourceLocation("eagler:capes/01.minecon_2011.png")), + MINECON_2012(2, "Minecon 2012", new ResourceLocation("eagler:capes/02.minecon_2012.png")), + MINECON_2013(3, "Minecon 2013", new ResourceLocation("eagler:capes/03.minecon_2013.png")), + MINECON_2015(4, "Minecon 2015", new ResourceLocation("eagler:capes/04.minecon_2015.png")), + MINECON_2016(5, "Minecon 2016", new ResourceLocation("eagler:capes/05.minecon_2016.png")), + MICROSOFT_ACCOUNT(6, "Microsoft Account", new ResourceLocation("eagler:capes/06.microsoft_account.png")), + MAPMAKER(7, "Realms Mapmaker", new ResourceLocation("eagler:capes/07.mapmaker.png")), + MOJANG_OLD(8, "Mojang Old", new ResourceLocation("eagler:capes/08.mojang_old.png")), + MOJANG_NEW(9, "Mojang New", new ResourceLocation("eagler:capes/09.mojang_new.png")), + JIRA_MOD(10, "Jira Moderator", new ResourceLocation("eagler:capes/10.jira_mod.png")), + MOJANG_VERY_OLD(11, "Mojang Very Old", new ResourceLocation("eagler:capes/11.mojang_very_old.png")), + SCROLLS(12, "Scrolls", new ResourceLocation("eagler:capes/12.scrolls.png")), + COBALT(13, "Cobalt", new ResourceLocation("eagler:capes/13.cobalt.png")), + TRANSLATOR(14, "Lang Translator", new ResourceLocation("eagler:capes/14.translator.png")), + MILLIONTH_ACCOUNT(15, "Millionth Player", new ResourceLocation("eagler:capes/15.millionth_account.png")), + PRISMARINE(16, "Prismarine", new ResourceLocation("eagler:capes/16.prismarine.png")), + SNOWMAN(17, "Snowman", new ResourceLocation("eagler:capes/17.snowman.png")), + SPADE(18, "Spade", new ResourceLocation("eagler:capes/18.spade.png")), + BIRTHDAY(19, "Birthday", new ResourceLocation("eagler:capes/19.birthday.png")), + DB(20, "dB", new ResourceLocation("eagler:capes/20.db.png")); + + public static final DefaultCapes[] defaultCapesMap = new DefaultCapes[21]; + + public final int id; + public final String name; + public final ResourceLocation location; + + private DefaultCapes(int id, String name, ResourceLocation location) { + this.id = id; + this.name = name; + this.location = location; + } + + public static DefaultCapes getCapeFromId(int id) { + DefaultCapes e = null; + if(id >= 0 && id < defaultCapesMap.length) { + e = defaultCapesMap[id]; + } + if(e != null) { + return e; + }else { + return NO_CAPE; + } + } + + static { + DefaultCapes[] capes = values(); + for(int i = 0; i < capes.length; ++i) { + defaultCapesMap[capes[i].id] = capes[i]; + } + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/DefaultSkins.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/DefaultSkins.java index 5c67d07..8f88b24 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/DefaultSkins.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/DefaultSkins.java @@ -42,9 +42,14 @@ public enum DefaultSkins { CREEPER(20, "Creeper", new ResourceLocation("eagler:skins/21.creeper.png"), SkinModel.STEVE), ZOMBIE(21, "Zombie", new ResourceLocation("eagler:skins/22.zombie.png"), SkinModel.ZOMBIE), PIG(22, "Pig", new ResourceLocation("eagler:skins/23.pig.png"), SkinModel.STEVE), - MOOSHROOM(23, "Mooshroom", new ResourceLocation("eagler:skins/24.mooshroom.png"), SkinModel.STEVE); + MOOSHROOM(23, "Mooshroom", new ResourceLocation("eagler:skins/24.mooshroom.png"), SkinModel.STEVE), + LONG_ARMS(24, "Long Arms", new ResourceLocation("eagler:mesh/longarms.fallback.png"), SkinModel.LONG_ARMS), + WEIRD_CLIMBER_DUDE(25, "Weird Climber Dude", new ResourceLocation("eagler:mesh/weirdclimber.fallback.png"), SkinModel.WEIRD_CLIMBER_DUDE), + LAXATIVE_DUDE(26, "Laxative Dude", new ResourceLocation("eagler:mesh/laxativedude.fallback.png"), SkinModel.LAXATIVE_DUDE), + BABY_CHARLES(27, "Baby Charles", new ResourceLocation("eagler:mesh/charles.fallback.png"), SkinModel.BABY_CHARLES), + BABY_WINSTON(28, "Baby Winston", new ResourceLocation("eagler:mesh/winston.fallback.png"), SkinModel.BABY_WINSTON); - public static final DefaultSkins[] defaultSkinsMap = new DefaultSkins[24]; + public static final DefaultSkins[] defaultSkinsMap = new DefaultSkins[29]; public final int id; public final String name; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/EaglerProfile.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/EaglerProfile.java index 2a6e212..13bd51d 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/EaglerProfile.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/EaglerProfile.java @@ -16,7 +16,7 @@ import net.minecraft.nbt.NBTTagList; import net.minecraft.util.ResourceLocation; /** - * Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -37,7 +37,11 @@ public class EaglerProfile { public static int presetSkinId; public static int customSkinId; + public static int presetCapeId; + public static int customCapeId; + public static final List customSkins = new ArrayList(); + public static final List customCapes = new ArrayList(); public static final EaglercraftRandom rand; @@ -78,6 +82,25 @@ public class EaglerProfile { } } } + + public static ResourceLocation getActiveCapeResourceLocation() { + if(presetCapeId == -1) { + if(customCapeId >= 0 && customCapeId < customCapes.size()) { + return customCapes.get(customCapeId).getResource(); + }else { + customCapeId = -1; + presetCapeId = 0; + return DefaultCapes.defaultCapesMap[0].location; + } + }else { + if(presetCapeId >= 0 && presetCapeId < DefaultCapes.defaultCapesMap.length) { + return DefaultCapes.defaultCapesMap[presetCapeId].location; + }else { + presetCapeId = 0; + return DefaultCapes.defaultCapesMap[0].location; + } + } + } public static EaglercraftUUID getPlayerUUID() { return Minecraft.getMinecraft().getSession().getProfile().getId(); @@ -114,6 +137,25 @@ public class EaglerProfile { } } + public static byte[] getCapePacket() { + if(presetCapeId == -1) { + if(customCapeId >= 0 && customCapeId < customCapes.size()) { + return CapePackets.writeMyCapeCustom(customCapes.get(customCapeId)); + }else { + customCapeId = -1; + presetCapeId = 0; + return CapePackets.writeMyCapePreset(0); + } + }else { + if(presetCapeId >= 0 && presetCapeId < DefaultCapes.defaultCapesMap.length) { + return CapePackets.writeMyCapePreset(presetCapeId); + }else { + presetCapeId = 0; + return CapePackets.writeMyCapePreset(0); + } + } + } + private static boolean doesSkinExist(String name) { for(int i = 0, l = customSkins.size(); i < l; ++i) { if(customSkins.get(i).name.equalsIgnoreCase(name)) { @@ -123,6 +165,15 @@ public class EaglerProfile { return false; } + private static boolean doesCapeExist(String name) { + for(int i = 0, l = customCapes.size(); i < l; ++i) { + if(customCapes.get(i).name.equalsIgnoreCase(name)) { + return true; + } + } + return false; + } + public static int addCustomSkin(String fileName, byte[] rawSkin) { if(doesSkinExist(fileName)) { String newName; @@ -139,6 +190,22 @@ public class EaglerProfile { return r; } + public static int addCustomCape(String fileName, byte[] rawCape23x17RGB) { + if(doesCapeExist(fileName)) { + String newName; + int i = 2; + while(doesCapeExist(newName = fileName + " (" + i + ")")) { + ++i; + } + fileName = newName; + } + CustomCape newCape = new CustomCape(fileName, rawCape23x17RGB); + newCape.load(); + int r = customCapes.size(); + customCapes.add(newCape); + return r; + } + public static void clearCustomSkins() { for(int i = 0, l = customSkins.size(); i < l; ++i) { customSkins.get(i).delete(); @@ -146,6 +213,13 @@ public class EaglerProfile { customSkins.clear(); } + public static void clearCustomCapes() { + for(int i = 0, l = customCapes.size(); i < l; ++i) { + customCapes.get(i).delete(); + } + customCapes.clear(); + } + public static void read() { read(EagRuntime.getStorage("p")); } @@ -169,6 +243,9 @@ public class EaglerProfile { presetSkinId = profile.getInteger("presetSkin"); customSkinId = profile.getInteger("customSkin"); + if(profile.hasKey("presetCape", 99)) presetCapeId = profile.getInteger("presetCape"); + if(profile.hasKey("customCape", 99)) customCapeId = profile.getInteger("customCape"); + String loadUsername = profile.getString("username").trim(); if(!loadUsername.isEmpty()) { @@ -193,7 +270,21 @@ public class EaglerProfile { newSkin.load(); customSkins.add(newSkin); } - + + if(profile.hasKey("capes", 9)) { + clearCustomCapes(); + NBTTagList capesList = profile.getTagList("capes", 10); + for(int i = 0, l = capesList.tagCount(); i < l; ++i) { + NBTTagCompound cape = capesList.getCompoundTagAt(i); + String capeName = cape.getString("name"); + byte[] capeData = cape.getByteArray("data"); + if(capeData.length != 1173) continue; + CustomCape newCape = new CustomCape(capeName, capeData); + newCape.load(); + customCapes.add(newCape); + } + } + if(presetSkinId == -1) { if(customSkinId < 0 || customSkinId >= customSkins.size()) { presetSkinId = 0; @@ -206,12 +297,26 @@ public class EaglerProfile { } } + if(presetCapeId == -1) { + if(customCapeId < 0 || customCapeId >= customCapes.size()) { + presetCapeId = 0; + customCapeId = -1; + } + }else { + customCapeId = -1; + if(presetCapeId < 0 || presetCapeId >= DefaultCapes.defaultCapesMap.length) { + presetCapeId = 0; + } + } + } public static byte[] write() { NBTTagCompound profile = new NBTTagCompound(); profile.setInteger("presetSkin", presetSkinId); profile.setInteger("customSkin", customSkinId); + profile.setInteger("presetCape", presetCapeId); + profile.setInteger("customCape", customCapeId); profile.setString("username", username); NBTTagList skinsList = new NBTTagList(); for(int i = 0, l = customSkins.size(); i < l; ++i) { @@ -223,6 +328,15 @@ public class EaglerProfile { skinsList.appendTag(skin); } profile.setTag("skins", skinsList); + NBTTagList capesList = new NBTTagList(); + for(int i = 0, l = customCapes.size(); i < l; ++i) { + CustomCape cp = customCapes.get(i); + NBTTagCompound cape = new NBTTagCompound(); + cape.setString("name", cp.name); + cape.setByteArray("data", cp.texture); + capesList.appendTag(cape); + } + profile.setTag("capes", capesList); EaglerOutputStream bao = new EaglerOutputStream(); try { CompressedStreamTools.writeCompressed(profile, bao); @@ -253,9 +367,14 @@ public class EaglerProfile { setName(username); - presetSkinId = rand.nextInt(DefaultSkins.defaultSkinsMap.length); + do { + presetSkinId = rand.nextInt(DefaultSkins.defaultSkinsMap.length); + }while(DefaultSkins.defaultSkinsMap[presetSkinId].model.highPoly != null); customSkinId = -1; + presetCapeId = 0; + customCapeId = -1; + } } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/GuiScreenEditCape.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/GuiScreenEditCape.java new file mode 100644 index 0000000..6e50f1a --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/GuiScreenEditCape.java @@ -0,0 +1,359 @@ +package net.lax1dude.eaglercraft.v1_8.profile; + +import net.lax1dude.eaglercraft.v1_8.EagRuntime; +import net.lax1dude.eaglercraft.v1_8.Keyboard; +import net.lax1dude.eaglercraft.v1_8.Mouse; +import net.lax1dude.eaglercraft.v1_8.internal.EnumCursorType; +import net.lax1dude.eaglercraft.v1_8.internal.FileChooserResult; +import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; +import net.lax1dude.eaglercraft.v1_8.opengl.ImageData; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; + +import java.io.IOException; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class GuiScreenEditCape extends GuiScreen { + + private final GuiScreenEditProfile parent; + + private boolean dropDownOpen = false; + private String[] dropDownOptions; + private int slotsVisible = 0; + protected int selectedSlot = 0; + private int scrollPos = -1; + private int skinsHeight = 0; + private boolean dragging = false; + private int mousex = 0; + private int mousey = 0; + + private static final ResourceLocation eaglerGui = new ResourceLocation("eagler:gui/eagler_gui.png"); + + protected String screenTitle = "Edit Cape"; + + public GuiScreenEditCape(GuiScreenEditProfile parent) { + this.parent = parent; + updateOptions(); + } + + public void initGui() { + Keyboard.enableRepeatEvents(true); + screenTitle = I18n.format("editCape.title"); + selectedSlot = EaglerProfile.presetCapeId == -1 ? EaglerProfile.customCapeId : (EaglerProfile.presetCapeId + EaglerProfile.customCapes.size()); + buttonList.add(new GuiButton(0, width / 2 - 100, height / 6 + 168, I18n.format("gui.done"))); + buttonList.add(new GuiButton(1, width / 2 - 21, height / 6 + 80, 71, 20, I18n.format("editCape.addCape"))); + buttonList.add(new GuiButton(2, width / 2 - 21 + 71, height / 6 + 80, 72, 20, I18n.format("editCape.clearCape"))); + } + + private void updateOptions() { + int numCustom = EaglerProfile.customCapes.size(); + String[] n = new String[numCustom + DefaultCapes.defaultCapesMap.length]; + for(int i = 0; i < numCustom; ++i) { + n[i] = EaglerProfile.customCapes.get(i).name; + } + int numDefault = DefaultCapes.defaultCapesMap.length; + for(int j = 0; j < numDefault; ++j) { + n[numCustom + j] = DefaultCapes.defaultCapesMap[j].name; + } + dropDownOptions = n; + } + + public void drawScreen(int mx, int my, float partialTicks) { + drawDefaultBackground(); + drawCenteredString(fontRendererObj, screenTitle, width / 2, 15, 16777215); + drawString(fontRendererObj, I18n.format("editCape.playerCape"), width / 2 - 20, height / 6 + 36, 10526880); + + mousex = mx; + mousey = my; + + int skinX = width / 2 - 120; + int skinY = height / 6 + 8; + int skinWidth = 80; + int skinHeight = 130; + + drawRect(skinX, skinY, skinX + skinWidth, skinY + skinHeight, 0xFFA0A0A0); + drawRect(skinX + 1, skinY + 1, skinX + skinWidth - 1, skinY + skinHeight - 1, 0xFF000015); + + int skid = selectedSlot - EaglerProfile.customCapes.size(); + if(skid < 0) { + skid = 0; + } + + if(dropDownOpen) { + super.drawScreen(0, 0, partialTicks); + }else { + super.drawScreen(mx, my, partialTicks); + } + + int numberOfCustomSkins = EaglerProfile.customSkins.size(); + int numberOfCustomCapes = EaglerProfile.customCapes.size(); + ResourceLocation skinTexture; + SkinModel model; + if(parent.selectedSlot < numberOfCustomSkins) { + CustomSkin customSkin = EaglerProfile.customSkins.get(parent.selectedSlot); + skinTexture = customSkin.getResource(); + model = customSkin.model; + }else { + DefaultSkins defaultSkin = DefaultSkins.getSkinFromId(parent.selectedSlot - numberOfCustomSkins); + skinTexture = defaultSkin.location; + model = defaultSkin.model; + } + + if(model.highPoly != null) { + drawCenteredString(fontRendererObj, I18n.format(this.mc.gameSettings.enableFNAWSkins ? "editProfile.disableFNAW" : "editProfile.enableFNAW"), width / 2, height / 6 + 150, 10526880); + } + + skinX = width / 2 - 20; + skinY = height / 6 + 52; + skinWidth = 140; + skinHeight = 22; + + drawRect(skinX, skinY, skinX + skinWidth, skinY + skinHeight, -6250336); + drawRect(skinX + 1, skinY + 1, skinX + skinWidth - 21, skinY + skinHeight - 1, -16777216); + drawRect(skinX + skinWidth - 20, skinY + 1, skinX + skinWidth - 1, skinY + skinHeight - 1, -16777216); + + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + + mc.getTextureManager().bindTexture(eaglerGui); + drawTexturedModalRect(skinX + skinWidth - 18, skinY + 3, 0, 0, 16, 16); + + drawString(fontRendererObj, dropDownOptions[selectedSlot], skinX + 5, skinY + 7, 14737632); + + skinX = width / 2 - 20; + skinY = height / 6 + 73; + skinWidth = 140; + skinHeight = (height - skinY - 10); + slotsVisible = (skinHeight / 10); + if(slotsVisible > dropDownOptions.length) slotsVisible = dropDownOptions.length; + skinHeight = slotsVisible * 10 + 7; + skinsHeight = skinHeight; + if(scrollPos == -1) { + scrollPos = selectedSlot - 2; + } + if(scrollPos > (dropDownOptions.length - slotsVisible)) { + scrollPos = (dropDownOptions.length - slotsVisible); + } + if(scrollPos < 0) { + scrollPos = 0; + } + if(dropDownOpen) { + drawRect(skinX, skinY, skinX + skinWidth, skinY + skinHeight, -6250336); + drawRect(skinX + 1, skinY + 1, skinX + skinWidth - 1, skinY + skinHeight - 1, -16777216); + for(int i = 0; i < slotsVisible; i++) { + if(i + scrollPos < dropDownOptions.length) { + if(selectedSlot == i + scrollPos) { + drawRect(skinX + 1, skinY + i*10 + 4, skinX + skinWidth - 1, skinY + i*10 + 14, 0x77ffffff); + }else if(mx >= skinX && mx < (skinX + skinWidth - 10) && my >= (skinY + i*10 + 5) && my < (skinY + i*10 + 15)) { + drawRect(skinX + 1, skinY + i*10 + 4, skinX + skinWidth - 1, skinY + i*10 + 14, 0x55ffffff); + } + drawString(fontRendererObj, dropDownOptions[i + scrollPos], skinX + 5, skinY + 5 + i*10, 14737632); + } + } + int scrollerSize = skinHeight * slotsVisible / dropDownOptions.length; + int scrollerPos = skinHeight * scrollPos / dropDownOptions.length; + drawRect(skinX + skinWidth - 4, skinY + scrollerPos + 1, skinX + skinWidth - 1, skinY + scrollerPos + scrollerSize, 0xff888888); + } + + if(!EagRuntime.getConfiguration().isDemo()) { + GlStateManager.pushMatrix(); + GlStateManager.scale(0.75f, 0.75f, 0.75f); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + String text = I18n.format("editProfile.importExport"); + + int w = mc.fontRendererObj.getStringWidth(text); + boolean hover = mx > 1 && my > 1 && mx < (w * 3 / 4) + 7 && my < 12; + if(hover) { + Mouse.showCursor(EnumCursorType.HAND); + } + + drawString(mc.fontRendererObj, EnumChatFormatting.UNDERLINE + text, 5, 5, hover ? 0xFFEEEE22 : 0xFFCCCCCC); + + GlStateManager.popMatrix(); + } + + int xx = width / 2 - 80; + int yy = height / 6 + 130; + + skinX = this.width / 2 - 120; + skinY = this.height / 6 + 8; + skinWidth = 80; + skinHeight = 130; + + ResourceLocation capeTexture; + if(selectedSlot < numberOfCustomCapes) { + capeTexture = EaglerProfile.customCapes.get(selectedSlot).getResource(); + }else { + capeTexture = DefaultCapes.getCapeFromId(selectedSlot - numberOfCustomCapes).location; + } + + SkinPreviewRenderer.renderPreview(xx, yy, mx, my, true, model, skinTexture, capeTexture); + } + + public void handleMouseInput() throws IOException { + super.handleMouseInput(); + if(dropDownOpen) { + int var1 = Mouse.getEventDWheel(); + if(var1 < 0) { + scrollPos += 3; + } + if(var1 > 0) { + scrollPos -= 3; + if(scrollPos < 0) { + scrollPos = 0; + } + } + } + } + + protected void actionPerformed(GuiButton par1GuiButton) { + if(!dropDownOpen) { + if(par1GuiButton.id == 0) { + safeProfile(); + this.mc.displayGuiScreen((GuiScreen) parent); + }else if(par1GuiButton.id == 1) { + EagRuntime.displayFileChooser("image/png", "png"); + }else if(par1GuiButton.id == 2) { + EaglerProfile.clearCustomCapes(); + safeProfile(); + updateOptions(); + selectedSlot = 0; + } + } + } + + public void updateScreen() { + if(EagRuntime.fileChooserHasResult()) { + FileChooserResult result = EagRuntime.getFileChooserResult(); + if(result != null) { + ImageData loadedCape = ImageData.loadImageFile(result.fileData); + if(loadedCape != null) { + if((loadedCape.width == 32 || loadedCape.width == 64) && loadedCape.height == 32) { + byte[] resized = new byte[1173]; + SkinConverter.convertCape32x32RGBAto23x17RGB(loadedCape, resized); + int k; + if((k = EaglerProfile.addCustomCape(result.fileName, resized)) != -1) { + selectedSlot = k; + updateOptions(); + safeProfile(); + } + }else { + EagRuntime.showPopup("The selected image '" + result.fileName + "' is not the right size!\nEaglercraft only supports 32x32 or 64x32 capes"); + } + }else { + EagRuntime.showPopup("The selected file '" + result.fileName + "' is not a PNG file!"); + } + } + } + if(dropDownOpen) { + if(Mouse.isButtonDown(0)) { + int skinX = width / 2 - 20; + int skinY = height / 6 + 73; + int skinWidth = 140; + if(mousex >= (skinX + skinWidth - 10) && mousex < (skinX + skinWidth) && mousey >= skinY && mousey < (skinY + skinsHeight)) { + dragging = true; + } + if(dragging) { + int scrollerSize = skinsHeight * slotsVisible / dropDownOptions.length; + scrollPos = (mousey - skinY - (scrollerSize / 2)) * dropDownOptions.length / skinsHeight; + } + }else { + dragging = false; + } + }else { + dragging = false; + } + } + + public void onGuiClosed() { + Keyboard.enableRepeatEvents(false); + } + + protected void keyTyped(char c, int k) { + if(k == 200 && selectedSlot > 0) { + --selectedSlot; + scrollPos = selectedSlot - 2; + } + if(k == 208 && selectedSlot < (dropDownOptions.length - 1)) { + ++selectedSlot; + scrollPos = selectedSlot - 2; + } + } + + protected void mouseClicked(int mx, int my, int button) { + if (button == 0) { + if(!EagRuntime.getConfiguration().isDemo()) { + int w = mc.fontRendererObj.getStringWidth(I18n.format("editProfile.importExport")); + if(mx > 1 && my > 1 && mx < (w * 3 / 4) + 7 && my < 12) { + safeProfile(); + mc.displayGuiScreen(new GuiScreenImportExportProfile(parent)); + mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + return; + } + } + + int skinX = width / 2 + 140 - 40; + int skinY = height / 6 + 52; + + if(mx >= skinX && mx < (skinX + 20) && my >= skinY && my < (skinY + 22)) { + dropDownOpen = !dropDownOpen; + return; + } + + skinX = width / 2 - 20; + skinY = height / 6 + 52; + int skinWidth = 140; + int skinHeight = skinsHeight; + + if(!(mx >= skinX && mx < (skinX + skinWidth) && my >= skinY && my < (skinY + skinHeight + 22))) { + dragging = false; + if(dropDownOpen) { + dropDownOpen = false; + return; + } + }else if(dropDownOpen && !dragging) { + skinY += 21; + for(int i = 0; i < slotsVisible; i++) { + if(i + scrollPos < dropDownOptions.length) { + if(mx >= skinX && mx < (skinX + skinWidth - 10) && my >= (skinY + i * 10 + 5) && my < (skinY + i * 10 + 15) && selectedSlot != i + scrollPos) { + selectedSlot = i + scrollPos; + dropDownOpen = false; + dragging = false; + return; + } + } + } + } + } + super.mouseClicked(mx, my, button); + } + + protected void safeProfile() { + int customLen = EaglerProfile.customCapes.size(); + if(selectedSlot < customLen) { + EaglerProfile.presetCapeId = -1; + EaglerProfile.customCapeId = selectedSlot; + }else { + EaglerProfile.presetCapeId = selectedSlot - customLen; + EaglerProfile.customCapeId = -1; + } + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/GuiScreenEditProfile.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/GuiScreenEditProfile.java index ac9f393..fe319bc 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/GuiScreenEditProfile.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/GuiScreenEditProfile.java @@ -20,7 +20,7 @@ import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; import java.io.IOException; /** - * Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -42,7 +42,7 @@ public class GuiScreenEditProfile extends GuiScreen { private boolean dropDownOpen = false; private String[] dropDownOptions; private int slotsVisible = 0; - private int selectedSlot = 0; + protected int selectedSlot = 0; private int scrollPos = -1; private int skinsHeight = 0; private boolean dragging = false; @@ -102,11 +102,25 @@ public class GuiScreenEditProfile extends GuiScreen { drawRect(skinX, skinY, skinX + skinWidth, skinY + skinHeight, 0xFFA0A0A0); drawRect(skinX + 1, skinY + 1, skinX + skinWidth - 1, skinY + skinHeight - 1, 0xFF000015); - int skid = selectedSlot - EaglerProfile.customSkins.size(); - if(skid < 0) { - skid = 0; + GlStateManager.pushMatrix(); + GlStateManager.translate(skinX + 2, skinY - 9, 0.0f); + GlStateManager.scale(0.75f, 0.75f, 0.75f); + + int numberOfCustomSkins = EaglerProfile.customSkins.size(); + int skid = selectedSlot - numberOfCustomSkins; + SkinModel selectedSkinModel = skid < 0 ? EaglerProfile.customSkins.get(selectedSlot).model : DefaultSkins.getSkinFromId(skid).model; + if(selectedSkinModel == SkinModel.STEVE || selectedSkinModel == SkinModel.ALEX || (selectedSkinModel.highPoly != null && !this.mc.gameSettings.enableFNAWSkins)) { + String capesText = I18n.format("editProfile.capes"); + int color = 10526880; + if(mx > skinX - 10 && my > skinY - 16 && mx < skinX + (fontRendererObj.getStringWidth(capesText) * 0.75f) + 10 && my < skinY + 7) { + color = 0xFFCCCC44; + Mouse.showCursor(EnumCursorType.HAND); + } + this.drawString(this.fontRendererObj, EnumChatFormatting.UNDERLINE + capesText, 0, 0, color); } + GlStateManager.popMatrix(); + usernameField.drawTextBox(); if(dropDownOpen || newSkinWaitSteveOrAlex) { super.drawScreen(0, 0, partialTicks); @@ -114,6 +128,10 @@ public class GuiScreenEditProfile extends GuiScreen { super.drawScreen(mx, my, partialTicks); } + if(selectedSkinModel.highPoly != null) { + drawCenteredString(fontRendererObj, I18n.format(this.mc.gameSettings.enableFNAWSkins ? "editProfile.disableFNAW" : "editProfile.enableFNAW"), width / 2, height / 6 + 150, 10526880); + } + skinX = width / 2 - 20; skinY = height / 6 + 82; skinWidth = 140; @@ -184,7 +202,6 @@ public class GuiScreenEditProfile extends GuiScreen { int xx = width / 2 - 80; int yy = height / 6 + 130; - int numberOfCustomSkins = EaglerProfile.customSkins.size(); if(newSkinWaitSteveOrAlex && selectedSlot < numberOfCustomSkins) { skinWidth = 70; @@ -217,8 +234,8 @@ public class GuiScreenEditProfile extends GuiScreen { drawCenteredString(fontRendererObj, "Steve", skinX + skinWidth / 2, skinY + skinHeight + 6, cc); } - mc.getTextureManager().bindTexture(newSkin.getResource()); - SkinPreviewRenderer.renderBiped(xx, yy, mx, my, SkinModel.STEVE); + SkinPreviewRenderer.renderPreview(xx, yy, mx, my, false, SkinModel.STEVE, newSkin.getResource(), + EaglerProfile.getActiveCapeResourceLocation()); skinX = width / 2 + 20; skinY = height / 4; @@ -242,8 +259,8 @@ public class GuiScreenEditProfile extends GuiScreen { drawCenteredString(fontRendererObj, "Alex", skinX + skinWidth / 2, skinY + skinHeight + 8, cc); } - mc.getTextureManager().bindTexture(newSkin.getResource()); - SkinPreviewRenderer.renderBiped(xx, yy, mx, my, SkinModel.ALEX); + SkinPreviewRenderer.renderPreview(xx, yy, mx, my, false, SkinModel.ALEX, newSkin.getResource(), + EaglerProfile.getActiveCapeResourceLocation()); }else { skinX = this.width / 2 - 120; skinY = this.height / 6 + 8; @@ -251,20 +268,17 @@ public class GuiScreenEditProfile extends GuiScreen { skinHeight = 130; ResourceLocation texture; - SkinModel model; - if(selectedSlot < numberOfCustomSkins) { - CustomSkin customSkin = EaglerProfile.customSkins.get(selectedSlot); - texture = customSkin.getResource(); - model = customSkin.model; + if(skid < 0) { + texture = EaglerProfile.customSkins.get(selectedSlot).getResource(); }else { - DefaultSkins defaultSkin = DefaultSkins.defaultSkinsMap[selectedSlot - numberOfCustomSkins]; - texture = defaultSkin.location; - model = defaultSkin.model; + texture = DefaultSkins.getSkinFromId(skid).location; } - mc.getTextureManager().bindTexture(texture); - SkinPreviewRenderer.renderBiped(xx, yy, newSkinWaitSteveOrAlex ? width / 2 : mx, newSkinWaitSteveOrAlex ? height / 2 : my, model); + SkinPreviewRenderer.renderPreview(xx, yy, newSkinWaitSteveOrAlex ? width / 2 : mx, + newSkinWaitSteveOrAlex ? height / 2 : my, false, selectedSkinModel, texture, + EaglerProfile.getActiveCapeResourceLocation()); } + } public void handleMouseInput() throws IOException { @@ -287,12 +301,14 @@ public class GuiScreenEditProfile extends GuiScreen { if(!dropDownOpen) { if(par1GuiButton.id == 0) { safeProfile(); + EaglerProfile.save(); this.mc.displayGuiScreen((GuiScreen) parent); }else if(par1GuiButton.id == 1) { EagRuntime.displayFileChooser("image/png", "png"); }else if(par1GuiButton.id == 2) { EaglerProfile.clearCustomSkins(); safeProfile(); + EaglerProfile.save(); updateOptions(); selectedSlot = 0; } @@ -335,6 +351,7 @@ public class GuiScreenEditProfile extends GuiScreen { newSkinWaitSteveOrAlex = true; updateOptions(); safeProfile(); + EaglerProfile.save(); } }else { EagRuntime.showPopup("The selected image '" + result.fileName + "' is not the right size!\nEaglercraft only supports 64x32 or 64x64 skins"); @@ -387,21 +404,37 @@ public class GuiScreenEditProfile extends GuiScreen { } protected void mouseClicked(int mx, int my, int button) { - super.mouseClicked(mx, my, button); usernameField.mouseClicked(mx, my, button); if (button == 0) { if(!EagRuntime.getConfiguration().isDemo()) { int w = mc.fontRendererObj.getStringWidth(I18n.format("editProfile.importExport")); if(mx > 1 && my > 1 && mx < (w * 3 / 4) + 7 && my < 12) { + safeProfile(); + EaglerProfile.save(); mc.displayGuiScreen(new GuiScreenImportExportProfile(this)); mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); return; } } + int skinX, skinY; + int skid = selectedSlot - EaglerProfile.customSkins.size(); + SkinModel selectedSkinModel = skid < 0 ? EaglerProfile.customSkins.get(selectedSlot).model : DefaultSkins.getSkinFromId(skid).model; + if(selectedSkinModel == SkinModel.STEVE || selectedSkinModel == SkinModel.ALEX || (selectedSkinModel.highPoly != null && !this.mc.gameSettings.enableFNAWSkins)) { + skinX = this.width / 2 - 120; + skinY = this.height / 6 + 8; + String capesText = I18n.format("editProfile.capes"); + if(mx > skinX - 10 && my > skinY - 16 && mx < skinX + (fontRendererObj.getStringWidth(capesText) * 0.75f) + 10 && my < skinY + 7) { + safeProfile(); + this.mc.displayGuiScreen(new GuiScreenEditCape(this)); + mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + return; + } + } + if(newSkinWaitSteveOrAlex) { - int skinX = width / 2 - 90; - int skinY = height / 4; + skinX = width / 2 - 90; + skinY = height / 4; int skinWidth = 70; int skinHeight = 120; if(mx >= skinX && my >= skinY && mx < skinX + skinWidth && my < skinY + skinHeight) { @@ -423,8 +456,8 @@ public class GuiScreenEditProfile extends GuiScreen { } return; }else if(selectedSlot < EaglerProfile.customSkins.size()) { - int skinX = width / 2 - 120; - int skinY = height / 6 + 18; + skinX = width / 2 - 120; + skinY = height / 6 + 18; int skinWidth = 80; int skinHeight = 120; if(mx >= skinX && my >= skinY && mx < skinX + skinWidth && my < skinY + skinHeight) { @@ -434,8 +467,8 @@ public class GuiScreenEditProfile extends GuiScreen { } } } - int skinX = width / 2 + 140 - 40; - int skinY = height / 6 + 82; + skinX = width / 2 + 140 - 40; + skinY = height / 6 + 82; if(mx >= skinX && mx < (skinX + 20) && my >= skinY && my < (skinY + 22)) { dropDownOpen = !dropDownOpen; @@ -448,27 +481,26 @@ public class GuiScreenEditProfile extends GuiScreen { int skinHeight = skinsHeight; if(!(mx >= skinX && mx < (skinX + skinWidth) && my >= skinY && my < (skinY + skinHeight + 22))) { - dropDownOpen = false; dragging = false; - return; - } - - skinY += 21; - - if(dropDownOpen && !dragging) { + if(dropDownOpen) { + dropDownOpen = false; + return; + } + }else if(dropDownOpen && !dragging) { + skinY += 21; for(int i = 0; i < slotsVisible; i++) { if(i + scrollPos < dropDownOptions.length) { - if(selectedSlot != i + scrollPos) { - if(mx >= skinX && mx < (skinX + skinWidth - 10) && my >= (skinY + i * 10 + 5) && my < (skinY + i * 10 + 15) && selectedSlot != i + scrollPos) { - selectedSlot = i + scrollPos; - dropDownOpen = false; - dragging = false; - } + if(mx >= skinX && mx < (skinX + skinWidth - 10) && my >= (skinY + i * 10 + 5) && my < (skinY + i * 10 + 15)) { + selectedSlot = i + scrollPos; + dropDownOpen = false; + dragging = false; + return; } } } } } + super.mouseClicked(mx, my, button); } protected void safeProfile() { @@ -488,7 +520,6 @@ public class GuiScreenEditProfile extends GuiScreen { name = name.substring(0, 16); } EaglerProfile.setName(name); - EaglerProfile.save(); } } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/HighPolySkin.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/HighPolySkin.java new file mode 100644 index 0000000..f53a1e4 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/HighPolySkin.java @@ -0,0 +1,113 @@ +package net.lax1dude.eaglercraft.v1_8.profile; + +import net.minecraft.util.ResourceLocation; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public enum HighPolySkin { + + LONG_ARMS( + new ResourceLocation("eagler:mesh/longarms.png"), + new ResourceLocation("eagler:mesh/longarms0.mdl"), + null, + new ResourceLocation("eagler:mesh/longarms2.mdl"), + new ResourceLocation[] { + new ResourceLocation("eagler:mesh/longarms1.mdl") + }, + new float[] { + 1.325f + }, + 0.0f, + new ResourceLocation("eagler:mesh/longarms.fallback.png") + ), + + WEIRD_CLIMBER_DUDE( + new ResourceLocation("eagler:mesh/weirdclimber.png"), + new ResourceLocation("eagler:mesh/weirdclimber0.mdl"), + null, + new ResourceLocation("eagler:mesh/weirdclimber2.mdl"), + new ResourceLocation[] { + new ResourceLocation("eagler:mesh/weirdclimber1.mdl") + }, + new float[] { + 2.62f + }, + -90.0f, + new ResourceLocation("eagler:mesh/weirdclimber.fallback.png") + ), + + LAXATIVE_DUDE( + new ResourceLocation("eagler:mesh/laxativedude.png"), + new ResourceLocation("eagler:mesh/laxativedude0.mdl"), + null, + new ResourceLocation("eagler:mesh/laxativedude3.mdl"), + new ResourceLocation[] { + new ResourceLocation("eagler:mesh/laxativedude1.mdl"), + new ResourceLocation("eagler:mesh/laxativedude2.mdl") + }, + new float[] { + 2.04f + }, + 0.0f, + new ResourceLocation("eagler:mesh/laxativedude.fallback.png") + ), + + BABY_CHARLES( + new ResourceLocation("eagler:mesh/charles.png"), + new ResourceLocation("eagler:mesh/charles0.mdl"), + new ResourceLocation("eagler:mesh/charles1.mdl"), + new ResourceLocation("eagler:mesh/charles2.mdl"), + new ResourceLocation[] {}, + new float[] {}, + 0.0f, + new ResourceLocation("eagler:mesh/charles.fallback.png") + ), + + BABY_WINSTON( + new ResourceLocation("eagler:mesh/winston.png"), + new ResourceLocation("eagler:mesh/winston0.mdl"), + null, + new ResourceLocation("eagler:mesh/winston1.mdl"), + new ResourceLocation[] {}, + new float[] {}, + 0.0f, + new ResourceLocation("eagler:mesh/winston.fallback.png") + ); + + public static float highPolyScale = 0.5f; + + public final ResourceLocation texture; + public final ResourceLocation bodyModel; + public final ResourceLocation headModel; + public final ResourceLocation eyesModel; + public final ResourceLocation[] limbsModel; + public final float[] limbsOffset; + public final float limbsInitialRotation; + public final ResourceLocation fallbackTexture; + + HighPolySkin(ResourceLocation texture, ResourceLocation bodyModel, ResourceLocation headModel, ResourceLocation eyesModel, + ResourceLocation[] limbsModel, float[] limbsOffset, float limbsInitialRotation, ResourceLocation fallbackTexture) { + this.texture = texture; + this.bodyModel = bodyModel; + this.headModel = headModel; + this.eyesModel = eyesModel; + this.limbsModel = limbsModel; + this.limbsOffset = limbsOffset; + this.limbsInitialRotation = limbsInitialRotation; + this.fallbackTexture = fallbackTexture; + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/RenderHighPoly.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/RenderHighPoly.java new file mode 100644 index 0000000..fb83670 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/RenderHighPoly.java @@ -0,0 +1,463 @@ +package net.lax1dude.eaglercraft.v1_8.profile; + +import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; + +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.opengl.EaglerMeshLoader; +import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU; +import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; +import net.lax1dude.eaglercraft.v1_8.opengl.OpenGlHelper; +import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.DeferredStateManager; +import net.lax1dude.eaglercraft.v1_8.vector.Matrix4f; +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.AbstractClientPlayer; +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.entity.RenderPlayer; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.util.MathHelper; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class RenderHighPoly extends RenderPlayer { + + private static final Logger logger = LogManager.getLogger("RenderHighPoly"); + + public RenderHighPoly(RenderManager renderManager, ModelBase fallbackModel, float fallbackScale) { + super(renderManager, fallbackModel, fallbackScale); + } + + private static final Matrix4f tmpMatrix = new Matrix4f(); + + public void doRender(AbstractClientPlayer abstractclientplayer, double d0, double d1, double d2, float f, + float f1) { + if (!abstractclientplayer.isUser() || this.renderManager.livingPlayer == abstractclientplayer) { + HighPolySkin highPolySkin = abstractclientplayer.getEaglerSkinModel().highPoly; + + if(highPolySkin == null) { + super.doRender(abstractclientplayer, d0, d1, d2, f, f1); + return; + } + + GlStateManager.pushMatrix(); + GlStateManager.disableCull(); + + try { + Minecraft mc = Minecraft.getMinecraft(); + float f2 = this.interpolateRotation(abstractclientplayer.prevRenderYawOffset, abstractclientplayer.renderYawOffset, + f1); + float f3 = this.interpolateRotation(abstractclientplayer.prevRotationYawHead, abstractclientplayer.rotationYawHead, + f1); + float f4 = f3 - f2; + if (abstractclientplayer.isRiding() && abstractclientplayer.ridingEntity instanceof EntityLivingBase) { + EntityLivingBase entitylivingbase1 = (EntityLivingBase) abstractclientplayer.ridingEntity; + f2 = this.interpolateRotation(entitylivingbase1.prevRenderYawOffset, entitylivingbase1.renderYawOffset, + f1); + f4 = f3 - f2; + float f5 = MathHelper.wrapAngleTo180_float(f4); + if (f5 < -85.0F) { + f5 = -85.0F; + } + + if (f5 >= 85.0F) { + f5 = 85.0F; + } + + f2 = f3 - f5; + if (f5 * f5 > 2500.0F) { + f2 += f5 * 0.2F; + } + } + + this.renderLivingAt(abstractclientplayer, d0, d1, d2); + float f10 = this.handleRotationFloat(abstractclientplayer, f1); + this.rotateCorpse(abstractclientplayer, f10, f2, f1); + GlStateManager.enableRescaleNormal(); + this.preRenderCallback(abstractclientplayer, f1); + float f6 = 0.0625F; + GlStateManager.scale(HighPolySkin.highPolyScale, HighPolySkin.highPolyScale, HighPolySkin.highPolyScale); + mc.getTextureManager().bindTexture(highPolySkin.texture); + + if(abstractclientplayer.isPlayerSleeping()) { + if(highPolySkin == HighPolySkin.LAXATIVE_DUDE || highPolySkin == HighPolySkin.WEIRD_CLIMBER_DUDE) { + GlStateManager.translate(0.0f, -3.7f, 0.0f); + }else if(highPolySkin == HighPolySkin.BABY_WINSTON) { + GlStateManager.translate(0.0f, -2.4f, 0.0f); + }else { + GlStateManager.translate(0.0f, -3.0f, 0.0f); + } + } + + float var15 = abstractclientplayer.prevLimbSwingAmount + (abstractclientplayer.limbSwingAmount - abstractclientplayer.prevLimbSwingAmount) * f1; + float var16 = abstractclientplayer.limbSwing - abstractclientplayer.limbSwingAmount * (1.0F - f1); + + if(highPolySkin == HighPolySkin.LONG_ARMS) { + GlStateManager.rotate(MathHelper.sin(var16) * 20f * var15, 0.0f, 1.0f, 0.0f); + GlStateManager.rotate(MathHelper.cos(var16) * 7f * var15, 0.0f, 0.0f, 1.0f); + }else if(highPolySkin == HighPolySkin.WEIRD_CLIMBER_DUDE) { + GlStateManager.rotate(MathHelper.sin(var16) * 7f * var15, 0.0f, 1.0f, 0.0f); + GlStateManager.rotate(MathHelper.cos(var16) * 3f * var15, 0.0f, 0.0f, 1.0f); + GlStateManager.rotate(-f3, 0.0f, 1.0f, 0.0f); + float xd = (float)(abstractclientplayer.posX - abstractclientplayer.prevPosX); + GlStateManager.rotate(xd * 70.0f * var15, 0.0f, 0.0f, 1.0f); + float zd = (float)(abstractclientplayer.posZ - abstractclientplayer.prevPosZ); + GlStateManager.rotate(zd * 70.0f * var15, 1.0f, 0.0f, 0.0f); + GlStateManager.rotate(f3, 0.0f, 1.0f, 0.0f); + }else if(highPolySkin == HighPolySkin.LAXATIVE_DUDE) { + GlStateManager.rotate(-f3, 0.0f, 1.0f, 0.0f); + float xd = (float)(abstractclientplayer.posX - abstractclientplayer.prevPosX); + GlStateManager.rotate(-xd * 40.0f * var15, 0.0f, 0.0f, 1.0f); + float zd = (float)(abstractclientplayer.posZ - abstractclientplayer.prevPosZ); + GlStateManager.rotate(-zd * 40.0f * var15, 1.0f, 0.0f, 0.0f); + GlStateManager.rotate(f3, 0.0f, 1.0f, 0.0f); + }else if(highPolySkin == HighPolySkin.BABY_WINSTON) { + GlStateManager.translate(0.0f, (MathHelper.cos(f10 % 100000.0f) + 1.0f) * var15 * 0.2f, 0.0f); + GlStateManager.rotate(MathHelper.sin(var16) * 5f * var15, 0.0f, 1.0f, 0.0f); + GlStateManager.rotate(MathHelper.cos(var16) * 5f * var15, 0.0f, 0.0f, 1.0f); + } + + if (abstractclientplayer.hurtTime > 0 || abstractclientplayer.deathTime > 0) { + GlStateManager.color(1.2f, 0.8F, 0.8F, 1.0F); + } + + if(DeferredStateManager.isInDeferredPass()) { + DeferredStateManager.setDefaultMaterialConstants(); + DeferredStateManager.setRoughnessConstant(0.5f); + DeferredStateManager.setMetalnessConstant(0.05f); + } + + if(highPolySkin.bodyModel != null) { + EaglercraftGPU.drawHighPoly(EaglerMeshLoader.getEaglerMesh(highPolySkin.bodyModel)); + } + float jumpFactor = 0.0f; + + if(highPolySkin.headModel != null) { + if(highPolySkin == HighPolySkin.BABY_CHARLES) { + long millis = System.currentTimeMillis(); + float partialTicks = (float) ((millis - abstractclientplayer.eaglerHighPolyAnimationTick) * 0.02); + //long l50 = millis / 50l * 50l; + //boolean runTick = par1EntityPlayer.eaglerHighPolyAnimationTick < l50 && millis >= l50; + abstractclientplayer.eaglerHighPolyAnimationTick = millis; + + if(partialTicks < 0.0f) { + partialTicks = 0.0f; + } + if(partialTicks > 1.0f) { + partialTicks = 1.0f; + } + + float jumpFac = (float)(abstractclientplayer.posY - abstractclientplayer.prevPosY); + if(jumpFac < 0.0f && !abstractclientplayer.isCollidedVertically) { + jumpFac = -jumpFac; + jumpFac *= 0.1f; + } + jumpFac -= 0.05f; + if(jumpFac > 0.1f && !abstractclientplayer.isCollidedVertically) { + jumpFac = 0.1f; + }else if(jumpFac < 0.0f) { + jumpFac = 0.0f; + }else if(jumpFac > 0.1f && abstractclientplayer.isCollidedVertically) { + jumpFac = 0.1f; + }else if(jumpFac > 0.4f) { + jumpFac = 0.4f; + } + jumpFac *= 10.0f; + + abstractclientplayer.eaglerHighPolyAnimationFloat3 += (jumpFac / (jumpFac + 1.0f)) * 6.0f * partialTicks; + + if(Float.isInfinite(abstractclientplayer.eaglerHighPolyAnimationFloat3)) { + abstractclientplayer.eaglerHighPolyAnimationFloat3 = 1.0f; + }else if(abstractclientplayer.eaglerHighPolyAnimationFloat3 > 1.0f) { + abstractclientplayer.eaglerHighPolyAnimationFloat3 = 1.0f; + }else if(abstractclientplayer.eaglerHighPolyAnimationFloat3 < -1.0f) { + abstractclientplayer.eaglerHighPolyAnimationFloat3 = -1.0f; + } + + abstractclientplayer.eaglerHighPolyAnimationFloat2 += abstractclientplayer.eaglerHighPolyAnimationFloat3 * partialTicks; + + abstractclientplayer.eaglerHighPolyAnimationFloat5 += partialTicks; + while(abstractclientplayer.eaglerHighPolyAnimationFloat5 > 0.05f) { + abstractclientplayer.eaglerHighPolyAnimationFloat5 -= 0.05f; + abstractclientplayer.eaglerHighPolyAnimationFloat3 *= 0.99f; + abstractclientplayer.eaglerHighPolyAnimationFloat2 *= 0.9f; + } + + jumpFactor = abstractclientplayer.eaglerHighPolyAnimationFloat2; //(abstractclientplayer.eaglerHighPolyAnimationFloat1 - abstractclientplayer.eaglerHighPolyAnimationFloat2) * partialTicks + abstractclientplayer.eaglerHighPolyAnimationFloat2; + jumpFactor -= 0.12f; + if(jumpFactor < 0.0f) { + jumpFactor = 0.0f; + } + jumpFactor = jumpFactor / (jumpFactor + 2.0f); + if(jumpFactor > 1.0f) { + jumpFactor = 1.0f; + } + } + if(jumpFactor > 0.0f) { + GlStateManager.pushMatrix(); + GlStateManager.translate(0.0f, jumpFactor * 3.0f, 0.0f); + } + + EaglercraftGPU.drawHighPoly(EaglerMeshLoader.getEaglerMesh(highPolySkin.headModel)); + + if(jumpFactor > 0.0f) { + GlStateManager.popMatrix(); + } + } + + if(highPolySkin.limbsModel != null && highPolySkin.limbsModel.length > 0) { + for(int i = 0; i < highPolySkin.limbsModel.length; ++i) { + DeferredStateManager.setRoughnessConstant(0.023f); + DeferredStateManager.setMetalnessConstant(0.902f); + float offset = 0.0f; + if(highPolySkin.limbsOffset != null) { + if(highPolySkin.limbsOffset.length == 1) { + offset = highPolySkin.limbsOffset[0]; + }else { + offset = highPolySkin.limbsOffset[i]; + } + } + + GlStateManager.pushMatrix(); + + if(offset != 0.0f || highPolySkin.limbsInitialRotation != 0.0f) { + if(offset != 0.0f) { + GlStateManager.translate(0.0f, offset, 0.0f); + } + if(highPolySkin.limbsInitialRotation != 0.0f) { + GlStateManager.rotate(highPolySkin.limbsInitialRotation, 1.0f, 0.0f, 0.0f); + } + } + + if(highPolySkin == HighPolySkin.LONG_ARMS) { + if(abstractclientplayer.isSwingInProgress) { + float var17 = MathHelper.cos(-abstractclientplayer.getSwingProgress(f1) * (float)Math.PI * 2.0f - 1.2f) - 0.362f; + var17 *= var17; + GlStateManager.rotate(-var17 * 20.0f, 1.0f, 0.0f, 0.0f); + } + }else if(highPolySkin == HighPolySkin.WEIRD_CLIMBER_DUDE) { + if(abstractclientplayer.isSwingInProgress) { + float var17 = MathHelper.cos(-abstractclientplayer.getSwingProgress(f1) * (float)Math.PI * 2.0f - 1.2f) - 0.362f; + var17 *= var17; + GlStateManager.rotate(var17 * 60.0f, 1.0f, 0.0f, 0.0f); + } + GlStateManager.rotate(40.0f * var15, 1.0f, 0.0f, 0.0f); + }else if(highPolySkin == HighPolySkin.LAXATIVE_DUDE) { + float fff = (i == 0) ? 1.0f : -1.0f; + float swing = (MathHelper.cos(f10 % 100000.0f) * fff + 0.2f) * var15; + float swing2 = (MathHelper.cos(f10 % 100000.0f) * fff * 0.5f + 0.0f) * var15; + GlStateManager.rotate(swing * 25.0f, 1.0f, 0.0f, 0.0f); + if(abstractclientplayer.isSwingInProgress) { + float var17 = MathHelper.cos(-abstractclientplayer.getSwingProgress(f1) * (float)Math.PI * 2.0f - 1.2f) - 0.362f; + var17 *= var17; + GlStateManager.rotate(-var17 * 25.0f, 1.0f, 0.0f, 0.0f); + } + + // shear matrix + tmpMatrix.setIdentity(); + tmpMatrix.m21 = swing2; + tmpMatrix.m23 = swing2 * -0.2f; + GlStateManager.multMatrix(tmpMatrix); + } + + if(i != 0) { + mc.getTextureManager().bindTexture(highPolySkin.texture); + if (abstractclientplayer.hurtTime > 0 || abstractclientplayer.deathTime > 0) { + GlStateManager.color(1.2f, 0.8F, 0.8F, 1.0F); + }else { + GlStateManager.color(1.0f, 1.0F, 1.0F, 1.0F); + } + } + EaglercraftGPU.drawHighPoly(EaglerMeshLoader.getEaglerMesh(highPolySkin.limbsModel[i])); + + if(i == 0) { + GlStateManager.pushMatrix(); + + GlStateManager.translate(-0.287f, 0.05f, 0.0f); + + if(highPolySkin == HighPolySkin.LONG_ARMS) { + GlStateManager.translate(1.72f, 2.05f, -0.24f); + ItemStack stk = abstractclientplayer.getHeldItem(); + if(stk != null) { + Item itm = stk.getItem(); + if(itm != null) { + if(itm == Items.bow) { + GlStateManager.translate(-0.22f, 0.8f, 0.6f); + GlStateManager.rotate(-90.0f, 1.0f, 0.0f, 0.0f); + }else if(itm instanceof ItemBlock && !((ItemBlock)itm).getBlock().isNormalCube()) { + GlStateManager.translate(0.0f, -0.1f, 0.13f); + }else if(!itm.isFull3D()) { + GlStateManager.translate(-0.08f, -0.1f, 0.16f); + } + } + } + }else if(highPolySkin == HighPolySkin.WEIRD_CLIMBER_DUDE) { + GlStateManager.translate(-0.029f, 1.2f, -3f); + GlStateManager.rotate(-5.0f, 0.0f, 1.0f, 0.0f); + float var17 = -1.2f * var15; + if(abstractclientplayer.isSwingInProgress) { + float vvar17 = MathHelper.cos(-abstractclientplayer.getSwingProgress(f1) * (float)Math.PI * 2.0f - 1.2f) - 0.362f; + var17 = vvar17 < var17 ? vvar17 : var17; + } + GlStateManager.translate(-0.02f * var17, 0.42f * var17, var17 * 0.35f); + GlStateManager.rotate(var17 * 30.0f, 1.0f, 0.0f, 0.0f); + GlStateManager.rotate(110.0f, 1.0f, 0.0f, 0.0f); + ItemStack stk = abstractclientplayer.getHeldItem(); + if(stk != null) { + Item itm = stk.getItem(); + if(itm != null) { + if(itm == Items.bow) { + GlStateManager.translate(-0.18f, 1.0f, 0.4f); + GlStateManager.rotate(-95.0f, 1.0f, 0.0f, 0.0f); + }else if(itm instanceof ItemBlock && !((ItemBlock)itm).getBlock().isNormalCube()) { + GlStateManager.translate(0.0f, -0.1f, 0.13f); + }else if(!itm.isFull3D()) { + GlStateManager.translate(-0.08f, -0.1f, 0.16f); + } + } + } + }else if(highPolySkin == HighPolySkin.LAXATIVE_DUDE) { + GlStateManager.translate(1.291f, 2.44f, -2.18f); + GlStateManager.rotate(95.0f, 1.0f, 0.0f, 0.0f); + ItemStack stk = abstractclientplayer.getHeldItem(); + if(stk != null) { + Item itm = stk.getItem(); + if(itm != null) { + if(itm == Items.bow) { + GlStateManager.translate(-0.65f, 1.3f, -0.1f); + GlStateManager.rotate(180.0f, 0.0f, 0.0f, 1.0f); + GlStateManager.rotate(20.0f, 1.0f, 0.0f, 0.0f); + }else if(itm instanceof ItemBlock && !((ItemBlock)itm).getBlock().isNormalCube()) { + GlStateManager.translate(0.0f, -0.35f, 0.4f); + }else if(!itm.isFull3D()) { + GlStateManager.translate(-0.1f, -0.1f, 0.16f); + } + } + } + } + + DeferredStateManager.setDefaultMaterialConstants(); + renderHeldItem(abstractclientplayer, f1); + GlStateManager.popMatrix(); + } + + GlStateManager.popMatrix(); + } + } + + if(highPolySkin.eyesModel != null && !DeferredStateManager.isEnableShadowRender()) { + float ff = 0.00416f; + int brightness = abstractclientplayer.getBrightnessForRender(0.0f); + float blockLight = (brightness % 65536) * ff; + float skyLight = (brightness / 65536) * ff; + float sunCurve = (float)((abstractclientplayer.worldObj.getWorldTime() + 4000l) % 24000) / 24000.0f; + sunCurve = MathHelper.clamp_float(9.8f - MathHelper.abs(sunCurve * 5.0f + sunCurve * sunCurve * 45.0f - 14.3f) * 0.7f, 0.0f, 1.0f); + skyLight = skyLight * (sunCurve * 0.85f + 0.15f); + blockLight = blockLight * (sunCurve * 0.3f + 0.7f); + float eyeBrightness = blockLight; + if(skyLight > eyeBrightness) { + eyeBrightness = skyLight; + } + eyeBrightness += blockLight * 0.2f; + eyeBrightness = 1.0f - eyeBrightness; + eyeBrightness = MathHelper.clamp_float(eyeBrightness * 1.9f - 1.0f, 0.0f, 1.0f); + if(eyeBrightness > 0.1f) { + if(DeferredStateManager.isInDeferredPass()) { + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + DeferredStateManager.setDefaultMaterialConstants(); + DeferredStateManager.setEmissionConstant(eyeBrightness); + }else { + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GL_ONE, GL_ONE); + GlStateManager.color(eyeBrightness * 7.0f, eyeBrightness * 7.0f, eyeBrightness * 7.0f, 1.0f); + if(jumpFactor > 0.0f) { + GlStateManager.pushMatrix(); + GlStateManager.translate(0.0f, jumpFactor * 3.0f, 0.0f); + } + } + GlStateManager.disableTexture2D(); + GlStateManager.disableLighting(); + GlStateManager.enableCull(); + + EaglercraftGPU.drawHighPoly(EaglerMeshLoader.getEaglerMesh(highPolySkin.eyesModel)); + + GlStateManager.enableTexture2D(); + GlStateManager.enableLighting(); + GlStateManager.disableCull(); + if(jumpFactor > 0.0f) { + GlStateManager.popMatrix(); + } + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + if(!DeferredStateManager.isInDeferredPass()) { + GlStateManager.disableBlend(); + } + } + } + }catch(Throwable t) { + logger.error("Couldn\'t render entity"); + logger.error(t); + } + GlStateManager.setActiveTexture(OpenGlHelper.lightmapTexUnit); + GlStateManager.enableTexture2D(); + GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit); + GlStateManager.enableCull(); + GlStateManager.popMatrix(); + } + } + + public void renderRightArm(AbstractClientPlayer clientPlayer) { + + } + + public void renderLeftArm(AbstractClientPlayer clientPlayer) { + + } + + protected void renderHeldItem(AbstractClientPlayer clientPlayer, float partialTicks) { + ItemStack itemstack = clientPlayer.getHeldItem(); + if (itemstack != null) { + GlStateManager.pushMatrix(); + GlStateManager.translate(-0.11F, 0.475F, 0.25F); + if (clientPlayer.fishEntity != null) { + itemstack = new ItemStack(Items.fishing_rod, 0); + } + + Item item = itemstack.getItem(); + Minecraft minecraft = Minecraft.getMinecraft(); + if (item instanceof ItemBlock && Block.getBlockFromItem(item).getRenderType() == 2) { + GlStateManager.translate(0.0F, 0.1875F, -0.3125F); + GlStateManager.rotate(20.0F, 1.0F, 0.0F, 0.0F); + GlStateManager.rotate(45.0F, 0.0F, 1.0F, 0.0F); + float f1 = 0.375F; + GlStateManager.scale(-f1, -f1, f1); + } + + if (clientPlayer.isSneaking()) { + GlStateManager.translate(0.0F, 0.203125F, 0.0F); + } + + minecraft.getItemRenderer().renderItem(clientPlayer, itemstack, + ItemCameraTransforms.TransformType.THIRD_PERSON); + GlStateManager.popMatrix(); + } + } +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/ServerCapeCache.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/ServerCapeCache.java new file mode 100644 index 0000000..77fb276 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/ServerCapeCache.java @@ -0,0 +1,242 @@ +package net.lax1dude.eaglercraft.v1_8.profile; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.socket.EaglercraftNetworkManager; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.client.C17PacketCustomPayload; +import net.minecraft.util.ResourceLocation; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class ServerCapeCache { + + private static final Logger logger = LogManager.getLogger("ServerCapeCache"); + + public class CapeCacheEntry { + + protected final boolean isPresetCape; + protected final int presetCapeId; + protected final CacheCustomCape customCape; + + protected long lastCacheHit = System.currentTimeMillis(); + + protected CapeCacheEntry(EaglerSkinTexture textureInstance, ResourceLocation resourceLocation) { + this.isPresetCape = false; + this.presetCapeId = -1; + this.customCape = new CacheCustomCape(textureInstance, resourceLocation); + ServerCapeCache.this.textureManager.loadTexture(resourceLocation, textureInstance); + } + + /** + * Use only for the constant for the client player + */ + protected CapeCacheEntry(ResourceLocation resourceLocation) { + this.isPresetCape = false; + this.presetCapeId = -1; + this.customCape = new CacheCustomCape(null, resourceLocation); + } + + protected CapeCacheEntry(int presetSkinId) { + this.isPresetCape = true; + this.presetCapeId = presetSkinId; + this.customCape = null; + } + + public ResourceLocation getResourceLocation() { + if(isPresetCape) { + return DefaultCapes.getCapeFromId(presetCapeId).location; + }else { + if(customCape != null) { + return customCape.resourceLocation; + }else { + return null; + } + } + } + + protected void free() { + if(!isPresetCape && customCape.resourceLocation != null) { + ServerCapeCache.this.textureManager.deleteTexture(customCape.resourceLocation); + } + } + + } + + protected static class CacheCustomCape { + + protected final EaglerSkinTexture textureInstance; + protected final ResourceLocation resourceLocation; + + protected CacheCustomCape(EaglerSkinTexture textureInstance, ResourceLocation resourceLocation) { + this.textureInstance = textureInstance; + this.resourceLocation = resourceLocation; + } + + } + + private final CapeCacheEntry defaultCacheEntry = new CapeCacheEntry(0); + private final Map capesCache = new HashMap(); + private final Map waitingCapes = new HashMap(); + private final Map evictedCapes = new HashMap(); + + private final EaglercraftNetworkManager networkManager; + protected final TextureManager textureManager; + + private final EaglercraftUUID clientPlayerId; + private final CapeCacheEntry clientPlayerCacheEntry; + + private long lastFlush = System.currentTimeMillis(); + private long lastFlushReq = System.currentTimeMillis(); + private long lastFlushEvict = System.currentTimeMillis(); + + private static int texId = 0; + + public ServerCapeCache(EaglercraftNetworkManager networkManager, TextureManager textureManager) { + this.networkManager = networkManager; + this.textureManager = textureManager; + this.clientPlayerId = EaglerProfile.getPlayerUUID(); + this.clientPlayerCacheEntry = new CapeCacheEntry(EaglerProfile.getActiveCapeResourceLocation()); + } + + public CapeCacheEntry getClientPlayerCape() { + return clientPlayerCacheEntry; + } + + public CapeCacheEntry getCape(EaglercraftUUID player) { + if(player.equals(clientPlayerId)) { + return clientPlayerCacheEntry; + } + CapeCacheEntry etr = capesCache.get(player); + if(etr == null) { + if(!waitingCapes.containsKey(player) && !evictedCapes.containsKey(player)) { + waitingCapes.put(player, System.currentTimeMillis()); + PacketBuffer buffer; + try { + buffer = CapePackets.writeGetOtherCape(player); + }catch(IOException ex) { + logger.error("Could not write cape request packet!"); + logger.error(ex); + return defaultCacheEntry; + } + networkManager.sendPacket(new C17PacketCustomPayload("EAG|Capes-1.8", buffer)); + } + return defaultCacheEntry; + }else { + etr.lastCacheHit = System.currentTimeMillis(); + return etr; + } + } + + public void cacheCapePreset(EaglercraftUUID player, int presetId) { + if(waitingCapes.remove(player) != null) { + CapeCacheEntry etr = capesCache.remove(player); + if(etr != null) { + etr.free(); + } + capesCache.put(player, new CapeCacheEntry(presetId)); + }else { + logger.error("Unsolicited cape response recieved for \"{}\"! (preset {})", player, presetId); + } + } + + public void cacheCapeCustom(EaglercraftUUID player, byte[] pixels) { + if(waitingCapes.remove(player) != null) { + CapeCacheEntry etr = capesCache.remove(player); + if(etr != null) { + etr.free(); + } + byte[] pixels32x32 = new byte[4096]; + SkinConverter.convertCape23x17RGBto32x32RGBA(pixels, pixels32x32); + try { + etr = new CapeCacheEntry(new EaglerSkinTexture(pixels32x32, 32, 32), + new ResourceLocation("eagler:capes/multiplayer/tex_" + texId++)); + }catch(Throwable t) { + etr = new CapeCacheEntry(0); + logger.error("Could not process custom skin packet for \"{}\"!", player); + logger.error(t); + } + capesCache.put(player, etr); + }else { + logger.error("Unsolicited skin response recieved for \"{}\"!", player); + } + } + + public void flush() { + long millis = System.currentTimeMillis(); + if(millis - lastFlushReq > 5000l) { + lastFlushReq = millis; + if(!waitingCapes.isEmpty()) { + Iterator waitingItr = waitingCapes.values().iterator(); + while(waitingItr.hasNext()) { + if(millis - waitingItr.next().longValue() > 30000l) { + waitingItr.remove(); + } + } + } + } + if(millis - lastFlushEvict > 1000l) { + lastFlushEvict = millis; + if(!evictedCapes.isEmpty()) { + Iterator evictItr = evictedCapes.values().iterator(); + while(evictItr.hasNext()) { + if(millis - evictItr.next().longValue() > 3000l) { + evictItr.remove(); + } + } + } + } + if(millis - lastFlush > 60000l) { + lastFlush = millis; + if(!capesCache.isEmpty()) { + Iterator entryItr = capesCache.values().iterator(); + while(entryItr.hasNext()) { + CapeCacheEntry etr = entryItr.next(); + if(millis - etr.lastCacheHit > 900000l) { // 15 minutes + entryItr.remove(); + etr.free(); + } + } + } + } + } + + public void destroy() { + Iterator entryItr = capesCache.values().iterator(); + while(entryItr.hasNext()) { + entryItr.next().free(); + } + capesCache.clear(); + waitingCapes.clear(); + evictedCapes.clear(); + } + + public void evictCape(EaglercraftUUID uuid) { + evictedCapes.put(uuid, Long.valueOf(System.currentTimeMillis())); + CapeCacheEntry etr = capesCache.remove(uuid); + if(etr != null) { + etr.free(); + } + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/ServerSkinCache.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/ServerSkinCache.java index f2f6abd..5a88c28 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/ServerSkinCache.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/ServerSkinCache.java @@ -321,6 +321,7 @@ public class ServerSkinCache { } skinsCache.clear(); waitingSkins.clear(); + evictedSkins.clear(); } public void evictSkin(EaglercraftUUID uuid) { diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinConverter.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinConverter.java index a91a05c..c776fb2 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinConverter.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinConverter.java @@ -3,7 +3,7 @@ package net.lax1dude.eaglercraft.v1_8.profile; import net.lax1dude.eaglercraft.v1_8.opengl.ImageData; /** - * Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved. + * Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -35,6 +35,56 @@ public class SkinConverter { copyRawPixels(skinIn.pixels, skinOut.pixels, 48, 52, 44, 64, 52, 20, 56, 32, 64, 64); } + public static void convertCape32x32RGBAto23x17RGB(ImageData skinIn, byte[] skinOut) { + int i, j; + for(int y = 0; y < 17; ++y) { + for(int x = 0; x < 22; ++x) { + i = (y * 23 + x) * 3; + j = skinIn.pixels[y * skinIn.width + x]; + if((j & 0xFF000000) != 0) { + skinOut[i] = (byte)(j >> 16); + skinOut[i + 1] = (byte)(j >> 8); + skinOut[i + 2] = (byte)(j & 0xFF); + }else { + skinOut[i] = skinOut[i + 1] = skinOut[i + 2] = 0; + } + } + } + for(int y = 0; y < 11; ++y) { + i = ((y + 6) * 23 + 22) * 3; + j = skinIn.pixels[(y + 11) * skinIn.width + 22]; + if((j & 0xFF000000) != 0) { + skinOut[i] = (byte)(j >> 16); + skinOut[i + 1] = (byte)(j >> 8); + skinOut[i + 2] = (byte)(j & 0xFF); + }else { + skinOut[i] = skinOut[i + 1] = skinOut[i + 2] = 0; + } + } + } + + public static void convertCape23x17RGBto32x32RGBA(byte[] skinIn, byte[] skinOut) { + int i, j; + for(int y = 0; y < 17; ++y) { + for(int x = 0; x < 22; ++x) { + i = (y * 32 + x) << 2; + j = (y * 23 + x) * 3; + skinOut[i] = (byte)0xFF; + skinOut[i + 1] = skinIn[j]; + skinOut[i + 2] = skinIn[j + 1]; + skinOut[i + 3] = skinIn[j + 2]; + } + } + for(int y = 0; y < 11; ++y) { + i = ((y + 11) * 32 + 22) << 2; + j = ((y + 6) * 23 + 22) * 3; + skinOut[i] = (byte)0xFF; + skinOut[i + 1] = skinIn[j]; + skinOut[i + 2] = skinIn[j + 1]; + skinOut[i + 3] = skinIn[j + 2]; + } + } + private static void copyRawPixels(int[] imageIn, int[] imageOut, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, int imgSrcWidth, int imgDstWidth) { if(dx1 > dx2) { diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinModel.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinModel.java index 7956881..8dbb655 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinModel.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinModel.java @@ -19,15 +19,19 @@ import java.util.Map; * */ public enum SkinModel { - STEVE(0, 64, 64, "default", false), ALEX(1, 64, 64, "slim", false), ZOMBIE(2, 64, 64, "zombie", true); + STEVE(0, 64, 64, "default", false), ALEX(1, 64, 64, "slim", false), ZOMBIE(2, 64, 64, "zombie", true), + LONG_ARMS(3, HighPolySkin.LONG_ARMS), WEIRD_CLIMBER_DUDE(4, HighPolySkin.WEIRD_CLIMBER_DUDE), + LAXATIVE_DUDE(5, HighPolySkin.LAXATIVE_DUDE), BABY_CHARLES(6, HighPolySkin.BABY_CHARLES), + BABY_WINSTON(7, HighPolySkin.BABY_WINSTON); public final int id; public final int width; public final int height; public final String profileSkinType; public final boolean sanitize; + public final HighPolySkin highPoly; - public static final SkinModel[] skinModels = new SkinModel[3]; + public static final SkinModel[] skinModels = new SkinModel[8]; private static final Map skinModelsByName = new HashMap(); private SkinModel(int id, int w, int h, String profileSkinType, boolean sanitize) { @@ -36,6 +40,16 @@ public enum SkinModel { this.height = h; this.profileSkinType = profileSkinType; this.sanitize = sanitize; + this.highPoly = null; + } + + private SkinModel(int id, HighPolySkin highPoly) { + this.id = id; + this.width = 256; + this.height = 128; + this.profileSkinType = "eagler"; + this.sanitize = true; + this.highPoly = highPoly; } public static SkinModel getModelFromId(String str) { diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinPackets.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinPackets.java index 93dc6b6..510bd89 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinPackets.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinPackets.java @@ -58,6 +58,9 @@ public class SkinPackets { modelId = SkinModel.STEVE; } } + if(modelId.highPoly != null) { + modelId = SkinModel.STEVE; + } int bytesToRead = modelId.width * modelId.height * 4; byte[] readSkin = new byte[bytesToRead]; buffer.readBytes(readSkin); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinPreviewRenderer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinPreviewRenderer.java index 1608c04..36a2089 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinPreviewRenderer.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/profile/SkinPreviewRenderer.java @@ -1,10 +1,14 @@ package net.lax1dude.eaglercraft.v1_8.profile; +import net.lax1dude.eaglercraft.v1_8.opengl.EaglerMeshLoader; +import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU; import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; +import net.minecraft.client.Minecraft; import net.minecraft.client.model.ModelBiped; import net.minecraft.client.model.ModelPlayer; import net.minecraft.client.model.ModelZombie; import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.util.ResourceLocation; /** * Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved. @@ -36,7 +40,11 @@ public class SkinPreviewRenderer { playerModelZombie.isChild = false; } - public static void renderBiped(int x, int y, int mx, int my, SkinModel skinModel) { + public static void renderPreview(int x, int y, int mx, int my, SkinModel skinModel) { + renderPreview(x, y, mx, my, false, skinModel, null, null); + } + + public static void renderPreview(int x, int y, int mx, int my, boolean capeMode, SkinModel skinModel, ResourceLocation skinTexture, ResourceLocation capeTexture) { ModelBiped model; switch(skinModel) { case STEVE: @@ -49,6 +57,17 @@ public class SkinPreviewRenderer { case ZOMBIE: model = playerModelZombie; break; + case LONG_ARMS: + case WEIRD_CLIMBER_DUDE: + case LAXATIVE_DUDE: + case BABY_CHARLES: + case BABY_WINSTON: + if(skinModel.highPoly != null && Minecraft.getMinecraft().gameSettings.enableFNAWSkins) { + renderHighPoly(x, y, mx, my, skinModel.highPoly); + return; + } + model = playerModelSteve; + break; } GlStateManager.enableTexture2D(); @@ -65,12 +84,95 @@ public class SkinPreviewRenderer { RenderHelper.enableGUIStandardItemLighting(); GlStateManager.translate(0.0f, 1.0f, 0.0f); - GlStateManager.rotate(((y - my) * -0.06f), 1.0f, 0.0f, 0.0f); + if(capeMode) { + GlStateManager.rotate(140.0f, 0.0f, 1.0f, 0.0f); + mx = x - (x - mx) - 20; + GlStateManager.rotate(((y - my) * -0.02f), 1.0f, 0.0f, 0.0f); + }else { + GlStateManager.rotate(((y - my) * -0.06f), 1.0f, 0.0f, 0.0f); + } GlStateManager.rotate(((x - mx) * 0.06f), 0.0f, 1.0f, 0.0f); GlStateManager.translate(0.0f, -1.0f, 0.0f); + if(skinTexture != null) { + Minecraft.getMinecraft().getTextureManager().bindTexture(skinTexture); + } + model.render(null, 0.0f, 0.0f, (float)(System.currentTimeMillis() % 2000000) / 50f, ((x - mx) * 0.06f), ((y - my) * -0.1f), 0.0625f); + if(capeTexture != null && model instanceof ModelPlayer) { + Minecraft.getMinecraft().getTextureManager().bindTexture(capeTexture); + GlStateManager.pushMatrix(); + GlStateManager.translate(0.0F, 0.0F, 0.125F); + GlStateManager.rotate(6.0F, 1.0F, 0.0F, 0.0F); + GlStateManager.rotate(180.0F, 0.0F, 1.0F, 0.0F); + ((ModelPlayer)model).renderCape(0.0625f); + GlStateManager.popMatrix(); + } + + GlStateManager.popMatrix(); + GlStateManager.disableLighting(); + } + + private static void renderHighPoly(int x, int y, int mx, int my, HighPolySkin msh) { + GlStateManager.enableTexture2D(); + GlStateManager.disableBlend(); + GlStateManager.disableCull(); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + + GlStateManager.pushMatrix(); + GlStateManager.translate(x, y - 80.0f, 100.0f); + GlStateManager.scale(50.0f, 50.0f, 50.0f); + GlStateManager.rotate(180.0f, 1.0f, 0.0f, 0.0f); + GlStateManager.scale(1.0f, -1.0f, 1.0f); + + RenderHelper.enableGUIStandardItemLighting(); + + GlStateManager.translate(0.0f, 1.0f, 0.0f); + GlStateManager.rotate(((y - my) * -0.06f), 1.0f, 0.0f, 0.0f); + GlStateManager.rotate(((x - mx) * 0.06f), 0.0f, 1.0f, 0.0f); + GlStateManager.rotate(180.0f, 0.0f, 0.0f, 1.0f); + GlStateManager.translate(0.0f, -0.6f, 0.0f); + + GlStateManager.scale(HighPolySkin.highPolyScale, HighPolySkin.highPolyScale, HighPolySkin.highPolyScale); + Minecraft.getMinecraft().getTextureManager().bindTexture(msh.texture); + + if(msh.bodyModel != null) { + EaglercraftGPU.drawHighPoly(EaglerMeshLoader.getEaglerMesh(msh.bodyModel)); + } + + if(msh.headModel != null) { + EaglercraftGPU.drawHighPoly(EaglerMeshLoader.getEaglerMesh(msh.headModel)); + } + + if(msh.limbsModel != null && msh.limbsModel.length > 0) { + for(int i = 0; i < msh.limbsModel.length; ++i) { + float offset = 0.0f; + if(msh.limbsOffset != null) { + if(msh.limbsOffset.length == 1) { + offset = msh.limbsOffset[0]; + }else { + offset = msh.limbsOffset[i]; + } + } + if(offset != 0.0f || msh.limbsInitialRotation != 0.0f) { + GlStateManager.pushMatrix(); + if(offset != 0.0f) { + GlStateManager.translate(0.0f, offset, 0.0f); + } + if(msh.limbsInitialRotation != 0.0f) { + GlStateManager.rotate(msh.limbsInitialRotation, 1.0f, 0.0f, 0.0f); + } + } + + EaglercraftGPU.drawHighPoly(EaglerMeshLoader.getEaglerMesh(msh.limbsModel[i])); + + if(offset != 0.0f || msh.limbsInitialRotation != 0.0f) { + GlStateManager.popMatrix(); + } + } + } + GlStateManager.popMatrix(); GlStateManager.disableLighting(); } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/socket/ConnectionHandshake.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/socket/ConnectionHandshake.java index b51965b..318118d 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/socket/ConnectionHandshake.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/socket/ConnectionHandshake.java @@ -293,6 +293,19 @@ public class ConnectionHandshake { d.write(packetSkin); PlatformNetworking.writePlayPacket(bao.toByteArray()); + bao.reset(); + d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA); + profileDataType = "cape_v1"; + d.writeByte(profileDataType.length()); + d.writeBytes(profileDataType); + byte[] packetCape = EaglerProfile.getCapePacket(); + if(packetCape.length > 0xFFFF) { + throw new IOException("Cape packet is too long: " + packetCape.length); + } + d.writeShort(packetCape.length); + d.write(packetCape); + PlatformNetworking.writePlayPacket(bao.toByteArray()); + byte[] packetSignatureData = UpdateService.getClientSignatureData(); if(packetSignatureData != null) { bao.reset(); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenAddRelay.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenAddRelay.java index 7fa49f4..7032d61 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenAddRelay.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenAddRelay.java @@ -140,4 +140,7 @@ public class GuiScreenAddRelay extends GuiScreen { super.drawScreen(par1, par2, par3); } + public boolean blockPTTKey() { + return this.serverName.isFocused() || this.serverAddress.isFocused(); + } } \ No newline at end of file diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenLANConnecting.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenLANConnecting.java index 38b6ecb..6445e8d 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenLANConnecting.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenLANConnecting.java @@ -117,7 +117,7 @@ public class GuiScreenLANConnecting extends GuiScreen { this.mc.clearTitles(); networkManager.setConnectionState(EnumConnectionState.LOGIN); networkManager.setNetHandler(new NetHandlerSingleplayerLogin(networkManager, mc, parent)); - networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(), EaglerProfile.getSkinPacket())); + networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(), EaglerProfile.getSkinPacket(), EaglerProfile.getCapePacket())); } } } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenSingleplayerConnecting.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenSingleplayerConnecting.java index ff7d193..fb38a61 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenSingleplayerConnecting.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiScreenSingleplayerConnecting.java @@ -88,7 +88,7 @@ public class GuiScreenSingleplayerConnecting extends GuiScreen { this.mc.clearTitles(); this.networkManager.setConnectionState(EnumConnectionState.LOGIN); this.networkManager.setNetHandler(new NetHandlerSingleplayerLogin(this.networkManager, this.mc, this.menu)); - this.networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(), EaglerProfile.getSkinPacket())); + this.networkManager.sendPacket(new C00PacketLoginStart(this.mc.getSession().getProfile(), EaglerProfile.getSkinPacket(), EaglerProfile.getCapePacket())); } try { this.networkManager.processReceivedPackets(); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiShareToLan.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiShareToLan.java index 7ab3dc3..9190a4e 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiShareToLan.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/gui/GuiShareToLan.java @@ -195,4 +195,8 @@ public class GuiShareToLan extends GuiScreen { super.updateScreen(); this.codeTextField.updateCursorCounter(); } + + public boolean blockPTTKey() { + return this.codeTextField.isFocused(); + } } \ No newline at end of file diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerIntegratedServerWorker.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerIntegratedServerWorker.java index f695ebe..c5d87ee 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerIntegratedServerWorker.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerIntegratedServerWorker.java @@ -361,6 +361,9 @@ public class EaglerIntegratedServerWorker { case IPCPacket17ConfigureLAN.ID: { IPCPacket17ConfigureLAN pkt = (IPCPacket17ConfigureLAN)ipc; + if(!pkt.iceServers.isEmpty() && ServerPlatformSingleplayer.getClientConfigAdapter().isAllowVoiceClient()) { + currentProcess.enableVoice(pkt.iceServers.toArray(new String[pkt.iceServers.size()])); + } currentProcess.getConfigurationManager().configureLAN(pkt.gamemode, pkt.cheats); // don't use iceServers break; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerMinecraftServer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerMinecraftServer.java index 85a6969..6e76ca9 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerMinecraftServer.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerMinecraftServer.java @@ -16,7 +16,9 @@ import net.minecraft.world.EnumDifficulty; import net.minecraft.world.WorldServer; import net.minecraft.world.WorldSettings; import net.minecraft.world.WorldSettings.GameType; +import net.lax1dude.eaglercraft.v1_8.sp.server.skins.IntegratedCapeService; import net.lax1dude.eaglercraft.v1_8.sp.server.skins.IntegratedSkinService; +import net.lax1dude.eaglercraft.v1_8.sp.server.voice.IntegratedVoiceService; /** * Copyright (c) 2023-2024 lax1dude, ayunami2000. All Rights Reserved. @@ -45,6 +47,8 @@ public class EaglerMinecraftServer extends MinecraftServer { protected boolean paused; protected EaglerSaveHandler saveHandler; protected IntegratedSkinService skinService; + protected IntegratedCapeService capeService; + protected IntegratedVoiceService voiceService; private long lastTPSUpdate = 0l; @@ -62,6 +66,8 @@ public class EaglerMinecraftServer extends MinecraftServer { Bootstrap.register(); this.saveHandler = new EaglerSaveHandler(savesDir, world); this.skinService = new IntegratedSkinService(new VFile2(saveHandler.getWorldDirectory(), "eagler/skulls")); + this.capeService = new IntegratedCapeService(); + this.voiceService = null; this.setServerOwner(owner); logger.info("server owner: " + owner); this.setDemo(demo); @@ -76,6 +82,27 @@ public class EaglerMinecraftServer extends MinecraftServer { return skinService; } + public IntegratedCapeService getCapeService() { + return capeService; + } + + public IntegratedVoiceService getVoiceService() { + return voiceService; + } + + public void enableVoice(String[] iceServers) { + if(iceServers != null) { + if(voiceService != null) { + voiceService.changeICEServers(iceServers); + }else { + voiceService = new IntegratedVoiceService(iceServers); + for(EntityPlayerMP player : getConfigurationManager().func_181057_v()) { + voiceService.handlePlayerLoggedIn(player); + } + } + } + } + public void setBaseServerProperties(EnumDifficulty difficulty, GameType gamemode) { this.difficulty = difficulty; this.gamemode = gamemode; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerPlayerList.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerPlayerList.java index 32e2b88..525110b 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerPlayerList.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerPlayerList.java @@ -43,6 +43,8 @@ public class EaglerPlayerList extends ServerConfigurationManager { public void playerLoggedOut(EntityPlayerMP playerIn) { super.playerLoggedOut(playerIn); - ((EaglerMinecraftServer)getServerInstance()).skinService.unregisterPlayer(playerIn.getUniqueID()); + EaglerMinecraftServer svr = (EaglerMinecraftServer)getServerInstance(); + svr.skinService.unregisterPlayer(playerIn.getUniqueID()); + svr.capeService.unregisterPlayer(playerIn.getUniqueID()); } } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/skins/IntegratedCapePackets.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/skins/IntegratedCapePackets.java new file mode 100644 index 0000000..af37b1e --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/skins/IntegratedCapePackets.java @@ -0,0 +1,110 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server.skins; + +import java.io.IOException; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.minecraft.entity.player.EntityPlayerMP; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class IntegratedCapePackets { + + public static final int PACKET_MY_CAPE_PRESET = 0x01; + public static final int PACKET_MY_CAPE_CUSTOM = 0x02; + public static final int PACKET_GET_OTHER_CAPE = 0x03; + public static final int PACKET_OTHER_CAPE_PRESET = 0x04; + public static final int PACKET_OTHER_CAPE_CUSTOM = 0x05; + + public static void processPacket(byte[] data, EntityPlayerMP sender, IntegratedCapeService capeService) throws IOException { + if(data.length == 0) { + throw new IOException("Zero-length packet recieved"); + } + int packetId = (int)data[0] & 0xFF; + try { + switch(packetId) { + case PACKET_GET_OTHER_CAPE: + processGetOtherCape(data, sender, capeService); + break; + default: + throw new IOException("Unknown packet type " + packetId); + } + }catch(IOException ex) { + throw ex; + }catch(Throwable t) { + throw new IOException("Unhandled exception handling packet type " + packetId, t); + } + } + + private static void processGetOtherCape(byte[] data, EntityPlayerMP sender, IntegratedCapeService capeService) throws IOException { + if(data.length != 17) { + throw new IOException("Invalid length " + data.length + " for skin request packet"); + } + EaglercraftUUID searchUUID = IntegratedSkinPackets.bytesToUUID(data, 1); + capeService.processGetOtherCape(searchUUID, sender); + } + + public static void registerEaglerPlayer(EaglercraftUUID clientUUID, byte[] bs, IntegratedCapeService capeService) throws IOException { + if(bs.length == 0) { + throw new IOException("Zero-length packet recieved"); + } + byte[] generatedPacket; + int packetType = (int)bs[0] & 0xFF; + switch(packetType) { + case PACKET_MY_CAPE_PRESET: + if(bs.length != 5) { + throw new IOException("Invalid length " + bs.length + " for preset cape packet"); + } + generatedPacket = IntegratedCapePackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF)); + break; + case PACKET_MY_CAPE_CUSTOM: + if(bs.length != 1174) { + throw new IOException("Invalid length " + bs.length + " for custom cape packet"); + } + generatedPacket = IntegratedCapePackets.makeCustomResponse(clientUUID, bs, 1, 1173); + break; + default: + throw new IOException("Unknown skin packet type: " + packetType); + } + capeService.registerEaglercraftPlayer(clientUUID, generatedPacket); + } + + public static void registerEaglerPlayerFallback(EaglercraftUUID clientUUID, IntegratedCapeService capeService) { + capeService.registerEaglercraftPlayer(clientUUID, IntegratedCapePackets.makePresetResponse(clientUUID, 0)); + } + + public static byte[] makePresetResponse(EaglercraftUUID uuid, int presetId) { + byte[] ret = new byte[1 + 16 + 4]; + ret[0] = (byte)PACKET_OTHER_CAPE_PRESET; + IntegratedSkinPackets.UUIDToBytes(uuid, ret, 1); + ret[17] = (byte)(presetId >> 24); + ret[18] = (byte)(presetId >> 16); + ret[19] = (byte)(presetId >> 8); + ret[20] = (byte)(presetId & 0xFF); + return ret; + } + + public static byte[] makeCustomResponse(EaglercraftUUID uuid, byte[] pixels) { + return makeCustomResponse(uuid, pixels, 0, pixels.length); + } + + public static byte[] makeCustomResponse(EaglercraftUUID uuid, byte[] pixels, int offset, int length) { + byte[] ret = new byte[1 + 16 + length]; + ret[0] = (byte)PACKET_OTHER_CAPE_CUSTOM; + IntegratedSkinPackets.UUIDToBytes(uuid, ret, 1); + System.arraycopy(pixels, offset, ret, 17, length); + return ret; + } +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/skins/IntegratedCapeService.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/skins/IntegratedCapeService.java new file mode 100644 index 0000000..b3401a8 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/skins/IntegratedCapeService.java @@ -0,0 +1,77 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server.skins; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.netty.Unpooled; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.S3FPacketCustomPayload; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class IntegratedCapeService { + + public static final Logger logger = LogManager.getLogger("IntegratedCapeService"); + + public static final int masterRateLimitPerPlayer = 250; + + public static final String CHANNEL = "EAG|Capes-1.8"; + + private final Map capesCache = new HashMap(); + + public void processPacket(byte[] packetData, EntityPlayerMP sender) { + try { + IntegratedCapePackets.processPacket(packetData, sender, this); + } catch (IOException e) { + logger.error("Invalid skin request packet recieved from player {}!", sender.getName()); + logger.error(e); + sender.playerNetServerHandler.kickPlayerFromServer("Invalid skin request packet recieved!"); + } + } + + public void processLoginPacket(byte[] packetData, EntityPlayerMP sender) { + try { + IntegratedCapePackets.registerEaglerPlayer(sender.getUniqueID(), packetData, this); + } catch (IOException e) { + logger.error("Invalid skin data packet recieved from player {}!", sender.getName()); + logger.error(e); + sender.playerNetServerHandler.kickPlayerFromServer("Invalid skin data packet recieved!"); + } + } + + public void registerEaglercraftPlayer(EaglercraftUUID playerUUID, byte[] capePacket) { + capesCache.put(playerUUID, capePacket); + } + + public void processGetOtherCape(EaglercraftUUID searchUUID, EntityPlayerMP sender) { + byte[] maybeCape = capesCache.get(searchUUID); + if(maybeCape == null) { + maybeCape = IntegratedCapePackets.makePresetResponse(searchUUID, 0); + } + sender.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, new PacketBuffer(Unpooled.buffer(maybeCape, maybeCape.length).writerIndex(maybeCape.length)))); + } + + public void unregisterPlayer(EaglercraftUUID playerUUID) { + synchronized(capesCache) { + capesCache.remove(playerUUID); + } + } +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceService.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceService.java new file mode 100644 index 0000000..bb20af2 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceService.java @@ -0,0 +1,255 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server.voice; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.netty.Unpooled; +import net.lax1dude.eaglercraft.v1_8.voice.ExpiringSet; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.S3FPacketCustomPayload; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class IntegratedVoiceService { + + public static final Logger logger = LogManager.getLogger("IntegratedVoiceService"); + + public static final String CHANNEL = "EAG|Voice-1.8"; + + private byte[] iceServersPacket; + + private final Map voicePlayers = new HashMap<>(); + private final Map> voiceRequests = new HashMap<>(); + private final Set voicePairs = new HashSet<>(); + + public IntegratedVoiceService(String[] iceServers) { + iceServersPacket = IntegratedVoiceSignalPackets.makeVoiceSignalPacketAllowed(true, iceServers); + } + + public void changeICEServers(String[] iceServers) { + iceServersPacket = IntegratedVoiceSignalPackets.makeVoiceSignalPacketAllowed(true, iceServers); + } + + private static class VoicePair { + + private final EaglercraftUUID uuid1; + private final EaglercraftUUID uuid2; + + @Override + public int hashCode() { + return uuid1.hashCode() ^ uuid2.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + VoicePair other = (VoicePair) obj; + return (uuid1.equals(other.uuid1) && uuid2.equals(other.uuid2)) + || (uuid1.equals(other.uuid2) && uuid2.equals(other.uuid1)); + } + + private VoicePair(EaglercraftUUID uuid1, EaglercraftUUID uuid2) { + this.uuid1 = uuid1; + this.uuid2 = uuid2; + } + + private boolean anyEquals(EaglercraftUUID uuid) { + return uuid1.equals(uuid) || uuid2.equals(uuid); + } + } + + public void handlePlayerLoggedIn(EntityPlayerMP player) { + player.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, new PacketBuffer( + Unpooled.buffer(iceServersPacket, iceServersPacket.length).writerIndex(iceServersPacket.length)))); + } + + public void handlePlayerLoggedOut(EntityPlayerMP player) { + removeUser(player.getUniqueID()); + } + + public void processPacket(PacketBuffer packetData, EntityPlayerMP sender) { + try { + IntegratedVoiceSignalPackets.processPacket(packetData, sender, this); + } catch (IOException e) { + logger.error("Invalid voice signal packet recieved from player {}!", sender.getName()); + logger.error(e); + sender.playerNetServerHandler.kickPlayerFromServer("Invalid voice signal packet recieved!"); + } + } + + void handleVoiceSignalPacketTypeRequest(EaglercraftUUID player, EntityPlayerMP sender) { + EaglercraftUUID senderUUID = sender.getUniqueID(); + if (senderUUID.equals(player)) + return; // prevent duplicates + if (!voicePlayers.containsKey(senderUUID)) + return; + EntityPlayerMP targetPlayerCon = voicePlayers.get(player); + if (targetPlayerCon == null) + return; + VoicePair newPair = new VoicePair(player, senderUUID); + if (voicePairs.contains(newPair)) + return; // already paired + ExpiringSet senderRequestSet = voiceRequests.get(senderUUID); + if (senderRequestSet == null) { + voiceRequests.put(senderUUID, senderRequestSet = new ExpiringSet<>(2000)); + } + if (!senderRequestSet.add(player)) { + return; + } + + // check if other has requested earlier + ExpiringSet theSet; + if ((theSet = voiceRequests.get(player)) != null && theSet.contains(senderUUID)) { + theSet.remove(senderUUID); + if (theSet.isEmpty()) + voiceRequests.remove(player); + senderRequestSet.remove(player); + if (senderRequestSet.isEmpty()) + voiceRequests.remove(senderUUID); + // send each other add data + voicePairs.add(newPair); + targetPlayerCon.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, + IntegratedVoiceSignalPackets.makeVoiceSignalPacketConnect(senderUUID, false))); + sender.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, + IntegratedVoiceSignalPackets.makeVoiceSignalPacketConnect(player, true))); + } + } + + void handleVoiceSignalPacketTypeConnect(EntityPlayerMP sender) { + if (voicePlayers.containsKey(sender.getUniqueID())) { + return; + } + boolean hasNoOtherPlayers = voicePlayers.isEmpty(); + voicePlayers.put(sender.getUniqueID(), sender); + if (hasNoOtherPlayers) { + return; + } + byte[] packetToBroadcast = IntegratedVoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values()); + for (EntityPlayerMP userCon : voicePlayers.values()) { + userCon.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, new PacketBuffer(Unpooled + .buffer(packetToBroadcast, packetToBroadcast.length).writerIndex(packetToBroadcast.length)))); + } + } + + void handleVoiceSignalPacketTypeICE(EaglercraftUUID player, String str, EntityPlayerMP sender) { + VoicePair pair = new VoicePair(player, sender.getUniqueID()); + EntityPlayerMP pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null; + if (pass != null) { + pass.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, + IntegratedVoiceSignalPackets.makeVoiceSignalPacketICE(sender.getUniqueID(), str))); + } + } + + void handleVoiceSignalPacketTypeDesc(EaglercraftUUID player, String str, EntityPlayerMP sender) { + VoicePair pair = new VoicePair(player, sender.getUniqueID()); + EntityPlayerMP pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null; + if (pass != null) { + pass.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, + IntegratedVoiceSignalPackets.makeVoiceSignalPacketDesc(sender.getUniqueID(), str))); + } + } + + void handleVoiceSignalPacketTypeDisconnect(EaglercraftUUID player, EntityPlayerMP sender) { + if (player != null) { + if (!voicePlayers.containsKey(player)) { + return; + } + byte[] userDisconnectPacket = null; + Iterator pairsItr = voicePairs.iterator(); + while (pairsItr.hasNext()) { + VoicePair voicePair = pairsItr.next(); + EaglercraftUUID target = null; + if (voicePair.uuid1.equals(player)) { + target = voicePair.uuid2; + } else if (voicePair.uuid2.equals(player)) { + target = voicePair.uuid1; + } + if (target != null) { + pairsItr.remove(); + EntityPlayerMP conn = voicePlayers.get(target); + if (conn != null) { + if (userDisconnectPacket == null) { + userDisconnectPacket = IntegratedVoiceSignalPackets.makeVoiceSignalPacketDisconnect(player); + } + conn.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, + new PacketBuffer(Unpooled.buffer(userDisconnectPacket, userDisconnectPacket.length) + .writerIndex(userDisconnectPacket.length)))); + } + sender.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, + IntegratedVoiceSignalPackets.makeVoiceSignalPacketDisconnectPB(target))); + } + } + } else { + removeUser(sender.getUniqueID()); + } + } + + public void removeUser(EaglercraftUUID user) { + if (voicePlayers.remove(user) == null) { + return; + } + voiceRequests.remove(user); + if (voicePlayers.size() > 0) { + byte[] voicePlayersPkt = IntegratedVoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values()); + for (EntityPlayerMP userCon : voicePlayers.values()) { + if (!user.equals(userCon.getUniqueID())) { + userCon.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, + new PacketBuffer(Unpooled.buffer(voicePlayersPkt, voicePlayersPkt.length) + .writerIndex(voicePlayersPkt.length)))); + } + } + } + byte[] userDisconnectPacket = null; + Iterator pairsItr = voicePairs.iterator(); + while (pairsItr.hasNext()) { + VoicePair voicePair = pairsItr.next(); + EaglercraftUUID target = null; + if (voicePair.uuid1.equals(user)) { + target = voicePair.uuid2; + } else if (voicePair.uuid2.equals(user)) { + target = voicePair.uuid1; + } + if (target != null) { + pairsItr.remove(); + if (voicePlayers.size() > 0) { + EntityPlayerMP conn = voicePlayers.get(target); + if (conn != null) { + if (userDisconnectPacket == null) { + userDisconnectPacket = IntegratedVoiceSignalPackets.makeVoiceSignalPacketDisconnect(user); + } + conn.playerNetServerHandler.sendPacket(new S3FPacketCustomPayload(CHANNEL, + new PacketBuffer(Unpooled.buffer(userDisconnectPacket, userDisconnectPacket.length) + .writerIndex(userDisconnectPacket.length)))); + } + } + } + } + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceSignalPackets.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceSignalPackets.java new file mode 100644 index 0000000..017916b --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceSignalPackets.java @@ -0,0 +1,198 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server.voice; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collection; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.netty.ByteBuf; +import net.lax1dude.eaglercraft.v1_8.netty.Unpooled; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.PacketBuffer; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class IntegratedVoiceSignalPackets { + + static final int VOICE_SIGNAL_ALLOWED = 0; + static final int VOICE_SIGNAL_REQUEST = 0; + static final int VOICE_SIGNAL_CONNECT = 1; + static final int VOICE_SIGNAL_DISCONNECT = 2; + static final int VOICE_SIGNAL_ICE = 3; + static final int VOICE_SIGNAL_DESC = 4; + static final int VOICE_SIGNAL_GLOBAL = 5; + + public static void processPacket(PacketBuffer buffer, EntityPlayerMP sender, IntegratedVoiceService voiceService) throws IOException { + int packetId = -1; + if(buffer.readableBytes() == 0) { + throw new IOException("Zero-length packet recieved"); + } + try { + packetId = buffer.readUnsignedByte(); + switch(packetId) { + case VOICE_SIGNAL_REQUEST: { + voiceService.handleVoiceSignalPacketTypeRequest(buffer.readUuid(), sender); + break; + } + case VOICE_SIGNAL_CONNECT: { + voiceService.handleVoiceSignalPacketTypeConnect(sender); + break; + } + case VOICE_SIGNAL_ICE: { + voiceService.handleVoiceSignalPacketTypeICE(buffer.readUuid(), buffer.readStringFromBuffer(32767), sender); + break; + } + case VOICE_SIGNAL_DESC: { + voiceService.handleVoiceSignalPacketTypeDesc(buffer.readUuid(), buffer.readStringFromBuffer(32767), sender); + break; + } + case VOICE_SIGNAL_DISCONNECT: { + voiceService.handleVoiceSignalPacketTypeDisconnect(buffer.readableBytes() > 0 ? buffer.readUuid() : null, sender); + break; + } + default: { + throw new IOException("Unknown packet type " + packetId); + } + } + if(buffer.readableBytes() > 0) { + throw new IOException("Voice packet is too long!"); + } + }catch(IOException ex) { + throw ex; + }catch(Throwable t) { + throw new IOException("Unhandled exception handling voice packet type " + packetId, t); + } + } + + static byte[] makeVoiceSignalPacketAllowed(boolean allowed, String[] iceServers) { + if (iceServers == null) { + byte[] ret = new byte[2]; + ByteBuf wrappedBuffer = Unpooled.buffer(ret, ret.length); + wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED); + wrappedBuffer.writeBoolean(allowed); + return ret; + } + byte[][] iceServersBytes = new byte[iceServers.length][]; + int totalLen = 2 + PacketBuffer.getVarIntSize(iceServers.length); + for(int i = 0; i < iceServers.length; ++i) { + byte[] b = iceServersBytes[i] = iceServers[i].getBytes(StandardCharsets.UTF_8); + totalLen += PacketBuffer.getVarIntSize(b.length) + b.length; + } + byte[] ret = new byte[totalLen]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED); + wrappedBuffer.writeBoolean(allowed); + wrappedBuffer.writeVarIntToBuffer(iceServersBytes.length); + for(int i = 0; i < iceServersBytes.length; ++i) { + byte[] b = iceServersBytes[i]; + wrappedBuffer.writeVarIntToBuffer(b.length); + wrappedBuffer.writeBytes(b); + } + return ret; + } + + static byte[] makeVoiceSignalPacketGlobal(Collection users) { + int cnt = users.size(); + byte[][] displayNames = new byte[cnt][]; + int i = 0; + for(EntityPlayerMP user : users) { + String name = user.getName(); + if(name.length() > 16) name = name.substring(0, 16); + displayNames[i++] = name.getBytes(StandardCharsets.UTF_8); + } + int totalLength = 1 + PacketBuffer.getVarIntSize(cnt) + (cnt << 4); + for(i = 0; i < cnt; ++i) { + totalLength += PacketBuffer.getVarIntSize(displayNames[i].length) + displayNames[i].length; + } + byte[] ret = new byte[totalLength]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_GLOBAL); + wrappedBuffer.writeVarIntToBuffer(cnt); + for(EntityPlayerMP user : users) { + wrappedBuffer.writeUuid(user.getUniqueID()); + } + for(i = 0; i < cnt; ++i) { + wrappedBuffer.writeVarIntToBuffer(displayNames[i].length); + wrappedBuffer.writeBytes(displayNames[i]); + } + return ret; + } + + static PacketBuffer makeVoiceSignalPacketConnect(EaglercraftUUID player, boolean offer) { + byte[] ret = new byte[18]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT); + wrappedBuffer.writeUuid(player); + wrappedBuffer.writeBoolean(offer); + return wrappedBuffer; + } + + static byte[] makeVoiceSignalPacketConnectAnnounce(EaglercraftUUID player) { + byte[] ret = new byte[17]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT); + wrappedBuffer.writeUuid(player); + return ret; + } + + static byte[] makeVoiceSignalPacketDisconnect(EaglercraftUUID player) { + if(player == null) { + return new byte[] { (byte)VOICE_SIGNAL_DISCONNECT }; + } + byte[] ret = new byte[17]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT); + wrappedBuffer.writeUuid(player); + return ret; + } + + static PacketBuffer makeVoiceSignalPacketDisconnectPB(EaglercraftUUID player) { + if(player == null) { + byte[] ret = new byte[1]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT); + return wrappedBuffer; + } + byte[] ret = new byte[17]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT); + wrappedBuffer.writeUuid(player); + return wrappedBuffer; + } + + static PacketBuffer makeVoiceSignalPacketICE(EaglercraftUUID player, String str) { + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] ret = new byte[17 + PacketBuffer.getVarIntSize(strBytes.length) + strBytes.length]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_ICE); + wrappedBuffer.writeUuid(player); + wrappedBuffer.writeVarIntToBuffer(strBytes.length); + wrappedBuffer.writeBytes(strBytes); + return wrappedBuffer; + } + + static PacketBuffer makeVoiceSignalPacketDesc(EaglercraftUUID player, String str) { + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] ret = new byte[17 + PacketBuffer.getVarIntSize(strBytes.length) + strBytes.length]; + PacketBuffer wrappedBuffer = new PacketBuffer(Unpooled.buffer(ret, ret.length)); + wrappedBuffer.writeByte(VOICE_SIGNAL_DESC); + wrappedBuffer.writeUuid(player); + wrappedBuffer.writeVarIntToBuffer(strBytes.length); + wrappedBuffer.writeBytes(strBytes); + return wrappedBuffer; + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/update/GuiUpdateCheckerOverlay.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/update/GuiUpdateCheckerOverlay.java index 0e1cc0c..4379bae 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/update/GuiUpdateCheckerOverlay.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/update/GuiUpdateCheckerOverlay.java @@ -41,6 +41,8 @@ public class GuiUpdateCheckerOverlay extends Gui { private int width; private int height; + private int totalHeightOffset = 0; + private boolean isIngame; private GuiScreen backScreen; @@ -61,10 +63,10 @@ public class GuiUpdateCheckerOverlay extends Gui { this.mc = mc; this.width = w; this.height = h; - checkForUpdatesButton = new GuiButton(0, w - 150, 0, 150, 20, I18n.format("update.button") + " " + I18n.format(mc.gameSettings.enableUpdateSvc ? "gui.yes" : "gui.no")); - startDownloadButton = new GuiButton(1, w - 115, 0, 115, 20, I18n.format("update.startDownload")); - viewAllUpdatesButton = new GuiButton(2, w - 115, 0, 115, 20, I18n.format("update.viewAll", 0)); - dismissUpdatesButton = new GuiButton(3, w - 115, 0, 115, 20, I18n.format("update.dismiss")); + checkForUpdatesButton = new GuiButton(0, 0, 0, 150, 20, I18n.format("update.button") + " " + I18n.format(mc.gameSettings.enableUpdateSvc ? "gui.yes" : "gui.no")); + startDownloadButton = new GuiButton(1, 1, 0, 115, 20, I18n.format("update.startDownload")); + viewAllUpdatesButton = new GuiButton(2, 1, 0, 115, 20, I18n.format("update.viewAll", 0)); + dismissUpdatesButton = new GuiButton(3, 1, 0, 115, 20, I18n.format("update.dismiss")); } public void drawScreen(int mx, int my, float partialTicks) { @@ -81,6 +83,7 @@ public class GuiUpdateCheckerOverlay extends Gui { startDownloadButton.visible = false; viewAllUpdatesButton.visible = false; dismissUpdatesButton.visible = false; + totalHeightOffset = 0; int i = UpdateService.getAvailableUpdates().size(); boolean shownSP = i > 0 || !mc.isSingleplayer() || LANServerController.isHostingLAN(); @@ -95,7 +98,7 @@ public class GuiUpdateCheckerOverlay extends Gui { dismissUpdatesButton.visible = true; viewAllUpdatesButton.displayString = I18n.format("update.viewAll", i); str = I18n.format("update.found"); - mc.fontRendererObj.drawStringWithShadow(str, width - mc.fontRendererObj.getStringWidth(str) - 3, 22, 0xFFFFAA); + mc.fontRendererObj.drawStringWithShadow(str, 3, 22, 0xFFFFAA); int embedY = 35; int embedWidth = 115; @@ -109,7 +112,7 @@ public class GuiUpdateCheckerOverlay extends Gui { } GlStateManager.pushMatrix(); - GlStateManager.translate(width - embedWidth - 1, embedY, 0.0f); + GlStateManager.translate(1.0f, embedY, 0.0f); GlStateManager.scale(0.75f, 0.75f, 0.75f); int embedHeight2 = (int)(embedHeight / 0.75f); @@ -143,16 +146,20 @@ public class GuiUpdateCheckerOverlay extends Gui { startDownloadButton.yPosition = embedHeight + embedY + 5; viewAllUpdatesButton.yPosition = startDownloadButton.yPosition + 22; dismissUpdatesButton.yPosition = viewAllUpdatesButton.yPosition + 22; + totalHeightOffset = dismissUpdatesButton.yPosition + 20; GlStateManager.popMatrix(); }else if(isIngame) { if(shownSP) { str = I18n.format("update.noneNew"); - mc.fontRendererObj.drawString(str, width - mc.fontRendererObj.getStringWidth(str) - 3, 22, 0xDDDDDD); + mc.fontRendererObj.drawString(str, 3, 22, 0xDDDDDD); if(i > 0) { viewAllUpdatesButton.yPosition = 40; viewAllUpdatesButton.visible = true; viewAllUpdatesButton.displayString = I18n.format("update.viewAll", i); + totalHeightOffset = 60; + }else { + totalHeightOffset = 32; } } } @@ -173,23 +180,23 @@ public class GuiUpdateCheckerOverlay extends Gui { viewAllUpdatesButton.visible = false; dismissUpdatesButton.visible = false; GlStateManager.pushMatrix(); - GlStateManager.translate(width, isIngame ? 0.0f : 10.0f, 0.0f); + GlStateManager.translate(1.0f, isIngame ? 0.0f : 18.0f, 0.0f); String str = I18n.format("update.downloading"); - mc.fontRendererObj.drawStringWithShadow(str, -mc.fontRendererObj.getStringWidth(str) - 2, 2, 0xFFFFAA); + mc.fontRendererObj.drawStringWithShadow(str, 2, 2, 0xFFFFAA); GlStateManager.translate(0.0f, 14.0f, 0.0f); GlStateManager.scale(0.75f, 0.75f, 0.75f); if(!StringUtils.isAllBlank(progressState.statusString1)) { str = progressState.statusString1; - mc.fontRendererObj.drawStringWithShadow(str, -mc.fontRendererObj.getStringWidth(str) - 3, 0, 0xFFFFFF); + mc.fontRendererObj.drawStringWithShadow(str, 3, 0, 0xFFFFFF); } int cc = isIngame ? 0xBBBBBB : 0xFFFFFF; if(!StringUtils.isAllBlank(progressState.statusString2)) { str = progressState.statusString2; - mc.fontRendererObj.drawStringWithShadow(str, -mc.fontRendererObj.getStringWidth(str) - 3, 11, cc); + mc.fontRendererObj.drawStringWithShadow(str, 3, 11, cc); } - int progX1 = -135; + int progX1 = 3; int progY1 = 22; - int progX2 = -3; + int progX2 = 135; int progY2 = 32; float prog = progressState.progressBar; if(prog >= 0.0f) { @@ -202,6 +209,7 @@ public class GuiUpdateCheckerOverlay extends Gui { drawGradientRect(progX1, progY1 + 1, progX1 + 1, progY2 - 1, 0xFF000000, 0xFF000000); drawGradientRect(progX2 - 1, progY1 + 1, progX2, progY2 - 1, 0xFF000000, 0xFF000000); } + totalHeightOffset = 32; if(!StringUtils.isAllBlank(progressState.statusString3)) { GlStateManager.translate(0.0f, progY2 + 2, 0.0f); GlStateManager.scale(0.66f, 0.66f, 0.66f); @@ -209,10 +217,12 @@ public class GuiUpdateCheckerOverlay extends Gui { List wrappedURL = mc.fontRendererObj.listFormattedStringToWidth(str, (int)((progX2 - progX1) * 1.5f)); for(int i = 0, l = wrappedURL.size(); i < l; ++i) { str = wrappedURL.get(i); - mc.fontRendererObj.drawStringWithShadow(str, -mc.fontRendererObj.getStringWidth(str) - 5, i * 11, cc); + mc.fontRendererObj.drawStringWithShadow(str, 5, i * 11, cc); } + totalHeightOffset += (int)(wrappedURL.size() * 5.5f); } GlStateManager.popMatrix(); + } public void mouseClicked(int mx, int my, int btn) { @@ -244,4 +254,8 @@ public class GuiUpdateCheckerOverlay extends Gui { } } } + + public int getSharedWorldInfoYOffset() { + return totalHeightOffset; + } } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelPeerState.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelPeerState.java new file mode 100644 index 0000000..0b1ff09 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelPeerState.java @@ -0,0 +1,20 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +/** + * Copyright (c) 2024 ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public enum EnumVoiceChannelPeerState { + FAILED, SUCCESS, LOADING; +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelReadyState.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelReadyState.java new file mode 100644 index 0000000..c470876 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelReadyState.java @@ -0,0 +1,20 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +/** + * Copyright (c) 2024 ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public enum EnumVoiceChannelReadyState { + NONE, ABORTED, DEVICE_INITIALIZED; +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelStatus.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelStatus.java new file mode 100644 index 0000000..8a6d737 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelStatus.java @@ -0,0 +1,20 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public enum EnumVoiceChannelStatus { + DISCONNECTED, CONNECTING, CONNECTED, UNAVAILABLE; +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelType.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelType.java new file mode 100644 index 0000000..d035edb --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/EnumVoiceChannelType.java @@ -0,0 +1,20 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public enum EnumVoiceChannelType { + NONE, GLOBAL, PROXIMITY; +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/ExpiringSet.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/ExpiringSet.java new file mode 100644 index 0000000..79af3a2 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/ExpiringSet.java @@ -0,0 +1,84 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +/** + * Copyright (c) 2022 ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class ExpiringSet extends HashSet { + private final long expiration; + private final ExpiringEvent event; + + private final Map timestamps = new HashMap<>(); + + public ExpiringSet(long expiration) { + this.expiration = expiration; + this.event = null; + } + + public ExpiringSet(long expiration, ExpiringEvent event) { + this.expiration = expiration; + this.event = event; + } + + public interface ExpiringEvent { + void onExpiration(T item); + } + + public void checkForExpirations() { + Iterator iterator = this.timestamps.keySet().iterator(); + long now = System.currentTimeMillis(); + while (iterator.hasNext()) { + T element = iterator.next(); + if (super.contains(element)) { + if (this.timestamps.get(element) + this.expiration < now) { + if (this.event != null) this.event.onExpiration(element); + iterator.remove(); + super.remove(element); + } + } else { + iterator.remove(); + super.remove(element); + } + } + } + + public boolean add(T o) { + checkForExpirations(); + boolean success = super.add(o); + if (success) timestamps.put(o, System.currentTimeMillis()); + return success; + } + + public boolean remove(Object o) { + checkForExpirations(); + boolean success = super.remove(o); + if (success) timestamps.remove(o); + return success; + } + + public void clear() { + this.timestamps.clear(); + super.clear(); + } + + public boolean contains(Object o) { + checkForExpirations(); + return super.contains(o); + } +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/GuiVoiceMenu.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/GuiVoiceMenu.java new file mode 100644 index 0000000..42f029a --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/GuiVoiceMenu.java @@ -0,0 +1,772 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; + +import java.util.List; +import java.util.Set; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.Keyboard; +import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; +import net.lax1dude.eaglercraft.v1_8.sp.gui.GuiSlider2; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.MathHelper; +import net.minecraft.util.ResourceLocation; + +/** + * Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class GuiVoiceMenu extends Gui { + + public class AbortedException extends RuntimeException { + } + + private static final ResourceLocation voiceGuiIcons = new ResourceLocation("eagler:gui/eagler_gui.png"); + + protected final GuiScreen parent; + + protected Minecraft mc; + protected FontRenderer fontRendererObj; + protected int width; + protected int height; + + protected int voiceButtonOFFposX; + protected int voiceButtonOFFposY; + protected int voiceButtonOFFposW; + protected int voiceButtonOFFposH; + + protected int voiceButtonRADIUSposX; + protected int voiceButtonRADIUSposY; + protected int voiceButtonRADIUSposW; + protected int voiceButtonRADIUSposH; + + protected int voiceButtonGLOBALposX; + protected int voiceButtonGLOBALposY; + protected int voiceButtonGLOBALposW; + protected int voiceButtonGLOBALposH; + + protected int voiceScreenButtonOFFposX; + protected int voiceScreenButtonOFFposY; + protected int voiceScreenButtonOFFposW; + protected int voiceScreenButtonOFFposH; + + protected int voiceScreenButtonRADIUSposX; + protected int voiceScreenButtonRADIUSposY; + protected int voiceScreenButtonRADIUSposW; + protected int voiceScreenButtonRADIUSposH; + + protected int voiceScreenButtonGLOBALposX; + protected int voiceScreenButtonGLOBALposY; + protected int voiceScreenButtonGLOBALposW; + protected int voiceScreenButtonGLOBALposH; + + protected int voiceScreenButtonChangeRadiusposX; + protected int voiceScreenButtonChangeRadiusposY; + protected int voiceScreenButtonChangeRadiusposW; + protected int voiceScreenButtonChangeRadiusposH; + + protected int voiceScreenVolumeIndicatorX; + protected int voiceScreenVolumeIndicatorY; + protected int voiceScreenVolumeIndicatorW; + protected int voiceScreenVolumeIndicatorH; + + protected boolean showSliderBlocks = false; + protected boolean showSliderVolume = false; + protected boolean showPTTKeyConfig = false; + protected int showNewPTTKey = 0; + protected GuiSlider2 sliderBlocks = null; + protected GuiSlider2 sliderListenVolume = null; + protected GuiSlider2 sliderSpeakVolume = null; + + protected GuiButton applyRadiusButton = null; + protected GuiButton applyVolumeButton = null; + protected GuiButton noticeContinueButton = null; + protected GuiButton noticeCancelButton = null; + + protected static boolean showingCompatWarning = false; + protected static boolean showCompatWarning = true; + + protected static boolean showingTrackingWarning = false; + protected static boolean showTrackingWarning = true; + + protected static EnumVoiceChannelType continueChannel = null; + + public GuiVoiceMenu(GuiScreen parent) { + this.parent = parent; + } + + public void setResolution(Minecraft mc, int w, int h) { + this.mc = mc; + this.fontRendererObj = mc.fontRendererObj; + this.width = w; + this.height = h; + initGui(); + } + + public void initGui() { + this.sliderBlocks = new GuiSlider2(-1, (width - 150) / 2, height / 3 + 20, 150, 20, (VoiceClientController.getVoiceProximity() - 5) / 17.0f, 1.0f) { + public boolean mousePressed(Minecraft par1Minecraft, int par2, int par3) { + if(super.mousePressed(par1Minecraft, par2, par3)) { + this.displayString = "" + (int)((sliderValue * 17.0f) + 5.0f) + " Blocks"; + return true; + }else { + return false; + } + } + public void mouseDragged(Minecraft par1Minecraft, int par2, int par3) { + super.mouseDragged(par1Minecraft, par2, par3); + this.displayString = "" + (int)((sliderValue * 17.0f) + 5.0f) + " Blocks"; + } + }; + sliderBlocks.displayString = "" + VoiceClientController.getVoiceProximity() + " Blocks"; + this.sliderListenVolume = new GuiSlider2(-1, (width - 150) / 2, height / 3 + 10, 150, 20, VoiceClientController.getVoiceListenVolume(), 1.0f); + this.sliderSpeakVolume = new GuiSlider2(-1, (width - 150) / 2, height / 3 + 56, 150, 20, VoiceClientController.getVoiceSpeakVolume(), 1.0f); + + applyRadiusButton = new GuiButton(2, (width - 150) / 2, height / 3 + 49, 150, 20, I18n.format("voice.apply")); + applyVolumeButton = new GuiButton(3, (width - 150) / 2, height / 3 + 90, 150, 20, I18n.format("voice.apply")); + noticeContinueButton = new GuiButton(5, (width - 150) / 2, height / 3 + 60, 150, 20, I18n.format("voice.unsupportedWarning10")); + noticeCancelButton = new GuiButton(6, (width - 150) / 2, height / 3 + 90, 150, 20, I18n.format("voice.unsupportedWarning11")); + applyRadiusButton.visible = applyVolumeButton.visible = noticeContinueButton.visible = noticeCancelButton.visible = false; + } + + private void drawButtons(int mx, int my, float partialTicks) { + applyRadiusButton.drawButton(mc, mx, my); + applyVolumeButton.drawButton(mc, mx, my); + noticeContinueButton.drawButton(mc, mx, my); + noticeCancelButton.drawButton(mc, mx, my); + } + + public void drawScreen(int mx, int my, float partialTicks) { + String txt = I18n.format("voice.title"); + drawString(fontRendererObj, txt, width - 5 - fontRendererObj.getStringWidth(txt), 5, 0xFFCC22); + + applyRadiusButton.visible = showSliderBlocks; + applyVolumeButton.visible = showSliderVolume; + + if(showSliderBlocks || showSliderVolume || showPTTKeyConfig) { + + drawRect(0, 0, this.width, this.height, 0xB0101010); + + if(showSliderBlocks) { + + drawRect(width / 2 - 86, height / 4 - 1, this.width / 2 + 86, height / 3 + 64 + height / 16, 0xFFDDDDDD); + drawRect(width / 2 - 85, height / 4 + 0, this.width / 2 + 85, height / 3 + 63 + height / 16, 0xFF333333); + + drawCenteredString(this.fontRendererObj, I18n.format("voice.radiusTitle"), this.width / 2, height / 4 + 9, 16777215); + drawString(this.fontRendererObj, I18n.format("voice.radiusLabel"), (this.width - 150) / 2 + 3, height / 3 + 6, 0xCCCCCC); + sliderBlocks.drawButton(mc, mx, my); + + }else if(showSliderVolume) { + + drawRect(width / 2 - 86, height / 4 - 11, this.width / 2 + 86, height / 3 + 104 + height / 16, 0xFFDDDDDD); + drawRect(width / 2 - 85, height / 4 - 10, this.width / 2 + 85, height / 3 + 103 + height / 16, 0xFF333333); + + drawCenteredString(this.fontRendererObj, I18n.format("voice.volumeTitle"), this.width / 2, height / 4 - 1, 16777215); + drawString(this.fontRendererObj, I18n.format("voice.volumeListen"), (this.width - 150) / 2 + 3, height / 3 - 4, 0xCCCCCC); + sliderListenVolume.drawButton(mc, mx, my); + + drawString(this.fontRendererObj, I18n.format("voice.volumeSpeak"), (this.width - 150) / 2 + 3, height / 3 + 42, 0xCCCCCC); + sliderSpeakVolume.drawButton(mc, mx, my); + + }else if(showPTTKeyConfig) { + + drawRect(width / 2 - 86, height / 3 - 10, this.width / 2 + 86, height / 3 + 35, 0xFFDDDDDD); + drawRect(width / 2 - 85, height / 3 - 9, this.width / 2 + 85, height / 3 + 34, 0xFF333333); + + if(showNewPTTKey > 0) { + GlStateManager.pushMatrix(); + GlStateManager.translate(this.width / 2, height / 3 + 5, 0.0f); + GlStateManager.scale(2.0f, 2.0f, 2.0f); + drawCenteredString(this.fontRendererObj, Keyboard.getKeyName(mc.gameSettings.voicePTTKey), 0, 0, 0xFFCC11); + GlStateManager.popMatrix(); + }else { + drawCenteredString(this.fontRendererObj, I18n.format("voice.pttChangeDesc"), this.width / 2, height / 3 + 8, 16777215); + } + } + + drawButtons(mx, my, partialTicks); + throw new AbortedException(); + } + + GlStateManager.pushMatrix(); + + GlStateManager.translate(width - 6, 15, 0.0f); + GlStateManager.scale(0.75f, 0.75f, 0.75f); + + if(!VoiceClientController.isClientSupported()) { + txt = I18n.format("voice.titleVoiceUnavailable"); + drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 6, 0xFF7777); + txt = I18n.format("voice.titleVoiceBrowserError"); + drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 19, 0xAA4444); + GlStateManager.popMatrix(); + return; + } + + if(!VoiceClientController.isServerSupported()) { + txt = I18n.format("voice.titleNoVoice"); + drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 5, 0xFF7777); + GlStateManager.popMatrix(); + return; + } + + int xo = 0; + // this feature is optional + //if(VoiceClientController.voiceRelayed()) { + // txt = I18n.format("voice.warning1"); + // drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 8, 0xBB9999); + // txt = I18n.format("voice.warning2"); + // drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 18, 0xBB9999); + // txt = I18n.format("voice.warning3"); + // drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 28, 0xBB9999); + // xo = 43; + // GlStateManager.translate(0.0f, xo, 0.0f); + //} + + EnumVoiceChannelStatus status = VoiceClientController.getVoiceStatus(); + EnumVoiceChannelType channel = VoiceClientController.getVoiceChannel(); + + boolean flag = false; + + if(channel == EnumVoiceChannelType.NONE) { + flag = true; + }else { + if(status == EnumVoiceChannelStatus.CONNECTED) { + + if(channel == EnumVoiceChannelType.PROXIMITY) { + txt = I18n.format("voice.connectedRadius").replace("$radius$", "" + VoiceClientController.getVoiceProximity()).replace("$f$", ""); + int w = fontRendererObj.getStringWidth(txt); + int xx = width - 5 - (w * 3 / 4); + int yy = 15 + (xo * 3 / 4); + voiceScreenButtonChangeRadiusposX = xx; + voiceScreenButtonChangeRadiusposY = yy; + voiceScreenButtonChangeRadiusposW = width - 3 - xx; + voiceScreenButtonChangeRadiusposH = 12; + if(mx >= xx && my >= yy && mx < xx + voiceScreenButtonChangeRadiusposW && my < yy + 12) { + txt = I18n.format("voice.connectedRadius").replace("$radius$", "" + VoiceClientController.getVoiceProximity()) + .replace("$f$", "" + EnumChatFormatting.UNDERLINE) + EnumChatFormatting.RESET; + } + }else { + txt = I18n.format("voice.connectedGlobal"); + } + + voiceScreenVolumeIndicatorX = width - 15 - (104 * 3 / 4); + voiceScreenVolumeIndicatorY = 15 + (xo * 3 / 4) + 30; + voiceScreenVolumeIndicatorW = width - voiceScreenVolumeIndicatorX - 4; + voiceScreenVolumeIndicatorH = 23; + + drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 5, 0x66DD66); + + drawRect(-90, 42, 2, 52, 0xFFAAAAAA); + drawRect(-89, 43, 1, 51, 0xFF222222); + + float vol = VoiceClientController.getVoiceListenVolume(); + drawRect(-89, 43, -89 + (int)(vol * 90), 51, 0xFF993322); + + for(float f = 0.07f; f < vol; f += 0.08f) { + int ww = (int)(f * 90); + drawRect(-89 + ww, 43, -89 + ww + 1, 51, 0xFF999999); + } + + drawRect(-90, 57, 2, 67, 0xFFAAAAAA); + drawRect(-89, 58, 1, 66, 0xFF222222); + + vol = VoiceClientController.getVoiceSpeakVolume(); + drawRect(-89, 58, -89 + (int)(vol * 90), 66, 0xFF993322); + + for(float f = 0.07f; f < vol; f += 0.08f) { + int ww = (int)(f * 90); + drawRect(-89 + ww, 58, -89 + ww + 1, 66, 0xFF999999); + } + + mc.getTextureManager().bindTexture(voiceGuiIcons); + GlStateManager.color(0.7f, 0.7f, 0.7f, 1.0f); + + GlStateManager.pushMatrix(); + GlStateManager.translate(-104.0f, 41.5f, 0.0f); + GlStateManager.scale(0.7f, 0.7f, 0.7f); + drawTexturedModalRect(0, 0, 64, 144, 16, 16); + GlStateManager.popMatrix(); + + GlStateManager.pushMatrix(); + GlStateManager.translate(-104.0f, 56.5f, 0.0f); + GlStateManager.scale(0.7f, 0.7f, 0.7f); + if((mc.currentScreen == null || !mc.currentScreen.blockPTTKey()) && Keyboard.isKeyDown(mc.gameSettings.voicePTTKey)) { + GlStateManager.color(0.9f, 0.4f, 0.4f, 1.0f); + drawTexturedModalRect(0, 0, 64, 64, 16, 16); + }else { + drawTexturedModalRect(0, 0, 64, 32, 16, 16); + } + GlStateManager.popMatrix(); + + txt = I18n.format("voice.ptt", Keyboard.getKeyName(mc.gameSettings.voicePTTKey)); + drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt) - 10, 76, 0x66DD66); + + mc.getTextureManager().bindTexture(voiceGuiIcons); + GlStateManager.color(0.4f, 0.9f, 0.4f, 1.0f); + GlStateManager.pushMatrix(); + GlStateManager.translate(-7.0f, 74.5f, 0.0f); + GlStateManager.scale(0.35f, 0.35f, 0.35f); + drawTexturedModalRect(0, 0, 32, 224, 32, 32); + GlStateManager.popMatrix(); + + txt = I18n.format("voice.playersListening"); + + GlStateManager.pushMatrix(); + GlStateManager.translate(0.0f, 98.0f, 0.0f); + GlStateManager.scale(1.2f, 1.2f, 1.2f); + drawString(fontRendererObj, txt, -fontRendererObj.getStringWidth(txt), 0, 0xFF7777); + GlStateManager.popMatrix(); + + List playersToRender = VoiceClientController.getVoiceRecent(); + + if(playersToRender.size() > 0) { + EaglercraftUUID uuid; + Set playersSpeaking = VoiceClientController.getVoiceSpeaking(); + Set playersMuted = VoiceClientController.getVoiceMuted(); + for(int i = 0, l = playersToRender.size(); i < l; ++i) { + uuid = playersToRender.get(i); + txt = VoiceClientController.getVoiceUsername(uuid); + + boolean muted = playersMuted.contains(uuid); + boolean speaking = !muted && playersSpeaking.contains(uuid); + + int mhy = voiceScreenVolumeIndicatorY + voiceScreenVolumeIndicatorH + 33 + i * 9; + boolean hovered = mx >= voiceScreenVolumeIndicatorX - 3 && my >= mhy && mx < voiceScreenVolumeIndicatorX + voiceScreenVolumeIndicatorW + 2 && my < mhy + 9; + float cm = hovered ? 1.5f : 1.0f; + mc.getTextureManager().bindTexture(voiceGuiIcons); + + GlStateManager.pushMatrix(); + GlStateManager.translate(-100.0f, 115.0f + i * 12.0f, 0.0f); + GlStateManager.scale(0.78f, 0.78f, 0.78f); + + if(muted) { + GlStateManager.color(1.0f * cm, 0.2f * cm, 0.2f * cm, 1.0f); + drawTexturedModalRect(0, 0, 64, 208, 16, 16); + }else if(speaking) { + GlStateManager.color(1.0f * cm, 1.0f * cm, 1.0f * cm, 1.0f); + drawTexturedModalRect(0, 0, 64, 176, 16, 16); + }else { + GlStateManager.color(0.65f * cm, 0.65f * cm, 0.65f * cm, 1.0f); + drawTexturedModalRect(0, 0, 64, 144, 16, 16); + } + + GlStateManager.popMatrix(); + + if(muted) { + drawString(fontRendererObj, txt, -84, 117 + i * 12, attenuate(0xCC4444, cm)); + }else if(speaking) { + drawString(fontRendererObj, txt, -84, 117 + i * 12, attenuate(0xCCCCCC, cm)); + }else { + drawString(fontRendererObj, txt, -84, 117 + i * 12, attenuate(0x999999, cm)); + } + + } + }else { + txt = "(none)"; + drawString(fontRendererObj, txt, -fontRendererObj.getStringWidth(txt), 112, 0xAAAAAA); + } + + }else if(status == EnumVoiceChannelStatus.CONNECTING) { + float fadeTimer = MathHelper.sin((float)((System.currentTimeMillis() % 700l) * 0.0014d) * 3.14159f) * 0.35f + 0.3f; + txt = I18n.format("voice.connecting"); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 5, (0xFFDD77 | ((int)(Math.pow(fadeTimer, 1.0d / 2.2d) * 255.0f) << 24))); + GlStateManager.disableBlend(); + }else if(status == EnumVoiceChannelStatus.UNAVAILABLE) { + txt = I18n.format("voice.unavailable"); + drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 5, 0xFF3333); + }else { + flag = true; + } + } + + if(flag) { + txt = I18n.format("voice.notConnected"); + drawString(fontRendererObj, txt, 1 - fontRendererObj.getStringWidth(txt), 5, 0xBB9999); + } + + String OFFstring = I18n.format("voice.off"); + String RADIUSstring = I18n.format("voice.radius"); + String GLOBALstring = I18n.format("voice.global"); + + int OFFwidth = fontRendererObj.getStringWidth(OFFstring); + int RADIUSwidth = fontRendererObj.getStringWidth(RADIUSstring); + int GLOBALwidth = fontRendererObj.getStringWidth(GLOBALstring); + + voiceButtonOFFposX = 0 - OFFwidth - 8 - RADIUSwidth - 8 - GLOBALwidth; + voiceButtonOFFposY = 20; + voiceButtonOFFposW = OFFwidth + 5; + voiceButtonOFFposH = 15; + + voiceScreenButtonOFFposX = voiceButtonOFFposX * 3 / 4 + width - 6; + voiceScreenButtonOFFposY = 15 + (voiceButtonOFFposY + xo) * 3 / 4; + voiceScreenButtonOFFposW = voiceButtonOFFposW * 3 / 4; + voiceScreenButtonOFFposH = voiceButtonOFFposH * 3 / 4; + + voiceButtonRADIUSposX = 0 - RADIUSwidth - 8 - GLOBALwidth; + voiceButtonRADIUSposY = 20; + voiceButtonRADIUSposW = RADIUSwidth + 5; + voiceButtonRADIUSposH = 15; + + voiceScreenButtonRADIUSposX = voiceButtonRADIUSposX * 3 / 4 + width - 6; + voiceScreenButtonRADIUSposY = 15 + (voiceButtonRADIUSposY + xo) * 3 / 4; + voiceScreenButtonRADIUSposW = voiceButtonRADIUSposW * 3 / 4; + voiceScreenButtonRADIUSposH = voiceButtonRADIUSposH * 3 / 4; + + voiceButtonGLOBALposX = 0 - GLOBALwidth; + voiceButtonGLOBALposY = 20; + voiceButtonGLOBALposW = GLOBALwidth + 5; + voiceButtonGLOBALposH = 15; + + voiceScreenButtonGLOBALposX = voiceButtonGLOBALposX * 3 / 4 + width - 6; + voiceScreenButtonGLOBALposY = 15 + (voiceButtonGLOBALposY + xo) * 3 / 4; + voiceScreenButtonGLOBALposW = voiceButtonGLOBALposW * 3 / 4; + voiceScreenButtonGLOBALposH = voiceButtonGLOBALposH * 3 / 4; + + if(channel == EnumVoiceChannelType.NONE) { + drawOutline(voiceButtonOFFposX, voiceButtonOFFposY, voiceButtonOFFposW, voiceButtonOFFposH, 0xFFCCCCCC); + drawRect(voiceButtonOFFposX + 1, voiceButtonOFFposY + 1, voiceButtonOFFposX + voiceButtonOFFposW - 2, + voiceButtonOFFposY + voiceButtonOFFposH - 1, 0xFF222222); + }else if(mx >= voiceScreenButtonOFFposX && my >= voiceScreenButtonOFFposY && mx < voiceScreenButtonOFFposX + + voiceScreenButtonOFFposW && my < voiceScreenButtonOFFposY + voiceScreenButtonOFFposH) { + drawOutline(voiceButtonOFFposX, voiceButtonOFFposY, voiceButtonOFFposW, voiceButtonOFFposH, 0xFF777777); + } + + if(channel == EnumVoiceChannelType.PROXIMITY) { + drawOutline(voiceButtonRADIUSposX, voiceButtonRADIUSposY, voiceButtonRADIUSposW, voiceButtonRADIUSposH, 0xFFCCCCCC); + drawRect(voiceButtonRADIUSposX + 1, voiceButtonRADIUSposY + 1, voiceButtonRADIUSposX + voiceButtonRADIUSposW - 2, + voiceButtonRADIUSposY + voiceButtonRADIUSposH - 1, 0xFF222222); + }else if(mx >= voiceScreenButtonRADIUSposX && my >= voiceScreenButtonRADIUSposY && mx < voiceScreenButtonRADIUSposX + + voiceScreenButtonRADIUSposW && my < voiceScreenButtonRADIUSposY + voiceScreenButtonRADIUSposH) { + drawOutline(voiceButtonRADIUSposX, voiceButtonRADIUSposY, voiceButtonRADIUSposW, voiceButtonRADIUSposH, 0xFF777777); + } + + if(channel == EnumVoiceChannelType.GLOBAL) { + drawOutline(voiceButtonGLOBALposX, voiceButtonGLOBALposY, voiceButtonGLOBALposW, voiceButtonGLOBALposH, 0xFFCCCCCC); + drawRect(voiceButtonGLOBALposX + 1, voiceButtonGLOBALposY + 1, voiceButtonGLOBALposX + voiceButtonGLOBALposW - 2, + voiceButtonGLOBALposY + voiceButtonGLOBALposH - 1, 0xFF222222); + }else if(mx >= voiceScreenButtonGLOBALposX && my >= voiceScreenButtonGLOBALposY && mx < voiceScreenButtonGLOBALposX + + voiceScreenButtonGLOBALposW && my < voiceScreenButtonGLOBALposY + voiceScreenButtonGLOBALposH) { + drawOutline(voiceButtonGLOBALposX, voiceButtonGLOBALposY, voiceButtonGLOBALposW, voiceButtonGLOBALposH, 0xFF777777); + } + + int enabledColor = (status == EnumVoiceChannelStatus.CONNECTED || channel == EnumVoiceChannelType.NONE) ? 0x66DD66 : 0xDDCC66; + int disabledColor = 0xDD4444; + + if(channel != EnumVoiceChannelType.NONE && status == EnumVoiceChannelStatus.UNAVAILABLE) { + enabledColor = disabledColor; + } + + drawString(fontRendererObj, OFFstring, 3 - OFFwidth - 8 - RADIUSwidth - 8 - GLOBALwidth, 24, channel == EnumVoiceChannelType.NONE ? enabledColor : disabledColor); + drawString(fontRendererObj, RADIUSstring, 3 - RADIUSwidth - 8 - GLOBALwidth, 24, channel == EnumVoiceChannelType.PROXIMITY ? enabledColor : disabledColor); + drawString(fontRendererObj, GLOBALstring, 3 - GLOBALwidth, 24, channel == EnumVoiceChannelType.GLOBAL ? enabledColor : disabledColor); + + GlStateManager.popMatrix(); + + if(showingCompatWarning) { + + drawNotice(I18n.format("voice.unsupportedWarning1"), false, I18n.format("voice.unsupportedWarning2"), I18n.format("voice.unsupportedWarning3"), + "", I18n.format("voice.unsupportedWarning4"), I18n.format("voice.unsupportedWarning5"), I18n.format("voice.unsupportedWarning6"), + I18n.format("voice.unsupportedWarning7"), I18n.format("voice.unsupportedWarning8"), I18n.format("voice.unsupportedWarning9")); + + noticeContinueButton.visible = true; + noticeCancelButton.visible = false; + }else if(showingTrackingWarning) { + + drawNotice(I18n.format("voice.ipGrabWarning1"), true, I18n.format("voice.ipGrabWarning2"), I18n.format("voice.ipGrabWarning3"), + I18n.format("voice.ipGrabWarning4"), "", I18n.format("voice.ipGrabWarning5"), I18n.format("voice.ipGrabWarning6"), + I18n.format("voice.ipGrabWarning7"), I18n.format("voice.ipGrabWarning8"), I18n.format("voice.ipGrabWarning9"), + I18n.format("voice.ipGrabWarning10"), I18n.format("voice.ipGrabWarning11"), I18n.format("voice.ipGrabWarning12")); + + noticeContinueButton.visible = true; + noticeCancelButton.visible = true; + }else { + noticeContinueButton.visible = false; + noticeCancelButton.visible = false; + } + + drawButtons(mx, my, partialTicks); + + if(showingCompatWarning || showingTrackingWarning) { + throw new AbortedException(); + } + } + + private void drawNotice(String title, boolean showCancel, String... lines) { + + int widthAccum = 0; + + for(int i = 0; i < lines.length; ++i) { + int w = fontRendererObj.getStringWidth(lines[i]); + if(widthAccum < w) { + widthAccum = w; + } + } + + int margin = 15; + + int x = (width - widthAccum) / 2; + int y = (height - lines.length * 10 - 60 - margin) / 2; + + drawRect(x - margin - 1, y - margin - 1, x + widthAccum + margin + 1, + y + lines.length * 10 + 49 + margin, 0xFFCCCCCC); + drawRect(x - margin, y - margin, x + widthAccum + margin, + y + lines.length * 10 + 48 + margin, 0xFF111111); + + drawCenteredString(fontRendererObj, EnumChatFormatting.BOLD + title, width / 2, y, 0xFF7766); + + for(int i = 0; i < lines.length; ++i) { + drawString(fontRendererObj, lines[i], x, y + i * 10 + 18, 0xDDAAAA); + } + + if(!showCancel) { + noticeContinueButton.width = 150; + noticeContinueButton.xPosition = (width - 150) / 2; + noticeContinueButton.yPosition = y + lines.length * 10 + 29; + }else { + noticeContinueButton.width = widthAccum / 2 - 10; + noticeContinueButton.xPosition = (width - widthAccum) / 2 + widthAccum / 2 + 3; + noticeContinueButton.yPosition = y + lines.length * 10 + 28; + noticeCancelButton.width = widthAccum / 2 - 10; + noticeCancelButton.xPosition = (width - widthAccum) / 2 + 4; + noticeCancelButton.yPosition = y + lines.length * 10 + 28; + } + + } + + public static int attenuate(int cin, float f) { + return attenuate(cin, f, f, f, 1.0f); + } + + public static int attenuate(int cin, float r, float g, float b, float a) { + float var10 = (float) (cin >> 24 & 255) / 255.0F; + float var6 = (float) (cin >> 16 & 255) / 255.0F; + float var7 = (float) (cin >> 8 & 255) / 255.0F; + float var8 = (float) (cin & 255) / 255.0F; + var10 *= a; + var6 *= r; + var7 *= g; + var8 *= b; + if(var10 > 1.0f) { + var10 = 1.0f; + } + if(var6 > 1.0f) { + var6 = 1.0f; + } + if(var7 > 1.0f) { + var7 = 1.0f; + } + if(var8 > 1.0f) { + var8 = 1.0f; + } + return (((int)(var10 * 255.0f) << 24) | ((int)(var6 * 255.0f) << 16) | ((int)(var7 * 255.0f) << 8) | (int)(var8 * 255.0f)); + } + + private void drawOutline(int x, int y, int w, int h, int color) { + drawRect(x, y, x + w, y + 1, color); + drawRect(x + w - 1, y + 1, x + w, y + h - 1, color); + drawRect(x, y + h - 1, x + w, y + h, color); + drawRect(x, y + 1, x + 1, y + h - 1, color); + } + + public void mouseReleased(int par1, int par2, int par3) { + applyRadiusButton.mouseReleased(par1, par2); + applyVolumeButton.mouseReleased(par1, par2); + noticeContinueButton.mouseReleased(par1, par2); + noticeCancelButton.mouseReleased(par1, par2); + if(showSliderBlocks || showSliderVolume) { + if(showSliderBlocks) { + if(par3 == 0) { + sliderBlocks.mouseReleased(par1, par2); + } + }else if(showSliderVolume) { + if(par3 == 0) { + sliderListenVolume.mouseReleased(par1, par2); + sliderSpeakVolume.mouseReleased(par1, par2); + } + } + throw new AbortedException(); + } + } + + public void keyTyped(char par1, int par2) { + if(showSliderBlocks || showSliderVolume || showPTTKeyConfig) { + if(showPTTKeyConfig) { + if(par2 == 1) { + showPTTKeyConfig = false; + }else { + mc.gameSettings.voicePTTKey = par2; + showNewPTTKey = 10; + } + } + throw new AbortedException(); + } + } + + public void mouseClicked(int mx, int my, int button) { + if(showSliderBlocks || showSliderVolume || showPTTKeyConfig || showingCompatWarning || showingTrackingWarning) { + if(showSliderBlocks) { + sliderBlocks.mousePressed(mc, mx, my); + }else if(showSliderVolume) { + sliderListenVolume.mousePressed(mc, mx, my); + sliderSpeakVolume.mousePressed(mc, mx, my); + } + if(button == 0) { + if(applyRadiusButton.mousePressed(mc, mx, my)) actionPerformed(applyRadiusButton); + if(applyVolumeButton.mousePressed(mc, mx, my)) actionPerformed(applyVolumeButton); + if(noticeContinueButton.mousePressed(mc, mx, my)) actionPerformed(noticeContinueButton); + if(noticeCancelButton.mousePressed(mc, mx, my)) actionPerformed(noticeCancelButton); + } + throw new AbortedException(); + } + + EnumVoiceChannelStatus status = VoiceClientController.getVoiceStatus(); + EnumVoiceChannelType channel = VoiceClientController.getVoiceChannel(); + + if(button == 0) { + if(VoiceClientController.isSupported()) { + if(mx >= voiceScreenButtonOFFposX && my >= voiceScreenButtonOFFposY && mx < voiceScreenButtonOFFposX + + voiceScreenButtonOFFposW && my < voiceScreenButtonOFFposY + voiceScreenButtonOFFposH) { + VoiceClientController.setVoiceChannel(EnumVoiceChannelType.NONE); + this.mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + }else if(mx >= voiceScreenButtonRADIUSposX && my >= voiceScreenButtonRADIUSposY && mx < voiceScreenButtonRADIUSposX + + voiceScreenButtonRADIUSposW && my < voiceScreenButtonRADIUSposY + voiceScreenButtonRADIUSposH) { + + if(showCompatWarning) { + continueChannel = EnumVoiceChannelType.PROXIMITY; + showingCompatWarning = true; + }else if(showTrackingWarning) { + continueChannel = EnumVoiceChannelType.PROXIMITY; + showingTrackingWarning = true; + }else { + VoiceClientController.setVoiceChannel(EnumVoiceChannelType.PROXIMITY); + } + + this.mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + + }else if(mx >= voiceScreenButtonGLOBALposX && my >= voiceScreenButtonGLOBALposY && mx < voiceScreenButtonGLOBALposX + + voiceScreenButtonGLOBALposW && my < voiceScreenButtonGLOBALposY + voiceScreenButtonGLOBALposH) { + + if(showCompatWarning) { + continueChannel = EnumVoiceChannelType.GLOBAL; + showingCompatWarning = true; + }else if(showTrackingWarning) { + continueChannel = EnumVoiceChannelType.GLOBAL; + showingTrackingWarning = true; + }else { + VoiceClientController.setVoiceChannel(EnumVoiceChannelType.GLOBAL); + } + + this.mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + + this.mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + }else if(channel == EnumVoiceChannelType.PROXIMITY && status == EnumVoiceChannelStatus.CONNECTED && mx >= voiceScreenButtonChangeRadiusposX && + my >= voiceScreenButtonChangeRadiusposY && mx < voiceScreenButtonChangeRadiusposX + voiceScreenButtonChangeRadiusposW && + my < voiceScreenButtonChangeRadiusposY + voiceScreenButtonChangeRadiusposH) { + showSliderBlocks = true; + sliderBlocks.sliderValue = (VoiceClientController.getVoiceProximity() - 5) / 17.0f; + this.mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + }else if(status == EnumVoiceChannelStatus.CONNECTED && channel != EnumVoiceChannelType.NONE && mx >= voiceScreenVolumeIndicatorX && + my >= voiceScreenVolumeIndicatorY && mx < voiceScreenVolumeIndicatorX + voiceScreenVolumeIndicatorW && + my < voiceScreenVolumeIndicatorY + voiceScreenVolumeIndicatorH) { + showSliderVolume = true; + sliderListenVolume.sliderValue = VoiceClientController.getVoiceListenVolume(); + sliderSpeakVolume.sliderValue = VoiceClientController.getVoiceSpeakVolume(); + this.mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + }else if(status == EnumVoiceChannelStatus.CONNECTED && channel != EnumVoiceChannelType.NONE && mx >= voiceScreenVolumeIndicatorX - 1 && + my >= voiceScreenVolumeIndicatorY + voiceScreenVolumeIndicatorH + 2 && mx < voiceScreenVolumeIndicatorX + voiceScreenVolumeIndicatorW + 2 && + my < voiceScreenVolumeIndicatorY + voiceScreenVolumeIndicatorH + 12) { + showPTTKeyConfig = true; + this.mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + }else if(status == EnumVoiceChannelStatus.CONNECTED) { + List playersToRender = VoiceClientController.getVoiceRecent(); + if(playersToRender.size() > 0) { + Set playersMuted = VoiceClientController.getVoiceMuted(); + for(int i = 0, l = playersToRender.size(); i < l; ++i) { + EaglercraftUUID uuid = playersToRender.get(i); + String txt = VoiceClientController.getVoiceUsername(uuid); + boolean muted = playersMuted.contains(uuid); + int mhy = voiceScreenVolumeIndicatorY + voiceScreenVolumeIndicatorH + 33 + i * 9; + if(mx >= voiceScreenVolumeIndicatorX - 3 && my >= mhy && mx < voiceScreenVolumeIndicatorX + voiceScreenVolumeIndicatorW + 2 && my < mhy + 9) { + VoiceClientController.setVoiceMuted(uuid, !muted); + this.mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F)); + break; + } + } + } + } + } + } + + } + + private void actionPerformed(GuiButton btn) { + if(btn.id == 2) { + showSliderBlocks = false; + VoiceClientController.setVoiceProximity(mc.gameSettings.voiceListenRadius = (int)((sliderBlocks.sliderValue * 17.0f) + 5.0f)); + mc.gameSettings.saveOptions(); + }else if(btn.id == 3) { + showSliderVolume = false; + VoiceClientController.setVoiceListenVolume(mc.gameSettings.voiceListenVolume = sliderListenVolume.sliderValue); + VoiceClientController.setVoiceSpeakVolume(mc.gameSettings.voiceSpeakVolume = sliderSpeakVolume.sliderValue); + mc.gameSettings.saveOptions(); + }else if(btn.id == 4) { + showPTTKeyConfig = false; + mc.gameSettings.saveOptions(); + }else if(btn.id == 5) { + if(showingCompatWarning) { + showingCompatWarning = false; + showCompatWarning = false; + if(showTrackingWarning) { + showingTrackingWarning = true; + }else { + VoiceClientController.setVoiceChannel(continueChannel); + } + }else if(showingTrackingWarning) { + showingTrackingWarning = false; + showTrackingWarning = false; + VoiceClientController.setVoiceChannel(continueChannel); + } + }else if(btn.id == 6) { + if(showingTrackingWarning) { + showingTrackingWarning = false; + VoiceClientController.setVoiceChannel(EnumVoiceChannelType.NONE); + } + } + } + + public void updateScreen() { + if(showNewPTTKey > 0) { + --showNewPTTKey; + if(showNewPTTKey == 0) { + showPTTKeyConfig = false; + mc.gameSettings.saveOptions(); + } + } + } + + public boolean isBlockingInput() { + return showSliderBlocks || showSliderVolume || showPTTKeyConfig || showingCompatWarning || showingTrackingWarning; + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/GuiVoiceOverlay.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/GuiVoiceOverlay.java new file mode 100644 index 0000000..2621c60 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/GuiVoiceOverlay.java @@ -0,0 +1,258 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.GL_GREATER; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.Keyboard; +import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiChat; +import net.minecraft.client.gui.GuiIngameMenu; +import net.minecraft.util.ResourceLocation; + +/** + * Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class GuiVoiceOverlay extends Gui { + + public final Minecraft mc; + public int width; + public int height; + + private long pttTimer = 0l; + + public GuiVoiceOverlay(Minecraft mc) { + this.mc = mc; + } + + public void setResolution(int w, int h) { + this.width = w; + this.height = h; + } + + private static final ResourceLocation voiceGuiIcons = new ResourceLocation("eagler:gui/eagler_gui.png"); + + public void drawOverlay() { + if(mc.theWorld != null && VoiceClientController.getVoiceStatus() == EnumVoiceChannelStatus.CONNECTED && VoiceClientController.getVoiceChannel() != EnumVoiceChannelType.NONE && + !(mc.currentScreen != null && (mc.currentScreen instanceof GuiIngameMenu))) { + + if(mc.currentScreen != null && mc.currentScreen.doesGuiPauseGame()) { + return; + } + + GlStateManager.disableLighting(); + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.alphaFunc(GL_GREATER, 0.1F); + GlStateManager.pushMatrix(); + + if(mc.currentScreen == null || (mc.currentScreen instanceof GuiChat)) { + GlStateManager.translate(width / 2 + 77, height - 56, 0.0f); + if(mc.thePlayer == null || mc.thePlayer.capabilities.isCreativeMode) { + GlStateManager.translate(0.0f, 16.0f, 0.0f); + } + }else { + GlStateManager.translate(width / 2 + 10, 4, 0.0f); + } + + GlStateManager.scale(0.75f, 0.75f, 0.75f); + + String txxt = "press '" + Keyboard.getKeyName(mc.gameSettings.voicePTTKey) + "'"; + drawString(mc.fontRendererObj, txxt, -3 - mc.fontRendererObj.getStringWidth(txxt), 9, 0xDDDDDD); + + GlStateManager.scale(0.66f, 0.66f, 0.66f); + + mc.getTextureManager().bindTexture(voiceGuiIcons); + + if((mc.currentScreen == null || !mc.currentScreen.blockPTTKey()) && Keyboard.isKeyDown(mc.gameSettings.voicePTTKey)) { + long millis = System.currentTimeMillis(); + if(pttTimer == 0l) { + pttTimer = millis; + } + GlStateManager.color(0.2f, 0.2f, 0.2f, 1.0f); + drawTexturedModalRect(0, 0, 0, 64, 32, 32); + GlStateManager.translate(-1.5f, -1.5f, 0.0f); + if(millis - pttTimer < 1050l) { + if((millis - pttTimer) % 300l < 150l) { + GlStateManager.color(0.9f, 0.2f, 0.2f, 1.0f); + }else { + GlStateManager.color(0.9f, 0.7f, 0.7f, 1.0f); + } + }else { + GlStateManager.color(0.9f, 0.3f, 0.3f, 1.0f); + } + drawTexturedModalRect(0, 0, 0, 64, 32, 32); + }else { + pttTimer = 0l; + GlStateManager.color(0.2f, 0.2f, 0.2f, 1.0f); + drawTexturedModalRect(0, 0, 0, 32, 32, 32); + GlStateManager.translate(-1.5f, -1.5f, 0.0f); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + drawTexturedModalRect(0, 0, 0, 32, 32, 32); + GlStateManager.translate(-0.5f, -0.5f, 0.0f); + drawTexturedModalRect(0, 0, 0, 32, 32, 32); + } + + GlStateManager.popMatrix(); + + if(VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.PROXIMITY) { + Set listeners = VoiceClientController.getVoiceListening(); + if(listeners.size() > 0) { + Set speakers = VoiceClientController.getVoiceSpeaking(); + Set muted = VoiceClientController.getVoiceMuted(); + + List listenerList = new ArrayList(); + listenerList.addAll(listeners); + listenerList.removeAll(muted); + + while(listenerList.size() > 5) { + boolean flag = false; + for(int i = 0, l = listenerList.size(); i < l; ++i) { + if(!speakers.contains(listenerList.get(i))) { + listenerList.remove(i); + flag = true; + break; + } + } + if(!flag) { + break; + } + } + + int more = listenerList.size() - 5; + + int ww = width; + int hh = height; + + if(mc.currentScreen != null && (mc.currentScreen instanceof GuiChat)) { + hh -= 15; + } + + List listenerListStr = new ArrayList(Math.min(5, listenerList.size())); + + int left = 50; + for(int i = 0, l = listenerList.size(); i < l && i < 5; ++i) { + String txt = VoiceClientController.getVoiceUsername(listenerList.get(i)); + listenerListStr.add(txt); + int j = mc.fontRendererObj.getStringWidth(txt) + 4; + if(j > left) { + left = j; + } + } + + if(more > 0) { + GlStateManager.pushMatrix(); + GlStateManager.translate(ww - left + 3, hh - 10, left); + GlStateManager.scale(0.75f, 0.75f, 0.75f); + drawString(mc.fontRendererObj, "(" + more + " more)", 0, 0, 0xBBBBBB); + GlStateManager.popMatrix(); + hh -= 9; + } + + for(int i = 0, l = listenerList.size(); i < l && i < 5; ++i) { + boolean speaking = speakers.contains(listenerList.get(i)); + float speakf = speaking ? 1.0f : 0.75f; + + drawString(mc.fontRendererObj, listenerListStr.get(i), ww - left, hh - 13 - i * 11, speaking ? 0xEEEEEE : 0xBBBBBB); + + mc.getTextureManager().bindTexture(voiceGuiIcons); + + GlStateManager.pushMatrix(); + GlStateManager.translate(ww - left - 14, hh - 14 - i * 11, 0.0f); + + GlStateManager.scale(0.75f, 0.75f, 0.75f); + GlStateManager.color(speakf * 0.2f, speakf * 0.2f, speakf * 0.2f, 1.0f); + drawTexturedModalRect(0, 0, 64, speaking ? 176 : 208, 16, 16); + GlStateManager.translate(0.25f, 0.25f, 0.0f); + drawTexturedModalRect(0, 0, 64, speaking ? 176 : 208, 16, 16); + + GlStateManager.translate(-1.25f, -1.25f, 0.0f); + GlStateManager.color(speakf, speakf, speakf, 1.0f); + drawTexturedModalRect(0, 0, 64, speaking ? 176 : 208, 16, 16); + + GlStateManager.popMatrix(); + + } + + } + }else if(VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.GLOBAL) { + Set speakers = VoiceClientController.getVoiceSpeaking(); + Set muted = VoiceClientController.getVoiceMuted(); + + List listenerList = new ArrayList(); + listenerList.addAll(speakers); + listenerList.removeAll(muted); + + int more = listenerList.size() - 5; + + int ww = width; + int hh = height; + + if(mc.currentScreen != null && (mc.currentScreen instanceof GuiChat)) { + hh -= 15; + } + + List listenerListStr = new ArrayList(Math.min(5, listenerList.size())); + + int left = 50; + for(int i = 0, l = listenerList.size(); i < l && i < 5; ++i) { + String txt = VoiceClientController.getVoiceUsername(listenerList.get(i)); + listenerListStr.add(txt); + int j = mc.fontRendererObj.getStringWidth(txt) + 4; + if(j > left) { + left = j; + } + } + + if(more > 0) { + GlStateManager.pushMatrix(); + GlStateManager.translate(ww - left + 3, hh - 10, left); + GlStateManager.scale(0.75f, 0.75f, 0.75f); + drawString(mc.fontRendererObj, "(" + more + " more)", 0, 0, 0xBBBBBB); + GlStateManager.popMatrix(); + hh -= 9; + } + + for(int i = 0, l = listenerList.size(); i < l && i < 5; ++i) { + drawString(mc.fontRendererObj, listenerListStr.get(i), ww - left, hh - 13 - i * 11, 0xEEEEEE); + + mc.getTextureManager().bindTexture(voiceGuiIcons); + + GlStateManager.pushMatrix(); + GlStateManager.translate(ww - left - 14, hh - 14 - i * 11, 0.0f); + + GlStateManager.scale(0.75f, 0.75f, 0.75f); + GlStateManager.color(0.2f, 0.2f, 0.2f, 1.0f); + drawTexturedModalRect(0, 0, 64, 176, 16, 16); + GlStateManager.translate(0.25f, 0.25f, 0.0f); + drawTexturedModalRect(0, 0, 64, 176, 16, 16); + + GlStateManager.translate(-1.25f, -1.25f, 0.0f); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + drawTexturedModalRect(0, 0, 64, 176, 16, 16); + + GlStateManager.popMatrix(); + + } + } + } + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceClientController.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceClientController.java new file mode 100644 index 0000000..5e3dc80 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceClientController.java @@ -0,0 +1,367 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import net.lax1dude.eaglercraft.v1_8.EagRuntime; +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.Keyboard; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformVoiceClient; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.network.PacketBuffer; + +/** + * Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class VoiceClientController { + + public static final String SIGNAL_CHANNEL = "EAG|Voice-1.8"; + + static final Logger logger = LogManager.getLogger("VoiceClientController"); + + private static boolean clientSupport = false; + private static boolean serverSupport = false; + private static Consumer packetSendCallback = null; + private static EnumVoiceChannelType voiceChannel = EnumVoiceChannelType.NONE; + private static final HashSet nearbyPlayers = new HashSet<>(); + private static final ExpiringSet recentlyNearbyPlayers = new ExpiringSet<>(5000, uuid -> { + if (!nearbyPlayers.contains(uuid)) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + }); + private static final Map uuidToNameLookup = new HashMap<>(256); + + public static boolean isSupported() { + return isClientSupported() && isServerSupported(); + } + + private static boolean checked = false; + + public static boolean isClientSupported() { + if (!checked) { + checked = true; + clientSupport = EagRuntime.getConfiguration().isAllowVoiceClient() && PlatformVoiceClient.isSupported(); + } + return clientSupport; + } + + public static boolean isServerSupported() { + return serverSupport; + } + + public static void initializeVoiceClient(Consumer signalSendCallbackIn) { + packetSendCallback = signalSendCallbackIn; + uuidToNameLookup.clear(); + if (getVoiceChannel() != EnumVoiceChannelType.NONE) sendInitialVoice(); + } + + public static void handleVoiceSignalPacket(PacketBuffer packetData) { + VoiceSignalPackets.handleVoiceSignal(packetData); + } + + static void handleVoiceSignalPacketTypeGlobal(EaglercraftUUID[] voicePlayers, String[] voiceNames) { + uuidToNameLookup.clear(); + for (int i = 0; i < voicePlayers.length; i++) { + if(voiceNames != null) { + uuidToNameLookup.put(voicePlayers[i], voiceNames[i]); + } + sendPacketRequestIfNeeded(voicePlayers[i]); + } + } + + public static void handleServerDisconnect() { + if(!isClientSupported()) return; + serverSupport = false; + uuidToNameLookup.clear(); + for (EaglercraftUUID uuid : nearbyPlayers) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + for (EaglercraftUUID uuid : recentlyNearbyPlayers) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + nearbyPlayers.clear(); + recentlyNearbyPlayers.clear(); + Set antiConcurrentModificationUUIDs = new HashSet<>(listeningSet); + for (EaglercraftUUID uuid : antiConcurrentModificationUUIDs) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + activateVoice(false); + } + + static void handleVoiceSignalPacketTypeAllowed(boolean voiceAvailableStat, String[] servs) { + serverSupport = voiceAvailableStat; + PlatformVoiceClient.setICEServers(servs); + if(isSupported()) { + EnumVoiceChannelType ch = getVoiceChannel(); + setVoiceChannel(EnumVoiceChannelType.NONE); + setVoiceChannel(ch); + } + } + + static void handleVoiceSignalPacketTypeConnect(EaglercraftUUID user, boolean offer) { + PlatformVoiceClient.signalConnect(user, offer); + } + + static void handleVoiceSignalPacketTypeConnectAnnounce(EaglercraftUUID user) { + sendPacketRequest(user); + } + + static void handleVoiceSignalPacketTypeDisconnect(EaglercraftUUID user) { + PlatformVoiceClient.signalDisconnect(user, true); + } + + static void handleVoiceSignalPacketTypeICECandidate(EaglercraftUUID user, String ice) { + PlatformVoiceClient.signalICECandidate(user, ice); + } + + static void handleVoiceSignalPacketTypeDescription(EaglercraftUUID user, String desc) { + PlatformVoiceClient.signalDescription(user, desc); + } + + public static void tickVoiceClient(Minecraft mc) { + if(!isClientSupported()) return; + recentlyNearbyPlayers.checkForExpirations(); + speakingSet.clear(); + PlatformVoiceClient.tickVoiceClient(); + + if (getVoiceChannel() != EnumVoiceChannelType.NONE && (getVoiceStatus() == EnumVoiceChannelStatus.CONNECTING || getVoiceStatus() == EnumVoiceChannelStatus.CONNECTED)) { + activateVoice((mc.currentScreen == null || !mc.currentScreen.blockPTTKey()) && Keyboard.isKeyDown(mc.gameSettings.voicePTTKey)); + + if (mc.theWorld != null && mc.thePlayer != null) { + HashSet seenPlayers = new HashSet<>(); + for (EntityPlayer player : mc.theWorld.playerEntities) { + if (player == mc.thePlayer) continue; + if (getVoiceChannel() == EnumVoiceChannelType.PROXIMITY) updateVoicePosition(player.getUniqueID(), player.posX, player.posY + player.getEyeHeight(), player.posZ); + int prox = 22; + // cube + if (Math.abs(mc.thePlayer.posX - player.posX) <= prox && Math.abs(mc.thePlayer.posY - player.posY) <= prox && Math.abs(mc.thePlayer.posZ - player.posZ) <= prox) { + if (!uuidToNameLookup.containsKey(player.getUniqueID())) { + uuidToNameLookup.put(player.getUniqueID(), player.getName()); + } + if (addNearbyPlayer(player.getUniqueID())) { + seenPlayers.add(player.getUniqueID()); + } + } + } + cleanupNearbyPlayers(seenPlayers); + } + } + } + + public static final boolean addNearbyPlayer(EaglercraftUUID uuid) { + recentlyNearbyPlayers.remove(uuid); + if (nearbyPlayers.add(uuid)) { + sendPacketRequestIfNeeded(uuid); + return true; + } + return false; + } + + public static final void removeNearbyPlayer(EaglercraftUUID uuid) { + if (nearbyPlayers.remove(uuid)) { + if (getVoiceStatus() == EnumVoiceChannelStatus.DISCONNECTED || getVoiceStatus() == EnumVoiceChannelStatus.UNAVAILABLE) return; + if (voiceChannel == EnumVoiceChannelType.PROXIMITY) recentlyNearbyPlayers.add(uuid); + } + } + + public static final void cleanupNearbyPlayers(HashSet existingPlayers) { + nearbyPlayers.stream().filter(ud -> !existingPlayers.contains(ud)).collect(Collectors.toSet()).forEach(VoiceClientController::removeNearbyPlayer); + } + + public static final void updateVoicePosition(EaglercraftUUID uuid, double x, double y, double z) { + PlatformVoiceClient.updateVoicePosition(uuid, x, y, z); + } + + public static void setVoiceChannel(EnumVoiceChannelType channel) { + if (voiceChannel == channel) return; + if (channel != EnumVoiceChannelType.NONE) PlatformVoiceClient.initializeDevices(); + PlatformVoiceClient.resetPeerStates(); + if (channel == EnumVoiceChannelType.NONE) { + for (EaglercraftUUID uuid : nearbyPlayers) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + for (EaglercraftUUID uuid : recentlyNearbyPlayers) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + nearbyPlayers.clear(); + recentlyNearbyPlayers.clear(); + Set antiConcurrentModificationUUIDs = new HashSet<>(listeningSet); + for (EaglercraftUUID uuid : antiConcurrentModificationUUIDs) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + sendPacketDisconnect(null); + activateVoice(false); + } else if (voiceChannel == EnumVoiceChannelType.PROXIMITY) { + for (EaglercraftUUID uuid : nearbyPlayers) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + for (EaglercraftUUID uuid : recentlyNearbyPlayers) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + nearbyPlayers.clear(); + recentlyNearbyPlayers.clear(); + sendPacketDisconnect(null); + } else if(voiceChannel == EnumVoiceChannelType.GLOBAL) { + Set antiConcurrentModificationUUIDs = new HashSet<>(listeningSet); + antiConcurrentModificationUUIDs.removeAll(nearbyPlayers); + antiConcurrentModificationUUIDs.removeAll(recentlyNearbyPlayers); + for (EaglercraftUUID uuid : antiConcurrentModificationUUIDs) { + PlatformVoiceClient.signalDisconnect(uuid, false); + } + sendPacketDisconnect(null); + } + voiceChannel = channel; + if (channel != EnumVoiceChannelType.NONE) { + sendInitialVoice(); + } + } + + public static void sendInitialVoice() { + sendPacketConnect(); + for (EaglercraftUUID uuid : nearbyPlayers) { + sendPacketRequest(uuid); + } + } + + public static EnumVoiceChannelType getVoiceChannel() { + return voiceChannel; + } + + private static boolean voicePeerErrored() { + return PlatformVoiceClient.getPeerState() == EnumVoiceChannelPeerState.FAILED || PlatformVoiceClient.getPeerStateConnect() == EnumVoiceChannelPeerState.FAILED || PlatformVoiceClient.getPeerStateInitial() == EnumVoiceChannelPeerState.FAILED || PlatformVoiceClient.getPeerStateDesc() == EnumVoiceChannelPeerState.FAILED || PlatformVoiceClient.getPeerStateIce() == EnumVoiceChannelPeerState.FAILED; + } + public static EnumVoiceChannelStatus getVoiceStatus() { + return (!isClientSupported() || !isServerSupported()) ? EnumVoiceChannelStatus.UNAVAILABLE : + (PlatformVoiceClient.getReadyState() != EnumVoiceChannelReadyState.DEVICE_INITIALIZED ? + EnumVoiceChannelStatus.CONNECTING : (voicePeerErrored() ? EnumVoiceChannelStatus.UNAVAILABLE : EnumVoiceChannelStatus.CONNECTED)); + } + + private static boolean talkStatus = false; + + public static void activateVoice(boolean talk) { + if (talkStatus != talk) { + PlatformVoiceClient.activateVoice(talk); + talkStatus = talk; + } + } + + private static int proximity = 16; + + public static void setVoiceProximity(int prox) { + PlatformVoiceClient.setVoiceProximity(prox); + proximity = prox; + } + + public static int getVoiceProximity() { + return proximity; + } + + private static float volumeListen = 0.5f; + + public static void setVoiceListenVolume(float f) { + PlatformVoiceClient.setVoiceListenVolume(f); + volumeListen = f; + } + + public static float getVoiceListenVolume() { + return volumeListen; + } + + private static float volumeSpeak = 0.5f; + + public static void setVoiceSpeakVolume(float f) { + if (volumeSpeak != f) { + PlatformVoiceClient.setMicVolume(f); + } + volumeSpeak = f; + } + + public static float getVoiceSpeakVolume() { + return volumeSpeak; + } + + private static final Set listeningSet = new HashSet<>(); + private static final Set speakingSet = new HashSet<>(); + private static final Set mutedSet = new HashSet<>(); + + public static Set getVoiceListening() { + return listeningSet; + } + + public static Set getVoiceSpeaking() { + return speakingSet; + } + + public static void setVoiceMuted(EaglercraftUUID uuid, boolean mute) { + PlatformVoiceClient.mutePeer(uuid, mute); + if (mute) { + mutedSet.add(uuid); + } else { + mutedSet.remove(uuid); + } + } + + public static Set getVoiceMuted() { + return mutedSet; + } + + public static List getVoiceRecent() { + return new ArrayList<>(listeningSet); + } + + public static String getVoiceUsername(EaglercraftUUID uuid) { + if(uuid == null) { + return "null"; + } + String ret = uuidToNameLookup.get(uuid); + return ret == null ? uuid.toString() : ret; + } + + public static void sendPacketICE(EaglercraftUUID peerId, String candidate) { + packetSendCallback.accept(VoiceSignalPackets.makeVoiceSignalPacketICE(peerId, candidate)); + } + + public static void sendPacketDesc(EaglercraftUUID peerId, String desc) { + packetSendCallback.accept(VoiceSignalPackets.makeVoiceSignalPacketDesc(peerId, desc)); + } + + public static void sendPacketDisconnect(EaglercraftUUID peerId) { + packetSendCallback.accept(VoiceSignalPackets.makeVoiceSignalPacketDisconnect(peerId)); + } + + public static void sendPacketConnect() { + packetSendCallback.accept(VoiceSignalPackets.makeVoiceSignalPacketConnect()); + } + + public static void sendPacketRequest(EaglercraftUUID peerId) { + packetSendCallback.accept(VoiceSignalPackets.makeVoiceSignalPacketRequest(peerId)); + } + + private static void sendPacketRequestIfNeeded(EaglercraftUUID uuid) { + if (getVoiceStatus() == EnumVoiceChannelStatus.DISCONNECTED || getVoiceStatus() == EnumVoiceChannelStatus.UNAVAILABLE) return; + if(uuid.equals(EaglerProfile.getPlayerUUID())) return; + if (!getVoiceListening().contains(uuid)) sendPacketRequest(uuid); + } +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceSignalPackets.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceSignalPackets.java new file mode 100644 index 0000000..07643a8 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceSignalPackets.java @@ -0,0 +1,142 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +import java.nio.charset.StandardCharsets; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.netty.Unpooled; +import net.minecraft.network.PacketBuffer; + +/** + * Copyright (c) 2024 lax1dude, ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class VoiceSignalPackets { + + static final int VOICE_SIGNAL_ALLOWED = 0; + static final int VOICE_SIGNAL_REQUEST = 0; + static final int VOICE_SIGNAL_CONNECT = 1; + static final int VOICE_SIGNAL_DISCONNECT = 2; + static final int VOICE_SIGNAL_ICE = 3; + static final int VOICE_SIGNAL_DESC = 4; + static final int VOICE_SIGNAL_GLOBAL = 5; + + static void handleVoiceSignal(PacketBuffer streamIn) { + try { + int sig = streamIn.readUnsignedByte(); + switch(sig) { + case VOICE_SIGNAL_ALLOWED: { + boolean voiceAvailableStat = streamIn.readUnsignedByte() == 1; + String[] servs = null; + if(voiceAvailableStat) { + servs = new String[streamIn.readVarIntFromBuffer()]; + for(int i = 0; i < servs.length; i++) { + servs[i] = streamIn.readStringFromBuffer(1024); + } + } + VoiceClientController.handleVoiceSignalPacketTypeAllowed(voiceAvailableStat, servs); + break; + } + case VOICE_SIGNAL_GLOBAL: { + if (VoiceClientController.getVoiceChannel() != EnumVoiceChannelType.GLOBAL) return; + EaglercraftUUID[] voiceIds = new EaglercraftUUID[streamIn.readVarIntFromBuffer()]; + for(int i = 0; i < voiceIds.length; i++) { + voiceIds[i] = streamIn.readUuid(); + } + String[] voiceNames = null; + if (streamIn.isReadable()) { + voiceNames = new String[voiceIds.length]; + for(int i = 0; i < voiceNames.length; i++) { + voiceNames[i] = streamIn.readStringFromBuffer(16); + } + } + VoiceClientController.handleVoiceSignalPacketTypeGlobal(voiceIds, voiceNames); + break; + } + case VOICE_SIGNAL_CONNECT: { + EaglercraftUUID uuid = streamIn.readUuid(); + if (streamIn.isReadable()) { + VoiceClientController.handleVoiceSignalPacketTypeConnect(uuid, streamIn.readBoolean()); + } else if (VoiceClientController.getVoiceChannel() != EnumVoiceChannelType.PROXIMITY || VoiceClientController.getVoiceListening().contains(uuid)) { + VoiceClientController.handleVoiceSignalPacketTypeConnectAnnounce(uuid); + } + break; + } + case VOICE_SIGNAL_DISCONNECT: { + VoiceClientController.handleVoiceSignalPacketTypeDisconnect(streamIn.readableBytes() > 0 ? streamIn.readUuid() : null); + break; + } + case VOICE_SIGNAL_ICE: { + VoiceClientController.handleVoiceSignalPacketTypeICECandidate(streamIn.readUuid(), streamIn.readStringFromBuffer(32767)); + break; + } + case VOICE_SIGNAL_DESC: { + VoiceClientController.handleVoiceSignalPacketTypeDescription(streamIn.readUuid(), streamIn.readStringFromBuffer(32767)); + break; + } + default: { + VoiceClientController.logger.error("Unknown voice signal packet '{}'!", sig); + break; + } + } + }catch(Throwable ex) { + VoiceClientController.logger.error("Failed to handle signal packet!"); + VoiceClientController.logger.error(ex); + } + } + + static PacketBuffer makeVoiceSignalPacketRequest(EaglercraftUUID user) { + PacketBuffer ret = new PacketBuffer(Unpooled.buffer(17, 17)); + ret.writeByte(VOICE_SIGNAL_REQUEST); + ret.writeUuid(user); + return ret; + } + + static PacketBuffer makeVoiceSignalPacketICE(EaglercraftUUID user, String icePacket) { + byte[] str = icePacket.getBytes(StandardCharsets.UTF_8); + int estLen = 17 + PacketBuffer.getVarIntSize(str.length) + str.length; + PacketBuffer ret = new PacketBuffer(Unpooled.buffer(estLen, estLen)); + ret.writeByte(VOICE_SIGNAL_ICE); + ret.writeUuid(user); + ret.writeByteArray(str); + return ret; + } + + static PacketBuffer makeVoiceSignalPacketDesc(EaglercraftUUID user, String descPacket) { + byte[] str = descPacket.getBytes(StandardCharsets.UTF_8); + int estLen = 17 + PacketBuffer.getVarIntSize(str.length) + str.length; + PacketBuffer ret = new PacketBuffer(Unpooled.buffer(estLen, estLen)); + ret.writeByte(VOICE_SIGNAL_DESC); + ret.writeUuid(user); + ret.writeByteArray(str); + return ret; + } + + static PacketBuffer makeVoiceSignalPacketDisconnect(EaglercraftUUID user) { + if (user == null) { + PacketBuffer ret = new PacketBuffer(Unpooled.buffer(1, 1)); + ret.writeByte(VOICE_SIGNAL_DISCONNECT); + return ret; + } + PacketBuffer ret = new PacketBuffer(Unpooled.buffer(17, 17)); + ret.writeByte(VOICE_SIGNAL_DISCONNECT); + ret.writeUuid(user); + return ret; + } + + public static PacketBuffer makeVoiceSignalPacketConnect() { + PacketBuffer ret = new PacketBuffer(Unpooled.buffer(1, 1)); + ret.writeByte(VOICE_SIGNAL_CONNECT); + return ret; + } +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceTagRenderer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceTagRenderer.java new file mode 100644 index 0000000..5c04715 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceTagRenderer.java @@ -0,0 +1,118 @@ +package net.lax1dude.eaglercraft.v1_8.voice; + +import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; + +import java.util.HashSet; +import java.util.Set; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; +import net.lax1dude.eaglercraft.v1_8.opengl.WorldRenderer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityOtherPlayerMP; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.ResourceLocation; + +/** + * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class VoiceTagRenderer { + + private static final ResourceLocation voiceGuiIcons = new ResourceLocation("eagler:gui/eagler_gui.png"); + + private static final Set voiceTagsDrawnThisFrame = new HashSet(); + + public static void renderVoiceNameTag(Minecraft mc, EntityOtherPlayerMP player, int offset) { + EaglercraftUUID uuid = player.getUniqueID(); + boolean mute = VoiceClientController.getVoiceMuted().contains(uuid); + if((mute || VoiceClientController.getVoiceSpeaking().contains(uuid)) && voiceTagsDrawnThisFrame.add(uuid)) { + GlStateManager.disableLighting(); + GlStateManager.disableTexture2D(); + GlStateManager.enableAlpha(); + GlStateManager.depthMask(false); + GlStateManager.disableDepth(); + GlStateManager.enableBlend(); + + GlStateManager.pushMatrix(); + GlStateManager.translate(-8.0f, -18.0f + offset, 0.0f); + + GlStateManager.scale(16.0f, 16.0f, 16.0f); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR); + float a = 0.25F; + worldrenderer.pos(-0.02, -0.02, 0.0).color(0.0F, 0.0F, 0.0F, a).endVertex(); + worldrenderer.pos(-0.02, 1.02, 0.0).color(0.0F, 0.0F, 0.0F, a).endVertex(); + worldrenderer.pos(1.02, 1.02, 0.0).color(0.0F, 0.0F, 0.0F, a).endVertex(); + worldrenderer.pos(1.02, -0.02, 0.0).color(0.0F, 0.0F, 0.0F, a).endVertex(); + tessellator.draw(); + + GlStateManager.enableTexture2D(); + GlStateManager.enableAlpha(); + GlStateManager.alphaFunc(GL_GREATER, 0.02f); + + mc.getTextureManager().bindTexture(voiceGuiIcons); + + int u = 0; + int v = mute ? 192 : 160; + + float var7 = 0.00390625F; + float var8 = 0.00390625F; + + if(mute) { + GlStateManager.color(0.9F, 0.3F, 0.3F, 0.125F); + }else { + GlStateManager.color(1.0F, 1.0F, 1.0F, 0.125F); + } + + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX); + worldrenderer.pos(0, 1.0, 0).tex((double) ((float) (u + 0.2f) * var7), (double) ((float) (v + 32 - 0.2f) * var8)).endVertex(); + worldrenderer.pos(1.0, 1.0, 0).tex((double) ((float) (u + 32 - 0.2f) * var7), (double) ((float) (v + 32 - 0.2f) * var8)).endVertex(); + worldrenderer.pos(1.0, 0, 0).tex((double) ((float) (u + 32 - 0.2f) * var7), (double) ((float) (v + 0.2f) * var8)).endVertex(); + worldrenderer.pos(0, 0, 0).tex((double) ((float) (u + 0.2f) * var7), (double) ((float) (v + 0.2f) * var8)).endVertex(); + tessellator.draw(); + + GlStateManager.alphaFunc(GL_GREATER, 0.1f); + GlStateManager.enableDepth(); + GlStateManager.depthMask(true); + + if(mute) { + GlStateManager.color(0.9F, 0.3F, 0.3F, 1.0F); + }else { + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + } + + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX); + worldrenderer.pos(0, 1.0, 0).tex((double) ((float) (u + 0.2f) * var7), (double) ((float) (v + 32 - 0.2f) * var8)).endVertex(); + worldrenderer.pos(1.0, 1.0, 0).tex((double) ((float) (u + 32 - 0.2f) * var7), (double) ((float) (v + 32 - 0.2f) * var8)).endVertex(); + worldrenderer.pos(1.0, 0, 0).tex((double) ((float) (u + 32 - 0.2f) * var7), (double) ((float) (v + 0.2f) * var8)).endVertex(); + worldrenderer.pos(0, 0, 0).tex((double) ((float) (u + 0.2f) * var7), (double) ((float) (v + 0.2f) * var8)).endVertex(); + tessellator.draw(); + + GlStateManager.enableLighting(); + GlStateManager.disableBlend(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + + GlStateManager.popMatrix(); + } + } + + public static void clearTagsDrawnSet() { + voiceTagsDrawnThisFrame.clear(); + } + +} diff --git a/sources/resources/assets/eagler/CREDITS.txt b/sources/resources/assets/eagler/CREDITS.txt index 8aec862..63df388 100644 --- a/sources/resources/assets/eagler/CREDITS.txt +++ b/sources/resources/assets/eagler/CREDITS.txt @@ -11,13 +11,15 @@ - Made the integrated PBR resource pack - Wrote all desktop emulation code - Wrote EaglercraftXBungee - - Wrote WebRTC Relay Server + - Wrote WebRTC relay server + - Wrote voice chat server - Wrote the patch and build system ayunami2000: - Many bug fixes - WebRTC LAN worlds + - WebRTC voice chat - Added resource packs - Added screen recording - Added seamless fullscreen diff --git a/sources/resources/assets/eagler/capes/01.minecon_2011.png b/sources/resources/assets/eagler/capes/01.minecon_2011.png new file mode 100644 index 0000000..77e0ccc Binary files /dev/null and b/sources/resources/assets/eagler/capes/01.minecon_2011.png differ diff --git a/sources/resources/assets/eagler/capes/02.minecon_2012.png b/sources/resources/assets/eagler/capes/02.minecon_2012.png new file mode 100644 index 0000000..6390662 Binary files /dev/null and b/sources/resources/assets/eagler/capes/02.minecon_2012.png differ diff --git a/sources/resources/assets/eagler/capes/03.minecon_2013.png b/sources/resources/assets/eagler/capes/03.minecon_2013.png new file mode 100644 index 0000000..1cd6a12 Binary files /dev/null and b/sources/resources/assets/eagler/capes/03.minecon_2013.png differ diff --git a/sources/resources/assets/eagler/capes/04.minecon_2015.png b/sources/resources/assets/eagler/capes/04.minecon_2015.png new file mode 100644 index 0000000..a272284 Binary files /dev/null and b/sources/resources/assets/eagler/capes/04.minecon_2015.png differ diff --git a/sources/resources/assets/eagler/capes/05.minecon_2016.png b/sources/resources/assets/eagler/capes/05.minecon_2016.png new file mode 100644 index 0000000..492eee1 Binary files /dev/null and b/sources/resources/assets/eagler/capes/05.minecon_2016.png differ diff --git a/sources/resources/assets/eagler/capes/06.microsoft_account.png b/sources/resources/assets/eagler/capes/06.microsoft_account.png new file mode 100644 index 0000000..7fccd56 Binary files /dev/null and b/sources/resources/assets/eagler/capes/06.microsoft_account.png differ diff --git a/sources/resources/assets/eagler/capes/07.mapmaker.png b/sources/resources/assets/eagler/capes/07.mapmaker.png new file mode 100644 index 0000000..58a217a Binary files /dev/null and b/sources/resources/assets/eagler/capes/07.mapmaker.png differ diff --git a/sources/resources/assets/eagler/capes/08.mojang_old.png b/sources/resources/assets/eagler/capes/08.mojang_old.png new file mode 100644 index 0000000..438edff Binary files /dev/null and b/sources/resources/assets/eagler/capes/08.mojang_old.png differ diff --git a/sources/resources/assets/eagler/capes/09.mojang_new.png b/sources/resources/assets/eagler/capes/09.mojang_new.png new file mode 100644 index 0000000..2eca0d3 Binary files /dev/null and b/sources/resources/assets/eagler/capes/09.mojang_new.png differ diff --git a/sources/resources/assets/eagler/capes/10.jira_mod.png b/sources/resources/assets/eagler/capes/10.jira_mod.png new file mode 100644 index 0000000..f3d4fe8 Binary files /dev/null and b/sources/resources/assets/eagler/capes/10.jira_mod.png differ diff --git a/sources/resources/assets/eagler/capes/11.mojang_very_old.png b/sources/resources/assets/eagler/capes/11.mojang_very_old.png new file mode 100644 index 0000000..375aea8 Binary files /dev/null and b/sources/resources/assets/eagler/capes/11.mojang_very_old.png differ diff --git a/sources/resources/assets/eagler/capes/12.scrolls.png b/sources/resources/assets/eagler/capes/12.scrolls.png new file mode 100644 index 0000000..25e1447 Binary files /dev/null and b/sources/resources/assets/eagler/capes/12.scrolls.png differ diff --git a/sources/resources/assets/eagler/capes/13.cobalt.png b/sources/resources/assets/eagler/capes/13.cobalt.png new file mode 100644 index 0000000..50d24b7 Binary files /dev/null and b/sources/resources/assets/eagler/capes/13.cobalt.png differ diff --git a/sources/resources/assets/eagler/capes/14.translator.png b/sources/resources/assets/eagler/capes/14.translator.png new file mode 100644 index 0000000..b681c26 Binary files /dev/null and b/sources/resources/assets/eagler/capes/14.translator.png differ diff --git a/sources/resources/assets/eagler/capes/15.millionth_account.png b/sources/resources/assets/eagler/capes/15.millionth_account.png new file mode 100644 index 0000000..c2f0a02 Binary files /dev/null and b/sources/resources/assets/eagler/capes/15.millionth_account.png differ diff --git a/sources/resources/assets/eagler/capes/16.prismarine.png b/sources/resources/assets/eagler/capes/16.prismarine.png new file mode 100644 index 0000000..3ee69b2 Binary files /dev/null and b/sources/resources/assets/eagler/capes/16.prismarine.png differ diff --git a/sources/resources/assets/eagler/capes/17.snowman.png b/sources/resources/assets/eagler/capes/17.snowman.png new file mode 100644 index 0000000..f4ad552 Binary files /dev/null and b/sources/resources/assets/eagler/capes/17.snowman.png differ diff --git a/sources/resources/assets/eagler/capes/18.spade.png b/sources/resources/assets/eagler/capes/18.spade.png new file mode 100644 index 0000000..7cf8e8b Binary files /dev/null and b/sources/resources/assets/eagler/capes/18.spade.png differ diff --git a/sources/resources/assets/eagler/capes/19.birthday.png b/sources/resources/assets/eagler/capes/19.birthday.png new file mode 100644 index 0000000..048ef8e Binary files /dev/null and b/sources/resources/assets/eagler/capes/19.birthday.png differ diff --git a/sources/resources/assets/eagler/capes/20.db.png b/sources/resources/assets/eagler/capes/20.db.png new file mode 100644 index 0000000..8110bd2 Binary files /dev/null and b/sources/resources/assets/eagler/capes/20.db.png differ diff --git a/sources/resources/assets/eagler/mesh/charles.fallback.png b/sources/resources/assets/eagler/mesh/charles.fallback.png new file mode 100644 index 0000000..f1d90ca Binary files /dev/null and b/sources/resources/assets/eagler/mesh/charles.fallback.png differ diff --git a/sources/resources/assets/eagler/mesh/charles.png b/sources/resources/assets/eagler/mesh/charles.png new file mode 100644 index 0000000..a23587f Binary files /dev/null and b/sources/resources/assets/eagler/mesh/charles.png differ diff --git a/sources/resources/assets/eagler/mesh/charles0.mdl b/sources/resources/assets/eagler/mesh/charles0.mdl new file mode 100644 index 0000000..84138ca Binary files /dev/null and b/sources/resources/assets/eagler/mesh/charles0.mdl differ diff --git a/sources/resources/assets/eagler/mesh/charles1.mdl b/sources/resources/assets/eagler/mesh/charles1.mdl new file mode 100644 index 0000000..549898a Binary files /dev/null and b/sources/resources/assets/eagler/mesh/charles1.mdl differ diff --git a/sources/resources/assets/eagler/mesh/charles2.mdl b/sources/resources/assets/eagler/mesh/charles2.mdl new file mode 100644 index 0000000..614cada Binary files /dev/null and b/sources/resources/assets/eagler/mesh/charles2.mdl differ diff --git a/sources/resources/assets/eagler/mesh/laxativedude.fallback.png b/sources/resources/assets/eagler/mesh/laxativedude.fallback.png new file mode 100644 index 0000000..27b1f9c Binary files /dev/null and b/sources/resources/assets/eagler/mesh/laxativedude.fallback.png differ diff --git a/sources/resources/assets/eagler/mesh/laxativedude.png b/sources/resources/assets/eagler/mesh/laxativedude.png new file mode 100644 index 0000000..ece3e4a Binary files /dev/null and b/sources/resources/assets/eagler/mesh/laxativedude.png differ diff --git a/sources/resources/assets/eagler/mesh/laxativedude0.mdl b/sources/resources/assets/eagler/mesh/laxativedude0.mdl new file mode 100644 index 0000000..90da430 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/laxativedude0.mdl differ diff --git a/sources/resources/assets/eagler/mesh/laxativedude1.mdl b/sources/resources/assets/eagler/mesh/laxativedude1.mdl new file mode 100644 index 0000000..3f4d94f Binary files /dev/null and b/sources/resources/assets/eagler/mesh/laxativedude1.mdl differ diff --git a/sources/resources/assets/eagler/mesh/laxativedude2.mdl b/sources/resources/assets/eagler/mesh/laxativedude2.mdl new file mode 100644 index 0000000..1dffcbc Binary files /dev/null and b/sources/resources/assets/eagler/mesh/laxativedude2.mdl differ diff --git a/sources/resources/assets/eagler/mesh/laxativedude3.mdl b/sources/resources/assets/eagler/mesh/laxativedude3.mdl new file mode 100644 index 0000000..56dee1c Binary files /dev/null and b/sources/resources/assets/eagler/mesh/laxativedude3.mdl differ diff --git a/sources/resources/assets/eagler/mesh/longarms.fallback.png b/sources/resources/assets/eagler/mesh/longarms.fallback.png new file mode 100644 index 0000000..b506534 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/longarms.fallback.png differ diff --git a/sources/resources/assets/eagler/mesh/longarms.png b/sources/resources/assets/eagler/mesh/longarms.png new file mode 100644 index 0000000..c021684 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/longarms.png differ diff --git a/sources/resources/assets/eagler/mesh/longarms0.mdl b/sources/resources/assets/eagler/mesh/longarms0.mdl new file mode 100644 index 0000000..29895c1 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/longarms0.mdl differ diff --git a/sources/resources/assets/eagler/mesh/longarms1.mdl b/sources/resources/assets/eagler/mesh/longarms1.mdl new file mode 100644 index 0000000..a72acfa Binary files /dev/null and b/sources/resources/assets/eagler/mesh/longarms1.mdl differ diff --git a/sources/resources/assets/eagler/mesh/longarms2.mdl b/sources/resources/assets/eagler/mesh/longarms2.mdl new file mode 100644 index 0000000..57544d6 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/longarms2.mdl differ diff --git a/sources/resources/assets/eagler/mesh/weirdclimber.fallback.png b/sources/resources/assets/eagler/mesh/weirdclimber.fallback.png new file mode 100644 index 0000000..da5d052 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/weirdclimber.fallback.png differ diff --git a/sources/resources/assets/eagler/mesh/weirdclimber.png b/sources/resources/assets/eagler/mesh/weirdclimber.png new file mode 100644 index 0000000..792cac5 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/weirdclimber.png differ diff --git a/sources/resources/assets/eagler/mesh/weirdclimber0.mdl b/sources/resources/assets/eagler/mesh/weirdclimber0.mdl new file mode 100644 index 0000000..72efddb Binary files /dev/null and b/sources/resources/assets/eagler/mesh/weirdclimber0.mdl differ diff --git a/sources/resources/assets/eagler/mesh/weirdclimber1.mdl b/sources/resources/assets/eagler/mesh/weirdclimber1.mdl new file mode 100644 index 0000000..933d3c8 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/weirdclimber1.mdl differ diff --git a/sources/resources/assets/eagler/mesh/weirdclimber2.mdl b/sources/resources/assets/eagler/mesh/weirdclimber2.mdl new file mode 100644 index 0000000..728db6a Binary files /dev/null and b/sources/resources/assets/eagler/mesh/weirdclimber2.mdl differ diff --git a/sources/resources/assets/eagler/mesh/winston.fallback.png b/sources/resources/assets/eagler/mesh/winston.fallback.png new file mode 100644 index 0000000..b0da89c Binary files /dev/null and b/sources/resources/assets/eagler/mesh/winston.fallback.png differ diff --git a/sources/resources/assets/eagler/mesh/winston.png b/sources/resources/assets/eagler/mesh/winston.png new file mode 100644 index 0000000..fceadbb Binary files /dev/null and b/sources/resources/assets/eagler/mesh/winston.png differ diff --git a/sources/resources/assets/eagler/mesh/winston0.mdl b/sources/resources/assets/eagler/mesh/winston0.mdl new file mode 100644 index 0000000..f66da49 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/winston0.mdl differ diff --git a/sources/resources/assets/eagler/mesh/winston1.mdl b/sources/resources/assets/eagler/mesh/winston1.mdl new file mode 100644 index 0000000..c8cced5 Binary files /dev/null and b/sources/resources/assets/eagler/mesh/winston1.mdl differ diff --git a/sources/resources/plugin_download.zip b/sources/resources/plugin_download.zip index 1e05891..7b99d4b 100644 Binary files a/sources/resources/plugin_download.zip and b/sources/resources/plugin_download.zip differ diff --git a/sources/resources/plugin_version.json b/sources/resources/plugin_version.json index 5f9aed6..56a0adb 100644 --- a/sources/resources/plugin_version.json +++ b/sources/resources/plugin_version.json @@ -1 +1 @@ -{"pluginName":"EaglercraftXBungee","pluginVersion":"1.0.10","pluginButton":"Download \"EaglerXBungee-1.0.10.jar\"","pluginFilename":"EaglerXBungee.zip"} \ No newline at end of file +{"pluginName":"EaglercraftXBungee","pluginVersion":"1.1.0","pluginButton":"Download \"EaglerXBungee-1.1.0.jar\"","pluginFilename":"EaglerXBungee.zip"} \ No newline at end of file diff --git a/sources/setup/workspace_template/desktopRuntime/UnsafeMemcpy.dll b/sources/setup/workspace_template/desktopRuntime/UnsafeMemcpy.dll new file mode 100644 index 0000000..067de85 Binary files /dev/null and b/sources/setup/workspace_template/desktopRuntime/UnsafeMemcpy.dll differ diff --git a/sources/setup/workspace_template/desktopRuntime/UnsafeMemcpy.jar b/sources/setup/workspace_template/desktopRuntime/UnsafeMemcpy.jar new file mode 100644 index 0000000..59fa359 Binary files /dev/null and b/sources/setup/workspace_template/desktopRuntime/UnsafeMemcpy.jar differ diff --git a/sources/setup/workspace_template/desktopRuntime/libUnsafeMemcpy.so b/sources/setup/workspace_template/desktopRuntime/libUnsafeMemcpy.so new file mode 100644 index 0000000..61ec556 Binary files /dev/null and b/sources/setup/workspace_template/desktopRuntime/libUnsafeMemcpy.so differ diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAudio.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAudio.java index b0087b1..07d0c9c 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAudio.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAudio.java @@ -46,8 +46,8 @@ public class PlatformAudio { static final Logger logger = LogManager.getLogger("BrowserAudio"); - private static AudioContext audioctx = null; - private static MediaStreamAudioDestinationNode recDest = null; + static AudioContext audioctx = null; + static MediaStreamAudioDestinationNode recDest = null; private static final Map soundCache = new HashMap(); private static long cacheFreeTimer = 0l; diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformBufferFunctions.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformBufferFunctions.java index 17f28cc..c3b6593 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformBufferFunctions.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformBufferFunctions.java @@ -21,10 +21,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer; public class PlatformBufferFunctions { public static void put(ByteBuffer newBuffer, ByteBuffer flip) { - int len = flip.remaining(); - for(int i = 0; i < len; ++i) { - newBuffer.put(flip.get()); - } + newBuffer.put(flip); } public static void put(IntBuffer intBuffer, int index, int[] data) { diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java index 813c779..5d948ae 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java @@ -18,7 +18,7 @@ import org.teavm.jso.indexeddb.IDBRequest; import org.teavm.jso.indexeddb.IDBTransaction; import org.teavm.jso.indexeddb.IDBVersionChangeEvent; import org.teavm.jso.typedarrays.ArrayBuffer; -import org.teavm.jso.typedarrays.DataView; +import org.teavm.jso.typedarrays.Int8Array; import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; import net.lax1dude.eaglercraft.v1_8.internal.buffer.EaglerArrayBufferAllocator; @@ -85,11 +85,11 @@ public class PlatformFilesystem { if(ar == null) { return null; } - return EaglerArrayBufferAllocator.wrapByteBufferTeaVM(DataView.create(ar)); + return EaglerArrayBufferAllocator.wrapByteBufferTeaVM(Int8Array.create(ar)); } public static void eaglerWrite(String pathName, ByteBuffer data) { - if(!AsyncHandlers.writeWholeFile(database, pathName, EaglerArrayBufferAllocator.getDataViewStupid(data).getBuffer()).bool) { + if(!AsyncHandlers.writeWholeFile(database, pathName, EaglerArrayBufferAllocator.getDataView8Unsigned(data).getBuffer()).bool) { throw new RuntimeException("Failed to write " + data.remaining() + " byte file to indexeddb table: " + pathName); } } diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java index 7c27bf3..5e0098e 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java @@ -4,6 +4,9 @@ import java.util.LinkedList; import java.util.List; import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils; + +import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; import org.teavm.jso.JSBody; import org.teavm.jso.JSObject; import org.teavm.jso.browser.TimerHandler; @@ -100,6 +103,8 @@ public class PlatformInput { public static boolean keyboardLockSupported = false; public static boolean lockKeys = false; + private static boolean vsync = true; + @JSBody(params = { }, script = "window.onbeforeunload = () => {return false;};") private static native void onBeforeCloseRegister(); @@ -291,6 +296,10 @@ public class PlatformInput { return false; } + public static void setVSync(boolean enable) { + vsync = enable; + } + public static void update() { double r = win.getDevicePixelRatio(); int w = PlatformRuntime.parent.getClientWidth(); @@ -304,9 +313,41 @@ public class PlatformInput { canvas.setHeight(h2); } flipBuffer(); - EagUtils.sleep(1l); + if (PlatformRuntime.recording) { + long t = System.currentTimeMillis(); + if(t - PlatformRuntime.lastFrame > (1000 / 30)) { + PlatformRuntime.recFrame(); + PlatformRuntime.lastFrame = t; + } + } + if(vsync) { + asyncRequestAnimationFrame(); + }else { + EagUtils.sleep(0l); + } } - + + @Async + private static native void asyncRequestAnimationFrame(); + + private static void asyncRequestAnimationFrame(AsyncCallback cb) { + final boolean[] hasCompleted = new boolean[1]; + final int[] timeout = new int[] { -1 }; + Window.requestAnimationFrame((d) -> { + if(!hasCompleted[0]) { + hasCompleted[0] = true; + Window.clearTimeout(timeout[0]); + cb.complete(null); + } + }); + timeout[0] = Window.setTimeout(() -> { + if(!hasCompleted[0]) { + hasCompleted[0] = true; + cb.complete(null); + } + }, 50); + } + static void initFramebuffer(WebGL2RenderingContext ctx, WebGLFramebuffer fbo, int sw, int sh) { context = ctx; mainFramebuffer = fbo; @@ -590,7 +631,7 @@ public class PlatformInput { @JSBody(params = { }, script = "window.navigator.keyboard.unlock();") private static native void unlockKeys(); - @JSBody(params = { }, script = "return 'keyboard' in window.navigator && 'lock' in window.navigator.keyboard;") + @JSBody(params = { }, script = "return !!(window.navigator.keyboard && window.navigator.keyboard.lock);") private static native boolean checkKeyboardLockSupported(); @JSBody(params = { }, script = "document.exitFullscreen();") diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java index 21ea7ad..3cead4b 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java @@ -36,12 +36,14 @@ public class PlatformOpenGL { static boolean hasDebugRenderInfoExt = false; static boolean hasFramebufferHDR16FSupport = false; static boolean hasFramebufferHDR32FSupport = false; + static boolean hasLinearHDR32FSupport = false; static void setCurrentContext(WebGL2RenderingContext context) { ctx = context; hasDebugRenderInfoExt = ctx.getExtension("WEBGL_debug_renderer_info") != null; hasFramebufferHDR16FSupport = ctx.getExtension("EXT_color_buffer_half_float") != null; hasFramebufferHDR32FSupport = ctx.getExtension("EXT_color_buffer_float") != null; + hasLinearHDR32FSupport = ctx.getExtension("OES_texture_float_linear") != null; _wglClearColor(1.0f, 1.0f, 1.0f, 1.0f); } @@ -191,15 +193,15 @@ public class PlatformOpenGL { } public static final void _wglBufferData(int target, ByteBuffer data, int usage) { - ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView(data), usage); + ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView8(data), usage); } public static final void _wglBufferData(int target, IntBuffer data, int usage) { - ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView(data), usage); + ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView32(data), usage); } public static final void _wglBufferData(int target, FloatBuffer data, int usage) { - ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView(data), usage); + ctx.bufferData(target, data == null ? null : EaglerArrayBufferAllocator.getDataView32F(data), usage); } public static final void _wglBufferData(int target, int size, int usage) { @@ -207,15 +209,15 @@ public class PlatformOpenGL { } public static final void _wglBufferSubData(int target, int offset, ByteBuffer data) { - ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView(data)); + ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView8(data)); } public static final void _wglBufferSubData(int target, int offset, IntBuffer data) { - ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView(data)); + ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView32(data)); } public static final void _wglBufferSubData(int target, int offset, FloatBuffer data) { - ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView(data)); + ctx.bufferSubData(target, offset, data == null ? null : EaglerArrayBufferAllocator.getDataView32F(data)); } public static final void _wglBindVertexArray(IBufferArrayGL obj) { @@ -258,55 +260,61 @@ public class PlatformOpenGL { public static final void _wglTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer data) { ctx.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView8Unsigned(data)); } public static final void _wglTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) { ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView8Unsigned(data)); } public static final void _wglTexImage2Du16(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) { ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid16(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView16Unsigned(data)); + } + + public static final void _wglTexImage2Df32(int target, int level, int internalFormat, int width, + int height, int border, int format, int type, ByteBuffer data) { + ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, + data == null ? null : EaglerArrayBufferAllocator.getDataView32F(data)); } public static final void _wglTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer data) { ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView8Unsigned(data)); } public static final void _wglTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer data) { ctx.texImage2D(target, level, internalFormat, width, height, border, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView8Unsigned(data)); } public static final void _wglTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data) { ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView8Unsigned(data)); } public static final void _wglTexSubImage2Du16(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data) { ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid16(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView16Unsigned(data)); } public static final void _wglTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer data) { ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView8Unsigned(data)); } public static final void _wglTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, FloatBuffer data) { ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, - data == null ? null : EaglerArrayBufferAllocator.getDataViewStupid(data)); + data == null ? null : EaglerArrayBufferAllocator.getDataView8Unsigned(data)); } public static final void _wglCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, @@ -455,32 +463,32 @@ public class PlatformOpenGL { public static final void _wglUniformMatrix2fv(IUniformGL obj, boolean transpose, FloatBuffer mat) { if(obj != null) ctx.uniformMatrix2fv(((OpenGLObjects.UniformGL)obj).ptr, transpose, - mat == null ? null : EaglerArrayBufferAllocator.getFloatArrayStupid(mat)); + mat == null ? null : EaglerArrayBufferAllocator.getDataView32F(mat)); } public static final void _wglUniformMatrix3fv(IUniformGL obj, boolean transpose, FloatBuffer mat) { if(obj != null) ctx.uniformMatrix3fv(((OpenGLObjects.UniformGL)obj).ptr, transpose, - mat == null ? null : EaglerArrayBufferAllocator.getFloatArrayStupid(mat)); + mat == null ? null : EaglerArrayBufferAllocator.getDataView32F(mat)); } public static final void _wglUniformMatrix3x2fv(IUniformGL obj, boolean transpose, FloatBuffer mat) { if(obj != null) ctx.uniformMatrix3x2fv(((OpenGLObjects.UniformGL)obj).ptr, transpose, - mat == null ? null : EaglerArrayBufferAllocator.getFloatArrayStupid(mat)); + mat == null ? null : EaglerArrayBufferAllocator.getDataView32F(mat)); } public static final void _wglUniformMatrix4fv(IUniformGL obj, boolean transpose, FloatBuffer mat) { if(obj != null) ctx.uniformMatrix4fv(((OpenGLObjects.UniformGL)obj).ptr, transpose, - mat == null ? null : EaglerArrayBufferAllocator.getFloatArrayStupid(mat)); + mat == null ? null : EaglerArrayBufferAllocator.getDataView32F(mat)); } public static final void _wglUniformMatrix4x2fv(IUniformGL obj, boolean transpose, FloatBuffer mat) { if(obj != null) ctx.uniformMatrix4x2fv(((OpenGLObjects.UniformGL)obj).ptr, transpose, - mat == null ? null : EaglerArrayBufferAllocator.getFloatArrayStupid(mat)); + mat == null ? null : EaglerArrayBufferAllocator.getDataView32F(mat)); } public static final void _wglUniformMatrix4x3fv(IUniformGL obj, boolean transpose, FloatBuffer mat) { if(obj != null) ctx.uniformMatrix4x3fv(((OpenGLObjects.UniformGL)obj).ptr, transpose, - mat == null ? null : EaglerArrayBufferAllocator.getFloatArrayStupid(mat)); + mat == null ? null : EaglerArrayBufferAllocator.getDataView32F(mat)); } public static final void _wglBindFramebuffer(int target, IFramebufferGL framebuffer) { @@ -570,7 +578,11 @@ public class PlatformOpenGL { return false; } } - + + public static final boolean checkLinearHDR32FSupport() { + return hasLinearHDR32FSupport; + } + private static final void checkErr(String name) { int i = ctx.getError(); if(i != 0) { diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java index f317319..d544104 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java @@ -28,7 +28,6 @@ import org.teavm.jso.dom.html.HTMLCanvasElement; import org.teavm.jso.dom.html.HTMLDocument; import org.teavm.jso.dom.html.HTMLElement; import org.teavm.jso.typedarrays.ArrayBuffer; -import org.teavm.jso.typedarrays.DataView; import org.teavm.jso.typedarrays.Uint8Array; import org.teavm.jso.webaudio.MediaStream; import org.teavm.jso.webgl.WebGLFramebuffer; @@ -275,27 +274,27 @@ public class PlatformRuntime { } public static ByteBuffer castPrimitiveByteArray(byte[] array) { - return EaglerArrayBufferAllocator.wrapByteBufferTeaVM(DataView.create(TeaVMUtils.unwrapArrayBuffer(array))); + return EaglerArrayBufferAllocator.wrapByteBufferTeaVM(TeaVMUtils.unwrapByteArray(array)); } public static IntBuffer castPrimitiveIntArray(int[] array) { - return EaglerArrayBufferAllocator.wrapIntBufferTeaVM(DataView.create(TeaVMUtils.unwrapArrayBuffer(array))); + return EaglerArrayBufferAllocator.wrapIntBufferTeaVM(TeaVMUtils.unwrapIntArray(array)); } public static FloatBuffer castPrimitiveFloatArray(float[] array) { - return EaglerArrayBufferAllocator.wrapFloatBufferTeaVM(DataView.create(TeaVMUtils.unwrapArrayBuffer(array))); + return EaglerArrayBufferAllocator.wrapFloatBufferTeaVM(TeaVMUtils.unwrapFloatArray(array)); } public static byte[] castNativeByteBuffer(ByteBuffer buffer) { - return TeaVMUtils.wrapUnsignedByteArray(EaglerArrayBufferAllocator.getDataViewStupid(buffer)); + return TeaVMUtils.wrapUnsignedByteArray(EaglerArrayBufferAllocator.getDataView8Unsigned(buffer)); } public static int[] castNativeIntBuffer(IntBuffer buffer) { - return TeaVMUtils.wrapIntArray(EaglerArrayBufferAllocator.getDataViewStupid32(buffer)); + return TeaVMUtils.wrapIntArray(EaglerArrayBufferAllocator.getDataView32(buffer)); } public static float[] castNativeFloatBuffer(FloatBuffer buffer) { - return TeaVMUtils.wrapFloatArray(EaglerArrayBufferAllocator.getFloatArrayStupid(buffer)); + return TeaVMUtils.wrapFloatArray(EaglerArrayBufferAllocator.getDataView32F(buffer)); } public static void freeByteBuffer(ByteBuffer byteBuffer) { @@ -500,12 +499,13 @@ public class PlatformRuntime { return TeaVMClientConfigAdapter.instance; } - private static boolean canRec = false; - private static boolean recording = false; - private static JSObject mediaRec = null; - private static HTMLCanvasElement recCanvas = null; - private static CanvasRenderingContext2D recCtx = null; - private static MediaStream recStream = null; + static boolean canRec = false; + static boolean recording = false; + static long lastFrame = 0l; + static JSObject mediaRec = null; + static HTMLCanvasElement recCanvas = null; + static CanvasRenderingContext2D recCtx = null; + static MediaStream recStream = null; public static boolean isRec() { return recording && canRec; @@ -534,7 +534,7 @@ public class PlatformRuntime { return recording ? "recording.stop" : "recording.start"; } - private static void recFrame() { + static void recFrame() { if (mediaRec != null) { int w = PlatformRuntime.canvas.getWidth(); int h = PlatformRuntime.canvas.getHeight(); @@ -546,21 +546,6 @@ public class PlatformRuntime { } } - private static void onRecFrame() { - if (recording) { - recFrame(); - long t = System.currentTimeMillis(); - Window.requestAnimationFrame(timestamp -> { - long d = (1000 / 30) - (System.currentTimeMillis() - t); - if (d <= 0) { - onRecFrame(); - } else { - Window.setTimeout(PlatformRuntime::onRecFrame, d); - } - }); - } - } - @JSFunctor private static interface MediaHandler extends JSObject { void onMedia(MediaStream stream); @@ -630,7 +615,6 @@ public class PlatformRuntime { }, logger::info); } }); - onRecFrame(); } else { stopRec(mediaRec); mediaRec = null; diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java new file mode 100644 index 0000000..2e100cc --- /dev/null +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java @@ -0,0 +1,439 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.voice.EnumVoiceChannelPeerState; +import net.lax1dude.eaglercraft.v1_8.voice.EnumVoiceChannelReadyState; +import net.lax1dude.eaglercraft.v1_8.voice.EnumVoiceChannelType; +import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController; +import org.json.JSONObject; +import org.json.JSONWriter; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.dom.events.Event; +import org.teavm.jso.dom.events.EventListener; +import org.teavm.jso.dom.html.HTMLAudioElement; +import org.teavm.jso.dom.html.HTMLDocument; +import org.teavm.jso.json.JSON; +import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.webaudio.*; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Copyright (c) 2022-2024 ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class PlatformVoiceClient { + + private static final Logger logger = LogManager.getLogger("PlatformVoiceClient"); + + private static final HashMap voiceAnalysers = new HashMap<>(); + private static final HashMap voiceGains = new HashMap<>(); + private static final HashMap voicePanners = new HashMap<>(); + + @JSBody(params = {}, script = "return typeof window.RTCPeerConnection !== \"undefined\" && typeof navigator.mediaDevices !== \"undefined\" && typeof navigator.mediaDevices.getUserMedia !== \"undefined\";") + public static native boolean isSupported(); + + @JSBody(params = { "item" }, script = "return item.streams[0];") + static native MediaStream getFirstStream(JSObject item); + + @JSBody(params = { "aud", "stream" }, script = "return aud.srcObject = stream;") + static native void setSrcObject(HTMLAudioElement aud, MediaStream stream); + + @JSBody(params = { "aud" }, script = "return aud.remove();") + static native void removeAud(HTMLAudioElement aud); + + @JSBody(params = { "pc", "stream" }, script = "return stream.getTracks().forEach((track) => { pc.addTrack(track, stream); });") + static native void addStream(JSObject pc, MediaStream stream); + + @JSBody(params = { "rawStream", "muted" }, script = "return rawStream.getAudioTracks()[0].enabled = !muted;") + static native void mute(MediaStream rawStream, boolean muted); + + @JSBody(params = { "peerConnection", "str" }, script = "return peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(str)));") + static native void addIceCandidate(JSObject peerConnection, String str); + + public static void disconnect(JSObject peerConnection) { + PlatformWebRTC.closeIt(peerConnection); + } + + public static void setVoiceProximity(int prox) { + for (PannerNode panner : voicePanners.values()) { + panner.setMaxDistance(VoiceClientController.getVoiceListenVolume() * 2 * prox + 0.1f); + } + } + + public static void updateVoicePosition(EaglercraftUUID uuid, double x, double y, double z) { + if (voicePanners.containsKey(uuid)) voicePanners.get(uuid).setPosition((float) x, (float) y, (float) z); + } + + public static class VoicePeer { + public final EaglercraftUUID peerId; + public final JSObject peerConnection; + public MediaStream rawStream; + public VoicePeer(EaglercraftUUID peerId, JSObject peerConnection, boolean offer) { + this.peerId = peerId; + this.peerConnection = peerConnection; + + TeaVMUtils.addEventListener(peerConnection, "icecandidate", (EventListener) evt -> { + if (PlatformWebRTC.hasCandidate(evt)) { + Map m = new HashMap<>(); + m.put("sdpMLineIndex", "" + PlatformWebRTC.getSdpMLineIndex(evt)); + m.put("candidate", PlatformWebRTC.getCandidate(evt)); + handleIceCandidate(peerId, JSONWriter.valueToString(m)); + } + }); + TeaVMUtils.addEventListener(peerConnection, "track", (EventListener) evt -> { + rawStream = getFirstStream(evt); + HTMLAudioElement aud = (HTMLAudioElement) HTMLDocument.current().createElement("audio"); + aud.setAutoplay(true); + aud.setMuted(true); + TeaVMUtils.addEventListener(aud, "ended", (EventListener) evt2 -> { + removeAud(aud); + }); + setSrcObject(aud, rawStream); + handlePeerTrack(peerId, rawStream); + }); + + addStream(peerConnection, localMediaStream.getStream()); + if (offer) { + PlatformWebRTC.createOffer(peerConnection, desc -> { + PlatformWebRTC.setLocalDescription(peerConnection, desc, () -> { + handleDescription(peerId, JSON.stringify(desc)); + }, err -> { + logger.error("Failed to set local description for \"{}\"! {}", peerId, err); + if (peerStateInitial == EnumVoiceChannelPeerState.LOADING) { + peerStateInitial = EnumVoiceChannelPeerState.FAILED; + } + signalDisconnect(peerId, false); + }); + }, err -> { + logger.error("Failed to set create offer for \"{}\"! {}", peerId, err); + if (peerStateInitial == EnumVoiceChannelPeerState.LOADING) { + peerStateInitial = EnumVoiceChannelPeerState.FAILED; + } + signalDisconnect(peerId, false); + }); + } + + TeaVMUtils.addEventListener(peerConnection, "connectionstatechange", (EventListener) evt -> { + String cs = PlatformWebRTC.getConnectionState(peerConnection); + if ("disconnected".equals(cs)) { + signalDisconnect(peerId, false); + } else if ("connected".equals(cs)) { + if (peerState != EnumVoiceChannelPeerState.SUCCESS) { + peerState = EnumVoiceChannelPeerState.SUCCESS; + } + } else if ("failed".equals(cs)) { + if (peerState == EnumVoiceChannelPeerState.LOADING) { + peerState = EnumVoiceChannelPeerState.FAILED; + } + signalDisconnect(peerId, false); + } + }); + } + + public void disconnect() { + PlatformVoiceClient.disconnect(peerConnection); + } + + public void mute(boolean muted) { + PlatformVoiceClient.mute(rawStream, muted); + } + + public void setRemoteDescription(String descJSON) { + try { + JSONObject remoteDesc = new JSONObject(descJSON); + PlatformWebRTC.setRemoteDescription2(peerConnection, descJSON, () -> { + if (remoteDesc.has("type") && "offer".equals(remoteDesc.getString("type"))) { + PlatformWebRTC.createAnswer(peerConnection, desc -> { + PlatformWebRTC.setLocalDescription(peerConnection, desc, () -> { + handleDescription(peerId, JSON.stringify(desc)); + if (peerStateDesc != EnumVoiceChannelPeerState.SUCCESS) peerStateDesc = EnumVoiceChannelPeerState.SUCCESS; + }, err -> { + logger.error("Failed to set local description for \"{}\"! {}", peerId, err.getMessage()); + if (peerStateDesc == EnumVoiceChannelPeerState.LOADING) peerStateDesc = EnumVoiceChannelPeerState.FAILED; + signalDisconnect(peerId, false); + }); + }, err -> { + logger.error("Failed to create answer for \"{}\"! {}", peerId, err.getMessage()); + if (peerStateDesc == EnumVoiceChannelPeerState.LOADING) peerStateDesc = EnumVoiceChannelPeerState.FAILED; + signalDisconnect(peerId, false); + }); + } + }, err -> { + logger.error("Failed to set remote description for \"{}\"! {}", peerId, err.getMessage()); + if (peerStateDesc == EnumVoiceChannelPeerState.LOADING) peerStateDesc = EnumVoiceChannelPeerState.FAILED; + signalDisconnect(peerId, false); + }); + } catch (Throwable err) { + logger.error("Failed to parse remote description for \"{}\"! {}", peerId, err.getMessage()); + if (peerStateDesc == EnumVoiceChannelPeerState.LOADING) peerStateDesc = EnumVoiceChannelPeerState.FAILED; + signalDisconnect(peerId, false); + } + } + + public void addICECandidate(String candidate) { + try { + addIceCandidate(peerConnection, candidate); + if (peerStateIce != EnumVoiceChannelPeerState.SUCCESS) peerStateIce = EnumVoiceChannelPeerState.SUCCESS; + } catch (Throwable err) { + logger.error("Failed to parse ice candidate for \"{}\"! {}", peerId, err.getMessage()); + if (peerStateIce == EnumVoiceChannelPeerState.LOADING) peerStateIce = EnumVoiceChannelPeerState.FAILED; + signalDisconnect(peerId, false); + } + } + } + + public static Set> iceServers = new HashSet<>(); + public static boolean hasInit = false; + public static Map peerList = new HashMap<>(); + public static MediaStreamAudioDestinationNode localMediaStream; + public static GainNode localMediaStreamGain; + public static MediaStream localRawMediaStream; + public static EnumVoiceChannelReadyState readyState = EnumVoiceChannelReadyState.NONE; + public static EnumVoiceChannelPeerState peerState = EnumVoiceChannelPeerState.LOADING; + public static EnumVoiceChannelPeerState peerStateConnect = EnumVoiceChannelPeerState.LOADING; + public static EnumVoiceChannelPeerState peerStateInitial = EnumVoiceChannelPeerState.LOADING; + public static EnumVoiceChannelPeerState peerStateDesc = EnumVoiceChannelPeerState.LOADING; + public static EnumVoiceChannelPeerState peerStateIce = EnumVoiceChannelPeerState.LOADING; + public static AudioContext microphoneVolumeAudioContext = null; + + public static void setICEServers(String[] urls) { + iceServers.clear(); + if (urls == null) return; + for (String url : urls) { + String[] etr = url.split(";"); + if (etr.length == 1) { + Map m = new HashMap<>(); + m.put("urls", etr[0]); + iceServers.add(m); + } else if (etr.length == 3) { + Map m = new HashMap<>(); + m.put("urls", etr[0]); + m.put("username", etr[1]); + m.put("credential", etr[2]); + iceServers.add(m); + } + } + } + + public static void activateVoice(boolean talk) { + if (hasInit) { + PlatformVoiceClient.mute(localRawMediaStream, !talk); + } + } + + public static void initializeDevices() { + if (!hasInit) { + localRawMediaStream = PlatformRuntime.getMic(); + if (localRawMediaStream == null) { + readyState = EnumVoiceChannelReadyState.ABORTED; + return; + } + microphoneVolumeAudioContext = AudioContext.create(); + mute(localRawMediaStream, true); + localMediaStream = microphoneVolumeAudioContext.createMediaStreamDestination(); + localMediaStreamGain = microphoneVolumeAudioContext.createGain(); + microphoneVolumeAudioContext.createMediaStreamSource(localRawMediaStream).connect(localMediaStreamGain); + localMediaStreamGain.connect(localMediaStream); + localMediaStreamGain.getGain().setValue(1.0F); + readyState = EnumVoiceChannelReadyState.DEVICE_INITIALIZED; + hasInit = true; + } else { + readyState = EnumVoiceChannelReadyState.DEVICE_INITIALIZED; + } + } + + public static void tickVoiceClient() { + for (EaglercraftUUID uuid : voiceAnalysers.keySet()) { + AnalyserNode analyser = voiceAnalysers.get(uuid); + Uint8Array array = Uint8Array.create(analyser.getFrequencyBinCount()); + analyser.getByteFrequencyData(array); + int len = array.getLength(); + for (int i = 0; i < len; i++) { + if (array.get(i) >= 0.1f) { + VoiceClientController.getVoiceSpeaking().add(uuid); + break; + } + } + } + } + + public static void setMicVolume(float val) { + if (hasInit) { + if(val > 0.5F) val = 0.5F + (val - 0.5F) * 2.0F; + if(val > 1.5F) val = 1.5F; + if(val < 0.0F) val = 0.0F; + localMediaStreamGain.getGain().setValue(val * 2.0F); + } + } + + public static void resetPeerStates() { + peerState = peerStateConnect = peerStateInitial = peerStateDesc = peerStateIce = EnumVoiceChannelPeerState.LOADING; + } + + public static EnumVoiceChannelPeerState getPeerState() { + return peerState; + } + + public static EnumVoiceChannelPeerState getPeerStateConnect() { + return peerStateConnect; + } + + public static EnumVoiceChannelPeerState getPeerStateInitial() { + return peerStateInitial; + } + + public static EnumVoiceChannelPeerState getPeerStateDesc() { + return peerStateDesc; + } + + public static EnumVoiceChannelPeerState getPeerStateIce() { + return peerStateIce; + } + + public static EnumVoiceChannelReadyState getReadyState() { + return readyState; + } + + public static void signalConnect(EaglercraftUUID peerId, boolean offer) { + if (!hasInit) initializeDevices(); + try { + JSObject peerConnection = PlatformWebRTC.createRTCPeerConnection(JSONWriter.valueToString(iceServers)); + VoicePeer peerInstance = new VoicePeer(peerId, peerConnection, offer); + peerList.put(peerId, peerInstance); + if (peerStateConnect != EnumVoiceChannelPeerState.SUCCESS) peerStateConnect = EnumVoiceChannelPeerState.SUCCESS; + } catch (Throwable e) { + if (peerStateConnect == EnumVoiceChannelPeerState.LOADING) peerStateConnect = EnumVoiceChannelPeerState.FAILED; + } + } + + public static void signalDescription(EaglercraftUUID peerId, String descJSON) { + VoicePeer peer = peerList.get(peerId); + if (peer != null) { + peer.setRemoteDescription(descJSON); + } + } + + public static void signalDisconnect(EaglercraftUUID peerId, boolean quiet) { + VoicePeer peer = peerList.get(peerId); + if (peer != null) { + peerList.remove(peerId, peer); + try { + peer.disconnect(); + } catch (Throwable ignored) {} + handlePeerDisconnect(peerId, quiet); + } + } + + public static void mutePeer(EaglercraftUUID peerId, boolean muted) { + VoicePeer peer = peerList.get(peerId); + if (peer != null) { + peer.mute(muted); + } + } + + public static void signalICECandidate(EaglercraftUUID peerId, String candidate) { + VoicePeer peer = peerList.get(peerId); + if (peer != null) { + peer.addICECandidate(candidate); + } + } + + public static void handleIceCandidate(EaglercraftUUID peerId, String candidate) { + VoiceClientController.sendPacketICE(peerId, candidate); + } + + public static void handleDescription(EaglercraftUUID peerId, String desc) { + VoiceClientController.sendPacketDesc(peerId, desc); + } + + public static void handlePeerTrack(EaglercraftUUID peerId, MediaStream audioStream) { + if (VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.NONE) return; + MediaStreamAudioSourceNode audioNode = PlatformAudio.audioctx.createMediaStreamSource(audioStream); + AnalyserNode analyser = PlatformAudio.audioctx.createAnalyser(); + analyser.setSmoothingTimeConstant(0f); + analyser.setFftSize(32); + audioNode.connect(analyser); + voiceAnalysers.put(peerId, analyser); + if (VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.GLOBAL) { + GainNode gain = PlatformAudio.audioctx.createGain(); + gain.getGain().setValue(VoiceClientController.getVoiceListenVolume()); + analyser.connect(gain); + gain.connect(PlatformAudio.audioctx.getDestination()); + gain.connect(PlatformAudio.recDest); + voiceGains.put(peerId, gain); + VoiceClientController.getVoiceListening().add(peerId); + } else if (VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.PROXIMITY) { + PannerNode panner = PlatformAudio.audioctx.createPanner(); + panner.setRolloffFactor(1f); + panner.setDistanceModel("linear"); + panner.setPanningModel("HRTF"); + 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); + GainNode gain = PlatformAudio.audioctx.createGain(); + gain.getGain().setValue(vol); + analyser.connect(gain); + gain.connect(panner); + panner.connect(PlatformAudio.audioctx.getDestination()); + panner.connect(PlatformAudio.recDest); + voiceGains.put(peerId, gain); + VoiceClientController.getVoiceListening().add(peerId); + voicePanners.put(peerId, panner); + } + if (VoiceClientController.getVoiceMuted().contains(peerId)) mutePeer(peerId, true); + } + + public static void handlePeerDisconnect(EaglercraftUUID peerId, boolean quiet) { + if (voiceAnalysers.containsKey(peerId)) { + voiceAnalysers.get(peerId).disconnect(); + voiceAnalysers.remove(peerId); + } + if (voiceGains.containsKey(peerId)) { + voiceGains.get(peerId).disconnect(); + voiceGains.remove(peerId); + VoiceClientController.getVoiceListening().remove(peerId); + } + if (voicePanners.containsKey(peerId)) { + voicePanners.get(peerId).disconnect(); + voicePanners.remove(peerId); + } + if (!quiet) { + VoiceClientController.sendPacketDisconnect(peerId); + } + } + + public static void setVoiceListenVolume(float f) { + for (EaglercraftUUID uuid : voiceGains.keySet()) { + GainNode gain = voiceGains.get(uuid); + float val = f; + if(val > 0.5f) val = 0.5f + (val - 0.5f) * 3.0f; + if(val > 2.0f) val = 2.0f; + if(val < 0.0f) val = 0.0f; + gain.getGain().setValue(val * 2.0f); + if (voicePanners.containsKey(uuid)) voicePanners.get(uuid).setMaxDistance(f * 2 * VoiceClientController.getVoiceProximity() + 0.1f); + } + } +} diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayBufferAllocator.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayBufferAllocator.java index fd2e3b6..755046a 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayBufferAllocator.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayBufferAllocator.java @@ -1,9 +1,9 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.teavm.jso.typedarrays.ArrayBuffer; import org.teavm.jso.typedarrays.DataView; import org.teavm.jso.typedarrays.Float32Array; import org.teavm.jso.typedarrays.Int32Array; +import org.teavm.jso.typedarrays.Int8Array; import org.teavm.jso.typedarrays.Uint16Array; import org.teavm.jso.typedarrays.Uint8Array; @@ -31,27 +31,31 @@ public class EaglerArrayBufferAllocator { } public static ByteBuffer allocateByteBuffer(int size) { - return new EaglerArrayByteBuffer(DataView.create(ArrayBuffer.create(size))); + return new EaglerArrayByteBuffer(Int8Array.create(size)); } public static ByteBuffer wrapByteBufferTeaVM(DataView dv) { return new EaglerArrayByteBuffer(dv); } - public static IntBuffer allocateIntBuffer(int size) { - return new EaglerArrayIntBuffer(DataView.create(ArrayBuffer.create(size << 2))); + public static ByteBuffer wrapByteBufferTeaVM(Int8Array typedArray) { + return new EaglerArrayByteBuffer(typedArray); } - public static IntBuffer wrapIntBufferTeaVM(DataView dv) { - return new EaglerArrayIntBuffer(dv); + public static IntBuffer allocateIntBuffer(int size) { + return new EaglerArrayIntBuffer(Int32Array.create(size)); + } + + public static IntBuffer wrapIntBufferTeaVM(Int32Array typedArray) { + return new EaglerArrayIntBuffer(typedArray); } public static FloatBuffer allocateFloatBuffer(int size) { - return new EaglerArrayFloatBuffer(DataView.create(ArrayBuffer.create(size << 2))); + return new EaglerArrayFloatBuffer(Float32Array.create(size)); } - public static FloatBuffer wrapFloatBufferTeaVM(DataView dv) { - return new EaglerArrayFloatBuffer(dv); + public static FloatBuffer wrapFloatBufferTeaVM(Float32Array typedArray) { + return new EaglerArrayFloatBuffer(typedArray); } public static DataView getDataView(ByteBuffer buffer) { @@ -63,121 +67,115 @@ public class EaglerArrayBufferAllocator { if(p == 0 && l == b.capacity) { return d; }else { - int i = d.getByteOffset(); - return DataView.create(d.getBuffer(), i + p, l - p); + return DataView.create(d.getBuffer(), d.getByteOffset() + p, l - p); } }else { throw notEagler(buffer); } } - public static Uint8Array getDataViewStupid(ByteBuffer buffer) { + public static Int8Array getDataView8(ByteBuffer buffer) { if(buffer instanceof EaglerArrayByteBuffer) { EaglerArrayByteBuffer b = (EaglerArrayByteBuffer)buffer; - DataView d = b.dataView; - int p = b.position; - int l = b.limit; - int i = d.getByteOffset(); - return Uint8Array.create(d.getBuffer(), i + p, l - p); - }else { - throw notEagler(buffer); - } - } - - public static Uint16Array getDataViewStupid16(ByteBuffer buffer) { - if(buffer instanceof EaglerArrayByteBuffer) { - EaglerArrayByteBuffer b = (EaglerArrayByteBuffer)buffer; - DataView d = b.dataView; - int p = b.position; - int l = b.limit; - int i = d.getByteOffset(); - return Uint16Array.create(d.getBuffer(), i + p, (l - p) >> 1); - }else { - throw notEagler(buffer); - } - } - - public static DataView getDataView(IntBuffer buffer) { - if(buffer instanceof EaglerArrayIntBuffer) { - EaglerArrayIntBuffer b = (EaglerArrayIntBuffer)buffer; - DataView d = b.dataView; + Int8Array d = b.typedArray; int p = b.position; int l = b.limit; if(p == 0 && l == b.capacity) { return d; }else { int i = d.getByteOffset(); - return DataView.create(d.getBuffer(), i + (p << 2), (l - p) << 2); + return Int8Array.create(d.getBuffer(), d.getByteOffset() + p, l - p); } }else { throw notEagler(buffer); } } - public static Uint8Array getDataViewStupid(IntBuffer buffer) { - if(buffer instanceof EaglerArrayIntBuffer) { - EaglerArrayIntBuffer b = (EaglerArrayIntBuffer)buffer; - DataView d = b.dataView; + public static Uint8Array getDataView8Unsigned(ByteBuffer buffer) { + if(buffer instanceof EaglerArrayByteBuffer) { + EaglerArrayByteBuffer b = (EaglerArrayByteBuffer)buffer; + Int8Array d = b.typedArray; int p = b.position; - int l = b.limit; int i = d.getByteOffset(); - return Uint8Array.create(d.getBuffer(), i + (p << 2), (l - p) << 2); + return Uint8Array.create(d.getBuffer(), i + p, b.limit - p); }else { throw notEagler(buffer); } } - public static Int32Array getDataViewStupid32(IntBuffer buffer) { - if(buffer instanceof EaglerArrayIntBuffer) { - EaglerArrayIntBuffer b = (EaglerArrayIntBuffer)buffer; - DataView d = b.dataView; + public static Uint16Array getDataView16Unsigned(ByteBuffer buffer) { + if(buffer instanceof EaglerArrayByteBuffer) { + EaglerArrayByteBuffer b = (EaglerArrayByteBuffer)buffer; + Int8Array d = b.typedArray; int p = b.position; - int l = b.limit; - int i = d.getByteOffset(); - return Int32Array.create(d.getBuffer(), i + (p << 2), (l - p) << 2); + return Uint16Array.create(d.getBuffer(), d.getByteOffset() + p, (b.limit - p) >> 1); }else { throw notEagler(buffer); } } - public static DataView getDataView(FloatBuffer buffer) { - if(buffer instanceof EaglerArrayFloatBuffer) { - EaglerArrayFloatBuffer b = (EaglerArrayFloatBuffer)buffer; - DataView d = b.dataView; + public static Float32Array getDataView32F(ByteBuffer buffer) { + if(buffer instanceof EaglerArrayByteBuffer) { + EaglerArrayByteBuffer b = (EaglerArrayByteBuffer)buffer; + Int8Array d = b.typedArray; + int p = b.position; + return Float32Array.create(d.getBuffer(), d.getByteOffset() + p, (b.limit - p) >> 2); + }else { + throw notEagler(buffer); + } + } + + public static Int32Array getDataView32(IntBuffer buffer) { + if(buffer instanceof EaglerArrayIntBuffer) { + EaglerArrayIntBuffer b = (EaglerArrayIntBuffer)buffer; + Int32Array d = b.typedArray; int p = b.position; int l = b.limit; if(p == 0 && l == b.capacity) { return d; }else { - int i = d.getByteOffset(); - return DataView.create(d.getBuffer(), i + (p << 2), (l - p) << 2); + return Int32Array.create(d.getBuffer(), d.getByteOffset() + (p << 2), l - p); } }else { throw notEagler(buffer); } } - public static Uint8Array getDataViewStupid(FloatBuffer buffer) { - if(buffer instanceof EaglerArrayFloatBuffer) { - EaglerArrayFloatBuffer b = (EaglerArrayFloatBuffer)buffer; - DataView d = b.dataView; + public static Uint8Array getDataView8Unsigned(IntBuffer buffer) { + if(buffer instanceof EaglerArrayIntBuffer) { + EaglerArrayIntBuffer b = (EaglerArrayIntBuffer)buffer; + Int32Array d = b.typedArray; int p = b.position; int l = b.limit; - int i = d.getByteOffset(); - return Uint8Array.create(d.getBuffer(), i + (p << 2), (l - p) << 2); + return Uint8Array.create(d.getBuffer(), d.getByteOffset() + (p << 2), (l - p) << 2); }else { throw notEagler(buffer); } } - public static Float32Array getFloatArrayStupid(FloatBuffer buffer) { + public static Float32Array getDataView32F(FloatBuffer buffer) { if(buffer instanceof EaglerArrayFloatBuffer) { EaglerArrayFloatBuffer b = (EaglerArrayFloatBuffer)buffer; - DataView d = b.dataView; + Float32Array d = b.typedArray; int p = b.position; int l = b.limit; - int i = d.getByteOffset(); - return Float32Array.create(d.getBuffer(), i + p, l - p); + if(p == 0 && l == b.capacity) { + return d; + }else { + return Float32Array.create(d.getBuffer(), d.getByteOffset() + (p << 2), l - p); + } + }else { + throw notEagler(buffer); + } + } + + public static Uint8Array getDataView8Unsigned(FloatBuffer buffer) { + if(buffer instanceof EaglerArrayFloatBuffer) { + EaglerArrayFloatBuffer b = (EaglerArrayFloatBuffer)buffer; + Float32Array d = b.typedArray; + int p = b.position; + int l = b.limit; + return Uint8Array.create(d.getBuffer(), d.getByteOffset() + (p << 2), (l - p) << 2); }else { throw notEagler(buffer); } diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayByteBuffer.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayByteBuffer.java index 5f2760c..adda4b2 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayByteBuffer.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayByteBuffer.java @@ -1,8 +1,12 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.teavm.jso.typedarrays.ArrayBuffer; import org.teavm.jso.typedarrays.DataView; -import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.typedarrays.Float32Array; +import org.teavm.jso.typedarrays.Int16Array; +import org.teavm.jso.typedarrays.Int32Array; +import org.teavm.jso.typedarrays.Int8Array; + +import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils; /** * Copyright (c) 2022-2023 lax1dude. All Rights Reserved. @@ -22,16 +26,18 @@ import org.teavm.jso.typedarrays.Uint8Array; public class EaglerArrayByteBuffer implements ByteBuffer { final DataView dataView; + final Int8Array typedArray; final int capacity; int position; int limit; int mark; - static final DataView ZERO_LENGTH_BUFFER = DataView.create(ArrayBuffer.create(0)); + static final Int8Array ZERO_LENGTH_BUFFER = Int8Array.create(0); EaglerArrayByteBuffer(DataView dataView) { this.dataView = dataView; + this.typedArray = Int8Array.create(dataView.getBuffer(), dataView.getByteOffset(), dataView.getByteLength()); this.capacity = dataView.getByteLength(); this.position = 0; this.limit = this.capacity; @@ -40,11 +46,30 @@ public class EaglerArrayByteBuffer implements ByteBuffer { EaglerArrayByteBuffer(DataView dataView, int position, int limit, int mark) { this.dataView = dataView; + this.typedArray = Int8Array.create(dataView.getBuffer(), dataView.getByteOffset(), dataView.getByteLength()); this.capacity = dataView.getByteLength(); this.position = position; this.limit = limit; this.mark = mark; } + + EaglerArrayByteBuffer(Int8Array typedArray) { + this.typedArray = typedArray; + this.dataView = DataView.create(typedArray.getBuffer(), typedArray.getByteOffset(), typedArray.getByteLength()); + this.capacity = typedArray.getByteLength(); + this.position = 0; + this.limit = this.capacity; + this.mark = -1; + } + + EaglerArrayByteBuffer(Int8Array typedArray, int position, int limit, int mark) { + this.typedArray = typedArray; + this.dataView = DataView.create(typedArray.getBuffer(), typedArray.getByteOffset(), typedArray.getByteLength()); + this.capacity = typedArray.getByteLength(); + this.position = position; + this.limit = limit; + this.mark = mark; + } @Override public int capacity() { @@ -93,8 +118,12 @@ public class EaglerArrayByteBuffer implements ByteBuffer { @Override public ByteBuffer slice() { - int o = dataView.getByteOffset(); - return new EaglerArrayByteBuffer(DataView.create(dataView.getBuffer(), o + position, limit - position)); + if(position == limit) { + return new EaglerArrayByteBuffer(ZERO_LENGTH_BUFFER); + }else { + if(position > limit) throw new ArrayIndexOutOfBoundsException(position); + return new EaglerArrayByteBuffer(Int8Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + position, limit - position)); + } } @Override @@ -110,35 +139,33 @@ public class EaglerArrayByteBuffer implements ByteBuffer { @Override public byte get() { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - return dataView.getInt8(position++); + return typedArray.get(position++); } @Override public ByteBuffer put(byte b) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - dataView.setInt8(position++, b); + typedArray.set(position++, b); return this; } @Override public byte get(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return dataView.getInt8(index); + return typedArray.get(index); } @Override public ByteBuffer put(int index, byte b) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - dataView.setInt8(index, b); + typedArray.set(index, b); return this; } @Override public ByteBuffer get(byte[] dst, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dst[offset + i] = dataView.getInt8(position + i); - } + TeaVMUtils.unwrapArrayBufferView(dst).set(Int8Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + position, length), offset); position += length; return this; } @@ -146,9 +173,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer { @Override public ByteBuffer get(byte[] dst) { if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1); - for(int i = 0; i < dst.length; ++i) { - dst[position + i] = dataView.getInt8(position + i); - } + TeaVMUtils.unwrapArrayBufferView(dst).set(Int8Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + position, dst.length)); position += dst.length; return this; } @@ -159,10 +184,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer { EaglerArrayByteBuffer c = (EaglerArrayByteBuffer)src; int l = c.limit - c.position; if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); - int o = c.dataView.getByteOffset(); - Uint8Array.create(dataView.getBuffer()).set( - Uint8Array.create(c.dataView.getBuffer(), o + c.position, c.limit - c.position), - dataView.getByteOffset() + position); + typedArray.set(Int8Array.create(c.typedArray.getBuffer(), c.typedArray.getByteOffset() + c.position, l), position); position += l; c.position += l; }else { @@ -179,8 +201,10 @@ public class EaglerArrayByteBuffer implements ByteBuffer { @Override public ByteBuffer put(byte[] src, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dataView.setInt8(position + i, src[offset + i]); + if(offset == 0 && length == src.length) { + typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position); + }else { + typedArray.set(Int8Array.create(TeaVMUtils.unwrapArrayBuffer(src), offset, length), position); } position += length; return this; @@ -189,10 +213,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer { @Override public ByteBuffer put(byte[] src) { if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1); - //dataView.set(src, position); // doesn't work - for(int i = 0; i < src.length; ++i) { - dataView.setInt8(position + i, src[i]); - } + typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position); position += src.length; return this; } @@ -211,12 +232,10 @@ public class EaglerArrayByteBuffer implements ByteBuffer { return new EaglerArrayByteBuffer(ZERO_LENGTH_BUFFER); } - int o = dataView.getByteOffset(); + Int8Array dst = Int8Array.create(limit - position); + dst.set(Int8Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + position, limit - position)); - Uint8Array dst = Uint8Array.create(ArrayBuffer.create(limit - position)); - dst.set(Uint8Array.create(dataView.getBuffer(), o + position, limit - position)); - - return new EaglerArrayByteBuffer(DataView.create(dst.getBuffer())); + return new EaglerArrayByteBuffer(dst); } @Override @@ -279,7 +298,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer { @Override public ShortBuffer asShortBuffer() { - return new EaglerArrayShortBuffer(dataView); + return new EaglerArrayShortBuffer(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset(), typedArray.getLength() >> 1)); } @Override @@ -313,7 +332,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer { @Override public IntBuffer asIntBuffer() { - return new EaglerArrayIntBuffer(dataView); + return new EaglerArrayIntBuffer(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset(), typedArray.getLength() >> 2)); } @Override @@ -388,7 +407,7 @@ public class EaglerArrayByteBuffer implements ByteBuffer { @Override public FloatBuffer asFloatBuffer() { - return new EaglerArrayFloatBuffer(dataView); + return new EaglerArrayFloatBuffer(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset(), typedArray.getLength() >> 2)); } @Override diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayFloatBuffer.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayFloatBuffer.java index d69d580..4e9ffe9 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayFloatBuffer.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayFloatBuffer.java @@ -1,8 +1,8 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.teavm.jso.typedarrays.ArrayBuffer; -import org.teavm.jso.typedarrays.DataView; -import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.typedarrays.Float32Array; + +import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils; /** * Copyright (c) 2022-2023 lax1dude. All Rights Reserved. @@ -21,7 +21,7 @@ import org.teavm.jso.typedarrays.Uint8Array; */ public class EaglerArrayFloatBuffer implements FloatBuffer { - final DataView dataView; + final Float32Array typedArray; final int capacity; int position; @@ -30,17 +30,19 @@ public class EaglerArrayFloatBuffer implements FloatBuffer { private static final int SHIFT = 2; - EaglerArrayFloatBuffer(DataView dataView) { - this.dataView = dataView; - this.capacity = dataView.getByteLength() >> SHIFT; + static final Float32Array ZERO_LENGTH_BUFFER = Float32Array.create(0); + + EaglerArrayFloatBuffer(Float32Array typedArray) { + this.typedArray = typedArray; + this.capacity = typedArray.getLength(); this.position = 0; this.limit = this.capacity; this.mark = -1; } - EaglerArrayFloatBuffer(DataView dataView, int position, int limit, int mark) { - this.dataView = dataView; - this.capacity = dataView.getByteLength() >> SHIFT; + EaglerArrayFloatBuffer(Float32Array typedArray, int position, int limit, int mark) { + this.typedArray = typedArray; + this.capacity = typedArray.getLength(); this.position = position; this.limit = limit; this.mark = mark; @@ -93,64 +95,66 @@ public class EaglerArrayFloatBuffer implements FloatBuffer { @Override public FloatBuffer slice() { - int o = dataView.getByteOffset(); - return new EaglerArrayFloatBuffer(DataView.create(dataView.getBuffer(), o + (position << SHIFT), (limit - position) << SHIFT)); + if(position == limit) { + return new EaglerArrayFloatBuffer(ZERO_LENGTH_BUFFER); + }else { + if(position > limit) throw new ArrayIndexOutOfBoundsException(position); + return new EaglerArrayFloatBuffer(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position)); + } } @Override public FloatBuffer duplicate() { - return new EaglerArrayFloatBuffer(dataView, position, limit, mark); + return new EaglerArrayFloatBuffer(typedArray, position, limit, mark); } @Override public FloatBuffer asReadOnlyBuffer() { - return new EaglerArrayFloatBuffer(dataView, position, limit, mark); + return new EaglerArrayFloatBuffer(typedArray, position, limit, mark); } @Override public float get() { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - return dataView.getFloat32((position++) << SHIFT, true); + return typedArray.get(position++); } @Override public FloatBuffer put(float b) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - dataView.setFloat32((position++) << SHIFT, b, true); + typedArray.set(position++, b); return this; } @Override public float get(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return dataView.getFloat32(index << SHIFT, true); + return typedArray.get(index); } @Override public FloatBuffer put(int index, float b) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - dataView.setFloat32(index << SHIFT, b, true); + typedArray.set(index, b); return this; } @Override public float getElement(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return dataView.getFloat32(index << SHIFT, true); + return typedArray.get(index); } @Override public void putElement(int index, float value) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - dataView.setFloat32(index << SHIFT, value, true); + typedArray.set(index, value); } @Override public FloatBuffer get(float[] dst, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dst[offset + i] = dataView.getFloat32((position + i) << SHIFT, true); - } + TeaVMUtils.unwrapArrayBufferView(dst).set(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), length), offset); position += length; return this; } @@ -158,9 +162,7 @@ public class EaglerArrayFloatBuffer implements FloatBuffer { @Override public FloatBuffer get(float[] dst) { if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1); - for(int i = 0; i < dst.length; ++i) { - dst[i] = dataView.getFloat32((position + i) << SHIFT, true); - } + TeaVMUtils.unwrapArrayBufferView(dst).set(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), dst.length)); position += dst.length; return this; } @@ -171,17 +173,14 @@ public class EaglerArrayFloatBuffer implements FloatBuffer { EaglerArrayFloatBuffer c = (EaglerArrayFloatBuffer)src; int l = c.limit - c.position; if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); - int o = c.dataView.getByteOffset(); - Uint8Array.create(dataView.getBuffer()).set( - Uint8Array.create(c.dataView.getBuffer(), o + (c.position << SHIFT), (c.limit - c.position) << SHIFT), - dataView.getByteOffset() + (position << SHIFT)); + typedArray.set(Float32Array.create(c.typedArray.getBuffer(), c.typedArray.getByteOffset() + (c.position << SHIFT), l), position); position += l; c.position += l; }else { int l = src.remaining(); if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); for(int i = 0; i < l; ++i) { - dataView.setFloat32((position + l) << SHIFT, src.get(), true); + typedArray.set(position + l, src.get()); } position += l; } @@ -191,8 +190,10 @@ public class EaglerArrayFloatBuffer implements FloatBuffer { @Override public FloatBuffer put(float[] src, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dataView.setFloat32((position + i) << SHIFT, src[offset + i], true); + if(offset == 0 && length == src.length) { + typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position); + }else { + typedArray.set(Float32Array.create(TeaVMUtils.unwrapArrayBuffer(src), offset << SHIFT, length), position); } position += length; return this; @@ -201,9 +202,7 @@ public class EaglerArrayFloatBuffer implements FloatBuffer { @Override public FloatBuffer put(float[] src) { if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1); - for(int i = 0; i < src.length; ++i) { - dataView.setFloat32((position + i) << SHIFT, src[i], true); - } + typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position); position += src.length; return this; } @@ -219,15 +218,13 @@ public class EaglerArrayFloatBuffer implements FloatBuffer { if(position > limit) throw new ArrayIndexOutOfBoundsException(position); if(position == limit) { - return new EaglerArrayFloatBuffer(EaglerArrayByteBuffer.ZERO_LENGTH_BUFFER); + return new EaglerArrayFloatBuffer(ZERO_LENGTH_BUFFER); } - int o = dataView.getByteOffset(); + Float32Array dst = Float32Array.create(limit - position); + dst.set(Float32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position)); - Uint8Array dst = Uint8Array.create(ArrayBuffer.create((limit - position) << SHIFT)); - dst.set(Uint8Array.create(dataView.getBuffer(), o + (position << SHIFT), (limit - position) << SHIFT)); - - return new EaglerArrayFloatBuffer(DataView.create(dst.getBuffer())); + return new EaglerArrayFloatBuffer(dst); } @Override diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayIntBuffer.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayIntBuffer.java index e2eaba8..7aee34a 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayIntBuffer.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayIntBuffer.java @@ -1,8 +1,8 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.teavm.jso.typedarrays.ArrayBuffer; -import org.teavm.jso.typedarrays.DataView; -import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.typedarrays.Int32Array; + +import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils; /** * Copyright (c) 2022-2023 lax1dude. All Rights Reserved. @@ -21,7 +21,7 @@ import org.teavm.jso.typedarrays.Uint8Array; */ public class EaglerArrayIntBuffer implements IntBuffer { - final DataView dataView; + final Int32Array typedArray; final int capacity; int position; @@ -30,17 +30,19 @@ public class EaglerArrayIntBuffer implements IntBuffer { private static final int SHIFT = 2; - EaglerArrayIntBuffer(DataView dataView) { - this.dataView = dataView; - this.capacity = dataView.getByteLength() >> SHIFT; + static final Int32Array ZERO_LENGTH_BUFFER = Int32Array.create(0); + + EaglerArrayIntBuffer(Int32Array typedArray) { + this.typedArray = typedArray; + this.capacity = typedArray.getLength(); this.position = 0; this.limit = this.capacity; this.mark = -1; } - EaglerArrayIntBuffer(DataView dataView, int position, int limit, int mark) { - this.dataView = dataView; - this.capacity = dataView.getByteLength() >> SHIFT; + EaglerArrayIntBuffer(Int32Array typedArray, int position, int limit, int mark) { + this.typedArray = typedArray; + this.capacity = typedArray.getLength(); this.position = position; this.limit = limit; this.mark = mark; @@ -93,64 +95,66 @@ public class EaglerArrayIntBuffer implements IntBuffer { @Override public IntBuffer slice() { - int o = dataView.getByteOffset(); - return new EaglerArrayIntBuffer(DataView.create(dataView.getBuffer(), o + (position << SHIFT), (limit - position) << SHIFT)); + if(position == limit) { + return new EaglerArrayIntBuffer(ZERO_LENGTH_BUFFER); + }else { + if(position > limit) throw new ArrayIndexOutOfBoundsException(position); + return new EaglerArrayIntBuffer(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position)); + } } @Override public IntBuffer duplicate() { - return new EaglerArrayIntBuffer(dataView, position, limit, mark); + return new EaglerArrayIntBuffer(typedArray, position, limit, mark); } @Override public IntBuffer asReadOnlyBuffer() { - return new EaglerArrayIntBuffer(dataView, position, limit, mark); + return new EaglerArrayIntBuffer(typedArray, position, limit, mark); } @Override public int get() { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - return dataView.getInt32((position++) << SHIFT, true); + return typedArray.get(position++); } @Override public IntBuffer put(int b) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - dataView.setInt32((position++) << SHIFT, b, true); + typedArray.set(position++, b); return this; } @Override public int get(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return dataView.getInt32(index << SHIFT, true); + return typedArray.get(index); } @Override public IntBuffer put(int index, int b) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - dataView.setInt32(index << SHIFT, b, true); + typedArray.set(index, b); return this; } @Override public int getElement(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return dataView.getInt32(index << SHIFT, true); + return typedArray.get(index); } @Override public void putElement(int index, int value) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - dataView.setInt32(index << SHIFT, value, true); + typedArray.set(index, value); } @Override public IntBuffer get(int[] dst, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dst[offset + i] = dataView.getInt32((position + i) << SHIFT, true); - } + TeaVMUtils.unwrapArrayBufferView(dst).set(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), length), offset); position += length; return this; } @@ -158,9 +162,7 @@ public class EaglerArrayIntBuffer implements IntBuffer { @Override public IntBuffer get(int[] dst) { if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1); - for(int i = 0; i < dst.length; ++i) { - dst[i] = dataView.getInt32((position + i) << SHIFT, true); - } + TeaVMUtils.unwrapArrayBufferView(dst).set(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), dst.length)); position += dst.length; return this; } @@ -171,17 +173,14 @@ public class EaglerArrayIntBuffer implements IntBuffer { EaglerArrayIntBuffer c = (EaglerArrayIntBuffer)src; int l = c.limit - c.position; if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); - int o = c.dataView.getByteOffset(); - Uint8Array.create(dataView.getBuffer()).set( - Uint8Array.create(c.dataView.getBuffer(), o + (c.position << SHIFT), (c.limit - c.position) << SHIFT), - dataView.getByteOffset() + (position << SHIFT)); + typedArray.set(Int32Array.create(c.typedArray.getBuffer(), c.typedArray.getByteOffset() + (c.position << SHIFT), l), position); position += l; c.position += l; }else { int l = src.remaining(); if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); for(int i = 0; i < l; ++i) { - dataView.setInt32((position + l) << SHIFT, src.get(), true); + typedArray.set(position + l, src.get()); } position += l; } @@ -191,8 +190,10 @@ public class EaglerArrayIntBuffer implements IntBuffer { @Override public IntBuffer put(int[] src, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dataView.setInt32((position + i) << SHIFT, src[offset + i], true); + if(offset == 0 && length == src.length) { + typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position); + }else { + typedArray.set(Int32Array.create(TeaVMUtils.unwrapArrayBuffer(src), offset << SHIFT, length), position); } position += length; return this; @@ -201,9 +202,7 @@ public class EaglerArrayIntBuffer implements IntBuffer { @Override public IntBuffer put(int[] src) { if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1); - for(int i = 0; i < src.length; ++i) { - dataView.setInt32((position + i) << SHIFT, src[i], true); - } + typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position); position += src.length; return this; } @@ -219,15 +218,13 @@ public class EaglerArrayIntBuffer implements IntBuffer { if(position > limit) throw new ArrayIndexOutOfBoundsException(position); if(position == limit) { - return new EaglerArrayIntBuffer(EaglerArrayByteBuffer.ZERO_LENGTH_BUFFER); + return new EaglerArrayIntBuffer(ZERO_LENGTH_BUFFER); } - int o = dataView.getByteOffset(); + Int32Array dst = Int32Array.create(limit - position); + dst.set(Int32Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position)); - Uint8Array dst = Uint8Array.create(ArrayBuffer.create((limit - position) << SHIFT)); - dst.set(Uint8Array.create(dataView.getBuffer(), o + (position << SHIFT), (limit - position) << SHIFT)); - - return new EaglerArrayIntBuffer(DataView.create(dst.getBuffer())); + return new EaglerArrayIntBuffer(dst); } @Override diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayShortBuffer.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayShortBuffer.java index a21e63e..16eabb5 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayShortBuffer.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/EaglerArrayShortBuffer.java @@ -1,8 +1,8 @@ package net.lax1dude.eaglercraft.v1_8.internal.buffer; -import org.teavm.jso.typedarrays.ArrayBuffer; -import org.teavm.jso.typedarrays.DataView; -import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.typedarrays.Int16Array; + +import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils; /** * Copyright (c) 2022-2023 lax1dude. All Rights Reserved. @@ -21,7 +21,7 @@ import org.teavm.jso.typedarrays.Uint8Array; */ public class EaglerArrayShortBuffer implements ShortBuffer { - final DataView dataView; + final Int16Array typedArray; final int capacity; int position; @@ -30,17 +30,19 @@ public class EaglerArrayShortBuffer implements ShortBuffer { private static final int SHIFT = 1; - EaglerArrayShortBuffer(DataView dataView) { - this.dataView = dataView; - this.capacity = dataView.getByteLength() >> SHIFT; + static final Int16Array ZERO_LENGTH_BUFFER = Int16Array.create(0); + + EaglerArrayShortBuffer(Int16Array typedArray) { + this.typedArray = typedArray; + this.capacity = typedArray.getLength(); this.position = 0; this.limit = this.capacity; this.mark = -1; } - EaglerArrayShortBuffer(DataView dataView, int position, int limit, int mark) { - this.dataView = dataView; - this.capacity = dataView.getByteLength() >> SHIFT; + EaglerArrayShortBuffer(Int16Array typedArray, int position, int limit, int mark) { + this.typedArray = typedArray; + this.capacity = typedArray.getLength(); this.position = position; this.limit = limit; this.mark = mark; @@ -93,64 +95,66 @@ public class EaglerArrayShortBuffer implements ShortBuffer { @Override public ShortBuffer slice() { - int o = dataView.getByteOffset(); - return new EaglerArrayShortBuffer(DataView.create(dataView.getBuffer(), o + (position << SHIFT), (limit - position) << SHIFT)); + if(position == limit) { + return new EaglerArrayShortBuffer(ZERO_LENGTH_BUFFER); + }else { + if(position > limit) throw new ArrayIndexOutOfBoundsException(position); + return new EaglerArrayShortBuffer(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position)); + } } @Override public ShortBuffer duplicate() { - return new EaglerArrayShortBuffer(dataView, position, limit, mark); + return new EaglerArrayShortBuffer(typedArray, position, limit, mark); } @Override public ShortBuffer asReadOnlyBuffer() { - return new EaglerArrayShortBuffer(dataView, position, limit, mark); + return new EaglerArrayShortBuffer(typedArray, position, limit, mark); } @Override public short get() { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - return dataView.getInt16((position++) << SHIFT, true); + return typedArray.get(position++); } @Override public ShortBuffer put(short b) { if(position >= limit) throw new ArrayIndexOutOfBoundsException(position); - dataView.setInt16((position++) << SHIFT, b, true); + typedArray.set(position++, b); return this; } @Override public short get(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return dataView.getInt16(index << SHIFT, true); + return typedArray.get(index); } @Override public ShortBuffer put(int index, short b) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - dataView.setInt16(index << SHIFT, b, true); + typedArray.set(index, b); return this; } @Override public short getElement(int index) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - return dataView.getInt16(index << SHIFT, true); + return typedArray.get(index); } @Override public void putElement(int index, short value) { if(index >= limit) throw new ArrayIndexOutOfBoundsException(index); - dataView.setInt16(index << SHIFT, value, true); + typedArray.set(index, value); } @Override public ShortBuffer get(short[] dst, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dst[offset + i] = dataView.getInt16((position + i) << SHIFT, true); - } + TeaVMUtils.unwrapArrayBufferView(dst).set(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), length), offset); position += length; return this; } @@ -158,9 +162,7 @@ public class EaglerArrayShortBuffer implements ShortBuffer { @Override public ShortBuffer get(short[] dst) { if(position + dst.length > limit) throw new ArrayIndexOutOfBoundsException(position + dst.length - 1); - for(int i = 0; i < dst.length; ++i) { - dst[i] = dataView.getInt16((position + i) << SHIFT, true); - } + TeaVMUtils.unwrapArrayBufferView(dst).set(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), dst.length)); position += dst.length; return this; } @@ -171,17 +173,14 @@ public class EaglerArrayShortBuffer implements ShortBuffer { EaglerArrayShortBuffer c = (EaglerArrayShortBuffer)src; int l = c.limit - c.position; if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); - int o = c.dataView.getByteOffset(); - Uint8Array.create(dataView.getBuffer()).set( - Uint8Array.create(c.dataView.getBuffer(), o + (c.position << SHIFT), (c.limit - c.position) << SHIFT), - dataView.getByteOffset() + (position << SHIFT)); + typedArray.set(Int16Array.create(c.typedArray.getBuffer(), c.typedArray.getByteOffset() + (c.position << SHIFT), l), position); position += l; c.position += l; }else { int l = src.remaining(); if(position + l > limit) throw new ArrayIndexOutOfBoundsException(position + l - 1); for(int i = 0; i < l; ++i) { - dataView.setInt16((position + l) << SHIFT, src.get(), true); + typedArray.set(position + l, src.get()); } position += l; } @@ -191,8 +190,10 @@ public class EaglerArrayShortBuffer implements ShortBuffer { @Override public ShortBuffer put(short[] src, int offset, int length) { if(position + length > limit) throw new ArrayIndexOutOfBoundsException(position + length - 1); - for(int i = 0; i < length; ++i) { - dataView.setInt16((position + i) << SHIFT, src[offset + i], true); + if(offset == 0 && length == src.length) { + typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position); + }else { + typedArray.set(Int16Array.create(TeaVMUtils.unwrapArrayBuffer(src), offset << SHIFT, length), position); } position += length; return this; @@ -201,9 +202,7 @@ public class EaglerArrayShortBuffer implements ShortBuffer { @Override public ShortBuffer put(short[] src) { if(position + src.length > limit) throw new ArrayIndexOutOfBoundsException(position + src.length - 1); - for(int i = 0; i < src.length; ++i) { - dataView.setInt16((position + i) << SHIFT, src[i], true); - } + typedArray.set(TeaVMUtils.unwrapArrayBufferView(src), position); position += src.length; return this; } @@ -219,15 +218,13 @@ public class EaglerArrayShortBuffer implements ShortBuffer { if(position > limit) throw new ArrayIndexOutOfBoundsException(position); if(position == limit) { - return new EaglerArrayShortBuffer(EaglerArrayByteBuffer.ZERO_LENGTH_BUFFER); + return new EaglerArrayShortBuffer(ZERO_LENGTH_BUFFER); } - int o = dataView.getByteOffset(); + Int16Array dst = Int16Array.create(limit - position); + dst.set(Int16Array.create(typedArray.getBuffer(), typedArray.getByteOffset() + (position << SHIFT), limit - position)); - Uint8Array dst = Uint8Array.create(ArrayBuffer.create((limit - position) << SHIFT)); - dst.set(Uint8Array.create(dataView.getBuffer(), o + (position << SHIFT), (limit - position) << SHIFT)); - - return new EaglerArrayShortBuffer(DataView.create(dst.getBuffer())); + return new EaglerArrayShortBuffer(dst); } @Override diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/ClientMain.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/ClientMain.java index c0336e9..c92acb7 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/ClientMain.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/ClientMain.java @@ -46,7 +46,7 @@ import net.minecraft.client.main.Main; */ public class ClientMain { - private static final String crashImage = "data:image/png;base64,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c+Jxky5I/Mh1xeM3zkWXBZ4pmpnUPLFILBU7WOlgVjI14mniiKrplC9kPVY5b3HWKjXWuid/YSivr6S5TmsEcSwhgSQkKKihjApsRGnXSbGQovOYj3/Y9SfJpZCrDEaOBVShQXb94H/we7ZWYWrSSwrFgO4Xx/kYBXp2gWbdcb6PHad5AgSfgSu97a82gNlP0uttLXIEDGwDF9dtTdkDLneA8JMhm7IrBWkJhQLwfkbflAMGb4G+NW9urXOcPgAZmtXyDXBwCIwVKXvd5929nXP7t6c1vx8743KRRjbQVgAADfdpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6NDJlMTU3MGEtNmMyZS00Y2E1LWI3ZTMtOGI4ODI1MmMwZDMwIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjU1NGY3N2UwLTc4NmEtNGFlZS1iYjhmLWNhYTBiZGNiYzE3MSIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOmNmMWYyMjUxLWIwY2QtNDE1NS1hMjAyLTExNGI0ZGM2MmFhNSIKICAgZGM6Rm9ybWF0PSJpbWFnZS9wbmciCiAgIEdJTVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IldpbmRvd3MiCiAgIEdJTVA6VGltZVN0YW1wPSIxNjQzMDYxODUwNDk0OTc0IgogICBHSU1QOlZlcnNpb249IjIuMTAuMjQiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ODUyMGQ4YTMtMWRhZC00ZjIwLWFjOTktODg4OTJkZDExNDQ0IgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKFdpbmRvd3MpIgogICAgICBzdEV2dDp3aGVuPSIyMDIxLTEyLTE3VDE3OjIyOjQ4Ii8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjJkY2U5N2M4LTBkZjItNGQzNi1iMzE1LWE0YjdmMmUyMjJiNSIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjEwIChXaW5kb3dzKSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0wMS0yNFQxNDowNDoxMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz61xwk6AAAABmJLR0QAnQCdAJ2roJyEAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5gEYFgQKOBb3JwAAIABJREFUeNrtvXl0lFWePv7UvlelKvu+koSQRQiyBJGISEB0hFYZwW1sp4/2csaZ1jlz5sz80cc5c7rnaI8zju2o09qiIrKowEGURXYI+5IASQjZl0plT2rff3/073O/byVVlUQSRPs+5+QkkMpbb9333ud+lufzuaJgMBgEBwcHxw8UYj4EHBwcnMQ4ODg4OIlxcHBwcBLj4ODgJMbBwcHBSYyDg4ODkxgHBwcHJzEODg5OYhwcHBycxDg4ODg4iXFw/GWAqgHHfv+ufyv8+q7XvVMh4rWTHBwc3BLj4ODg4CTGwcHBwUmMg4ODkxgHBwcHJzEODg4OTmIcHBwcnMQ4ODg4iXFwcHBwEuPg4ODgJMbBwcHBSYyDg+NHBCkfAo6/ZAiLov1+P3w+H/x+P/x+P4LBIDQaDWQyGR+oSYyjSCSK+G9OYhwctwiHw4Guri44HA54vV74fD72FQgE4Pf74Xa74XA44Ha74Xa7EQgEsGrVKqSnp9+2BTlT5EJETT+PJZxgMAixWMx+FwgEIBb/2VGjn4W/o78P9/qxPSXo2pFez0mMg2MSC7mrqwt/+MMfYDabYbfb4XQ64XQ64XA42HeHw4HBwUFIJBJIJBLExMRg4cKFSEtL+0GSmJAwxsLn80EqlSIQCIRYomKxGD6fD2KxGH6/n31un88HiUQCv98fcm26Pr2H0LIlkhLeg/B3RIbCa30XS46TGMePHn6/HxaLBVu2bIFcLg9ZXLRQpFIpDAYDDAYDW0RyufwHRV7ChU9kQeQhtHyInLxeL7OwyFoS/o7caCI1+k7XlEgkjIiEJOXz+SASiSCVStl9SKX/j2pE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"; + private static final String crashImage = "data:image/png;base64,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"; // avoid inlining of constant private static String crashImageWrapper() { diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMClientConfigAdapter.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMClientConfigAdapter.java index e9ad8a2..61d1791 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMClientConfigAdapter.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMClientConfigAdapter.java @@ -55,6 +55,7 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter { private boolean logInvalidCerts = false; private boolean checkRelaysForUpdates = false; private boolean enableSignatureBadge = false; + private boolean allowVoiceClient = true; public void loadNative(JSObject jsObject) { integratedServerOpts = new JSONObject(); @@ -73,12 +74,14 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter { useSpecialCursors = eaglercraftXOpts.getHtml5CursorSupport(false); logInvalidCerts = EaglercraftVersion.enableUpdateService && !demoMode && eaglercraftXOpts.getLogInvalidCerts(false); enableSignatureBadge = eaglercraftXOpts.getEnableSignatureBadge(false); + allowVoiceClient = eaglercraftXOpts.getAllowVoiceClient(true); integratedServerOpts.put("worldsDB", worldsDB); integratedServerOpts.put("demoMode", demoMode); integratedServerOpts.put("lang", defaultLocale); integratedServerOpts.put("allowUpdateSvc", isAllowUpdateSvc); integratedServerOpts.put("allowUpdateDL", isAllowUpdateDL); + integratedServerOpts.put("allowVoiceClient", allowVoiceClient); JSEaglercraftXOptsServersArray serversArray = eaglercraftXOpts.getServers(); if(serversArray != null) { @@ -158,6 +161,7 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter { useSpecialCursors = eaglercraftOpts.optBoolean("html5CursorSupport", false); logInvalidCerts = EaglercraftVersion.enableUpdateService && !demoMode && eaglercraftOpts.optBoolean("logInvalidCerts", false); enableSignatureBadge = eaglercraftOpts.optBoolean("enableSignatureBadge", false); + allowVoiceClient = eaglercraftOpts.optBoolean("allowVoiceClient", true); JSONArray serversArray = eaglercraftOpts.optJSONArray("servers"); if(serversArray != null) { for(int i = 0, l = serversArray.length(); i < l; ++i) { @@ -300,6 +304,11 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter { return enableSignatureBadge; } + @Override + public boolean isAllowVoiceClient() { + return allowVoiceClient; + } + @Override public String toString() { JSONObject jsonObject = new JSONObject(); @@ -317,6 +326,7 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter { jsonObject.put("logInvalidCerts", logInvalidCerts); jsonObject.put("checkRelaysForUpdates", checkRelaysForUpdates); jsonObject.put("enableSignatureBadge", enableSignatureBadge); + jsonObject.put("allowVoiceClient", allowVoiceClient); JSONArray serversArr = new JSONArray(); for(int i = 0, l = defaultServers.size(); i < l; ++i) { DefaultServer srv = defaultServers.get(i); diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMUtils.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMUtils.java index 98a6963..0a8edf7 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMUtils.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMUtils.java @@ -9,6 +9,7 @@ import org.teavm.jso.browser.Window; import org.teavm.jso.typedarrays.ArrayBuffer; import org.teavm.jso.typedarrays.ArrayBufferView; import org.teavm.jso.typedarrays.Float32Array; +import org.teavm.jso.typedarrays.Int16Array; import org.teavm.jso.typedarrays.Int32Array; import org.teavm.jso.typedarrays.Int8Array; import org.teavm.jso.typedarrays.Uint8Array; @@ -57,6 +58,10 @@ public class TeaVMUtils { return ((TeaVMArrayObject)(Object)buf).getData().getBuffer(); } + public static ArrayBufferView unwrapArrayBufferView(byte[] buf) { + return ((TeaVMArrayObject)(Object)buf).getData(); + } + @JSBody(params = { "buf" }, script = "return $rt_createByteArray(buf.buffer)") private static native JSObject wrapByteArray0(JSObject buf); @@ -80,6 +85,10 @@ public class TeaVMUtils { return ((TeaVMArrayObject)(Object)buf).getData().getBuffer(); } + public static ArrayBufferView unwrapArrayBufferView(int[] buf) { + return ((TeaVMArrayObject)(Object)buf).getData(); + } + @JSBody(params = { "buf" }, script = "return $rt_createIntArray(buf.buffer)") private static native JSObject wrapIntArray0(JSObject buf); @@ -95,6 +104,10 @@ public class TeaVMUtils { return ((TeaVMArrayObject)(Object)buf).getData().getBuffer(); } + public static ArrayBufferView unwrapArrayBufferView(float[] buf) { + return ((TeaVMArrayObject)(Object)buf).getData(); + } + @JSBody(params = { "buf" }, script = "return $rt_createFloatArray(buf.buffer)") private static native JSObject wrapFloatArray0(JSObject buf); @@ -102,6 +115,25 @@ public class TeaVMUtils { return (float[])(Object)wrapFloatArray0(buf); } + public static Int16Array unwrapShortArray(short[] buf) { + return Int16Array.create(((TeaVMArrayObject)(Object)buf).getData().getBuffer()); + } + + public static ArrayBuffer unwrapArrayBuffer(short[] buf) { + return ((TeaVMArrayObject)(Object)buf).getData().getBuffer(); + } + + public static ArrayBufferView unwrapArrayBufferView(short[] buf) { + return ((TeaVMArrayObject)(Object)buf).getData(); + } + + @JSBody(params = { "buf" }, script = "return $rt_createShortArray(buf.buffer)") + private static native JSObject wrapShortArray0(JSObject buf); + + public static short[] wrapShortArray(Int16Array buf) { + return (short[])(Object)wrapShortArray0(buf); + } + @Async public static native void sleepSetTimeout(int millis); diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/opts/JSEaglercraftXOptsRoot.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/opts/JSEaglercraftXOptsRoot.java index 7449bd8..c5c9483 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/opts/JSEaglercraftXOptsRoot.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/opts/JSEaglercraftXOptsRoot.java @@ -80,4 +80,7 @@ public abstract class JSEaglercraftXOptsRoot implements JSObject { @JSBody(params = { "def" }, script = "return (typeof this.checkRelaysForUpdates === \"boolean\") ? this.checkRelaysForUpdates : def;") public native boolean getCheckRelaysForUpdates(boolean defaultValue); + @JSBody(params = { "def" }, script = "return (typeof this.allowVoiceClient === \"boolean\") ? this.allowVoiceClient : def;") + public native boolean getAllowVoiceClient(boolean defaultValue); + }