Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/extensions

This commit is contained in:
Camotoy 2022-02-10 09:17:27 -05:00
commit 354e87b747
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
77 changed files with 2001 additions and 1476 deletions

View file

@ -17,7 +17,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.17.30 - 1.17.41 + 1.18.0 - 1.18.2 and Minecraft Java 1.18/1.18.1. ### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18/1.18.1.
## Setting Up ## Setting Up
Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser. Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser.

View file

@ -6,9 +6,9 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId> <artifactId>geyser-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>ap</artifactId> <artifactId>ap</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</project> </project>

View file

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>api-parent</artifactId> <artifactId>api-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>api-parent</artifactId> <artifactId>api-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -35,7 +35,7 @@
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>base-api</artifactId> <artifactId>base-api</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId> <artifactId>geyser-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId> <artifactId>bootstrap-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>bootstrap-bungeecord</artifactId> <artifactId>bootstrap-bungeecord</artifactId>
@ -14,7 +14,7 @@
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- Used for better working with internals without reflection --> <!-- Used for better working with internals without reflection -->

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId> <artifactId>geyser-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>bootstrap-parent</artifactId> <artifactId>bootstrap-parent</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
@ -34,7 +34,7 @@
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>ap</artifactId> <artifactId>ap</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId> <artifactId>bootstrap-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>bootstrap-spigot</artifactId> <artifactId>bootstrap-spigot</artifactId>
@ -25,7 +25,7 @@
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -97,7 +97,7 @@ public class GeyserPistonListener implements Listener {
int dX = Math.abs(location.getBlockX() - player.getLocation().getBlockX()) >> 4; int dX = Math.abs(location.getBlockX() - player.getLocation().getBlockX()) >> 4;
int dZ = Math.abs(location.getBlockZ() - player.getLocation().getBlockZ()) >> 4; int dZ = Math.abs(location.getBlockZ() - player.getLocation().getBlockZ()) >> 4;
if ((dX * dX + dZ * dZ) > session.getRenderDistance() * session.getRenderDistance()) { if ((dX * dX + dZ * dZ) > session.getServerRenderDistance() * session.getServerRenderDistance()) {
// Ignore pistons outside the player's render distance // Ignore pistons outside the player's render distance
continue; continue;
} }

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId> <artifactId>bootstrap-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>bootstrap-sponge</artifactId> <artifactId>bootstrap-sponge</artifactId>
@ -14,7 +14,7 @@
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId> <artifactId>bootstrap-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>bootstrap-standalone</artifactId> <artifactId>bootstrap-standalone</artifactId>
@ -18,7 +18,7 @@
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId> <artifactId>bootstrap-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>bootstrap-velocity</artifactId> <artifactId>bootstrap-velocity</artifactId>
@ -14,7 +14,7 @@
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId> <artifactId>geyser-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>common</artifactId> <artifactId>common</artifactId>

View file

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId> <artifactId>geyser-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>core</artifactId> <artifactId>core</artifactId>
@ -20,19 +20,19 @@
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>ap</artifactId> <artifactId>ap</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>geyser-api</artifactId> <artifactId>geyser-api</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- Jackson JSON and YAML serialization --> <!-- Jackson JSON and YAML serialization -->
@ -119,8 +119,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.CloudburstMC.Protocol</groupId> <groupId>com.github.CloudburstMC.Protocol</groupId>
<artifactId>bedrock-v475</artifactId> <artifactId>bedrock-v486</artifactId>
<version>c22aa595</version> <version>0cd24c0</version>
<scope>compile</scope> <scope>compile</scope>
<exclusions> <exclusions>
<exclusion> <exclusion>

View file

@ -64,7 +64,7 @@ public class GeyserSession {
} }
public int getRenderDistance() { public int getRenderDistance() {
return this.handle.getRenderDistance(); return this.handle.getServerRenderDistance();
} }
public boolean isSentSpawnPacket() { public boolean isSentSpawnPacket() {

View file

@ -68,6 +68,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.SessionManager; import org.geysermc.geyser.session.SessionManager;
import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.session.auth.AuthType;
import org.geysermc.geyser.skin.FloodgateSkinUploader; import org.geysermc.geyser.skin.FloodgateSkinUploader;
import org.geysermc.geyser.skin.SkinProvider;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
@ -211,6 +212,8 @@ public class GeyserImpl implements GeyserApi {
ScoreboardUpdater.init(); ScoreboardUpdater.init();
SkinProvider.registerCacheImageTask(this);
ResourcePack.loadPacks(); ResourcePack.loadPacks();
this.extensionManager.enableExtensions(); this.extensionManager.enableExtensions();

View file

@ -25,6 +25,8 @@
package org.geysermc.geyser; package org.geysermc.geyser;
import javax.annotation.Nullable;
public interface GeyserLogger { public interface GeyserLogger {
/** /**
@ -78,6 +80,15 @@ public interface GeyserLogger {
*/ */
void debug(String message); void debug(String message);
/**
* Logs an object to console if debug mode is enabled
*
* @param object the object to log
*/
default void debug(@Nullable Object object) {
debug(String.valueOf(object));
}
/** /**
* Sets if the logger should print debug messages * Sets if the logger should print debug messages
* *

View file

@ -76,6 +76,10 @@ public interface GeyserConfiguration {
boolean isShowCoordinates(); boolean isShowCoordinates();
boolean isDisableBedrockScaffolding();
boolean isAlwaysQuickChangeArmor();
EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround(); EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround();
String getDefaultLocale(); String getDefaultLocale();

View file

@ -105,6 +105,12 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("show-coordinates") @JsonProperty("show-coordinates")
private boolean showCoordinates = true; private boolean showCoordinates = true;
@JsonProperty("disable-bedrock-scaffolding")
private boolean disableBedrockScaffolding = false;
@JsonProperty("always-quick-change-armor")
private boolean alwaysQuickChangeArmor = false;
@JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class) @JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class)
@JsonProperty("emote-offhand-workaround") @JsonProperty("emote-offhand-workaround")
private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED; private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED;

View file

@ -34,7 +34,7 @@ import java.util.Map;
/** /**
* A write-only wrapper for temporarily storing entity metadata that will be sent to Bedrock. * A write-only wrapper for temporarily storing entity metadata that will be sent to Bedrock.
*/ */
public class GeyserDirtyMetadata { public final class GeyserDirtyMetadata {
private final Map<EntityData, Object> metadata = new Object2ObjectLinkedOpenHashMap<>(); private final Map<EntityData, Object> metadata = new Object2ObjectLinkedOpenHashMap<>();
public void put(EntityData entityData, Object value) { public void put(EntityData entityData, Object value) {

View file

@ -37,12 +37,11 @@ import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
import lombok.Getter; import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
import org.geysermc.geyser.registry.type.ItemMapping;
import java.util.UUID; import java.util.UUID;
@ -85,10 +84,8 @@ public class ItemFrameEntity extends Entity {
.putInt("version", session.getBlockMappings().getBlockStateVersion()); .putInt("version", session.getBlockMappings().getBlockStateVersion());
NbtMapBuilder statesBuilder = NbtMap.builder() NbtMapBuilder statesBuilder = NbtMap.builder()
.putInt("facing_direction", direction.ordinal()) .putInt("facing_direction", direction.ordinal())
.putByte("item_frame_map_bit", (byte) 0); .putByte("item_frame_map_bit", (byte) 0)
if (session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) { .putByte("item_frame_photo_bit", (byte) 0);
statesBuilder.putByte("item_frame_photo_bit", (byte) 0);
}
blockBuilder.put("states", statesBuilder.build()); blockBuilder.put("states", statesBuilder.build());
bedrockRuntimeId = session.getBlockMappings().getItemFrame(blockBuilder.build()); bedrockRuntimeId = session.getBlockMappings().getItemFrame(blockBuilder.build());

View file

@ -136,7 +136,7 @@ public class ArmorStandEntity extends LivingEntity {
} }
isSmall = newIsSmall; isSmall = newIsSmall;
if (!isMarker) { if (!isMarker && !isInvisible) { // Addition for isInvisible check caused by https://github.com/GeyserMC/Geyser/issues/2780
toggleSmallStatus(); toggleSmallStatus();
} }
} }

View file

@ -382,15 +382,26 @@ public class PlayerEntity extends LivingEntity {
@Override @Override
protected void setDimensions(Pose pose) { protected void setDimensions(Pose pose) {
float height; float height;
float width;
switch (pose) { switch (pose) {
case SNEAKING -> height = SNEAKING_POSE_HEIGHT; case SNEAKING -> {
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> height = 0.6f; height = SNEAKING_POSE_HEIGHT;
width = definition.width();
}
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> {
height = 0.6f;
width = definition.width();
}
case DYING -> {
height = 0.2f;
width = 0.2f;
}
default -> { default -> {
super.setDimensions(pose); super.setDimensions(pose);
return; return;
} }
} }
setBoundingBoxWidth(definition.width()); setBoundingBoxWidth(width);
setBoundingBoxHeight(height); setBoundingBoxHeight(height);
} }

View file

@ -26,10 +26,6 @@
package org.geysermc.geyser.inventory; package org.geysermc.geyser.inventory;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import lombok.Value;
@Value public record BedrockContainerSlot(ContainerSlotType container, int slot) {
public class BedrockContainerSlot {
ContainerSlotType container;
int slot;
} }

View file

@ -27,9 +27,11 @@ package org.geysermc.geyser.inventory;
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.jetbrains.annotations.Range;
import javax.annotation.Nonnull;
/** /**
* Combination of {@link Inventory} and {@link PlayerInventory} * Combination of {@link Inventory} and {@link PlayerInventory}
@ -60,7 +62,12 @@ public class Container extends Inventory {
} }
@Override @Override
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { public int getOffsetForHotbar(@Range(from = 0, to = 8) int slot) {
return playerInventory.getOffsetForHotbar(slot) - InventoryTranslator.PLAYER_INVENTORY_OFFSET + this.size;
}
@Override
public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession session) {
if (slot < this.size) { if (slot < this.size) {
super.setItem(slot, newItem, session); super.setItem(slot, newItem, session);
} else { } else {

View file

@ -31,17 +31,19 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.Tag; import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
import org.jetbrains.annotations.Range;
import javax.annotation.Nonnull;
import java.util.Arrays; import java.util.Arrays;
@ToString @ToString
public class Inventory { public abstract class Inventory {
@Getter @Getter
protected final int id; protected final int id;
@ -69,8 +71,7 @@ public class Inventory {
protected final ContainerType containerType; protected final ContainerType containerType;
@Getter @Getter
@Setter protected final String title;
protected String title;
protected final GeyserItemStack[] items; protected final GeyserItemStack[] items;
@ -110,7 +111,9 @@ public class Inventory {
return items[slot]; return items[slot];
} }
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { public abstract int getOffsetForHotbar(@Range(from = 0, to = 8) int slot);
public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession session) {
if (slot > this.size) { if (slot > this.size) {
session.getGeyser().getLogger().debug("Tried to set an item out of bounds! " + this); session.getGeyser().getLogger().debug("Tried to set an item out of bounds! " + this);
return; return;
@ -133,7 +136,9 @@ public class Inventory {
protected void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) { protected void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) {
if (!newItem.isEmpty()) { if (!newItem.isEmpty()) {
if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { ItemMapping oldMapping = ItemTranslator.getBedrockItemMapping(session, oldItem);
ItemMapping newMapping = ItemTranslator.getBedrockItemMapping(session, newItem);
if (oldMapping.getBedrockId() == newMapping.getBedrockId()) {
newItem.setNetId(oldItem.getNetId()); newItem.setNetId(oldItem.getNetId());
} else { } else {
newItem.setNetId(session.getNextItemNetId()); newItem.setNetId(session.getNextItemNetId());

View file

@ -26,10 +26,12 @@
package org.geysermc.geyser.inventory; package org.geysermc.geyser.inventory;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull;
import lombok.Setter; import lombok.Setter;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.jetbrains.annotations.Range;
import javax.annotation.Nonnull;
public class PlayerInventory extends Inventory { public class PlayerInventory extends Inventory {
/** /**
@ -41,7 +43,7 @@ public class PlayerInventory extends Inventory {
private int heldItemSlot; private int heldItemSlot;
@Getter @Getter
@NonNull @Nonnull
private GeyserItemStack cursor = GeyserItemStack.EMPTY; private GeyserItemStack cursor = GeyserItemStack.EMPTY;
public PlayerInventory() { public PlayerInventory() {
@ -49,7 +51,12 @@ public class PlayerInventory extends Inventory {
heldItemSlot = 0; heldItemSlot = 0;
} }
public void setCursor(@NonNull GeyserItemStack newCursor, GeyserSession session) { @Override
public int getOffsetForHotbar(@Range(from = 0, to = 8) int slot) {
return slot + 36;
}
public void setCursor(@Nonnull GeyserItemStack newCursor, GeyserSession session) {
updateItemNetId(cursor, newCursor, session); updateItemNetId(cursor, newCursor, session);
cursor = newCursor; cursor = newCursor;
} }
@ -62,7 +69,7 @@ public class PlayerInventory extends Inventory {
return items[36 + heldItemSlot]; return items[36 + heldItemSlot];
} }
public void setItemInHand(@NonNull GeyserItemStack item) { public void setItemInHand(@Nonnull GeyserItemStack item) {
if (36 + heldItemSlot > this.size) { if (36 + heldItemSlot > this.size) {
GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!"); GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!");
return; return;

View file

@ -40,20 +40,22 @@ import org.geysermc.geyser.inventory.SlotType;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator; import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator;
import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.geyser.util.InventoryUtils;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
public class ClickPlan { public final class ClickPlan {
private final List<ClickAction> plan = new ArrayList<>(); private final List<ClickAction> plan = new ArrayList<>();
private final Int2ObjectMap<GeyserItemStack> simulatedItems; private final Int2ObjectMap<GeyserItemStack> simulatedItems;
/**
* Used for 1.17.1+ proper packet translation - any non-cursor item that is changed in a single transaction gets sent here.
*/
private Int2ObjectMap<ItemStack> changedItems;
private GeyserItemStack simulatedCursor; private GeyserItemStack simulatedCursor;
private boolean simulating; private boolean finished;
private final GeyserSession session; private final GeyserSession session;
private final InventoryTranslator translator; private final InventoryTranslator translator;
@ -66,16 +68,11 @@ public class ClickPlan {
this.inventory = inventory; this.inventory = inventory;
this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize()); this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize());
this.changedItems = null;
this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); this.simulatedCursor = session.getPlayerInventory().getCursor().copy();
this.simulating = true; this.finished = false;
if (translator instanceof PlayerInventoryTranslator) { gridSize = translator.getGridSize();
gridSize = 4;
} else if (translator instanceof CraftingInventoryTranslator) {
gridSize = 9;
} else {
gridSize = -1;
}
} }
private void resetSimulation() { private void resetSimulation() {
@ -88,7 +85,7 @@ public class ClickPlan {
} }
public void add(Click click, int slot, boolean force) { public void add(Click click, int slot, boolean force) {
if (!simulating) if (finished)
throw new UnsupportedOperationException("ClickPlan already executed"); throw new UnsupportedOperationException("ClickPlan already executed");
if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) { if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) {
@ -97,6 +94,8 @@ public class ClickPlan {
ClickAction action = new ClickAction(click, slot, force); ClickAction action = new ClickAction(click, slot, force);
plan.add(action); plan.add(action);
// RUNNING THE SIMULATION HERE IS IMPORTANT. The contents of the simulation are used in complex, multi-stage tasks
// such as autocrafting.
simulateAction(action); simulateAction(action);
} }
@ -112,33 +111,48 @@ public class ClickPlan {
refresh = true; refresh = true;
} }
//int stateId = stateIdHack(action); changedItems = new Int2ObjectOpenHashMap<>();
//simulateAction(action); boolean emulatePost1_16Logic = session.isEmulatePost1_16Logic();
int stateId;
if (emulatePost1_16Logic) {
stateId = stateIdHack(action);
simulateAction(action);
} else {
stateId = inventory.getStateId();
}
ItemStack clickedItemStack; ItemStack clickedItemStack;
if (!planIter.hasNext() && refresh) { if (!planIter.hasNext() && refresh) {
clickedItemStack = InventoryUtils.REFRESH_ITEM; clickedItemStack = InventoryUtils.REFRESH_ITEM;
} else if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) {
clickedItemStack = null;
} else { } else {
//// The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1) if (emulatePost1_16Logic) {
//clickedItemStack = simulatedCursor.getItemStack(); TODO fix - this is the proper behavior but it terribly breaks 1.16.5 // The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1)
clickedItemStack = getItem(action.slot).getItemStack(); clickedItemStack = simulatedCursor.getItemStack();
} else {
if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) {
clickedItemStack = null;
} else {
clickedItemStack = getItem(action.slot).getItemStack();
}
}
}
if (!emulatePost1_16Logic) {
simulateAction(action);
} }
ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket( ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket(
inventory.getId(), inventory.getId(),
inventory.getStateId(), stateId,
action.slot, action.slot,
action.click.actionType, action.click.actionType,
action.click.action, action.click.action,
clickedItemStack, clickedItemStack,
Collections.emptyMap() // Anything else we change, at this time, should have a packet sent to address changedItems
); );
simulateAction(action);
session.sendDownstreamPacket(clickPacket); session.sendDownstreamPacket(clickPacket);
} }
@ -146,19 +160,11 @@ public class ClickPlan {
for (Int2ObjectMap.Entry<GeyserItemStack> simulatedSlot : simulatedItems.int2ObjectEntrySet()) { for (Int2ObjectMap.Entry<GeyserItemStack> simulatedSlot : simulatedItems.int2ObjectEntrySet()) {
inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session); inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session);
} }
simulating = false; finished = true;
} }
public GeyserItemStack getItem(int slot) { public GeyserItemStack getItem(int slot) {
return getItem(slot, true); return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
}
public GeyserItemStack getItem(int slot, boolean generate) {
if (generate) {
return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
} else {
return simulatedItems.getOrDefault(slot, inventory.getItem(slot));
}
} }
public GeyserItemStack getCursor() { public GeyserItemStack getCursor() {
@ -166,23 +172,40 @@ public class ClickPlan {
} }
private void setItem(int slot, GeyserItemStack item) { private void setItem(int slot, GeyserItemStack item) {
if (simulating) { simulatedItems.put(slot, item);
simulatedItems.put(slot, item); onSlotItemChange(slot, item);
} else {
inventory.setItem(slot, item, session);
}
} }
private void setCursor(GeyserItemStack item) { private void setCursor(GeyserItemStack item) {
if (simulating) { simulatedCursor = item;
simulatedCursor = item; }
} else {
session.getPlayerInventory().setCursor(item, session); private void add(int slot, GeyserItemStack itemStack, int amount) {
itemStack.add(amount);
onSlotItemChange(slot, itemStack);
}
private void sub(int slot, GeyserItemStack itemStack, int amount) {
itemStack.sub(amount);
onSlotItemChange(slot, itemStack);
}
private void setAmount(int slot, GeyserItemStack itemStack, int amount) {
itemStack.setAmount(amount);
onSlotItemChange(slot, itemStack);
}
/**
* Does not need to be called for the cursor
*/
private void onSlotItemChange(int slot, GeyserItemStack itemStack) {
if (changedItems != null) {
changedItems.put(slot, itemStack.getItemStack());
} }
} }
private void simulateAction(ClickAction action) { private void simulateAction(ClickAction action) {
GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor(); GeyserItemStack cursor = getCursor();
switch (action.click) { switch (action.click) {
case LEFT_OUTSIDE -> { case LEFT_OUTSIDE -> {
setCursor(GeyserItemStack.EMPTY); setCursor(GeyserItemStack.EMPTY);
@ -196,7 +219,7 @@ public class ClickPlan {
} }
} }
GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot); GeyserItemStack clicked = getItem(action.slot);
if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { if (translator.getSlotType(action.slot) == SlotType.OUTPUT) {
switch (action.click) { switch (action.click) {
case LEFT, RIGHT -> { case LEFT, RIGHT -> {
@ -206,6 +229,7 @@ public class ClickPlan {
cursor.add(clicked.getAmount()); cursor.add(clicked.getAmount());
} }
reduceCraftingGrid(false); reduceCraftingGrid(false);
setItem(action.slot, GeyserItemStack.EMPTY); // Matches Java Edition 1.18.1
} }
case LEFT_SHIFT -> reduceCraftingGrid(true); case LEFT_SHIFT -> reduceCraftingGrid(true);
} }
@ -217,55 +241,55 @@ public class ClickPlan {
setItem(action.slot, cursor); setItem(action.slot, cursor);
} else { } else {
setCursor(GeyserItemStack.EMPTY); setCursor(GeyserItemStack.EMPTY);
clicked.add(cursor.getAmount()); add(action.slot, clicked, cursor.getAmount());
} }
break; break;
case RIGHT: case RIGHT:
if (cursor.isEmpty() && !clicked.isEmpty()) { if (cursor.isEmpty() && !clicked.isEmpty()) {
int half = clicked.getAmount() / 2; //smaller half int half = clicked.getAmount() / 2; //smaller half
setCursor(clicked.copy(clicked.getAmount() - half)); //larger half setCursor(clicked.copy(clicked.getAmount() - half)); //larger half
clicked.setAmount(half); setAmount(action.slot, clicked, half);
} else if (!cursor.isEmpty() && clicked.isEmpty()) { } else if (!cursor.isEmpty() && clicked.isEmpty()) {
cursor.sub(1); cursor.sub(1);
setItem(action.slot, cursor.copy(1)); setItem(action.slot, cursor.copy(1));
} else if (InventoryUtils.canStack(cursor, clicked)) { } else if (InventoryUtils.canStack(cursor, clicked)) {
cursor.sub(1); cursor.sub(1);
clicked.add(1); add(action.slot, clicked, 1);
} }
break; break;
case SWAP_TO_HOTBAR_1: case SWAP_TO_HOTBAR_1:
swap(action.slot, 36, clicked); swap(action.slot, inventory.getOffsetForHotbar(0), clicked);
break; break;
case SWAP_TO_HOTBAR_2: case SWAP_TO_HOTBAR_2:
swap(action.slot, 37, clicked); swap(action.slot, inventory.getOffsetForHotbar(1), clicked);
break; break;
case SWAP_TO_HOTBAR_3: case SWAP_TO_HOTBAR_3:
swap(action.slot, 38, clicked); swap(action.slot, inventory.getOffsetForHotbar(2), clicked);
break; break;
case SWAP_TO_HOTBAR_4: case SWAP_TO_HOTBAR_4:
swap(action.slot, 39, clicked); swap(action.slot, inventory.getOffsetForHotbar(3), clicked);
break; break;
case SWAP_TO_HOTBAR_5: case SWAP_TO_HOTBAR_5:
swap(action.slot, 40, clicked); swap(action.slot, inventory.getOffsetForHotbar(4), clicked);
break; break;
case SWAP_TO_HOTBAR_6: case SWAP_TO_HOTBAR_6:
swap(action.slot, 41, clicked); swap(action.slot, inventory.getOffsetForHotbar(5), clicked);
break; break;
case SWAP_TO_HOTBAR_7: case SWAP_TO_HOTBAR_7:
swap(action.slot, 42, clicked); swap(action.slot, inventory.getOffsetForHotbar(6), clicked);
break; break;
case SWAP_TO_HOTBAR_8: case SWAP_TO_HOTBAR_8:
swap(action.slot, 43, clicked); swap(action.slot, inventory.getOffsetForHotbar(7), clicked);
break; break;
case SWAP_TO_HOTBAR_9: case SWAP_TO_HOTBAR_9:
swap(action.slot, 44, clicked); swap(action.slot, inventory.getOffsetForHotbar(8), clicked);
break; break;
case LEFT_SHIFT: case LEFT_SHIFT:
//TODO //TODO
break; break;
case DROP_ONE: case DROP_ONE:
if (!clicked.isEmpty()) { if (!clicked.isEmpty()) {
clicked.sub(1); sub(action.slot, clicked, 1);
} }
break; break;
case DROP_ALL: case DROP_ALL:
@ -279,7 +303,7 @@ public class ClickPlan {
* Swap between two inventory slots without a cursor. This should only be used with {@link ContainerActionType#MOVE_TO_HOTBAR_SLOT} * Swap between two inventory slots without a cursor. This should only be used with {@link ContainerActionType#MOVE_TO_HOTBAR_SLOT}
*/ */
private void swap(int sourceSlot, int destSlot, GeyserItemStack sourceItem) { private void swap(int sourceSlot, int destSlot, GeyserItemStack sourceItem) {
GeyserItemStack destinationItem = simulating ? getItem(destSlot) : inventory.getItem(destSlot); GeyserItemStack destinationItem = getItem(destSlot);
setItem(sourceSlot, destinationItem); setItem(sourceSlot, destinationItem);
setItem(destSlot, sourceItem); setItem(destSlot, sourceItem);
} }
@ -292,63 +316,44 @@ public class ClickPlan {
stateId = inventory.getStateId(); stateId = inventory.getStateId();
} }
// This is a hack. // Java will never ever send more than one container click packet per set of actions*.
// Java will never ever send more than one container click packet per set of actions. // *(exception being Java's "quick craft"/painting feature)
// Bedrock might, and this would generally fall into one of two categories: // Bedrock might, and this would generally fall into one of two categories:
// - Bedrock is sending an item directly from one slot to another, without picking it up, that cannot // - Bedrock is sending an item directly from one slot to another, without picking it up, that cannot
// be expressed with a shift click // be expressed with a shift click
// - Bedrock wants to pick up or place an arbitrary amount of items that cannot be expressed from // - Bedrock wants to pick up or place an arbitrary amount of items that cannot be expressed from
// one left/right click action. // one left/right click action.
// When Bedrock does one of these actions and sends multiple packets, a 1.17.1+ server will // Java typically doesn't increment the state ID if you send a vanilla-accurate container click packet,
// increment the state ID on each confirmation packet it sends back (I.E. set slot). Then when it // but it will increment the state ID with a vanilla client in at least the crafting table
// reads our next packet, because we kept the same state ID but the server incremented it, it'll be
// desynced and send the entire inventory contents back at us.
// This hack therefore increments the state ID to what the server will presumably send back to us.
// (This won't be perfect, but should get us through most vanilla situations, and if this is wrong the
// server will just send a set content packet back at us)
if (inventory.getContainerType() == ContainerType.CRAFTING && CraftingInventoryTranslator.isCraftingGrid(action.slot)) { if (inventory.getContainerType() == ContainerType.CRAFTING && CraftingInventoryTranslator.isCraftingGrid(action.slot)) {
// 1.18.1 sends a second set slot update for any action in the crafting grid // 1.18.1 sends a second set slot update for any action in the crafting grid
// And an additional packet if something is removed (Mojmap: CraftingContainer#removeItem) // And an additional packet if something is removed (Mojmap: CraftingContainer#removeItem)
//TODO this code kind of really sucks; it's potentially possible to see what Bedrock sends us and send a PlaceRecipePacket
int stateIdIncrements; int stateIdIncrements;
GeyserItemStack clicked = getItem(action.slot); GeyserItemStack clicked = getItem(action.slot);
if (action.click == Click.LEFT) { if (action.click == Click.LEFT) {
if (!clicked.isEmpty() && !InventoryUtils.canStack(simulatedCursor, clicked)) { if (!clicked.isEmpty() && !InventoryUtils.canStack(simulatedCursor, clicked)) {
// An item is removed from the crafting table; yes deletion // An item is removed from the crafting table; yes deletion
stateIdIncrements = 3; stateIdIncrements = 2;
} else { } else {
// We can stack and we add all the items to the crafting slot; no deletion // We can stack and we add all the items to the crafting slot; no deletion
stateIdIncrements = 2; stateIdIncrements = 1;
} }
} else if (action.click == Click.RIGHT) { } else if (action.click == Click.RIGHT) {
if (simulatedCursor.isEmpty() && !clicked.isEmpty()) { stateIdIncrements = 1;
// Items are taken; yes deletion } else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) {
stateIdIncrements = 3; stateIdIncrements = 1;
} else if ((!simulatedCursor.isEmpty() && clicked.isEmpty()) || InventoryUtils.canStack(simulatedCursor, clicked)) {
// Adding our cursor item to the slot; no deletion
stateIdIncrements = 2;
} else {
// ?? nothing I guess
stateIdIncrements = 2;
}
} else { } else {
if (session.getGeyser().getConfig().isDebugMode()) { if (session.getGeyser().getConfig().isDebugMode()) {
session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan); session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan);
} }
stateIdIncrements = 2; stateIdIncrements = 1;
} }
inventory.incrementStateId(stateIdIncrements); inventory.incrementStateId(stateIdIncrements);
} else if (action.click.action instanceof MoveToHotbarAction) {
// Two slot changes sent
inventory.incrementStateId(2);
} else {
inventory.incrementStateId(1);
} }
return stateId; return stateId;
} }
//TODO
private void reduceCraftingGrid(boolean makeAll) { private void reduceCraftingGrid(boolean makeAll) {
if (gridSize == -1) if (gridSize == -1)
return; return;
@ -370,9 +375,12 @@ public class ClickPlan {
} }
for (int i = 0; i < gridSize; i++) { for (int i = 0; i < gridSize; i++) {
GeyserItemStack item = getItem(i + 1); final int slot = i + 1;
if (!item.isEmpty()) GeyserItemStack item = getItem(slot);
item.sub(crafted); if (!item.isEmpty()) {
// These changes should be broadcasted to the server
sub(slot, item, crafted);
}
} }
} }
@ -385,6 +393,10 @@ public class ClickPlan {
for (ClickAction action : plan) { for (ClickAction action : plan) {
if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) { if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) {
affectedSlots.add(action.slot); affectedSlots.add(action.slot);
if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) {
//TODO won't work if offhand is added
affectedSlots.add(inventory.getOffsetForHotbar(((MoveToHotbarAction) action.click.action).ordinal()));
}
} }
} }
return affectedSlots; return affectedSlots;

