3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-10-04 00:41:13 +02:00

Merge branch 'master' into rp

Dieser Commit ist enthalten in:
chris 2024-06-25 14:19:01 +02:00 committet von GitHub
Commit 3638d4dd9e
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
25 geänderte Dateien mit 307 neuen und 108 gelöschten Zeilen

14
.editorconfig Normale Datei
Datei anzeigen

@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
tab_width = 4
max_line_length = off
[*.java]
ij_java_class_count_to_use_import_on_demand = 9999
ij_java_doc_align_exception_comments = false
ij_java_doc_align_param_comments = false

Datei anzeigen

@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.0 and Minecraft Java 1.21 ### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.1 and Minecraft Java 1.21
## Setting Up ## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.

Datei anzeigen

@ -1,6 +1,6 @@
import net.kyori.blossom.BlossomExtension
plugins { plugins {
// Allow blossom to mark sources root of templates
idea
alias(libs.plugins.blossom) alias(libs.plugins.blossom)
id("geyser.publish-conventions") id("geyser.publish-conventions")
} }
@ -75,7 +75,7 @@ tasks.processResources {
expand( expand(
"branch" to info.branch, "branch" to info.branch,
"buildNumber" to info.buildNumber, "buildNumber" to info.buildNumber,
"projectVersion" to project.version, "projectVersion" to info.version,
"commit" to info.commit, "commit" to info.commit,
"commitAbbrev" to info.commitAbbrev, "commitAbbrev" to info.commitAbbrev,
"commitMessage" to info.commitMessage, "commitMessage" to info.commitMessage,
@ -84,21 +84,30 @@ tasks.processResources {
} }
} }
configure<BlossomExtension> { sourceSets {
val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java" main {
val info = GitInfo() blossom {
val info = GitInfo()
replaceToken("\${version}", "${project.version} (${info.gitVersion})", mainFile) javaSources {
replaceToken("\${gitVersion}", info.gitVersion, mainFile) property("version", "${info.version} (${info.gitVersion})")
replaceToken("\${buildNumber}", info.buildNumber, mainFile) property("gitVersion", info.gitVersion)
replaceToken("\${branch}", info.branch, mainFile) property("buildNumber", info.buildNumber.toString())
replaceToken("\${commit}", info.commit, mainFile) property("branch", info.branch)
replaceToken("\${repository}", info.repository, mainFile) property("commit", info.commit)
property("repository", info.repository)
property("devVersion", info.isDev.toString())
}
}
}
} }
fun Project.buildNumber(): Int = fun buildNumber(): Int =
(System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1 (System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1
fun isDevBuild(branch: String, repository: String): Boolean {
return branch != "master" || repository.equals("https://github.com/GeyserMC/Geyser", ignoreCase = true).not()
}
inner class GitInfo { inner class GitInfo {
val branch: String val branch: String
val commit: String val commit: String
@ -111,22 +120,25 @@ inner class GitInfo {
val commitMessage: String val commitMessage: String
val repository: String val repository: String
val isDev: Boolean
init { init {
// On Jenkins, a detached head is checked out, so indra cannot determine the branch. branch = indraGit.branchName() ?: "DEV"
// Fortunately, this environment variable is available.
branch = indraGit.branchName() ?: System.getenv("BRANCH_NAME") ?: "DEV"
val commit = indraGit.commit() val commit = indraGit.commit()
this.commit = commit?.name ?: "0".repeat(40) this.commit = commit?.name ?: "0".repeat(40)
commitAbbrev = commit?.name?.substring(0, 7) ?: "0".repeat(7) commitAbbrev = commit?.name?.substring(0, 7) ?: "0".repeat(7)
gitVersion = "git-${branch}-${commitAbbrev}" gitVersion = "git-${branch}-${commitAbbrev}"
version = "${project.version} ($gitVersion)"
buildNumber = buildNumber()
val git = indraGit.git() val git = indraGit.git()
commitMessage = git?.commit()?.message ?: "" commitMessage = git?.commit()?.message ?: ""
repository = git?.repository?.config?.getString("remote", "origin", "url") ?: "" repository = git?.repository?.config?.getString("remote", "origin", "url") ?: ""
buildNumber = buildNumber()
isDev = isDevBuild(branch, repository)
val projectVersion = if (isDev) project.version else project.version.toString().replace("SNAPSHOT", "b${buildNumber}")
version = "$projectVersion ($gitVersion)"
} }
} }

Datei anzeigen

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2022 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;
// The constants are replaced before compilation
public class BuildData {
public static final String GIT_VERSION = "{{ gitVersion }}";
public static final String VERSION = "{{ version }}";
public static final String BUILD_NUMBER = "{{ buildNumber }}";
public static final String BRANCH = "{{ branch }}";
public static final String COMMIT = "{{ commit }}";
public static final String REPOSITORY = "{{ repository }}";
private static final String DEV = "{{ devVersion }}";
public static boolean isDevBuild() {
return Boolean.parseBoolean(DEV);
}
}

Datei anzeigen

@ -116,13 +116,14 @@ public class GeyserImpl implements GeyserApi {
.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
public static final String NAME = "Geyser"; public static final String NAME = "Geyser";
public static final String GIT_VERSION = "${gitVersion}"; public static final String GIT_VERSION = BuildData.GIT_VERSION;
public static final String VERSION = "${version}"; public static final String VERSION = BuildData.VERSION;
public static final String BUILD_NUMBER = "${buildNumber}"; public static final String BUILD_NUMBER = BuildData.BUILD_NUMBER;
public static final String BRANCH = "${branch}"; public static final String BRANCH = BuildData.BRANCH;
public static final String COMMIT = "${commit}"; public static final String COMMIT = BuildData.COMMIT;
public static final String REPOSITORY = "${repository}"; public static final String REPOSITORY = BuildData.REPOSITORY;
public static final boolean IS_DEV = BuildData.isDevBuild();
/** /**
* Oauth client ID for Microsoft authentication * Oauth client ID for Microsoft authentication
@ -208,6 +209,12 @@ public class GeyserImpl implements GeyserApi {
logger.info(""); logger.info("");
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION)); logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION));
logger.info(""); logger.info("");
if (IS_DEV) {
// TODO cloud use language string
//logger.info(GeyserLocale.getLocaleStringLog("geyser.core.dev_build", "https://discord.gg/geysermc"));
logger.info("You are running a development build of Geyser! Please report any bugs you find on our Discord server: %s".formatted("https://discord.gg/geysermc"));
logger.info("");
}
logger.info("******************************************"); logger.info("******************************************");
/* Initialize registries */ /* Initialize registries */
@ -685,6 +692,7 @@ public class GeyserImpl implements GeyserApi {
* *
* @return true if the version number is not 'DEV'. * @return true if the version number is not 'DEV'.
*/ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isProductionEnvironment() { public boolean isProductionEnvironment() {
// First is if Blossom runs, second is if Blossom doesn't run // First is if Blossom runs, second is if Blossom doesn't run
//noinspection ConstantConditions,MismatchedStringCase - changes in production //noinspection ConstantConditions,MismatchedStringCase - changes in production

Datei anzeigen

@ -25,8 +25,8 @@
package org.geysermc.geyser.command.defaults; package org.geysermc.geyser.command.defaults;
import com.fasterxml.jackson.databind.JsonNode;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommand;
@ -37,8 +37,7 @@ import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.WebUtils; import org.geysermc.geyser.util.WebUtils;
import java.net.URLEncoder; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
public class VersionCommand extends GeyserCommand { public class VersionCommand extends GeyserCommand {
@ -72,27 +71,36 @@ public class VersionCommand extends GeyserCommand {
GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions)); GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions));
// Disable update checking in dev mode and for players in Geyser Standalone // Disable update checking in dev mode and for players in Geyser Standalone
if (GeyserImpl.getInstance().isProductionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { if (!GeyserImpl.getInstance().isProductionEnvironment() || (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale())); return;
try { }
String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" +
URLEncoder.encode(GeyserImpl.BRANCH, StandardCharsets.UTF_8) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); if (GeyserImpl.IS_DEV) {
if (buildXML.startsWith("<buildNumber>")) { // TODO cloud use language string
int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); sender.sendMessage("You are running a development build of Geyser! Please report any bugs you find on our Discord server: %s"
int buildNum = this.geyser.buildNumber(); .formatted("https://discord.gg/geysermc"));
if (latestBuildNum == buildNum) { //sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.core.dev_build", sender.locale(), "https://discord.gg/geysermc"));
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale())); return;
} else { }
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated",
sender.locale(), (latestBuildNum - buildNum), Constants.GEYSER_DOWNLOAD_LOCATION)); sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale()));
} try {
} else { int buildNumber = this.geyser.buildNumber();
throw new AssertionError("buildNumber missing"); JsonNode response = WebUtils.getJson("https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest");
} int latestBuildNumber = response.get("build").asInt();
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e); if (latestBuildNumber == buildNumber) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale())); sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale()));
return;
} }
sender.sendMessage(GeyserLocale.getPlayerLocaleString(
"geyser.commands.version.outdated",
sender.locale(), (latestBuildNumber - buildNumber), "https://geysermc.org/download"
));
} catch (IOException e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e);
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale()));
} }
} }

