diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5c965..90d8f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.8.1 - 2026-09-12 + +### Fixed +- **Right clicking an entity with a page bound to it crashed the game on NeoForge.** The interaction event arrives on both the client and the server, and the client's copy was being handed to code that only a server can run: the first click on a bound entity took the whole client down. Fabric was never affected. +- **A page opened from an entity was told nothing about the entity.** `window.webgui.entity` was `null`, so one page bound to several NPCs had no way to tell which of them had been clicked. The entity was sent a moment before the screen that needed it, and opening that screen threw it away. +- **The HUD disappeared when one page replaced another.** Dying with a shop open — the death screen replaces the shop — or simply sending a second `/webgui gui` destroyed the HUD, and it stayed gone until the server sent it again. +- The pages a server hosts kept being served for a moment after the player left that server, on a token that leaving was supposed to revoke. + ## 1.8.0 - 2026-09-10 ### Added diff --git a/gradle.properties b/gradle.properties index b80ac93..a74b415 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx4G org.gradle.parallel=true dev.kikugie.stonecutter.hard_mode=true -mod_version=1.8.0 +mod_version=1.8.1 maven_group=land.webgui archives_base_name=webgui diff --git a/src/main/java/land/webgui/EntityInteractionListener.java b/src/main/java/land/webgui/EntityInteractionListener.java index 6020727..f0877b1 100644 --- a/src/main/java/land/webgui/EntityInteractionListener.java +++ b/src/main/java/land/webgui/EntityInteractionListener.java @@ -44,10 +44,15 @@ public static void register() { private static void onEntityInteract(PlayerInteractEvent.EntityInteract event) { if (event.getHand() != InteractionHand.MAIN_HAND) return; + + // The event fires on both sides, and on the client the player is a LocalPlayer. + // Casting it crashed the game on the first right click of a bound entity. Only the + // server can open a gui for a player anyway, which is what the Fabric side does. + if (!(event.getEntity() instanceof ServerPlayer sp)) return; + var opt = EntityBindingStore.get(event.getTarget().getUUID()); if (opt.isEmpty()) return; - ServerPlayer sp = (ServerPlayer) event.getEntity(); EntityBinding b = opt.get(); String url = WebviewPlaceholders.resolve(b.urlTemplate(), sp, event.getTarget()); String entityJson = WebviewEntityContext.buildJson(event.getTarget()); diff --git a/src/main/java/land/webgui/WebGUIAssetServer.java b/src/main/java/land/webgui/WebGUIAssetServer.java index b75a01f..20b6f3f 100644 --- a/src/main/java/land/webgui/WebGUIAssetServer.java +++ b/src/main/java/land/webgui/WebGUIAssetServer.java @@ -37,9 +37,12 @@ public final class WebGUIAssetServer { private static final SecureRandom RANDOM = new SecureRandom(); - private static HttpServer server; - private static int port; - private static String sessionToken = ""; + // Volatile because the eight threads serving requests read these without holding the + // lock the writers take: without it a request thread can keep honouring a token that + // leaving a server has already revoked. + private static volatile HttpServer server; + private static volatile int port; + private static volatile String sessionToken = ""; /** Paths already reported as missing, so one broken build logs each name once. */ private static final java.util.Set WARNED = java.util.concurrent.ConcurrentHashMap.newKeySet(); diff --git a/src/main/java/land/webgui/WebHudOverlay.java b/src/main/java/land/webgui/WebHudOverlay.java index 227b4a0..23aa2cb 100644 --- a/src/main/java/land/webgui/WebHudOverlay.java +++ b/src/main/java/land/webgui/WebHudOverlay.java @@ -319,7 +319,12 @@ public static void onGuiOpened() { //? } else { /*public static void onGuiOpened() {*/ //? } - restoreHudAfterGuiClose = hudVisible && WebSession.mode() == WebSession.Mode.HUD_OVERLAY; + // Only decided when a gui is opening over the hud. A gui replacing another gui + // runs this too, and by then the mode is already GUI_SCREEN, so recomputing it + // here answered "there is no hud to put back" and threw the parked one away. + if (WebSession.mode() == WebSession.Mode.HUD_OVERLAY) { + restoreHudAfterGuiClose = hudVisible; + } hudInteractive = false; lastPixelW = -1; lastPixelH = -1; diff --git a/src/main/java/land/webgui/WebSession.java b/src/main/java/land/webgui/WebSession.java index c36776f..810b167 100644 --- a/src/main/java/land/webgui/WebSession.java +++ b/src/main/java/land/webgui/WebSession.java @@ -71,11 +71,17 @@ public static void closeGuiAndRestoreHud() { } public static RinkuBrowser openForGui(String url) { - closeSuspendedHudBrowser(); if (mode == Mode.HUD_OVERLAY && browser != null) { + // Coming from the hud: park it. Nothing should already be parked in this mode, + // but closing first keeps that from becoming a leak if it ever is. + closeSuspendedHudBrowser(); suspendedHudBrowser = browser; browser = null; } else { + // One gui replacing another, which is ordinary: dying with a gui open does it. + // Only the outgoing gui is closed here - whatever hud is parked belongs to + // whoever opened the first gui, and closing it here left the player with no + // hud at all once they escaped out. closeActiveBrowser(); } browser = Rinku.createBrowser(WebGUIAssetServer.resolve(url), true); diff --git a/src/main/java/land/webgui/WebviewNetworking.java b/src/main/java/land/webgui/WebviewNetworking.java index 895ec6c..7388531 100644 --- a/src/main/java/land/webgui/WebviewNetworking.java +++ b/src/main/java/land/webgui/WebviewNetworking.java @@ -205,7 +205,6 @@ public static void openHud(ServerPlayerEntity player, String url) { } public static void openGuiForEntity(ServerPlayerEntity player, String url, String entityJson) { - sendEntityContext(player, entityJson); //? if >=1.20.5 { ServerPlayNetworking.send(player, new WebviewPayloads.OpenWebS2CPayload(PROTOCOL_VERSION, MODE_GUI, withPlayerToken(player, url))); //? } else { @@ -215,6 +214,11 @@ public static void openGuiForEntity(ServerPlayerEntity player, String url, Strin buf.writeString(withPlayerToken(player, url), MAX_URL_LENGTH); ServerPlayNetworking.send(player, WebviewPayloads.OPEN_WEB_CHANNEL, buf);*/ //? } + // The context goes out after the open, not before it. Opening a gui builds a new + // browser and clears the client's push cache, and the entity context lives in that + // cache: sent first, it was wiped by the very screen it was meant for, and the page + // received null. Packets on one connection keep their order, so this arrives after. + sendEntityContext(player, entityJson); } public static void sendEntityContext(ServerPlayerEntity player, String entityJson) { @@ -427,8 +431,12 @@ public static void openHud(ServerPlayer player, String url) { } public static void openGuiForEntity(ServerPlayer player, String url, String entityJson) { - sendEntityContext(player, entityJson); PacketDistributor.sendToPlayer(player, new WebviewPayloads.OpenWebS2CPayload(PROTOCOL_VERSION, MODE_GUI, withPlayerToken(player, url))); + // The context goes out after the open, not before it. Opening a gui builds a new + // browser and clears the client's push cache, and the entity context lives in that + // cache: sent first, it was wiped by the very screen it was meant for, and the page + // received null. Packets on one connection keep their order, so this arrives after. + sendEntityContext(player, entityJson); } public static void sendEntityContext(ServerPlayer player, String entityJson) {