View file

@ -28,9 +28,9 @@ package org.geysermc.geyser.network;
import com.github.steveice10.mc.protocol.codec.MinecraftCodec; import com.github.steveice10.mc.protocol.codec.MinecraftCodec;
import com.github.steveice10.mc.protocol.codec.PacketCodec; import com.github.steveice10.mc.protocol.codec.PacketCodec;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475;
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -45,7 +45,7 @@ public final class MinecraftProtocol {
* Default Bedrock codec that should act as a fallback. Should represent the latest available * Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports. * release of the game that Geyser supports.
*/ */
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v475.V475_CODEC; public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v486.V486_CODEC;
/** /**
* A list of all supported Bedrock versions that can join Geyser * A list of all supported Bedrock versions that can join Geyser
*/ */
@ -58,8 +58,8 @@ public final class MinecraftProtocol {
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC; private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
static { static {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v465.V465_CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC); SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC);
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v475.V475_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
} }

View file

@ -0,0 +1,122 @@
/*
* 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.registry;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.geysermc.geyser.registry.loader.RegistryLoader;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
/**
* A mapped registry with an integer as the key. This class is designed to minimize the need for boxing/unboxing keys.
*
* @param <V> the value
*/
public class IntMappedRegistry<V> extends AbstractMappedRegistry<Integer, V, Int2ObjectMap<V>> {
protected <I> IntMappedRegistry(I input, RegistryLoader<I, Int2ObjectMap<V>> registryLoader) {
super(input, registryLoader);
}
/**
* Returns the value registered by the given integer.
*
* @param i the integer
* @return the value registered by the given integer.
*/
public V get(int i) {
return this.mappings.get(i);
}
@Nullable
@Override
@Deprecated
public V get(Integer key) {
return super.get(key);
}
/**
* Returns the value registered by the given key or the default value
* specified if null.
*
* @param i the key
* @param defaultValue the default value
* @return the value registered by the given key or the default value
* specified if null.
*/
public V getOrDefault(int i, V defaultValue) {
return this.mappings.getOrDefault(i, defaultValue);
}
@Override
@Deprecated
public V getOrDefault(Integer key, V defaultValue) {
return super.getOrDefault(key, defaultValue);
}
/**
* Registers a new value into this registry with the given key.
*
* @param i the key
* @param value the value
* @return a new value into this registry with the given key.
*/
public V register(int i, V value) {
return this.mappings.put(i, value);
}
@Override
@Deprecated
public V register(Integer key, V value) {
return super.register(key, value);
}
/**
* Creates a new integer mapped registry with the given {@link RegistryLoader}. The
* input type is not specified here, meaning the loader return type is either
* predefined, or the registry is populated at a later point.
*
* @param registryLoader the registry loader
* @param <I> the input
* @param <V> the map value
* @return a new registry with the given RegistryLoader
*/
public static <I, V> IntMappedRegistry<V> create(RegistryLoader<I, Int2ObjectMap<V>> registryLoader) {
return new IntMappedRegistry<>(null, registryLoader);
}
/**
* Creates a new integer mapped registry with the given {@link RegistryLoader} and input.
*
* @param registryLoader the registry loader
* @param <I> the input
* @param <V> the map value
* @return a new registry with the given RegistryLoader supplier
*/
public static <I, V> IntMappedRegistry<V> create(I input, Supplier<RegistryLoader<I, Int2ObjectMap<V>>> registryLoader) {
return new IntMappedRegistry<>(input, registryLoader.get());
}
}

