From abb0e94f86a341bb9196683fbbf83e46b9768caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B5=D0=B2?= Date: Thu, 10 Sep 2026 20:42:53 +0500 Subject: [PATCH 1/4] =?UTF-8?q?fix(neoforge):=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=B0=D0=B4=D0=B0=D1=82=D1=8C=20=D0=BF=D1=80=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BB=D0=B8=D0=BA=D0=B5=20=D0=BF=D0=BE=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D0=B2=D1=8F=D0=B7=D0=B0=D0=BD=D0=BD=D0=BE=D0=B9=20=D1=81=D1=83?= =?UTF-8?q?=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PlayerInteractEvent.EntityInteract приходит на обеих сторонах, и на клиенте игрок — LocalPlayer. Приведение к ServerPlayer роняло игру на первом же правом клике по сущности с привязанным GUI. Обработчик теперь выходит, если это не ServerPlayer: открыть GUI игроку может только сервер, ровно как это уже сделано в fabric-ветке. --- src/main/java/land/webgui/EntityInteractionListener.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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()); From fa6053e448199b2f1249e07ada3774cc606d1d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B5=D0=B2?= Date: Thu, 10 Sep 2026 21:35:34 +0500 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D1=82=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=B4=D0=BE=D1=85=D0=BE=D0=B4=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B4=D0=BE=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7?= =?UTF-8?q?=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Порядок пакетов. Открытие GUI создаёт новый браузер и сбрасывает кэш клиентского пуша, а контекст сущности живёт именно в этом кэше — отправленный до пакета открытия, он затирался тем самым экраном, для которого и был нужен, и страница получала null. Теперь контекст уходит после открытия: пакеты одного соединения сохраняют порядок, так что он приходит уже после сброса. --- src/main/java/land/webgui/WebviewNetworking.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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) { From ab2c898ad6990928c38d4bd53f53a38fe9e4aabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B5=D0=B2?= Date: Sat, 12 Sep 2026 00:40:36 +0500 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20HUD=20=D0=BF=D1=80=D0=BE=D0=BF=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB,=20=D0=BA=D0=BE=D0=B3=D0=B4=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=B4=D0=B8=D0=BD=20GUI=20=D1=81=D0=BC=D0=B5=D0=BD=D1=8F=D0=BB?= =?UTF-8?q?=20=D0=B4=D1=80=D1=83=D0=B3=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Игра при замене экрана зовёт removed() у уходящего, а не onClose(), и мод переопределяет только второй — восстановление HUD не срабатывало. Заодно входящий экран закрывал припаркованный HUD-браузер по дороге: openForGui делал closeSuspendedHudBrowser() безусловно, хотя заменял всего лишь GUI. А onGuiOpened, вызываемый до openForGui, к этому моменту видел режим GUI_SCREEN и гасил флаг восстановления. Итог: умереть с открытым магазином (экран смерти заменяет GUI) или получить второй `webgui gui` — и HUD не возвращался, пока сервер не пришлёт его заново. Теперь HUD паркуется только при переходе из HUD, а флаг пересчитывается только тогда же. Сценарий стенда на этот путь ходит: hud, gui, gui, escape. Отдельно: sessionToken, port и server в сервере статики стали volatile. Их пишут под замком, а читают восемь потоков пула без него, из-за чего отозванный при выходе токен мог продолжать работать. --- src/main/java/land/webgui/WebGUIAssetServer.java | 9 ++++++--- src/main/java/land/webgui/WebHudOverlay.java | 7 ++++++- src/main/java/land/webgui/WebSession.java | 8 +++++++- 3 files changed, 19 insertions(+), 5 deletions(-) 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); From 2d7d52aa24d224b89201664d878ef2fba11b8a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B5=D0=B2?= Date: Sat, 12 Sep 2026 01:58:57 +0500 Subject: [PATCH 4/4] =?UTF-8?q?1.8.1:=20=D0=BA=D1=80=D0=B0=D1=88=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=BA=D0=BB=D0=B8=D0=BA=D0=B5=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B8,=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D0=B5=D1=80=D1=8F=D0=BD=D0=BD=D1=8B=D0=B9=20HUD=20?= =?UTF-8?q?=D0=B8=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=BA=D1=81=D1=82=20?= =?UTF-8?q?=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Три исправления после разбора. Подробности в CHANGELOG. --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) 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