Datei anzeigen

@ -47,6 +47,7 @@ import java.util.function.BiConsumer;
* metadata translators needed to translate the properties sent from the server. The translators are structured in such * metadata translators needed to translate the properties sent from the server. The translators are structured in such
* a way that inserting a new one (for example in version updates) is convenient. * a way that inserting a new one (for example in version updates) is convenient.
* *
* @param identifier the Bedrock identifier of this entity
* @param <T> the entity type this definition represents * @param <T> the entity type this definition represents
*/ */
public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, EntityType entityType, String identifier, public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, EntityType entityType, String identifier,

Datei anzeigen

@ -86,7 +86,11 @@ public class PaintingEntity extends Entity {
private Vector3f fixOffset(PaintingType paintingName) { private Vector3f fixOffset(PaintingType paintingName) {
Vector3f position = super.position; Vector3f position = super.position;
position = position.add(0.5, 0.5, 0.5); // ViaVersion already adds the offset for us on older versions,
// so no need to do it then otherwise it will be spaced
if (session.isEmulatePost1_18Logic()) {
position = position.add(0.5, 0.5, 0.5);
}
double widthOffset = paintingName.getWidth() > 1 && paintingName.getWidth() != 3 ? 0.5 : 0; double widthOffset = paintingName.getWidth() > 1 && paintingName.getWidth() != 3 ? 0.5 : 0;
double heightOffset = paintingName.getHeight() > 1 && paintingName.getHeight() != 3 ? 0.5 : 0; double heightOffset = paintingName.getHeight() > 1 && paintingName.getHeight() != 3 ? 0.5 : 0;

Datei anzeigen

@ -27,15 +27,18 @@ package org.geysermc.geyser.entity.type.player;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.AttributeData; import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.AttributeUtils;
import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.DimensionUtils;
@ -69,6 +72,15 @@ public class SessionPlayerEntity extends PlayerEntity {
private int lastAirSupply = getMaxAir(); private int lastAirSupply = getMaxAir();
/**
* Determines if our position is currently out-of-sync with the Java server
* due to our workaround for the void floor
* <p>
* Must be reset when dying, switching worlds, or being teleported out of the void
*/
@Getter @Setter
private boolean voidPositionDesynched;
public SessionPlayerEntity(GeyserSession session) { public SessionPlayerEntity(GeyserSession session) {
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null); super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
@ -87,10 +99,25 @@ public class SessionPlayerEntity extends PlayerEntity {
@Override @Override
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
if (voidPositionDesynched) {
if (!isBelowVoidFloor()) {
voidPositionDesynched = false; // No need to fix our offset; we've been moved
}
}
super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset())); session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset()));
} }
@Override
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
if (voidPositionDesynched) {
if (!isBelowVoidFloor()) {
voidPositionDesynched = false; // No need to fix our offset; we've been moved
}
}
super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported);
}
@Override @Override
public void setPosition(Vector3f position) { public void setPosition(Vector3f position) {
if (valid) { // Don't update during session init if (valid) { // Don't update during session init
@ -225,6 +252,9 @@ public class SessionPlayerEntity extends PlayerEntity {
} else { } else {
dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false); dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false);
} }
// We're either respawning or switching worlds, either way, we are no longer desynched
this.setVoidPositionDesynched(false);
} }
@Override @Override
@ -276,4 +306,48 @@ public class SessionPlayerEntity extends PlayerEntity {
public void resetAir() { public void resetAir() {
this.setAirSupply(getMaxAir()); this.setAirSupply(getMaxAir());
} }
private boolean isBelowVoidFloor() {
return position.getY() < voidFloorPosition();
}
public int voidFloorPosition() {
// The void floor is offset about 40 blocks below the bottom of the world
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
return bedrockDimension.minY() - 40;
}
/**
* This method handles teleporting the player below or above the Bedrock void floor.
* The Java server should never see this desync as we adjust the position that we send to it
*
* @param up in which direction to teleport - true to resync our position, or false to be
* teleported below the void floor.
*/
public void teleportVoidFloorFix(boolean up) {
// Safety to avoid double teleports
if ((voidPositionDesynched && !up) || (!voidPositionDesynched && up)) {
return;
}
// Work around there being a floor at the bottom of the world and teleport the player below it
// Moving from below to above the void floor works fine
Vector3f newPosition = this.getPosition();
if (up) {
newPosition = newPosition.up(4f);
voidPositionDesynched = false;
} else {
newPosition = newPosition.down(4f);
voidPositionDesynched = true;
}
this.setPositionManual(newPosition);
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(geyserId);
movePlayerPacket.setPosition(newPosition);
movePlayerPacket.setRotation(getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
session.sendUpstreamPacketImmediately(movePlayerPacket);
}
} }