View file

@ -44,20 +44,20 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import org.geysermc.geyser.api.extension.ExtensionLoader; import org.geysermc.geyser.api.extension.ExtensionLoader;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
import org.geysermc.geyser.translator.collision.BlockCollision;
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment; import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
import org.geysermc.geyser.translator.sound.SoundTranslator;
import org.geysermc.geyser.translator.sound.SoundInteractionTranslator;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
import org.geysermc.geyser.registry.loader.*; import org.geysermc.geyser.registry.loader.*;
import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; import org.geysermc.geyser.registry.populator.ItemRegistryPopulator;
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator; import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator;
import org.geysermc.geyser.registry.type.EnchantmentData; import org.geysermc.geyser.registry.type.EnchantmentData;
import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.registry.type.ParticleMapping; import org.geysermc.geyser.registry.type.ParticleMapping;
import org.geysermc.geyser.registry.type.SoundMapping; import org.geysermc.geyser.registry.type.SoundMapping;
import org.geysermc.geyser.translator.collision.BlockCollision;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
import org.geysermc.geyser.translator.sound.SoundInteractionTranslator;
import org.geysermc.geyser.translator.sound.SoundTranslator;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
@ -96,7 +96,7 @@ public final class Registries {
/** /**
* A mapped registry containing which holds block IDs to its {@link BlockCollision}. * A mapped registry containing which holds block IDs to its {@link BlockCollision}.
*/ */
public static final SimpleMappedRegistry<Integer, BlockCollision> COLLISIONS = SimpleMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new); public static final IntMappedRegistry<BlockCollision> COLLISIONS = IntMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new);
/** /**
* A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link CraftingData}. * A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link CraftingData}.
@ -154,7 +154,7 @@ public final class Registries {
* A mapped registry holding the available records, with the ID of the record being the key, and the {@link com.nukkitx.protocol.bedrock.data.SoundEvent} * A mapped registry holding the available records, with the ID of the record being the key, and the {@link com.nukkitx.protocol.bedrock.data.SoundEvent}
* as the value. * as the value.
*/ */
public static final SimpleMappedRegistry<Integer, com.nukkitx.protocol.bedrock.data.SoundEvent> RECORDS = SimpleMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); public static final IntMappedRegistry<com.nukkitx.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
/** /**
* A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}. * A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}.

View file

@ -50,10 +50,10 @@ import java.util.regex.Pattern;
/** /**
* Loads collision data from the given resource path. * Loads collision data from the given resource path.
*/ */
public class CollisionRegistryLoader extends MultiResourceRegistryLoader<String, Map<Integer, BlockCollision>> { public class CollisionRegistryLoader extends MultiResourceRegistryLoader<String, Int2ObjectMap<BlockCollision>> {
@Override @Override
public Map<Integer, BlockCollision> load(Pair<String, String> input) { public Int2ObjectMap<BlockCollision> load(Pair<String, String> input) {
Int2ObjectMap<BlockCollision> collisions = new Int2ObjectOpenHashMap<>(); Int2ObjectMap<BlockCollision> collisions = new Int2ObjectOpenHashMap<>();
Map<Class<?>, CollisionInfo> annotationMap = new IdentityHashMap<>(); Map<Class<?>, CollisionInfo> annotationMap = new IdentityHashMap<>();

View file

@ -28,8 +28,8 @@ package org.geysermc.geyser.registry.populator;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.nukkitx.nbt.*; import com.nukkitx.nbt.*;
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
@ -46,7 +46,10 @@ import org.geysermc.geyser.util.BlockUtils;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.*; import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.Map;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@ -59,8 +62,34 @@ public class BlockRegistryPopulator {
static { static {
ImmutableMap.Builder<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> stateMapperBuilder = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder() ImmutableMap.Builder<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> stateMapperBuilder = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder()
.put(ObjectIntPair.of("1_17_30", Bedrock_v465.V465_CODEC.getProtocolVersion()), EMPTY_MAPPER) .put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER)
.put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER); .put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> {
statesBuilder.remove("no_drop_bit"); // Used in skulls
if (bedrockIdentifier.equals("minecraft:glow_lichen")) {
// Moved around north, south, west
int bits = (int) statesBuilder.get("multi_face_direction_bits");
boolean north = (bits & (1 << 2)) != 0;
boolean south = (bits & (1 << 3)) != 0;
boolean west = (bits & (1 << 4)) != 0;
if (north) {
bits |= 1 << 4;
} else {
bits &= ~(1 << 4);
}
if (south) {
bits |= 1 << 2;
} else {
bits &= ~(1 << 2);
}
if (west) {
bits |= 1 << 3;
} else {
bits &= ~(1 << 3);
}
statesBuilder.put("multi_face_direction_bits", bits);
}
return null;
});
BLOCK_MAPPERS = stateMapperBuilder.build(); BLOCK_MAPPERS = stateMapperBuilder.build();
} }

View file

@ -35,9 +35,9 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData; import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475;
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
@ -63,9 +63,9 @@ public class ItemRegistryPopulator {
static { static {
PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>(); PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>();
PALETTE_VERSIONS.put("1_17_30", new PaletteVersion(Bedrock_v465.V465_CODEC.getProtocolVersion(), Collections.emptyMap()));
PALETTE_VERSIONS.put("1_17_40", new PaletteVersion(Bedrock_v471.V471_CODEC.getProtocolVersion(), Collections.emptyMap())); PALETTE_VERSIONS.put("1_17_40", new PaletteVersion(Bedrock_v471.V471_CODEC.getProtocolVersion(), Collections.emptyMap()));
PALETTE_VERSIONS.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap())); PALETTE_VERSIONS.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap()));
PALETTE_VERSIONS.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap()));
} }
private record PaletteVersion(int protocolVersion, Map<String, String> additionalTranslatedItems) { private record PaletteVersion(int protocolVersion, Map<String, String> additionalTranslatedItems) {

View file

@ -38,10 +38,14 @@ import com.github.steveice10.mc.protocol.data.ProtocolState;
import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException; import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference;
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility;
import com.github.steveice10.mc.protocol.data.game.setting.SkinPart;
import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic;
import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic;
import com.github.steveice10.mc.protocol.packet.handshake.serverbound.ClientIntentionPacket; import com.github.steveice10.mc.protocol.packet.handshake.serverbound.ClientIntentionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientInformationPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket; import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket;
@ -245,7 +249,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter @Setter
private Vector2i lastChunkPosition = null; private Vector2i lastChunkPosition = null;
private int renderDistance; @Setter
private int clientRenderDistance = -1;
private int serverRenderDistance;
// Exposed for GeyserConnect usage // Exposed for GeyserConnect usage
protected boolean sentSpawnPacket; protected boolean sentSpawnPacket;
@ -355,6 +361,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter @Setter
private Int2ObjectMap<IntList> stonecutterRecipes; private Int2ObjectMap<IntList> stonecutterRecipes;
/**
* Starting in 1.17, Java servers expect the <code>carriedItem</code> parameter of the serverbound click container
* packet to be the current contents of the mouse after the transaction has been done. 1.16 expects the clicked slot
* contents before any transaction is done. With the current ViaVersion structure, if we do not send what 1.16 expects
* and send multiple click container packets, then successive transactions will be rejected.
*/
@Setter
private boolean emulatePost1_16Logic = true;
/** /**
* The current attack speed of the player. Used for sending proper cooldown timings. * The current attack speed of the player. Used for sending proper cooldown timings.
* Setting a default fixes cooldowns not showing up on a fresh world. * Setting a default fixes cooldowns not showing up on a fresh world.
@ -790,6 +805,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
FloodgateSkinUploader skinUploader = geyser.getSkinUploader(); FloodgateSkinUploader skinUploader = geyser.getSkinUploader();
FloodgateCipher cipher = geyser.getCipher(); FloodgateCipher cipher = geyser.getCipher();
String bedrockAddress = upstream.getAddress().getAddress().getHostAddress();
// both BungeeCord and Velocity remove the IPv6 scope (if there is one) for Spigot
int ipv6ScopeIndex = bedrockAddress.indexOf('%');
if (ipv6ScopeIndex != -1) {
bedrockAddress = bedrockAddress.substring(0, ipv6ScopeIndex);
}
encryptedData = cipher.encryptFromString(BedrockData.of( encryptedData = cipher.encryptFromString(BedrockData.of(
clientData.getGameVersion(), clientData.getGameVersion(),
authData.name(), authData.name(),
@ -798,7 +820,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
clientData.getLanguageCode(), clientData.getLanguageCode(),
clientData.getUiProfile().ordinal(), clientData.getUiProfile().ordinal(),
clientData.getCurrentInputMode().ordinal(), clientData.getCurrentInputMode().ordinal(),
upstream.getAddress().getAddress().getHostAddress(), bedrockAddress,
skinUploader.getId(), skinUploader.getId(),
skinUploader.getVerifyCode() skinUploader.getVerifyCode()
).toString()); ).toString());
@ -1160,9 +1182,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
return clientData.getLanguageCode(); return clientData.getLanguageCode();
} }
public void setRenderDistance(int renderDistance) { public void setServerRenderDistance(int renderDistance) {
renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle
this.renderDistance = renderDistance; this.serverRenderDistance = renderDistance;
ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket();
chunkRadiusUpdatedPacket.setRadius(renderDistance); chunkRadiusUpdatedPacket.setRadius(renderDistance);
@ -1420,6 +1442,27 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
sendUpstreamPacket(adventureSettingsPacket); sendUpstreamPacket(adventureSettingsPacket);
} }
private int getRenderDistance() {
if (clientRenderDistance != -1) {
// The client has sent a render distance
return clientRenderDistance;
}
return serverRenderDistance;
}
// We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc
private static final List<SkinPart> SKIN_PARTS = Arrays.asList(SkinPart.values());
/**
* Send a packet to the server to indicate client render distance, locale, skin parts, and hand preference.
*/
public void sendJavaClientSettings() {
ServerboundClientInformationPacket clientSettingsPacket = new ServerboundClientInformationPacket(locale(),
getRenderDistance(), ChatVisibility.FULL, true, SKIN_PARTS,
HandPreference.RIGHT_HAND, false, true);
sendDownstreamPacket(clientSettingsPacket);
}
/** /**
* Used for updating statistic values since we only get changes from the server * Used for updating statistic values since we only get changes from the server
* *

View file

@ -30,9 +30,6 @@ import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.StringTag;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -43,7 +40,7 @@ import java.util.WeakHashMap;
* A temporary cache for lodestone information. * A temporary cache for lodestone information.
* Bedrock requests the lodestone position information separately from the item. * Bedrock requests the lodestone position information separately from the item.
*/ */
public class LodestoneCache { public final class LodestoneCache {
/** /**
* A list of any GeyserItemStacks that are lodestones. Used mainly to minimize Bedrock's "pop-in" effect * A list of any GeyserItemStacks that are lodestones. Used mainly to minimize Bedrock's "pop-in" effect
* when a new item has been created; instead we can re-use already existing IDs * when a new item has been created; instead we can re-use already existing IDs
@ -121,8 +118,16 @@ public class LodestoneCache {
} }
public @Nullable LodestonePos getPos(int id) { public @Nullable LodestonePos getPos(int id) {
// We should not need to check the activeLodestones map as Bedrock should already be aware of this ID LodestonePos pos = this.lodestones.remove(id);
return this.lodestones.remove(id); if (pos != null) {
return pos;
}
for (LodestonePos activePos : this.activeLodestones.values()) {
if (activePos.id == id) {
return activePos;
}
}
return null;
} }
public void clear() { public void clear() {
@ -131,16 +136,7 @@ public class LodestoneCache {
this.lodestones.clear(); this.lodestones.clear();
} }
@Getter public record LodestonePos(int id, int x, int y, int z, String dimension) {
@AllArgsConstructor
@EqualsAndHashCode
public static class LodestonePos {
private final int id;
private final int x;
private final int y;
private final int z;
private final String dimension;
boolean equals(int x, int y, int z, String dimension) { boolean equals(int x, int y, int z, String dimension) {
return this.x == x && this.y == y && this.z == z && this.dimension.equals(dimension); return this.x == x && this.y == y && this.z == z && this.dimension.equals(dimension);
} }

View file

@ -115,10 +115,12 @@ public class SkinProvider {
WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull, false); WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull, false);
String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8); String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8);
WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim, false); WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim, false);
}
public static void registerCacheImageTask(GeyserImpl geyser) {
// Schedule Daily Image Expiry if we are caching them // Schedule Daily Image Expiry if we are caching them
if (GeyserImpl.getInstance().getConfig().getCacheImages() > 0) { if (geyser.getConfig().getCacheImages() > 0) {
GeyserImpl.getInstance().getScheduledThread().scheduleAtFixedRate(() -> { geyser.getScheduledThread().scheduleAtFixedRate(() -> {
File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile(); File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile();
if (!cacheFolder.exists()) { if (!cacheFolder.exists()) {
return; return;

View file

@ -38,17 +38,16 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequ
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import it.unimi.dsi.fastutil.ints.IntSets;
import org.geysermc.geyser.inventory.BeaconContainer; import org.geysermc.geyser.inventory.BeaconContainer;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder; import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater; import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.geyser.util.InventoryUtils;
import java.util.Collections;
public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator { public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator {
public BeaconInventoryTranslator() { public BeaconInventoryTranslator() {
super(1, new BlockInventoryHolder("minecraft:beacon", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.BEACON) { super(1, new BlockInventoryHolder("minecraft:beacon", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.BEACON) {
@ -104,7 +103,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
} }
@Override @Override
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
return action.getType() == StackRequestActionType.BEACON_PAYMENT; return action.getType() == StackRequestActionType.BEACON_PAYMENT;
} }
@ -114,7 +113,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0]; BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0];
ServerboundSetBeaconPacket packet = new ServerboundSetBeaconPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect()); ServerboundSetBeaconPacket packet = new ServerboundSetBeaconPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect());
session.sendDownstreamPacket(packet); session.sendDownstreamPacket(packet);
return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet()));
} }
@Override @Override

View file

@ -37,6 +37,11 @@ public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslato
super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE); super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE);
} }
@Override
public int getGridSize() {
return 9;
}
@Override @Override
public SlotType getSlotType(int javaSlot) { public SlotType getSlotType(int javaSlot) {
if (javaSlot == 0) { if (javaSlot == 0) {

View file

@ -27,23 +27,22 @@ package org.geysermc.geyser.translator.inventory;
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket;
import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket; import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket;
import org.geysermc.geyser.inventory.EnchantingContainer; import it.unimi.dsi.fastutil.ints.IntSets;
import org.geysermc.geyser.inventory.GeyserEnchantOption; import org.geysermc.geyser.inventory.*;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.inventory.item.Enchantment; import org.geysermc.geyser.inventory.item.Enchantment;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.session.GeyserSession;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator { public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator {
public EnchantingInventoryTranslator() { public EnchantingInventoryTranslator() {
@ -104,7 +103,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
} }
@Override @Override
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
return action.getType() == StackRequestActionType.CRAFT_RECIPE; return action.getType() == StackRequestActionType.CRAFT_RECIPE;
} }
@ -130,7 +129,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
} }
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), javaSlot); ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), javaSlot);
session.sendDownstreamPacket(packet); session.sendDownstreamPacket(packet);
return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet()));
} }
@Override @Override

View file

@ -26,12 +26,11 @@
package org.geysermc.geyser.translator.inventory; package org.geysermc.geyser.translator.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.Tag; import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
@ -43,15 +42,10 @@ import it.unimi.dsi.fastutil.ints.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.CartographyContainer; import org.geysermc.geyser.inventory.*;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.SlotType;
import org.geysermc.geyser.inventory.click.Click; import org.geysermc.geyser.inventory.click.Click;
import org.geysermc.geyser.inventory.click.ClickPlan; import org.geysermc.geyser.inventory.click.ClickPlan;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator;
import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator;
import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator; import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator;
@ -119,6 +113,13 @@ public abstract class InventoryTranslator {
public abstract SlotType getSlotType(int javaSlot); public abstract SlotType getSlotType(int javaSlot);
public abstract Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory); public abstract Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory);
/**
* Used for crafting-related transactions. Will override in PlayerInventoryTranslator and CraftingInventoryTranslator.
*/
public int getGridSize() {
return -1;
}
/** /**
* Should be overwritten in cases where specific inventories should reject an item being in a specific spot. * Should be overwritten in cases where specific inventories should reject an item being in a specific spot.
* For examples, looms use this to reject items that are dyes in Bedrock but not in Java. * For examples, looms use this to reject items that are dyes in Bedrock but not in Java.
@ -136,7 +137,7 @@ public abstract class InventoryTranslator {
* Should be overrided if this request matches a certain criteria and shouldn't be treated normally. * Should be overrided if this request matches a certain criteria and shouldn't be treated normally.
* E.G. anvil renaming or enchanting * E.G. anvil renaming or enchanting
*/ */
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
return false; return false;
} }
@ -147,7 +148,7 @@ public abstract class InventoryTranslator {
return rejectRequest(request); return rejectRequest(request);
} }
public void translateRequests(GeyserSession session, Inventory inventory, List<ItemStackRequest> requests) { public final void translateRequests(GeyserSession session, Inventory inventory, List<ItemStackRequest> requests) {
boolean refresh = false; boolean refresh = false;
ItemStackResponsePacket responsePacket = new ItemStackResponsePacket(); ItemStackResponsePacket responsePacket = new ItemStackResponsePacket();
for (ItemStackRequest request : requests) { for (ItemStackRequest request : requests) {
@ -199,10 +200,6 @@ public abstract class InventoryTranslator {
case PLACE: { case PLACE: {
TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action;
if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) {
if (session.getGameMode().equals(GameMode.CREATIVE) && transferAction.getSource().getContainer() == ContainerSlotType.CRAFTING_INPUT &&
transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) {
return rejectRequest(request, false);
}
if (session.getGeyser().getConfig().isDebugMode()) { if (session.getGeyser().getConfig().isDebugMode()) {
session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.name()); session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.name());
dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination()); dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination());
@ -212,17 +209,19 @@ public abstract class InventoryTranslator {
int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int sourceSlot = bedrockSlotToJava(transferAction.getSource());
int destSlot = bedrockSlotToJava(transferAction.getDestination()); int destSlot = bedrockSlotToJava(transferAction.getDestination());
boolean isSourceCursor = isCursor(transferAction.getSource());
boolean isDestCursor = isCursor(transferAction.getDestination());
if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(), if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(),
isCursor(transferAction.getSource()) ? -1 : sourceSlot, isSourceCursor ? -1 : sourceSlot,
transferAction.getDestination().getContainer(), isCursor(transferAction.getDestination()) ? -1 : destSlot)) { transferAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) {
// This item would not be here in Java // This item would not be here in Java
return rejectRequest(request, false); return rejectRequest(request, false);
} }
if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? if (isSourceCursor && isDestCursor) { //???
return rejectRequest(request); return rejectRequest(request);
} else if (isCursor(transferAction.getSource())) { //releasing cursor } else if (isSourceCursor) { //releasing cursor
int sourceAmount = cursor.getAmount(); int sourceAmount = cursor.getAmount();
if (transferAction.getCount() == sourceAmount) { //release all if (transferAction.getCount() == sourceAmount) { //release all
plan.add(Click.LEFT, destSlot); plan.add(Click.LEFT, destSlot);
@ -231,7 +230,7 @@ public abstract class InventoryTranslator {
plan.add(Click.RIGHT, destSlot); plan.add(Click.RIGHT, destSlot);
} }
} }
} else if (isCursor(transferAction.getDestination())) { //picking up into cursor } else if (isDestCursor) { //picking up into cursor
GeyserItemStack sourceItem = plan.getItem(sourceSlot); GeyserItemStack sourceItem = plan.getItem(sourceSlot);
int sourceAmount = sourceItem.getAmount(); int sourceAmount = sourceItem.getAmount();
if (cursor.isEmpty()) { //picking up into empty cursor if (cursor.isEmpty()) { //picking up into empty cursor
@ -313,18 +312,7 @@ public abstract class InventoryTranslator {
if (!isSourceCursor && destination.getContainer() == ContainerSlotType.HOTBAR || destination.getContainer() == ContainerSlotType.HOTBAR_AND_INVENTORY) { if (!isSourceCursor && destination.getContainer() == ContainerSlotType.HOTBAR || destination.getContainer() == ContainerSlotType.HOTBAR_AND_INVENTORY) {
// Tell the server we're pressing one of the hotbar keys to save clicks // Tell the server we're pressing one of the hotbar keys to save clicks
Click click = switch (destination.getSlot()) { Click click = InventoryUtils.getClickForHotbarSwap(destination.getSlot());
case 0 -> Click.SWAP_TO_HOTBAR_1;
case 1 -> Click.SWAP_TO_HOTBAR_2;
case 2 -> Click.SWAP_TO_HOTBAR_3;
case 3 -> Click.SWAP_TO_HOTBAR_4;
case 4 -> Click.SWAP_TO_HOTBAR_5;
case 5 -> Click.SWAP_TO_HOTBAR_6;
case 6 -> Click.SWAP_TO_HOTBAR_7;
case 7 -> Click.SWAP_TO_HOTBAR_8;
case 8 -> Click.SWAP_TO_HOTBAR_9;
default -> null;
};
if (click != null) { if (click != null) {
plan.add(click, sourceSlot); plan.add(click, sourceSlot);
break; break;
@ -442,6 +430,8 @@ public abstract class InventoryTranslator {
int leftover = 0; int leftover = 0;
ClickPlan plan = new ClickPlan(session, this, inventory); ClickPlan plan = new ClickPlan(session, this, inventory);
// Track all the crafting table slots to report back the contents of the slots after crafting
IntSet affectedSlots = new IntOpenHashSet();
for (StackRequestActionData action : request.getActions()) { for (StackRequestActionData action : request.getActions()) {
switch (action.getType()) { switch (action.getType()) {
case CRAFT_RECIPE: { case CRAFT_RECIPE: {
@ -473,6 +463,7 @@ public abstract class InventoryTranslator {
return rejectRequest(request); return rejectRequest(request);
} }
craftState = CraftState.INGREDIENTS; craftState = CraftState.INGREDIENTS;
affectedSlots.add(bedrockSlotToJava(((ConsumeStackRequestActionData) action).getSource()));
break; break;
} }
case TAKE: case TAKE:
@ -533,21 +524,16 @@ public abstract class InventoryTranslator {
} }
} }
plan.execute(false); plan.execute(false);
return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); affectedSlots.addAll(plan.getAffectedSlots());
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
} }
public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
int gridSize; final int gridSize = getGridSize();
int gridDimensions; if (gridSize == -1) {
if (this instanceof PlayerInventoryTranslator) {
gridSize = 4;
gridDimensions = 2;
} else if (this instanceof CraftingInventoryTranslator) {
gridSize = 9;
gridDimensions = 3;
} else {
return rejectRequest(request); return rejectRequest(request);
} }
int gridDimensions = gridSize == 4 ? 2 : 3;
Recipe recipe; Recipe recipe;
Ingredient[] ingredients = new Ingredient[0]; Ingredient[] ingredients = new Ingredient[0];
@ -733,7 +719,7 @@ public abstract class InventoryTranslator {
/** /**
* Handled in {@link PlayerInventoryTranslator} * Handled in {@link PlayerInventoryTranslator}
*/ */
public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { protected ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
return rejectRequest(request); return rejectRequest(request);
} }
@ -768,14 +754,14 @@ public abstract class InventoryTranslator {
} }
} }
public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequest request, List<ItemStackResponsePacket.ContainerEntry> containerEntries) { protected static ItemStackResponsePacket.Response acceptRequest(ItemStackRequest request, List<ItemStackResponsePacket.ContainerEntry> containerEntries) {
return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries); return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries);
} }
/** /**
* Reject an incorrect ItemStackRequest. * Reject an incorrect ItemStackRequest.
*/ */
public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) { protected static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) {
return rejectRequest(request, true); return rejectRequest(request, true);
} }
@ -785,7 +771,7 @@ public abstract class InventoryTranslator {
* @param throwError whether this request was truly erroneous (true), or known as an outcome and should not be treated * @param throwError whether this request was truly erroneous (true), or known as an outcome and should not be treated
* as bad (false). * as bad (false).
*/ */
public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) { protected static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) {
if (throwError && GeyserImpl.getInstance().getConfig().isDebugMode()) { if (throwError && GeyserImpl.getInstance().getConfig().isDebugMode()) {
new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace(); new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace();
} }
@ -860,12 +846,15 @@ public abstract class InventoryTranslator {
return -1; return -1;
} }
public List<ItemStackResponsePacket.ContainerEntry> makeContainerEntries(GeyserSession session, Inventory inventory, Set<Integer> affectedSlots) { protected final List<ItemStackResponsePacket.ContainerEntry> makeContainerEntries(GeyserSession session, Inventory inventory, IntSet affectedSlots) {
Map<ContainerSlotType, List<ItemStackResponsePacket.ItemEntry>> containerMap = new HashMap<>(); Map<ContainerSlotType, List<ItemStackResponsePacket.ItemEntry>> containerMap = new HashMap<>();
for (int slot : affectedSlots) { // Manually call iterator to prevent Integer boxing
IntIterator it = affectedSlots.iterator();
while (it.hasNext()) {
int slot = it.nextInt();
BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot); BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot);
List<ItemStackResponsePacket.ItemEntry> list = containerMap.computeIfAbsent(bedrockSlot.getContainer(), k -> new ArrayList<>()); List<ItemStackResponsePacket.ItemEntry> list = containerMap.computeIfAbsent(bedrockSlot.container(), k -> new ArrayList<>());
list.add(makeItemEntry(session, bedrockSlot.getSlot(), inventory.getItem(slot))); list.add(makeItemEntry(session, bedrockSlot.slot(), inventory.getItem(slot)));
} }
List<ItemStackResponsePacket.ContainerEntry> containerEntries = new ArrayList<>(); List<ItemStackResponsePacket.ContainerEntry> containerEntries = new ArrayList<>();
@ -879,7 +868,7 @@ public abstract class InventoryTranslator {
return containerEntries; return containerEntries;
} }
public static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) { private static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) {
ItemStackResponsePacket.ItemEntry itemEntry; ItemStackResponsePacket.ItemEntry itemEntry;
if (!itemStack.isEmpty()) { if (!itemStack.isEmpty()) {
// As of 1.16.210: Bedrock needs confirmation on what the current item durability is. // As of 1.16.210: Bedrock needs confirmation on what the current item durability is.

View file

@ -117,7 +117,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
} }
@Override @Override
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
// If the LOOM_MATERIAL slot is not empty, we are crafting a pattern that does not come from an item // If the LOOM_MATERIAL slot is not empty, we are crafting a pattern that does not come from an item
// Remove the CRAFT_NON_IMPLEMENTED_DEPRECATED when 1.17.30 is dropped // Remove the CRAFT_NON_IMPLEMENTED_DEPRECATED when 1.17.30 is dropped
return (action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_LOOM) return (action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_LOOM)

View file

@ -35,6 +35,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*;
import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import org.geysermc.geyser.inventory.*; import org.geysermc.geyser.inventory.*;
@ -55,6 +56,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
super(46); super(46);
} }
@Override
public int getGridSize() {
return 4;
}
@Override @Override
public void updateInventory(GeyserSession session, Inventory inventory) { public void updateInventory(GeyserSession session, Inventory inventory) {
updateCraftingGrid(session, inventory); updateCraftingGrid(session, inventory);
@ -370,14 +376,17 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
} }
} }
} }
for (int slot : affectedSlots) { // Manually call iterator to prevent Integer boxing
IntIterator it = affectedSlots.iterator();
while (it.hasNext()) {
int slot = it.nextInt();
sendCreativeAction(session, inventory, slot); sendCreativeAction(session, inventory, slot);
} }
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
} }
@Override @Override
public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { protected ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
ItemStack javaCreativeItem = null; ItemStack javaCreativeItem = null;
IntSet affectedSlots = new IntOpenHashSet(); IntSet affectedSlots = new IntOpenHashSet();
CraftState craftState = CraftState.START; CraftState craftState = CraftState.START;
@ -478,7 +487,10 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
return rejectRequest(request); return rejectRequest(request);
} }
} }
for (int slot : affectedSlots) { // Manually call iterator to prevent Integer boxing
IntIterator it = affectedSlots.iterator();
while (it.hasNext()) {
int slot = it.nextInt();
sendCreativeAction(session, inventory, slot); sendCreativeAction(session, inventory, slot);
} }
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));

