From 9666390ba2ba0bef000245bbd0dca60ed470d0d9 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 25 Sep 2025 15:24:46 +0000 Subject: [PATCH] Expose CodeOfConductManager through API events --- .../SessionAcceptCodeOfConductEvent.java | 71 ++++++++++++++++++ .../event/java/ServerCodeOfConductEvent.java | 72 +++++++++++++++++++ .../entity/CopperBlockEntityTranslator.java | 4 +- .../java/JavaCodeOfConductTranslator.java | 2 +- .../geyser/util/CodeOfConductManager.java | 33 ++++++--- 5 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionAcceptCodeOfConductEvent.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/java/ServerCodeOfConductEvent.java diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionAcceptCodeOfConductEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionAcceptCodeOfConductEvent.java new file mode 100644 index 000000000..1b9da105a --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionAcceptCodeOfConductEvent.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.bedrock; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.connection.ConnectionEvent; +import org.geysermc.geyser.api.event.java.ServerCodeOfConductEvent; + +/** + * Fired when a player accepts a code of conduct sent by the Java server. API users can listen to this event + * to store the acceptance in a cache, and tell Geyser not to do so. + * + *

Java clients cache acceptance locally, but bedrock clients don't. Normally Geyser uses a simple JSON file to implement this, + * but an alternative solution may be preferred when using multiple Geyser instances. Such a solution can be implemented through this event and {@link ServerCodeOfConductEvent}.

+ * + * @see ServerCodeOfConductEvent + */ +public class SessionAcceptCodeOfConductEvent extends ConnectionEvent { + private final String codeOfConduct; + private boolean wasSavedElsewhere = false; + + public SessionAcceptCodeOfConductEvent(@NonNull GeyserConnection connection, String codeOfConduct) { + super(connection); + this.codeOfConduct = codeOfConduct; + } + + /** + * @return the code of conduct sent by the server + */ + public String codeOfConduct() { + return codeOfConduct; + } + + /** + * @return {@code true} if Geyser should not save the acceptance of the code of conduct in its own cache (through a JSON file), because it was saved elsewhere + */ + public boolean wasSavedElsewhere() { + return wasSavedElsewhere; + } + + /** + * Sets {@link SessionAcceptCodeOfConductEvent#wasSavedElsewhere()} to {@code true}. + */ + public void setSavedElsewhere() { + this.wasSavedElsewhere = true; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerCodeOfConductEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerCodeOfConductEvent.java new file mode 100644 index 000000000..3e8c52306 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerCodeOfConductEvent.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.java; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.bedrock.SessionAcceptCodeOfConductEvent; +import org.geysermc.geyser.api.event.connection.ConnectionEvent; + +/** + * Fired when the Java server sends a code of conduct during the configuration phase. + * API users can listen to this event and tell Geyser the player has accepted the code of conduct before, which will result in the + * code of conduct not being shown to the player. + * + *

Java clients cache this locally, but bedrock clients don't. Normally Geyser uses a simple JSON file to implement this, + * but an alternative solution may be preferred when using multiple Geyser instances. Such a solution can be implemented through this event and {@link SessionAcceptCodeOfConductEvent}.

+ * + * @see SessionAcceptCodeOfConductEvent + */ +public final class ServerCodeOfConductEvent extends ConnectionEvent { + private final String codeOfConduct; + private boolean hasAccepted = false; + + public ServerCodeOfConductEvent(@NonNull GeyserConnection connection, String codeOfConduct) { + super(connection); + this.codeOfConduct = codeOfConduct; + } + + /** + * @return the code of conduct sent by the server + */ + public String codeOfConduct() { + return codeOfConduct; + } + + /** + * @return {@code true} if Geyser should not show the code of conduct to the player, because they have already accepted it + */ + public boolean hasAccepted() { + return hasAccepted; + } + + /** + * Sets {@link ServerCodeOfConductEvent#hasAccepted()} to {@code true}. + */ + public void accept() { + this.hasAccepted = true; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CopperBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CopperBlockEntityTranslator.java index e1361ebf1..bc6a91eff 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CopperBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CopperBlockEntityTranslator.java @@ -39,10 +39,10 @@ public class CopperBlockEntityTranslator extends BlockEntityTranslator implement public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { // Copper golem poses are set through block states on Java and through NBT on bedrock bedrockNbt.putBoolean("isMovable", true) - .putInt("Pose", javaToCopperPose(blockState.getValue(Properties.COPPER_GOLEM_POSE))); + .putInt("Pose", translateCopperPose(blockState.getValue(Properties.COPPER_GOLEM_POSE))); } - private static int javaToCopperPose(String java) { + private static int translateCopperPose(String java) { return switch (java) { case "standing" -> 0; case "sitting" -> 1; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCodeOfConductTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCodeOfConductTranslator.java index 004b4b788..b75cc5acd 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCodeOfConductTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCodeOfConductTranslator.java @@ -56,7 +56,7 @@ public class JavaCodeOfConductTranslator extends PacketTranslator { if (response.asToggle()) { - CodeOfConductManager.getInstance().saveCodeOfConduct(session, codeOfConduct); + CodeOfConductManager.getInstance().saveCodeOfConductAccepted(session, codeOfConduct); } session.acceptCodeOfConduct(); }) diff --git a/core/src/main/java/org/geysermc/geyser/util/CodeOfConductManager.java b/core/src/main/java/org/geysermc/geyser/util/CodeOfConductManager.java index 3bc582387..d391d4102 100644 --- a/core/src/main/java/org/geysermc/geyser/util/CodeOfConductManager.java +++ b/core/src/main/java/org/geysermc/geyser/util/CodeOfConductManager.java @@ -31,6 +31,8 @@ import com.google.gson.JsonParser; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.event.bedrock.SessionAcceptCodeOfConductEvent; +import org.geysermc.geyser.api.event.java.ServerCodeOfConductEvent; import org.geysermc.geyser.session.GeyserSession; import java.io.FileReader; @@ -46,6 +48,7 @@ public class CodeOfConductManager { private static CodeOfConductManager loaded = null; private final Object2IntMap playerAcceptedCodeOfConducts = new Object2IntOpenHashMap<>(); + private boolean dirty = false; private CodeOfConductManager() { Path savePath = getSavePath(); @@ -69,22 +72,32 @@ public class CodeOfConductManager { } public boolean hasAcceptedCodeOfConduct(GeyserSession session, String codeOfConduct) { - return playerAcceptedCodeOfConducts.getInt(session.xuid()) == codeOfConduct.hashCode(); + ServerCodeOfConductEvent event = new ServerCodeOfConductEvent(session, codeOfConduct); + session.getGeyser().getEventBus().fire(event); + return event.hasAccepted() || playerAcceptedCodeOfConducts.getInt(session.xuid()) == codeOfConduct.hashCode(); } - public void saveCodeOfConduct(GeyserSession session, String codeOfConduct) { - playerAcceptedCodeOfConducts.put(session.xuid(), codeOfConduct.hashCode()); + public void saveCodeOfConductAccepted(GeyserSession session, String codeOfConduct) { + SessionAcceptCodeOfConductEvent event = new SessionAcceptCodeOfConductEvent(session, codeOfConduct); + session.getGeyser().getEventBus().fire(event); + if (!event.wasSavedElsewhere()) { + playerAcceptedCodeOfConducts.put(session.xuid(), codeOfConduct.hashCode()); + dirty = true; + } } public void save() { - GeyserImpl.getInstance().getLogger().debug("Saving codeofconducts.json"); + if (dirty) { + GeyserImpl.getInstance().getLogger().debug("Saving codeofconducts.json"); - JsonObject saved = new JsonObject(); - playerAcceptedCodeOfConducts.forEach(saved::addProperty); - try { - Files.writeString(getSavePath(), saved.toString()); - } catch (IOException exception) { - GeyserImpl.getInstance().getLogger().error("Failed to write code of conduct cache!", exception); + JsonObject saved = new JsonObject(); + playerAcceptedCodeOfConducts.forEach(saved::addProperty); + try { + Files.writeString(getSavePath(), saved.toString()); + dirty = false; + } catch (IOException exception) { + GeyserImpl.getInstance().getLogger().error("Failed to write code of conduct cache!", exception); + } } }