Datei anzeigen

@ -152,7 +152,7 @@ public class SkullPlayerEntity extends PlayerEntity {
case EAST -> x -= 0.24f; case EAST -> x -= 0.24f;
} }
} else { } else {
rotation = (180f + (blockState.getValue(Properties.ROTATION_16) * 22.5f)) % 360; rotation = (180f + blockState.getValue(Properties.ROTATION_16, 0) * 22.5f) % 360;
} }
moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true); moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true);

Datei anzeigen

@ -51,6 +51,7 @@ public class FilledMapItem extends MapItem {
switch (mapColor) { switch (mapColor) {
case 3830373 -> builder.damage(3); // Ocean Monument case 3830373 -> builder.damage(3); // Ocean Monument
case 5393476 -> builder.damage(4); // Woodland explorer case 5393476 -> builder.damage(4); // Woodland explorer
case 12741452 -> builder.damage(14); // Trial Chamber
} }
} }
} }

Datei anzeigen

@ -63,7 +63,8 @@ public enum BedrockMapIcon {
ICON_SNOWY_VILLAGE(MapIconType.SNOWY_VILLAGE, 20), ICON_SNOWY_VILLAGE(MapIconType.SNOWY_VILLAGE, 20),
ICON_TAIGA_VILLAGE(MapIconType.TAIGA_VILLAGE, 21), ICON_TAIGA_VILLAGE(MapIconType.TAIGA_VILLAGE, 21),
ICON_JUNGLE_TEMPLE(MapIconType.JUNGLE_TEMPLE, 22), ICON_JUNGLE_TEMPLE(MapIconType.JUNGLE_TEMPLE, 22),
ICON_SWAMP_HUT(MapIconType.SWAMP_HUT, 23); ICON_SWAMP_HUT(MapIconType.SWAMP_HUT, 23),
ICON_TRIAL_CHAMBERS(MapIconType.TRIAL_CHAMBERS, 24);
private static final BedrockMapIcon[] VALUES = values(); private static final BedrockMapIcon[] VALUES = values();

Datei anzeigen

@ -25,6 +25,7 @@
package org.geysermc.geyser.level.block.type; package org.geysermc.geyser.level.block.type;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.level.block.property.Property; import org.geysermc.geyser.level.block.property.Property;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
@ -184,7 +185,14 @@ public final class BlockState {
return builder.toString(); return builder.toString();
} }
/**
* Null-safe method that looks up a Java block state ID in the BLOCK_STATES registry, and defaults to air if not found.
*
* @param javaId the Java block state ID to look up.
* @return the corresponding block state, or air if the given ID wasn't registered and returned null.
*/
@NonNull
public static BlockState of(int javaId) { public static BlockState of(int javaId) {
return BlockRegistries.BLOCK_STATES.get(javaId); return BlockRegistries.BLOCK_STATES.getOrDefault(javaId, BlockRegistries.BLOCK_STATES.get(Block.JAVA_AIR_ID));
} }
} }