View file

@ -52,7 +52,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
} }
@Override @Override
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
// First is pre-1.18. TODO remove after 1.17.40 support is dropped and refactor stonecutter support to use CraftRecipeStackRequestActionData's recipe ID // First is pre-1.18. TODO remove after 1.17.40 support is dropped and refactor stonecutter support to use CraftRecipeStackRequestActionData's recipe ID
return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_RECIPE; return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_RECIPE;
} }

View file

@ -155,7 +155,7 @@ public class BannerTranslator extends ItemTranslator {
} }
@Override @Override
public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (itemStack.getNbt() == null) { if (itemStack.getNbt() == null) {
return super.translateToBedrock(itemStack, mapping, mappings); return super.translateToBedrock(itemStack, mapping, mappings);
} }
@ -163,9 +163,7 @@ public class BannerTranslator extends ItemTranslator {
ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings); ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings);
CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag");
if (blockEntityTag != null && blockEntityTag.contains("Patterns")) { if (blockEntityTag != null && blockEntityTag.get("Patterns") instanceof ListTag patterns) {
ListTag patterns = blockEntityTag.get("Patterns");
NbtMapBuilder nbtBuilder = builder.build().getTag().toBuilder(); //TODO fix ugly hack NbtMapBuilder nbtBuilder = builder.build().getTag().toBuilder(); //TODO fix ugly hack
if (patterns.equals(OMINOUS_BANNER_PATTERN)) { if (patterns.equals(OMINOUS_BANNER_PATTERN)) {
// Remove the current patterns and set the ominous banner type // Remove the current patterns and set the ominous banner type

View file

@ -26,7 +26,9 @@
package org.geysermc.geyser.translator.inventory.item; package org.geysermc.geyser.translator.inventory.item;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.*; import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
@ -51,19 +53,30 @@ public class CompassTranslator extends ItemTranslator {
} }
@Override @Override
public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); if (isLodestoneCompass(itemStack.getNbt())) {
// NBT will be translated in nbt/LodestoneCompassTranslator if applicable
Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); return super.translateToBedrock(itemStack, mappings.getStoredItems().lodestoneCompass(), mappings);
if (lodestoneTag instanceof ByteTag) {
// Get the fake lodestonecompass entry
mapping = mappings.getStoredItems().lodestoneCompass();
// NBT will be translated in nbt/LodestoneCompassTranslator
} }
return super.translateToBedrock(itemStack, mapping, mappings); return super.translateToBedrock(itemStack, mapping, mappings);
} }
@Override
protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) {
if (isLodestoneCompass(nbt)) {
return mappings.getStoredItems().lodestoneCompass();
}
return super.getItemMapping(javaId, nbt, mappings);
}
private boolean isLodestoneCompass(CompoundTag nbt) {
if (nbt != null) {
Tag lodestoneTag = nbt.get("LodestoneTracked");
return lodestoneTag instanceof ByteTag;
}
return false;
}
@Override @Override
public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) { public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) {
if (mapping.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { if (mapping.getBedrockIdentifier().equals("minecraft:lodestone_compass")) {

View file

@ -37,6 +37,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ItemMappings;
@ -157,18 +158,13 @@ public abstract class ItemTranslator {
nbt = translateDisplayProperties(session, nbt, bedrockItem); nbt = translateDisplayProperties(session, nbt, bedrockItem);
if (session.isAdvancedTooltips()) { if (session.isAdvancedTooltips()) {
nbt = addAdvancedTooltips(nbt, session.getItemMappings().getMapping(stack), session.locale()); nbt = addAdvancedTooltips(nbt, bedrockItem, session.locale());
} }
ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt); ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt);
ItemData.Builder builder; ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.getOrDefault(bedrockItem.getJavaId(), DEFAULT_TRANSLATOR);
ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId()); ItemData.Builder builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings());
if (itemStackTranslator != null) {
builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings());
} else {
builder = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem, session.getItemMappings());
}
if (bedrockItem.isBlock()) { if (bedrockItem.isBlock()) {
builder.blockRuntimeId(bedrockItem.getBedrockBlockId()); builder.blockRuntimeId(bedrockItem.getBedrockBlockId());
} }
@ -263,6 +259,19 @@ public abstract class ItemTranslator {
return canModifyBedrock; return canModifyBedrock;
} }
/**
* Given an item stack, determine the item mapping that should be applied to Bedrock players.
*/
@Nonnull
public static ItemMapping getBedrockItemMapping(GeyserSession session, @Nonnull GeyserItemStack itemStack) {
if (itemStack.isEmpty()) {
return ItemMapping.AIR;
}
int javaId = itemStack.getJavaId();
return ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR)
.getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings());
}
private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() { private static final ItemTranslator DEFAULT_TRANSLATOR = new ItemTranslator() {
@Override @Override
public List<ItemMapping> getAppliedItems() { public List<ItemMapping> getAppliedItems() {
@ -270,7 +279,7 @@ public abstract class ItemTranslator {
} }
}; };
public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (itemStack == null) { if (itemStack == null) {
// Return, essentially, air // Return, essentially, air
return ItemData.builder(); return ItemData.builder();
@ -295,6 +304,10 @@ public abstract class ItemTranslator {
public abstract List<ItemMapping> getAppliedItems(); public abstract List<ItemMapping> getAppliedItems();
protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) {
return mappings.getMapping(javaId);
}
public NbtMap translateNbtToBedrock(CompoundTag tag) { public NbtMap translateNbtToBedrock(CompoundTag tag) {
NbtMapBuilder builder = NbtMap.builder(); NbtMapBuilder builder = NbtMap.builder();
if (tag.getValue() != null && !tag.getValue().isEmpty()) { if (tag.getValue() != null && !tag.getValue().isEmpty()) {
@ -469,9 +482,8 @@ public abstract class ItemTranslator {
public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemMapping mapping, char translationColor) { public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemMapping mapping, char translationColor) {
boolean hasCustomName = false; boolean hasCustomName = false;
if (tag != null) { if (tag != null) {
CompoundTag display = tag.get("display"); if (tag.get("display") instanceof CompoundTag display && display.get("Name") instanceof StringTag tagName) {
if (display != null && display.contains("Name")) { String name = tagName.getValue();
String name = ((StringTag) display.get("Name")).getValue();
// Get the translated name and prefix it with a reset char // Get the translated name and prefix it with a reset char
name = MessageTranslator.convertMessageLenient(name, session.locale()); name = MessageTranslator.convertMessageLenient(name, session.locale());
@ -491,8 +503,10 @@ public abstract class ItemTranslator {
if (tag == null) { if (tag == null) {
tag = new CompoundTag(""); tag = new CompoundTag("");
} }
CompoundTag display = tag.get("display"); CompoundTag display;
if (display == null) { if (tag.get("display") instanceof CompoundTag oldDisplay) {
display = oldDisplay;
} else {
display = new CompoundTag("display"); display = new CompoundTag("display");
// Add to the new root tag // Add to the new root tag
tag.put(display); tag.put(display);

View file

@ -54,7 +54,7 @@ public class PotionTranslator extends ItemTranslator {
} }
@Override @Override
public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings);
Tag potionTag = itemStack.getNbt().get("Potion"); Tag potionTag = itemStack.getNbt().get("Potion");
if (potionTag instanceof StringTag) { if (potionTag instanceof StringTag) {

View file

@ -59,7 +59,7 @@ public class TippedArrowTranslator extends ItemTranslator {
} }
@Override @Override
public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) { protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (!mapping.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) { if (!mapping.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) {
// We're only concerned about minecraft:arrow when translating Bedrock -> Java // We're only concerned about minecraft:arrow when translating Bedrock -> Java
return super.translateToBedrock(itemStack, mapping, mappings); return super.translateToBedrock(itemStack, mapping, mappings);

View file

@ -51,13 +51,11 @@ public class BasicItemTranslator extends NbtItemStackTranslator {
} }
} }
CompoundTag displayTag = itemTag.get("display"); if (!(itemTag.get("display") instanceof CompoundTag displayTag)) {
if (displayTag == null) {
return; return;
} }
Tag loreTag = displayTag.get("Lore"); if (displayTag.get("Lore") instanceof ListTag listTag) {
if (loreTag instanceof ListTag listTag) {
List<Tag> lore = new ArrayList<>(); List<Tag> lore = new ArrayList<>();
for (Tag tag : listTag.getValue()) { for (Tag tag : listTag.getValue()) {
if (!(tag instanceof StringTag)) continue; if (!(tag instanceof StringTag)) continue;

View file

@ -41,12 +41,14 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
@Override @Override
public void translate(GeyserSession session, BlockEntityDataPacket packet) { public void translate(GeyserSession session, BlockEntityDataPacket packet) {
NbtMap tag = packet.getData(); NbtMap tag = packet.getData();
if (tag.getString("id").equals("Sign")) { String id = tag.getString("id");
if (id.equals("Sign")) {
String text = tag.getString("Text");
// This is the reason why this all works - Bedrock sends packets every time you update the sign, Java only wants the final packet // This is the reason why this all works - Bedrock sends packets every time you update the sign, Java only wants the final packet
// But Bedrock sends one final packet when you're done editing the sign, which should be equal to the last message since there's no edits // But Bedrock sends one final packet when you're done editing the sign, which should be equal to the last message since there's no edits
// So if the latest update does not match the last cached update then it's still being edited // So if the latest update does not match the last cached update then it's still being edited
if (!tag.getString("Text").equals(session.getLastSignMessage())) { if (!text.equals(session.getLastSignMessage())) {
session.setLastSignMessage(tag.getString("Text")); session.setLastSignMessage(text);
return; return;
} }
// Otherwise the two messages are identical and we can get to work deconstructing // Otherwise the two messages are identical and we can get to work deconstructing
@ -59,7 +61,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
// If it goes over the maximum, we need to start a new line to match Java // If it goes over the maximum, we need to start a new line to match Java
int widthCount = 0; int widthCount = 0;
// This converts the message into the array'd message Java wants // This converts the message into the array'd message Java wants
for (char character : tag.getString("Text").toCharArray()) { for (char character : text.toCharArray()) {
widthCount += SignUtils.getCharacterWidth(character); widthCount += SignUtils.getCharacterWidth(character);
// If we get a return in Bedrock, or go over the character width max, that signals to use the next line. // If we get a return in Bedrock, or go over the character width max, that signals to use the next line.
if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) { if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) {
@ -111,7 +113,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
// We set the sign text cached in the session to null to indicate there is no work-in-progress sign // We set the sign text cached in the session to null to indicate there is no work-in-progress sign
session.setLastSignMessage(null); session.setLastSignMessage(null);
} else if (tag.getString("id").equals("JigsawBlock")) { } else if (id.equals("JigsawBlock")) {
// Client has just sent a jigsaw block update // Client has just sent a jigsaw block update
Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
String name = tag.getString("name"); String name = tag.getString("name");

View file

@ -25,12 +25,14 @@
package org.geysermc.geyser.translator.protocol.bedrock; package org.geysermc.geyser.translator.protocol.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
@ -40,21 +42,27 @@ import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.data.inventory.*;
import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity;
import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.inventory.click.Click;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator;
import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.geyser.util.InventoryUtils;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -109,6 +117,23 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
case ITEM_USE: case ITEM_USE:
switch (packet.getActionType()) { switch (packet.getActionType()) {
case 0 -> { case 0 -> {
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
if (session.getGeyser().getConfig().isDisableBedrockScaffolding()) {
float yaw = session.getPlayerEntity().getYaw();
boolean isGodBridging = switch (packet.getBlockFace()) {
case 2 -> yaw <= -135f || yaw > 135f;
case 3 -> yaw <= 45f && yaw > -45f;
case 4 -> yaw > 45f && yaw <= 135f;
case 5 -> yaw <= -45f && yaw > -135f;
default -> false;
};
if (isGodBridging) {
restoreCorrectBlock(session, blockPos, packet);
return;
}
}
// Check to make sure the client isn't spamming interaction // Check to make sure the client isn't spamming interaction
// Based on Nukkit 1.0, with changes to ensure holding down still works // Based on Nukkit 1.0, with changes to ensure holding down still works
boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 && boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 &&
@ -138,7 +163,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
} }
} }
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
/* /*
Checks to ensure that the range will be accepted by the server. Checks to ensure that the range will be accepted by the server.
"Not in range" doesn't refer to how far a vanilla client goes (that's a whole other mess), "Not in range" doesn't refer to how far a vanilla client goes (that's a whole other mess),
@ -253,16 +277,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.setInteracting(true); session.setInteracting(true);
} }
case 1 -> { case 1 -> {
if (packet.getActions().size() == 1 && packet.getLegacySlots().size() > 0) {
InventoryActionData actionData = packet.getActions().get(0);
LegacySetItemSlotData slotData = packet.getLegacySlots().get(0);
if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) {
// The player is trying to swap out an armor piece that already has an item in it
// Java Edition does not allow this; let's revert it
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
}
}
// Handled when sneaking // Handled when sneaking
if (session.getPlayerInventory().getItemInHand().getJavaId() == mappings.getStoredItems().shield().getJavaId()) { if (session.getPlayerInventory().getItemInHand().getJavaId() == mappings.getStoredItems().shield().getJavaId()) {
break; break;
@ -282,6 +296,43 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
ServerboundUseItemPacket useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); ServerboundUseItemPacket useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(useItemPacket); session.sendDownstreamPacket(useItemPacket);
List<LegacySetItemSlotData> legacySlots = packet.getLegacySlots();
if (packet.getActions().size() == 1 && legacySlots.size() > 0) {
InventoryActionData actionData = packet.getActions().get(0);
LegacySetItemSlotData slotData = legacySlots.get(0);
if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) {
// The player is trying to swap out an armor piece that already has an item in it
if (session.getGeyser().getConfig().isAlwaysQuickChangeArmor()) {
// Java doesn't know when a player is in its own inventory and not, so we
// can abuse this feature to send a swap inventory packet
int bedrockHotbarSlot = packet.getHotbarSlot();
Click click = InventoryUtils.getClickForHotbarSwap(bedrockHotbarSlot);
if (click != null && slotData.getSlots().length != 0) {
Inventory playerInventory = session.getPlayerInventory();
// Bedrock sends us the index of the slot in the armor container; armor in Java
// Edition is offset by 5 in the player inventory
int armorSlot = slotData.getSlots()[0] + 5;
GeyserItemStack armorSlotItem = playerInventory.getItem(armorSlot);
GeyserItemStack hotbarItem = playerInventory.getItem(playerInventory.getOffsetForHotbar(bedrockHotbarSlot));
playerInventory.setItem(armorSlot, hotbarItem, session);
playerInventory.setItem(bedrockHotbarSlot, armorSlotItem, session);
Int2ObjectMap<ItemStack> changedSlots = new Int2ObjectOpenHashMap<>(2);
changedSlots.put(armorSlot, hotbarItem.getItemStack());
changedSlots.put(bedrockHotbarSlot, armorSlotItem.getItemStack());
ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket(
playerInventory.getId(), playerInventory.getStateId(), armorSlot,
click.actionType, click.action, null, changedSlots);
session.sendDownstreamPacket(clickPacket);
}
} else {
// Disallowed; let's revert
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
}
}
}
} }
case 2 -> { case 2 -> {
int blockState = session.getGameMode() == GameMode.CREATIVE ? int blockState = session.getGameMode() == GameMode.CREATIVE ?

View file

@ -58,14 +58,14 @@ public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTran
// Build the NBT data for the update // Build the NBT data for the update
NbtMapBuilder builder = NbtMap.builder(); NbtMapBuilder builder = NbtMap.builder();
builder.putInt("dim", DimensionUtils.javaToBedrock(pos.getDimension())); builder.putInt("dim", DimensionUtils.javaToBedrock(pos.dimension()));
builder.putString("id", "0x" + String.format("%08X", packet.getTrackingId())); builder.putString("id", "0x" + String.format("%08X", packet.getTrackingId()));
builder.putByte("version", (byte) 1); // Not sure what this is for builder.putByte("version", (byte) 1); // Not sure what this is for
builder.putByte("status", (byte) 0); // Not sure what this is for builder.putByte("status", (byte) 0); // Not sure what this is for
// Build the position for the update // Build the position for the update
builder.putList("pos", NbtType.INT, pos.getX(), pos.getY(), pos.getZ()); builder.putList("pos", NbtType.INT, pos.x(), pos.y(), pos.z());
broadcastPacket.setTag(builder.build()); broadcastPacket.setTag(builder.build());
session.sendUpstreamPacket(broadcastPacket); session.sendUpstreamPacket(broadcastPacket);

View file

@ -0,0 +1,47 @@
/*
* 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.translator.protocol.bedrock;
import com.nukkitx.protocol.bedrock.packet.RequestChunkRadiusPacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
/**
* Sent when the client updates its desired render distance.
*/
@Translator(packet = RequestChunkRadiusPacket.class)
public class BedrockRequestChunkRadiusTranslator extends PacketTranslator<RequestChunkRadiusPacket> {
@Override
public void translate(GeyserSession session, RequestChunkRadiusPacket packet) {
session.setClientRenderDistance(packet.getRadius());
if (session.isLoggedIn()) {
session.sendJavaClientSettings();
}
}
}

View file

@ -27,10 +27,7 @@ package org.geysermc.geyser.translator.protocol.bedrock;
import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.data.game.ClientCommand;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import com.nukkitx.protocol.bedrock.packet.RespawnPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
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;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
@ -41,28 +38,6 @@ public class BedrockRespawnTranslator extends PacketTranslator<RespawnPacket> {
@Override @Override
public void translate(GeyserSession session, RespawnPacket packet) { public void translate(GeyserSession session, RespawnPacket packet) {
if (packet.getState() == RespawnPacket.State.CLIENT_READY) { if (packet.getState() == RespawnPacket.State.CLIENT_READY) {
// Previously we only sent the respawn packet before the server finished loading
// The message included was 'Otherwise when immediate respawn is on the client never loads'
// But I assume the new if statement below fixes that problem
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.setRuntimeEntityId(0);
respawnPacket.setPosition(Vector3f.ZERO);
respawnPacket.setState(RespawnPacket.State.SERVER_READY);
session.sendUpstreamPacket(respawnPacket);
if (session.isSpawned()) {
// Client might be stuck; resend spawn information
PlayerEntity entity = session.getPlayerEntity();
entity.updateBedrockMetadata(); // TODO test?
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
movePlayerPacket.setPosition(entity.getPosition());
movePlayerPacket.setRotation(entity.getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN);
session.sendUpstreamPacket(movePlayerPacket);
}
ServerboundClientCommandPacket javaRespawnPacket = new ServerboundClientCommandPacket(ClientCommand.RESPAWN); ServerboundClientCommandPacket javaRespawnPacket = new ServerboundClientCommandPacket(ClientCommand.RESPAWN);
session.sendDownstreamPacket(javaRespawnPacket); session.sendDownstreamPacket(javaRespawnPacket);
} }

View file

@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket;
import com.nukkitx.protocol.bedrock.packet.TextPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
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.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.translator.text.MessageTranslator;
@ -44,6 +45,18 @@ public class BedrockTextTranslator extends PacketTranslator<TextPacket> {
return; return;
} }
if (message.indexOf(ChatColor.ESCAPE) != -1) {
// Filter out all escape characters - Java doesn't let you type these
StringBuilder builder = new StringBuilder();
for (int i = 0; i < message.length(); i++) {
char c = message.charAt(i);
if (c != ChatColor.ESCAPE) {
builder.append(c);
}
}
message = builder.toString();
}
if (MessageTranslator.isTooLong(message, session)) { if (MessageTranslator.isTooLong(message, session)) {
return; return;
} }

View file

@ -25,31 +25,25 @@
package org.geysermc.geyser.translator.protocol.java; package org.geysermc.geyser.translator.protocol.java;
import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference;
import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility;
import com.github.steveice10.mc.protocol.data.game.setting.SkinPart;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientInformationPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCustomPayloadPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCustomPayloadPacket;
import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.GameRuleData;
import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
import org.geysermc.geyser.session.auth.AuthType; import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket;
import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket;
import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.auth.AuthType;
import org.geysermc.geyser.translator.level.BiomeTranslator;
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.geyser.translator.level.BiomeTranslator;
import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.DimensionUtils;
import org.geysermc.geyser.util.PluginMessageUtils; import org.geysermc.geyser.util.PluginMessageUtils;
import java.util.Arrays;
import java.util.List;
@Translator(packet = ClientboundLoginPacket.class) @Translator(packet = ClientboundLoginPacket.class)
public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket> { public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket> {
private static final List<SkinPart> SKIN_PART_VALUES = Arrays.asList(SkinPart.values());
@Override @Override
public void translate(GeyserSession session, ClientboundLoginPacket packet) { public void translate(GeyserSession session, ClientboundLoginPacket packet) {
@ -99,13 +93,10 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
session.setReducedDebugInfo(packet.isReducedDebugInfo()); session.setReducedDebugInfo(packet.isReducedDebugInfo());
session.setRenderDistance(packet.getViewDistance()); session.setServerRenderDistance(packet.getViewDistance());
// We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc
String locale = session.locale();
// TODO customize // TODO customize
ServerboundClientInformationPacket infoPacket = new ServerboundClientInformationPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, SKIN_PART_VALUES, HandPreference.RIGHT_HAND, false, true); session.sendJavaClientSettings();
session.sendDownstreamPacket(infoPacket);
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData())); session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));

View file

@ -31,7 +31,7 @@ import org.geysermc.geyser.session.GeyserSession;
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 java.util.Arrays; import java.util.Collections;
/** /**
* Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage) * Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage)
@ -42,9 +42,11 @@ public class JavaRecipeTranslator extends PacketTranslator<ClientboundRecipePack
@Override @Override
public void translate(GeyserSession session, ClientboundRecipePacket packet) { public void translate(GeyserSession session, ClientboundRecipePacket packet) {
if (packet.getAction() == UnlockRecipesAction.REMOVE) { if (packet.getAction() == UnlockRecipesAction.REMOVE) {
session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes())); for (String identifier : packet.getRecipes()) {
session.getUnlockedRecipes().remove(identifier);
}
} else { } else {
session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes())); Collections.addAll(session.getUnlockedRecipes(), packet.getRecipes());
} }
} }
} }

View file

@ -46,6 +46,8 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
public void translate(GeyserSession session, ClientboundRespawnPacket packet) { public void translate(GeyserSession session, ClientboundRespawnPacket packet) {
SessionPlayerEntity entity = session.getPlayerEntity(); SessionPlayerEntity entity = session.getPlayerEntity();
session.setSpawned(false);
entity.setHealth(entity.getMaxHealth()); entity.setHealth(entity.getMaxHealth());
entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute()); entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute());

View file

@ -31,12 +31,14 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.SmithingRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.StoneCuttingRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.StoneCuttingRecipeData;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket;
import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMap;
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket;
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.ints.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -76,6 +78,8 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
// Get the last known network ID (first used for the pregenerated recipes) and increment from there. // Get the last known network ID (first used for the pregenerated recipes) and increment from there.
int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1; int netId = InventoryUtils.LAST_RECIPE_NET_ID + 1;
boolean applySmithingRecipes = session.getUpstream().getProtocolVersion() >= Bedrock_v486.V486_CODEC.getProtocolVersion();
Int2ObjectMap<Recipe> recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion())); Int2ObjectMap<Recipe> recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion()));
Int2ObjectMap<List<StoneCuttingRecipeData>> unsortedStonecutterData = new Int2ObjectOpenHashMap<>(); Int2ObjectMap<List<StoneCuttingRecipeData>> unsortedStonecutterData = new Int2ObjectOpenHashMap<>();
CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
@ -128,6 +132,27 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
data.add(stoneCuttingData); data.add(stoneCuttingData);
// Save for processing after all recipes have been received // Save for processing after all recipes have been received
} }
case SMITHING -> {
// Required to translate these as of 1.18.10, or else they cannot be crafted
if (!applySmithingRecipes) {
continue;
}
SmithingRecipeData recipeData = (SmithingRecipeData) recipe.getData();
ItemData output = ItemTranslator.translateToBedrock(session, recipeData.getResult());
for (ItemStack base : recipeData.getBase().getOptions()) {
ItemData bedrockBase = ItemTranslator.translateToBedrock(session, base);
for (ItemStack addition : recipeData.getAddition().getOptions()) {
ItemData bedrockAddition = ItemTranslator.translateToBedrock(session, addition);
UUID uuid = UUID.randomUUID();
craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(),
Arrays.asList(bedrockBase, bedrockAddition),
Collections.singletonList(output), uuid, "smithing_table", 2, netId++));
}
}
}
default -> { default -> {
List<CraftingData> craftingData = recipeTypes.get(recipe.getType()); List<CraftingData> craftingData = recipeTypes.get(recipe.getType());
if (craftingData != null) { if (craftingData != null) {

View file

@ -245,8 +245,8 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
float baseX = position.getX(); float baseX = position.getX();
float baseY = position.getY(); float baseY = position.getY();
float baseZ = position.getZ(); float baseZ = position.getZ();
float height = entity.getDefinition().height(); float height = entity.getBoundingBoxHeight();
float width = entity.getDefinition().width(); float width = entity.getBoundingBoxWidth();
Random random = ThreadLocalRandom.current(); Random random = ThreadLocalRandom.current();
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
// Reconstruct the Java Edition (1.18.1) logic, but in floats // Reconstruct the Java Edition (1.18.1) logic, but in floats

View file

@ -27,12 +27,12 @@ package org.geysermc.geyser.translator.protocol.java.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.InteractiveTagManager;
import org.geysermc.geyser.entity.type.Entity;
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;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.entity.InteractiveTagManager;
@Translator(packet = ClientboundSetEntityDataPacket.class) @Translator(packet = ClientboundSetEntityDataPacket.class)
public class JavaSetEntityDataTranslator extends PacketTranslator<ClientboundSetEntityDataPacket> { public class JavaSetEntityDataTranslator extends PacketTranslator<ClientboundSetEntityDataPacket> {

View file

@ -86,13 +86,14 @@ public class JavaPlayerPositionTranslator extends PacketTranslator<ClientboundPl
ChunkUtils.updateChunkPosition(session, pos.toInt()); ChunkUtils.updateChunkPosition(session, pos.toInt());
session.getGeyser().getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ())); if (session.getGeyser().getConfig().isDebugMode()) {
session.getGeyser().getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ()));
}
return; return;
} }
Entity vehicle = session.getPlayerEntity().getVehicle(); Entity vehicle;
if (packet.isDismountVehicle() && vehicle != null) { if (packet.isDismountVehicle() && (vehicle = session.getPlayerEntity().getVehicle()) != null) {
SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); SetEntityLinkPacket linkPacket = new SetEntityLinkPacket();
linkPacket.setEntityLink(new EntityLinkData(vehicle.getGeyserId(), entity.getGeyserId(), EntityLinkData.Type.REMOVE, false, false)); linkPacket.setEntityLink(new EntityLinkData(vehicle.getGeyserId(), entity.getGeyserId(), EntityLinkData.Type.REMOVE, false, false));
session.sendUpstreamPacket(linkPacket); session.sendUpstreamPacket(linkPacket);

View file

@ -26,6 +26,7 @@
package org.geysermc.geyser.translator.protocol.java.inventory; package org.geysermc.geyser.translator.protocol.java.inventory;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetContentPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetContentPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
@ -43,9 +44,26 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
if (inventory == null) if (inventory == null)
return; return;
inventory.setStateId(packet.getStateId()); int inventorySize = inventory.getSize();
for (int i = 0; i < packet.getItems().length; i++) { for (int i = 0; i < packet.getItems().length; i++) {
if (i > inventorySize) {
GeyserImpl geyser = session.getGeyser();
geyser.getLogger().warning("ClientboundContainerSetContentPacket sent to " + session.name()
+ " that exceeds inventory size!");
if (geyser.getConfig().isDebugMode()) {
geyser.getLogger().debug(packet);
geyser.getLogger().debug(inventory);
}
InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) {
translator.updateInventory(session, inventory);
}
// 1.18.1 behavior: the previous items will be correctly set, but the state ID and carried item will not
// as this produces a stack trace on the client.
// If Java processes this correctly in the future, we can revert this behavior
return;
}
GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]); GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]);
inventory.setItem(i, newItem, session); inventory.setItem(i, newItem, session);
} }
@ -55,6 +73,10 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
translator.updateInventory(session, inventory); translator.updateInventory(session, inventory);
} }
int stateId = packet.getStateId();
session.setEmulatePost1_16Logic(stateId > 0 || stateId != inventory.getStateId());
inventory.setStateId(stateId);
session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session); session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session);
InventoryUtils.updateCursor(session); InventoryUtils.updateCursor(session);
} }

View file

@ -30,7 +30,6 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
@ -40,17 +39,15 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
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.Translator;
import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator;
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator; import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.geyser.util.InventoryUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -72,14 +69,16 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
return; return;
// Intentional behavior here below the cursor; Minecraft 1.18.1 also does this. // Intentional behavior here below the cursor; Minecraft 1.18.1 also does this.
inventory.setStateId(packet.getStateId()); int stateId = packet.getStateId();
session.setEmulatePost1_16Logic(stateId > 0 || stateId != inventory.getStateId());
inventory.setStateId(stateId);
InventoryTranslator translator = session.getInventoryTranslator(); InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) { if (translator != null) {
if (session.getCraftingGridFuture() != null) { if (session.getCraftingGridFuture() != null) {
session.getCraftingGridFuture().cancel(false); session.getCraftingGridFuture().cancel(false);
} }
session.setCraftingGridFuture(session.scheduleInEventLoop(() -> updateCraftingGrid(session, packet, inventory, translator), 150, TimeUnit.MILLISECONDS)); updateCraftingGrid(session, packet.getSlot(), packet.getItem(), inventory, translator);
GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) { if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) {
@ -93,21 +92,23 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
} }
} }
private static void updateCraftingGrid(GeyserSession session, ClientboundContainerSetSlotPacket packet, Inventory inventory, InventoryTranslator translator) { /**
if (packet.getSlot() == 0) { * Checks for a changed output slot in the crafting grid, and ensures Bedrock sees the recipe.
int gridSize; */
if (translator instanceof PlayerInventoryTranslator) { private static void updateCraftingGrid(GeyserSession session, int slot, ItemStack item, Inventory inventory, InventoryTranslator translator) {
gridSize = 4; if (slot != 0) {
} else if (translator instanceof CraftingInventoryTranslator) { return;
gridSize = 9; }
} else { int gridSize = translator.getGridSize();
return; if (gridSize == -1) {
} return;
}
if (packet.getItem() == null || packet.getItem().getId() == 0) { if (item == null || item.getId() == 0) {
return; return;
} }
session.setCraftingGridFuture(session.scheduleInEventLoop(() -> {
int offset = gridSize == 4 ? 28 : 32; int offset = gridSize == 4 ? 28 : 32;
int gridDimensions = gridSize == 4 ? 2 : 3; int gridDimensions = gridSize == 4 ? 2 : 3;
int firstRow = -1, height = -1; int firstRow = -1, height = -1;
@ -135,62 +136,10 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
height += -firstRow + 1; height += -firstRow + 1;
width += -firstCol + 1; width += -firstCol + 1;
recipes: if (InventoryUtils.getValidRecipe(session, item, inventory::getItem, gridDimensions, firstRow,
for (Recipe recipe : session.getCraftingRecipes().values()) { height, firstCol, width) != null) {
if (recipe.getType() == RecipeType.CRAFTING_SHAPED) { // Recipe is already present on the client; don't send packet
ShapedRecipeData data = (ShapedRecipeData) recipe.getData(); return;
if (!data.getResult().equals(packet.getItem())) {
continue;
}
if (data.getWidth() != width || data.getHeight() != height || width * height != data.getIngredients().length) {
continue;
}
Ingredient[] ingredients = data.getIngredients();
if (!testShapedRecipe(ingredients, inventory, gridDimensions, firstRow, height, firstCol, width)) {
Ingredient[] mirroredIngredients = new Ingredient[data.getIngredients().length];
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
mirroredIngredients[col + (row * width)] = ingredients[(width - 1 - col) + (row * width)];
}
}
if (Arrays.equals(ingredients, mirroredIngredients) ||
!testShapedRecipe(mirroredIngredients, inventory, gridDimensions, firstRow, height, firstCol, width)) {
continue;
}
}
// Recipe is had, don't sent packet
return;
} else if (recipe.getType() == RecipeType.CRAFTING_SHAPELESS) {
ShapelessRecipeData data = (ShapelessRecipeData) recipe.getData();
if (!data.getResult().equals(packet.getItem())) {
continue;
}
for (int i = 0; i < data.getIngredients().length; i++) {
Ingredient ingredient = data.getIngredients()[i];
for (ItemStack itemStack : ingredient.getOptions()) {
boolean inventoryHasItem = false;
for (int j = 0; j < inventory.getSize(); j++) {
GeyserItemStack geyserItemStack = inventory.getItem(j);
if (geyserItemStack.isEmpty()) {
inventoryHasItem = itemStack == null || itemStack.getId() == 0;
if (inventoryHasItem) {
break;
}
} else if (itemStack.equals(geyserItemStack.getItemStack(1))) {
inventoryHasItem = true;
break;
}
}
if (!inventoryHasItem) {
continue recipes;
}
}
}
// Recipe is had, don't sent packet
return;
}
} }
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
@ -216,7 +165,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
} }
} }
ShapedRecipeData data = new ShapedRecipeData(width, height, "", javaIngredients, packet.getItem()); ShapedRecipeData data = new ShapedRecipeData(width, height, "", javaIngredients, item);
// Cache this recipe so we know the client has received it // Cache this recipe so we know the client has received it
session.getCraftingRecipes().put(newRecipeId, new Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); session.getCraftingRecipes().put(newRecipeId, new Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data));
@ -226,7 +175,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
width, width,
height, height,
Arrays.asList(ingredients), Arrays.asList(ingredients),
Collections.singletonList(ItemTranslator.translateToBedrock(session, packet.getItem())), Collections.singletonList(ItemTranslator.translateToBedrock(session, item)),
uuid, uuid,
"crafting_table", "crafting_table",
0, 0,
@ -246,33 +195,6 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
index++; index++;
} }
} }
} }, 150, TimeUnit.MILLISECONDS));
}
private static boolean testShapedRecipe(Ingredient[] ingredients, Inventory inventory, int gridDimensions, int firstRow, int height, int firstCol, int width) {
int ingredientIndex = 0;
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1);
Ingredient ingredient = ingredients[ingredientIndex++];
if (ingredient.getOptions().length == 0) {
if (!geyserItemStack.isEmpty()) {
return false;
}
} else {
boolean inventoryHasItem = false;
for (ItemStack item : ingredient.getOptions()) {
if (Objects.equals(geyserItemStack.getItemStack(1), item)) {
inventoryHasItem = true;
break;
}
}
if (!inventoryHasItem) {
return false;
}
}
}
}
return true;
} }
} }

View file

@ -43,6 +43,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
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.geyser.util.MathUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -99,16 +100,17 @@ public class JavaMerchantOffersTranslator extends PacketTranslator<ClientboundMe
recipe.putInt("maxUses", trade.isTradeDisabled() ? 0 : trade.getMaxUses()); recipe.putInt("maxUses", trade.isTradeDisabled() ? 0 : trade.getMaxUses());
recipe.putInt("traderExp", trade.getXp()); recipe.putInt("traderExp", trade.getXp());
recipe.putFloat("priceMultiplierA", trade.getPriceMultiplier()); recipe.putFloat("priceMultiplierA", trade.getPriceMultiplier());
recipe.put("sell", getItemTag(session, trade.getOutput(), 0));
recipe.putFloat("priceMultiplierB", 0.0f); recipe.putFloat("priceMultiplierB", 0.0f);
recipe.putInt("buyCountB", trade.getSecondInput() != null ? trade.getSecondInput().getAmount() : 0); recipe.put("sell", getItemTag(session, trade.getOutput()));
recipe.putInt("buyCountA", trade.getFirstInput().getAmount());
recipe.putInt("demand", trade.getDemand()); // The buy count before demand and special price adjustments
recipe.putInt("buyCountA", Math.max(trade.getFirstInput().getAmount(), 0));
recipe.putInt("buyCountB", trade.getSecondInput() != null ? Math.max(trade.getSecondInput().getAmount(), 0) : 0);
recipe.putInt("demand", trade.getDemand()); // Seems to have no effect
recipe.putInt("tier", packet.getVillagerLevel() > 0 ? packet.getVillagerLevel() - 1 : 0); // -1 crashes client recipe.putInt("tier", packet.getVillagerLevel() > 0 ? packet.getVillagerLevel() - 1 : 0); // -1 crashes client
recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice())); recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice(), trade.getDemand(), trade.getPriceMultiplier()));
if (trade.getSecondInput() != null) { recipe.put("buyB", getItemTag(session, trade.getSecondInput()));
recipe.put("buyB", getItemTag(session, trade.getSecondInput(), 0));
}
recipe.putInt("uses", trade.getNumUses()); recipe.putInt("uses", trade.getNumUses());
recipe.putByte("rewardExp", (byte) 1); recipe.putByte("rewardExp", (byte) 1);
tags.add(recipe.build()); tags.add(recipe.build());
@ -144,12 +146,31 @@ public class JavaMerchantOffersTranslator extends PacketTranslator<ClientboundMe
session.sendUpstreamPacket(updateTradePacket); session.sendUpstreamPacket(updateTradePacket);
} }
private static NbtMap getItemTag(GeyserSession session, ItemStack stack, int specialPrice) { private static NbtMap getItemTag(GeyserSession session, ItemStack stack) {
ItemData itemData = ItemTranslator.translateToBedrock(session, stack); if (stack == null || stack.getAmount() <= 0) { // Negative item counts appear as air on Java
return NbtMap.EMPTY;
}
return getItemTag(session, stack, session.getItemMappings().getMapping(stack), stack.getAmount());
}
private static NbtMap getItemTag(GeyserSession session, ItemStack stack, int specialPrice, int demand, float priceMultiplier) {
if (stack == null || stack.getAmount() <= 0) { // Negative item counts appear as air on Java
return NbtMap.EMPTY;
}
ItemMapping mapping = session.getItemMappings().getMapping(stack); ItemMapping mapping = session.getItemMappings().getMapping(stack);
// Bedrock expects all price adjustments to be applied to the item's count
int count = stack.getAmount() + ((int) Math.max(Math.floor(stack.getAmount() * demand * priceMultiplier), 0)) + specialPrice;
count = MathUtils.constrain(count, 1, mapping.getStackSize());
return getItemTag(session, stack, mapping, count);
}
private static NbtMap getItemTag(GeyserSession session, ItemStack stack, ItemMapping mapping, int count) {
ItemData itemData = ItemTranslator.translateToBedrock(session, stack);
NbtMapBuilder builder = NbtMap.builder(); NbtMapBuilder builder = NbtMap.builder();
builder.putByte("Count", (byte) (Math.max(itemData.getCount() + specialPrice, 1))); builder.putByte("Count", (byte) count);
builder.putShort("Damage", (short) itemData.getDamage()); builder.putShort("Damage", (short) itemData.getDamage());
builder.putString("Name", mapping.getBedrockIdentifier()); builder.putString("Name", mapping.getBedrockIdentifier());
if (itemData.getTag() != null) { if (itemData.getTag() != null) {

View file

@ -25,14 +25,7 @@
package org.geysermc.geyser.translator.protocol.java.level; package org.geysermc.geyser.translator.protocol.java.level;
import com.github.steveice10.mc.protocol.data.game.level.event.BonemealGrowEventData; import com.github.steveice10.mc.protocol.data.game.level.event.*;
import com.github.steveice10.mc.protocol.data.game.level.event.BreakBlockEventData;
import com.github.steveice10.mc.protocol.data.game.level.event.BreakPotionEventData;
import com.github.steveice10.mc.protocol.data.game.level.event.ComposterEventData;
import com.github.steveice10.mc.protocol.data.game.level.event.DragonFireballEventData;
import com.github.steveice10.mc.protocol.data.game.level.event.ParticleEvent;
import com.github.steveice10.mc.protocol.data.game.level.event.RecordEventData;
import com.github.steveice10.mc.protocol.data.game.level.event.SmokeEventData;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.LevelEventType;
@ -40,14 +33,13 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
import com.nukkitx.protocol.bedrock.packet.TextPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket;
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
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.geyser.translator.level.event.LevelEventTranslator;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.text.MinecraftLocale;
import java.util.Collections; import java.util.Collections;
import java.util.Locale; import java.util.Locale;
@ -218,8 +210,7 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
case BREAK_EYE_OF_ENDER -> effectPacket.setType(LevelEventType.PARTICLE_EYE_OF_ENDER_DEATH); case BREAK_EYE_OF_ENDER -> effectPacket.setType(LevelEventType.PARTICLE_EYE_OF_ENDER_DEATH);
case MOB_SPAWN -> effectPacket.setType(LevelEventType.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java case MOB_SPAWN -> effectPacket.setType(LevelEventType.PARTICLE_MOB_BLOCK_SPAWN); // TODO: Check, but I don't think I really verified this ever went into effect on Java
case BONEMEAL_GROW_WITH_SOUND, BONEMEAL_GROW -> { case BONEMEAL_GROW_WITH_SOUND, BONEMEAL_GROW -> {
effectPacket.setType((particleEvent == ParticleEvent.BONEMEAL_GROW effectPacket.setType(particleEvent == ParticleEvent.BONEMEAL_GROW ? LevelEventType.PARTICLE_TURTLE_EGG : LevelEventType.PARTICLE_CROP_GROWTH);
&& session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) ? LevelEventType.PARTICLE_TURTLE_EGG : LevelEventType.PARTICLE_CROP_GROWTH);
BonemealGrowEventData growEventData = (BonemealGrowEventData) packet.getData(); BonemealGrowEventData growEventData = (BonemealGrowEventData) packet.getData();
effectPacket.setData(growEventData.getParticleCount()); effectPacket.setData(growEventData.getParticleCount());

View file

@ -35,6 +35,6 @@ public class JavaSetChunkCacheRadiusTranslator extends PacketTranslator<Clientbo
@Override @Override
public void translate(GeyserSession session, ClientboundSetChunkCacheRadiusPacket packet) { public void translate(GeyserSession session, ClientboundSetChunkCacheRadiusPacket packet) {
session.setRenderDistance(packet.getViewDistance()); session.setServerRenderDistance(packet.getViewDistance());
} }
} }

View file

@ -121,7 +121,7 @@ public class ChunkUtils {
if (chunkPos == null || !chunkPos.equals(newChunkPos)) { if (chunkPos == null || !chunkPos.equals(newChunkPos)) {
NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket(); NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket();
chunkPublisherUpdatePacket.setPosition(position); chunkPublisherUpdatePacket.setPosition(position);
chunkPublisherUpdatePacket.setRadius(session.getRenderDistance() << 4); chunkPublisherUpdatePacket.setRadius(session.getServerRenderDistance() << 4);
session.sendUpstreamPacket(chunkPublisherUpdatePacket); session.sendUpstreamPacket(chunkPublisherUpdatePacket);
session.setLastChunkPosition(newChunkPos); session.setLastChunkPosition(newChunkPos);

View file

@ -169,8 +169,8 @@ public class FileUtils {
* @return The byte array of the file * @return The byte array of the file
*/ */
public static byte[] readAllBytes(File file) { public static byte[] readAllBytes(File file) {
try (InputStream inputStream = new FileInputStream(file)) { try (InputStream stream = new FileInputStream(file)) {
return readAllBytes(inputStream); return stream.readAllBytes();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Cannot read " + file); throw new RuntimeException("Cannot read " + file);
} }
@ -182,30 +182,12 @@ public class FileUtils {
*/ */
public static byte[] readAllBytes(String resource) { public static byte[] readAllBytes(String resource) {
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(resource)) { try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(resource)) {
return readAllBytes(stream); return stream.readAllBytes();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Error while trying to read internal input stream!", e); throw new RuntimeException("Error while trying to read internal input stream!", e);
} }
} }
/**
* @param stream the InputStream to read off of
* @return the byte array of an InputStream
*/
public static byte[] readAllBytes(InputStream stream) {
try {
int size = stream.available();
byte[] bytes = new byte[size];
try (BufferedInputStream buf = new BufferedInputStream(stream)) {
//noinspection ResultOfMethodCallIgnored
buf.read(bytes, 0, bytes.length);
}
return bytes;
} catch (IOException e) {
throw new RuntimeException("Error while trying to read input stream!", e);
}
}
/** /**
* Read the lines of a file and return it as a stream * Read the lines of a file and return it as a stream
* *

View file

@ -27,6 +27,11 @@ package org.geysermc.geyser.util;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundPickItemPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
@ -41,6 +46,7 @@ import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.click.Click;
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.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
@ -50,6 +56,8 @@ import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTransl
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -330,4 +338,131 @@ public class InventoryUtils {
session.sendUpstreamPacket(hotbarPacket); session.sendUpstreamPacket(hotbarPacket);
// No need to send a Java packet as Bedrock sends a confirmation packet back that we translate // No need to send a Java packet as Bedrock sends a confirmation packet back that we translate
} }
@Nullable
public static Click getClickForHotbarSwap(int slot) {
return switch (slot) {
case 0 -> Click.SWAP_TO_HOTBAR_1;
case 1 -> Click.SWAP_TO_HOTBAR_2;
case 2 -> Click.SWAP_TO_HOTBAR_3;
case 3 -> Click.SWAP_TO_HOTBAR_4;
case 4 -> Click.SWAP_TO_HOTBAR_5;
case 5 -> Click.SWAP_TO_HOTBAR_6;
case 6 -> Click.SWAP_TO_HOTBAR_7;
case 7 -> Click.SWAP_TO_HOTBAR_8;
case 8 -> Click.SWAP_TO_HOTBAR_9;
default -> null;
};
}
/**
* Test all known recipes to find a valid match
*
* @param output if not null, the recipe has to output this item
*/
@Nullable
public static Recipe getValidRecipe(final GeyserSession session, final @Nullable ItemStack output, final IntFunction<GeyserItemStack> inventoryGetter,
final int gridDimensions, final int firstRow, final int height, final int firstCol, final int width) {
int nonAirCount = 0; // Used for shapeless recipes for amount of items needed in recipe
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
if (!inventoryGetter.apply(col + (row * gridDimensions) + 1).isEmpty()) {
nonAirCount++;
}
}
}
recipes:
for (Recipe recipe : session.getCraftingRecipes().values()) {
if (recipe.getType() == RecipeType.CRAFTING_SHAPED) {
ShapedRecipeData data = (ShapedRecipeData) recipe.getData();
if (output != null && !data.getResult().equals(output)) {
continue;
}
Ingredient[] ingredients = data.getIngredients();
if (data.getWidth() != width || data.getHeight() != height || width * height != ingredients.length) {
continue;
}
if (!testShapedRecipe(ingredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) {
Ingredient[] mirroredIngredients = new Ingredient[ingredients.length];
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
mirroredIngredients[col + (row * width)] = ingredients[(width - 1 - col) + (row * width)];
}
}
if (Arrays.equals(ingredients, mirroredIngredients) ||
!testShapedRecipe(mirroredIngredients, inventoryGetter, gridDimensions, firstRow, height, firstCol, width)) {
continue;
}
}
return recipe;
} else if (recipe.getType() == RecipeType.CRAFTING_SHAPELESS) {
ShapelessRecipeData data = (ShapelessRecipeData) recipe.getData();
if (output != null && !data.getResult().equals(output)) {
continue;
}
if (nonAirCount != data.getIngredients().length) {
// There is an amount of items on the crafting table that is not the same as the ingredient count so this is invalid
continue;
}
for (int i = 0; i < data.getIngredients().length; i++) {
Ingredient ingredient = data.getIngredients()[i];
for (ItemStack itemStack : ingredient.getOptions()) {
boolean inventoryHasItem = false;
// Iterate only over the crafting table to find this item
crafting:
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1);
if (geyserItemStack.isEmpty()) {
inventoryHasItem = itemStack == null || itemStack.getId() == 0;
if (inventoryHasItem) {
break crafting;
}
} else if (itemStack.equals(geyserItemStack.getItemStack(1))) {
inventoryHasItem = true;
break crafting;
}
}
}
if (!inventoryHasItem) {
continue recipes;
}
}
}
return recipe;
}
}
return null;
}
private static boolean testShapedRecipe(final Ingredient[] ingredients, final IntFunction<GeyserItemStack> inventoryGetter,
final int gridDimensions, final int firstRow, final int height, final int firstCol, final int width) {
int ingredientIndex = 0;
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventoryGetter.apply(col + (row * gridDimensions) + 1);
Ingredient ingredient = ingredients[ingredientIndex++];
if (ingredient.getOptions().length == 0) {
if (!geyserItemStack.isEmpty()) {
return false;
}
} else {
boolean inventoryHasItem = false;
for (ItemStack item : ingredient.getOptions()) {
if (Objects.equals(geyserItemStack.getItemStack(1), item)) {
inventoryHasItem = true;
break;
}
}
if (!inventoryHasItem) {
return false;
}
}
}
}
return true;
}
} }

