Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-19 14:30:17 +01:00
Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/extensions
Dieser Commit ist enthalten in:
Commit
354e87b747
@ -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!
|
||||
|
||||
### 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
|
||||
Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser.
|
||||
|
@ -6,9 +6,9 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ap</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</project>
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>api-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>api-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>base-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>bootstrap-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>bootstrap-bungeecord</artifactId>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- Used for better working with internals without reflection -->
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>bootstrap-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
@ -34,7 +34,7 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>ap</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>bootstrap-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>bootstrap-spigot</artifactId>
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -97,7 +97,7 @@ public class GeyserPistonListener implements Listener {
|
||||
|
||||
int dX = Math.abs(location.getBlockX() - player.getLocation().getBlockX()) >> 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
|
||||
continue;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>bootstrap-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>bootstrap-sponge</artifactId>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>bootstrap-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>bootstrap-standalone</artifactId>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>bootstrap-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>bootstrap-velocity</artifactId>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common</artifactId>
|
||||
|
||||
|
12
core/pom.xml
12
core/pom.xml
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>core</artifactId>
|
||||
|
||||
@ -20,19 +20,19 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>ap</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-api</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- Jackson JSON and YAML serialization -->
|
||||
@ -119,8 +119,8 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.CloudburstMC.Protocol</groupId>
|
||||
<artifactId>bedrock-v475</artifactId>
|
||||
<version>c22aa595</version>
|
||||
<artifactId>bedrock-v486</artifactId>
|
||||
<version>0cd24c0</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
|
@ -64,7 +64,7 @@ public class GeyserSession {
|
||||
}
|
||||
|
||||
public int getRenderDistance() {
|
||||
return this.handle.getRenderDistance();
|
||||
return this.handle.getServerRenderDistance();
|
||||
}
|
||||
|
||||
public boolean isSentSpawnPacket() {
|
||||
|
@ -68,6 +68,7 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.SessionManager;
|
||||
import org.geysermc.geyser.session.auth.AuthType;
|
||||
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
||||
@ -211,6 +212,8 @@ public class GeyserImpl implements GeyserApi {
|
||||
|
||||
ScoreboardUpdater.init();
|
||||
|
||||
SkinProvider.registerCacheImageTask(this);
|
||||
|
||||
ResourcePack.loadPacks();
|
||||
|
||||
this.extensionManager.enableExtensions();
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface GeyserLogger {
|
||||
|
||||
/**
|
||||
@ -78,6 +80,15 @@ public interface GeyserLogger {
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
@ -76,6 +76,10 @@ public interface GeyserConfiguration {
|
||||
|
||||
boolean isShowCoordinates();
|
||||
|
||||
boolean isDisableBedrockScaffolding();
|
||||
|
||||
boolean isAlwaysQuickChangeArmor();
|
||||
|
||||
EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround();
|
||||
|
||||
String getDefaultLocale();
|
||||
|
@ -105,6 +105,12 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
@JsonProperty("show-coordinates")
|
||||
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)
|
||||
@JsonProperty("emote-offhand-workaround")
|
||||
private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED;
|
||||
|
@ -34,7 +34,7 @@ import java.util.Map;
|
||||
/**
|
||||
* 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<>();
|
||||
|
||||
public void put(EntityData entityData, Object value) {
|
||||
|
@ -37,12 +37,11 @@ import com.nukkitx.nbt.NbtMapBuilder;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -85,10 +84,8 @@ public class ItemFrameEntity extends Entity {
|
||||
.putInt("version", session.getBlockMappings().getBlockStateVersion());
|
||||
NbtMapBuilder statesBuilder = NbtMap.builder()
|
||||
.putInt("facing_direction", direction.ordinal())
|
||||
.putByte("item_frame_map_bit", (byte) 0);
|
||||
if (session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) {
|
||||
statesBuilder.putByte("item_frame_photo_bit", (byte) 0);
|
||||
}
|
||||
.putByte("item_frame_map_bit", (byte) 0)
|
||||
.putByte("item_frame_photo_bit", (byte) 0);
|
||||
blockBuilder.put("states", statesBuilder.build());
|
||||
|
||||
bedrockRuntimeId = session.getBlockMappings().getItemFrame(blockBuilder.build());
|
||||
|
@ -136,7 +136,7 @@ public class ArmorStandEntity extends LivingEntity {
|
||||
}
|
||||
|
||||
isSmall = newIsSmall;
|
||||
if (!isMarker) {
|
||||
if (!isMarker && !isInvisible) { // Addition for isInvisible check caused by https://github.com/GeyserMC/Geyser/issues/2780
|
||||
toggleSmallStatus();
|
||||
}
|
||||
}
|
||||
|
@ -382,15 +382,26 @@ public class PlayerEntity extends LivingEntity {
|
||||
@Override
|
||||
protected void setDimensions(Pose pose) {
|
||||
float height;
|
||||
float width;
|
||||
switch (pose) {
|
||||
case SNEAKING -> height = SNEAKING_POSE_HEIGHT;
|
||||
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> height = 0.6f;
|
||||
case SNEAKING -> {
|
||||
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 -> {
|
||||
super.setDimensions(pose);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setBoundingBoxWidth(definition.width());
|
||||
setBoundingBoxWidth(width);
|
||||
setBoundingBoxHeight(height);
|
||||
}
|
||||
|
||||
|
@ -26,10 +26,6 @@
|
||||
package org.geysermc.geyser.inventory;
|
||||
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class BedrockContainerSlot {
|
||||
ContainerSlotType container;
|
||||
int slot;
|
||||
public record BedrockContainerSlot(ContainerSlotType container, int slot) {
|
||||
}
|
||||
|
@ -27,9 +27,11 @@ package org.geysermc.geyser.inventory;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Combination of {@link Inventory} and {@link PlayerInventory}
|
||||
@ -60,7 +62,12 @@ public class Container extends Inventory {
|
||||
}
|
||||
|
||||
@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) {
|
||||
super.setItem(slot, newItem, session);
|
||||
} else {
|
||||
|
@ -31,17 +31,19 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
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;
|
||||
|
||||
@ToString
|
||||
public class Inventory {
|
||||
|
||||
public abstract class Inventory {
|
||||
@Getter
|
||||
protected final int id;
|
||||
|
||||
@ -69,8 +71,7 @@ public class Inventory {
|
||||
protected final ContainerType containerType;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
protected String title;
|
||||
protected final String title;
|
||||
|
||||
protected final GeyserItemStack[] items;
|
||||
|
||||
@ -110,7 +111,9 @@ public class Inventory {
|
||||
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) {
|
||||
session.getGeyser().getLogger().debug("Tried to set an item out of bounds! " + this);
|
||||
return;
|
||||
@ -133,7 +136,9 @@ public class Inventory {
|
||||
|
||||
protected void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) {
|
||||
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());
|
||||
} else {
|
||||
newItem.setNetId(session.getNextItemNetId());
|
||||
|
@ -26,10 +26,12 @@
|
||||
package org.geysermc.geyser.inventory;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class PlayerInventory extends Inventory {
|
||||
/**
|
||||
@ -41,7 +43,7 @@ public class PlayerInventory extends Inventory {
|
||||
private int heldItemSlot;
|
||||
|
||||
@Getter
|
||||
@NonNull
|
||||
@Nonnull
|
||||
private GeyserItemStack cursor = GeyserItemStack.EMPTY;
|
||||
|
||||
public PlayerInventory() {
|
||||
@ -49,7 +51,12 @@ public class PlayerInventory extends Inventory {
|
||||
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);
|
||||
cursor = newCursor;
|
||||
}
|
||||
@ -62,7 +69,7 @@ public class PlayerInventory extends Inventory {
|
||||
return items[36 + heldItemSlot];
|
||||
}
|
||||
|
||||
public void setItemInHand(@NonNull GeyserItemStack item) {
|
||||
public void setItemInHand(@Nonnull GeyserItemStack item) {
|
||||
if (36 + heldItemSlot > this.size) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!");
|
||||
return;
|
||||
|
@ -40,20 +40,22 @@ import org.geysermc.geyser.inventory.SlotType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
public class ClickPlan {
|
||||
public final class ClickPlan {
|
||||
private final List<ClickAction> plan = new ArrayList<>();
|
||||
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 boolean simulating;
|
||||
private boolean finished;
|
||||
|
||||
private final GeyserSession session;
|
||||
private final InventoryTranslator translator;
|
||||
@ -66,16 +68,11 @@ public class ClickPlan {
|
||||
this.inventory = inventory;
|
||||
|
||||
this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize());
|
||||
this.changedItems = null;
|
||||
this.simulatedCursor = session.getPlayerInventory().getCursor().copy();
|
||||
this.simulating = true;
|
||||
this.finished = false;
|
||||
|
||||
if (translator instanceof PlayerInventoryTranslator) {
|
||||
gridSize = 4;
|
||||
} else if (translator instanceof CraftingInventoryTranslator) {
|
||||
gridSize = 9;
|
||||
} else {
|
||||
gridSize = -1;
|
||||
}
|
||||
gridSize = translator.getGridSize();
|
||||
}
|
||||
|
||||
private void resetSimulation() {
|
||||
@ -88,7 +85,7 @@ public class ClickPlan {
|
||||
}
|
||||
|
||||
public void add(Click click, int slot, boolean force) {
|
||||
if (!simulating)
|
||||
if (finished)
|
||||
throw new UnsupportedOperationException("ClickPlan already executed");
|
||||
|
||||
if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) {
|
||||
@ -97,6 +94,8 @@ public class ClickPlan {
|
||||
|
||||
ClickAction action = new ClickAction(click, slot, force);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -112,33 +111,48 @@ public class ClickPlan {
|
||||
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;
|
||||
if (!planIter.hasNext() && refresh) {
|
||||
clickedItemStack = InventoryUtils.REFRESH_ITEM;
|
||||
} else if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) {
|
||||
} else {
|
||||
if (emulatePost1_16Logic) {
|
||||
// The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1)
|
||||
clickedItemStack = simulatedCursor.getItemStack();
|
||||
} else {
|
||||
if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) {
|
||||
clickedItemStack = null;
|
||||
} else {
|
||||
//// The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1)
|
||||
//clickedItemStack = simulatedCursor.getItemStack(); TODO fix - this is the proper behavior but it terribly breaks 1.16.5
|
||||
clickedItemStack = getItem(action.slot).getItemStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!emulatePost1_16Logic) {
|
||||
simulateAction(action);
|
||||
}
|
||||
|
||||
ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket(
|
||||
inventory.getId(),
|
||||
inventory.getStateId(),
|
||||
stateId,
|
||||
action.slot,
|
||||
action.click.actionType,
|
||||
action.click.action,
|
||||
clickedItemStack,
|
||||
Collections.emptyMap() // Anything else we change, at this time, should have a packet sent to address
|
||||
changedItems
|
||||
);
|
||||
|
||||
simulateAction(action);
|
||||
|
||||
session.sendDownstreamPacket(clickPacket);
|
||||
}
|
||||
|
||||
@ -146,19 +160,11 @@ public class ClickPlan {
|
||||
for (Int2ObjectMap.Entry<GeyserItemStack> simulatedSlot : simulatedItems.int2ObjectEntrySet()) {
|
||||
inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session);
|
||||
}
|
||||
simulating = false;
|
||||
finished = true;
|
||||
}
|
||||
|
||||
public GeyserItemStack getItem(int slot) {
|
||||
return getItem(slot, true);
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -166,23 +172,40 @@ public class ClickPlan {
|
||||
}
|
||||
|
||||
private void setItem(int slot, GeyserItemStack item) {
|
||||
if (simulating) {
|
||||
simulatedItems.put(slot, item);
|
||||
} else {
|
||||
inventory.setItem(slot, item, session);
|
||||
}
|
||||
onSlotItemChange(slot, item);
|
||||
}
|
||||
|
||||
private void setCursor(GeyserItemStack item) {
|
||||
if (simulating) {
|
||||
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) {
|
||||
GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor();
|
||||
GeyserItemStack cursor = getCursor();
|
||||
switch (action.click) {
|
||||
case LEFT_OUTSIDE -> {
|
||||
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) {
|
||||
switch (action.click) {
|
||||
case LEFT, RIGHT -> {
|
||||
@ -206,6 +229,7 @@ public class ClickPlan {
|
||||
cursor.add(clicked.getAmount());
|
||||
}
|
||||
reduceCraftingGrid(false);
|
||||
setItem(action.slot, GeyserItemStack.EMPTY); // Matches Java Edition 1.18.1
|
||||
}
|
||||
case LEFT_SHIFT -> reduceCraftingGrid(true);
|
||||
}
|
||||
@ -217,55 +241,55 @@ public class ClickPlan {
|
||||
setItem(action.slot, cursor);
|
||||
} else {
|
||||
setCursor(GeyserItemStack.EMPTY);
|
||||
clicked.add(cursor.getAmount());
|
||||
add(action.slot, clicked, cursor.getAmount());
|
||||
}
|
||||
break;
|
||||
case RIGHT:
|
||||
if (cursor.isEmpty() && !clicked.isEmpty()) {
|
||||
int half = clicked.getAmount() / 2; //smaller half
|
||||
setCursor(clicked.copy(clicked.getAmount() - half)); //larger half
|
||||
clicked.setAmount(half);
|
||||
setAmount(action.slot, clicked, half);
|
||||
} else if (!cursor.isEmpty() && clicked.isEmpty()) {
|
||||
cursor.sub(1);
|
||||
setItem(action.slot, cursor.copy(1));
|
||||
} else if (InventoryUtils.canStack(cursor, clicked)) {
|
||||
cursor.sub(1);
|
||||
clicked.add(1);
|
||||
add(action.slot, clicked, 1);
|
||||
}
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_1:
|
||||
swap(action.slot, 36, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(0), clicked);
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_2:
|
||||
swap(action.slot, 37, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(1), clicked);
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_3:
|
||||
swap(action.slot, 38, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(2), clicked);
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_4:
|
||||
swap(action.slot, 39, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(3), clicked);
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_5:
|
||||
swap(action.slot, 40, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(4), clicked);
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_6:
|
||||
swap(action.slot, 41, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(5), clicked);
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_7:
|
||||
swap(action.slot, 42, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(6), clicked);
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_8:
|
||||
swap(action.slot, 43, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(7), clicked);
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_9:
|
||||
swap(action.slot, 44, clicked);
|
||||
swap(action.slot, inventory.getOffsetForHotbar(8), clicked);
|
||||
break;
|
||||
case LEFT_SHIFT:
|
||||
//TODO
|
||||
break;
|
||||
case DROP_ONE:
|
||||
if (!clicked.isEmpty()) {
|
||||
clicked.sub(1);
|
||||
sub(action.slot, clicked, 1);
|
||||
}
|
||||
break;
|
||||
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}
|
||||
*/
|
||||
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(destSlot, sourceItem);
|
||||
}
|
||||
@ -292,63 +316,44 @@ public class ClickPlan {
|
||||
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 is sending an item directly from one slot to another, without picking it up, that cannot
|
||||
// be expressed with a shift click
|
||||
// - Bedrock wants to pick up or place an arbitrary amount of items that cannot be expressed from
|
||||
// one left/right click action.
|
||||
// When Bedrock does one of these actions and sends multiple packets, a 1.17.1+ server will
|
||||
// increment the state ID on each confirmation packet it sends back (I.E. set slot). Then when it
|
||||
// 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)
|
||||
// Java typically doesn't increment the state ID if you send a vanilla-accurate container click packet,
|
||||
// but it will increment the state ID with a vanilla client in at least the crafting table
|
||||
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
|
||||
// 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;
|
||||
GeyserItemStack clicked = getItem(action.slot);
|
||||
if (action.click == Click.LEFT) {
|
||||
if (!clicked.isEmpty() && !InventoryUtils.canStack(simulatedCursor, clicked)) {
|
||||
// An item is removed from the crafting table; yes deletion
|
||||
stateIdIncrements = 3;
|
||||
stateIdIncrements = 2;
|
||||
} else {
|
||||
// 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) {
|
||||
if (simulatedCursor.isEmpty() && !clicked.isEmpty()) {
|
||||
// Items are taken; yes deletion
|
||||
stateIdIncrements = 3;
|
||||
} 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;
|
||||
}
|
||||
stateIdIncrements = 1;
|
||||
} else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) {
|
||||
stateIdIncrements = 1;
|
||||
} else {
|
||||
if (session.getGeyser().getConfig().isDebugMode()) {
|
||||
session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan);
|
||||
}
|
||||
stateIdIncrements = 2;
|
||||
stateIdIncrements = 1;
|
||||
}
|
||||
inventory.incrementStateId(stateIdIncrements);
|
||||
} else if (action.click.action instanceof MoveToHotbarAction) {
|
||||
// Two slot changes sent
|
||||
inventory.incrementStateId(2);
|
||||
} else {
|
||||
inventory.incrementStateId(1);
|
||||
}
|
||||
|
||||
return stateId;
|
||||
}
|
||||
|
||||
//TODO
|
||||
private void reduceCraftingGrid(boolean makeAll) {
|
||||
if (gridSize == -1)
|
||||
return;
|
||||
@ -370,9 +375,12 @@ public class ClickPlan {
|
||||
}
|
||||
|
||||
for (int i = 0; i < gridSize; i++) {
|
||||
GeyserItemStack item = getItem(i + 1);
|
||||
if (!item.isEmpty())
|
||||
item.sub(crafted);
|
||||
final int slot = i + 1;
|
||||
GeyserItemStack item = getItem(slot);
|
||||
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) {
|
||||
if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_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;
|
||||
|
@ -28,9 +28,9 @@ package org.geysermc.geyser.network;
|
||||
import com.github.steveice10.mc.protocol.codec.MinecraftCodec;
|
||||
import com.github.steveice10.mc.protocol.codec.PacketCodec;
|
||||
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.v475.Bedrock_v475;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
@ -58,8 +58,8 @@ public final class MinecraftProtocol {
|
||||
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
|
||||
|
||||
static {
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v465.V465_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);
|
||||
}
|
||||
|
||||
|
122
core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java
Normale Datei
122
core/src/main/java/org/geysermc/geyser/registry/IntMappedRegistry.java
Normale Datei
@ -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());
|
||||
}
|
||||
}
|
@ -44,20 +44,20 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.geysermc.geyser.api.extension.ExtensionLoader;
|
||||
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.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.populator.ItemRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.type.EnchantmentData;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.registry.type.ParticleMapping;
|
||||
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.List;
|
||||
@ -96,7 +96,7 @@ public final class Registries {
|
||||
/**
|
||||
* 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}.
|
||||
@ -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}
|
||||
* 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}.
|
||||
|
@ -50,10 +50,10 @@ import java.util.regex.Pattern;
|
||||
/**
|
||||
* 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
|
||||
public Map<Integer, BlockCollision> load(Pair<String, String> input) {
|
||||
public Int2ObjectMap<BlockCollision> load(Pair<String, String> input) {
|
||||
Int2ObjectMap<BlockCollision> collisions = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
Map<Class<?>, CollisionInfo> annotationMap = new IdentityHashMap<>();
|
||||
|
@ -28,8 +28,8 @@ package org.geysermc.geyser.registry.populator;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
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.v486.Bedrock_v486;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
@ -46,7 +46,10 @@ import org.geysermc.geyser.util.BlockUtils;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
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.zip.GZIPInputStream;
|
||||
|
||||
@ -59,8 +62,34 @@ public class BlockRegistryPopulator {
|
||||
|
||||
static {
|
||||
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();
|
||||
}
|
||||
|
@ -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.ItemData;
|
||||
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.v475.Bedrock_v475;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
@ -63,9 +63,9 @@ public class ItemRegistryPopulator {
|
||||
|
||||
static {
|
||||
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_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) {
|
||||
|
@ -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.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.HandPreference;
|
||||
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.Statistic;
|
||||
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.ServerboundPlayerAbilitiesPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket;
|
||||
@ -245,7 +249,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
|
||||
@Setter
|
||||
private Vector2i lastChunkPosition = null;
|
||||
private int renderDistance;
|
||||
@Setter
|
||||
private int clientRenderDistance = -1;
|
||||
private int serverRenderDistance;
|
||||
|
||||
// Exposed for GeyserConnect usage
|
||||
protected boolean sentSpawnPacket;
|
||||
@ -355,6 +361,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
@Setter
|
||||
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.
|
||||
* 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();
|
||||
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(
|
||||
clientData.getGameVersion(),
|
||||
authData.name(),
|
||||
@ -798,7 +820,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
clientData.getLanguageCode(),
|
||||
clientData.getUiProfile().ordinal(),
|
||||
clientData.getCurrentInputMode().ordinal(),
|
||||
upstream.getAddress().getAddress().getHostAddress(),
|
||||
bedrockAddress,
|
||||
skinUploader.getId(),
|
||||
skinUploader.getVerifyCode()
|
||||
).toString());
|
||||
@ -1160,9 +1182,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
return clientData.getLanguageCode();
|
||||
}
|
||||
|
||||
public void setRenderDistance(int renderDistance) {
|
||||
public void setServerRenderDistance(int renderDistance) {
|
||||
renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle
|
||||
this.renderDistance = renderDistance;
|
||||
this.serverRenderDistance = renderDistance;
|
||||
|
||||
ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket();
|
||||
chunkRadiusUpdatedPacket.setRadius(renderDistance);
|
||||
@ -1420,6 +1442,27 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
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
|
||||
*
|
||||
|
@ -30,9 +30,6 @@ import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@ -43,7 +40,7 @@ import java.util.WeakHashMap;
|
||||
* A temporary cache for lodestone information.
|
||||
* 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
|
||||
* 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) {
|
||||
// We should not need to check the activeLodestones map as Bedrock should already be aware of this ID
|
||||
return this.lodestones.remove(id);
|
||||
LodestonePos pos = 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() {
|
||||
@ -131,16 +136,7 @@ public class LodestoneCache {
|
||||
this.lodestones.clear();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@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;
|
||||
|
||||
public record LodestonePos(int id, 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);
|
||||
}
|
||||
|
@ -115,10 +115,12 @@ public class SkinProvider {
|
||||
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);
|
||||
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
|
||||
if (GeyserImpl.getInstance().getConfig().getCacheImages() > 0) {
|
||||
GeyserImpl.getInstance().getScheduledThread().scheduleAtFixedRate(() -> {
|
||||
if (geyser.getConfig().getCacheImages() > 0) {
|
||||
geyser.getScheduledThread().scheduleAtFixedRate(() -> {
|
||||
File cacheFolder = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile();
|
||||
if (!cacheFolder.exists()) {
|
||||
return;
|
||||
|
@ -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.packet.BlockEntityDataPacket;
|
||||
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.BedrockContainerSlot;
|
||||
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.holder.BlockInventoryHolder;
|
||||
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator {
|
||||
public BeaconInventoryTranslator() {
|
||||
super(1, new BlockInventoryHolder("minecraft:beacon", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.BEACON) {
|
||||
@ -104,7 +103,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
|
||||
protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
|
||||
return action.getType() == StackRequestActionType.BEACON_PAYMENT;
|
||||
}
|
||||
|
||||
@ -114,7 +113,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
|
||||
BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0];
|
||||
ServerboundSetBeaconPacket packet = new ServerboundSetBeaconPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect());
|
||||
session.sendDownstreamPacket(packet);
|
||||
return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
|
||||
return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,6 +37,11 @@ public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslato
|
||||
super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGridSize() {
|
||||
return 9;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SlotType getSlotType(int javaSlot) {
|
||||
if (javaSlot == 0) {
|
||||
|
@ -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.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.StackRequestActionData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
|
||||
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket;
|
||||
import org.geysermc.geyser.inventory.EnchantingContainer;
|
||||
import org.geysermc.geyser.inventory.GeyserEnchantOption;
|
||||
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 it.unimi.dsi.fastutil.ints.IntSets;
|
||||
import org.geysermc.geyser.inventory.*;
|
||||
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.Collections;
|
||||
|
||||
public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator {
|
||||
public EnchantingInventoryTranslator() {
|
||||
@ -104,7 +103,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
|
||||
protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
|
||||
return action.getType() == StackRequestActionType.CRAFT_RECIPE;
|
||||
}
|
||||
|
||||
@ -130,7 +129,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
|
||||
}
|
||||
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getId(), javaSlot);
|
||||
session.sendDownstreamPacket(packet);
|
||||
return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
|
||||
return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,12 +26,11 @@
|
||||
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.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.Recipe;
|
||||
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.inventory.ContainerType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||
@ -43,15 +42,10 @@ import it.unimi.dsi.fastutil.ints.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.CartographyContainer;
|
||||
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.*;
|
||||
import org.geysermc.geyser.inventory.click.Click;
|
||||
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.SingleChestInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator;
|
||||
@ -119,6 +113,13 @@ public abstract class InventoryTranslator {
|
||||
public abstract SlotType getSlotType(int javaSlot);
|
||||
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.
|
||||
* 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.
|
||||
* E.G. anvil renaming or enchanting
|
||||
*/
|
||||
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
|
||||
protected boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -147,7 +148,7 @@ public abstract class InventoryTranslator {
|
||||
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;
|
||||
ItemStackResponsePacket responsePacket = new ItemStackResponsePacket();
|
||||
for (ItemStackRequest request : requests) {
|
||||
@ -199,10 +200,6 @@ public abstract class InventoryTranslator {
|
||||
case PLACE: {
|
||||
TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action;
|
||||
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()) {
|
||||
session.getGeyser().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.name());
|
||||
dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination());
|
||||
@ -212,17 +209,19 @@ public abstract class InventoryTranslator {
|
||||
|
||||
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
|
||||
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||
boolean isSourceCursor = isCursor(transferAction.getSource());
|
||||
boolean isDestCursor = isCursor(transferAction.getDestination());
|
||||
|
||||
if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(),
|
||||
isCursor(transferAction.getSource()) ? -1 : sourceSlot,
|
||||
transferAction.getDestination().getContainer(), isCursor(transferAction.getDestination()) ? -1 : destSlot)) {
|
||||
isSourceCursor ? -1 : sourceSlot,
|
||||
transferAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) {
|
||||
// This item would not be here in Java
|
||||
return rejectRequest(request, false);
|
||||
}
|
||||
|
||||
if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //???
|
||||
if (isSourceCursor && isDestCursor) { //???
|
||||
return rejectRequest(request);
|
||||
} else if (isCursor(transferAction.getSource())) { //releasing cursor
|
||||
} else if (isSourceCursor) { //releasing cursor
|
||||
int sourceAmount = cursor.getAmount();
|
||||
if (transferAction.getCount() == sourceAmount) { //release all
|
||||
plan.add(Click.LEFT, destSlot);
|
||||
@ -231,7 +230,7 @@ public abstract class InventoryTranslator {
|
||||
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);
|
||||
int sourceAmount = sourceItem.getAmount();
|
||||
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) {
|
||||
// Tell the server we're pressing one of the hotbar keys to save clicks
|
||||
Click click = switch (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;
|
||||
};
|
||||
Click click = InventoryUtils.getClickForHotbarSwap(destination.getSlot());
|
||||
if (click != null) {
|
||||
plan.add(click, sourceSlot);
|
||||
break;
|
||||
@ -442,6 +430,8 @@ public abstract class InventoryTranslator {
|
||||
|
||||
int leftover = 0;
|
||||
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()) {
|
||||
switch (action.getType()) {
|
||||
case CRAFT_RECIPE: {
|
||||
@ -473,6 +463,7 @@ public abstract class InventoryTranslator {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
craftState = CraftState.INGREDIENTS;
|
||||
affectedSlots.add(bedrockSlotToJava(((ConsumeStackRequestActionData) action).getSource()));
|
||||
break;
|
||||
}
|
||||
case TAKE:
|
||||
@ -533,21 +524,16 @@ public abstract class InventoryTranslator {
|
||||
}
|
||||
}
|
||||
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) {
|
||||
int gridSize;
|
||||
int gridDimensions;
|
||||
if (this instanceof PlayerInventoryTranslator) {
|
||||
gridSize = 4;
|
||||
gridDimensions = 2;
|
||||
} else if (this instanceof CraftingInventoryTranslator) {
|
||||
gridSize = 9;
|
||||
gridDimensions = 3;
|
||||
} else {
|
||||
final int gridSize = getGridSize();
|
||||
if (gridSize == -1) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
int gridDimensions = gridSize == 4 ? 2 : 3;
|
||||
|
||||
Recipe recipe;
|
||||
Ingredient[] ingredients = new Ingredient[0];
|
||||
@ -733,7 +719,7 @@ public abstract class InventoryTranslator {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject an incorrect ItemStackRequest.
|
||||
*/
|
||||
public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) {
|
||||
protected static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) {
|
||||
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
|
||||
* 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()) {
|
||||
new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace();
|
||||
}
|
||||
@ -860,12 +846,15 @@ public abstract class InventoryTranslator {
|
||||
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<>();
|
||||
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);
|
||||
List<ItemStackResponsePacket.ItemEntry> list = containerMap.computeIfAbsent(bedrockSlot.getContainer(), k -> new ArrayList<>());
|
||||
list.add(makeItemEntry(session, bedrockSlot.getSlot(), inventory.getItem(slot)));
|
||||
List<ItemStackResponsePacket.ItemEntry> list = containerMap.computeIfAbsent(bedrockSlot.container(), k -> new ArrayList<>());
|
||||
list.add(makeItemEntry(session, bedrockSlot.slot(), inventory.getItem(slot)));
|
||||
}
|
||||
|
||||
List<ItemStackResponsePacket.ContainerEntry> containerEntries = new ArrayList<>();
|
||||
@ -879,7 +868,7 @@ public abstract class InventoryTranslator {
|
||||
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;
|
||||
if (!itemStack.isEmpty()) {
|
||||
// As of 1.16.210: Bedrock needs confirmation on what the current item durability is.
|
||||
|
@ -117,7 +117,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
|
||||
}
|
||||
|
||||
@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
|
||||
// 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)
|
||||
|
@ -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.InventorySlotPacket;
|
||||
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.IntSet;
|
||||
import org.geysermc.geyser.inventory.*;
|
||||
@ -55,6 +56,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
||||
super(46);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGridSize() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateInventory(GeyserSession session, Inventory 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);
|
||||
}
|
||||
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||
protected ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||
ItemStack javaCreativeItem = null;
|
||||
IntSet affectedSlots = new IntOpenHashSet();
|
||||
CraftState craftState = CraftState.START;
|
||||
@ -478,7 +487,10 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
||||
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);
|
||||
}
|
||||
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
|
||||
|
@ -52,7 +52,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
|
||||
}
|
||||
|
||||
@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
|
||||
return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED || action.getType() == StackRequestActionType.CRAFT_RECIPE;
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ public class BannerTranslator extends ItemTranslator {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@ -163,9 +163,7 @@ public class BannerTranslator extends ItemTranslator {
|
||||
ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings);
|
||||
|
||||
CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag");
|
||||
if (blockEntityTag != null && blockEntityTag.contains("Patterns")) {
|
||||
ListTag patterns = blockEntityTag.get("Patterns");
|
||||
|
||||
if (blockEntityTag != null && blockEntityTag.get("Patterns") instanceof ListTag patterns) {
|
||||
NbtMapBuilder nbtBuilder = builder.build().getTag().toBuilder(); //TODO fix ugly hack
|
||||
if (patterns.equals(OMINOUS_BANNER_PATTERN)) {
|
||||
// Remove the current patterns and set the ominous banner type
|
||||
|
@ -26,7 +26,9 @@
|
||||
package org.geysermc.geyser.translator.inventory.item;
|
||||
|
||||
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 org.geysermc.geyser.network.MinecraftProtocol;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
@ -51,17 +53,28 @@ public class CompassTranslator extends ItemTranslator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
|
||||
if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings);
|
||||
|
||||
Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked");
|
||||
if (lodestoneTag instanceof ByteTag) {
|
||||
// Get the fake lodestonecompass entry
|
||||
mapping = mappings.getStoredItems().lodestoneCompass();
|
||||
// NBT will be translated in nbt/LodestoneCompassTranslator
|
||||
protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
|
||||
if (isLodestoneCompass(itemStack.getNbt())) {
|
||||
// NBT will be translated in nbt/LodestoneCompassTranslator if applicable
|
||||
return super.translateToBedrock(itemStack, mappings.getStoredItems().lodestoneCompass(), mappings);
|
||||
}
|
||||
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
|
||||
|
@ -37,6 +37,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
@ -157,18 +158,13 @@ public abstract class ItemTranslator {
|
||||
|
||||
nbt = translateDisplayProperties(session, nbt, bedrockItem);
|
||||
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);
|
||||
|
||||
ItemData.Builder builder;
|
||||
ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId());
|
||||
if (itemStackTranslator != null) {
|
||||
builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings());
|
||||
} else {
|
||||
builder = DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem, session.getItemMappings());
|
||||
}
|
||||
ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.getOrDefault(bedrockItem.getJavaId(), DEFAULT_TRANSLATOR);
|
||||
ItemData.Builder builder = itemStackTranslator.translateToBedrock(itemStack, bedrockItem, session.getItemMappings());
|
||||
if (bedrockItem.isBlock()) {
|
||||
builder.blockRuntimeId(bedrockItem.getBedrockBlockId());
|
||||
}
|
||||
@ -263,6 +259,19 @@ public abstract class ItemTranslator {
|
||||
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() {
|
||||
@Override
|
||||
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) {
|
||||
// Return, essentially, air
|
||||
return ItemData.builder();
|
||||
@ -295,6 +304,10 @@ public abstract class ItemTranslator {
|
||||
|
||||
public abstract List<ItemMapping> getAppliedItems();
|
||||
|
||||
protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) {
|
||||
return mappings.getMapping(javaId);
|
||||
}
|
||||
|
||||
public NbtMap translateNbtToBedrock(CompoundTag tag) {
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
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) {
|
||||
boolean hasCustomName = false;
|
||||
if (tag != null) {
|
||||
CompoundTag display = tag.get("display");
|
||||
if (display != null && display.contains("Name")) {
|
||||
String name = ((StringTag) display.get("Name")).getValue();
|
||||
if (tag.get("display") instanceof CompoundTag display && display.get("Name") instanceof StringTag tagName) {
|
||||
String name = tagName.getValue();
|
||||
|
||||
// Get the translated name and prefix it with a reset char
|
||||
name = MessageTranslator.convertMessageLenient(name, session.locale());
|
||||
@ -491,8 +503,10 @@ public abstract class ItemTranslator {
|
||||
if (tag == null) {
|
||||
tag = new CompoundTag("");
|
||||
}
|
||||
CompoundTag display = tag.get("display");
|
||||
if (display == null) {
|
||||
CompoundTag display;
|
||||
if (tag.get("display") instanceof CompoundTag oldDisplay) {
|
||||
display = oldDisplay;
|
||||
} else {
|
||||
display = new CompoundTag("display");
|
||||
// Add to the new root tag
|
||||
tag.put(display);
|
||||
|
@ -54,7 +54,7 @@ public class PotionTranslator extends ItemTranslator {
|
||||
}
|
||||
|
||||
@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);
|
||||
Tag potionTag = itemStack.getNbt().get("Potion");
|
||||
if (potionTag instanceof StringTag) {
|
||||
|
@ -59,7 +59,7 @@ public class TippedArrowTranslator extends ItemTranslator {
|
||||
}
|
||||
|
||||
@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) {
|
||||
// We're only concerned about minecraft:arrow when translating Bedrock -> Java
|
||||
return super.translateToBedrock(itemStack, mapping, mappings);
|
||||
|
@ -51,13 +51,11 @@ public class BasicItemTranslator extends NbtItemStackTranslator {
|
||||
}
|
||||
}
|
||||
|
||||
CompoundTag displayTag = itemTag.get("display");
|
||||
if (displayTag == null) {
|
||||
if (!(itemTag.get("display") instanceof CompoundTag displayTag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Tag loreTag = displayTag.get("Lore");
|
||||
if (loreTag instanceof ListTag listTag) {
|
||||
if (displayTag.get("Lore") instanceof ListTag listTag) {
|
||||
List<Tag> lore = new ArrayList<>();
|
||||
for (Tag tag : listTag.getValue()) {
|
||||
if (!(tag instanceof StringTag)) continue;
|
||||
|
@ -41,12 +41,14 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
|
||||
@Override
|
||||
public void translate(GeyserSession session, BlockEntityDataPacket packet) {
|
||||
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
|
||||
// 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
|
||||
if (!tag.getString("Text").equals(session.getLastSignMessage())) {
|
||||
session.setLastSignMessage(tag.getString("Text"));
|
||||
if (!text.equals(session.getLastSignMessage())) {
|
||||
session.setLastSignMessage(text);
|
||||
return;
|
||||
}
|
||||
// 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
|
||||
int widthCount = 0;
|
||||
// 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);
|
||||
// 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) {
|
||||
@ -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
|
||||
session.setLastSignMessage(null);
|
||||
|
||||
} else if (tag.getString("id").equals("JigsawBlock")) {
|
||||
} else if (id.equals("JigsawBlock")) {
|
||||
// Client has just sent a jigsaw block update
|
||||
Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
|
||||
String name = tag.getString("name");
|
||||
|
@ -25,12 +25,14 @@
|
||||
|
||||
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.object.Direction;
|
||||
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.InteractAction;
|
||||
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.ServerboundPlayerActionPacket;
|
||||
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.inventory.*;
|
||||
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.Entity;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
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.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.click.Click;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
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.InventoryUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@ -109,6 +117,23 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
case ITEM_USE:
|
||||
switch (packet.getActionType()) {
|
||||
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
|
||||
// Based on Nukkit 1.0, with changes to ensure holding down still works
|
||||
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.
|
||||
"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);
|
||||
}
|
||||
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
|
||||
if (session.getPlayerInventory().getItemInHand().getJavaId() == mappings.getStoredItems().shield().getJavaId()) {
|
||||
break;
|
||||
@ -282,6 +296,43 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
|
||||
ServerboundUseItemPacket useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
|
||||
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 -> {
|
||||
int blockState = session.getGameMode() == GameMode.CREATIVE ?
|
||||
|
@ -58,14 +58,14 @@ public class BedrockPositionTrackingDBClientRequestTranslator extends PacketTran
|
||||
|
||||
// Build the NBT data for the update
|
||||
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.putByte("version", (byte) 1); // Not sure what this is for
|
||||
builder.putByte("status", (byte) 0); // Not sure what this is for
|
||||
|
||||
// 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());
|
||||
|
||||
session.sendUpstreamPacket(broadcastPacket);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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.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 org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
@ -41,28 +38,6 @@ public class BedrockRespawnTranslator extends PacketTranslator<RespawnPacket> {
|
||||
@Override
|
||||
public void translate(GeyserSession session, RespawnPacket packet) {
|
||||
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);
|
||||
session.sendDownstreamPacket(javaRespawnPacket);
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.TextPacket;
|
||||
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.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
@ -44,6 +45,18 @@ public class BedrockTextTranslator extends PacketTranslator<TextPacket> {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
@ -25,31 +25,25 @@
|
||||
|
||||
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.serverbound.ServerboundClientInformationPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCustomPayloadPacket;
|
||||
import com.nukkitx.protocol.bedrock.data.GameRuleData;
|
||||
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import org.geysermc.geyser.session.auth.AuthType;
|
||||
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
|
||||
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.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.Translator;
|
||||
import org.geysermc.geyser.translator.level.BiomeTranslator;
|
||||
import org.geysermc.geyser.util.ChunkUtils;
|
||||
import org.geysermc.geyser.util.DimensionUtils;
|
||||
import org.geysermc.geyser.util.PluginMessageUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Translator(packet = ClientboundLoginPacket.class)
|
||||
public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket> {
|
||||
private static final List<SkinPart> SKIN_PART_VALUES = Arrays.asList(SkinPart.values());
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundLoginPacket packet) {
|
||||
@ -99,13 +93,10 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
||||
|
||||
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
|
||||
ServerboundClientInformationPacket infoPacket = new ServerboundClientInformationPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, SKIN_PART_VALUES, HandPreference.RIGHT_HAND, false, true);
|
||||
session.sendDownstreamPacket(infoPacket);
|
||||
session.sendJavaClientSettings();
|
||||
|
||||
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
|
||||
|
||||
|
@ -31,7 +31,7 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
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)
|
||||
@ -42,9 +42,11 @@ public class JavaRecipeTranslator extends PacketTranslator<ClientboundRecipePack
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundRecipePacket packet) {
|
||||
if (packet.getAction() == UnlockRecipesAction.REMOVE) {
|
||||
session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes()));
|
||||
for (String identifier : packet.getRecipes()) {
|
||||
session.getUnlockedRecipes().remove(identifier);
|
||||
}
|
||||
} else {
|
||||
session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes()));
|
||||
Collections.addAll(session.getUnlockedRecipes(), packet.getRecipes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,8 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
|
||||
public void translate(GeyserSession session, ClientboundRespawnPacket packet) {
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
|
||||
session.setSpawned(false);
|
||||
|
||||
entity.setHealth(entity.getMaxHealth());
|
||||
entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute());
|
||||
|
||||
|
@ -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.data.ShapedRecipeData;
|
||||
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.packet.ingame.clientbound.ClientboundUpdateRecipesPacket;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import it.unimi.dsi.fastutil.ints.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
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.
|
||||
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<List<StoneCuttingRecipeData>> unsortedStonecutterData = new Int2ObjectOpenHashMap<>();
|
||||
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
|
||||
@ -128,6 +132,27 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
data.add(stoneCuttingData);
|
||||
// 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 -> {
|
||||
List<CraftingData> craftingData = recipeTypes.get(recipe.getType());
|
||||
if (craftingData != null) {
|
||||
|
@ -245,8 +245,8 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
|
||||
float baseX = position.getX();
|
||||
float baseY = position.getY();
|
||||
float baseZ = position.getZ();
|
||||
float height = entity.getDefinition().height();
|
||||
float width = entity.getDefinition().width();
|
||||
float height = entity.getBoundingBoxHeight();
|
||||
float width = entity.getBoundingBoxWidth();
|
||||
Random random = ThreadLocalRandom.current();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
// Reconstruct the Java Edition (1.18.1) logic, but in floats
|
||||
|
@ -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.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
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.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.entity.InteractiveTagManager;
|
||||
|
||||
@Translator(packet = ClientboundSetEntityDataPacket.class)
|
||||
public class JavaSetEntityDataTranslator extends PacketTranslator<ClientboundSetEntityDataPacket> {
|
||||
|
@ -86,13 +86,14 @@ public class JavaPlayerPositionTranslator extends PacketTranslator<ClientboundPl
|
||||
|
||||
ChunkUtils.updateChunkPosition(session, pos.toInt());
|
||||
|
||||
if (session.getGeyser().getConfig().isDebugMode()) {
|
||||
session.getGeyser().getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Entity vehicle = session.getPlayerEntity().getVehicle();
|
||||
if (packet.isDismountVehicle() && vehicle != null) {
|
||||
|
||||
Entity vehicle;
|
||||
if (packet.isDismountVehicle() && (vehicle = session.getPlayerEntity().getVehicle()) != null) {
|
||||
SetEntityLinkPacket linkPacket = new SetEntityLinkPacket();
|
||||
linkPacket.setEntityLink(new EntityLinkData(vehicle.getGeyserId(), entity.getGeyserId(), EntityLinkData.Type.REMOVE, false, false));
|
||||
session.sendUpstreamPacket(linkPacket);
|
||||
|
@ -26,6 +26,7 @@
|
||||
package org.geysermc.geyser.translator.protocol.java.inventory;
|
||||
|
||||
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.Inventory;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -43,9 +44,26 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
|
||||
if (inventory == null)
|
||||
return;
|
||||
|
||||
inventory.setStateId(packet.getStateId());
|
||||
|
||||
int inventorySize = inventory.getSize();
|
||||
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]);
|
||||
inventory.setItem(i, newItem, session);
|
||||
}
|
||||
@ -55,6 +73,10 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
|
||||
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);
|
||||
InventoryUtils.updateCursor(session);
|
||||
}
|
||||
|
@ -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.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.clientbound.inventory.ClientboundContainerSetSlotPacket;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
|
||||
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.Inventory;
|
||||
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.CraftingInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
|
||||
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 java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -72,14 +69,16 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
|
||||
return;
|
||||
|
||||
// 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();
|
||||
if (translator != null) {
|
||||
if (session.getCraftingGridFuture() != null) {
|
||||
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());
|
||||
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) {
|
||||
int gridSize;
|
||||
if (translator instanceof PlayerInventoryTranslator) {
|
||||
gridSize = 4;
|
||||
} else if (translator instanceof CraftingInventoryTranslator) {
|
||||
gridSize = 9;
|
||||
} else {
|
||||
/**
|
||||
* Checks for a changed output slot in the crafting grid, and ensures Bedrock sees the recipe.
|
||||
*/
|
||||
private static void updateCraftingGrid(GeyserSession session, int slot, ItemStack item, Inventory inventory, InventoryTranslator translator) {
|
||||
if (slot != 0) {
|
||||
return;
|
||||
}
|
||||
int gridSize = translator.getGridSize();
|
||||
if (gridSize == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getItem() == null || packet.getItem().getId() == 0) {
|
||||
if (item == null || item.getId() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
session.setCraftingGridFuture(session.scheduleInEventLoop(() -> {
|
||||
int offset = gridSize == 4 ? 28 : 32;
|
||||
int gridDimensions = gridSize == 4 ? 2 : 3;
|
||||
int firstRow = -1, height = -1;
|
||||
@ -135,62 +136,10 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
|
||||
height += -firstRow + 1;
|
||||
width += -firstCol + 1;
|
||||
|
||||
recipes:
|
||||
for (Recipe recipe : session.getCraftingRecipes().values()) {
|
||||
if (recipe.getType() == RecipeType.CRAFTING_SHAPED) {
|
||||
ShapedRecipeData data = (ShapedRecipeData) recipe.getData();
|
||||
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
|
||||
if (InventoryUtils.getValidRecipe(session, item, inventory::getItem, gridDimensions, firstRow,
|
||||
height, firstCol, width) != null) {
|
||||
// Recipe is already present on the client; don't send 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();
|
||||
@ -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
|
||||
session.getCraftingRecipes().put(newRecipeId, new Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data));
|
||||
|
||||
@ -226,7 +175,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
|
||||
width,
|
||||
height,
|
||||
Arrays.asList(ingredients),
|
||||
Collections.singletonList(ItemTranslator.translateToBedrock(session, packet.getItem())),
|
||||
Collections.singletonList(ItemTranslator.translateToBedrock(session, item)),
|
||||
uuid,
|
||||
"crafting_table",
|
||||
0,
|
||||
@ -246,33 +195,6 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}, 150, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||
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.MathUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -99,16 +100,17 @@ public class JavaMerchantOffersTranslator extends PacketTranslator<ClientboundMe
|
||||
recipe.putInt("maxUses", trade.isTradeDisabled() ? 0 : trade.getMaxUses());
|
||||
recipe.putInt("traderExp", trade.getXp());
|
||||
recipe.putFloat("priceMultiplierA", trade.getPriceMultiplier());
|
||||
recipe.put("sell", getItemTag(session, trade.getOutput(), 0));
|
||||
recipe.putFloat("priceMultiplierB", 0.0f);
|
||||
recipe.putInt("buyCountB", trade.getSecondInput() != null ? trade.getSecondInput().getAmount() : 0);
|
||||
recipe.putInt("buyCountA", trade.getFirstInput().getAmount());
|
||||
recipe.putInt("demand", trade.getDemand());
|
||||
recipe.put("sell", getItemTag(session, trade.getOutput()));
|
||||
|
||||
// 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.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice()));
|
||||
if (trade.getSecondInput() != null) {
|
||||
recipe.put("buyB", getItemTag(session, trade.getSecondInput(), 0));
|
||||
}
|
||||
recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice(), trade.getDemand(), trade.getPriceMultiplier()));
|
||||
recipe.put("buyB", getItemTag(session, trade.getSecondInput()));
|
||||
recipe.putInt("uses", trade.getNumUses());
|
||||
recipe.putByte("rewardExp", (byte) 1);
|
||||
tags.add(recipe.build());
|
||||
@ -144,12 +146,31 @@ public class JavaMerchantOffersTranslator extends PacketTranslator<ClientboundMe
|
||||
session.sendUpstreamPacket(updateTradePacket);
|
||||
}
|
||||
|
||||
private static NbtMap getItemTag(GeyserSession session, ItemStack stack, int specialPrice) {
|
||||
ItemData itemData = ItemTranslator.translateToBedrock(session, stack);
|
||||
private static NbtMap getItemTag(GeyserSession session, ItemStack 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);
|
||||
|
||||
// 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();
|
||||
builder.putByte("Count", (byte) (Math.max(itemData.getCount() + specialPrice, 1)));
|
||||
builder.putByte("Count", (byte) count);
|
||||
builder.putShort("Damage", (short) itemData.getDamage());
|
||||
builder.putString("Name", mapping.getBedrockIdentifier());
|
||||
if (itemData.getTag() != null) {
|
||||
|
@ -25,14 +25,7 @@
|
||||
|
||||
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.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.data.game.level.event.*;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
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.LevelSoundEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.TextPacket;
|
||||
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
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.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.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 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 -> {
|
||||
effectPacket.setType((particleEvent == ParticleEvent.BONEMEAL_GROW
|
||||
&& session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) ? LevelEventType.PARTICLE_TURTLE_EGG : LevelEventType.PARTICLE_CROP_GROWTH);
|
||||
effectPacket.setType(particleEvent == ParticleEvent.BONEMEAL_GROW ? LevelEventType.PARTICLE_TURTLE_EGG : LevelEventType.PARTICLE_CROP_GROWTH);
|
||||
|
||||
BonemealGrowEventData growEventData = (BonemealGrowEventData) packet.getData();
|
||||
effectPacket.setData(growEventData.getParticleCount());
|
||||
|
@ -35,6 +35,6 @@ public class JavaSetChunkCacheRadiusTranslator extends PacketTranslator<Clientbo
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundSetChunkCacheRadiusPacket packet) {
|
||||
session.setRenderDistance(packet.getViewDistance());
|
||||
session.setServerRenderDistance(packet.getViewDistance());
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ public class ChunkUtils {
|
||||
if (chunkPos == null || !chunkPos.equals(newChunkPos)) {
|
||||
NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket();
|
||||
chunkPublisherUpdatePacket.setPosition(position);
|
||||
chunkPublisherUpdatePacket.setRadius(session.getRenderDistance() << 4);
|
||||
chunkPublisherUpdatePacket.setRadius(session.getServerRenderDistance() << 4);
|
||||
session.sendUpstreamPacket(chunkPublisherUpdatePacket);
|
||||
|
||||
session.setLastChunkPosition(newChunkPos);
|
||||
|
@ -169,8 +169,8 @@ public class FileUtils {
|
||||
* @return The byte array of the file
|
||||
*/
|
||||
public static byte[] readAllBytes(File file) {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
return readAllBytes(inputStream);
|
||||
try (InputStream stream = new FileInputStream(file)) {
|
||||
return stream.readAllBytes();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read " + file);
|
||||
}
|
||||
@ -182,30 +182,12 @@ public class FileUtils {
|
||||
*/
|
||||
public static byte[] readAllBytes(String resource) {
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(resource)) {
|
||||
return readAllBytes(stream);
|
||||
return stream.readAllBytes();
|
||||
} catch (IOException 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
|
||||
*
|
||||
|
@ -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.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.ServerboundSetCreativeModeSlotPacket;
|
||||
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.Inventory;
|
||||
import org.geysermc.geyser.inventory.PlayerInventory;
|
||||
import org.geysermc.geyser.inventory.click.Click;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
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.type.ItemMapping;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -330,4 +338,131 @@ public class InventoryUtils {
|
||||
session.sendUpstreamPacket(hotbarPacket);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
Binäre Datei nicht angezeigt.
BIN
core/src/main/resources/bedrock/block_palette.1_18_10.nbt
Normale Datei
BIN
core/src/main/resources/bedrock/block_palette.1_18_10.nbt
Normale Datei
Binäre Datei nicht angezeigt.
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -51,6 +51,10 @@
|
||||
"name" : "minecraft:air",
|
||||
"id" : -158
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:allay_spawn_egg",
|
||||
"id" : 631
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:allow",
|
||||
"id" : 210
|
||||
@ -65,7 +69,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:amethyst_shard",
|
||||
"id" : 623
|
||||
"id" : 625
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:ancient_debris",
|
||||
@ -117,7 +121,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:balloon",
|
||||
"id" : 597
|
||||
"id" : 598
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:bamboo",
|
||||
@ -133,7 +137,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:banner_pattern",
|
||||
"id" : 627
|
||||
"id" : 635
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:barrel",
|
||||
@ -293,7 +297,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:bleach",
|
||||
"id" : 595
|
||||
"id" : 596
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:blue_candle",
|
||||
@ -317,7 +321,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:boat",
|
||||
"id" : 625
|
||||
"id" : 633
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:bone",
|
||||
@ -429,11 +433,11 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:camera",
|
||||
"id" : 592
|
||||
"id" : 593
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:campfire",
|
||||
"id" : 588
|
||||
"id" : 589
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:candle",
|
||||
@ -493,7 +497,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:chain",
|
||||
"id" : 617
|
||||
"id" : 619
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:chain_command_block",
|
||||
@ -575,6 +579,10 @@
|
||||
"name" : "minecraft:clay_ball",
|
||||
"id" : 384
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:client_request_placeholder_block",
|
||||
"id" : -465
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:clock",
|
||||
"id" : 393
|
||||
@ -669,7 +677,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:compound",
|
||||
"id" : 593
|
||||
"id" : 594
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:concrete",
|
||||
@ -793,7 +801,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:crimson_door",
|
||||
"id" : 614
|
||||
"id" : 616
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:crimson_double_slab",
|
||||
@ -833,7 +841,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:crimson_sign",
|
||||
"id" : 612
|
||||
"id" : 614
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:crimson_slab",
|
||||
@ -1169,7 +1177,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:dye",
|
||||
"id" : 626
|
||||
"id" : 634
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:egg",
|
||||
@ -1697,7 +1705,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:end_crystal",
|
||||
"id" : 629
|
||||
"id" : 637
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:end_gateway",
|
||||
@ -1803,6 +1811,10 @@
|
||||
"name" : "minecraft:fire_charge",
|
||||
"id" : 509
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:firefly_spawn_egg",
|
||||
"id" : 632
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:firework_rocket",
|
||||
"id" : 519
|
||||
@ -1855,6 +1867,14 @@
|
||||
"name" : "minecraft:frame",
|
||||
"id" : 513
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:frog_egg",
|
||||
"id" : -468
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:frog_spawn_egg",
|
||||
"id" : 628
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:frosted_ice",
|
||||
"id" : 207
|
||||
@ -1891,13 +1911,17 @@
|
||||
"name" : "minecraft:glistering_melon_slice",
|
||||
"id" : 434
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:globe_banner_pattern",
|
||||
"id" : 588
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:glow_berries",
|
||||
"id" : 630
|
||||
"id" : 638
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:glow_frame",
|
||||
"id" : 621
|
||||
"id" : 623
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:glow_ink_sac",
|
||||
@ -1913,7 +1937,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:glow_stick",
|
||||
"id" : 166
|
||||
"id" : 601
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:glowingobsidian",
|
||||
@ -1929,7 +1953,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:goat_horn",
|
||||
"id" : 622
|
||||
"id" : 624
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:goat_spawn_egg",
|
||||
@ -2109,11 +2133,11 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:honey_bottle",
|
||||
"id" : 591
|
||||
"id" : 592
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:honeycomb",
|
||||
"id" : 590
|
||||
"id" : 591
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:honeycomb_block",
|
||||
@ -2141,7 +2165,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:ice_bomb",
|
||||
"id" : 594
|
||||
"id" : 595
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:infested_deepslate",
|
||||
@ -2569,7 +2593,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:lodestone_compass",
|
||||
"id" : 600
|
||||
"id" : 602
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:log",
|
||||
@ -2613,7 +2637,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:medicine",
|
||||
"id" : 598
|
||||
"id" : 599
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:medium_amethyst_bud",
|
||||
@ -2723,9 +2747,13 @@
|
||||
"name" : "minecraft:music_disc_mellohi",
|
||||
"id" : 540
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:music_disc_otherside",
|
||||
"id" : 627
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:music_disc_pigstep",
|
||||
"id" : 618
|
||||
"id" : 620
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:music_disc_stal",
|
||||
@ -2751,6 +2779,14 @@
|
||||
"name" : "minecraft:mycelium",
|
||||
"id" : 110
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:mysterious_frame",
|
||||
"id" : -466
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:mysterious_frame_slot",
|
||||
"id" : -467
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:name_tag",
|
||||
"id" : 548
|
||||
@ -2777,7 +2813,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:nether_sprouts",
|
||||
"id" : 619
|
||||
"id" : 621
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:nether_star",
|
||||
@ -2797,7 +2833,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_axe",
|
||||
"id" : 605
|
||||
"id" : 607
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_block",
|
||||
@ -2805,43 +2841,43 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_boots",
|
||||
"id" : 610
|
||||
"id" : 612
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_chestplate",
|
||||
"id" : 608
|
||||
"id" : 610
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_helmet",
|
||||
"id" : 607
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_hoe",
|
||||
"id" : 606
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_ingot",
|
||||
"id" : 601
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_leggings",
|
||||
"id" : 609
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_pickaxe",
|
||||
"id" : 604
|
||||
"name" : "minecraft:netherite_hoe",
|
||||
"id" : 608
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_scrap",
|
||||
"id" : 611
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherite_shovel",
|
||||
"name" : "minecraft:netherite_ingot",
|
||||
"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",
|
||||
"id" : 602
|
||||
"id" : 604
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:netherrack",
|
||||
@ -2887,6 +2923,10 @@
|
||||
"name" : "minecraft:ocelot_spawn_egg",
|
||||
"id" : 451
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:ochre_froglight",
|
||||
"id" : -471
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:orange_candle",
|
||||
"id" : -414
|
||||
@ -2943,6 +2983,10 @@
|
||||
"name" : "minecraft:parrot_spawn_egg",
|
||||
"id" : 478
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:pearlescent_froglight",
|
||||
"id" : -469
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:phantom_membrane",
|
||||
"id" : 574
|
||||
@ -3257,7 +3301,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:rapid_fertilizer",
|
||||
"id" : 596
|
||||
"id" : 597
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:ravager_spawn_egg",
|
||||
@ -3577,7 +3621,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:soul_campfire",
|
||||
"id" : 620
|
||||
"id" : 622
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:soul_fire",
|
||||
@ -3601,11 +3645,11 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:sparkler",
|
||||
"id" : 599
|
||||
"id" : 600
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:spawn_egg",
|
||||
"id" : 628
|
||||
"id" : 636
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:spider_eye",
|
||||
@ -3669,7 +3713,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:spyglass",
|
||||
"id" : 624
|
||||
"id" : 626
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:squid_spawn_egg",
|
||||
@ -3829,7 +3873,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:suspicious_stew",
|
||||
"id" : 589
|
||||
"id" : 590
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:sweet_berries",
|
||||
@ -3839,6 +3883,14 @@
|
||||
"name" : "minecraft:sweet_berry_bush",
|
||||
"id" : -207
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:tadpole_bucket",
|
||||
"id" : 630
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:tadpole_spawn_egg",
|
||||
"id" : 629
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:tallgrass",
|
||||
"id" : 31
|
||||
@ -3943,6 +3995,10 @@
|
||||
"name" : "minecraft:unpowered_repeater",
|
||||
"id" : 93
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:verdant_froglight",
|
||||
"id" : -470
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:vex_spawn_egg",
|
||||
"id" : 476
|
||||
@ -3977,7 +4033,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:warped_door",
|
||||
"id" : 615
|
||||
"id" : 617
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:warped_double_slab",
|
||||
@ -3997,7 +4053,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:warped_fungus_on_a_stick",
|
||||
"id" : 616
|
||||
"id" : 618
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:warped_hyphae",
|
||||
@ -4021,7 +4077,7 @@
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:warped_sign",
|
||||
"id" : 613
|
||||
"id" : 615
|
||||
},
|
||||
{
|
||||
"name" : "minecraft:warped_slab",
|
@ -125,6 +125,13 @@ show-cooldown: title
|
||||
# Controls if coordinates are shown to players.
|
||||
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
|
||||
# There are three options this can be set to:
|
||||
# disabled - the default/fallback, which doesn't apply this workaround
|
||||
|
2
pom.xml
2
pom.xml
@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>2.0.1-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Geyser</name>
|
||||
<description>Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers.</description>
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren