diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java index c99c180ab13..a14afa0ad3f 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java @@ -221,6 +221,11 @@ public Path getFloodgateKeyPath() { return floodgateKeyPath; } + @Override + public boolean isServerControlledHardcore() { + return true; + } + @Nullable @Override public InputStream getResourceOrNull(String resource) { diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index ce1894538e8..713da381e9d 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -471,6 +471,11 @@ public Path getFloodgateKeyPath() { return FloodgateKeyLoader.getKeyPath(geyserConfig, floodgateDataFolder, geyserDataFolder, geyserLogger); } + @Override + public boolean isServerControlledHardcore() { + return true; + } + @Override public MetricsPlatform createMetricsPlatform() { return new SpigotMetrics(this); diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index a5b96fcccb5..d69f553c2ff 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -42,7 +42,7 @@ public final class Constants { public static final String MINECRAFT_SKIN_SERVER_URL = "https://textures.minecraft.net/texture/"; - public static final int CONFIG_VERSION = 7; + public static final int CONFIG_VERSION = 8; public static final int BSTATS_ID = 5273; diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index aa0d651b9c9..87f78e53e55 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -212,6 +212,14 @@ default Path getLogsPath() { */ Path getFloodgateKeyPath(); + /** + * Returns whether or not the hardcore value should be taken from the server, or the Geyser config. + * @return {@code true} when it should pull from the server, {@code false} if it should pull from the config. + */ + default boolean isServerControlledHardcore() { + return false; + } + @Nullable default MetricsPlatform createMetricsPlatform() { return new ProvidedMetricsPlatform(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/ConfigMigrations.java b/core/src/main/java/org/geysermc/geyser/configuration/ConfigMigrations.java index 56c1e217769..47e362310bb 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/ConfigMigrations.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/ConfigMigrations.java @@ -187,6 +187,9 @@ public class ConfigMigrations { .addVersion(7, ConfigurationTransformation.builder() .addAction(path("gameplay", "show-cooldown"), rename(new Object[] { "gameplay", "cooldown-type" })) .build()) + .addVersion(8, ConfigurationTransformation.builder() + .addAction(path("gameplay", "enable-integrated-pack"), renameAndMove("gameplay", "integrated-pack", "enabled")) + .build()) .build(); static TransformAction renameAndMove(String... newPath) { diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java index cebdc5dd468..de762ec1efd 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfig.java @@ -249,6 +249,21 @@ interface MotdConfig { int pingPassthroughInterval(); } + @ConfigSerializable + interface IntegratedPackConfig { + @Comment(""" + Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. + If enabled, force-resource-packs will be enabled.""") + @DefaultBoolean(true) + boolean enabled(); + + @Comment(""" + Whether or not to show non-vanilla inventories on bedrock, for example, a 2 row chest. This option is ignored when the IntegratedPack is disabled. + """) + @DefaultBoolean(true) + boolean nonVanillaInventories(); + } + @ConfigSerializable interface GameplayConfig { @@ -292,6 +307,13 @@ default CooldownUtils.CooldownType cooldownType() { @DefaultBoolean(true) boolean emotesEnabled(); + @Comment(""" + Whether to show the world as in hardcore mode to Bedrock Edition players. + """) + @DefaultBoolean(false) + @ExcludePlatform(platforms = {"Spigot", "Fabric", "NeoForge"}) + boolean hardcoreMode(); + @Comment(""" Whether to remove legacy text formatting codes sent by Bedrock players. Unlike on Java Edition, typing section signs for legacy color codes is possible on Bedrock Edition. @@ -325,10 +347,9 @@ default CooldownUtils.CooldownType cooldownType() { boolean forceResourcePacks(); @Comment(""" - Whether to automatically serve a resource pack that is required for some Geyser features to all connecting Bedrock players. - If enabled, force-resource-packs will be enabled.""") - @DefaultBoolean(true) - boolean enableIntegratedPack(); + Options related to the GeyserIntegratedPack. + """) + IntegratedPackConfig integratedPack(); @Comment(""" Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Container.java index 74480929d78..bb28d8fb12c 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/Container.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/Container.java @@ -93,7 +93,7 @@ public void setUsingRealBlock(boolean usingRealBlock, Block block) { @Override protected String getPrefixedTitle(GeyserSession session, String title) { - if (session.integratedPackActive()) { + if (session.integratedPackActive() && session.getGeyser().config().gameplay().integratedPack().nonVanillaInventories()) { return getIntegratedPackTitlePrefix(this.containerType) + title; } return title; diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 708d91cf3a4..464cac83058 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -434,6 +434,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private GameMode gameMode = GameMode.SURVIVAL; + @Setter + private boolean hardcore = false; + /** * Keeps track of the world name for respawning. */ @@ -1795,6 +1798,7 @@ private void startGame() { startGamePacket.setUniqueEntityId(playerEntity.geyserId()); startGamePacket.setRuntimeEntityId(playerEntity.geyserId()); startGamePacket.setPlayerGameType(EntityUtils.toBedrockGamemode(gameMode)); + startGamePacket.setHardcore(hardcore); startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0)); startGamePacket.setRotation(Vector2f.from(1, 1)); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 622b2d23b65..d16163b75c2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -27,6 +27,7 @@ import net.kyori.adventure.key.Key; import org.cloudburstmc.protocol.bedrock.data.GameRuleData; +import org.cloudburstmc.protocol.bedrock.packet.DeathInfoPacket; import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket; import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket; import org.geysermc.erosion.Constants; @@ -92,6 +93,11 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { session.setWorldName(spawnInfo.getWorldName()); session.setLevels(Arrays.stream(packet.getWorldNames()).map(Key::asString).toArray(String[]::new)); session.setGameMode(spawnInfo.getGameMode()); + if (session.getGeyser().getBootstrap().isServerControlledHardcore()) { + session.setHardcore(packet.isHardcore()); + } else { + session.setHardcore(session.getGeyser().config().gameplay().hardcoreMode()); + } boolean needsSpawnPacket = !session.isSentSpawnPacket(); if (needsSpawnPacket) { @@ -99,6 +105,13 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { DimensionUtils.setBedrockDimension(session, newDimension.bedrockId()); session.connect(); + // This is to ensure that you don't get stuck without a respawn screen in hardcore, has no effect when hardcore mode is off + if (!entity.isAlive()) { + DeathInfoPacket deathInfoPacket = new DeathInfoPacket(); + deathInfoPacket.setCauseAttackName(""); + session.sendUpstreamPacket(deathInfoPacket); + } + // It is now safe to send these packets session.getUpstream().sendPostStartGamePackets(); } else { diff --git a/core/src/main/java/org/geysermc/geyser/util/GeyserIntegratedPackUtil.java b/core/src/main/java/org/geysermc/geyser/util/GeyserIntegratedPackUtil.java index b89a2d48a2d..72cc456edee 100644 --- a/core/src/main/java/org/geysermc/geyser/util/GeyserIntegratedPackUtil.java +++ b/core/src/main/java/org/geysermc/geyser/util/GeyserIntegratedPackUtil.java @@ -53,10 +53,10 @@ public interface GeyserIntegratedPackUtil { UUID OPTIONAL_PACK_UUID = UUID.fromString("e5f5c938-a701-11eb-b2a3-047d7bb283ba"); UUID INTEGRATED_PACK_UUID = UUID.fromString("2254393d-8430-45b0-838a-bd397828c765"); AtomicReference INTEGRATED_PACK_VERSION = new AtomicReference<>(); - AtomicBoolean PACK_ENABLED = new AtomicBoolean(GeyserImpl.getInstance().config().gameplay().enableIntegratedPack()); + AtomicBoolean PACK_ENABLED = new AtomicBoolean(GeyserImpl.getInstance().config().gameplay().integratedPack().enabled()); default void registerGeyserPack(GeyserDefineResourcePacksEventImpl event) { - if (!GeyserImpl.getInstance().config().gameplay().enableIntegratedPack()) { + if (!GeyserImpl.getInstance().config().gameplay().integratedPack().enabled()) { return; } @@ -152,6 +152,6 @@ default String warnMessageLocation(PackCodec codec) { boolean integratedPackRegistered(); default boolean isIntegratedPackActive() { - return GeyserImpl.getInstance().config().gameplay().enableIntegratedPack() && integratedPackRegistered(); + return GeyserImpl.getInstance().config().gameplay().integratedPack().enabled() && integratedPackRegistered(); } }