View file

@ -51,6 +51,10 @@
"name" : "minecraft:air", "name" : "minecraft:air",
"id" : -158 "id" : -158
}, },
{
"name" : "minecraft:allay_spawn_egg",
"id" : 631
},
{ {
"name" : "minecraft:allow", "name" : "minecraft:allow",
"id" : 210 "id" : 210
@ -65,7 +69,7 @@
}, },
{ {
"name" : "minecraft:amethyst_shard", "name" : "minecraft:amethyst_shard",
"id" : 623 "id" : 625
}, },
{ {
"name" : "minecraft:ancient_debris", "name" : "minecraft:ancient_debris",
@ -117,7 +121,7 @@
}, },
{ {
"name" : "minecraft:balloon", "name" : "minecraft:balloon",
"id" : 597 "id" : 598
}, },
{ {
"name" : "minecraft:bamboo", "name" : "minecraft:bamboo",
@ -133,7 +137,7 @@
}, },
{ {
"name" : "minecraft:banner_pattern", "name" : "minecraft:banner_pattern",
"id" : 627 "id" : 635
}, },
{ {
"name" : "minecraft:barrel", "name" : "minecraft:barrel",
@ -293,7 +297,7 @@
}, },
{ {
"name" : "minecraft:bleach", "name" : "minecraft:bleach",
"id" : 595 "id" : 596
}, },
{ {
"name" : "minecraft:blue_candle", "name" : "minecraft:blue_candle",
@ -317,7 +321,7 @@
}, },
{ {
"name" : "minecraft:boat", "name" : "minecraft:boat",
"id" : 625 "id" : 633
}, },
{ {
"name" : "minecraft:bone", "name" : "minecraft:bone",
@ -429,11 +433,11 @@
}, },
{ {
"name" : "minecraft:camera", "name" : "minecraft:camera",
"id" : 592 "id" : 593
}, },
{ {
"name" : "minecraft:campfire", "name" : "minecraft:campfire",
"id" : 588 "id" : 589
}, },
{ {
"name" : "minecraft:candle", "name" : "minecraft:candle",
@ -493,7 +497,7 @@
}, },
{ {
"name" : "minecraft:chain", "name" : "minecraft:chain",
"id" : 617 "id" : 619
}, },
{ {
"name" : "minecraft:chain_command_block", "name" : "minecraft:chain_command_block",
@ -575,6 +579,10 @@
"name" : "minecraft:clay_ball", "name" : "minecraft:clay_ball",
"id" : 384 "id" : 384
}, },
{
"name" : "minecraft:client_request_placeholder_block",
"id" : -465
},
{ {
"name" : "minecraft:clock", "name" : "minecraft:clock",
"id" : 393 "id" : 393
@ -669,7 +677,7 @@
}, },
{ {
"name" : "minecraft:compound", "name" : "minecraft:compound",
"id" : 593 "id" : 594
}, },
{ {
"name" : "minecraft:concrete", "name" : "minecraft:concrete",
@ -793,7 +801,7 @@
}, },
{ {
"name" : "minecraft:crimson_door", "name" : "minecraft:crimson_door",
"id" : 614 "id" : 616
}, },
{ {
"name" : "minecraft:crimson_double_slab", "name" : "minecraft:crimson_double_slab",
@ -833,7 +841,7 @@
}, },
{ {
"name" : "minecraft:crimson_sign", "name" : "minecraft:crimson_sign",
"id" : 612 "id" : 614
}, },
{ {
"name" : "minecraft:crimson_slab", "name" : "minecraft:crimson_slab",
@ -1169,7 +1177,7 @@
}, },
{ {
"name" : "minecraft:dye", "name" : "minecraft:dye",
"id" : 626 "id" : 634
}, },
{ {
"name" : "minecraft:egg", "name" : "minecraft:egg",
@ -1697,7 +1705,7 @@
}, },
{ {
"name" : "minecraft:end_crystal", "name" : "minecraft:end_crystal",
"id" : 629 "id" : 637
}, },
{ {
"name" : "minecraft:end_gateway", "name" : "minecraft:end_gateway",
@ -1803,6 +1811,10 @@
"name" : "minecraft:fire_charge", "name" : "minecraft:fire_charge",
"id" : 509 "id" : 509
}, },
{
"name" : "minecraft:firefly_spawn_egg",
"id" : 632
},
{ {
"name" : "minecraft:firework_rocket", "name" : "minecraft:firework_rocket",
"id" : 519 "id" : 519
@ -1855,6 +1867,14 @@
"name" : "minecraft:frame", "name" : "minecraft:frame",
"id" : 513 "id" : 513
}, },
{
"name" : "minecraft:frog_egg",
"id" : -468
},
{
"name" : "minecraft:frog_spawn_egg",
"id" : 628
},
{ {
"name" : "minecraft:frosted_ice", "name" : "minecraft:frosted_ice",
"id" : 207 "id" : 207
@ -1891,13 +1911,17 @@
"name" : "minecraft:glistering_melon_slice", "name" : "minecraft:glistering_melon_slice",
"id" : 434 "id" : 434
}, },
{
"name" : "minecraft:globe_banner_pattern",
"id" : 588
},
{ {
"name" : "minecraft:glow_berries", "name" : "minecraft:glow_berries",
"id" : 630 "id" : 638
}, },
{ {
"name" : "minecraft:glow_frame", "name" : "minecraft:glow_frame",
"id" : 621 "id" : 623
}, },
{ {
"name" : "minecraft:glow_ink_sac", "name" : "minecraft:glow_ink_sac",
@ -1913,7 +1937,7 @@
}, },
{ {
"name" : "minecraft:glow_stick", "name" : "minecraft:glow_stick",
"id" : 166 "id" : 601
}, },
{ {
"name" : "minecraft:glowingobsidian", "name" : "minecraft:glowingobsidian",
@ -1929,7 +1953,7 @@
}, },
{ {
"name" : "minecraft:goat_horn", "name" : "minecraft:goat_horn",
"id" : 622 "id" : 624
}, },
{ {
"name" : "minecraft:goat_spawn_egg", "name" : "minecraft:goat_spawn_egg",
@ -2109,11 +2133,11 @@
}, },
{ {
"name" : "minecraft:honey_bottle", "name" : "minecraft:honey_bottle",
"id" : 591 "id" : 592
}, },
{ {
"name" : "minecraft:honeycomb", "name" : "minecraft:honeycomb",
"id" : 590 "id" : 591
}, },
{ {
"name" : "minecraft:honeycomb_block", "name" : "minecraft:honeycomb_block",
@ -2141,7 +2165,7 @@
}, },
{ {
"name" : "minecraft:ice_bomb", "name" : "minecraft:ice_bomb",
"id" : 594 "id" : 595
}, },
{ {
"name" : "minecraft:infested_deepslate", "name" : "minecraft:infested_deepslate",
@ -2569,7 +2593,7 @@
}, },
{ {
"name" : "minecraft:lodestone_compass", "name" : "minecraft:lodestone_compass",
"id" : 600 "id" : 602
}, },
{ {
"name" : "minecraft:log", "name" : "minecraft:log",
@ -2613,7 +2637,7 @@
}, },
{ {
"name" : "minecraft:medicine", "name" : "minecraft:medicine",
"id" : 598 "id" : 599
}, },
{ {
"name" : "minecraft:medium_amethyst_bud", "name" : "minecraft:medium_amethyst_bud",
@ -2723,9 +2747,13 @@
"name" : "minecraft:music_disc_mellohi", "name" : "minecraft:music_disc_mellohi",
"id" : 540 "id" : 540
}, },
{
"name" : "minecraft:music_disc_otherside",
"id" : 627
},
{ {
"name" : "minecraft:music_disc_pigstep", "name" : "minecraft:music_disc_pigstep",
"id" : 618 "id" : 620
}, },
{ {
"name" : "minecraft:music_disc_stal", "name" : "minecraft:music_disc_stal",
@ -2751,6 +2779,14 @@
"name" : "minecraft:mycelium", "name" : "minecraft:mycelium",
"id" : 110 "id" : 110
}, },
{
"name" : "minecraft:mysterious_frame",
"id" : -466
},
{
"name" : "minecraft:mysterious_frame_slot",
"id" : -467
},
{ {
"name" : "minecraft:name_tag", "name" : "minecraft:name_tag",
"id" : 548 "id" : 548
@ -2777,7 +2813,7 @@
}, },
{ {
"name" : "minecraft:nether_sprouts", "name" : "minecraft:nether_sprouts",
"id" : 619 "id" : 621
}, },
{ {
"name" : "minecraft:nether_star", "name" : "minecraft:nether_star",
@ -2797,7 +2833,7 @@
}, },
{ {
"name" : "minecraft:netherite_axe", "name" : "minecraft:netherite_axe",
"id" : 605 "id" : 607
}, },
{ {
"name" : "minecraft:netherite_block", "name" : "minecraft:netherite_block",
@ -2805,43 +2841,43 @@
}, },
{ {
"name" : "minecraft:netherite_boots", "name" : "minecraft:netherite_boots",
"id" : 610 "id" : 612
}, },
{ {
"name" : "minecraft:netherite_chestplate", "name" : "minecraft:netherite_chestplate",
"id" : 608 "id" : 610
}, },
{ {
"name" : "minecraft:netherite_helmet", "name" : "minecraft:netherite_helmet",
"id" : 607
},
{
"name" : "minecraft:netherite_hoe",
"id" : 606
},
{
"name" : "minecraft:netherite_ingot",
"id" : 601
},
{
"name" : "minecraft:netherite_leggings",
"id" : 609 "id" : 609
}, },
{ {
"name" : "minecraft:netherite_pickaxe", "name" : "minecraft:netherite_hoe",
"id" : 604 "id" : 608
}, },
{ {
"name" : "minecraft:netherite_scrap", "name" : "minecraft:netherite_ingot",
"id" : 611
},
{
"name" : "minecraft:netherite_shovel",
"id" : 603 "id" : 603
}, },
{
"name" : "minecraft:netherite_leggings",
"id" : 611
},
{
"name" : "minecraft:netherite_pickaxe",
"id" : 606
},
{
"name" : "minecraft:netherite_scrap",
"id" : 613
},
{
"name" : "minecraft:netherite_shovel",
"id" : 605
},
{ {
"name" : "minecraft:netherite_sword", "name" : "minecraft:netherite_sword",
"id" : 602 "id" : 604
}, },
{ {
"name" : "minecraft:netherrack", "name" : "minecraft:netherrack",
@ -2887,6 +2923,10 @@
"name" : "minecraft:ocelot_spawn_egg", "name" : "minecraft:ocelot_spawn_egg",
"id" : 451 "id" : 451
}, },
{
"name" : "minecraft:ochre_froglight",
"id" : -471
},
{ {
"name" : "minecraft:orange_candle", "name" : "minecraft:orange_candle",
"id" : -414 "id" : -414
@ -2943,6 +2983,10 @@
"name" : "minecraft:parrot_spawn_egg", "name" : "minecraft:parrot_spawn_egg",
"id" : 478 "id" : 478
}, },
{
"name" : "minecraft:pearlescent_froglight",
"id" : -469
},
{ {
"name" : "minecraft:phantom_membrane", "name" : "minecraft:phantom_membrane",
"id" : 574 "id" : 574
@ -3257,7 +3301,7 @@
}, },
{ {
"name" : "minecraft:rapid_fertilizer", "name" : "minecraft:rapid_fertilizer",
"id" : 596 "id" : 597
}, },
{ {
"name" : "minecraft:ravager_spawn_egg", "name" : "minecraft:ravager_spawn_egg",
@ -3577,7 +3621,7 @@
}, },
{ {
"name" : "minecraft:soul_campfire", "name" : "minecraft:soul_campfire",
"id" : 620 "id" : 622
}, },
{ {
"name" : "minecraft:soul_fire", "name" : "minecraft:soul_fire",
@ -3601,11 +3645,11 @@
}, },
{ {
"name" : "minecraft:sparkler", "name" : "minecraft:sparkler",
"id" : 599 "id" : 600
}, },
{ {
"name" : "minecraft:spawn_egg", "name" : "minecraft:spawn_egg",
"id" : 628 "id" : 636
}, },
{ {
"name" : "minecraft:spider_eye", "name" : "minecraft:spider_eye",
@ -3669,7 +3713,7 @@
}, },
{ {
"name" : "minecraft:spyglass", "name" : "minecraft:spyglass",
"id" : 624 "id" : 626
}, },
{ {
"name" : "minecraft:squid_spawn_egg", "name" : "minecraft:squid_spawn_egg",
@ -3829,7 +3873,7 @@
}, },
{ {
"name" : "minecraft:suspicious_stew", "name" : "minecraft:suspicious_stew",
"id" : 589 "id" : 590
}, },
{ {
"name" : "minecraft:sweet_berries", "name" : "minecraft:sweet_berries",
@ -3839,6 +3883,14 @@
"name" : "minecraft:sweet_berry_bush", "name" : "minecraft:sweet_berry_bush",
"id" : -207 "id" : -207
}, },
{
"name" : "minecraft:tadpole_bucket",
"id" : 630
},
{
"name" : "minecraft:tadpole_spawn_egg",
"id" : 629
},
{ {
"name" : "minecraft:tallgrass", "name" : "minecraft:tallgrass",
"id" : 31 "id" : 31
@ -3943,6 +3995,10 @@
"name" : "minecraft:unpowered_repeater", "name" : "minecraft:unpowered_repeater",
"id" : 93 "id" : 93
}, },
{
"name" : "minecraft:verdant_froglight",
"id" : -470
},
{ {
"name" : "minecraft:vex_spawn_egg", "name" : "minecraft:vex_spawn_egg",
"id" : 476 "id" : 476
@ -3977,7 +4033,7 @@
}, },
{ {
"name" : "minecraft:warped_door", "name" : "minecraft:warped_door",
"id" : 615 "id" : 617
}, },
{ {
"name" : "minecraft:warped_double_slab", "name" : "minecraft:warped_double_slab",
@ -3997,7 +4053,7 @@
}, },
{ {
"name" : "minecraft:warped_fungus_on_a_stick", "name" : "minecraft:warped_fungus_on_a_stick",
"id" : 616 "id" : 618
}, },
{ {
"name" : "minecraft:warped_hyphae", "name" : "minecraft:warped_hyphae",
@ -4021,7 +4077,7 @@
}, },
{ {
"name" : "minecraft:warped_sign", "name" : "minecraft:warped_sign",
"id" : 613 "id" : 615
}, },
{ {
"name" : "minecraft:warped_slab", "name" : "minecraft:warped_slab",

View file

@ -125,6 +125,13 @@ show-cooldown: title
# Controls if coordinates are shown to players. # Controls if coordinates are shown to players.
show-coordinates: true show-coordinates: true
# Whether Bedrock players are blocked from performing their scaffolding-style bridging.
disable-bedrock-scaffolding: false
# Whether Bedrock players can right-click outside of their inventory to replace armor in their inventory, even if the
# armor slot is already occupied (which Java Edition doesn't allow)
always-quick-change-armor: false
# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind # If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind
# There are three options this can be set to: # There are three options this can be set to:
# disabled - the default/fallback, which doesn't apply this workaround # disabled - the default/fallback, which doesn't apply this workaround

View file

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.geysermc</groupId> <groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId> <artifactId>geyser-parent</artifactId>
<version>2.0.0-SNAPSHOT</version> <version>2.0.1-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Geyser</name> <name>Geyser</name>
<description>Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers.</description> <description>Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers.</description>