Datei anzeigen

@ -48,7 +48,7 @@ public final class GameProtocol {
* release of the game that Geyser supports. * release of the game that Geyser supports.
*/ */
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder() public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder()
.minecraftVersion("1.21.0") .minecraftVersion("1.21.1")
.build()); .build());
/** /**
@ -67,7 +67,7 @@ public final class GameProtocol {
.minecraftVersion("1.20.80/1.20.81") .minecraftVersion("1.20.80/1.20.81")
.build())); .build()));
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(DEFAULT_BEDROCK_CODEC.toBuilder() SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(DEFAULT_BEDROCK_CODEC.toBuilder()
.minecraftVersion("1.21.0") .minecraftVersion("1.21.0/1.20.1")
.build())); .build()));
} }

Datei anzeigen

@ -996,7 +996,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Override @Override
public void disconnected(DisconnectedEvent event) { public void disconnected(DisconnectedEvent event) {
loggingIn = false; loggingIn = false;
loggedIn = false;
String disconnectMessage; String disconnectMessage;
Throwable cause = event.getCause(); Throwable cause = event.getCause();
@ -1036,13 +1035,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
} else { } else {
GeyserImpl.getInstance().getLogger().error("An exception occurred: ", cause); GeyserImpl.getInstance().getLogger().error("An exception occurred: ", cause);
} }
// GeyserSession is disconnected via session.disconnect() called indirectly be the server
// This only needs to be "initiated" here when there is an exception, hence the cause clause
GeyserSession.this.disconnect(disconnectMessage);
if (geyser.getConfig().isDebugMode()) { if (geyser.getConfig().isDebugMode()) {
cause.printStackTrace(); cause.printStackTrace();
} }
} }
if ((!GeyserSession.this.closed && GeyserSession.this.loggedIn) || cause != null) {
// GeyserSession is disconnected via session.disconnect() called indirectly be the server
// This needs to be "initiated" here when there is an exception, but also when the Netty connection
// is closed without a disconnect packet - in this case, closed will still be false, but loggedIn
// will also be true as GeyserSession#disconnect will not have been called.
GeyserSession.this.disconnect(disconnectMessage);
}
loggedIn = false;
} }
@Override @Override

Datei anzeigen

@ -309,6 +309,15 @@ public class SkinManager {
return null; return null;
} }
if (DEFAULT_FLOODGATE_STEVE.equals(skinUrl)) {
// https://github.com/GeyserMC/Floodgate/commit/00b8b1b6364116ff4bc9b00e2015ce35bae8abb1 ensures that
// Bedrock players on online-mode servers will always have a textures property. However, this skin is
// also sent our way, and isn't overwritten. It's very likely that this skin is *only* a placeholder,
// and no one should ever be using it outside of Floodgate, and therefore no one wants to see this
// specific Steve skin.
return null;
}
boolean isAlex = skinTexture.has("metadata"); boolean isAlex = skinTexture.has("metadata");
String capeUrl = null; String capeUrl = null;
@ -322,5 +331,7 @@ public class SkinManager {
return new GameProfileData(skinUrl, capeUrl, isAlex); return new GameProfileData(skinUrl, capeUrl, isAlex);
} }
private static final String DEFAULT_FLOODGATE_STEVE = "https://textures.minecraft.net/texture/31f477eb1a7beee631c2ca64d06f8f68fa93a3386d04452ab27f43acdf1b60cb";
} }
} }

Datei anzeigen

@ -418,13 +418,15 @@ public final class ItemTranslator {
if (components != null) { if (components != null) {
// ItemStack#getHoverName as of 1.20.5 // ItemStack#getHoverName as of 1.20.5
Component customName = components.get(DataComponentType.CUSTOM_NAME); Component customName = components.get(DataComponentType.CUSTOM_NAME);
if (customName == null) {
customName = components.get(DataComponentType.ITEM_NAME);
}
if (customName != null) { if (customName != null) {
// Get the translated name and prefix it with a reset char
return MessageTranslator.convertMessage(customName, session.locale()); return MessageTranslator.convertMessage(customName, session.locale());
} }
customName = components.get(DataComponentType.ITEM_NAME);
if (customName != null) {
// Get the translated name and prefix it with a reset char to prevent italics - matches Java Edition
// behavior as of 1.21
return ChatColor.RESET + ChatColor.ESCAPE + translationColor + MessageTranslator.convertMessage(customName, session.locale());
}
} }
if (mapping.hasTranslation()) { if (mapping.hasTranslation()) {

Datei anzeigen

@ -107,7 +107,7 @@ public class SpawnerBlockEntityTranslator extends BlockEntityTranslator {
bedrockNbt.put("isMovable", (byte) 1); bedrockNbt.put("isMovable", (byte) 1);
} }
static void translateSpawnData(@NonNull NbtMapBuilder builder, @Nullable NbtMap spawnData) { private static void translateSpawnData(@NonNull NbtMapBuilder builder, @Nullable NbtMap spawnData) {
if (spawnData == null) { if (spawnData == null) {
return; return;
} }

Datei anzeigen

@ -27,22 +27,31 @@ package org.geysermc.geyser.translator.level.block.entity;
import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.nbt.NbtMapBuilder;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType; import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
@BlockEntity(type = BlockEntityType.TRIAL_SPAWNER) @BlockEntity(type = BlockEntityType.TRIAL_SPAWNER)
public class TrialSpawnerBlockEntityTranslator extends BlockEntityTranslator { public class TrialSpawnerBlockEntityTranslator extends BlockEntityTranslator {
// Note that it would appear block entity updates don't include the NBT, but we do need it on chunk load.
@Override @Override
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) { public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
if (javaNbt == null) { if (javaNbt == null) {
return; return;
} }
// trial spawners have "spawn_data" instead of "SpawnData" NbtMap entityData = javaNbt.getCompound("spawn_data").getCompound("entity");
SpawnerBlockEntityTranslator.translateSpawnData(bedrockNbt, javaNbt.getCompound("spawn_data", null)); if (entityData.isEmpty()) {
return;
// Because trial spawners don't exist on bedrock yet }
bedrockNbt.put("id", "MobSpawner"); NbtMapBuilder spawnData = NbtMap.builder();
EntityDefinition<?> definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(entityData.getString("id"));
if (definition != null) {
spawnData.putString("TypeId", definition.identifier());
}
spawnData.putInt("Weight", entityData.getInt("Size", 1)); // ??? presumably since these are the only other two extra attributes
bedrockNbt.putCompound("spawn_data", spawnData.build());
} }
} }

Datei anzeigen

@ -160,7 +160,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
LevelEventPacket startBreak = new LevelEventPacket(); LevelEventPacket startBreak = new LevelEventPacket();
startBreak.setType(LevelEvent.BLOCK_START_BREAK); startBreak.setType(LevelEvent.BLOCK_START_BREAK);
startBreak.setPosition(vector.toFloat()); startBreak.setPosition(vector.toFloat());
double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.BLOCK_STATES.getOrDefault(blockState, BlockState.of(Block.JAVA_AIR_ID)).block()) * 20; double breakTime = BlockUtils.getSessionBreakTime(session, BlockState.of(blockState).block()) * 20;
// If the block is custom or the breaking item is custom, we must keep track of break time ourselves // If the block is custom or the breaking item is custom, we must keep track of break time ourselves
GeyserItemStack item = session.getPlayerInventory().getItemInHand(); GeyserItemStack item = session.getPlayerInventory().getItemInHand();
@ -211,7 +211,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
LevelEventPacket updateBreak = new LevelEventPacket(); LevelEventPacket updateBreak = new LevelEventPacket();
updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK); updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK);
updateBreak.setPosition(vectorFloat); updateBreak.setPosition(vectorFloat);
double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.BLOCK_STATES.getOrDefault(breakingBlock, BlockState.of(Block.JAVA_AIR_ID)).block()) * 20; double breakTime = BlockUtils.getSessionBreakTime(session, BlockState.of(breakingBlock).block()) * 20;
// If the block is custom, we must keep track of when it should break ourselves // If the block is custom, we must keep track of when it should break ourselves

Datei anzeigen

@ -25,20 +25,19 @@
package org.geysermc.geyser.translator.protocol.bedrock.entity.player; package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerRotPacket;
import org.geysermc.mcprotocollib.network.packet.Packet;
import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3d;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.mcprotocollib.network.packet.Packet;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerRotPacket;
@Translator(packet = MovePlayerPacket.class) @Translator(packet = MovePlayerPacket.class)
public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPacket> { public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPacket> {
@ -93,29 +92,42 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT); Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT);
if (position != null) { // A null return value cancels the packet if (position != null) { // A null return value cancels the packet
boolean onGround = packet.isOnGround(); boolean onGround = packet.isOnGround();
boolean isBelowVoid = entity.isVoidPositionDesynched();
boolean teleportThroughVoidFloor; boolean teleportThroughVoidFloor, mustResyncPosition;
// Compare positions here for void floor fix below before the player's position variable is set to the packet position // Compare positions here for void floor fix below before the player's position variable is set to the packet position
if (entity.getPosition().getY() >= packet.getPosition().getY()) { if (entity.getPosition().getY() >= packet.getPosition().getY() && !isBelowVoid) {
int floorY = position.getFloorY(); int floorY = position.getFloorY();
// The void floor is offset about 40 blocks below the bottom of the world int voidFloorLocation = entity.voidFloorPosition();
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension(); teleportThroughVoidFloor = floorY <= (voidFloorLocation + 1) && floorY >= voidFloorLocation;
int voidFloorLocation = bedrockDimension.minY() - 40;
teleportThroughVoidFloor = floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation;
if (teleportThroughVoidFloor) {
// https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground.
onGround = false;
}
} else { } else {
teleportThroughVoidFloor = false; teleportThroughVoidFloor = false;
} }
if (teleportThroughVoidFloor || isBelowVoid) {
// https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground.
onGround = false;
}
if (isBelowVoid) {
int floorY = position.getFloorY();
int voidFloorLocation = entity.voidFloorPosition();
mustResyncPosition = floorY < voidFloorLocation && floorY >= voidFloorLocation - 1;
} else {
mustResyncPosition = false;
}
double yPosition = position.getY();
if (entity.isVoidPositionDesynched()) { // not using the cached variable on purpose
yPosition += 4; // We are de-synched since we had to teleport below the void floor.
}
Packet movePacket; Packet movePacket;
if (rotationChanged) { if (rotationChanged) {
// Send rotation updates as well // Send rotation updates as well
movePacket = new ServerboundMovePlayerPosRotPacket( movePacket = new ServerboundMovePlayerPosRotPacket(
onGround, onGround,
position.getX(), position.getY(), position.getZ(), position.getX(), yPosition, position.getZ(),
yaw, pitch yaw, pitch
); );
entity.setYaw(yaw); entity.setYaw(yaw);
@ -123,7 +135,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
entity.setHeadYaw(headYaw); entity.setHeadYaw(headYaw);
} else { } else {
// Rotation did not change; don't send an update with rotation // Rotation did not change; don't send an update with rotation
movePacket = new ServerboundMovePlayerPosPacket(onGround, position.getX(), position.getY(), position.getZ()); movePacket = new ServerboundMovePlayerPosPacket(onGround, position.getX(), yPosition, position.getZ());
} }
entity.setPositionManual(packet.getPosition()); entity.setPositionManual(packet.getPosition());
@ -133,16 +145,9 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
session.sendDownstreamGamePacket(movePacket); session.sendDownstreamGamePacket(movePacket);
if (teleportThroughVoidFloor) { if (teleportThroughVoidFloor) {
// Work around there being a floor at the bottom of the world and teleport the player below it entity.teleportVoidFloorFix(false);
// Moving from below to above the void floor works fine } else if (mustResyncPosition) {
entity.setPosition(entity.getPosition().sub(0, 4f, 0)); entity.teleportVoidFloorFix(true);
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
movePlayerPacket.setPosition(entity.getPosition());
movePlayerPacket.setRotation(entity.getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
session.sendUpstreamPacket(movePlayerPacket);
} }
session.getSkullCache().updateVisibleSkulls(); session.getSkullCache().updateVisibleSkulls();

Datei anzeigen

@ -27,9 +27,7 @@ package org.geysermc.geyser.translator.protocol.java.level;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent; import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -43,7 +41,7 @@ public class JavaBlockDestructionTranslator extends PacketTranslator<Clientbound
@Override @Override
public void translate(GeyserSession session, ClientboundBlockDestructionPacket packet) { public void translate(GeyserSession session, ClientboundBlockDestructionPacket packet) {
int state = session.getGeyser().getWorldManager().getBlockAt(session, packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()); int state = session.getGeyser().getWorldManager().getBlockAt(session, packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ());
int breakTime = (int) (65535 / Math.ceil(BlockUtils.getBreakTime(session, BlockRegistries.BLOCK_STATES.getOrDefault(state, BlockState.of(Block.JAVA_AIR_ID)).block(), ItemMapping.AIR, null, false) * 20)); int breakTime = (int) (65535 / Math.ceil(BlockUtils.getBreakTime(session, BlockState.of(state).block(), ItemMapping.AIR, null, false) * 20));
LevelEventPacket levelEventPacket = new LevelEventPacket(); LevelEventPacket levelEventPacket = new LevelEventPacket();
levelEventPacket.setPosition(packet.getPosition().toFloat()); levelEventPacket.setPosition(packet.getPosition().toFloat());
levelEventPacket.setType(LevelEvent.BLOCK_START_BREAK); levelEventPacket.setType(LevelEvent.BLOCK_START_BREAK);

Datei anzeigen

@ -33,9 +33,7 @@ import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror;
import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation; import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation;
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator; import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator;
@ -59,8 +57,7 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(type); BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(type);
// The Java block state is used in BlockEntityTranslator.translateTag() to make up for some inconsistencies // The Java block state is used in BlockEntityTranslator.translateTag() to make up for some inconsistencies
// between Java block states and Bedrock block entity data // between Java block states and Bedrock block entity data
BlockState blockState = BlockRegistries.BLOCK_STATES.getOrDefault(session.getGeyser().getWorldManager().getBlockAt(session, packet.getPosition()), BlockState blockState = session.getGeyser().getWorldManager().blockAt(session, packet.getPosition());
Blocks.AIR.defaultBlockState());
if (blockState.block().blockEntityType() != type) { if (blockState.block().blockEntityType() != type) {
return; return;

Datei anzeigen

@ -39,7 +39,6 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket;
import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.chunk.BlockStorage; import org.geysermc.geyser.level.chunk.BlockStorage;
@ -399,7 +398,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
// Get the Java block state ID from block entity position // Get the Java block state ID from block entity position
DataPalette section = javaChunks[(y >> 4) - yOffset]; DataPalette section = javaChunks[(y >> 4) - yOffset];
BlockState blockState = BlockRegistries.BLOCK_STATES.getOrDefault(section.get(x, y & 0xF, z), Blocks.AIR.defaultBlockState()); BlockState blockState = BlockState.of(section.get(x, y & 0xF, z));
// Note that, since 1.20.5, tags can be null, but Bedrock still needs a default tag to render the item // Note that, since 1.20.5, tags can be null, but Bedrock still needs a default tag to render the item
// Also, some properties - like banner base colors - are part of the tag and is processed here. // Also, some properties - like banner base colors - are part of the tag and is processed here.

Datei anzeigen

@ -42,7 +42,7 @@ architectury-plugin = "3.4-SNAPSHOT"
architectury-loom = "1.6-SNAPSHOT" architectury-loom = "1.6-SNAPSHOT"
minotaur = "2.8.7" minotaur = "2.8.7"
lombok = "8.4" lombok = "8.4"
blossom = "1.2.0" blossom = "2.1.0"
[libraries] [libraries]
base-api = { group = "org.geysermc.api", name = "base-api", version.ref = "base-api" } base-api = { group = "org.geysermc.api", name = "base-api", version.ref = "base-api" }