diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 17e88f26..326a1ebd 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -7,51 +7,34 @@ assignees: ''
---
-
-
-
+
**Describe the bug**
-
-A clear and concise description of what the bug is.
+
**To Reproduce**
-
-Steps to reproduce the behavior:
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
+
+
+
+
+
**Expected behavior**
-
-A clear and concise description of what you expected to happen.
+
**Screenshots / Videos**
+
-If applicable, add screenshots to help explain your problem.
+**Server Version**
+
-**Server Version and Plugins**
-
-If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information.
-
-If you're running a multi-server instance, or using Geyser Standalone:
-
-- Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all.
-- Please list all plugins on all servers involved.
-
-If this bug occurs on a server you do not control, please fill this in to the best of your knowledge.
-
-**Geyser Dump**
-
-If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you use the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue.
+**Geyser Version**
+
**Minecraft: Bedrock Edition Version**
-
-The version of your Minecraft: Bedrock Edition client you tested with, along with your device type (e.g. Windows 10, Switch...).
+
**Additional Context**
-
-Add any other context about the problem here.
+
diff --git a/Jenkinsfile b/Jenkinsfile
index 50149136..e7f2ec4e 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -26,27 +26,7 @@ pipeline {
}
steps {
- rtMavenDeployer(
- id: "maven-deployer",
- serverId: "opencollab-artifactory",
- releaseRepo: "maven-releases",
- snapshotRepo: "maven-snapshots"
- )
- rtMavenResolver(
- id: "maven-resolver",
- serverId: "opencollab-artifactory",
- releaseRepo: "release",
- snapshotRepo: "snapshot"
- )
- rtMavenRun(
- pom: 'pom.xml',
- goals: 'javadoc:jar source:jar install -DskipTests',
- deployerId: "maven-deployer",
- resolverId: "maven-resolver"
- )
- rtPublishBuildInfo(
- serverId: "opencollab-artifactory"
- )
+ sh 'mvn javadoc:jar source:jar deploy -DskipTests'
}
}
}
@@ -89,13 +69,5 @@ pipeline {
discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.opencollab.dev/job/GeyserMC/job/Geyser)", footer: 'Open Collaboration Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
}
}
- success {
- script {
- if (env.BRANCH_NAME == 'master') {
- build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.16'
- build propagate: false, wait: false, job: 'GeyserMC/GeyserAndroid/master'
- }
- }
- }
}
}
diff --git a/LICENSE b/LICENSE
index 0e368d54..acd4af14 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License
-Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
+Copyright (c) 2019-2020 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
diff --git a/README.md b/README.md
index 1d1657ed..816f765d 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,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 now joined us here!
-### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.201 and Minecraft Java v1.16.4.
+### Currently supporting Minecraft Bedrock v1.16.100/v1.16.101/v1.16.200 and Minecraft Java v1.16.4.
## Setting Up
Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser.
diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml
index 54e0d56e..124967b0 100644
--- a/bootstrap/bungeecord/pom.xml
+++ b/bootstrap/bungeecord/pom.xml
@@ -86,8 +86,8 @@
org.geysermc.platform.bungeecord.shaded.dom4j
- net.kyori
- org.geysermc.platform.bungeecord.shaded.kyori
+ net.kyori.adventure
+ org.geysermc.platform.bungeecord.shaded.adventure
diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml
index 93eebc3d..adaa7557 100644
--- a/bootstrap/spigot/pom.xml
+++ b/bootstrap/spigot/pom.xml
@@ -97,8 +97,8 @@
org.geysermc.platform.spigot.shaded.dom4j
- net.kyori
- org.geysermc.platform.spigot.shaded.kyori
+ net.kyori.adventure
+ org.geysermc.platform.spigot.shaded.adventure
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
index b85aa313..74b9e03d 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
@@ -157,14 +157,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
if (isViaVersion && isViaVersionNeeded()) {
if (isLegacy) {
// Pre-1.13
- this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager();
+ this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this);
} else {
// Post-1.13
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes);
}
} else {
// No ViaVersion
- this.geyserWorldManager = new GeyserSpigotNativeWorldManager(use3dBiomes);
+ this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, use3dBiomes);
}
geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion);
} catch (Exception e) {
@@ -180,13 +180,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// No NMS adapter
if (isLegacy && isViaVersion) {
// Use ViaVersion for converting pre-1.13 block states
- this.geyserWorldManager = new GeyserSpigot1_12WorldManager();
+ this.geyserWorldManager = new GeyserSpigot1_12WorldManager(this);
} else if (isLegacy) {
// Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air
- this.geyserWorldManager = new GeyserSpigotFallbackWorldManager();
+ this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this);
} else {
// Post-1.13
- this.geyserWorldManager = new GeyserSpigotWorldManager(use3dBiomes);
+ this.geyserWorldManager = new GeyserSpigotWorldManager(this, use3dBiomes);
}
geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass());
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java
index ae199272..f67fd944 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java
@@ -27,6 +27,7 @@ package org.geysermc.platform.spigot.world.manager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
import org.geysermc.adapters.spigot.SpigotAdapters;
import org.geysermc.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.connector.network.session.GeyserSession;
@@ -40,7 +41,8 @@ import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager {
private final SpigotWorldAdapter adapter;
- public GeyserSpigot1_12NativeWorldManager() {
+ public GeyserSpigot1_12NativeWorldManager(Plugin plugin) {
+ super(plugin);
this.adapter = SpigotAdapters.getWorldAdapter();
// Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java
index cb450f7f..987bd440 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java
@@ -30,6 +30,7 @@ import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import us.myles.ViaVersion.api.Pair;
@@ -61,8 +62,8 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager {
*/
private final List> protocolList;
- public GeyserSpigot1_12WorldManager() {
- super(false);
+ public GeyserSpigot1_12WorldManager(Plugin plugin) {
+ super(plugin, false);
this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData();
this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION,
ProtocolVersion.v1_13.getVersion());
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java
index f2ae8a64..4cac791a 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java
@@ -26,6 +26,7 @@
package org.geysermc.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
+import org.bukkit.plugin.Plugin;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
@@ -35,9 +36,9 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator;
* If this occurs to you somehow, please let us know!!
*/
public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager {
- public GeyserSpigotFallbackWorldManager() {
+ public GeyserSpigotFallbackWorldManager(Plugin plugin) {
// Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes.
- super(false);
+ super(plugin, false);
}
@Override
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java
index 8ed1b388..8f407de0 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java
@@ -47,7 +47,7 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl
private final Int2IntMap oldToNewBlockId;
public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) {
- super(use3dBiomes);
+ super(plugin, use3dBiomes);
IntList allBlockStates = adapter.getAllBlockStates();
oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size());
ProtocolVersion serverVersion = plugin.getServerProtocolVersion();
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java
index c7e3a3d4..6576d93a 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java
@@ -27,6 +27,7 @@ package org.geysermc.platform.spigot.world.manager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
import org.geysermc.adapters.spigot.SpigotAdapters;
import org.geysermc.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.connector.network.session.GeyserSession;
@@ -35,8 +36,8 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
protected final SpigotWorldAdapter adapter;
- public GeyserSpigotNativeWorldManager(boolean use3dBiomes) {
- super(use3dBiomes);
+ public GeyserSpigotNativeWorldManager(Plugin plugin, boolean use3dBiomes) {
+ super(plugin, use3dBiomes);
adapter = SpigotAdapters.getWorldAdapter();
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java
index 748d0f1e..25546a3a 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java
@@ -28,23 +28,35 @@ package org.geysermc.platform.spigot.world.manager;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
+import com.nukkitx.math.vector.Vector3i;
+import com.nukkitx.nbt.NbtMap;
+import com.nukkitx.nbt.NbtMapBuilder;
+import com.nukkitx.nbt.NbtType;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
+import org.bukkit.block.Lectern;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.BookMeta;
+import org.bukkit.plugin.Plugin;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator;
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
+import org.geysermc.connector.utils.BlockEntityUtils;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.GameRule;
import org.geysermc.connector.utils.LanguageUtils;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
/**
* The base world manager to use when there is no supported NMS revision
@@ -72,8 +84,11 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
*/
private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length);
- public GeyserSpigotWorldManager(boolean use3dBiomes) {
+ private final Plugin plugin;
+
+ public GeyserSpigotWorldManager(Plugin plugin, boolean use3dBiomes) {
this.use3dBiomes = use3dBiomes;
+ this.plugin = plugin;
// Load the values into the biome-to-ID map
InputStream biomeStream = FileUtils.getResource("biomes.json");
@@ -132,9 +147,6 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
@Override
@SuppressWarnings("deprecation")
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
- if (session.getPlayerEntity() == null) {
- return new int[1024];
- }
int[] biomeData = new int[1024];
World world = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld();
int chunkX = x << 4;
@@ -167,6 +179,56 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
return biomeData;
}
+ @Override
+ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) {
+ // Run as a task to prevent async issues
+ Bukkit.getScheduler().runTask(this.plugin, () -> {
+ Player bukkitPlayer;
+ if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
+ return;
+ }
+ Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
+ if (!(block.getState() instanceof Lectern)) {
+ session.getConnector().getLogger().error("Lectern expected at: " + Vector3i.from(x, y, z).toString() + " but was not! " + block.toString());
+ return;
+ }
+ Lectern lectern = (Lectern) block.getState();
+ ItemStack itemStack = lectern.getInventory().getItem(0);
+ if (itemStack == null || !(itemStack.getItemMeta() instanceof BookMeta)) {
+ if (!isChunkLoad) {
+ // We need to update the lectern since it's not going to be updated otherwise
+ BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
+ }
+ // We don't care; return
+ return;
+ }
+ BookMeta bookMeta = (BookMeta) itemStack.getItemMeta();
+ NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, bookMeta.getPageCount());
+ lecternTag.putInt("page", lectern.getPage() / 2);
+ NbtMapBuilder bookTag = NbtMap.builder()
+ .putByte("Count", (byte) itemStack.getAmount())
+ .putShort("Damage", (short) 0)
+ .putString("Name", "minecraft:writable_book");
+ List pages = new ArrayList<>();
+ for (String page : bookMeta.getPages()) {
+ NbtMapBuilder pageBuilder = NbtMap.builder()
+ .putString("photoname", "")
+ .putString("text", page);
+ pages.add(pageBuilder.build());
+ }
+ bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build());
+ lecternTag.putCompound("book", bookTag.build());
+ NbtMap blockEntityTag = lecternTag.build();
+ BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z));
+ });
+ return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); // Will be updated later
+ }
+
+ @Override
+ public boolean shouldExpectLecternHandled() {
+ return true;
+ }
+
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()));
}
diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml
index 97c4ac8a..e6ce8f85 100644
--- a/bootstrap/sponge/pom.xml
+++ b/bootstrap/sponge/pom.xml
@@ -86,8 +86,8 @@
org.geysermc.platform.sponge.shaded.dom4j
- net.kyori
- org.geysermc.platform.sponge.shaded.kyori
+ net.kyori.adventure
+ org.geysermc.platform.sponge.shaded.adventure
diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml
index 5c0824de..2fedca71 100644
--- a/bootstrap/velocity/pom.xml
+++ b/bootstrap/velocity/pom.xml
@@ -82,8 +82,8 @@
org.geysermc.platform.velocity.shaded.dom4j
- net.kyori
- org.geysermc.platform.velocity.shaded.kyori
+ net.kyori.adventure
+ org.geysermc.platform.velocity.shaded.adventure
diff --git a/connector/pom.xml b/connector/pom.xml
index a7001be0..21f442df 100644
--- a/connector/pom.xml
+++ b/connector/pom.xml
@@ -30,9 +30,9 @@
compile
- com.github.CloudburstMC.Protocol
+ com.nukkitx.protocol
bedrock-v422
- d41b84e86c
+ 2.6.1-SNAPSHOT
compile
@@ -132,10 +132,6 @@
com.github.steveice10
packetlib
-
- com.github.steveice10
- mcauthlib
-
@@ -202,11 +198,6 @@
4.13.1
test
-
- com.github.GeyserMC
- MCAuthLib
- 0e48a094f2
-
diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
index d61500e0..5dd00dab 100644
--- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
+++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
@@ -86,11 +86,6 @@ public class GeyserConnector {
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
public static final String VERSION = "DEV"; // A fallback for running in IDEs
- /**
- * Oauth client ID for Microsoft authentication
- */
- public static final String OAUTH_CLIENT_ID = "204cefd1-4818-4de1-b98d-513fae875d88";
-
private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";
private final List players = new ArrayList<>();
@@ -106,8 +101,8 @@ public class GeyserConnector {
private final ScheduledExecutorService generalThreadPool;
private BedrockServer bedrockServer;
- private final PlatformType platformType;
- private final GeyserBootstrap bootstrap;
+ private PlatformType platformType;
+ private GeyserBootstrap bootstrap;
private Metrics metrics;
diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java
index d31983eb..469d613c 100644
--- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java
+++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java
@@ -52,7 +52,6 @@ public abstract class CommandManager {
registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version"));
registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings"));
registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
- registerCommand(new AdvancementsCommand(connector, "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
}
public void registerCommand(GeyserCommand command) {
diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java
deleted file mode 100644
index 3067f3d5..00000000
--- a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.command.defaults;
-
-import org.geysermc.common.window.SimpleFormWindow;
-import org.geysermc.connector.GeyserConnector;
-import org.geysermc.connector.command.CommandSender;
-import org.geysermc.connector.command.GeyserCommand;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.session.cache.AdvancementsCache;
-
-public class AdvancementsCommand extends GeyserCommand {
-
- private final GeyserConnector connector;
-
- public AdvancementsCommand(GeyserConnector connector, String name, String description, String permission) {
- super(name, description, permission);
-
- this.connector = connector;
- }
-
- @Override
- public void execute(CommandSender sender, String[] args) {
- if (sender.isConsole()) {
- return;
- }
-
- // Make sure the sender is a Bedrock edition client
- GeyserSession session = null;
- if (sender instanceof GeyserSession) {
- session = (GeyserSession) sender;
- } else {
- // Needed for Spigot - sender is not an instance of GeyserSession
- for (GeyserSession otherSession : connector.getPlayers()) {
- if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) {
- session = otherSession;
- break;
- }
- }
- }
- if (session == null) return;
-
- SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm();
- session.sendForm(window, AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID);
- }
-
- @Override
- public boolean isExecutableOnConsole() {
- return false;
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
index e21aa6bb..d21893f8 100644
--- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
+++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
@@ -118,8 +118,6 @@ public interface GeyserConfiguration {
String getAuthType();
- boolean isPasswordAuthentication();
-
boolean isUseProxyProtocol();
}
@@ -127,12 +125,6 @@ public interface GeyserConfiguration {
String getEmail();
String getPassword();
-
- /**
- * Will be removed after Microsoft accounts are fully migrated
- */
- @Deprecated
- boolean isMicrosoftAccount();
}
interface IMetricsInfo {
diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
index 7c9532ff..3a8946e0 100644
--- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
+++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
@@ -149,24 +149,17 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("auth-type")
private String authType = "online";
- @JsonProperty("allow-password-authentication")
- private boolean passwordAuthentication = true;
-
@JsonProperty("use-proxy-protocol")
private boolean useProxyProtocol = false;
}
@Getter
- @JsonIgnoreProperties(ignoreUnknown = true) // DO NOT REMOVE THIS! Otherwise, after we remove microsoft-account configs will not load
public static class UserAuthenticationInfo implements IUserAuthenticationInfo {
@AsteriskSerializer.Asterisk()
private String email;
@AsteriskSerializer.Asterisk()
private String password;
-
- @JsonProperty("microsoft-account")
- private boolean microsoftAccount = false;
}
@Getter
diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java
index d41578db..4a3700d9 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java
@@ -50,6 +50,7 @@ import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.entity.living.ArmorStandEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
+import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.utils.AttributeUtils;
@@ -284,11 +285,12 @@ public class Entity {
// Shield code
if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) {
- if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) ||
- (session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemRegistry.SHIELD.getJavaId())) {
+ PlayerInventory playerInv = session.getPlayerInventory();
+ if ((playerInv.getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) ||
+ (playerInv.getOffhand().getJavaId() == ItemRegistry.SHIELD.getJavaId())) {
ClientPlayerUseItemPacket useItemPacket;
metadata.getFlags().setFlag(EntityFlag.BLOCKING, true);
- if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) {
+ if (playerInv.getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) {
useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
}
// Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent
diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java
index 2b411109..488c0e90 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java
@@ -38,11 +38,11 @@ public class ItemedFireballEntity extends ThrowableEntity {
}
@Override
- public void tick(GeyserSession session) {
+ protected void updatePosition(GeyserSession session) {
position = position.add(motion);
// TODO: While this reduces latency in position updating (needed for better fireball reflecting),
- // TODO: movement is incredibly stiff.
- // TODO: Only use this laggy movement for fireballs that be reflected
+ // TODO: movement is incredibly stiff. See if the MoveEntityDeltaPacket in 1.16.100 fixes this, and if not,
+ // TODO: only use this laggy movement for fireballs that be reflected
moveAbsoluteImmediate(session, position, rotation, false, true);
float drag = getDrag(session);
motion = motion.add(acceleration).mul(drag);
diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java
index 4e0c25ab..553e558e 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java
@@ -33,35 +33,50 @@ import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
/**
* Used as a class for any object-like entity that moves as a projectile
*/
-public class ThrowableEntity extends Entity implements Tickable {
+public class ThrowableEntity extends Entity {
private Vector3f lastPosition;
+ /**
+ * Updates the position for the Bedrock client.
+ *
+ * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions
+ */
+ protected ScheduledFuture> positionUpdater;
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
this.lastPosition = position;
}
- /**
- * Updates the position for the Bedrock client.
- *
- * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions
- */
@Override
- public void tick(GeyserSession session) {
- super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
- float drag = getDrag(session);
- float gravity = getGravity();
- motion = motion.mul(drag).down(gravity);
+ public void spawnEntity(GeyserSession session) {
+ super.spawnEntity(session);
+ positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
+ if (session.isClosed()) {
+ positionUpdater.cancel(true);
+ return;
+ }
+ updatePosition(session);
+ }, 0, 50, TimeUnit.MILLISECONDS);
}
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
}
+ protected void updatePosition(GeyserSession session) {
+ super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
+ float drag = getDrag(session);
+ float gravity = getGravity();
+ motion = motion.mul(drag).down(gravity);
+ }
+
/**
* Get the gravity of this entity type. Used for applying gravity while the entity is in motion.
*
@@ -125,6 +140,7 @@ public class ThrowableEntity extends Entity implements Tickable {
@Override
public boolean despawnEntity(GeyserSession session) {
+ positionUpdater.cancel(true);
if (entityType == EntityType.THROWN_ENDERPEARL) {
LevelEventPacket particlePacket = new LevelEventPacket();
particlePacket.setType(LevelEventType.PARTICLE_TELEPORT);
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java
index 628beff1..41073246 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java
@@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.living.animal.AnimalEntity;
import org.geysermc.connector.entity.type.EntityType;
@@ -40,6 +41,9 @@ public class AbstractHorseEntity extends AnimalEntity {
public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
+
+ // Specifies the size of the entity's inventory. Required to place slots in the entity.
+ metadata.put(EntityData.CONTAINER_BASE_SIZE, 2);
}
@Override
@@ -75,6 +79,9 @@ public class AbstractHorseEntity extends AnimalEntity {
entityEventPacket.setData(ItemRegistry.WHEAT.getBedrockId() << 16);
session.sendUpstreamPacket(entityEventPacket);
}
+
+ // Set container type if tamed
+ metadata.put(EntityData.CONTAINER_TYPE, ((xd & 0x02) == 0x02) ? (byte) ContainerType.HORSE.getId() : (byte) 0);
}
// Needed to control horses
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java
index f67567c9..461d636b 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java
@@ -27,6 +27,7 @@ package org.geysermc.connector.entity.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
+import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@@ -35,6 +36,8 @@ public class ChestedHorseEntity extends AbstractHorseEntity {
public ChestedHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
+
+ metadata.put(EntityData.CONTAINER_BASE_SIZE, 16);
}
@Override
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java
index a04539dc..5fdde527 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java
@@ -38,6 +38,8 @@ public class LlamaEntity extends ChestedHorseEntity {
public LlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
+
+ metadata.put(EntityData.CONTAINER_STRENGTH_MODIFIER, 3); // Presumably 3 slots for every 1 strength
}
@Override
@@ -56,7 +58,7 @@ public class LlamaEntity extends ChestedHorseEntity {
// The damage value is the dye color that Java sends us
// Always going to be a carpet so we can hardcode 171 in BlockTranslator
// The int then short conversion is required or we get a ClassCastException
- equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short)((int) entityMetadata.getValue()), 1));
+ equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short) ((int) entityMetadata.getValue()), 1));
} else {
equipmentPacket.setChestplate(ItemData.AIR);
}
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java
index 62167979..7dbd96a4 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java
@@ -28,28 +28,19 @@ package org.geysermc.connector.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.AttributeData;
-import com.nukkitx.protocol.bedrock.data.LevelEventType;
-import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
-import com.nukkitx.protocol.bedrock.packet.*;
+import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
+import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import lombok.Data;
-import org.geysermc.connector.entity.Tickable;
-import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.entity.living.InsentientEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.utils.AttributeUtils;
-import org.geysermc.connector.utils.DimensionUtils;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
-public class EnderDragonEntity extends InsentientEntity implements Tickable {
+public class EnderDragonEntity extends InsentientEntity {
/**
* The Ender Dragon has multiple hit boxes, which
* are each its own invisible entity
@@ -70,19 +61,9 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
private final Segment[] segmentHistory = new Segment[19];
private int latestSegment = -1;
- private int phase;
- /**
- * The number of ticks since the beginning of the phase
- */
- private int phaseTicks;
+ private boolean hovering;
- private int ticksTillNextGrowl = 100;
-
- /**
- * Used to determine when the wing flap sound should be played
- */
- private float wingPosition;
- private float lastWingPosition;
+ private ScheduledFuture> partPositionUpdater;
public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
@@ -92,67 +73,49 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
- if (entityMetadata.getId() == 15) { // Phase
- phase = (int) entityMetadata.getValue();
- phaseTicks = 0;
- metadata.getFlags().setFlag(EntityFlag.SITTING, isSitting());
- }
-
- super.updateBedrockMetadata(entityMetadata, session);
-
- if (entityMetadata.getId() == 8) { // Health
- // Update the health attribute, so that the death animation gets played
- // Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1
- float health = (float) Math.ceil(metadata.getFloat(EntityData.HEALTH));
- if (phase == 9 && health <= 0) { // Dying phase
+ // Phase
+ if (entityMetadata.getId() == 15) {
+ int value = (int) entityMetadata.getValue();
+ if (value == 5) {
+ // Performing breath attack
EntityEventPacket entityEventPacket = new EntityEventPacket();
- entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH);
+ entityEventPacket.setType(EntityEventType.DRAGON_FLAMING);
entityEventPacket.setRuntimeEntityId(geyserId);
entityEventPacket.setData(0);
session.sendUpstreamPacket(entityEventPacket);
}
- attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, 200));
- updateBedrockAttributes(session);
+ metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7);
+ hovering = value == 10;
}
- }
-
- /**
- * Send an updated list of attributes to the Bedrock client.
- * This is overwritten to allow the health attribute to differ from
- * the health specified in the metadata.
- *
- * @param session GeyserSession
- */
- @Override
- public void updateBedrockAttributes(GeyserSession session) {
- if (!valid) return;
-
- List attributes = new ArrayList<>();
- for (Map.Entry entry : this.attributes.entrySet()) {
- if (!entry.getValue().getType().isBedrockAttribute())
- continue;
- attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue()));
- }
-
- UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
- updateAttributesPacket.setRuntimeEntityId(geyserId);
- updateAttributesPacket.setAttributes(attributes);
- session.sendUpstreamPacket(updateAttributesPacket);
+ super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void spawnEntity(GeyserSession session) {
- super.spawnEntity(session);
+ AddEntityPacket addEntityPacket = new AddEntityPacket();
+ addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase());
+ addEntityPacket.setRuntimeEntityId(geyserId);
+ addEntityPacket.setUniqueEntityId(geyserId);
+ addEntityPacket.setPosition(position);
+ addEntityPacket.setMotion(motion);
+ addEntityPacket.setRotation(getBedrockRotation());
+ addEntityPacket.setEntityType(entityType.getType());
+ addEntityPacket.getMetadata().putAll(metadata);
- AtomicLong nextEntityId = session.getEntityCache().getNextEntityId();
- head = new EnderDragonPartEntity(entityId + 1, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 1, 1);
- neck = new EnderDragonPartEntity(entityId + 2, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 3, 3);
- body = new EnderDragonPartEntity(entityId + 3, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 5, 3);
- leftWing = new EnderDragonPartEntity(entityId + 4, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2);
- rightWing = new EnderDragonPartEntity(entityId + 5, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2);
+ // Otherwise dragon is always 'dying'
+ addEntityPacket.getAttributes().add(new AttributeData("minecraft:health", 0.0f, 200f, 200f, 200f));
+
+ valid = true;
+ session.sendUpstreamPacket(addEntityPacket);
+
+ head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1);
+ neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3);
+ body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3);
+ leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
+ rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
tail = new EnderDragonPartEntity[3];
for (int i = 0; i < 3; i++) {
- tail[i] = new EnderDragonPartEntity(entityId + 6 + i, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 2, 2);
+ tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2);
}
allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]};
@@ -166,25 +129,25 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
segmentHistory[i].yaw = rotation.getZ();
segmentHistory[i].y = position.getY();
}
+
+ partPositionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
+ pushSegment();
+ updateBoundingBoxes(session);
+ }, 0, 50, TimeUnit.MILLISECONDS);
+
+ session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
}
@Override
public boolean despawnEntity(GeyserSession session) {
+ partPositionUpdater.cancel(true);
+
for (EnderDragonPartEntity part : allParts) {
part.despawnEntity(session);
}
return super.despawnEntity(session);
}
- @Override
- public void tick(GeyserSession session) {
- effectTick(session);
- if (!metadata.getFlags().getFlag(EntityFlag.NO_AI) && isAlive()) {
- pushSegment();
- updateBoundingBoxes(session);
- }
- }
-
/**
* Updates the positions of the Ender Dragon's multiple bounding boxes
*
@@ -200,7 +163,7 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
// Lowers the head when the dragon sits/hovers
float headDuck;
- if (isHovering() || isSitting()) {
+ if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) {
headDuck = -1f;
} else {
headDuck = baseSegment.y - getSegment(0).y;
@@ -230,105 +193,6 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
}
}
- /**
- * Handles the particles and sounds of the Ender Dragon
- * @param session GeyserSession.
- */
- private void effectTick(GeyserSession session) {
- Random random = ThreadLocalRandom.current();
- if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) {
- if (Math.cos(wingPosition * 2f * Math.PI) <= -0.3f && Math.cos(lastWingPosition * 2f * Math.PI) >= -0.3f) {
- PlaySoundPacket playSoundPacket = new PlaySoundPacket();
- playSoundPacket.setSound("mob.enderdragon.flap");
- playSoundPacket.setPosition(position);
- playSoundPacket.setVolume(5f);
- playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f);
- session.sendUpstreamPacket(playSoundPacket);
- }
-
- if (!isSitting() && !isHovering() && ticksTillNextGrowl-- == 0) {
- playGrowlSound(session);
- ticksTillNextGrowl = 200 + random.nextInt(200);
- }
-
- lastWingPosition = wingPosition;
- }
- if (isAlive()) {
- if (metadata.getFlags().getFlag(EntityFlag.NO_AI)) {
- wingPosition = 0.5f;
- } else if (isHovering() || isSitting()) {
- wingPosition += 0.1f;
- } else {
- double speed = motion.length();
- wingPosition += 0.2f / (speed * 10f + 1) * Math.pow(2, motion.getY());
- }
-
- phaseTicks++;
- if (phase == 3) { // Landing Phase
- float headHeight = head.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT);
- Vector3f headCenter = head.getPosition().up(headHeight * 0.5f);
-
- for (int i = 0; i < 8; i++) {
- Vector3f particlePos = headCenter.add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f);
- // This is missing velocity information
- LevelEventPacket particlePacket = new LevelEventPacket();
- particlePacket.setType(LevelEventType.PARTICLE_DRAGONS_BREATH);
- particlePacket.setPosition(particlePos);
- session.sendUpstreamPacket(particlePacket);
- }
- } else if (phase == 5) { // Sitting Flaming Phase
- if (phaseTicks % 2 == 0 && phaseTicks < 10) {
- // Performing breath attack
- // Entity event DRAGON_FLAMING seems to create particles from the origin of the dragon,
- // so we need to manually spawn particles
- for (int i = 0; i < 8; i++) {
- SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket();
- spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
- spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f));
- spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire");
- session.sendUpstreamPacket(spawnParticleEffectPacket);
- }
- }
- } else if (phase == 7) { // Sitting Attacking Phase
- playGrowlSound(session);
- } else if (phase == 9) { // Dying Phase
- // Send explosion particles as the dragon move towards the end portal
- if (phaseTicks % 10 == 0) {
- float xOffset = 8f * (random.nextFloat() - 0.5f);
- float yOffset = 4f * (random.nextFloat() - 0.5f) + 2f;
- float zOffset = 8f * (random.nextFloat() - 0.5f);
- Vector3f particlePos = position.add(xOffset, yOffset, zOffset);
- LevelEventPacket particlePacket = new LevelEventPacket();
- particlePacket.setType(LevelEventType.PARTICLE_EXPLOSION);
- particlePacket.setPosition(particlePos);
- session.sendUpstreamPacket(particlePacket);
- }
- }
- }
- }
-
- private void playGrowlSound(GeyserSession session) {
- Random random = ThreadLocalRandom.current();
- PlaySoundPacket playSoundPacket = new PlaySoundPacket();
- playSoundPacket.setSound("mob.enderdragon.growl");
- playSoundPacket.setPosition(position);
- playSoundPacket.setVolume(2.5f);
- playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f);
- session.sendUpstreamPacket(playSoundPacket);
- }
-
- private boolean isAlive() {
- return metadata.getFloat(EntityData.HEALTH) > 0;
- }
-
- private boolean isHovering() {
- return phase == 10;
- }
-
- private boolean isSitting() {
- return phase == 5 || phase == 6 || phase == 7;
- }
-
/**
* Store the current yaw and y into the circular buffer
*/
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java
index 288a3e42..095d12b2 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java
@@ -32,12 +32,11 @@ import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.type.EntityType;
public class EnderDragonPartEntity extends Entity {
- public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, float width, float height) {
- super(entityId, geyserId, entityType, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
+ public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) {
+ super(entityId, geyserId, entityType, position, motion, rotation);
metadata.put(EntityData.BOUNDING_BOX_WIDTH, width);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height);
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
- metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java
index 3151ae47..e12b60d6 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java
@@ -27,10 +27,8 @@ package org.geysermc.connector.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
-import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
-import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
@@ -47,21 +45,11 @@ public class EndermanEntity extends MonsterEntity {
if (entityMetadata.getId() == 15) {
metadata.put(EntityData.CARRIED_BLOCK, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue()));
}
- // "Is screaming" - controls sound
+ // 'Angry' - mouth open
if (entityMetadata.getId() == 16) {
- if ((boolean) entityMetadata.getValue()) {
- LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet();
- packet.setSound(SoundEvent.STARE);
- packet.setPosition(this.position);
- packet.setExtraData(-1);
- packet.setIdentifier("minecraft:enderman");
- session.sendUpstreamPacket(packet);
- }
- }
- // "Is staring/provoked" - controls visuals
- if (entityMetadata.getId() == 17) {
metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue());
}
+ // TODO: ID 17 is stared at but I don't believe it's used - maybe only for the sound effect. Check after particle merge
super.updateBedrockMetadata(entityMetadata, session);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java
new file mode 100644
index 00000000..02e1c225
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.inventory;
+
+/**
+ * Used to determine if rename packets should be sent.
+ */
+public class AnvilContainer extends Container {
+ public AnvilContainer(String title, int id, int size, PlayerInventory playerInventory) {
+ super(title, id, size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java b/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java
similarity index 74%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java
rename to connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java
index fdfc2d57..66de2d8a 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java
+++ b/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java
@@ -23,16 +23,18 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory.action;
+package org.geysermc.connector.inventory;
-import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam;
-import com.github.steveice10.mc.protocol.data.game.window.WindowActionParam;
-import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
-@AllArgsConstructor
-enum Click {
- LEFT(ClickItemParam.LEFT_CLICK),
- RIGHT(ClickItemParam.RIGHT_CLICK);
+@Getter
+@Setter
+public class BeaconContainer extends Container {
+ private int primaryId;
+ private int secondaryId;
- public final WindowActionParam actionParam;
+ public BeaconContainer(String title, int id, int size, PlayerInventory playerInventory) {
+ super(title, id, size, playerInventory);
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java
new file mode 100644
index 00000000..be4abd9e
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.inventory;
+
+public class CartographyContainer extends Container {
+ public CartographyContainer(String title, int id, int size, PlayerInventory playerInventory) {
+ super(title, id, size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Container.java
new file mode 100644
index 00000000..520a76ef
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/inventory/Container.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.inventory;
+
+import lombok.Getter;
+import lombok.NonNull;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+
+/**
+ * Combination of {@link Inventory} and {@link PlayerInventory}
+ */
+@Getter
+public class Container extends Inventory {
+ private final PlayerInventory playerInventory;
+ private final int containerSize;
+
+ public Container(String title, int id, int size, PlayerInventory playerInventory) {
+ super(title, id, size);
+ this.playerInventory = playerInventory;
+ this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE;
+ }
+
+ @Override
+ public GeyserItemStack getItem(int slot) {
+ if (slot < this.size) {
+ return super.getItem(slot);
+ } else {
+ return playerInventory.getItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET);
+ }
+ }
+
+ @Override
+ public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
+ if (slot < this.size) {
+ super.setItem(slot, newItem, session);
+ } else {
+ playerInventory.setItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET, newItem, session);
+ }
+ }
+
+ @Override
+ public int getSize() {
+ return this.containerSize;
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java
new file mode 100644
index 00000000..8638e6ea
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.inventory;
+
+import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
+import lombok.Getter;
+
+public class EnchantingContainer extends Container {
+ /**
+ * A cache of what Bedrock sees
+ */
+ @Getter
+ private final EnchantOptionData[] enchantOptions;
+ /**
+ * A mutable cache of what the server sends us
+ */
+ @Getter
+ private final GeyserEnchantOption[] geyserEnchantOptions;
+
+ public EnchantingContainer(String title, int id, int size, PlayerInventory playerInventory) {
+ super(title, id, size, playerInventory);
+
+ enchantOptions = new EnchantOptionData[3];
+ geyserEnchantOptions = new GeyserEnchantOption[3];
+ for (int i = 0; i < geyserEnchantOptions.length; i++) {
+ geyserEnchantOptions[i] = new GeyserEnchantOption(i);
+ }
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java
new file mode 100644
index 00000000..a643fc19
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.inventory;
+
+import com.nukkitx.protocol.bedrock.data.inventory.EnchantData;
+import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
+import lombok.Getter;
+import lombok.Setter;
+import org.geysermc.connector.network.session.GeyserSession;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A mutable "wrapper" around {@link EnchantOptionData}
+ */
+@Setter
+public class GeyserEnchantOption {
+ private static final List EMPTY = Collections.emptyList();
+ /**
+ * This: https://cdn.discordapp.com/attachments/613168850925649981/791030657169227816/unknown.png
+ * is controlled by the server.
+ * So, of course, we have to throw in some easter eggs. ;)
+ */
+ private static final List ENCHANT_NAMES = Arrays.asList("tougher armor", "lukeeey", "fall better",
+ "explode less", "camo toy", "breathe better", "rtm five one six", "armor stab", "water walk", "you are elsa",
+ "tim two zero three", "fast walk nether", "oof ouch owie", "enemy on fire", "spider sad", "aj ferguson", "redned",
+ "more items thx", "long sword reach", "fast tool", "give me block", "less breaky break", "cube craft",
+ "strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser", "come here fish", "i like this",
+ "stabby stab", "supreme mortal", "avatar i guess", "more arrows", "fly finder seventeen", "in and out",
+ "xp heals tools", "dragon proxy waz here");
+
+ @Getter
+ private final int javaIndex;
+
+ private int xpCost = 0;
+ private int javaEnchantIndex = -1;
+ private int bedrockEnchantIndex = -1;
+ private int enchantLevel = -1;
+
+ public GeyserEnchantOption(int javaIndex) {
+ this.javaIndex = javaIndex;
+ }
+
+ public EnchantOptionData build(GeyserSession session) {
+ if (enchantLevel == -1) {
+ // Should not be sent to the client, as it is supposed to be empty
+ return null;
+ }
+ return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY,
+ Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY,
+ javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), session.getNextItemNetId());
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java
new file mode 100644
index 00000000..76f7674a
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.inventory;
+
+import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
+import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
+import lombok.Data;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.item.ItemEntry;
+import org.geysermc.connector.network.translators.item.ItemRegistry;
+import org.geysermc.connector.network.translators.item.ItemTranslator;
+
+@Data
+public class GeyserItemStack {
+ public static final GeyserItemStack EMPTY = new GeyserItemStack(0, 0, null);
+
+ private final int javaId;
+ private int amount;
+ private CompoundTag nbt;
+ private int netId;
+
+ public GeyserItemStack(int javaId) {
+ this(javaId, 1);
+ }
+
+ public GeyserItemStack(int javaId, int amount) {
+ this(javaId, amount, null);
+ }
+
+ public GeyserItemStack(int javaId, int amount, CompoundTag nbt) {
+ this(javaId, amount, nbt, 1);
+ }
+
+ public GeyserItemStack(int javaId, int amount, CompoundTag nbt, int netId) {
+ this.javaId = javaId;
+ this.amount = amount;
+ this.nbt = nbt;
+ this.netId = netId;
+ }
+
+ public int getJavaId() {
+ return isEmpty() ? 0 : javaId;
+ }
+
+ public int getAmount() {
+ return isEmpty() ? 0 : amount;
+ }
+
+ public CompoundTag getNbt() {
+ return isEmpty() ? null : nbt;
+ }
+
+ public void setNetId(int netId) {
+ this.netId = netId;
+ }
+
+ public int getNetId() {
+ return isEmpty() ? 0 : netId;
+ }
+
+ public void add(int add) {
+ amount += add;
+ }
+
+ public void sub(int sub) {
+ amount -= sub;
+ }
+
+ public static GeyserItemStack from(ItemStack itemStack) {
+ return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt());
+ }
+
+ public ItemStack getItemStack() {
+ return isEmpty() ? null : new ItemStack(javaId, amount, nbt);
+ }
+
+ public ItemData getItemData(GeyserSession session) {
+ ItemData itemData = ItemTranslator.translateToBedrock(session, getItemStack());
+ itemData.setNetId(getNetId());
+ return itemData;
+ }
+
+ public ItemEntry getItemEntry() {
+ return ItemRegistry.ITEM_ENTRIES.get(getJavaId());
+ }
+
+ public boolean isEmpty() {
+ return amount <= 0 || javaId == 0;
+ }
+
+ public GeyserItemStack copy() {
+ return copy(amount);
+ }
+
+ public GeyserItemStack copy(int newAmount) {
+ return isEmpty() ? EMPTY : new GeyserItemStack(javaId, newAmount, nbt == null ? null : nbt.clone(), netId);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java
index 41ae994f..7048ce80 100644
--- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java
+++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java
@@ -25,26 +25,19 @@
package org.geysermc.connector.inventory;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.math.vector.Vector3i;
import lombok.Getter;
+import lombok.NonNull;
import lombok.Setter;
+import org.geysermc.connector.network.session.GeyserSession;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.Arrays;
public class Inventory {
@Getter
protected int id;
- @Getter
- @Setter
- protected boolean open;
-
- @Getter
- protected WindowType windowType;
-
@Getter
protected final int size;
@@ -52,9 +45,11 @@ public class Inventory {
@Setter
protected String title;
- @Setter
- protected ItemStack[] items;
+ protected GeyserItemStack[] items;
+ /**
+ * The location of the inventory block. Will either be a fake block above the player's head, or the actual block location
+ */
@Getter
@Setter
protected Vector3i holderPosition = Vector3i.ZERO;
@@ -64,27 +59,41 @@ public class Inventory {
protected long holderId = -1;
@Getter
- protected AtomicInteger transactionId = new AtomicInteger(1);
+ protected short transactionId = 0;
- public Inventory(int id, WindowType windowType, int size) {
- this("Inventory", id, windowType, size);
+ protected Inventory(int id, int size) {
+ this("Inventory", id, size);
}
- public Inventory(String title, int id, WindowType windowType, int size) {
+ protected Inventory(String title, int id, int size) {
this.title = title;
this.id = id;
- this.windowType = windowType;
this.size = size;
- this.items = new ItemStack[size];
+ this.items = new GeyserItemStack[size];
+ Arrays.fill(items, GeyserItemStack.EMPTY);
}
- public ItemStack getItem(int slot) {
+ public GeyserItemStack getItem(int slot) {
return items[slot];
}
- public void setItem(int slot, ItemStack item) {
- if (item != null && (item.getId() == 0 || item.getAmount() < 1))
- item = null;
- items[slot] = item;
+ public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
+ GeyserItemStack oldItem = items[slot];
+ updateItemNetId(oldItem, newItem, session);
+ items[slot] = newItem;
+ }
+
+ protected static void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) {
+ if (!newItem.isEmpty()) {
+ if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) {
+ newItem.setNetId(oldItem.getNetId());
+ } else {
+ newItem.setNetId(session.getNextItemNetId());
+ }
+ }
+ }
+
+ public short getNextTransactionId() {
+ return ++transactionId;
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java
new file mode 100644
index 00000000..1b686a8f
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.inventory;
+
+import com.nukkitx.math.vector.Vector3i;
+import com.nukkitx.nbt.NbtMap;
+import lombok.Getter;
+import lombok.Setter;
+
+public class LecternContainer extends Container {
+ @Getter @Setter
+ private int currentBedrockPage = 0;
+ @Getter @Setter
+ private NbtMap blockEntityTag;
+ @Getter @Setter
+ private Vector3i position;
+
+ public LecternContainer(String title, int id, int size, PlayerInventory playerInventory) {
+ super(title, id, size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java
new file mode 100644
index 00000000..f4f2d90e
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.inventory;
+
+import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
+import lombok.Getter;
+import lombok.Setter;
+import org.geysermc.connector.entity.Entity;
+
+@Getter
+@Setter
+public class MerchantContainer extends Container {
+ private Entity villager;
+ private VillagerTrade[] villagerTrades;
+
+ public MerchantContainer(String title, int id, int size, PlayerInventory playerInventory) {
+ super(title, id, size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java
index 4816e3c3..9d03c69a 100644
--- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java
+++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java
@@ -25,9 +25,10 @@
package org.geysermc.connector.inventory;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import lombok.Getter;
+import lombok.NonNull;
import lombok.Setter;
+import org.geysermc.connector.network.session.GeyserSession;
public class PlayerInventory extends Inventory {
@@ -40,20 +41,28 @@ public class PlayerInventory extends Inventory {
private int heldItemSlot;
@Getter
- private ItemStack cursor;
+ @NonNull
+ private GeyserItemStack cursor = GeyserItemStack.EMPTY;
public PlayerInventory() {
- super(0, null, 46);
+ super(0, 46);
heldItemSlot = 0;
}
- public void setCursor(ItemStack stack) {
- if (stack != null && (stack.getId() == 0 || stack.getAmount() < 1))
- stack = null;
- cursor = stack;
+ public void setCursor(@NonNull GeyserItemStack newCursor, GeyserSession session) {
+ updateItemNetId(cursor, newCursor, session);
+ cursor = newCursor;
}
- public ItemStack getItemInHand() {
+ public GeyserItemStack getItemInHand() {
return items[36 + heldItemSlot];
}
+
+ public void setItemInHand(@NonNull GeyserItemStack item) {
+ items[36 + heldItemSlot] = item;
+ }
+
+ public GeyserItemStack getOffhand() {
+ return items[45];
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java b/connector/src/main/java/org/geysermc/connector/inventory/StonecutterContainer.java
similarity index 63%
rename from connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java
rename to connector/src/main/java/org/geysermc/connector/inventory/StonecutterContainer.java
index 3ead687f..48620282 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java
+++ b/connector/src/main/java/org/geysermc/connector/inventory/StonecutterContainer.java
@@ -23,39 +23,31 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.session.cache;
+package org.geysermc.connector.inventory;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
+import lombok.NonNull;
import lombok.Setter;
-import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
-public class InventoryCache {
-
- private GeyserSession session;
-
+public class StonecutterContainer extends Container {
+ /**
+ * The button that has currently been pressed Java-side
+ */
@Getter
@Setter
- private Inventory openInventory;
+ private int stonecutterButton = -1;
- @Getter
- private Int2ObjectMap inventories = new Int2ObjectOpenHashMap<>();
-
- public InventoryCache(GeyserSession session) {
- this.session = session;
+ public StonecutterContainer(String title, int id, int size, PlayerInventory playerInventory) {
+ super(title, id, size, playerInventory);
}
- public Inventory getPlayerInventory() {
- return inventories.get(0);
- }
-
- public void cacheInventory(Inventory inventory) {
- inventories.put(inventory.getId(), inventory);
- }
-
- public void uncacheInventory(int id) {
- inventories.remove(id);
+ @Override
+ public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
+ if (slot == 0 && newItem.getJavaId() != items[slot].getJavaId()) {
+ // The pressed stonecutter button output resets whenever the input item changes
+ this.stonecutterButton = -1;
+ }
+ super.setItem(slot, newItem, session);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java
index 87883087..79c04f67 100644
--- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java
+++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java
@@ -30,16 +30,15 @@ import com.nukkitx.protocol.bedrock.BedrockServerEventHandler;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
-import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.ping.GeyserPingInfo;
+import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
+import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.utils.LanguageUtils;
import java.net.InetSocketAddress;
-import java.nio.charset.StandardCharsets;
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
@@ -95,20 +94,6 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
pong.setMaximumPlayerCount(config.getMaxPlayers());
}
- // The ping will not appear if the MOTD + sub-MOTD is of a certain length.
- // We don't know why, though
- byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
- if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
- // Remove the sub-MOTD first since that only appears locally
- pong.setSubMotd("");
- if (motdArray.length > 338) {
- // If the top MOTD is still too long, we chop it down
- byte[] newMotdArray = new byte[339];
- System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
- pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
- }
- }
-
//Bedrock will not even attempt a connection if the client thinks the server is full
//so we have to fake it not being full
if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java
index 7ebfaeda..a6a369e4 100644
--- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java
+++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java
@@ -33,9 +33,14 @@ import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.session.cache.AdvancementsCache;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
-import org.geysermc.connector.utils.*;
+import org.geysermc.connector.utils.LanguageUtils;
+import org.geysermc.connector.utils.LoginEncryptionUtils;
+import org.geysermc.connector.utils.MathUtils;
+import org.geysermc.connector.utils.ResourcePack;
+import org.geysermc.connector.utils.ResourcePackManifest;
+import org.geysermc.connector.utils.SettingsUtils;
+import org.geysermc.connector.utils.StatisticsUtils;
import java.io.FileInputStream;
import java.io.InputStream;
@@ -139,19 +144,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public boolean handle(ModalFormResponsePacket packet) {
- switch (packet.getFormId()) {
- case AdvancementsCache.ADVANCEMENT_INFO_FORM_ID:
- return session.getAdvancementsCache().handleInfoForm(packet.getFormData());
- case AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID:
- return session.getAdvancementsCache().handleListForm(packet.getFormData());
- case AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID:
- return session.getAdvancementsCache().handleMenuForm(packet.getFormData());
- case SettingsUtils.SETTINGS_FORM_ID:
- return SettingsUtils.handleSettingsForm(session, packet.getFormData());
- case StatisticsUtils.STATISTICS_LIST_FORM_ID:
- return StatisticsUtils.handleListForm(session, packet.getFormData());
- case StatisticsUtils.STATISTICS_MENU_FORM_ID:
- return StatisticsUtils.handleMenuForm(session, packet.getFormData());
+ if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) {
+ return SettingsUtils.handleSettingsForm(session, packet.getFormData());
+ } else if (packet.getFormId() == StatisticsUtils.STATISTICS_MENU_FORM_ID) {
+ return StatisticsUtils.handleMenuForm(session, packet.getFormData());
+ } else if (packet.getFormId() == StatisticsUtils.STATISTICS_LIST_FORM_ID) {
+ return StatisticsUtils.handleListForm(session, packet.getFormData());
}
return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData());
@@ -163,7 +161,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
if (info != null) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName()));
- session.setMicrosoftAccount(info.isMicrosoftAccount());
session.authenticate(info.getEmail(), info.getPassword());
// TODO send a message to bedrock user telling them they are connected (if nothing like a motd
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
index 5b43fec0..e2587ee0 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
@@ -26,22 +26,19 @@
package org.geysermc.connector.network.session;
import com.github.steveice10.mc.auth.data.GameProfile;
-import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException;
import com.github.steveice10.mc.auth.exception.request.RequestException;
-import com.github.steveice10.mc.auth.service.AuthenticationService;
-import com.github.steveice10.mc.auth.service.MojangAuthenticationService;
-import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.SubProtocol;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
+import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.statistic.Statistic;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
-import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
+import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket;
import com.github.steveice10.packetlib.BuiltinFlags;
import com.github.steveice10.packetlib.Client;
@@ -57,24 +54,27 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.geysermc.common.window.CustomFormWindow;
import org.geysermc.common.window.FormWindow;
import org.geysermc.connector.GeyserConnector;
-import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.player.SessionPlayerEntity;
import org.geysermc.connector.entity.player.SkullPlayerEntity;
+import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.remote.RemoteServer;
import org.geysermc.connector.network.session.auth.AuthData;
@@ -85,7 +85,7 @@ import org.geysermc.connector.network.translators.EntityIdentifierRegistry;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.network.translators.collision.CollisionManager;
-import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.skin.SkinManager;
import org.geysermc.connector.utils.*;
@@ -98,9 +98,8 @@ import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
@Getter
public class GeyserSession implements CommandSender {
@@ -114,23 +113,33 @@ public class GeyserSession implements CommandSender {
@Setter
private BedrockClientData clientData;
- @Deprecated
- @Setter
- private boolean microsoftAccount;
-
private final SessionPlayerEntity playerEntity;
- private PlayerInventory inventory;
- private AdvancementsCache advancementsCache;
- private BookEditCache bookEditCache;
private ChunkCache chunkCache;
private EntityCache entityCache;
private EntityEffectCache effectCache;
- private InventoryCache inventoryCache;
private WorldCache worldCache;
private WindowCache windowCache;
private final Int2ObjectMap teleportMap = new Int2ObjectOpenHashMap<>();
+ private final PlayerInventory playerInventory;
+ @Setter
+ private Inventory openInventory;
+
+ @Setter
+ private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
+
+ /**
+ * Use {@link #getNextItemNetId()} instead for consistency
+ */
+ @Getter(AccessLevel.NONE)
+ private final AtomicInteger itemNetId = new AtomicInteger(1);
+
+ @Getter(AccessLevel.NONE)
+ private final Object inventoryLock = new Object();
+ @Getter(AccessLevel.NONE)
+ private CompletableFuture inventoryFuture;
+
/**
* Stores session collision
*/
@@ -145,6 +154,16 @@ public class GeyserSession implements CommandSender {
*/
private final Object2LongMap itemFrameCache = new Object2LongOpenHashMap<>();
+ /**
+ * Stores a list of all lectern locations and their block entity tags.
+ * See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)}
+ * for more information.
+ */
+ private final List lecternCache = new ArrayList<>();
+
+ @Setter
+ private boolean droppingLecternBook;
+
@Setter
private Vector2i lastChunkPosition = null;
private int renderDistance;
@@ -159,11 +178,7 @@ public class GeyserSession implements CommandSender {
@Setter
private GameMode gameMode = GameMode.SURVIVAL;
- /**
- * Keeps track of the world name for respawning.
- */
- @Setter
- private String worldName = null;
+ private final AtomicInteger pendingDimSwitches = new AtomicInteger(0);
private boolean sneaking;
@@ -200,11 +215,11 @@ public class GeyserSession implements CommandSender {
@Setter
private Vector3i lastInteractionPosition = Vector3i.ZERO;
- @Setter
- private Entity ridingVehicleEntity;
+ private boolean manyDimPackets = false;
+ private ServerRespawnPacket lastDimPacket = null;
@Setter
- private int craftSlot = 0;
+ private Entity ridingVehicleEntity;
@Setter
private long lastWindowCloseTime = 0;
@@ -214,10 +229,16 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastInteractedVillagerEid;
+ @Setter
+ private Int2ObjectMap craftingRecipes;
+ private final Set unlockedRecipes;
+
/**
- * Stores the enchantment information the client has received if they are in an enchantment table GUI
+ * Saves a list of all stonecutter recipes, for use in a stonecutter inventory.
+ * The key is the Java ID of the item; the values are all the possible outputs' Java IDs sorted by their string identifier
*/
- private final EnchantmentInventoryTranslator.EnchantmentSlotData[] enchantmentSlotData = new EnchantmentInventoryTranslator.EnchantmentSlotData[3];
+ @Setter
+ private Int2ObjectMap stonecutterRecipes;
/**
* The current attack speed of the player. Used for sending proper cooldown timings.
@@ -258,14 +279,15 @@ public class GeyserSession implements CommandSender {
private ScheduledFuture> bucketScheduledFuture;
/**
- * Used to send a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
+ * Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
*/
@Setter
- private long lastMovementTimestamp = System.currentTimeMillis();
+ private ScheduledFuture> movementSendIfIdle;
/**
* Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless.
*/
+ @Setter
private boolean daylightCycle = true;
private boolean reducedDebugInfo = false;
@@ -340,36 +362,33 @@ public class GeyserSession implements CommandSender {
private List selectedEmotes = new ArrayList<>();
private final Set emotes = new HashSet<>();
- /**
- * The thread that will run every 50 milliseconds - one Minecraft tick.
- */
- private ScheduledFuture> tickThread = null;
-
private MinecraftProtocol protocol;
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
this.connector = connector;
this.upstream = new UpstreamSession(bedrockServerSession);
- this.advancementsCache = new AdvancementsCache(this);
- this.bookEditCache = new BookEditCache(this);
this.chunkCache = new ChunkCache(this);
this.entityCache = new EntityCache(this);
this.effectCache = new EntityEffectCache();
- this.inventoryCache = new InventoryCache(this);
this.worldCache = new WorldCache(this);
this.windowCache = new WindowCache(this);
this.collisionManager = new CollisionManager(this);
this.playerEntity = new SessionPlayerEntity(this);
- this.inventory = new PlayerInventory();
+ this.worldCache = new WorldCache(this);
+ this.windowCache = new WindowCache(this);
+
+ this.playerInventory = new PlayerInventory();
+ this.openInventory = null;
+ this.inventoryFuture = CompletableFuture.completedFuture(null);
+ this.craftingRecipes = new Int2ObjectOpenHashMap<>();
+ this.unlockedRecipes = new ObjectOpenHashSet<>();
this.spawned = false;
this.loggedIn = false;
- this.inventoryCache.getInventories().put(0, inventory);
-
connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes()));
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
@@ -452,22 +471,146 @@ public class GeyserSession implements CommandSender {
new Thread(() -> {
try {
if (password != null && !password.isEmpty()) {
- AuthenticationService authenticationService;
- if (microsoftAccount) {
- authenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
- } else {
- authenticationService = new MojangAuthenticationService();
- }
- authenticationService.setUsername(username);
- authenticationService.setPassword(password);
- authenticationService.login();
-
- protocol = new MinecraftProtocol(authenticationService);
+ protocol = new MinecraftProtocol(username, password);
} else {
protocol = new MinecraftProtocol(username);
}
- connectDownstream();
+ boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
+ final PublicKey publicKey;
+
+ if (floodgate) {
+ PublicKey key = null;
+ try {
+ key = EncryptionUtil.getKeyFromFile(
+ connector.getConfig().getFloodgateKeyPath(),
+ PublicKey.class
+ );
+ } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
+ connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
+ }
+ publicKey = key;
+ } else publicKey = null;
+
+ if (publicKey != null) {
+ connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
+ }
+
+ downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
+ if (connector.getConfig().getRemote().isUseProxyProtocol()) {
+ downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
+ downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
+ }
+ // Let Geyser handle sending the keep alive
+ downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
+ downstream.getSession().addListener(new SessionAdapter() {
+ @Override
+ public void packetSending(PacketSendingEvent event) {
+ //todo move this somewhere else
+ if (event.getPacket() instanceof HandshakePacket && floodgate) {
+ String encrypted = "";
+ try {
+ encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
+ clientData.getGameVersion(),
+ authData.getName(),
+ authData.getXboxUUID(),
+ clientData.getDeviceOS().ordinal(),
+ clientData.getLanguageCode(),
+ clientData.getCurrentInputMode().ordinal(),
+ upstream.getSession().getAddress().getAddress().getHostAddress()
+ ));
+ } catch (Exception e) {
+ connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
+ }
+
+ HandshakePacket handshakePacket = event.getPacket();
+ event.setPacket(new HandshakePacket(
+ handshakePacket.getProtocolVersion(),
+ handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
+ handshakePacket.getPort(),
+ handshakePacket.getIntent()
+ ));
+ }
+ }
+
+ @Override
+ public void connected(ConnectedEvent event) {
+ loggingIn = false;
+ loggedIn = true;
+ if (protocol.getProfile() == null) {
+ // Java account is offline
+ disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
+ return;
+ }
+ connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
+ playerEntity.setUuid(protocol.getProfile().getId());
+ playerEntity.setUsername(protocol.getProfile().getName());
+
+ String locale = clientData.getLanguageCode();
+
+ // Let the user know there locale may take some time to download
+ // as it has to be extracted from a JAR
+ if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
+ // This should probably be left hardcoded as it will only show for en_us clients
+ sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
+ }
+
+ // Download and load the language for the player
+ LocaleUtils.downloadAndLoadLocale(locale);
+ }
+
+ @Override
+ public void disconnected(DisconnectedEvent event) {
+ loggingIn = false;
+ loggedIn = false;
+ connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
+ if (event.getCause() != null) {
+ event.getCause().printStackTrace();
+ }
+
+ upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
+ }
+
+ @Override
+ public void packetReceived(PacketReceivedEvent event) {
+ if (!closed) {
+ //handle consecutive respawn packets
+ if (event.getPacket().getClass().equals(ServerRespawnPacket.class)) {
+ manyDimPackets = lastDimPacket != null;
+ lastDimPacket = event.getPacket();
+ return;
+ } else if (lastDimPacket != null) {
+ PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(lastDimPacket.getClass(), lastDimPacket, GeyserSession.this);
+ lastDimPacket = null;
+ }
+
+ // Required, or else Floodgate players break with Bukkit chunk caching
+ if (event.getPacket() instanceof LoginSuccessPacket) {
+ GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
+ playerEntity.setUsername(profile.getName());
+ playerEntity.setUuid(profile.getId());
+
+ // Check if they are not using a linked account
+ if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
+ SkinManager.handleBedrockSkin(playerEntity, clientData);
+ }
+ }
+
+ PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
+ }
+ }
+
+ @Override
+ public void packetError(PacketErrorEvent event) {
+ connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage()));
+ if (connector.getConfig().isDebugMode())
+ event.getCause().printStackTrace();
+ event.setSuppress(true);
+ }
+ });
+
+ downstream.getSession().connect();
+ connector.addPlayer(this);
} catch (InvalidCredentialsException | IllegalArgumentException e) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
@@ -477,199 +620,6 @@ public class GeyserSession implements CommandSender {
}).start();
}
- /**
- * Present a form window to the user asking to log in with another web browser
- */
- public void authenticateWithMicrosoftCode() {
- if (loggedIn) {
- connector.getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().getName()));
- return;
- }
-
- loggingIn = true;
- // new thread so clients don't timeout
- new Thread(() -> {
- try {
- MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
-
- MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode();
- LoginEncryptionUtils.showMicrosoftCodeWindow(this, response);
-
- // This just looks cool
- SetTimePacket packet = new SetTimePacket();
- packet.setTime(16000);
- sendUpstreamPacket(packet);
-
- // Wait for the code to validate
- attemptCodeAuthentication(msaAuthenticationService);
- } catch (InvalidCredentialsException | IllegalArgumentException e) {
- connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", getAuthData().getName()));
- disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
- } catch (RequestException ex) {
- ex.printStackTrace();
- }
- }).start();
- }
-
- /**
- * Poll every second to see if the user has successfully signed in
- */
- private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) {
- if (loggedIn || closed) {
- return;
- }
- try {
- msaAuthenticationService.login();
- protocol = new MinecraftProtocol(msaAuthenticationService);
-
- connectDownstream();
- } catch (RequestException e) {
- if (!(e instanceof AuthPendingException)) {
- e.printStackTrace();
- } else {
- // Wait one second before trying again
- connector.getGeneralThreadPool().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS);
- }
- }
- }
-
- /**
- * After getting whatever credentials needed, we attempt to join the Java server.
- */
- private void connectDownstream() {
- boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
- final PublicKey publicKey;
-
- if (floodgate) {
- PublicKey key = null;
- try {
- key = EncryptionUtil.getKeyFromFile(
- connector.getConfig().getFloodgateKeyPath(),
- PublicKey.class
- );
- } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
- connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
- }
- publicKey = key;
- } else publicKey = null;
-
- if (publicKey != null) {
- connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
- }
-
- // Start ticking
- tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
-
- downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
- if (connector.getConfig().getRemote().isUseProxyProtocol()) {
- downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
- downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
- }
- // Let Geyser handle sending the keep alive
- downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
- downstream.getSession().addListener(new SessionAdapter() {
- @Override
- public void packetSending(PacketSendingEvent event) {
- //todo move this somewhere else
- if (event.getPacket() instanceof HandshakePacket && floodgate) {
- String encrypted = "";
- try {
- encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
- clientData.getGameVersion(),
- authData.getName(),
- authData.getXboxUUID(),
- clientData.getDeviceOS().ordinal(),
- clientData.getLanguageCode(),
- clientData.getCurrentInputMode().ordinal(),
- upstream.getSession().getAddress().getAddress().getHostAddress()
- ));
- } catch (Exception e) {
- connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
- }
-
- HandshakePacket handshakePacket = event.getPacket();
- event.setPacket(new HandshakePacket(
- handshakePacket.getProtocolVersion(),
- handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
- handshakePacket.getPort(),
- handshakePacket.getIntent()
- ));
- }
- }
-
- @Override
- public void connected(ConnectedEvent event) {
- loggingIn = false;
- loggedIn = true;
- if (protocol.getProfile() == null) {
- // Java account is offline
- disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
- return;
- }
- connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
- playerEntity.setUuid(protocol.getProfile().getId());
- playerEntity.setUsername(protocol.getProfile().getName());
-
- String locale = clientData.getLanguageCode();
-
- // Let the user know there locale may take some time to download
- // as it has to be extracted from a JAR
- if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
- // This should probably be left hardcoded as it will only show for en_us clients
- sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
- }
-
- // Download and load the language for the player
- LocaleUtils.downloadAndLoadLocale(locale);
- }
-
- @Override
- public void disconnected(DisconnectedEvent event) {
- loggingIn = false;
- loggedIn = false;
- connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
- if (event.getCause() != null) {
- event.getCause().printStackTrace();
- }
-
- upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
- }
-
- @Override
- public void packetReceived(PacketReceivedEvent event) {
- if (!closed) {
- // Required, or else Floodgate players break with Bukkit chunk caching
- if (event.getPacket() instanceof LoginSuccessPacket) {
- GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
- playerEntity.setUsername(profile.getName());
- playerEntity.setUuid(profile.getId());
-
- // Check if they are not using a linked account
- if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
- SkinManager.handleBedrockSkin(playerEntity, clientData);
- }
- }
-
- PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
- }
- }
-
- @Override
- public void packetError(PacketErrorEvent event) {
- connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage()));
- if (connector.getConfig().isDebugMode())
- event.getCause().printStackTrace();
- event.setSuppress(true);
- }
- });
-
- if (!daylightCycle) {
- setDaylightCycle(true);
- }
- downstream.getSession().connect();
- connector.addPlayer(this);
- }
-
public void disconnect(String reason) {
if (!closed) {
loggedIn = false;
@@ -682,17 +632,10 @@ public class GeyserSession implements CommandSender {
}
}
- if (tickThread != null) {
- tickThread.cancel(true);
- }
-
- this.advancementsCache = null;
- this.bookEditCache = null;
this.chunkCache = null;
this.entityCache = null;
this.effectCache = null;
this.worldCache = null;
- this.inventoryCache = null;
this.windowCache = null;
closed = true;
@@ -702,28 +645,6 @@ public class GeyserSession implements CommandSender {
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode()));
}
- /**
- * Called every 50 milliseconds - one Minecraft tick.
- */
- public void tick() {
- // Check to see if the player's position needs updating - a position update should be sent once every 3 seconds
- if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) {
- // Recalculate in case something else changed position
- Vector3d position = collisionManager.adjustBedrockPosition(playerEntity.getPosition(), playerEntity.isOnGround());
- // A null return value cancels the packet
- if (position != null) {
- ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(playerEntity.isOnGround(),
- position.getX(), position.getY(), position.getZ());
- sendDownstreamPacket(packet);
- }
- lastMovementTimestamp = System.currentTimeMillis();
- }
-
- for (Tickable entity : entityCache.getTickableEntities()) {
- entity.tick(this);
- }
- }
-
public void setAuthenticationData(AuthData authData) {
this.authData = authData;
}
@@ -827,15 +748,57 @@ public class GeyserSession implements CommandSender {
startGamePacket.setLevelName(serverName);
startGamePacket.setPremiumWorldTemplateId("00000000-0000-0000-0000-000000000000");
- // startGamePacket.setCurrentTick(0);
startGamePacket.setEnchantmentSeed(0);
startGamePacket.setMultiplayerCorrelationId("");
startGamePacket.setItemEntries(ItemRegistry.ITEMS);
startGamePacket.setVanillaVersion("*");
+ startGamePacket.setInventoriesServerAuthoritative(true);
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT);
upstream.sendPacket(startGamePacket);
}
+ /**
+ * Adds a new inventory task.
+ * Inventory tasks are executed one at a time, in order.
+ *
+ * @param task the task to run
+ */
+ public void addInventoryTask(Runnable task) {
+ synchronized (inventoryLock) {
+ System.out.println("new task " + task.toString());
+ inventoryFuture = inventoryFuture.thenRun(task).exceptionally(throwable -> {
+ GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause());
+ return null;
+ });
+ }
+ }
+
+ /**
+ * Adds a new inventory task with a delay.
+ * The delay is achieved by scheduling with the Geyser general thread pool.
+ * Inventory tasks are executed one at a time, in order.
+ *
+ * @param task the delayed task to run
+ * @param delayMillis delay in milliseconds
+ */
+ public void addInventoryTask(Runnable task, long delayMillis) {
+ synchronized (inventoryLock) {
+ System.out.println("new delayed task " + task.toString());
+ Executor delayedExecutor = command -> GeyserConnector.getInstance().getGeneralThreadPool().schedule(command, delayMillis, TimeUnit.MILLISECONDS);
+ inventoryFuture = inventoryFuture.thenRunAsync(task, delayedExecutor).exceptionally(throwable -> {
+ GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause());
+ return null;
+ });
+ }
+ }
+
+ /**
+ * @return the next Bedrock item network ID to use for a new item
+ */
+ public int getNextItemNetId() {
+ return itemNetId.getAndIncrement();
+ }
+
public void addTeleport(TeleportCache teleportCache) {
teleportMap.put(teleportCache.getTeleportConfirmId(), teleportCache);
@@ -958,18 +921,6 @@ public class GeyserSession implements CommandSender {
reducedDebugInfo = value;
}
- /**
- * Changes the daylight cycle gamerule on the client
- * This is used in the login screen along-side normal usage
- *
- * @param doCycle If the cycle should continue
- */
- public void setDaylightCycle(boolean doCycle) {
- sendGameRule("dodaylightcycle", doCycle);
- // Save the value so we don't have to constantly send a daylight cycle gamerule update
- this.daylightCycle = doCycle;
- }
-
/**
* Send a gamerule value to the client
*
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java
deleted file mode 100644
index 369967ac..00000000
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.network.session.cache;
-
-import com.github.steveice10.mc.protocol.data.game.advancement.Advancement;
-import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket;
-import lombok.Getter;
-import lombok.Setter;
-import org.geysermc.common.window.SimpleFormWindow;
-import org.geysermc.common.window.button.FormButton;
-import org.geysermc.common.window.response.SimpleFormResponse;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.chat.MessageTranslator;
-import org.geysermc.connector.utils.GeyserAdvancement;
-import org.geysermc.connector.utils.LanguageUtils;
-import org.geysermc.connector.utils.LocaleUtils;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class AdvancementsCache {
-
- // Different form IDs
- public static final int ADVANCEMENTS_MENU_FORM_ID = 1341;
- public static final int ADVANCEMENTS_LIST_FORM_ID = 1342;
- public static final int ADVANCEMENT_INFO_FORM_ID = 1343;
-
- /**
- * Stores the player's advancement progress
- */
- @Getter
- private final Map> storedAdvancementProgress = new HashMap<>();
-
- /**
- * Stores advancements for the player.
- */
- @Getter
- private final Map storedAdvancements = new HashMap<>();
-
- /**
- * Stores player's chosen advancement's ID and title for use in form creators.
- */
- @Setter
- private String currentAdvancementCategoryId = null;
-
- private final GeyserSession session;
-
- public AdvancementsCache(GeyserSession session) {
- this.session = session;
- }
-
- /**
- * Build a form with all advancement categories
- *
- * @return The built advancement category menu
- */
- public SimpleFormWindow buildMenuForm() {
- // Cache the language for cleaner access
- String language = session.getClientData().getLanguageCode();
-
- // Created menu window for advancement categories
- SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), "");
- for (Map.Entry advancement : storedAdvancements.entrySet()) {
- if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement
- window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language)));
- }
- }
-
- if (window.getButtons().isEmpty()) {
- window.setContent(LocaleUtils.getLocaleString("advancements.empty", language));
- }
-
- return window;
- }
-
- /**
- * Builds the list of advancements
- *
- * @return The built list form
- */
- public SimpleFormWindow buildListForm() {
- // Cache the language for easier access
- String language = session.getLocale();
- String id = currentAdvancementCategoryId;
- GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId);
-
- // Create the window
- SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language),
- MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language));
-
- if (id != null) {
- for (Map.Entry advancementEntry : storedAdvancements.entrySet()) {
- GeyserAdvancement advancement = advancementEntry.getValue();
- if (advancement != null) {
- if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) {
- boolean earned = isEarned(advancement);
-
- if (earned || !advancement.getDisplayData().isShowToast()) {
- window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
- } else {
- window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
- }
- }
- }
- }
- }
-
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language)));
-
- return window;
- }
-
- /**
- * Builds the advancement display info based on the chosen category
- *
- * @param advancement The advancement used to create the info display
- * @return The information for the chosen advancement
- */
- public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) {
- // Cache language for easier access
- String language = session.getLocale();
-
- String earned = isEarned(advancement) ? "yes" : "no";
-
- String description = getColorFromAdvancementFrameType(advancement) + MessageTranslator.convertMessage(advancement.getDisplayData().getDescription(), language);
- String earnedString = LanguageUtils.getPlayerLocaleString("geyser.advancements.earned", language, LocaleUtils.getLocaleString("gui." + earned, language));
-
- /*
- Layout will look like:
-
- (Form title) Stone Age
-
- (Description) Mine stone with your new pickaxe
-
- Earned: Yes
- Parent Advancement: Minecraft // If relevant
- */
-
- String content = description + "\n\n§f" +
- earnedString + "\n";
- if (!currentAdvancementCategoryId.equals(advancement.getParentId())) {
- // Only display the parent if it is not the category
- content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language));
- }
- SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content);
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language)));
-
- return window;
- }
-
- /**
- * Determine if this advancement has been earned.
- *
- * @param advancement the advancement to determine
- * @return true if the advancement has been earned.
- */
- public boolean isEarned(GeyserAdvancement advancement) {
- boolean earned = false;
- if (advancement.getRequirements().size() == 0) {
- // Minecraft handles this case, so we better as well
- return false;
- }
- Map progress = storedAdvancementProgress.get(advancement.getId());
- if (progress != null) {
- // Each advancement's requirement must be fulfilled
- // For example, [[zombie, blaze, skeleton]] means that one of those three categories must be achieved
- // But [[zombie], [blaze], [skeleton]] means that all three requirements must be completed
- for (List requirements : advancement.getRequirements()) {
- boolean requirementsDone = false;
- for (String requirement : requirements) {
- Long obtained = progress.get(requirement);
- // -1 means that this particular component required for completing the advancement
- // has yet to be fulfilled
- if (obtained != null && !obtained.equals(-1L)) {
- requirementsDone = true;
- break;
- }
- }
- if (!requirementsDone) {
- return false;
- }
- }
- earned = true;
- }
- return earned;
- }
-
- /**
- * Handle the menu form response
- *
- * @param response The response string to parse
- * @return True if the form was parsed correctly, false if not
- */
- public boolean handleMenuForm(String response) {
- SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID);
- menuForm.setResponse(response);
-
- SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse();
-
- String id = "";
- if (formResponse != null && formResponse.getClickedButton() != null) {
- int advancementIndex = 0;
- for (Map.Entry advancement : storedAdvancements.entrySet()) {
- if (advancement.getValue().getParentId() == null) { // Root advancement
- if (advancementIndex == formResponse.getClickedButtonId()) {
- id = advancement.getKey();
- break;
- } else {
- advancementIndex++;
- }
- }
- }
- }
- if (!id.equals("")) {
- if (id.equals(currentAdvancementCategoryId)) {
- // The server thinks we are already on this tab
- session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID);
- } else {
- // Send a packet indicating that we intend to open this particular advancement window
- ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id);
- session.sendDownstreamPacket(packet);
- // Wait for a response there
- }
- }
-
- return true;
- }
-
- /**
- * Handle the list form response (Advancement category choice)
- *
- * @param response The response string to parse
- * @return True if the form was parsed correctly, false if not
- */
- public boolean handleListForm(String response) {
- SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID);
- listForm.setResponse(response);
-
- SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse();
-
- if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) {
- GeyserAdvancement advancement = null;
- int advancementIndex = 0;
- // Loop around to find the advancement that the client pressed
- for (GeyserAdvancement advancementEntry : storedAdvancements.values()) {
- if (advancementEntry.getParentId() != null &&
- currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) {
- if (advancementIndex == formResponse.getClickedButtonId()) {
- advancement = advancementEntry;
- break;
- } else {
- advancementIndex++;
- }
- }
- }
- if (advancement != null) {
- session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID);
- } else {
- session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID);
- // Indicate that we have closed the current advancement tab
- session.sendDownstreamPacket(new ClientAdvancementTabPacket());
- }
- } else {
- // Indicate that we have closed the current advancement tab
- session.sendDownstreamPacket(new ClientAdvancementTabPacket());
- }
-
- return true;
- }
-
- /**
- * Handle the info form response
- *
- * @param response The response string to parse
- * @return True if the form was parsed correctly, false if not
- */
- public boolean handleInfoForm(String response) {
- SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID);
- listForm.setResponse(response);
-
- SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse();
-
- if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) {
- session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID);
- }
-
- return true;
- }
-
- public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) {
- String base = "\u00a7";
- if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) {
- return base + "5";
- }
- return base + "a"; // Used for types TASK and GOAL
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
deleted file mode 100644
index f81a9fdf..00000000
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.network.session.cache;
-
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket;
-import lombok.Setter;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.item.ItemRegistry;
-
-/**
- * Manages updating the current writable book.
- *
- * Java sends book updates less frequently than Bedrock, and this can cause issues with servers that rate limit
- * book packets. Because of this, we need to ensure packets are only send every second or so at maximum.
- */
-public class BookEditCache {
- private final GeyserSession session;
- @Setter
- private ClientEditBookPacket packet;
- /**
- * Stores the last time a book update packet was sent to the server.
- */
- private long lastBookUpdate;
-
- public BookEditCache(GeyserSession session) {
- this.session = session;
- }
-
- /**
- * Check to see if there is a book edit update to send, and if so, send it.
- */
- public void checkForSend() {
- if (packet == null) {
- // No new packet has to be sent
- return;
- }
- // Prevent kicks due to rate limiting - specifically on Spigot servers
- if ((System.currentTimeMillis() - lastBookUpdate) < 1000) {
- return;
- }
- // Don't send the update if the player isn't not holding a book, shouldn't happen if we catch all interactions
- ItemStack itemStack = session.getInventory().getItemInHand();
- if (itemStack == null || itemStack.getId() != ItemRegistry.WRITABLE_BOOK.getJavaId()) {
- packet = null;
- return;
- }
- session.getDownstream().getSession().send(packet);
- packet = null;
- lastBookUpdate = System.currentTimeMillis();
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
index 40000551..62b0dbd6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java
@@ -28,7 +28,6 @@ package org.geysermc.connector.network.session.cache;
import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
-import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
@@ -41,21 +40,17 @@ import java.util.concurrent.atomic.AtomicLong;
* for that player (e.g. seeing vanished players from /vanish)
*/
public class EntityCache {
- private final GeyserSession session;
+ private GeyserSession session;
@Getter
private Long2ObjectMap entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
- /**
- * A list of all entities that must be ticked.
- */
- private final List tickableEntities = Collections.synchronizedList(new ArrayList<>());
private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
private Map playerEntities = Collections.synchronizedMap(new HashMap<>());
private Map bossBars = Collections.synchronizedMap(new HashMap<>());
- private final Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
+ private Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
@Getter
- private final AtomicLong nextEntityId = new AtomicLong(2L);
+ private AtomicLong nextEntityId = new AtomicLong(2L);
public EntityCache(GeyserSession session) {
this.session = session;
@@ -64,11 +59,6 @@ public class EntityCache {
public void spawnEntity(Entity entity) {
if (cacheEntity(entity)) {
entity.spawnEntity(session);
-
- if (entity instanceof Tickable) {
- // Start ticking it
- tickableEntities.add((Tickable) entity);
- }
}
}
@@ -86,10 +76,6 @@ public class EntityCache {
if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) {
long geyserId = entityIdTranslations.remove(entity.getEntityId());
entities.remove(geyserId);
-
- if (entity instanceof Tickable) {
- tickableEntities.remove(entity);
- }
return true;
}
return false;
@@ -166,8 +152,4 @@ public class EntityCache {
public void addCachedPlayerEntityLink(long playerId, long linkedEntityId) {
cachedPlayerEntityLinks.put(playerId, linkedEntityId);
}
-
- public List getTickableEntities() {
- return tickableEntities;
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java
index a114b8bb..d9625ff6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java
@@ -37,10 +37,10 @@ import org.geysermc.connector.network.session.GeyserSession;
public class WindowCache {
- private final GeyserSession session;
+ private GeyserSession session;
@Getter
- private final Int2ObjectMap windows = new Int2ObjectOpenHashMap<>();
+ private Int2ObjectMap windows = new Int2ObjectOpenHashMap<>();
public WindowCache(GeyserSession session) {
this.session = session;
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java
deleted file mode 100644
index dd5d08a2..00000000
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.network.translators.bedrock;
-
-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.packet.ingame.client.window.ClientEditBookPacket;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.github.steveice10.opennbt.tag.builtin.ListTag;
-import com.github.steveice10.opennbt.tag.builtin.StringTag;
-import com.github.steveice10.opennbt.tag.builtin.Tag;
-import com.nukkitx.protocol.bedrock.packet.BookEditPacket;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.PacketTranslator;
-import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-@Translator(packet = BookEditPacket.class)
-public class BedrockBookEditTranslator extends PacketTranslator {
-
- @Override
- public void translate(BookEditPacket packet, GeyserSession session) {
- ItemStack itemStack = session.getInventory().getItemInHand();
- if (itemStack != null) {
- CompoundTag tag = itemStack.getNbt() != null ? itemStack.getNbt() : new CompoundTag("");
- ItemStack bookItem = new ItemStack(itemStack.getId(), itemStack.getAmount(), tag);
- List pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>();
-
- int page = packet.getPageNumber();
- // Creative edits the NBT for us
- if (session.getGameMode() != GameMode.CREATIVE) {
- switch (packet.getAction()) {
- case ADD_PAGE: {
- // Add empty pages in between
- for (int i = pages.size(); i < page; i++) {
- pages.add(i, new StringTag("", ""));
- }
- pages.add(page, new StringTag("", packet.getText()));
- break;
- }
- // Called whenever a page is modified
- case REPLACE_PAGE: {
- if (page < pages.size()) {
- pages.set(page, new StringTag("", packet.getText()));
- } else {
- // Add empty pages in between
- for (int i = pages.size(); i < page; i++) {
- pages.add(i, new StringTag("", ""));
- }
- pages.add(page, new StringTag("", packet.getText()));
- }
- break;
- }
- case DELETE_PAGE: {
- if (page < pages.size()) {
- pages.remove(page);
- }
- break;
- }
- case SWAP_PAGES: {
- int page2 = packet.getSecondaryPageNumber();
- if (page < pages.size() && page2 < pages.size()) {
- Collections.swap(pages, page, page2);
- }
- break;
- }
- case SIGN_BOOK: {
- tag.put(new StringTag("author", packet.getAuthor()));
- tag.put(new StringTag("title", packet.getTitle()));
- break;
- }
- default:
- return;
- }
- }
- // Remove empty pages at the end
- while (pages.size() > 0) {
- StringTag currentPage = (StringTag) pages.get(pages.size() - 1);
- if (currentPage.getValue() == null || currentPage.getValue().isEmpty()) {
- pages.remove(pages.size() - 1);
- } else {
- break;
- }
- }
- tag.put(new ListTag("pages", pages));
- session.getInventory().setItem(36 + session.getInventory().getHeldItemSlot(), bookItem);
- InventoryTranslator.INVENTORY_TRANSLATORS.get(null).updateInventory(session, session.getInventory());
-
- session.getBookEditCache().setPacket(new ClientEditBookPacket(bookItem, packet.getAction() == BookEditPacket.Action.SIGN_BOOK, session.getInventory().getHeldItemSlot()));
- // There won't be any more book updates after this, so we can try sending the edit packet immediately
- if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
- session.getBookEditCache().checkForSend();
- }
- }
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java
index 21eb73be..7ac3dba5 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java
@@ -38,24 +38,24 @@ public class BedrockContainerCloseTranslator extends PacketTranslator {
+ session.setLastWindowCloseTime(0);
+ byte windowId = packet.getId();
+
+ if (windowId == -1 && session.getOpenInventory() != null) {
+ windowId = (byte) session.getOpenInventory().getId();
}
- }
- if (windowId == 0 || (openInventory != null && openInventory.getId() == windowId)) {
- ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
- session.getDownstream().getSession().send(closeWindowPacket);
- InventoryUtils.closeInventory(session, windowId);
- }
+ Inventory openInventory = session.getOpenInventory();
+ if (openInventory != null && windowId == openInventory.getId()) {
+ System.out.println(packet);
+ ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
+ session.sendDownstreamPacket(closeWindowPacket);
+ InventoryUtils.closeInventory(session, windowId);
+ }
- //Client wants close confirmation
- session.sendUpstreamPacket(packet);
+ //Client wants close confirmation
+ session.sendUpstreamPacket(packet);
+ });
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java
index db01df15..aeedfae1 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java
@@ -25,7 +25,10 @@
package org.geysermc.connector.network.translators.bedrock;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket;
import com.nukkitx.protocol.bedrock.packet.FilterTextPacket;
+import org.geysermc.connector.inventory.AnvilContainer;
+import org.geysermc.connector.inventory.CartographyContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@@ -39,7 +42,18 @@ public class BedrockFilterTextTranslator extends PacketTranslator {
- private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f;
- private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49;
- private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36;
- private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f;
-
@Override
public void translate(InventoryTransactionPacket packet, GeyserSession session) {
- // Send book updates before opening inventories
- session.getBookEditCache().checkForSend();
-
switch (packet.getTransactionType()) {
case NORMAL:
- Inventory inventory = session.getInventoryCache().getOpenInventory();
- if (inventory == null) inventory = session.getInventory();
- InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()).translateActions(session, inventory, packet.getActions());
+ System.out.println(packet);
+ if (packet.getActions().size() == 2) {
+ InventoryActionData worldAction = packet.getActions().get(0);
+ InventoryActionData containerAction = packet.getActions().get(1);
+ if (worldAction.getSource().getType() == InventorySource.Type.WORLD_INTERACTION
+ && worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
+ session.addInventoryTask(() -> {
+ if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot())
+ return;
+ if (session.getPlayerInventory().getItemInHand().isEmpty())
+ return;
+
+ boolean dropAll = worldAction.getToItem().getCount() > 1;
+ ClientPlayerActionPacket dropAllPacket = new ClientPlayerActionPacket(
+ dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM,
+ new Position(0, 0, 0),
+ BlockFace.DOWN
+ );
+ session.sendDownstreamPacket(dropAllPacket);
+
+ if (dropAll) {
+ session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY);
+ } else {
+ session.getPlayerInventory().getItemInHand().sub(1);
+ }
+ });
+ }
+ }
break;
case INVENTORY_MISMATCH:
- Inventory inv = session.getInventoryCache().getOpenInventory();
- if (inv == null) inv = session.getInventory();
- InventoryTranslator.INVENTORY_TRANSLATORS.get(inv.getWindowType()).updateInventory(session, inv);
- InventoryUtils.updateCursor(session);
break;
case ITEM_USE:
switch (packet.getActionType()) {
@@ -120,46 +129,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator
- (session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
- restoreCorrectBlock(session, blockPos, packet);
- return;
- }
-
- // Vanilla check
- if (!(session.getPlayerEntity().getPosition().sub(0, EntityType.PLAYER.getOffset(), 0)
- .distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) {
- // The client thinks that its blocks have been successfully placed. Restore the server's blocks instead.
- restoreCorrectBlock(session, blockPos, packet);
- return;
- }
- /*
- Block place checks end - client is good to go
- */
-
ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
BlockFace.values()[packet.getBlockFace()],
@@ -207,6 +176,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator MAXIMUM_BLOCK_DESTROYING_DISTANCE) {
- restoreCorrectBlock(session, packet.getBlockPosition(), packet);
- return;
+ LevelEventPacket blockBreakPacket = new LevelEventPacket();
+ blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
+ blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
+ blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState));
+ session.sendUpstreamPacket(blockBreakPacket);
}
- LevelEventPacket blockBreakPacket = new LevelEventPacket();
- blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
- blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
- blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState));
- session.sendUpstreamPacket(blockBreakPacket);
- session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID);
-
long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition());
if (frameEntityId != -1 && session.getEntityCache().getEntityByJavaId(frameEntityId) != null) {
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) frameEntityId, InteractAction.ATTACK, session.isSneaking());
@@ -330,34 +286,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator {
+
+ @Override
+ public void translate(ItemStackRequestPacket packet, GeyserSession session) {
+ session.getConnector().getLogger().info(packet.toString());
+ Inventory inventory = session.getOpenInventory();
+ if (inventory == null)
+ return;
+
+ InventoryTranslator translator = session.getInventoryTranslator();
+ session.addInventoryTask(() -> translator.translateRequests(session, inventory, packet.getRequests()));
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java
new file mode 100644
index 00000000..832d1347
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.bedrock;
+
+import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
+import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
+import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
+import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
+import com.nukkitx.math.vector.Vector3f;
+import com.nukkitx.protocol.bedrock.packet.LecternUpdatePacket;
+import org.geysermc.connector.inventory.LecternContainer;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.PacketTranslator;
+import org.geysermc.connector.network.translators.Translator;
+import org.geysermc.connector.utils.InventoryUtils;
+
+@Translator(packet = LecternUpdatePacket.class)
+public class BedrockLecternUpdateTranslator extends PacketTranslator {
+
+ @Override
+ public void translate(LecternUpdatePacket packet, GeyserSession session) {
+ session.getConnector().getLogger().error(packet.toString());
+ if (packet.isDroppingBook()) {
+ // Bedrock drops the book outside of the GUI. Java drops it in the GUI
+ // So, we enter the GUI and then drop it! :)
+ session.setDroppingLecternBook(true);
+
+ Vector3f diff = session.getPlayerEntity().getPosition().sub(packet.getBlockPosition().toFloat());
+ System.out.println(diff);
+ // Emulate an interact packet
+ ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(
+ new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
+ BlockFace.values()[0],
+ Hand.MAIN_HAND,
+ packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ(), //TODO
+ false);
+ session.sendDownstreamPacket(blockPacket);
+ } else {
+ // Bedrock wants to either move a page or exit
+ LecternContainer lecternContainer = (LecternContainer) session.getOpenInventory();
+ if (lecternContainer.getCurrentBedrockPage() == packet.getPage()) {
+ // The same page means Bedrock is closing the window
+ ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId());
+ session.sendDownstreamPacket(closeWindowPacket);
+ InventoryUtils.closeInventory(session, lecternContainer.getId());
+ } else {
+ // Each "page" Bedrock gives to us actually represents two pages (think opening a book and seeing two pages)
+ // Each "page" on Java is just one page (think a spiral notebook folded back to only show one page)
+ int newJavaPage = (packet.getPage() * 2);
+ int currentJavaPage = (lecternContainer.getCurrentBedrockPage() * 2);
+ // Send as many click button packets as we need to
+ // Java has the option to specify exact page numbers by adding 100 to the number, but buttonId variable
+ // is a byte and therefore this stops us at 128
+ if (newJavaPage > currentJavaPage) {
+ for (int i = currentJavaPage; i < newJavaPage; i++) {
+ ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 2);
+ System.out.println(clickButtonPacket);
+ session.sendDownstreamPacket(clickButtonPacket);
+ }
+ } else {
+ for (int i = currentJavaPage; i > newJavaPage; i--) {
+ ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 1);
+ System.out.println(clickButtonPacket);
+ session.sendDownstreamPacket(clickButtonPacket);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java
index 47fb9702..94954484 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java
@@ -40,15 +40,12 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator 8 ||
- packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().getHeldItemSlot() == packet.getHotbarSlot()) {
+ packet.getContainerId() != ContainerId.INVENTORY || session.getPlayerInventory().getHeldItemSlot() == packet.getHotbarSlot()) {
// For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention
return;
}
- // Send book update before switching hotbar slot
- session.getBookEditCache().checkForSend();
-
- session.getInventory().setHeldItemSlot(packet.getHotbarSlot());
+ session.getPlayerInventory().setHeldItemSlot(packet.getHotbarSlot());
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
session.sendDownstreamPacket(changeHeldItemPacket);
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java
index a89bfdfb..6267597f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java
@@ -26,12 +26,13 @@
package org.geysermc.connector.network.translators.bedrock.entity;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
-import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientSelectTradePacket;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.Entity;
+import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.MerchantContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@@ -47,20 +48,25 @@ public class BedrockEntityEventTranslator extends PacketTranslator {
+ ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData());
+ session.sendDownstreamPacket(selectTradePacket);
+ });
- Entity villager = session.getPlayerEntity();
- Inventory openInventory = session.getInventoryCache().getOpenInventory();
- if (openInventory != null && openInventory.getWindowType() == WindowType.MERCHANT) {
- VillagerTrade[] trades = session.getVillagerTrades();
- if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) {
- VillagerTrade trade = session.getVillagerTrades()[packet.getData()];
- openInventory.setItem(2, trade.getOutput());
- villager.getMetadata().put(EntityData.TRADE_XP, trade.getXp() + villager.getMetadata().getInt(EntityData.TRADE_XP));
- villager.updateBedrockMetadata(session);
+ session.addInventoryTask(() -> {
+ Entity villager = session.getPlayerEntity();
+ Inventory openInventory = session.getOpenInventory();
+ if (openInventory instanceof MerchantContainer) {
+ MerchantContainer merchantInventory = (MerchantContainer) openInventory;
+ VillagerTrade[] trades = merchantInventory.getVillagerTrades();
+ if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) {
+ VillagerTrade trade = merchantInventory.getVillagerTrades()[packet.getData()];
+ openInventory.setItem(2, GeyserItemStack.from(trade.getOutput()), session);
+ villager.getMetadata().put(EntityData.TRADE_XP, trade.getXp() + villager.getMetadata().getInt(EntityData.TRADE_XP));
+ villager.updateBedrockMetadata(session);
+ }
}
- }
+ }, 100);
return;
}
session.getConnector().getLogger().debug("Did not translate incoming EntityEventPacket: " + packet.toString());
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java
index c4dbbec4..106a0d8d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java
@@ -25,16 +25,13 @@
package org.geysermc.connector.network.translators.bedrock.entity.player;
-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.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
@@ -43,12 +40,10 @@ import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket;
import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket;
import org.geysermc.connector.entity.Entity;
-import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.network.translators.item.ItemEntry;
-import org.geysermc.connector.network.translators.item.ItemRegistry;
+import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;
@@ -63,11 +58,6 @@ public class BedrockActionTranslator extends PacketTranslator
switch (packet.getAction()) {
case INTERACT:
- if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemRegistry.SHIELD.getJavaId()) {
+ if (session.getPlayerInventory().getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) {
break;
}
ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
@@ -122,7 +125,7 @@ public class BedrockInteractTranslator extends PacketTranslator
if (interactEntity == null)
return;
EntityDataMap entityMetadata = interactEntity.getMetadata();
- ItemEntry itemEntry = session.getInventory().getItemInHand() == null ? ItemEntry.AIR : ItemRegistry.getItem(session.getInventory().getItemInHand());
+ ItemEntry itemEntry = session.getPlayerInventory().getItemInHand() == GeyserItemStack.EMPTY ? ItemEntry.AIR : ItemRegistry.getItem(session.getPlayerInventory().getItemInHand().getItemStack());
String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", "");
// TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen
@@ -136,8 +139,8 @@ public class BedrockInteractTranslator extends PacketTranslator
interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == EntityType.STRIDER)) {
// Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed)
interactiveTag = InteractiveTag.SADDLE;
- } else if (javaIdentifierStripped.equals("name_tag") && session.getInventory().getItemInHand().getNbt() != null &&
- session.getInventory().getItemInHand().getNbt().contains("display")) {
+ } else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null &&
+ session.getPlayerInventory().getItemInHand().getNbt().contains("display")) {
// Holding a named name tag
interactiveTag = InteractiveTag.NAME;
} else if (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) &&
@@ -210,6 +213,11 @@ public class BedrockInteractTranslator extends PacketTranslator
case SKELETON_HORSE:
case TRADER_LLAMA:
case ZOMBIE_HORSE:
+ boolean tamed = entityMetadata.getFlags().getFlag(EntityFlag.TAMED);
+ if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || entityMetadata.getFlags().getFlag(EntityFlag.CHESTED))) {
+ interactiveTag = InteractiveTag.OPEN_CONTAINER;
+ break;
+ }
// have another switch statement as, while these share mount attributes they don't share food
switch (interactEntity.getEntityType()) {
case LLAMA:
@@ -228,9 +236,9 @@ public class BedrockInteractTranslator extends PacketTranslator
}
if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) {
// Can't ride a baby
- if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) {
+ if (tamed) {
interactiveTag = InteractiveTag.RIDE_HORSE;
- } else if (!entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && itemEntry.equals(ItemEntry.AIR)) {
+ } else if (itemEntry.equals(ItemEntry.AIR)) {
// Can't hide an untamed entity without having your hand empty
interactiveTag = InteractiveTag.MOUNT;
}
@@ -349,20 +357,30 @@ public class BedrockInteractTranslator extends PacketTranslator
} else {
if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) {
// No interactive tag should be sent
- session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG);
+ session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, "");
session.getPlayerEntity().updateBedrockMetadata(session);
}
}
break;
case OPEN_INVENTORY:
- if (!session.getInventory().isOpen()) {
- ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
- containerOpenPacket.setId((byte) 0);
- containerOpenPacket.setType(ContainerType.INVENTORY);
- containerOpenPacket.setUniqueEntityId(-1);
- containerOpenPacket.setBlockPosition(entity.getPosition().toInt());
- session.sendUpstreamPacket(containerOpenPacket);
- session.getInventory().setOpen(true);
+ if (session.getOpenInventory() == null) {
+ Entity ridingEntity = session.getRidingVehicleEntity();
+ if (ridingEntity instanceof AbstractHorseEntity) {
+ if (ridingEntity.getMetadata().getFlags().getFlag(EntityFlag.TAMED)) {
+ // We should request to open the horse inventory instead
+ ClientPlayerStatePacket openHorseWindowPacket = new ClientPlayerStatePacket((int)session.getPlayerEntity().getEntityId(), PlayerState.OPEN_HORSE_INVENTORY);
+ session.sendDownstreamPacket(openHorseWindowPacket);
+ }
+ } else {
+ session.setOpenInventory(session.getPlayerInventory());
+
+ ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
+ containerOpenPacket.setId((byte) 0);
+ containerOpenPacket.setType(ContainerType.INVENTORY);
+ containerOpenPacket.setUniqueEntityId(-1);
+ containerOpenPacket.setBlockPosition(entity.getPosition().toInt());
+ session.sendUpstreamPacket(containerOpenPacket);
+ }
}
break;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java
index 2f6ba0d4..83fdf090 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java
@@ -33,12 +33,16 @@ import com.nukkitx.math.vector.Vector3d;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
+import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
+import org.geysermc.connector.network.translators.collision.CollisionManager;
+
+import java.util.concurrent.TimeUnit;
@Translator(packet = MovePlayerPacket.class)
public class BedrockMovePlayerTranslator extends PacketTranslator {
@@ -46,7 +50,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator 0) return;
if (!session.getUpstream().isInitialized()) {
MoveEntityAbsolutePacket moveEntityBack = new MoveEntityAbsolutePacket();
@@ -59,10 +63,9 @@ public class BedrockMovePlayerTranslator extends PacketTranslator sendPositionIfIdle(session),
+ 3, TimeUnit.SECONDS));
}
- private boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
+ public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
if (mode != MovePlayerPacket.Mode.NORMAL)
return true;
@@ -164,5 +171,81 @@ public class BedrockMovePlayerTranslator extends PacketTranslator sendPositionIfIdle(session),
+ 3, TimeUnit.SECONDS));
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java
index 203e4406..22e5c95f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java
@@ -30,12 +30,8 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
-import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
-import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import lombok.Getter;
import lombok.Setter;
-import org.geysermc.connector.entity.player.PlayerEntity;
-import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
@@ -109,7 +105,6 @@ public class CollisionManager {
// According to the Minecraft Wiki, when sneaking:
// - In Bedrock Edition, the height becomes 1.65 blocks, allowing movement through spaces as small as 1.75 (2 - 1⁄4) blocks high.
// - In Java Edition, the height becomes 1.5 blocks.
- // TODO: Have this depend on the player's literal bounding box variable
if (session.isSneaking()) {
playerBoundingBox.setSizeY(1.5);
} else {
@@ -118,65 +113,6 @@ public class CollisionManager {
}
}
- /**
- * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
- * the two versions.
- *
- * @param bedrockPosition the current Bedrock position of the client
- * @param onGround whether the Bedrock player is on the ground
- * @return the position to send to the Java server, or null to cancel sending the packet
- */
- public Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround) {
- // We need to parse the float as a string since casting a float to a double causes us to
- // lose precision and thus, causes players to get stuck when walking near walls
- double javaY = bedrockPosition.getY() - EntityType.PLAYER.getOffset();
-
- Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
- Double.parseDouble(Float.toString(bedrockPosition.getZ())));
-
- if (session.getConnector().getConfig().isCacheChunks()) {
- // With chunk caching, we can do some proper collision checks
- updatePlayerBoundingBox(position);
-
- // Correct player position
- if (!correctPlayerPosition()) {
- // Cancel the movement if it needs to be cancelled
- recalculatePosition();
- return null;
- }
-
- position = Vector3d.from(playerBoundingBox.getMiddleX(),
- playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2),
- playerBoundingBox.getMiddleZ());
- } else {
- // When chunk caching is off, we have to rely on this
- // It rounds the Y position up to the nearest 0.5
- // This snaps players to snap to the top of stairs and slabs like on Java Edition
- // However, it causes issues such as the player floating on carpets
- if (onGround) javaY = Math.ceil(javaY * 2) / 2;
- position = position.up(javaY - position.getY());
- }
-
- return position;
- }
-
- // TODO: This makes the player look upwards for some reason, rotation values must be wrong
- public void recalculatePosition() {
- PlayerEntity entity = session.getPlayerEntity();
- // Gravity might need to be reset...
- SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
- entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
- entityDataPacket.getMetadata().putAll(entity.getMetadata());
- session.sendUpstreamPacket(entityDataPacket);
-
- MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
- movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
- movePlayerPacket.setPosition(entity.getPosition());
- movePlayerPacket.setRotation(entity.getBedrockRotation());
- movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
- session.sendUpstreamPacket(movePlayerPacket);
- }
-
public List getPlayerCollidableBlocks() {
List blocks = new ArrayList<>();
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java
deleted file mode 100644
index fb487bea..00000000
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.network.translators.inventory;
-
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.google.gson.JsonSyntaxException;
-import com.nukkitx.nbt.NbtMap;
-import com.nukkitx.protocol.bedrock.data.inventory.*;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
-import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
-import org.geysermc.connector.inventory.Inventory;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class AnvilInventoryTranslator extends BlockInventoryTranslator {
- public AnvilInventoryTranslator() {
- super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, new CursorInventoryUpdater());
- }
-
- @Override
- public int bedrockSlotToJava(InventoryActionData action) {
- if (action.getSource().getContainerId() == ContainerId.UI) {
- switch (action.getSlot()) {
- case 1:
- return 0;
- case 2:
- return 1;
- case 50:
- return 2;
- }
- }
- if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) {
- return 2;
- }
- return super.bedrockSlotToJava(action);
- }
-
- @Override
- public int javaSlotToBedrock(int slot) {
- switch (slot) {
- case 0:
- return 1;
- case 1:
- return 2;
- case 2:
- return 50;
- }
- return super.javaSlotToBedrock(slot);
- }
-
- @Override
- public SlotType getSlotType(int javaSlot) {
- if (javaSlot == 2)
- return SlotType.OUTPUT;
- return SlotType.NORMAL;
- }
-
- @Override
- public void translateActions(GeyserSession session, Inventory inventory, List actions) {
- InventoryActionData anvilResult = null;
- InventoryActionData anvilInput = null;
- for (InventoryActionData action : actions) {
- if (action.getSource().getContainerId() == ContainerId.ANVIL_MATERIAL) {
- //useless packet
- return;
- } else if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) {
- anvilResult = action;
- } else if (bedrockSlotToJava(action) == 0) {
- anvilInput = action;
- }
- }
- ItemData itemName = null;
- if (anvilResult != null) {
- itemName = anvilResult.getFromItem();
- } else if (anvilInput != null) {
- itemName = anvilInput.getToItem();
- }
- if (itemName != null) {
- String rename;
- NbtMap tag = itemName.getTag();
- if (tag != null && tag.containsKey("display")) {
- String name = tag.getCompound("display").getString("Name");
- try {
- Component component = GsonComponentSerializer.gson().deserialize(name);
- rename = LegacyComponentSerializer.legacySection().serialize(component);
- } catch (JsonSyntaxException e) {
- rename = name;
- }
- } else {
- rename = "";
- }
- ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename);
- session.sendDownstreamPacket(renameItemPacket);
- }
- if (anvilResult != null) {
- //Strip unnecessary actions
- List strippedActions = actions.stream()
- .filter(action -> action.getSource().getContainerId() == ContainerId.ANVIL_RESULT
- || (action.getSource().getType() == InventorySource.Type.CONTAINER
- && !(action.getSource().getContainerId() == ContainerId.UI && action.getSlot() != 0)))
- .collect(Collectors.toList());
- super.translateActions(session, inventory, strippedActions);
- return;
- }
-
- super.translateActions(session, inventory, actions);
- }
-
- @Override
- public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
- if (slot == 0) {
- ItemStack item = inventory.getItem(slot);
- if (item != null) {
- String rename;
- CompoundTag tag = item.getNbt();
- if (tag != null) {
- CompoundTag displayTag = tag.get("display");
- if (displayTag != null && displayTag.contains("Name")) {
- String itemName = displayTag.get("Name").getValue().toString();
- try {
- Component component = GsonComponentSerializer.gson().deserialize(itemName);
- rename = LegacyComponentSerializer.legacySection().serialize(component);
- } catch (JsonSyntaxException e) {
- rename = itemName;
- }
- } else {
- rename = "";
- }
- } else {
- rename = "";
- }
- ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename);
- session.sendDownstreamPacket(renameItemPacket);
- }
- }
- super.updateSlot(session, inventory, slot);
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/entity/Tickable.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java
similarity index 81%
rename from connector/src/main/java/org/geysermc/connector/entity/Tickable.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java
index a7d571cc..47d1f070 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/Tickable.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java
@@ -23,13 +23,13 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.entity;
+package org.geysermc.connector.network.translators.inventory;
-import org.geysermc.connector.network.session.GeyserSession;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import lombok.Value;
-/**
- * Implemented onto anything that should have code ran every Minecraft tick - 50 milliseconds.
- */
-public interface Tickable {
- void tick(GeyserSession session);
+@Value
+public class BedrockContainerSlot {
+ ContainerSlotType container;
+ int slot;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java
deleted file mode 100644
index b7b98bf7..00000000
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.network.translators.inventory;
-
-import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
-import com.nukkitx.nbt.NbtMap;
-import com.nukkitx.nbt.NbtMapBuilder;
-import com.nukkitx.nbt.NbtType;
-import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
-import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
-import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
-import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-import lombok.ToString;
-import org.geysermc.connector.common.ChatColor;
-import org.geysermc.connector.inventory.Inventory;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
-import org.geysermc.connector.network.translators.item.ItemRegistry;
-import org.geysermc.connector.network.translators.item.ItemTranslator;
-import org.geysermc.connector.utils.InventoryUtils;
-import org.geysermc.connector.utils.LocaleUtils;
-
-import java.util.*;
-
-/**
- * A temporary reconstruction of the enchantment table UI until our inventory rewrite is complete.
- * The enchantment table on Bedrock without server authoritative inventories doesn't tell us which button is pressed
- * when selecting an enchantment.
- */
-public class EnchantmentInventoryTranslator extends BlockInventoryTranslator {
-
- private static final int DYE_ID = ItemRegistry.getItemEntry("minecraft:lapis_lazuli").getBedrockId();
- private static final int ENCHANTED_BOOK_ID = ItemRegistry.getItemEntry("minecraft:enchanted_book").getBedrockId();
-
- public EnchantmentInventoryTranslator(InventoryUpdater updater) {
- super(2, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, updater);
- }
-
- @Override
- public void translateActions(GeyserSession session, Inventory inventory, List actions) {
- for (InventoryActionData action : actions) {
- if (action.getSource().getContainerId() == inventory.getId()) {
- // This is the hopper UI
- switch (action.getSlot()) {
- case 1:
- // Don't allow the slot to be put through if the item isn't lapis
- if ((action.getToItem().getId() != DYE_ID) && action.getToItem() != ItemData.AIR) {
- updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- return;
- }
- break;
- case 2:
- case 3:
- case 4:
- // The books here act as buttons
- ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), action.getSlot() - 2);
- session.sendDownstreamPacket(packet);
- updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- return;
- default:
- break;
- }
- }
- }
-
- super.translateActions(session, inventory, actions);
- }
-
- @Override
- public void updateInventory(GeyserSession session, Inventory inventory) {
- super.updateInventory(session, inventory);
- List items = new ArrayList<>(5);
- items.add(ItemTranslator.translateToBedrock(session, inventory.getItem(0)));
- items.add(ItemTranslator.translateToBedrock(session, inventory.getItem(1)));
- for (int i = 0; i < 3; i++) {
- items.add(session.getEnchantmentSlotData()[i].getItem() != null ? session.getEnchantmentSlotData()[i].getItem() : createEnchantmentBook());
- }
-
- InventoryContentPacket contentPacket = new InventoryContentPacket();
- contentPacket.setContainerId(inventory.getId());
- contentPacket.setContents(items);
- session.sendUpstreamPacket(contentPacket);
- }
-
- @Override
- public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
- int bookSlotToUpdate;
- switch (key) {
- case 0:
- case 1:
- case 2:
- // Experience required
- bookSlotToUpdate = key;
- session.getEnchantmentSlotData()[bookSlotToUpdate].setExperienceRequired(value);
- break;
- case 4:
- case 5:
- case 6:
- // Enchantment name
- bookSlotToUpdate = key - 4;
- if (value != -1) {
- session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(EnchantmentTableEnchantments.values()[value - 1]);
- } else {
- // -1 means no enchantment specified
- session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(null);
- }
- break;
- case 7:
- case 8:
- case 9:
- // Enchantment level
- bookSlotToUpdate = key - 7;
- session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentLevel(value);
- break;
- default:
- return;
- }
- updateEnchantmentBook(session, inventory, bookSlotToUpdate);
- }
-
- @Override
- public void openInventory(GeyserSession session, Inventory inventory) {
- super.openInventory(session, inventory);
- for (int i = 0; i < session.getEnchantmentSlotData().length; i++) {
- session.getEnchantmentSlotData()[i] = new EnchantmentSlotData();
- }
- }
-
- @Override
- public void closeInventory(GeyserSession session, Inventory inventory) {
- super.closeInventory(session, inventory);
- Arrays.fill(session.getEnchantmentSlotData(), null);
- }
-
- private ItemData createEnchantmentBook() {
- NbtMapBuilder root = NbtMap.builder();
- NbtMapBuilder display = NbtMap.builder();
-
- display.putString("Name", ChatColor.RESET + "No Enchantment");
-
- root.put("display", display.build());
- return ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build());
- }
-
- private void updateEnchantmentBook(GeyserSession session, Inventory inventory, int slot) {
- NbtMapBuilder root = NbtMap.builder();
- NbtMapBuilder display = NbtMap.builder();
- EnchantmentSlotData data = session.getEnchantmentSlotData()[slot];
- if (data.getEnchantmentType() != null) {
- display.putString("Name", ChatColor.ITALIC + data.getEnchantmentType().toString(session) +
- (data.getEnchantmentLevel() != -1 ? " " + toRomanNumeral(session, data.getEnchantmentLevel()) : "") + "?");
- } else {
- display.putString("Name", ChatColor.RESET + "No Enchantment");
- }
-
- display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.DARK_GRAY + data.getExperienceRequired() + "xp"));
- root.put("display", display.build());
- ItemData book = ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build());
-
- InventorySlotPacket slotPacket = new InventorySlotPacket();
- slotPacket.setContainerId(inventory.getId());
- slotPacket.setSlot(slot + 2);
- slotPacket.setItem(book);
- session.sendUpstreamPacket(slotPacket);
- data.setItem(book);
- }
-
- private String toRomanNumeral(GeyserSession session, int level) {
- return LocaleUtils.getLocaleString("enchantment.level." + level,
- session.getLocale());
- }
-
- /**
- * Stores the data of each slot in an enchantment table
- */
- @NoArgsConstructor
- @Getter
- @Setter
- @ToString
- public static class EnchantmentSlotData {
- private EnchantmentTableEnchantments enchantmentType = null;
- private int enchantmentLevel = 0;
- private int experienceRequired = 0;
- private ItemData item;
- }
-
- /**
- * Classifies enchantments by Java order
- */
- public enum EnchantmentTableEnchantments {
- PROTECTION,
- FIRE_PROTECTION,
- FEATHER_FALLING,
- BLAST_PROTECTION,
- PROJECTILE_PROTECTION,
- RESPIRATION,
- AQUA_AFFINITY,
- THORNS,
- DEPTH_STRIDER,
- FROST_WALKER,
- BINDING_CURSE,
- SHARPNESS,
- SMITE,
- BANE_OF_ARTHROPODS,
- KNOCKBACK,
- FIRE_ASPECT,
- LOOTING,
- SWEEPING,
- EFFICIENCY,
- SILK_TOUCH,
- UNBREAKING,
- FORTUNE,
- POWER,
- PUNCH,
- FLAME,
- INFINITY,
- LUCK_OF_THE_SEA,
- LURE,
- LOYALTY,
- IMPALING,
- RIPTIDE,
- CHANNELING,
- MENDING,
- VANISHING_CURSE, // After this is not documented
- MULTISHOT,
- PIERCING,
- QUICK_CHARGE,
- SOUL_SPEED;
-
- public String toString(GeyserSession session) {
- return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(),
- session.getLocale());
- }
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java
index f8ef0f7c..f0f734fc 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java
@@ -25,52 +25,91 @@
package org.geysermc.connector.network.translators.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.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.window.WindowType;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientPrepareCraftingGridPacket;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*;
+import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
+import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.AllArgsConstructor;
+import org.geysermc.connector.inventory.CartographyContainer;
+import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
-import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
+import org.geysermc.connector.network.translators.inventory.click.Click;
+import org.geysermc.connector.network.translators.inventory.click.ClickPlan;
+import org.geysermc.connector.network.translators.inventory.translators.*;
+import org.geysermc.connector.network.translators.inventory.translators.chest.DoubleChestInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.chest.SingleChestInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.furnace.BlastFurnaceInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.furnace.FurnaceInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.furnace.SmokerInventoryTranslator;
+import org.geysermc.connector.network.translators.item.ItemRegistry;
+import org.geysermc.connector.network.translators.item.ItemTranslator;
+import org.geysermc.connector.utils.InventoryUtils;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
@AllArgsConstructor
public abstract class InventoryTranslator {
+ public static final InventoryTranslator PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator();
public static final Map INVENTORY_TRANSLATORS = new HashMap() {
{
- put(null, new PlayerInventoryTranslator()); //player inventory
+ /* Player Inventory */
+ put(null, PLAYER_INVENTORY_TRANSLATOR);
+
+ /* Chest UIs */
put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9));
put(WindowType.GENERIC_9X2, new SingleChestInventoryTranslator(18));
put(WindowType.GENERIC_9X3, new SingleChestInventoryTranslator(27));
put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36));
put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45));
put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54));
- put(WindowType.BREWING_STAND, new BrewingInventoryTranslator());
+
+ /* Furnaces */
+ put(WindowType.FURNACE, new FurnaceInventoryTranslator());
+ put(WindowType.BLAST_FURNACE, new BlastFurnaceInventoryTranslator());
+ put(WindowType.SMOKER, new SmokerInventoryTranslator());
+
+ /* Specific Inventories */
put(WindowType.ANVIL, new AnvilInventoryTranslator());
+ put(WindowType.BEACON, new BeaconInventoryTranslator());
+ put(WindowType.BREWING_STAND, new BrewingInventoryTranslator());
+ put(WindowType.CARTOGRAPHY, new CartographyInventoryTranslator());
put(WindowType.CRAFTING, new CraftingInventoryTranslator());
- //put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); //FIXME
+ put(WindowType.ENCHANTMENT, new EnchantingInventoryTranslator());
+ put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());
+ put(WindowType.LOOM, new LoomInventoryTranslator());
put(WindowType.MERCHANT, new MerchantInventoryTranslator());
- //put(WindowType.SMITHING, new SmithingInventoryTranslator()); //TODO for server authoritative inventories
+ put(WindowType.SHULKER_BOX, new ShulkerInventoryTranslator());
+ put(WindowType.SMITHING, new SmithingInventoryTranslator());
+ put(WindowType.STONECUTTER, new StonecutterInventoryTranslator());
- InventoryTranslator furnace = new FurnaceInventoryTranslator();
- put(WindowType.FURNACE, furnace);
- put(WindowType.BLAST_FURNACE, furnace);
- put(WindowType.SMOKER, furnace);
+ /* Generics */
+ put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER));
+ put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER));
- InventoryUpdater containerUpdater = new ContainerInventoryUpdater();
- put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator(containerUpdater)); //TODO
- put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater));
- put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater));
- put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater));
- //put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO
+ /* Lectern */
+ put(WindowType.LECTERN, new LecternInventoryTranslator());
}
};
+ public static final int PLAYER_INVENTORY_SIZE = 36;
+ public static final int PLAYER_INVENTORY_OFFSET = 9;
+ private static final int MAX_ITEM_STACK_SIZE = 64;
public final int size;
public abstract void prepareInventory(GeyserSession session, Inventory inventory);
@@ -79,8 +118,790 @@ public abstract class InventoryTranslator {
public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value);
public abstract void updateInventory(GeyserSession session, Inventory inventory);
public abstract void updateSlot(GeyserSession session, Inventory inventory, int slot);
- public abstract int bedrockSlotToJava(InventoryActionData action);
- public abstract int javaSlotToBedrock(int slot);
+ public abstract int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData);
+ public abstract int javaSlotToBedrock(int javaSlot); //TODO
+ public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot); //TODO
public abstract SlotType getSlotType(int javaSlot);
- public abstract void translateActions(GeyserSession session, Inventory inventory, List actions);
+ public abstract Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory);
+
+ /**
+ * 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.
+ *
+ * The source/destination slot will be -1 if the cursor is the slot
+ *
+ * @return true if this transfer should be rejected
+ */
+ public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) {
+ return false;
+ }
+
+ /**
+ * 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) {
+ return false;
+ }
+
+ /**
+ * If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called
+ */
+ public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
+ return null;
+ }
+
+ public void translateRequests(GeyserSession session, Inventory inventory, List requests) {
+ ItemStackResponsePacket responsePacket = new ItemStackResponsePacket();
+ for (ItemStackRequestPacket.Request request : requests) {
+ if (request.getActions().length > 0) {
+ StackRequestActionData firstAction = request.getActions()[0];
+ if (shouldHandleRequestFirst(firstAction, inventory)) {
+ // Some special request that shouldn't be processed normally
+ responsePacket.getEntries().add(translateSpecialRequest(session, inventory, request));
+ } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE || firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) {
+ responsePacket.getEntries().add(translateCraftingRequest(session, inventory, request));
+ } else if (firstAction.getType() == StackRequestActionType.CRAFT_CREATIVE) {
+ // This is also used for pulling items out of creative
+ responsePacket.getEntries().add(translateCreativeRequest(session, inventory, request));
+ } else {
+ responsePacket.getEntries().add(translateRequest(session, inventory, request));
+ }
+ } else {
+ responsePacket.getEntries().add(rejectRequest(request));
+ }
+ }
+ session.sendUpstreamPacket(responsePacket);
+ System.out.println(responsePacket);
+ }
+
+ public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
+ System.out.println(request);
+ ClickPlan plan = new ClickPlan(session, this, inventory);
+ IntSet affectedSlots = new IntOpenHashSet();
+ for (StackRequestActionData action : request.getActions()) {
+ GeyserItemStack cursor = session.getPlayerInventory().getCursor();
+ switch (action.getType()) {
+ case TAKE:
+ 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);
+ }
+ session.getConnector().getLogger().error("DEBUG: About to reject request.");
+ session.getConnector().getLogger().error("Source: " + transferAction.getSource().toString() + " Result: " + checkNetId(session, inventory, transferAction.getSource()));
+ session.getConnector().getLogger().error("Destination: " + transferAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, transferAction.getDestination()));
+ return rejectRequest(request);
+ }
+
+ int sourceSlot = bedrockSlotToJava(transferAction.getSource());
+ int destSlot = bedrockSlotToJava(transferAction.getDestination());
+
+ if (shouldRejectItemPlace(session, inventory, isCursor(transferAction.getSource()) ? -1 : sourceSlot,
+ isCursor(transferAction.getDestination()) ? -1 : destSlot)) {
+ // This item would not be here in Java
+ return rejectRequest(request, false);
+ }
+
+ if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //???
+ return rejectRequest(request);
+ } else if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { // TODO: does the Java server use this stuff all the time in creative?
+ // Creative acts a little differently because it just edits slots
+ boolean sourceIsCursor = isCursor(transferAction.getSource());
+ boolean destIsCursor = isCursor(transferAction.getDestination());
+
+ GeyserItemStack sourceItem = sourceIsCursor ? session.getPlayerInventory().getCursor() :
+ inventory.getItem(sourceSlot);
+ GeyserItemStack newItem = sourceItem.copy();
+ if (sourceIsCursor) {
+ GeyserItemStack destItem = inventory.getItem(destSlot);
+ if (destItem.getJavaId() == sourceItem.getJavaId()) {
+ // Combining items
+ int itemsLeftOver = destItem.getAmount() + transferAction.getCount();
+ if (itemsLeftOver > MAX_ITEM_STACK_SIZE) {
+ // Items will remain in cursor because destination slot gets set to 64
+ destItem.setAmount(MAX_ITEM_STACK_SIZE);
+ sourceItem.setAmount(itemsLeftOver - MAX_ITEM_STACK_SIZE);
+ } else {
+ // Cursor will be emptied
+ destItem.setAmount(itemsLeftOver);
+ session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
+ }
+ ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
+ destSlot,
+ destItem.getItemStack()
+ );
+ session.sendDownstreamPacket(creativeActionPacket);
+ affectedSlots.add(destSlot);
+ break;
+ }
+ } else {
+ // Delete the source since we're moving it
+ inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session);
+ ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
+ sourceSlot,
+ new ItemStack(0)
+ );
+ session.sendDownstreamPacket(creativeActionPacket);
+ affectedSlots.add(sourceSlot);
+ }
+ // Update the item count with however much the client took
+ newItem.setAmount(transferAction.getCount());
+ // Remove that amount from the existing item
+ sourceItem.setAmount(sourceItem.getAmount() - transferAction.getCount());
+ if (sourceItem.isEmpty()) {
+ // Item is basically deleted
+ if (sourceIsCursor) {
+ session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
+ } else {
+ inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session);
+ }
+ }
+ if (destIsCursor) {
+ session.getPlayerInventory().setCursor(newItem, session);
+ } else {
+ inventory.setItem(destSlot, newItem, session);
+ }
+ GeyserItemStack itemToUpdate = destIsCursor ? sourceItem : newItem;
+ // The Java server doesn't care about what's in the mouse in creative mode, so we just need to track
+ // which inventory slot the client modified
+ ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
+ destIsCursor ? sourceSlot : destSlot,
+ itemToUpdate.isEmpty() ? new ItemStack(0) : itemToUpdate.getItemStack()
+ );
+ session.sendDownstreamPacket(creativeActionPacket);
+ System.out.println(creativeActionPacket);
+
+ if (!sourceIsCursor) { // Cursor is always added for us as an affected slot
+ affectedSlots.add(sourceSlot);
+ }
+ if (!destIsCursor) {
+ affectedSlots.add(destSlot);
+ }
+
+ } else if (isCursor(transferAction.getSource())) { //releasing cursor
+ int sourceAmount = cursor.getAmount();
+ if (transferAction.getCount() == sourceAmount) { //release all
+ plan.add(Click.LEFT, destSlot);
+ } else { //release some
+ for (int i = 0; i < transferAction.getCount(); i++) {
+ plan.add(Click.RIGHT, destSlot);
+ }
+ }
+ } else if (isCursor(transferAction.getDestination())) { //picking up into cursor
+ GeyserItemStack sourceItem = plan.getItem(sourceSlot);
+ int sourceAmount = sourceItem.getAmount();
+ if (cursor.isEmpty()) { //picking up into empty cursor
+ if (transferAction.getCount() == sourceAmount) { //pickup all
+ plan.add(Click.LEFT, sourceSlot);
+ } else if (transferAction.getCount() == sourceAmount - (sourceAmount / 2)) { //larger half; simple right click
+ plan.add(Click.RIGHT, sourceSlot);
+ } else { //pickup some; not a simple right click
+ plan.add(Click.LEFT, sourceSlot); //first pickup all
+ for (int i = 0; i < sourceAmount - transferAction.getCount(); i++) {
+ plan.add(Click.RIGHT, sourceSlot); //release extra items back into source slot
+ }
+ }
+ } else { //pickup into non-empty cursor
+ if (!InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //doesn't make sense, reject
+ return rejectRequest(request);
+ }
+ if (transferAction.getCount() != sourceAmount) {
+ int tempSlot = findTempSlot(inventory, cursor, false, sourceSlot);
+ if (tempSlot == -1) {
+ return rejectRequest(request);
+ }
+ plan.add(Click.LEFT, tempSlot); //place cursor into temp slot
+ plan.add(Click.LEFT, sourceSlot); //pickup source items into cursor
+ for (int i = 0; i < transferAction.getCount(); i++) {
+ plan.add(Click.RIGHT, tempSlot); //partially transfer source items into temp slot (original cursor)
+ }
+ plan.add(Click.LEFT, sourceSlot); //return remaining source items
+ plan.add(Click.LEFT, tempSlot); //retrieve original cursor items from temp slot
+ } else {
+ if (getSlotType(sourceSlot).equals(SlotType.NORMAL)) {
+ plan.add(Click.LEFT, sourceSlot); //release cursor onto source slot
+ }
+ plan.add(Click.LEFT, sourceSlot); //pickup combined cursor and source
+ }
+ }
+ } else { //transfer from one slot to another
+ int tempSlot = -1;
+ if (!cursor.isEmpty()) {
+ tempSlot = findTempSlot(inventory, cursor, false, sourceSlot, destSlot);
+ if (tempSlot == -1) {
+ return rejectRequest(request);
+ }
+ plan.add(Click.LEFT, tempSlot); //place cursor into temp slot
+ }
+ int sourceAmount = plan.getItem(sourceSlot).getAmount();
+ if (transferAction.getCount() == sourceAmount) { //transfer all
+ plan.add(Click.LEFT, sourceSlot); //pickup source
+ plan.add(Click.LEFT, destSlot); //let go of all items and done
+ } else { //transfer some
+ //try to transfer items with least clicks possible
+ int halfSource = sourceAmount - (sourceAmount / 2); //larger half
+ int holding;
+ if (plan.getCursor().isEmpty() && transferAction.getCount() <= halfSource) { //faster to take only half. CURSOR MUST BE EMPTY
+ plan.add(Click.RIGHT, sourceSlot);
+ holding = halfSource;
+ } else { //need all
+ plan.add(Click.LEFT, sourceSlot);
+ holding = sourceAmount;
+ }
+ if (transferAction.getCount() > holding / 2) { //faster to release extra items onto source or dest slot?
+ for (int i = 0; i < holding - transferAction.getCount(); i++) {
+ plan.add(Click.RIGHT, sourceSlot); //prepare cursor
+ }
+ plan.add(Click.LEFT, destSlot); //release cursor onto dest slot
+ } else {
+ for (int i = 0; i < transferAction.getCount(); i++) {
+ plan.add(Click.RIGHT, destSlot); //right click until transfer goal is met
+ }
+ plan.add(Click.LEFT, sourceSlot); //return extra items to source slot
+ }
+ }
+ if (tempSlot != -1) {
+ plan.add(Click.LEFT, tempSlot); //retrieve original cursor
+ }
+ }
+ break;
+ }
+ case SWAP: { //TODO
+ SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action;
+ if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination())))
+ return rejectRequest(request);
+
+ if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) {
+ int destSlot = bedrockSlotToJava(swapAction.getDestination());
+ GeyserItemStack oldSourceItem;
+ GeyserItemStack oldDestinationItem = inventory.getItem(destSlot);
+ if (isCursor(swapAction.getSource())) {
+ oldSourceItem = session.getPlayerInventory().getCursor();
+ session.getPlayerInventory().setCursor(oldDestinationItem, session);
+ } else {
+ int sourceSlot = bedrockSlotToJava(swapAction.getSource());
+ oldSourceItem = inventory.getItem(sourceSlot);
+ ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
+ sourceSlot,
+ oldDestinationItem.isEmpty() ? new ItemStack(0) : oldDestinationItem.getItemStack() // isEmpty check... just in case
+ );
+ System.out.println(creativeActionPacket);
+ session.sendDownstreamPacket(creativeActionPacket);
+ inventory.setItem(sourceSlot, oldDestinationItem, session);
+ }
+ if (isCursor(swapAction.getDestination())) {
+ session.getPlayerInventory().setCursor(oldSourceItem, session);
+ } else {
+ ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
+ destSlot,
+ oldSourceItem.isEmpty() ? new ItemStack(0) : oldSourceItem.getItemStack()
+ );
+ System.out.println(creativeActionPacket);
+ session.sendDownstreamPacket(creativeActionPacket);
+ inventory.setItem(destSlot, oldSourceItem, session);
+ }
+
+ } else if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //???
+ return rejectRequest(request);
+ } else if (isCursor(swapAction.getSource())) { //swap cursor
+ int destSlot = bedrockSlotToJava(swapAction.getDestination());
+ if (InventoryUtils.canStack(cursor, plan.getItem(destSlot))) { //TODO: cannot simply swap if cursor stacks with slot (temp slot)
+ return rejectRequest(request);
+ }
+ plan.add(Click.LEFT, destSlot);
+ } else if (isCursor(swapAction.getDestination())) { //swap cursor
+ int sourceSlot = bedrockSlotToJava(swapAction.getSource());
+ if (InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //TODO
+ return rejectRequest(request);
+ }
+ plan.add(Click.LEFT, sourceSlot);
+ } else {
+ int sourceSlot = bedrockSlotToJava(swapAction.getSource());
+ int destSlot = bedrockSlotToJava(swapAction.getDestination());
+ if (!cursor.isEmpty()) { //TODO: (temp slot)
+ return rejectRequest(request);
+ }
+ if (sourceSlot == destSlot) { //doesn't make sense
+ return rejectRequest(request);
+ }
+ if (InventoryUtils.canStack(plan.getItem(sourceSlot), plan.getItem(destSlot))) { //TODO: (temp slot)
+ return rejectRequest(request);
+ }
+ plan.add(Click.LEFT, sourceSlot); //pickup source into cursor
+ plan.add(Click.LEFT, destSlot); //swap cursor with dest slot
+ plan.add(Click.LEFT, sourceSlot); //release cursor onto source
+ }
+ break;
+ }
+ case DROP: {
+ DropStackRequestActionData dropAction = (DropStackRequestActionData) action;
+ if (!checkNetId(session, inventory, dropAction.getSource()))
+ return rejectRequest(request);
+
+ if (isCursor(dropAction.getSource())) { //clicking outside of window
+ if (session.getGameMode() == GameMode.CREATIVE && inventory instanceof PlayerInventory) {
+ GeyserItemStack cursorItem = session.getPlayerInventory().getCursor();
+ GeyserItemStack droppingItem = cursorItem.copy();
+ // Subtract the cursor item by however much is being dropped
+ cursorItem.setAmount(cursorItem.getAmount() - dropAction.getCount());
+ if (cursorItem.isEmpty()) {
+ // Cursor item no longer exists
+ session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
+ }
+ droppingItem.setAmount(dropAction.getCount());
+ ClientCreativeInventoryActionPacket packet = new ClientCreativeInventoryActionPacket(
+ Click.OUTSIDE_SLOT,
+ droppingItem.getItemStack()
+ );
+ System.out.println(packet.toString());
+ session.sendDownstreamPacket(packet);
+ } else {
+ int sourceAmount = plan.getCursor().getAmount();
+ if (dropAction.getCount() == sourceAmount) { //drop all
+ plan.add(Click.LEFT_OUTSIDE, Click.OUTSIDE_SLOT);
+ } else { //drop some
+ for (int i = 0; i < dropAction.getCount(); i++) {
+ plan.add(Click.RIGHT_OUTSIDE, Click.OUTSIDE_SLOT); //drop one until goal is met
+ }
+ }
+ }
+ } else { //dropping from inventory
+ int sourceSlot = bedrockSlotToJava(dropAction.getSource());
+ int sourceAmount = plan.getItem(sourceSlot).getAmount();
+ if (dropAction.getCount() == sourceAmount && sourceAmount > 1) { //dropping all? (prefer DROP_ONE if only one)
+ plan.add(Click.DROP_ALL, sourceSlot);
+ } else { //drop some
+ for (int i = 0; i < dropAction.getCount(); i++) {
+ plan.add(Click.DROP_ONE, sourceSlot); //drop one until goal is met
+ }
+ }
+ }
+ break;
+ }
+ case CRAFT_CREATIVE: {
+ CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action;
+ System.out.println(creativeAction.getCreativeItemNetworkId());
+ break;
+ }
+ case DESTROY: {
+ // Only called when a creative client wants to destroy an item... I think - Camotoy
+ //TODO there is a Count here we don't use
+ DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action;
+ if (!session.getGameMode().equals(GameMode.CREATIVE)) {
+ // If this happens, let's throw an error and figure out why.
+ return rejectRequest(request);
+ }
+ if (!isCursor(destroyAction.getSource())) {
+ // Item exists; let's remove it from the inventory
+ int javaSlot = bedrockSlotToJava(destroyAction.getSource());
+ ClientCreativeInventoryActionPacket destroyItemPacket = new ClientCreativeInventoryActionPacket(
+ javaSlot,
+ new ItemStack(0)
+ );
+ session.sendDownstreamPacket(destroyItemPacket);
+ System.out.println(destroyItemPacket);
+ inventory.setItem(javaSlot, GeyserItemStack.EMPTY, session);
+ affectedSlots.add(javaSlot);
+ } else {
+ // Just sync up the item on our end, since the server doesn't care what's in our cursor
+ session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
+ }
+ break;
+ }
+ // The following three tend to be called for UI inventories
+ case CONSUME: {
+ if (inventory instanceof CartographyContainer) {
+ // TODO add this for more inventories? Only seems to glitch out the cartography table, though.
+ ConsumeStackRequestActionData consumeData = (ConsumeStackRequestActionData) action;
+ int sourceSlot = bedrockSlotToJava(consumeData.getSource());
+ if (sourceSlot == 0 && inventory.getItem(1).isEmpty()) {
+ // Java doesn't allow an item to be renamed; this is why CARTOGRAPHY_ADDITIONAL could remain empty for Bedrock
+ // We check this during slot 0 since setting the inventory slots here messes up shouldRejectItemPlace
+ return rejectRequest(request, false);
+ }
+
+ GeyserItemStack item = inventory.getItem(sourceSlot);
+ item.setAmount(item.getAmount() - consumeData.getCount());
+ if (item.isEmpty()) {
+ inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session);
+ }
+ affectedSlots.add(sourceSlot);
+ }
+ break;
+ }
+ case CRAFT_NON_IMPLEMENTED_DEPRECATED: {
+ break;
+ }
+ case CRAFT_RESULTS_DEPRECATED: {
+ break;
+ }
+ case CRAFT_RECIPE_OPTIONAL: {
+ // Anvils and cartography tables will handle this
+ break;
+ }
+ default:
+ return rejectRequest(request);
+ }
+ }
+ plan.execute(false);
+ affectedSlots.addAll(plan.getAffectedSlots());
+ return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
+ }
+
+ public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
+ System.out.println(request);
+
+ int recipeId = 0;
+ int resultSize = 0;
+ int timesCrafted = 0;
+ boolean autoCraft = false;
+ CraftState craftState = CraftState.START;
+
+ int leftover = 0;
+ ClickPlan plan = new ClickPlan(session, this, inventory);
+ for (StackRequestActionData action : request.getActions()) {
+ switch (action.getType()) {
+ case CRAFT_RECIPE: {
+ CraftRecipeStackRequestActionData craftAction = (CraftRecipeStackRequestActionData) action;
+ if (craftState != CraftState.START) {
+ return rejectRequest(request);
+ }
+ craftState = CraftState.RECIPE_ID;
+ recipeId = craftAction.getRecipeNetworkId();
+ autoCraft = false;
+ break;
+ }
+ case CRAFT_RECIPE_AUTO: {
+ AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action;
+ if (craftState != CraftState.START) {
+ return rejectRequest(request);
+ }
+ craftState = CraftState.RECIPE_ID;
+
+ recipeId = autoCraftAction.getRecipeNetworkId();
+ if (!plan.getCursor().isEmpty()) {
+ return rejectRequest(request);
+ }
+ //reject if crafting grid is not clear
+ int gridSize = inventory.getId() == 0 ? 4 : 9;
+ for (int i = 1; i <= gridSize; i++) {
+ if (!inventory.getItem(i).isEmpty()) {
+ return rejectRequest(request);
+ }
+ }
+ autoCraft = true;
+ break;
+ }
+ case CRAFT_RESULTS_DEPRECATED: {
+ CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action;
+ if (craftState != CraftState.RECIPE_ID) {
+ return rejectRequest(request);
+ }
+ craftState = CraftState.DEPRECATED;
+
+ if (deprecatedCraftAction.getResultItems().length != 1) {
+ return rejectRequest(request);
+ }
+ resultSize = deprecatedCraftAction.getResultItems()[0].getCount();
+ timesCrafted = deprecatedCraftAction.getTimesCrafted();
+ if (resultSize <= 0 || timesCrafted <= 0) {
+ return rejectRequest(request);
+ }
+ break;
+ }
+ case CONSUME: {
+ ConsumeStackRequestActionData consumeAction = (ConsumeStackRequestActionData) action;
+ if (craftState != CraftState.DEPRECATED && craftState != CraftState.INGREDIENTS) {
+ return rejectRequest(request);
+ }
+ craftState = CraftState.INGREDIENTS;
+ break;
+ }
+ case TAKE:
+ case PLACE: {
+ TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action;
+ if (craftState != CraftState.INGREDIENTS && craftState != CraftState.TRANSFER) {
+ return rejectRequest(request);
+ }
+ craftState = CraftState.TRANSFER;
+
+ if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) {
+ return rejectRequest(request);
+ }
+ if (transferAction.getCount() <= 0) {
+ return rejectRequest(request);
+ }
+
+ int sourceSlot = bedrockSlotToJava(transferAction.getSource());
+ int destSlot = bedrockSlotToJava(transferAction.getDestination());
+
+ if (autoCraft) {
+ Recipe recipe = session.getCraftingRecipes().get(recipeId);
+ //cannot use java recipe book if recipe is locked
+ if (recipe == null || !session.getUnlockedRecipes().contains(recipe.getIdentifier())) {
+ return rejectRequest(request);
+ }
+
+ boolean cursorDest = isCursor(transferAction.getDestination());
+ boolean makeAll = timesCrafted > 1;
+ if (cursorDest) {
+ makeAll = false;
+ }
+
+ ClientPrepareCraftingGridPacket prepareCraftingPacket = new ClientPrepareCraftingGridPacket(inventory.getId(), recipe.getIdentifier(), makeAll);
+ session.sendDownstreamPacket(prepareCraftingPacket);
+
+ ItemStack output = null;
+ switch (recipe.getType()) {
+ case CRAFTING_SHAPED:
+ output = ((ShapedRecipeData)recipe.getData()).getResult();
+ break;
+ case CRAFTING_SHAPELESS:
+ output = ((ShapelessRecipeData)recipe.getData()).getResult();
+ break;
+ }
+ inventory.setItem(0, GeyserItemStack.from(output), session);
+
+ plan.add(cursorDest ? Click.LEFT : Click.LEFT_SHIFT, 0);
+ plan.execute(true);
+
+ return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
+ }
+
+ if (isCursor(transferAction.getDestination())) {
+ plan.add(Click.LEFT, sourceSlot);
+ craftState = CraftState.DONE;
+ } else {
+ if (leftover != 0) {
+ if (transferAction.getCount() > leftover) {
+ return rejectRequest(request);
+ }
+ if (transferAction.getCount() == leftover) {
+ plan.add(Click.LEFT, destSlot);
+ } else {
+ for (int i = 0; i < transferAction.getCount(); i++) {
+ plan.add(Click.RIGHT, destSlot);
+ }
+ }
+ leftover -= transferAction.getCount();
+ break;
+ }
+
+ int remainder = transferAction.getCount() % resultSize;
+ int timesToCraft = transferAction.getCount() / resultSize;
+ for (int i = 0; i < timesToCraft; i++) {
+ plan.add(Click.LEFT, sourceSlot);
+ plan.add(Click.LEFT, destSlot);
+ }
+ if (remainder > 0) {
+ plan.add(Click.LEFT, 0);
+ for (int i = 0; i < remainder; i++) {
+ plan.add(Click.RIGHT, destSlot);
+ }
+ leftover = resultSize - remainder;
+ }
+ }
+ break;
+ }
+ default:
+ return rejectRequest(request);
+ }
+ }
+ plan.execute(false);
+ Set affectedSlots = plan.getAffectedSlots();
+ affectedSlots.addAll(Arrays.asList(1, 2, 3, 4)); //TODO: crafting grid
+ return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
+ }
+
+ public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
+ int creativeId = 0;
+ CraftState craftState = CraftState.START;
+ for (StackRequestActionData action : request.getActions()) {
+ switch (action.getType()) {
+ case CRAFT_CREATIVE: {
+ CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action;
+ if (craftState != CraftState.START) {
+ return rejectRequest(request);
+ }
+ craftState = CraftState.RECIPE_ID;
+
+ creativeId = creativeAction.getCreativeItemNetworkId();
+ break;
+ }
+ case CRAFT_RESULTS_DEPRECATED: {
+ CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action;
+ if (craftState != CraftState.RECIPE_ID) {
+ return rejectRequest(request);
+ }
+ craftState = CraftState.DEPRECATED;
+ break;
+ }
+ case TAKE:
+ case PLACE: {
+ TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action;
+ if (craftState != CraftState.DEPRECATED) {
+ return rejectRequest(request);
+ }
+ craftState = CraftState.TRANSFER;
+
+ if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) {
+ return rejectRequest(request);
+ }
+ // Reference the creative items list we send to the client to know what it's asking of us
+ ItemData creativeItem = ItemRegistry.CREATIVE_ITEMS[creativeId - 1];
+ // Get the correct count
+ creativeItem = ItemData.of(creativeItem.getId(), creativeItem.getDamage(), transferAction.getCount(), creativeItem.getTag());
+ ItemStack javaCreativeItem = ItemTranslator.translateToJava(creativeItem);
+
+ if (isCursor(transferAction.getDestination())) {
+ session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem), session);
+ return acceptRequest(request, Collections.singletonList(
+ new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR,
+ Collections.singletonList(makeItemEntry(0, session.getPlayerInventory().getCursor())))));
+ } else {
+ int javaSlot = bedrockSlotToJava(transferAction.getDestination());
+ GeyserItemStack existingItem = inventory.getItem(javaSlot);
+ if (existingItem.getJavaId() == javaCreativeItem.getId()) {
+ // Adding more to an existing item
+ existingItem.setAmount(existingItem.getAmount() + transferAction.getCount());
+ javaCreativeItem = existingItem.getItemStack();
+ } else {
+ inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem), session);
+ }
+ ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
+ javaSlot,
+ javaCreativeItem
+ );
+ session.sendDownstreamPacket(creativeActionPacket);
+ System.out.println(creativeActionPacket);
+ Set affectedSlots = Collections.singleton(javaSlot);
+ return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
+ }
+ }
+ default:
+ return rejectRequest(request);
+ }
+ }
+ return rejectRequest(request);
+ }
+
+ public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequestPacket.Request request, List containerEntries) {
+ return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries);
+ }
+
+ public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request) {
+ return rejectRequest(request, true);
+ }
+
+ public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request, boolean throwError) {
+ if (throwError) {
+ // Currently for debugging, but might be worth it to keep in the future if something goes terribly wrong.
+ new Throwable("DEBUGGING: ItemStackRequest rejected").printStackTrace();
+ }
+ return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.ERROR, request.getRequestId(), Collections.emptyList());
+ }
+
+ public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getStackNetworkId() < 0)
+ return true;
+// if (slotInfoData.getContainer() == ContainerSlotType.CURSOR) //TODO: temporary
+// return true;
+
+ GeyserItemStack currentItem = isCursor(slotInfoData) ? session.getPlayerInventory().getCursor() : inventory.getItem(bedrockSlotToJava(slotInfoData));
+ return currentItem.getNetId() == slotInfoData.getStackNetworkId();
+ }
+
+ /**
+ * Try to find a slot that can temporarily store the given item.
+ * Only looks in the main inventory and hotbar (excluding offhand).
+ * Only slots that are empty or contain a different type of item are valid.
+ *
+ * @return java id for the temporary slot, or -1 if no viable slot was found
+ */
+ //TODO: compatibility for simulated inventory (ClickPlan)
+ private static int findTempSlot(Inventory inventory, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) {
+ int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable temp slot
+ HashSet itemBlacklist = new HashSet<>(slotBlacklist.length + 1);
+ itemBlacklist.add(item);
+
+ IntSet potentialSlots = new IntOpenHashSet(36);
+ for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) {
+ potentialSlots.add(i);
+ }
+ for (int i : slotBlacklist) {
+ potentialSlots.remove(i);
+ GeyserItemStack blacklistedItem = inventory.getItem(i);
+ if (!blacklistedItem.isEmpty()) {
+ itemBlacklist.add(blacklistedItem);
+ }
+ }
+
+ for (int i : potentialSlots) {
+ GeyserItemStack testItem = inventory.getItem(i);
+ if ((emptyOnly && !testItem.isEmpty())) {
+ continue;
+ }
+
+ boolean viable = true;
+ for (GeyserItemStack blacklistedItem : itemBlacklist) {
+ if (InventoryUtils.canStack(testItem, blacklistedItem)) {
+ viable = false;
+ break;
+ }
+ }
+ if (!viable) {
+ continue;
+ }
+
+ System.out.println("TEMP SLOT CHOSEN: " + i + " => " + inventory.getItem(i));
+ return i;
+ }
+ //could not find a viable temp slot
+ return -1;
+ }
+
+ public List makeContainerEntries(GeyserSession session, Inventory inventory, Set affectedSlots) {
+ Map> containerMap = new HashMap<>();
+ for (int slot : affectedSlots) {
+ BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot);
+ List list = containerMap.computeIfAbsent(bedrockSlot.getContainer(), k -> new ArrayList<>());
+ list.add(makeItemEntry(bedrockSlot.getSlot(), inventory.getItem(slot)));
+ }
+
+ List containerEntries = new ArrayList<>();
+ for (Map.Entry> entry : containerMap.entrySet()) {
+ containerEntries.add(new ItemStackResponsePacket.ContainerEntry(entry.getKey(), entry.getValue()));
+ }
+
+ ItemStackResponsePacket.ItemEntry cursorEntry = makeItemEntry(0, session.getPlayerInventory().getCursor());
+ containerEntries.add(new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry)));
+
+ return containerEntries;
+ }
+
+ public static ItemStackResponsePacket.ItemEntry makeItemEntry(int bedrockSlot, GeyserItemStack itemStack) {
+ ItemStackResponsePacket.ItemEntry itemEntry;
+ if (!itemStack.isEmpty()) {
+ itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), itemStack.getNetId(), "");
+ } else {
+ itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) 0, 0, "");
+ }
+ return itemEntry;
+ }
+
+ private static boolean isCursor(StackRequestSlotInfoData slotInfoData) {
+ return slotInfoData.getContainer() == ContainerSlotType.CURSOR;
+ }
+
+ private enum CraftState {
+ START,
+ RECIPE_ID,
+ DEPRECATED,
+ INGREDIENTS,
+ TRANSFER,
+ DONE
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java
deleted file mode 100644
index c72954bf..00000000
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.network.translators.inventory.action;
-
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.mc.protocol.data.game.window.WindowAction;
-import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket;
-import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket;
-import org.geysermc.connector.inventory.Inventory;
-import org.geysermc.connector.inventory.PlayerInventory;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
-import org.geysermc.connector.network.translators.inventory.SlotType;
-import org.geysermc.connector.utils.InventoryUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.ListIterator;
-
-class ClickPlan {
- private final List plan = new ArrayList<>();
-
- public void add(Click click, int slot) {
- plan.add(new ClickAction(click, slot));
- }
-
- public void execute(GeyserSession session, InventoryTranslator translator, Inventory inventory, boolean refresh) {
- PlayerInventory playerInventory = session.getInventory();
- ListIterator planIter = plan.listIterator();
- while (planIter.hasNext()) {
- final ClickAction action = planIter.next();
- final ItemStack cursorItem = playerInventory.getCursor();
- final ItemStack clickedItem = inventory.getItem(action.slot);
- final short actionId = (short) inventory.getTransactionId().getAndIncrement();
-
- //TODO: stop relying on refreshing the inventory for crafting to work properly
- if (translator.getSlotType(action.slot) != SlotType.NORMAL)
- refresh = true;
-
- ClientWindowActionPacket clickPacket = new ClientWindowActionPacket(inventory.getId(),
- actionId, action.slot, !planIter.hasNext() && refresh ? InventoryUtils.REFRESH_ITEM : clickedItem,
- WindowAction.CLICK_ITEM, action.click.actionParam);
-
- if (translator.getSlotType(action.slot) == SlotType.OUTPUT) {
- if (cursorItem == null && clickedItem != null) {
- playerInventory.setCursor(clickedItem);
- } else if (InventoryUtils.canStack(cursorItem, clickedItem)) {
- playerInventory.setCursor(new ItemStack(cursorItem.getId(),
- cursorItem.getAmount() + clickedItem.getAmount(), cursorItem.getNbt()));
- }
- } else {
- switch (action.click) {
- case LEFT:
- if (!InventoryUtils.canStack(cursorItem, clickedItem)) {
- playerInventory.setCursor(clickedItem);
- inventory.setItem(action.slot, cursorItem);
- } else {
- playerInventory.setCursor(null);
- inventory.setItem(action.slot, new ItemStack(clickedItem.getId(),
- clickedItem.getAmount() + cursorItem.getAmount(), clickedItem.getNbt()));
- }
- break;
- case RIGHT:
- if (cursorItem == null && clickedItem != null) {
- ItemStack halfItem = new ItemStack(clickedItem.getId(),
- clickedItem.getAmount() / 2, clickedItem.getNbt());
- inventory.setItem(action.slot, halfItem);
- playerInventory.setCursor(new ItemStack(clickedItem.getId(),
- clickedItem.getAmount() - halfItem.getAmount(), clickedItem.getNbt()));
- } else if (cursorItem != null && clickedItem == null) {
- playerInventory.setCursor(new ItemStack(cursorItem.getId(),
- cursorItem.getAmount() - 1, cursorItem.getNbt()));
- inventory.setItem(action.slot, new ItemStack(cursorItem.getId(),
- 1, cursorItem.getNbt()));
- } else if (InventoryUtils.canStack(cursorItem, clickedItem)) {
- playerInventory.setCursor(new ItemStack(cursorItem.getId(),
- cursorItem.getAmount() - 1, cursorItem.getNbt()));
- inventory.setItem(action.slot, new ItemStack(clickedItem.getId(),
- clickedItem.getAmount() + 1, clickedItem.getNbt()));
- }
- break;
- }
- }
- session.sendDownstreamPacket(clickPacket);
- session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true));
- }
-
- /*if (refresh) {
- translator.updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- }*/
- }
-
- private static class ClickAction {
- final Click click;
- final int slot;
- ClickAction(Click click, int slot) {
- this.click = click;
- this.slot = slot;
- }
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java
deleted file mode 100644
index c313e366..00000000
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.network.translators.inventory.action;
-
-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.player.PlayerAction;
-import com.github.steveice10.mc.protocol.data.game.window.*;
-import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
-import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
-import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket;
-import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
-import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
-import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
-import org.geysermc.connector.inventory.Inventory;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
-import org.geysermc.connector.network.translators.inventory.SlotType;
-import org.geysermc.connector.network.translators.item.ItemTranslator;
-import org.geysermc.connector.utils.InventoryUtils;
-
-import java.util.*;
-
-public class InventoryActionDataTranslator {
- public static void translate(InventoryTranslator translator, GeyserSession session, Inventory inventory, List actions) {
- if (actions.size() != 2)
- return;
-
- InventoryActionData worldAction = null;
- InventoryActionData cursorAction = null;
- InventoryActionData containerAction = null;
- boolean refresh = false;
- for (InventoryActionData action : actions) {
- if (action.getSource().getContainerId() == ContainerId.CRAFTING_USE_INGREDIENT) {
- return;
- } else if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION) {
- worldAction = action;
- } else if (action.getSource().getContainerId() == ContainerId.UI && action.getSlot() == 0) {
- cursorAction = action;
- ItemData translatedCursor = ItemTranslator.translateToBedrock(session, session.getInventory().getCursor());
- if (!translatedCursor.equals(action.getFromItem())) {
- refresh = true;
- }
- } else {
- containerAction = action;
- ItemData translatedItem = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.bedrockSlotToJava(action)));
- if (!translatedItem.equals(action.getFromItem())) {
- refresh = true;
- }
- }
- }
-
- final int craftSlot = session.getCraftSlot();
- session.setCraftSlot(0);
-
- if (worldAction != null) {
- InventoryActionData sourceAction;
- if (cursorAction != null) {
- sourceAction = cursorAction;
- } else {
- sourceAction = containerAction;
- }
-
- if (sourceAction != null) {
- if (worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
- //quick dropping from hotbar?
- if (session.getInventoryCache().getOpenInventory() == null && sourceAction.getSource().getContainerId() == ContainerId.INVENTORY) {
- int heldSlot = session.getInventory().getHeldItemSlot();
- if (sourceAction.getSlot() == heldSlot) {
- ClientPlayerActionPacket actionPacket = new ClientPlayerActionPacket(
- sourceAction.getToItem().getCount() == 0 ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM,
- new Position(0, 0, 0), BlockFace.DOWN);
- session.sendDownstreamPacket(actionPacket);
- ItemStack item = session.getInventory().getItem(heldSlot);
- if (item != null) {
- session.getInventory().setItem(heldSlot, new ItemStack(item.getId(), item.getAmount() - 1, item.getNbt()));
- }
- return;
- }
- }
- int dropAmount = sourceAction.getFromItem().getCount() - sourceAction.getToItem().getCount();
- if (sourceAction != cursorAction) { //dropping directly from inventory
- int javaSlot = translator.bedrockSlotToJava(sourceAction);
- if (dropAmount == sourceAction.getFromItem().getCount()) {
- ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(),
- inventory.getTransactionId().getAndIncrement(),
- javaSlot, null, WindowAction.DROP_ITEM,
- DropItemParam.DROP_SELECTED_STACK);
- session.sendDownstreamPacket(dropPacket);
- } else {
- for (int i = 0; i < dropAmount; i++) {
- ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(),
- inventory.getTransactionId().getAndIncrement(),
- javaSlot, null, WindowAction.DROP_ITEM,
- DropItemParam.DROP_FROM_SELECTED);
- session.sendDownstreamPacket(dropPacket);
- }
- }
- ItemStack item = inventory.getItem(javaSlot);
- if (item != null) {
- inventory.setItem(javaSlot, new ItemStack(item.getId(), item.getAmount() - dropAmount, item.getNbt()));
- }
- return;
- } else { //clicking outside of inventory
- ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), inventory.getTransactionId().getAndIncrement(),
- -999, null, WindowAction.CLICK_ITEM,
- dropAmount > 1 ? ClickItemParam.LEFT_CLICK : ClickItemParam.RIGHT_CLICK);
- session.sendDownstreamPacket(dropPacket);
- ItemStack cursor = session.getInventory().getCursor();
- if (cursor != null) {
- session.getInventory().setCursor(new ItemStack(cursor.getId(), dropAmount > 1 ? 0 : cursor.getAmount() - 1, cursor.getNbt()));
- }
- return;
- }
- }
- }
- } else if (cursorAction != null && containerAction != null) {
- //left/right click
- ClickPlan plan = new ClickPlan();
- int javaSlot = translator.bedrockSlotToJava(containerAction);
- if (cursorAction.getFromItem().equals(containerAction.getToItem())
- && containerAction.getFromItem().equals(cursorAction.getToItem())
- && !InventoryUtils.canStack(cursorAction.getFromItem(), containerAction.getFromItem())) { //simple swap
- plan.add(Click.LEFT, javaSlot);
- } else if (cursorAction.getFromItem().getCount() > cursorAction.getToItem().getCount()) { //release
- if (cursorAction.getToItem().getCount() == 0) {
- plan.add(Click.LEFT, javaSlot);
- } else {
- int difference = cursorAction.getFromItem().getCount() - cursorAction.getToItem().getCount();
- for (int i = 0; i < difference; i++) {
- plan.add(Click.RIGHT, javaSlot);
- }
- }
- } else { //pickup
- if (cursorAction.getFromItem().getCount() == 0) {
- if (containerAction.getToItem().getCount() == 0) { //pickup all
- plan.add(Click.LEFT, javaSlot);
- } else { //pickup some
- if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT
- || containerAction.getToItem().getCount() == containerAction.getFromItem().getCount() / 2) { //right click
- plan.add(Click.RIGHT, javaSlot);
- } else {
- plan.add(Click.LEFT, javaSlot);
- int difference = containerAction.getFromItem().getCount() - cursorAction.getToItem().getCount();
- for (int i = 0; i < difference; i++) {
- plan.add(Click.RIGHT, javaSlot);
- }
- }
- }
- } else { //pickup into non-empty cursor
- if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT) {
- if (containerAction.getToItem().getCount() == 0) {
- plan.add(Click.LEFT, javaSlot);
- } else {
- ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(),
- inventory.getTransactionId().getAndIncrement(),
- javaSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM,
- ShiftClickItemParam.LEFT_CLICK);
- session.sendDownstreamPacket(shiftClickPacket);
- translator.updateInventory(session, inventory);
- return;
- }
- } else if (translator.getSlotType(javaSlot) == SlotType.OUTPUT) {
- plan.add(Click.LEFT, javaSlot);
- } else {
- int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot), false);
- if (cursorSlot != -1) {
- plan.add(Click.LEFT, cursorSlot);
- } else {
- translator.updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- return;
- }
- plan.add(Click.LEFT, javaSlot);
- int difference = cursorAction.getToItem().getCount() - cursorAction.getFromItem().getCount();
- for (int i = 0; i < difference; i++) {
- plan.add(Click.RIGHT, cursorSlot);
- }
- plan.add(Click.LEFT, javaSlot);
- plan.add(Click.LEFT, cursorSlot);
- }
- }
- }
- plan.execute(session, translator, inventory, refresh);
- return;
- } else {
- ClickPlan plan = new ClickPlan();
- InventoryActionData fromAction;
- InventoryActionData toAction;
- if (actions.get(0).getFromItem().getCount() >= actions.get(0).getToItem().getCount()) {
- fromAction = actions.get(0);
- toAction = actions.get(1);
- } else {
- fromAction = actions.get(1);
- toAction = actions.get(0);
- }
- int fromSlot = translator.bedrockSlotToJava(fromAction);
- int toSlot = translator.bedrockSlotToJava(toAction);
-
- if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) {
- if ((craftSlot != 0 && craftSlot != -2) && (inventory.getItem(toSlot) == null
- || InventoryUtils.canStack(session.getInventory().getCursor(), inventory.getItem(toSlot)))) {
- if (fromAction.getToItem().getCount() == 0) {
- refresh = true;
- plan.add(Click.LEFT, toSlot);
- if (craftSlot != -1) {
- plan.add(Click.LEFT, craftSlot);
- }
- } else {
- int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount();
- for (int i = 0; i < difference; i++) {
- plan.add(Click.RIGHT, toSlot);
- }
- session.setCraftSlot(craftSlot);
- }
- plan.execute(session, translator, inventory, refresh);
- return;
- } else {
- session.setCraftSlot(-2);
- }
- }
-
- int cursorSlot = -1;
- if (session.getInventory().getCursor() != null) { //move cursor contents to a temporary slot
- cursorSlot = findTempSlot(inventory,
- session.getInventory().getCursor(),
- Arrays.asList(fromSlot, toSlot),
- translator.getSlotType(fromSlot) == SlotType.OUTPUT);
- if (cursorSlot != -1) {
- plan.add(Click.LEFT, cursorSlot);
- } else {
- translator.updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- return;
- }
- }
- if ((fromAction.getFromItem().equals(toAction.getToItem()) && !InventoryUtils.canStack(fromAction.getFromItem(), toAction.getFromItem()))
- || fromAction.getToItem().getId() == 0) { //slot swap
- plan.add(Click.LEFT, fromSlot);
- plan.add(Click.LEFT, toSlot);
- if (fromAction.getToItem().getId() != 0) {
- plan.add(Click.LEFT, fromSlot);
- }
- } else if (InventoryUtils.canStack(fromAction.getFromItem(), toAction.getToItem())) { //partial item move
- if (translator.getSlotType(fromSlot) == SlotType.FURNACE_OUTPUT) {
- ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(),
- inventory.getTransactionId().getAndIncrement(),
- fromSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM,
- ShiftClickItemParam.LEFT_CLICK);
- session.sendDownstreamPacket(shiftClickPacket);
- translator.updateInventory(session, inventory);
- return;
- } else if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) {
- session.setCraftSlot(cursorSlot);
- plan.add(Click.LEFT, fromSlot);
- int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount();
- for (int i = 0; i < difference; i++) {
- plan.add(Click.RIGHT, toSlot);
- }
- //client will send additional packets later to finish transferring crafting output
- //translator will know how to handle this using the craftSlot variable
- } else {
- plan.add(Click.LEFT, fromSlot);
- int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount();
- for (int i = 0; i < difference; i++) {
- plan.add(Click.RIGHT, toSlot);
- }
- plan.add(Click.LEFT, fromSlot);
- }
- }
- if (cursorSlot != -1) {
- plan.add(Click.LEFT, cursorSlot);
- }
- plan.execute(session, translator, inventory, refresh);
- return;
- }
-
- translator.updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- }
-
- private static int findTempSlot(Inventory inventory, ItemStack item, List slotBlacklist, boolean emptyOnly) {
- /*try and find a slot that can temporarily store the given item
- only look in the main inventory and hotbar
- only slots that are empty or contain a different type of item are valid*/
- int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable slot (some servers disable it)
- List itemBlacklist = new ArrayList<>(slotBlacklist.size() + 1);
- itemBlacklist.add(item);
- for (int slot : slotBlacklist) {
- ItemStack blacklistItem = inventory.getItem(slot);
- if (blacklistItem != null)
- itemBlacklist.add(blacklistItem);
- }
- for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) {
- ItemStack testItem = inventory.getItem(i);
- boolean acceptable = true;
- if (testItem != null) {
- if (emptyOnly) {
- continue;
- }
- for (ItemStack blacklistItem : itemBlacklist) {
- if (InventoryUtils.canStack(testItem, blacklistItem)) {
- acceptable = false;
- break;
- }
- }
- }
- if (acceptable && !slotBlacklist.contains(i))
- return i;
- }
- //could not find a viable temp slot
- return -1;
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java
new file mode 100644
index 00000000..d3666a9e
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.click;
+
+import com.github.steveice10.mc.protocol.data.game.window.*;
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public enum Click {
+ LEFT(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK),
+ RIGHT(WindowAction.CLICK_ITEM, ClickItemParam.RIGHT_CLICK),
+ LEFT_SHIFT(WindowAction.SHIFT_CLICK_ITEM, ShiftClickItemParam.LEFT_CLICK),
+ DROP_ONE(WindowAction.DROP_ITEM, DropItemParam.DROP_FROM_SELECTED),
+ DROP_ALL(WindowAction.DROP_ITEM, DropItemParam.DROP_SELECTED_STACK),
+ LEFT_OUTSIDE(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK),
+ RIGHT_OUTSIDE(WindowAction.CLICK_ITEM, ClickItemParam.RIGHT_CLICK);
+
+ public static final int OUTSIDE_SLOT = -999;
+
+ public final WindowAction windowAction;
+ public final WindowActionParam actionParam;
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java
new file mode 100644
index 00000000..9268cbf1
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.click;
+
+import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
+import com.github.steveice10.mc.protocol.data.game.window.WindowAction;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import lombok.Value;
+import org.geysermc.connector.inventory.GeyserItemStack;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.SlotType;
+import org.geysermc.connector.utils.InventoryUtils;
+
+import java.util.*;
+
+public class ClickPlan {
+ private final List plan = new ArrayList<>();
+ private final Int2ObjectMap simulatedItems;
+ private GeyserItemStack simulatedCursor;
+ private boolean simulating;
+
+ private final GeyserSession session;
+ private final InventoryTranslator translator;
+ private final Inventory inventory;
+
+ public ClickPlan(GeyserSession session, InventoryTranslator translator, Inventory inventory) {
+ this.session = session;
+ this.translator = translator;
+ this.inventory = inventory;
+
+ this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize());
+ this.simulatedCursor = session.getPlayerInventory().getCursor().copy();
+ this.simulating = true;
+ }
+
+ private void resetSimulation() {
+ this.simulatedItems.clear();
+ this.simulatedCursor = session.getPlayerInventory().getCursor().copy();
+ }
+
+ public void add(Click click, int slot) {
+ if (!simulating)
+ throw new UnsupportedOperationException("ClickPlan already executed");
+
+ if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) {
+ slot = Click.OUTSIDE_SLOT;
+ }
+
+ ClickAction action = new ClickAction(click, slot);
+ plan.add(action);
+ simulateAction(action);
+ }
+
+ public void execute(boolean refresh) {
+ //update geyser inventory after simulation to avoid net id desync
+ resetSimulation();
+ ListIterator planIter = plan.listIterator();
+ while (planIter.hasNext()) {
+ ClickAction action = planIter.next();
+
+ if (action.slot != Click.OUTSIDE_SLOT && translator.getSlotType(action.slot) != SlotType.NORMAL) {
+ refresh = true;
+ }
+
+ ItemStack clickedItemStack;
+ if (!planIter.hasNext() && refresh) {
+ clickedItemStack = InventoryUtils.REFRESH_ITEM;
+ } else if (action.click.windowAction == WindowAction.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) {
+ clickedItemStack = null;
+ } else {
+ clickedItemStack = getItem(action.slot).getItemStack();
+ }
+
+ short actionId = inventory.getNextTransactionId();
+ ClientWindowActionPacket clickPacket = new ClientWindowActionPacket(
+ inventory.getId(),
+ actionId,
+ action.slot,
+ clickedItemStack,
+ action.click.windowAction,
+ action.click.actionParam
+ );
+
+ simulateAction(action);
+
+ session.sendDownstreamPacket(clickPacket);
+ if (clickedItemStack == InventoryUtils.REFRESH_ITEM) {
+ session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true));
+ }
+ System.out.println(clickPacket);
+ }
+
+ session.getPlayerInventory().setCursor(simulatedCursor, session);
+ for (Int2ObjectMap.Entry simulatedSlot : simulatedItems.int2ObjectEntrySet()) {
+ inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session);
+ }
+ simulating = false;
+ }
+
+ public GeyserItemStack getItem(int slot) {
+ return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
+ }
+
+ public GeyserItemStack getCursor() {
+ return simulatedCursor;
+ }
+
+ private void setItem(int slot, GeyserItemStack item) {
+ if (simulating) {
+ simulatedItems.put(slot, item);
+ } else {
+ inventory.setItem(slot, item, session);
+ }
+ }
+
+ private void setCursor(GeyserItemStack item) {
+ if (simulating) {
+ simulatedCursor = item;
+ } else {
+ session.getPlayerInventory().setCursor(item, session);
+ }
+ }
+
+ private void simulateAction(ClickAction action) {
+ GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor();
+ switch (action.click) {
+ case LEFT_OUTSIDE:
+ setCursor(GeyserItemStack.EMPTY);
+ return;
+ case RIGHT_OUTSIDE:
+ if (!cursor.isEmpty()) {
+ cursor.sub(1);
+ }
+ return;
+ }
+
+ GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot);
+ if (translator.getSlotType(action.slot) == SlotType.OUTPUT) {
+ switch (action.click) {
+ case LEFT:
+ case RIGHT:
+ if (cursor.isEmpty() && !clicked.isEmpty()) {
+ setCursor(clicked.copy());
+ } else if (InventoryUtils.canStack(cursor, clicked)) {
+ cursor.add(clicked.getAmount());
+ }
+ break;
+ }
+ } else {
+ switch (action.click) {
+ case LEFT:
+ if (!InventoryUtils.canStack(cursor, clicked)) {
+ setCursor(clicked);
+ setItem(action.slot, cursor);
+ } else {
+ setCursor(GeyserItemStack.EMPTY);
+ clicked.add(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);
+ } 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);
+ }
+ break;
+ case LEFT_SHIFT:
+ //TODO
+ break;
+ case DROP_ONE:
+ if (!clicked.isEmpty()) {
+ clicked.sub(1);
+ }
+ break;
+ case DROP_ALL:
+ setItem(action.slot, GeyserItemStack.EMPTY);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @return a new set of all affected slots. This isn't a constant variable; it's newly generated each time it is run.
+ */
+ public IntSet getAffectedSlots() {
+ IntSet affectedSlots = new IntOpenHashSet();
+ for (ClickAction action : plan) {
+ if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) {
+ affectedSlots.add(action.slot);
+ }
+ }
+ return affectedSlots;
+ }
+
+ @Value
+ private static class ClickAction {
+ Click click;
+ /**
+ * Java slot
+ */
+ int slot;
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java
index 6b47cf70..5c49c723 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java
@@ -38,6 +38,9 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
+/**
+ * Manages the fake block we implement for each inventory.
+ */
@AllArgsConstructor
public class BlockInventoryHolder extends InventoryHolder {
private final int blockId;
@@ -45,6 +48,11 @@ public class BlockInventoryHolder extends InventoryHolder {
@Override
public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
+ //TODO: Improve on this (for example, multiple block states). We need this for the beacon.
+ if (BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionPosition())) == blockId) {
+ inventory.setHolderPosition(session.getLastInteractionPosition());
+ return;
+ }
Vector3i position = session.getPlayerEntity().getPosition().toInt();
position = position.add(Vector3i.UP);
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
@@ -79,12 +87,16 @@ public class BlockInventoryHolder extends InventoryHolder {
@Override
public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
Vector3i holderPos = inventory.getHolderPosition();
+ if (holderPos.equals(session.getLastInteractionPosition())) {
+ return;
+ }
Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ());
int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ());
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
blockPacket.setDataLayer(0);
blockPacket.setBlockPosition(holderPos);
blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock));
+ blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(blockPacket);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java
similarity index 77%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java
index 8f70189d..92710656 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java
@@ -23,25 +23,33 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder;
import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
+import org.geysermc.connector.network.translators.world.block.BlockTranslator;
-public class BlockInventoryTranslator extends BaseInventoryTranslator {
+/**
+ * Provided as a base for any inventory that requires a block for opening it
+ */
+public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTranslator {
private final InventoryHolder holder;
private final InventoryUpdater updater;
- public BlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) {
+ /**
+ * @param size the amount of slots that the inventory adds alongside the base inventory slots
+ * @param javaBlockIdentifier a Java block identifier that is used as a temporary block
+ * @param containerType the container type of this inventory
+ * @param updater updater
+ */
+ public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) {
super(size);
int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier);
- int blockId = BlockTranslator.getBedrockBlockId(javaBlockState);
- this.holder = new BlockInventoryHolder(blockId, containerType);
+ this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), containerType);
this.updater = updater;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java
new file mode 100644
index 00000000..b131544b
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import org.geysermc.connector.inventory.AnvilContainer;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+
+public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
+ public AnvilInventoryTranslator() {
+ super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE);
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_INPUT) {
+ return 0;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_MATERIAL) {
+ return 1;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
+ return 2;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ switch (slot) {
+ case 0:
+ return new BedrockContainerSlot(ContainerSlotType.ANVIL_INPUT, 1);
+ case 1:
+ return new BedrockContainerSlot(ContainerSlotType.ANVIL_MATERIAL, 2);
+ case 2:
+ return new BedrockContainerSlot(ContainerSlotType.ANVIL_RESULT, 50);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ switch (slot) {
+ case 0:
+ return 1;
+ case 1:
+ return 2;
+ case 2:
+ return 50;
+ }
+ return super.javaSlotToBedrock(slot);
+ }
+
+ @Override
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ return new AnvilContainer(name, windowId, this.size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java
similarity index 53%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java
index ca241e29..b67ca008 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java
@@ -23,18 +23,21 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators;
-import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import org.geysermc.connector.inventory.Container;
import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.SlotType;
-import java.util.List;
-
-public abstract class BaseInventoryTranslator extends InventoryTranslator{
- BaseInventoryTranslator(int size) {
+public abstract class BaseInventoryTranslator extends InventoryTranslator {
+ public BaseInventoryTranslator(int size) {
super(size);
}
@@ -44,15 +47,18 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator{
}
@Override
- public int bedrockSlotToJava(InventoryActionData action) {
- int slotnum = action.getSlot();
- if (action.getSource().getContainerId() == ContainerId.INVENTORY) {
- //hotbar
- if (slotnum >= 9) {
- return slotnum + this.size - 9;
- } else {
- return slotnum + this.size + 27;
- }
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ int slotnum = slotInfoData.getSlot();
+ switch (slotInfoData.getContainer()) {
+ case HOTBAR_AND_INVENTORY:
+ case HOTBAR:
+ case INVENTORY:
+ //hotbar
+ if (slotnum >= 9) {
+ return slotnum + this.size - 9;
+ } else {
+ return slotnum + this.size + 27;
+ }
}
return slotnum;
}
@@ -70,13 +76,26 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator{
return slot;
}
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot >= this.size) {
+ final int tmp = slot - this.size;
+ if (tmp < 27) {
+ return new BedrockContainerSlot(ContainerSlotType.INVENTORY, tmp + 9);
+ } else {
+ return new BedrockContainerSlot(ContainerSlotType.HOTBAR, tmp - 27);
+ }
+ }
+ throw new IllegalArgumentException("Unknown bedrock slot");
+ }
+
@Override
public SlotType getSlotType(int javaSlot) {
return SlotType.NORMAL;
}
@Override
- public void translateActions(GeyserSession session, Inventory inventory, List actions) {
- InventoryActionDataTranslator.translate(this, session, inventory, actions);
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ return new Container(name, windowId, this.size, playerInventory);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java
new file mode 100644
index 00000000..5b31c83b
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientSetBeaconEffectPacket;
+import com.nukkitx.math.vector.Vector3i;
+import com.nukkitx.nbt.NbtMap;
+import com.nukkitx.nbt.NbtMapBuilder;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.BeaconPaymentStackRequestActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
+import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
+import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
+import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
+import org.geysermc.connector.inventory.BeaconContainer;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+
+import java.util.Collections;
+
+public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator {
+ public BeaconInventoryTranslator() {
+ super(1, "minecraft:beacon", ContainerType.BEACON, UIInventoryUpdater.INSTANCE);
+ }
+
+ @Override
+ public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
+ //FIXME?: Beacon graphics look weird after inputting an item. This might be a Bedrock bug, since it resets to nothing
+ // on BDS
+ BeaconContainer beaconContainer = (BeaconContainer) inventory;
+ switch (key) {
+ case 0:
+ // Power - beacon doesn't use this, and uses the block position instead
+ break;
+ case 1:
+ beaconContainer.setPrimaryId(value == -1 ? 0 : value);
+ break;
+ case 2:
+ beaconContainer.setSecondaryId(value == -1 ? 0 : value);
+ break;
+ }
+
+ // Send a block entity data packet update to the fake beacon inventory
+ Vector3i position = inventory.getHolderPosition();
+ NbtMapBuilder builder = NbtMap.builder()
+ .putInt("x", position.getX())
+ .putInt("y", position.getY())
+ .putInt("z", position.getZ())
+ .putString("CustomName", inventory.getTitle())
+ .putString("id", "Beacon")
+ .putInt("primary", beaconContainer.getPrimaryId())
+ .putInt("secondary", beaconContainer.getSecondaryId());
+
+ BlockEntityDataPacket packet = new BlockEntityDataPacket();
+ packet.setBlockPosition(position);
+ packet.setData(builder.build());
+ System.out.println(packet.toString());
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
+ return action.getType() == StackRequestActionType.BEACON_PAYMENT;
+ }
+
+ @Override
+ public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
+ // Input a beacon payment
+ BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0];
+ ClientSetBeaconEffectPacket packet = new ClientSetBeaconEffectPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect());
+ System.out.println(packet.toString());
+ session.sendDownstreamPacket(packet);
+ return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.BEACON_PAYMENT) {
+ return 0;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 0) {
+ return new BedrockContainerSlot(ContainerSlotType.BEACON_PAYMENT, 27);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ if (slot == 0) {
+ return 27;
+ }
+ return super.javaSlotToBedrock(slot);
+ }
+
+ @Override
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ return new BeaconContainer(name, windowId, this.size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java
similarity index 67%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java
index 2242a979..9dbbbf96 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java
@@ -23,18 +23,20 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
-public class BrewingInventoryTranslator extends BlockInventoryTranslator {
+public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator {
public BrewingInventoryTranslator() {
- super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, new ContainerInventoryUpdater());
+ super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, ContainerInventoryUpdater.INSTANCE);
}
@Override
@@ -66,20 +68,18 @@ public class BrewingInventoryTranslator extends BlockInventoryTranslator {
}
@Override
- public int bedrockSlotToJava(InventoryActionData action) {
- final int slot = super.bedrockSlotToJava(action);
- switch (slot) {
- case 0:
- return 3;
- case 1:
- return 0;
- case 2:
- return 1;
- case 3:
- return 2;
- default:
- return slot;
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ System.out.println("Brewing stand: " + slotInfoData);
+ if (slotInfoData.getContainer() == ContainerSlotType.BREWING_INPUT) {
+ // Ingredient
+ // TODO: This hasn't worked and then suddenly, it did.
+ return 3;
}
+ if (slotInfoData.getContainer() == ContainerSlotType.BREWING_RESULT) {
+ // Potions
+ return slotInfoData.getSlot() - 1;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
}
@Override
@@ -96,4 +96,18 @@ public class BrewingInventoryTranslator extends BlockInventoryTranslator {
}
return super.javaSlotToBedrock(slot);
}
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 0 || slot == 1 || slot == 2) {
+ return new BedrockContainerSlot(ContainerSlotType.BREWING_RESULT, javaSlotToBedrock(slot));
+ }
+ if (slot == 3) {
+ return new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0);
+ }
+ if (slot == 4) {
+ return new BedrockContainerSlot(ContainerSlotType.BREWING_FUEL, 4);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java
new file mode 100644
index 00000000..04794433
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import org.geysermc.connector.inventory.CartographyContainer;
+import org.geysermc.connector.inventory.GeyserItemStack;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+
+public class CartographyInventoryTranslator extends AbstractBlockInventoryTranslator {
+ public CartographyInventoryTranslator() {
+ super(3, "minecraft:cartography_table", ContainerType.CARTOGRAPHY, UIInventoryUpdater.INSTANCE);
+ }
+
+ @Override
+ public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) {
+ if (javaDestinationSlot == 0) {
+ // Bedrock Edition can use paper in slot 0
+ GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
+ return itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:paper");
+ } else if (javaDestinationSlot == 1) {
+ // Bedrock Edition can use a compass to create locator maps in the ADDITIONAL slot
+ GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
+ return itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:compass");
+ }
+ return false;
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_INPUT) {
+ return 0;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_ADDITIONAL) {
+ return 1;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
+ return 2;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ switch (slot) {
+ case 0:
+ return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_INPUT, 12);
+ case 1:
+ return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_ADDITIONAL, 13);
+ case 2:
+ return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_RESULT, 50);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ switch (slot) {
+ case 0:
+ return 12;
+ case 1:
+ return 13;
+ case 2:
+ return 50;
+ }
+ return super.javaSlotToBedrock(slot);
+ }
+
+ @Override
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ return new CartographyContainer(name, windowId, this.size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java
similarity index 53%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java
index 18cbbae7..81769c00 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java
@@ -23,47 +23,18 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators;
-import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
-import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
-import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
-import org.geysermc.connector.inventory.Inventory;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
-import org.geysermc.connector.utils.InventoryUtils;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.SlotType;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
-import java.util.List;
-
-public class CraftingInventoryTranslator extends BlockInventoryTranslator {
+public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslator {
public CraftingInventoryTranslator() {
- super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, new CursorInventoryUpdater());
- }
-
- @Override
- public int bedrockSlotToJava(InventoryActionData action) {
- if (action.getSlot() == 50) {
- // Slot 50 is used for crafting with a controller.
- return 0;
- }
-
- if (action.getSource().getContainerId() == ContainerId.UI) {
- int slotnum = action.getSlot();
- if (slotnum >= 32 && 42 >= slotnum) {
- return slotnum - 31;
- }
- }
- return super.bedrockSlotToJava(action);
- }
-
- @Override
- public int javaSlotToBedrock(int slot) {
- if (slot < size) {
- return slot == 0 ? 50 : slot + 31;
- }
- return super.javaSlotToBedrock(slot);
+ super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE);
}
@Override
@@ -74,16 +45,34 @@ public class CraftingInventoryTranslator extends BlockInventoryTranslator {
}
@Override
- public void translateActions(GeyserSession session, Inventory inventory, List actions) {
- if (session.getGameMode() == GameMode.CREATIVE) {
- for (InventoryActionData action : actions) {
- if (action.getSource().getType() == InventorySource.Type.CREATIVE) {
- updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- return;
- }
- }
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot >= 1 && slot <= 9) {
+ return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 31);
}
- super.translateActions(session, inventory, actions);
+ if (slot == 0) {
+ return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT) {
+ // Java goes from 1 - 9, left to right then up to down
+ // Bedrock is the same, but it starts from 32.
+ return slotInfoData.getSlot() - 31;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.CRAFTING_OUTPUT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
+ return 0;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ if (slot < size) {
+ return slot == 0 ? 50 : slot + 31;
+ }
+ return super.javaSlotToBedrock(slot);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java
new file mode 100644
index 00000000..4d105d42
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
+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.ItemStackRequestPacket;
+import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
+import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket;
+import org.geysermc.connector.inventory.EnchantingContainer;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+import org.geysermc.connector.network.translators.item.Enchantment;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator {
+ public EnchantingInventoryTranslator() {
+ super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, UIInventoryUpdater.INSTANCE);
+ }
+
+ @Override
+ public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
+ int slotToUpdate;
+ EnchantingContainer enchantingInventory = (EnchantingContainer) inventory;
+ boolean shouldUpdate = false;
+ switch (key) {
+ case 0:
+ case 1:
+ case 2:
+ // Experience required
+ slotToUpdate = key;
+ enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setXpCost(value);
+ break;
+ case 4:
+ case 5:
+ case 6:
+ // Enchantment type
+ slotToUpdate = key - 4;
+ int index = value;
+ if (index != -1) {
+ Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + JavaEnchantment.values()[index].name().toLowerCase());
+ if (enchantment != null) {
+ // Convert the Java enchantment index to Bedrock's
+ index = enchantment.ordinal();
+ } else {
+ index = -1;
+ }
+ }
+ enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setJavaEnchantIndex(value);
+ enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setBedrockEnchantIndex(index);
+ break;
+ case 7:
+ case 8:
+ case 9:
+ // Enchantment level
+ slotToUpdate = key - 7;
+ enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantLevel(value);
+ shouldUpdate = true; // Java sends each property as its own packet, so let's only update after all properties have been sent
+ break;
+ default:
+ return;
+ }
+ if (shouldUpdate) {
+ enchantingInventory.getEnchantOptions()[slotToUpdate] = enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].build(session);
+ PlayerEnchantOptionsPacket packet = new PlayerEnchantOptionsPacket();
+ packet.getOptions().addAll(Arrays.asList(enchantingInventory.getEnchantOptions()));
+ System.out.println(packet);
+ session.sendUpstreamPacket(packet);
+ }
+ }
+
+ @Override
+ public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
+ return action.getType() == StackRequestActionType.CRAFT_RECIPE;
+ }
+
+ @Override
+ public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
+ // Client has requested an item to be enchanted
+ CraftRecipeStackRequestActionData craftRecipeData = (CraftRecipeStackRequestActionData) request.getActions()[0];
+ EnchantingContainer enchantingInventory = (EnchantingContainer) inventory;
+ int javaSlot = -1;
+ for (int i = 0; i < enchantingInventory.getEnchantOptions().length; i++) {
+ EnchantOptionData enchantData = enchantingInventory.getEnchantOptions()[i];
+ if (enchantData != null) {
+ if (craftRecipeData.getRecipeNetworkId() == enchantData.getEnchantNetId()) {
+ // Enchant net ID is how we differentiate between what item Bedrock wants
+ javaSlot = enchantingInventory.getGeyserEnchantOptions()[i].getJavaIndex();
+ break;
+ }
+ }
+ }
+ if (javaSlot == -1) {
+ // Slot should be determined as 0, 1, or 2
+ return rejectRequest(request);
+ }
+ ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), javaSlot);
+ System.out.println(packet);
+ session.sendDownstreamPacket(packet);
+ return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.ENCHANTING_INPUT) {
+ return 0;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.ENCHANTING_LAPIS) {
+ return 1;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 0) {
+ return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_INPUT, 14);
+ }
+ if (slot == 1) {
+ return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_LAPIS, 15);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ if (slot == 0) {
+ return 14;
+ }
+ if (slot == 1) {
+ return 15;
+ }
+ return super.javaSlotToBedrock(slot);
+ }
+
+ @Override
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ return new EnchantingContainer(name, windowId, this.size, playerInventory);
+ }
+
+ /**
+ * Enchantments classified by their Java index
+ */
+ public enum JavaEnchantment {
+ PROTECTION,
+ FIRE_PROTECTION,
+ FEATHER_FALLING,
+ BLAST_PROTECTION,
+ PROJECTILE_PROTECTION,
+ RESPIRATION,
+ AQUA_AFFINITY,
+ THORNS,
+ DEPTH_STRIDER,
+ FROST_WALKER,
+ BINDING_CURSE,
+ SOUL_SPEED,
+ SHARPNESS,
+ SMITE,
+ BANE_OF_ARTHROPODS,
+ KNOCKBACK,
+ FIRE_ASPECT,
+ LOOTING,
+ SWEEPING,
+ EFFICIENCY,
+ SILK_TOUCH,
+ UNBREAKING,
+ FORTUNE,
+ POWER,
+ PUNCH,
+ FLAME,
+ INFINITY,
+ LUCK_OF_THE_SEA,
+ LURE,
+ LOYALTY,
+ IMPALING,
+ RIPTIDE,
+ CHANNELING,
+ MULTISHOT,
+ QUICK_CHARGE,
+ PIERCING,
+ MENDING,
+ VANISHING_CURSE
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java
new file mode 100644
index 00000000..55df41c1
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
+
+/**
+ * Implemented on top of any block that does not have special properties implemented
+ */
+public class GenericBlockInventoryTranslator extends AbstractBlockInventoryTranslator {
+ public GenericBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType) {
+ super(size, javaBlockIdentifier, containerType, ContainerInventoryUpdater.INSTANCE);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
+ if (javaSlot < this.size) {
+ return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot);
+ }
+ return super.javaSlotToBedrockContainer(javaSlot);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java
similarity index 53%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java
index 87448ff5..81f2df8b 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java
@@ -23,34 +23,44 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators;
-import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
-import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
-
-public class GrindstoneInventoryTranslator extends BlockInventoryTranslator {
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTranslator {
public GrindstoneInventoryTranslator() {
- super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, new CursorInventoryUpdater());
+ super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, UIInventoryUpdater.INSTANCE);
}
@Override
- public int bedrockSlotToJava(InventoryActionData action) {
- final int slot = super.bedrockSlotToJava(action);
- if (action.getSource().getContainerId() == ContainerId.UI) {
- switch (slot) {
- case 16:
- return 0;
- case 17:
- return 1;
- case 50:
- return 2;
- default:
- return slot;
- }
- } return slot;
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_INPUT) {
+ return 0;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_ADDITIONAL) {
+ return 1;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
+ return 2;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ switch (slot) {
+ case 0:
+ return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_INPUT, 16);
+ case 1:
+ return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_ADDITIONAL, 17);
+ case 2:
+ return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_RESULT, 50);
+ }
+ return super.javaSlotToBedrockContainer(slot);
}
@Override
@@ -65,5 +75,4 @@ public class GrindstoneInventoryTranslator extends BlockInventoryTranslator {
}
return super.javaSlotToBedrock(slot);
}
-
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java
new file mode 100644
index 00000000..9ce0ee0d
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
+import com.github.steveice10.opennbt.tag.builtin.ListTag;
+import com.nukkitx.math.vector.Vector3i;
+import com.nukkitx.nbt.NbtMap;
+import com.nukkitx.nbt.NbtMapBuilder;
+import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
+import org.geysermc.connector.inventory.GeyserItemStack;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.LecternContainer;
+import org.geysermc.connector.inventory.PlayerInventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
+import org.geysermc.connector.utils.BlockEntityUtils;
+import org.geysermc.connector.utils.InventoryUtils;
+
+public class LecternInventoryTranslator extends BaseInventoryTranslator {
+ private final InventoryUpdater updater;
+
+ public LecternInventoryTranslator() {
+ super(1);
+ this.updater = new LecternInventoryUpdater();
+ }
+
+ @Override
+ public void prepareInventory(GeyserSession session, Inventory inventory) {
+
+ }
+
+ @Override
+ public void openInventory(GeyserSession session, Inventory inventory) {
+
+ }
+
+ @Override
+ public void closeInventory(GeyserSession session, Inventory inventory) {
+
+ }
+
+ @Override
+ public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
+ if (key == 0) { // Lectern page update
+ LecternContainer lecternContainer = (LecternContainer) inventory;
+ lecternContainer.setCurrentBedrockPage(value / 2);
+ lecternContainer.setBlockEntityTag(lecternContainer.getBlockEntityTag().toBuilder().putInt("page", lecternContainer.getCurrentBedrockPage()).build());
+ BlockEntityUtils.updateBlockEntity(session, lecternContainer.getBlockEntityTag(), lecternContainer.getPosition());
+ }
+ }
+
+ @Override
+ public void updateInventory(GeyserSession session, Inventory inventory) {
+
+ }
+
+ @Override
+ public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
+ this.updater.updateSlot(this, session, inventory, slot);
+ if (slot == 0) {
+ LecternContainer lecternContainer = (LecternContainer) inventory;
+ if (session.isDroppingLecternBook()) {
+ // We have to enter the inventory GUI to eject the book
+ ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), 3);
+ session.sendDownstreamPacket(packet);
+ session.setDroppingLecternBook(false);
+ InventoryUtils.closeInventory(session, inventory.getId());
+ } else if (lecternContainer.getBlockEntityTag() == null) {
+ // If the method returns true, this is already handled for us
+ GeyserItemStack geyserItemStack = inventory.getItem(0);
+ CompoundTag tag = geyserItemStack.getNbt();
+ if (tag != null) {
+ // Position has to be the last interacted position... right?
+ Vector3i position = session.getLastInteractionPosition();
+ // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet
+ boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position);
+ int pagesSize = ((ListTag) tag.get("pages")).size();
+ ItemData itemData = geyserItemStack.getItemData(session);
+ NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize);
+ lecternTag.putCompound("book", NbtMap.builder()
+ .putByte("Count", (byte) itemData.getCount())
+ .putShort("Damage", (short) 0)
+ .putString("Name", "minecraft:written_book")
+ .putCompound("tag", itemData.getTag())
+ .build());
+ lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage());
+ NbtMap blockEntityTag = lecternTag.build();
+ // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild
+ // the block entity tag
+ lecternContainer.setBlockEntityTag(blockEntityTag);
+ lecternContainer.setPosition(position);
+ if (shouldRefresh) {
+ // Update the lectern because it's not updated client-side
+ BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position);
+ session.getLecternCache().add(position);
+ // Close the window - we will reopen it once the client has this data synced
+ ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId());
+ session.sendDownstreamPacket(closeWindowPacket);
+ InventoryUtils.closeInventory(session, inventory.getId());
+ session.getConnector().getLogger().warning("Closing inventory");
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ return new LecternContainer(name, windowId, this.size, playerInventory);
+ }
+
+ public static NbtMapBuilder getBaseLecternTag(int x, int y, int z, int totalPages) {
+ NbtMapBuilder builder = NbtMap.builder()
+ .putInt("x", x)
+ .putInt("y", y)
+ .putInt("z", z)
+ .putString("id", "Lectern");
+ if (totalPages != 0) {
+ builder.putByte("hasBook", (byte) 1);
+ builder.putInt("totalPages", totalPages);
+ } else {
+ // Not usually needed, but helps with kicking out Bedrock players from reading the UI
+ builder.putByte("hasBook", (byte) 0);
+ }
+ return builder;
+ }
+
+ private static class LecternInventoryUpdater extends InventoryUpdater {
+
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java
new file mode 100644
index 00000000..bac9e7ae
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
+import com.github.steveice10.opennbt.tag.builtin.ListTag;
+import com.nukkitx.nbt.NbtMap;
+import com.nukkitx.nbt.NbtType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
+import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
+import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import org.geysermc.connector.inventory.GeyserItemStack;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+import org.geysermc.connector.network.translators.item.translators.BannerTranslator;
+
+import java.util.Collections;
+import java.util.List;
+
+public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
+ /**
+ * A map of Bedrock patterns to Java index. Used to request for a specific banner pattern.
+ */
+ private static final Object2IntMap PATTERN_TO_INDEX = new Object2IntOpenHashMap<>();
+
+ static {
+ // Added from left-to-right then up-to-down in the order Java presents it
+ int index = 1;
+ PATTERN_TO_INDEX.put("bl", index++);
+ PATTERN_TO_INDEX.put("br", index++);
+ PATTERN_TO_INDEX.put("tl", index++);
+ PATTERN_TO_INDEX.put("tr", index++);
+ PATTERN_TO_INDEX.put("bs", index++);
+ PATTERN_TO_INDEX.put("ts", index++);
+ PATTERN_TO_INDEX.put("ls", index++);
+ PATTERN_TO_INDEX.put("rs", index++);
+ PATTERN_TO_INDEX.put("cs", index++);
+ PATTERN_TO_INDEX.put("ms", index++);
+ PATTERN_TO_INDEX.put("drs", index++);
+ PATTERN_TO_INDEX.put("dls", index++);
+ PATTERN_TO_INDEX.put("ss", index++);
+ PATTERN_TO_INDEX.put("cr", index++);
+ PATTERN_TO_INDEX.put("sc", index++);
+ PATTERN_TO_INDEX.put("bt", index++);
+ PATTERN_TO_INDEX.put("tt", index++);
+ PATTERN_TO_INDEX.put("bts", index++);
+ PATTERN_TO_INDEX.put("tts", index++);
+ PATTERN_TO_INDEX.put("ld", index++);
+ PATTERN_TO_INDEX.put("rd", index++);
+ PATTERN_TO_INDEX.put("lud", index++);
+ PATTERN_TO_INDEX.put("rud", index++);
+ PATTERN_TO_INDEX.put("mc", index++);
+ PATTERN_TO_INDEX.put("mr", index++);
+ PATTERN_TO_INDEX.put("vh", index++);
+ PATTERN_TO_INDEX.put("hh", index++);
+ PATTERN_TO_INDEX.put("vhr", index++);
+ PATTERN_TO_INDEX.put("hhb", index++);
+ PATTERN_TO_INDEX.put("bo", index++);
+ index++; // Bordure indented, does not appear to exist in Bedrock?
+ PATTERN_TO_INDEX.put("gra", index++);
+ PATTERN_TO_INDEX.put("gru", index);
+ // Bricks do not appear to be a pattern on Bedrock, either
+ }
+
+ public LoomInventoryTranslator() {
+ super(4, "minecraft:loom[facing=north]", ContainerType.LOOM, UIInventoryUpdater.INSTANCE);
+ }
+
+ @Override
+ public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) {
+ if (javaDestinationSlot != 1) {
+ return false;
+ }
+ GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
+ if (itemStack.isEmpty()) {
+ return false;
+ }
+
+ // Reject the item if Bedrock is attempting to put in a dye that is not a dye in Java Edition
+ return !itemStack.getItemEntry().getJavaIdentifier().endsWith("_dye");
+ }
+
+ @Override
+ public 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
+ return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED && inventory.getItem(2).isEmpty();
+ }
+
+ @Override
+ public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
+ // TODO: I anticipate this will be changed in the future to use something non-deprecated. Keep an eye out.
+ StackRequestActionData data = request.getActions()[1];
+ if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) {
+ return rejectRequest(request);
+ }
+ CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data;
+ // Get the patterns compound tag
+ List newBlockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND);
+ // Get the pattern that the Bedrock client requests - the last pattern in the Patterns list
+ NbtMap pattern = newBlockEntityTag.get(newBlockEntityTag.size() - 1);
+ // Get the Java index of this pattern
+ int index = PATTERN_TO_INDEX.getOrDefault(pattern.getString("Pattern"), -1);
+ if (index == -1) {
+ return rejectRequest(request);
+ }
+ // Java's formula: 4 * row + col
+ // And the Java loom window has a fixed row/width of four
+ // So... Number / 4 = row (so we don't have to bother there), and number % 4 is our column, which leads us back to our index. :)
+ ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index);
+ System.out.println(packet);
+ session.sendDownstreamPacket(packet);
+
+ GeyserItemStack inputCopy = inventory.getItem(0).copy();
+ inputCopy.setNetId(session.getNextItemNetId());
+ // Add the pattern manually, for better item synchronization
+ if (inputCopy.getNbt() == null) {
+ inputCopy.setNbt(new CompoundTag(""));
+ }
+ CompoundTag blockEntityTag = inputCopy.getNbt().get("BlockEntityTag");
+ CompoundTag javaBannerPattern = BannerTranslator.getJavaBannerPattern(pattern);
+ if (blockEntityTag != null) {
+ ListTag patternsList = blockEntityTag.get("Patterns");
+ if (patternsList != null) {
+ patternsList.add(javaBannerPattern);
+ } else {
+ patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern));
+ blockEntityTag.put(patternsList);
+ }
+ } else {
+ blockEntityTag = new CompoundTag("BlockEntityTag");
+ ListTag patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern));
+ blockEntityTag.put(patternsList);
+ inputCopy.getNbt().put(blockEntityTag);
+ }
+ // Set the new item as the output
+ inventory.setItem(3, inputCopy, session);
+
+ return translateRequest(session, inventory, request);
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.LOOM_INPUT) {
+ return 0;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.LOOM_DYE) {
+ return 1;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.LOOM_MATERIAL) {
+ return 2;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.LOOM_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
+ return 3;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ switch (slot) {
+ case 0:
+ return new BedrockContainerSlot(ContainerSlotType.LOOM_INPUT, 9);
+ case 1:
+ return new BedrockContainerSlot(ContainerSlotType.LOOM_DYE, 10);
+ case 2:
+ return new BedrockContainerSlot(ContainerSlotType.LOOM_MATERIAL, 11);
+ case 3:
+ return new BedrockContainerSlot(ContainerSlotType.LOOM_RESULT, 50);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ switch (slot) {
+ case 0:
+ return 9;
+ case 1:
+ return 10;
+ case 2:
+ return 11;
+ case 3:
+ return 50;
+ }
+ return super.javaSlotToBedrock(slot);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java
new file mode 100644
index 00000000..7422c7e4
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.nukkitx.math.vector.Vector3f;
+import com.nukkitx.protocol.bedrock.data.entity.EntityData;
+import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;
+import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
+import com.nukkitx.protocol.bedrock.data.inventory.*;
+import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
+import org.geysermc.connector.entity.Entity;
+import org.geysermc.connector.entity.type.EntityType;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.MerchantContainer;
+import org.geysermc.connector.inventory.PlayerInventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.SlotType;
+import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+
+public class MerchantInventoryTranslator extends BaseInventoryTranslator {
+ private final InventoryUpdater updater;
+
+ public MerchantInventoryTranslator() {
+ super(3);
+ this.updater = UIInventoryUpdater.INSTANCE;
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ switch (slot) {
+ case 0:
+ return 4;
+ case 1:
+ return 5;
+ case 2:
+ return 50;
+ }
+ return super.javaSlotToBedrock(slot);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ switch (slot) {
+ case 0:
+ return new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT1, 4);
+ case 1:
+ return new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT2, 5);
+ case 2:
+ return new BedrockContainerSlot(ContainerSlotType.TRADE2_RESULT, 50);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ switch (slotInfoData.getContainer()) {
+ case TRADE2_INGREDIENT1:
+ return 0;
+ case TRADE2_INGREDIENT2:
+ return 1;
+ case TRADE2_RESULT:
+ case CREATIVE_OUTPUT:
+ return 2;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public SlotType getSlotType(int javaSlot) {
+ if (javaSlot == 2) {
+ return SlotType.OUTPUT;
+ }
+ return SlotType.NORMAL;
+ }
+
+ @Override
+ public void prepareInventory(GeyserSession session, Inventory inventory) {
+ MerchantContainer merchantInventory = (MerchantContainer) inventory;
+ if (merchantInventory.getVillager() == null) {
+ long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet();
+ Vector3f pos = session.getPlayerEntity().getPosition().sub(0, 3, 0);
+
+ EntityDataMap metadata = new EntityDataMap();
+ metadata.put(EntityData.SCALE, 0f);
+ metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0f);
+ metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0f);
+
+ Entity villager = new Entity(0, geyserId, EntityType.VILLAGER, pos, Vector3f.ZERO, Vector3f.ZERO);
+ villager.setMetadata(metadata);
+ villager.spawnEntity(session);
+
+ SetEntityLinkPacket linkPacket = new SetEntityLinkPacket();
+ EntityLinkData.Type type = EntityLinkData.Type.PASSENGER;
+ linkPacket.setEntityLink(new EntityLinkData(session.getPlayerEntity().getGeyserId(), geyserId, type, true, false));
+ session.sendUpstreamPacket(linkPacket);
+
+ merchantInventory.setVillager(villager);
+ }
+ }
+
+ @Override
+ public void openInventory(GeyserSession session, Inventory inventory) {
+ //Handled in JavaTradeListTranslator
+ }
+
+ @Override
+ public void closeInventory(GeyserSession session, Inventory inventory) {
+ MerchantContainer merchantInventory = (MerchantContainer) inventory;
+ if (merchantInventory.getVillager() != null) {
+ merchantInventory.getVillager().despawnEntity(session);
+ }
+ }
+
+ @Override
+ public void updateInventory(GeyserSession session, Inventory inventory) {
+ updater.updateInventory(this, session, inventory);
+ }
+
+ @Override
+ public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
+ updater.updateSlot(this, session, inventory, slot);
+ }
+
+ @Override
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ return new MerchantContainer(name, windowId, this.size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java
similarity index 61%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java
index 0ff20772..51ff0f5d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java
@@ -23,20 +23,20 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators;
-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.packet.ingame.client.window.ClientCreativeInventoryActionPacket;
-import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
-import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
-import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.nukkitx.protocol.bedrock.data.inventory.*;
import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
+import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LanguageUtils;
@@ -61,11 +61,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
ItemData[] contents = new ItemData[36];
// Inventory
for (int i = 9; i < 36; i++) {
- contents[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
+ contents[i] = inventory.getItem(i).getItemData(session);
}
// Hotbar
for (int i = 36; i < 45; i++) {
- contents[i - 36] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
+ contents[i - 36] = inventory.getItem(i).getItemData(session);
}
inventoryContentPacket.setContents(Arrays.asList(contents));
session.sendUpstreamPacket(inventoryContentPacket);
@@ -75,7 +75,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
armorContentPacket.setContainerId(ContainerId.ARMOR);
contents = new ItemData[4];
for (int i = 5; i < 9; i++) {
- contents[i - 5] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
+ contents[i - 5] = inventory.getItem(i).getItemData(session);
}
armorContentPacket.setContents(Arrays.asList(contents));
session.sendUpstreamPacket(armorContentPacket);
@@ -83,7 +83,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
// Offhand
InventoryContentPacket offhandPacket = new InventoryContentPacket();
offhandPacket.setContainerId(ContainerId.OFFHAND);
- offhandPacket.setContents(Collections.singletonList(ItemTranslator.translateToBedrock(session, inventory.getItem(45))));
+ offhandPacket.setContents(Collections.singletonList(inventory.getItem(45).getItemData(session)));
session.sendUpstreamPacket(offhandPacket);
}
@@ -102,7 +102,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
if (session.getGameMode() == GameMode.CREATIVE) {
slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK);
}else{
- slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i)));
+ slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i).getItemStack()));
}
session.sendUpstreamPacket(slotPacket);
@@ -127,21 +127,23 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
slotPacket.setContainerId(ContainerId.UI);
slotPacket.setSlot(slot + 27);
}
- slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(slot)));
+ slotPacket.setItem(inventory.getItem(slot).getItemData(session));
session.sendUpstreamPacket(slotPacket);
} else if (slot == 45) {
InventoryContentPacket offhandPacket = new InventoryContentPacket();
offhandPacket.setContainerId(ContainerId.OFFHAND);
- offhandPacket.setContents(Collections.singletonList(ItemTranslator.translateToBedrock(session, inventory.getItem(slot))));
+ offhandPacket.setContents(Collections.singletonList(inventory.getItem(slot).getItemData(session)));
session.sendUpstreamPacket(offhandPacket);
}
}
@Override
- public int bedrockSlotToJava(InventoryActionData action) {
- int slotnum = action.getSlot();
- switch (action.getSource().getContainerId()) {
- case ContainerId.INVENTORY:
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ int slotnum = slotInfoData.getSlot();
+ switch (slotInfoData.getContainer()) {
+ case HOTBAR_AND_INVENTORY:
+ case HOTBAR:
+ case INVENTORY:
// Inventory
if (slotnum >= 9 && slotnum <= 35) {
return slotnum;
@@ -151,19 +153,19 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
return slotnum + 36;
}
break;
- case ContainerId.ARMOR:
+ case ARMOR:
if (slotnum >= 0 && slotnum <= 3) {
return slotnum + 5;
}
break;
- case ContainerId.OFFHAND:
+ case OFFHAND:
return 45;
- case ContainerId.UI:
+ case CRAFTING_INPUT:
if (slotnum >= 28 && 31 >= slotnum) {
return slotnum - 27;
}
break;
- case ContainerId.CRAFTING_RESULT:
+ case CREATIVE_OUTPUT:
return 0;
}
return slotnum;
@@ -171,7 +173,26 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
@Override
public int javaSlotToBedrock(int slot) {
- return slot;
+ return -1;
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot >= 36 && slot <= 44) {
+ return new BedrockContainerSlot(ContainerSlotType.HOTBAR, slot - 36);
+ } else if (slot >= 9 && slot <= 35) {
+ return new BedrockContainerSlot(ContainerSlotType.INVENTORY, slot);
+ } else if (slot >= 5 && slot <= 8) {
+ return new BedrockContainerSlot(ContainerSlotType.ARMOR, slot - 5);
+ } else if (slot == 45) {
+ return new BedrockContainerSlot(ContainerSlotType.OFFHAND, 1);
+ } else if (slot >= 1 && slot <= 4) {
+ return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 27);
+ } else if (slot == 0) {
+ return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0);
+ } else {
+ throw new IllegalArgumentException("Unknown bedrock slot");
+ }
}
@Override
@@ -182,52 +203,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
- public void translateActions(GeyserSession session, Inventory inventory, List actions) {
- if (session.getGameMode() == GameMode.CREATIVE) {
- //crafting grid is not visible in creative mode in java edition
- for (InventoryActionData action : actions) {
- if (action.getSource().getContainerId() == ContainerId.UI && (action.getSlot() >= 28 && 31 >= action.getSlot())) {
- updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- return;
- }
- }
+ public void translateRequests(GeyserSession session, Inventory inventory, List requests) {
+ super.translateRequests(session, inventory, requests);
+ }
- ItemStack javaItem;
- for (InventoryActionData action : actions) {
- switch (action.getSource().getContainerId()) {
- case ContainerId.INVENTORY:
- case ContainerId.ARMOR:
- case ContainerId.OFFHAND:
- int javaSlot = bedrockSlotToJava(action);
- if (action.getToItem().getId() == 0) {
- javaItem = new ItemStack(-1, 0, null);
- } else {
- javaItem = ItemTranslator.translateToJava(action.getToItem());
- }
- ClientCreativeInventoryActionPacket creativePacket = new ClientCreativeInventoryActionPacket(javaSlot, javaItem);
- session.sendDownstreamPacket(creativePacket);
- inventory.setItem(javaSlot, javaItem);
- break;
- case ContainerId.UI:
- if (action.getSlot() == 0) {
- session.getInventory().setCursor(ItemTranslator.translateToJava(action.getToItem()));
- }
- break;
- case ContainerId.NONE:
- if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION
- && action.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
- javaItem = ItemTranslator.translateToJava(action.getToItem());
- ClientCreativeInventoryActionPacket creativeDropPacket = new ClientCreativeInventoryActionPacket(-1, javaItem);
- session.sendDownstreamPacket(creativeDropPacket);
- }
- break;
- }
- }
- return;
- }
-
- InventoryActionDataTranslator.translate(this, session, inventory, actions);
+ @Override
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ throw new UnsupportedOperationException();
}
@Override
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java
new file mode 100644
index 00000000..43584df4
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
+
+public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator {
+ public ShulkerInventoryTranslator() {
+ super(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, ContainerInventoryUpdater.INSTANCE);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
+ if (javaSlot < this.size) {
+ return new BedrockContainerSlot(ContainerSlotType.SHULKER, javaSlot);
+ }
+ return super.javaSlotToBedrockContainer(javaSlot);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java
similarity index 53%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java
index 19c2522e..dabe5c51 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java
@@ -23,34 +23,44 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators;
-import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
-import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
-
-public class SmithingInventoryTranslator extends BlockInventoryTranslator {
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator {
public SmithingInventoryTranslator() {
- super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, new CursorInventoryUpdater());
+ super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
}
@Override
- public int bedrockSlotToJava(InventoryActionData action) {
- final int slot = super.bedrockSlotToJava(action);
- if (action.getSource().getContainerId() == ContainerId.UI) {
- switch (slot) {
- case 51:
- return 0;
- case 52:
- return 1;
- case 50:
- return 2;
- default:
- return slot;
- }
- } return slot;
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_INPUT) {
+ return 0;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_MATERIAL) {
+ return 1;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
+ return 2;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ switch (slot) {
+ case 0:
+ return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
+ case 1:
+ return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
+ case 2:
+ return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
+ }
+ return super.javaSlotToBedrockContainer(slot);
}
@Override
@@ -65,5 +75,4 @@ public class SmithingInventoryTranslator extends BlockInventoryTranslator {
}
return super.javaSlotToBedrock(slot);
}
-
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java
new file mode 100644
index 00000000..0168d0ef
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators;
+
+import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
+import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
+import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
+import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
+import it.unimi.dsi.fastutil.ints.IntList;
+import org.geysermc.connector.inventory.GeyserItemStack;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
+import org.geysermc.connector.inventory.StonecutterContainer;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.SlotType;
+import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
+import org.geysermc.connector.network.translators.item.ItemTranslator;
+
+public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator {
+ public StonecutterInventoryTranslator() {
+ super(2, "minecraft:stonecutter[facing=north]", ContainerType.STONECUTTER, UIInventoryUpdater.INSTANCE);
+ }
+
+ @Override
+ public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
+ return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED;
+ }
+
+ @Override
+ public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
+ // TODO: Also surely to change in the future
+ StackRequestActionData data = request.getActions()[1];
+ if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) {
+ return rejectRequest(request);
+ }
+ CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data;
+ StonecutterContainer container = (StonecutterContainer) inventory;
+ // Get the ID of the item we are cutting
+ int id = inventory.getItem(0).getJavaId();
+ // Look up all possible options of cutting from this ID
+ IntList results = session.getStonecutterRecipes().get(id);
+ if (results == null) {
+ return rejectRequest(request);
+ }
+ System.out.println(id + " " + results);
+ ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0]);
+ System.out.println(javaOutput);
+ int button = results.indexOf(javaOutput.getId());
+ // If we've already pressed the button with this item, no need to press it again!
+ if (container.getStonecutterButton() != button) {
+ // Getting the index of the item in the Java stonecutter list
+ ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), button);
+ System.out.println(packet.toString());
+ session.sendDownstreamPacket(packet);
+ container.setStonecutterButton(button);
+ if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
+ // We don't know there is an output here, so we tell ourselves that there is
+ inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
+ }
+ }
+ return translateRequest(session, inventory, request);
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.STONECUTTER_INPUT) {
+ return 0;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.STONECUTTER_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
+ return 1;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 0) {
+ return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_INPUT, 3);
+ }
+ if (slot == 1) {
+ return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_RESULT, 50);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ if (slot == 0) {
+ return 3;
+ }
+ if (slot == 1) {
+ return 50;
+ }
+ return super.javaSlotToBedrock(slot);
+ }
+
+ @Override
+ public SlotType getSlotType(int javaSlot) {
+ if (javaSlot == 1) {
+ return SlotType.OUTPUT;
+ }
+ return super.getSlotType(javaSlot);
+ }
+
+ @Override
+ public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
+ return new StonecutterContainer(name, windowId, this.size, playerInventory);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java
similarity index 74%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java
index 3bc587b1..89366e2c 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java
@@ -23,16 +23,15 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators.chest;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.translators.BaseInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
-import org.geysermc.connector.utils.InventoryUtils;
-
-import java.util.List;
public abstract class ChestInventoryTranslator extends BaseInventoryTranslator {
private final InventoryUpdater updater;
@@ -53,17 +52,10 @@ public abstract class ChestInventoryTranslator extends BaseInventoryTranslator {
}
@Override
- public void translateActions(GeyserSession session, Inventory inventory, List actions) {
- for (InventoryActionData action : actions) {
- if (action.getSource().getContainerId() == inventory.getId()) {
- if (action.getSlot() >= size) {
- updateInventory(session, inventory);
- InventoryUtils.updateCursor(session);
- return;
- }
- }
+ public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
+ if (javaSlot < this.size) {
+ return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot);
}
-
- super.translateActions(session, inventory, actions);
+ return super.javaSlotToBedrockContainer(javaSlot);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java
similarity index 98%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java
index 14ccf745..c27eacff 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java
@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators.chest;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.nukkitx.math.vector.Vector3i;
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java
similarity index 96%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java
index 462762d0..9fc3c016 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java
@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators.chest;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.inventory.Inventory;
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/AbstractFurnaceInventoryTranslator.java
similarity index 69%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/AbstractFurnaceInventoryTranslator.java
index c7bc6acf..dc9b00fd 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/AbstractFurnaceInventoryTranslator.java
@@ -23,18 +23,21 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators.furnace;
-import com.github.steveice10.mc.protocol.data.game.window.WindowType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+import org.geysermc.connector.network.translators.inventory.SlotType;
+import org.geysermc.connector.network.translators.inventory.translators.AbstractBlockInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
-public class FurnaceInventoryTranslator extends BlockInventoryTranslator {
- public FurnaceInventoryTranslator() {
- super(3, "minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE, new ContainerInventoryUpdater());
+public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockInventoryTranslator {
+ AbstractFurnaceInventoryTranslator(String javaBlockIdentifier, ContainerType containerType) {
+ super(3, javaBlockIdentifier, containerType, ContainerInventoryUpdater.INSTANCE);
}
@Override
@@ -50,9 +53,6 @@ public class FurnaceInventoryTranslator extends BlockInventoryTranslator {
break;
case 2:
dataPacket.setProperty(ContainerSetDataPacket.FURNACE_TICK_COUNT);
- if (inventory.getWindowType() == WindowType.BLAST_FURNACE || inventory.getWindowType() == WindowType.SMOKER) {
- value *= 2;
- }
break;
default:
return;
@@ -67,4 +67,15 @@ public class FurnaceInventoryTranslator extends BlockInventoryTranslator {
return SlotType.FURNACE_OUTPUT;
return SlotType.NORMAL;
}
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 1) {
+ return new BedrockContainerSlot(ContainerSlotType.FURNACE_FUEL, javaSlotToBedrock(slot));
+ }
+ if (slot == 2) {
+ return new BedrockContainerSlot(ContainerSlotType.FURNACE_OUTPUT, javaSlotToBedrock(slot));
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java
new file mode 100644
index 00000000..ed9a8a79
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators.furnace;
+
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+
+public class BlastFurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator {
+ public BlastFurnaceInventoryTranslator() {
+ super("minecraft:blast_furnace[facing=north,lit=false]", ContainerType.BLAST_FURNACE);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 0) {
+ return new BedrockContainerSlot(ContainerSlotType.BLAST_FURNACE_INGREDIENT, javaSlotToBedrock(slot));
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java
new file mode 100644
index 00000000..b41c9b03
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators.furnace;
+
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+
+public class FurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator {
+ public FurnaceInventoryTranslator() {
+ super("minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 0) {
+ return new BedrockContainerSlot(ContainerSlotType.FURNACE_INGREDIENT, javaSlotToBedrock(slot));
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java
new file mode 100644
index 00000000..2b9a78c7
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators.furnace;
+
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+
+public class SmokerInventoryTranslator extends AbstractFurnaceInventoryTranslator {
+ public SmokerInventoryTranslator() {
+ super("minecraft:smoker[facing=north,lit=false]", ContainerType.SMOKER);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 0) {
+ return new BedrockContainerSlot(ContainerSlotType.SMOKER_INGREDIENT, javaSlotToBedrock(slot));
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java
similarity index 51%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java
index aa36a8a8..6c6c9a0c 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java
@@ -23,68 +23,20 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.connector.network.translators.inventory;
+package org.geysermc.connector.network.translators.inventory.translators.horse;
-import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
-import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
+import org.geysermc.connector.network.translators.inventory.translators.BaseInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.updater.HorseInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
-import java.util.List;
-
-public class MerchantInventoryTranslator extends BaseInventoryTranslator {
-
+public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTranslator {
private final InventoryUpdater updater;
- public MerchantInventoryTranslator() {
- super(3);
- this.updater = new CursorInventoryUpdater();
- }
-
- @Override
- public int javaSlotToBedrock(int slot) {
- switch (slot) {
- case 0:
- return 4;
- case 1:
- return 5;
- case 2:
- return 50;
- }
- return super.javaSlotToBedrock(slot);
- }
-
- @Override
- public int bedrockSlotToJava(InventoryActionData action) {
- switch (action.getSource().getContainerId()) {
- case ContainerId.UI:
- switch (action.getSlot()) {
- case 4:
- return 0;
- case 5:
- return 1;
- case 50:
- return 2;
- }
- break;
- case -28: // Trading 1?
- return 0;
- case -29: // Trading 2?
- return 1;
- case -30: // Trading Output?
- return 2;
- }
- return super.bedrockSlotToJava(action);
- }
-
- @Override
- public SlotType getSlotType(int javaSlot) {
- if (javaSlot == 2) {
- return SlotType.OUTPUT;
- }
- return SlotType.NORMAL;
+ public AbstractHorseInventoryTranslator(int size) {
+ super(size);
+ this.updater = HorseInventoryUpdater.INSTANCE;
}
@Override
@@ -99,8 +51,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
- session.setLastInteractedVillagerEid(-1);
- session.setVillagerTrades(null);
+
}
@Override
@@ -112,13 +63,4 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
}
-
- @Override
- public void translateActions(GeyserSession session, Inventory inventory, List actions) {
- if (actions.stream().anyMatch(a -> a.getSource().getContainerId() == -31)) {
- return;
- }
-
- super.translateActions(session, inventory, actions);
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java
new file mode 100644
index 00000000..f74c2d36
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators.horse;
+
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+
+import java.util.Arrays;
+
+public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInventoryTranslator {
+ private final int chestSize;
+ private final int equipSlot;
+
+ /**
+ * @param size the total Java size of the inventory
+ * @param equipSlot the Java equipment slot. Java always has two slots - one for armor and one for saddle. Chested horses
+ * on Bedrock only acknowledge one slot.
+ */
+ public ChestedHorseInventoryTranslator(int size, int equipSlot) {
+ super(size);
+ this.chestSize = size - 2;
+ this.equipSlot = equipSlot;
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.HORSE_EQUIP) {
+ return this.equipSlot;
+ }
+ if (slotInfoData.getContainer() == ContainerSlotType.CONTAINER) {
+ return slotInfoData.getSlot() + 1;
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == this.equipSlot) {
+ return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, 0);
+ }
+ if (slot <= this.size - 1) { // Accommodate for the lack of one slot (saddle or armor)
+ return new BedrockContainerSlot(ContainerSlotType.CONTAINER, slot - 1);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+
+ @Override
+ public int javaSlotToBedrock(int slot) {
+ if (slot == 0 && this.equipSlot == 0) {
+ return 0;
+ }
+ if (slot <= this.size - 1) {
+ return slot - 1;
+ }
+ return super.javaSlotToBedrock(slot);
+ }
+
+ @Override
+ public void updateInventory(GeyserSession session, Inventory inventory) {
+ ItemData[] bedrockItems = new ItemData[36];
+ for (int i = 0; i < 36; i++) {
+ final int offset = i < 9 ? 27 : -9;
+ bedrockItems[i] = inventory.getItem(this.size + i + offset).getItemData(session);
+ }
+ InventoryContentPacket contentPacket = new InventoryContentPacket();
+ contentPacket.setContainerId(ContainerId.INVENTORY);
+ contentPacket.setContents(Arrays.asList(bedrockItems));
+ session.sendUpstreamPacket(contentPacket);
+
+ ItemData[] horseItems = new ItemData[chestSize + 1];
+ // Manually specify the first slot - Java always has two slots (armor and saddle) and one is invisible.
+ // Bedrock doesn't have this invisible slot.
+ horseItems[0] = inventory.getItem(this.equipSlot).getItemData(session);
+ for (int i = 1; i < horseItems.length; i++) {
+ horseItems[i] = inventory.getItem(i + 1).getItemData(session);
+ }
+
+ InventoryContentPacket horseContentsPacket = new InventoryContentPacket();
+ horseContentsPacket.setContainerId(inventory.getId());
+ horseContentsPacket.setContents(Arrays.asList(horseItems));
+ System.out.println(horseContentsPacket);
+ session.sendUpstreamPacket(horseContentsPacket);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java
new file mode 100644
index 00000000..bf13bd6d
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators.horse;
+
+public class DonkeyInventoryTranslator extends ChestedHorseInventoryTranslator {
+ public DonkeyInventoryTranslator(int size) {
+ super(size, 0);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java
new file mode 100644
index 00000000..09a8f5de
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators.horse;
+
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
+import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
+import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
+
+public class HorseInventoryTranslator extends AbstractHorseInventoryTranslator {
+ public HorseInventoryTranslator(int size) {
+ super(size);
+ }
+
+ @Override
+ public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
+ if (slotInfoData.getContainer() == ContainerSlotType.HORSE_EQUIP) {
+ return slotInfoData.getSlot();
+ }
+ return super.bedrockSlotToJava(slotInfoData);
+ }
+
+ @Override
+ public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
+ if (slot == 0 || slot == 1) {
+ return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, slot);
+ }
+ return super.javaSlotToBedrockContainer(slot);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java
new file mode 100644
index 00000000..cea605f8
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.translators.horse;
+
+public class LlamaInventoryTranslator extends ChestedHorseInventoryTranslator {
+ public LlamaInventoryTranslator(int size) {
+ super(size, 1);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java
index 73c1f2eb..f5029f74 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java
@@ -32,7 +32,6 @@ import lombok.AllArgsConstructor;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
-import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LanguageUtils;
@@ -52,7 +51,7 @@ public class ChestInventoryUpdater extends InventoryUpdater {
List bedrockItems = new ArrayList<>(paddedSize);
for (int i = 0; i < paddedSize; i++) {
if (i < translator.size) {
- bedrockItems.add(ItemTranslator.translateToBedrock(session, inventory.getItem(i)));
+ bedrockItems.add(inventory.getItem(i).getItemData(session));
} else {
bedrockItems.add(UNUSUABLE_SPACE_BLOCK);
}
@@ -72,7 +71,7 @@ public class ChestInventoryUpdater extends InventoryUpdater {
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(inventory.getId());
slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot));
- slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot)));
+ slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session));
session.sendUpstreamPacket(slotPacket);
return true;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java
index d7bdbde4..f77e687a 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java
@@ -31,18 +31,19 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
-import org.geysermc.connector.network.translators.item.ItemTranslator;
import java.util.Arrays;
public class ContainerInventoryUpdater extends InventoryUpdater {
+ public static final ContainerInventoryUpdater INSTANCE = new ContainerInventoryUpdater();
+
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
super.updateInventory(translator, session, inventory);
ItemData[] bedrockItems = new ItemData[translator.size];
for (int i = 0; i < bedrockItems.length; i++) {
- bedrockItems[translator.javaSlotToBedrock(i)] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
+ bedrockItems[translator.javaSlotToBedrock(i)] = inventory.getItem(i).getItemData(session);
}
InventoryContentPacket contentPacket = new InventoryContentPacket();
@@ -59,7 +60,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater {
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(inventory.getId());
slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot));
- slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot)));
+ slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session));
session.sendUpstreamPacket(slotPacket);
return true;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java
new file mode 100644
index 00000000..838e59d7
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.inventory.updater;
+
+import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
+import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
+import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
+import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+
+import java.util.Arrays;
+
+public class HorseInventoryUpdater extends InventoryUpdater {
+ public static final HorseInventoryUpdater INSTANCE = new HorseInventoryUpdater();
+
+ @Override
+ public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
+ super.updateInventory(translator, session, inventory);
+
+ ItemData[] bedrockItems = new ItemData[translator.size];
+ for (int i = 0; i < bedrockItems.length; i++) {
+ bedrockItems[translator.javaSlotToBedrock(i)] = inventory.getItem(i).getItemData(session);
+ }
+
+ InventoryContentPacket contentPacket = new InventoryContentPacket();
+ contentPacket.setContainerId(inventory.getId());
+ contentPacket.setContents(Arrays.asList(bedrockItems));
+ session.sendUpstreamPacket(contentPacket);
+ }
+
+ @Override
+ public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
+ if (super.updateSlot(translator, session, inventory, javaSlot))
+ return true;
+
+ InventorySlotPacket slotPacket = new InventorySlotPacket();
+ slotPacket.setContainerId(4); // Horse GUI?
+ slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot));
+ slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session));
+ session.sendUpstreamPacket(slotPacket);
+ System.out.println(slotPacket);
+ return true;
+ }
+
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java
index 020f7467..d7c13717 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java
@@ -32,7 +32,6 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
-import org.geysermc.connector.network.translators.item.ItemTranslator;
import java.util.Arrays;
@@ -41,7 +40,7 @@ public abstract class InventoryUpdater {
ItemData[] bedrockItems = new ItemData[36];
for (int i = 0; i < 36; i++) {
final int offset = i < 9 ? 27 : -9;
- bedrockItems[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.size + i + offset));
+ bedrockItems[i] = inventory.getItem(translator.size + i + offset).getItemData(session);
}
InventoryContentPacket contentPacket = new InventoryContentPacket();
contentPacket.setContainerId(ContainerId.INVENTORY);
@@ -54,7 +53,7 @@ public abstract class InventoryUpdater {
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.INVENTORY);
slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot));
- slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot)));
+ slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session));
session.sendUpstreamPacket(slotPacket);
return true;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java
similarity index 87%
rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java
index 89abdd84..5bb8ad5d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java
@@ -30,11 +30,10 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
-import org.geysermc.connector.network.translators.item.ItemTranslator;
-public class CursorInventoryUpdater extends InventoryUpdater {
+public class UIInventoryUpdater extends InventoryUpdater {
+ public static final UIInventoryUpdater INSTANCE = new UIInventoryUpdater();
- //TODO: Consider renaming this? Since the Protocol enum updated
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
super.updateInventory(translator, session, inventory);
@@ -46,7 +45,7 @@ public class CursorInventoryUpdater extends InventoryUpdater {
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.UI);
slotPacket.setSlot(bedrockSlot);
- slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i)));
+ slotPacket.setItem(inventory.getItem(i).getItemData(session));
session.sendUpstreamPacket(slotPacket);
}
}
@@ -59,7 +58,7 @@ public class CursorInventoryUpdater extends InventoryUpdater {
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.UI);
slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot));
- slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot)));
+ slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session));
session.sendUpstreamPacket(slotPacket);
return true;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java
index f61c3d70..278d708f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java
@@ -34,7 +34,7 @@ import lombok.ToString;
@ToString
public class ItemEntry {
- public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, false);
+ public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, false, 64);
private final String javaIdentifier;
private final String bedrockIdentifier;
@@ -43,6 +43,7 @@ public class ItemEntry {
private final int bedrockData;
private final boolean block;
+ private final int stackSize;
@Override
public boolean equals(Object obj) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java
index e9b82158..4237f7d6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java
@@ -56,7 +56,7 @@ public class ItemRegistry {
* A list of all identifiers that only exist on Java. Used to prevent creative items from becoming these unintentionally.
*/
private static final List JAVA_ONLY_ITEMS = Arrays.asList("minecraft:spectral_arrow", "minecraft:debug_stick",
- "minecraft:knowledge_book");
+ "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:furnace_minecart");
public static final ItemData[] CREATIVE_ITEMS;
@@ -95,10 +95,6 @@ public class ItemRegistry {
* Wheat item entry, used in AbstractHorseEntity.java
*/
public static ItemEntry WHEAT;
- /**
- * Writable book item entry, used in BedrockBookEditTranslator.java
- */
- public static ItemEntry WRITABLE_BOOK;
public static int BARRIER_INDEX = 0;
@@ -151,6 +147,8 @@ public class ItemRegistry {
if (bedrockIdentifier == null) {
throw new RuntimeException("Missing Bedrock ID in mappings!: " + bedrockId);
}
+ JsonNode stackSizeNode = entry.getValue().get("stack_size");
+ int stackSize = stackSizeNode == null ? 64 : stackSizeNode.intValue();
if (entry.getValue().has("tool_type")) {
if (entry.getValue().has("tool_tier")) {
ITEM_ENTRIES.put(itemIndex, new ToolItemEntry(
@@ -158,19 +156,22 @@ public class ItemRegistry {
entry.getValue().get("bedrock_data").intValue(),
entry.getValue().get("tool_type").textValue(),
entry.getValue().get("tool_tier").textValue(),
- entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue()));
+ entry.getValue().get("is_block").booleanValue(),
+ stackSize));
} else {
ITEM_ENTRIES.put(itemIndex, new ToolItemEntry(
entry.getKey(), bedrockIdentifier, itemIndex, bedrockId,
entry.getValue().get("bedrock_data").intValue(),
entry.getValue().get("tool_type").textValue(),
- "", entry.getValue().get("is_block").booleanValue()));
+ "", entry.getValue().get("is_block").booleanValue(),
+ stackSize));
}
} else {
ITEM_ENTRIES.put(itemIndex, new ItemEntry(
entry.getKey(), bedrockIdentifier, itemIndex, bedrockId,
entry.getValue().get("bedrock_data").intValue(),
- entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue()));
+ entry.getValue().get("is_block").booleanValue(),
+ stackSize));
}
switch (entry.getKey()) {
case "minecraft:barrier":
@@ -194,9 +195,6 @@ public class ItemRegistry {
case "minecraft:wheat":
WHEAT = ITEM_ENTRIES.get(itemIndex);
break;
- case "minecraft:writable_book":
- WRITABLE_BOOK = ITEM_ENTRIES.get(itemIndex);
- break;
default:
break;
}
@@ -216,7 +214,7 @@ public class ItemRegistry {
// Add the loadstone compass since it doesn't exist on java but we need it for item conversion
ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", "minecraft:lodestone_compass", itemIndex,
- lodestoneCompassId, 0, false));
+ lodestoneCompassId, 0, false, 1));
/* Load creative items */
stream = FileUtils.getResource("bedrock/creative_items.json");
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java
index 7e307281..110014bf 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java
@@ -26,13 +26,25 @@
package org.geysermc.connector.network.translators.item;
import com.fasterxml.jackson.databind.JsonNode;
+import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
+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.nukkitx.nbt.NbtMap;
+import com.nukkitx.nbt.NbtUtils;
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.util.*;
@@ -47,6 +59,12 @@ public class RecipeRegistry {
*/
public static int LAST_RECIPE_NET_ID = 0;
+ /**
+ * A list of all the following crafting recipes, but in a format understood by Java servers.
+ * Used for console autocrafting.
+ */
+ public static final Int2ObjectMap ALL_CRAFTING_RECIPES = new Int2ObjectOpenHashMap<>();
+
/**
* A list of all possible leather armor dyeing recipes.
* Created manually.
@@ -78,6 +96,12 @@ public class RecipeRegistry {
*/
public static final List TIPPED_ARROW_RECIPES = new ObjectArrayList<>();
+ /**
+ * Recipe data that, when sent to the client, enables cartography features.
+ * This does not have a Java equivalent.
+ */
+ public static final List CARTOGRAPHY_RECIPE_DATA = new ObjectArrayList<>();
+
/**
* Recipe data that, when sent to the client, enables book cloning
*/
@@ -106,6 +130,11 @@ public class RecipeRegistry {
MAP_EXTENDING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), LAST_RECIPE_NET_ID++);
MAP_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), LAST_RECIPE_NET_ID++);
BANNER_DUPLICATING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("b5c5d105-75a2-4076-af2b-923ea2bf4bf0"), LAST_RECIPE_NET_ID++);
+
+ CARTOGRAPHY_RECIPE_DATA.add(CraftingData.fromMulti(UUID.fromString("8b36268c-1829-483c-a0f1-993b7156a8f2"), LAST_RECIPE_NET_ID++)); // Map extending
+ CARTOGRAPHY_RECIPE_DATA.add(CraftingData.fromMulti(UUID.fromString("442d85ed-8272-4543-a6f1-418f90ded05d"), LAST_RECIPE_NET_ID++)); // Map cloning
+ CARTOGRAPHY_RECIPE_DATA.add(CraftingData.fromMulti(UUID.fromString("98c84b38-1085-46bd-b1ce-dd38c159e6cc"), LAST_RECIPE_NET_ID++)); // Map upgrading
+ CARTOGRAPHY_RECIPE_DATA.add(CraftingData.fromMulti(UUID.fromString("602234e4-cac1-4353-8bb7-b1ebff70024b"), LAST_RECIPE_NET_ID++)); // Map locking
// https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/inventory/MultiRecipe.php
// Get all recipes that are not directly sent from a Java server
@@ -118,7 +147,7 @@ public class RecipeRegistry {
throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e);
}
- for (JsonNode entry: items.get("leather_armor")) {
+ for (JsonNode entry : items.get("leather_armor")) {
// This won't be perfect, as we can't possibly send every leather input for every kind of color
// But it does display the correct output from a base leather armor, and besides visuals everything works fine
LEATHER_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry));
@@ -146,9 +175,13 @@ public class RecipeRegistry {
* @return the {@link CraftingData} to send to the Bedrock client.
*/
private static CraftingData getCraftingDataFromJsonNode(JsonNode node) {
- ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0));
+ int netId = LAST_RECIPE_NET_ID++;
+ int type = node.get("bedrockRecipeType").asInt();
+ JsonNode outputNode = node.get("output");
+ ItemEntry outputEntry = ItemRegistry.getItemEntry(outputNode.get("identifier").asText());
+ ItemData output = getBedrockItemFromIdentifierJson(outputEntry, outputNode);
UUID uuid = UUID.randomUUID();
- if (node.get("type").asInt() == 1) {
+ if (type == 1) {
// Shaped recipe
List shape = new ArrayList<>();
// Get the shape of the recipe
@@ -158,10 +191,12 @@ public class RecipeRegistry {
// In recipes.json each recipe is mapped by a letter
Map letterToRecipe = new HashMap<>();
- Iterator> iterator = node.get("input").fields();
+ Iterator> iterator = node.get("inputs").fields();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
- letterToRecipe.put(entry.getKey(), ItemRegistry.getBedrockItemFromJson(entry.getValue()));
+ JsonNode inputNode = entry.getValue();
+ ItemEntry inputEntry = ItemRegistry.getItemEntry(inputNode.get("identifier").asText());
+ letterToRecipe.put(entry.getKey(), getBedrockItemFromIdentifierJson(inputEntry, inputNode));
}
List inputs = new ArrayList<>(shape.size() * shape.get(0).length());
@@ -175,20 +210,69 @@ public class RecipeRegistry {
}
}
+ /* Convert into a Java recipe class for autocrafting */
+ List ingredients = new ArrayList<>();
+ for (ItemData input : inputs) {
+ ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input)}));
+ }
+ ShapedRecipeData data = new ShapedRecipeData(shape.get(0).length(), shape.size(), "crafting_table",
+ ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output));
+ Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPED, "", data);
+ ALL_CRAFTING_RECIPES.put(netId, recipe);
+ /* Convert end */
+
return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(),
- inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++);
+ inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId);
}
List inputs = new ObjectArrayList<>();
- for (JsonNode entry : node.get("input")) {
- inputs.add(ItemRegistry.getBedrockItemFromJson(entry));
+ for (JsonNode entry : node.get("inputs")) {
+ ItemEntry inputEntry = ItemRegistry.getItemEntry(entry.get("identifier").asText());
+ inputs.add(getBedrockItemFromIdentifierJson(inputEntry, entry));
}
- if (node.get("type").asInt() == 5) {
+
+ /* Convert into a Java Recipe class for autocrafting */
+ List ingredients = new ArrayList<>();
+ for (ItemData input : inputs) {
+ ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input)}));
+ }
+ ShapelessRecipeData data = new ShapelessRecipeData("crafting_table",
+ ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output));
+ Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPELESS, "", data);
+ ALL_CRAFTING_RECIPES.put(netId, recipe);
+ /* Convert end */
+
+ if (type == 5) {
// Shulker box
return CraftingData.fromShulkerBox(uuid.toString(),
- inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++);
+ inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId);
}
return CraftingData.fromShapeless(uuid.toString(),
- inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++);
+ inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId);
+ }
+
+ private static ItemData getBedrockItemFromIdentifierJson(ItemEntry itemEntry, JsonNode itemNode) {
+ int count = 1;
+ short damage = 0;
+ NbtMap tag = null;
+ JsonNode damageNode = itemNode.get("bedrockDamage");
+ if (damageNode != null) {
+ damage = damageNode.numberValue().shortValue();
+ }
+ JsonNode countNode = itemNode.get("count");
+ if (countNode != null) {
+ count = countNode.asInt();
+ }
+ JsonNode nbtNode = itemNode.get("bedrockNbt");
+ if (nbtNode != null) {
+ byte[] bytes = Base64.getDecoder().decode(nbtNode.asText());
+ ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+ try {
+ tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return ItemData.of(itemEntry.getBedrockId(), damage, count, tag);
}
public static void init() {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java
index 5352938c..ba1753a3 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java
@@ -32,8 +32,8 @@ public class ToolItemEntry extends ItemEntry {
private final String toolType;
private final String toolTier;
- public ToolItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) {
- super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, isBlock);
+ public ToolItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock, int stackSize) {
+ super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, isBlock, stackSize);
this.toolType = toolType;
this.toolTier = toolTier;
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java
index 25bfe3d2..a96c47f6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java
@@ -195,13 +195,14 @@ public class BannerTranslator extends ItemTranslator {
blockEntityTag.put(OMINOUS_BANNER_PATTERN);
itemStack.getNbt().put(blockEntityTag);
- } else if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) {
+ } else if (nbtTag.containsKey("Patterns", NbtType.LIST)) {
List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND);
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag");
blockEntityTag.put(convertBannerPattern(patterns));
itemStack.getNbt().put(blockEntityTag);
+ itemStack.getNbt().remove("Patterns"); // Remove the old Bedrock patterns list
}
return itemStack;
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
index 90eef3bc..cf97f643 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java
@@ -78,8 +78,9 @@ public class BookPagesTranslator extends NbtItemStackTranslator {
CompoundTag pageTag = (CompoundTag) tag;
StringTag textTag = pageTag.get("text");
- pages.add(new StringTag("", textTag.getValue()));
+ pages.add(new StringTag(MessageTranslator.convertToJavaMessage(textTag.getValue())));
}
+
itemTag.remove("pages");
itemTag.put(new ListTag("pages", pages));
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTranslator.java
deleted file mode 100644
index 714578e9..00000000
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTranslator.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.network.translators.java;
-
-import com.github.steveice10.mc.protocol.data.game.advancement.Advancement;
-import com.github.steveice10.mc.protocol.packet.ingame.server.ServerAdvancementsPacket;
-import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
-import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.PacketTranslator;
-import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.network.translators.chat.MessageTranslator;
-import org.geysermc.connector.network.session.cache.AdvancementsCache;
-import org.geysermc.connector.utils.GeyserAdvancement;
-import org.geysermc.connector.utils.LocaleUtils;
-
-import java.util.Map;
-
-@Translator(packet = ServerAdvancementsPacket.class)
-public class JavaAdvancementsTranslator extends PacketTranslator {
-
- @Override
- public void translate(ServerAdvancementsPacket packet, GeyserSession session) {
- AdvancementsCache advancementsCache = session.getAdvancementsCache();
- if (packet.isReset()) {
- advancementsCache.getStoredAdvancements().clear();
- advancementsCache.getStoredAdvancementProgress().clear();
- }
-
- // Removes removed advancements from player's stored advancements
- for (String removedAdvancement : packet.getRemovedAdvancements()) {
- advancementsCache.getStoredAdvancements().remove(removedAdvancement);
- }
-
- advancementsCache.getStoredAdvancementProgress().putAll(packet.getProgress());
-
- sendToolbarAdvancementUpdates(session, packet);
-
- // Adds advancements to the player's stored advancements when advancements are sent
- for (Advancement advancement : packet.getAdvancements()) {
- if (advancement.getDisplayData() != null && !advancement.getDisplayData().isHidden()) {
- GeyserAdvancement geyserAdvancement = GeyserAdvancement.from(advancement);
- advancementsCache.getStoredAdvancements().put(advancement.getId(), geyserAdvancement);
- } else {
- advancementsCache.getStoredAdvancements().remove(advancement.getId());
- }
- }
- }
-
- /**
- * Handle all advancements progress updates
- */
- public void sendToolbarAdvancementUpdates(GeyserSession session, ServerAdvancementsPacket packet) {
- if (packet.isReset()) {
- // Advancements are being cleared, so they can't be granted
- return;
- }
- for (Map.Entry> progress : packet.getProgress().entrySet()) {
- GeyserAdvancement advancement = session.getAdvancementsCache().getStoredAdvancements().get(progress.getKey());
- if (advancement != null && advancement.getDisplayData() != null) {
- if (session.getAdvancementsCache().isEarned(advancement)) {
- // Java uses some pink color for toast challenge completes
- String color = advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE ?
- "§d" : "§a";
- String advancementName = MessageTranslator.convertMessage(advancement.getDisplayData().getTitle(), session.getLocale());
-
- // Send an action bar message stating they earned an achievement
- // Sent for instances where broadcasting advancements through chat are disabled
- SetTitlePacket titlePacket = new SetTitlePacket();
- titlePacket.setText(color + "[" + LocaleUtils.getLocaleString("advancements.toast." +
- advancement.getDisplayData().getFrameType().toString().toLowerCase(), session.getLocale()) + "]§f " + advancementName);
- titlePacket.setType(SetTitlePacket.Type.ACTIONBAR);
- titlePacket.setFadeOutTime(3);
- titlePacket.setFadeInTime(3);
- titlePacket.setStayTime(3);
- session.sendUpstreamPacket(titlePacket);
- }
- }
- }
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java
index 33ebc7ea..3b4f14d7 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java
@@ -25,17 +25,18 @@
package org.geysermc.connector.network.translators.java;
+import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
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.recipe.data.StoneCuttingRecipeData;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareRecipesPacket;
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 it.unimi.dsi.fastutil.ints.IntOpenHashSet;
-import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.ints.*;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import org.geysermc.connector.network.session.GeyserSession;
@@ -46,13 +47,20 @@ import org.geysermc.connector.network.translators.item.*;
import java.util.*;
import java.util.stream.Collectors;
+/**
+ * Used to send all valid recipes from Java to Bedrock.
+ *
+ * Bedrock REQUIRES a CraftingDataPacket to be sent in order to craft anything.
+ */
@Translator(packet = ServerDeclareRecipesPacket.class)
public class JavaDeclareRecipesTranslator extends PacketTranslator {
@Override
public void translate(ServerDeclareRecipesPacket packet, GeyserSession session) {
// Get the last known network ID (first used for the pregenerated recipes) and increment from there.
- int networkId = RecipeRegistry.LAST_RECIPE_NET_ID;
+ int netId = RecipeRegistry.LAST_RECIPE_NET_ID + 1;
+ Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(RecipeRegistry.ALL_CRAFTING_RECIPES);
+ Int2ObjectMap> unsortedStonecutterData = new Int2ObjectOpenHashMap<>();
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
craftingDataPacket.setCleanRecipes(true);
for (Recipe recipe : packet.getRecipes()) {
@@ -65,7 +73,8 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator data = unsortedStonecutterData.get(ingredient.getId());
+ if (data == null) {
+ data = new ArrayList<>();
+ unsortedStonecutterData.put(ingredient.getId(), data);
+ }
+ data.add(stoneCuttingData);
+ // Save for processing after all recipes have been received
+ }
}
}
+ // Add all cartography table recipe UUIDs, so we can use the cartography table
+ craftingDataPacket.getCraftingData().addAll(RecipeRegistry.CARTOGRAPHY_RECIPE_DATA);
+
craftingDataPacket.getPotionMixData().addAll(PotionMixRegistry.POTION_MIXES);
+
+ Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>();
+ for (Int2ObjectMap.Entry> data : unsortedStonecutterData.int2ObjectEntrySet()) {
+ data.getValue().sort(Comparator.comparing((stoneCuttingRecipeData ->
+ // Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore
+ // We can get the correct order for button pressing
+ ItemRegistry.getItem(stoneCuttingRecipeData.getResult()).getJavaIdentifier())));
+ // Now that it's sorted, let's translate these recipes
+ for (StoneCuttingRecipeData stoneCuttingData : data.getValue()) {
+ // As of 1.16.4, all stonecutter recipes have one ingredient option
+ ItemStack ingredient = stoneCuttingData.getIngredient().getOptions()[0];
+ ItemData input = ItemTranslator.translateToBedrock(session, ingredient);
+ ItemData output = ItemTranslator.translateToBedrock(session, stoneCuttingData.getResult());
+ UUID uuid = UUID.randomUUID();
+ // We need to register stonecutting recipes so they show up on Bedrock
+ craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(),
+ Collections.singletonList(input), Collections.singletonList(output), uuid, "stonecutter", 0, netId++));
+ // Save the recipe list for reference when crafting
+ IntList outputs = stonecutterRecipeMap.get(ingredient.getId());
+ if (outputs == null) {
+ outputs = new IntArrayList();
+ // Add the ingredient as the key and all possible values as the value
+ stonecutterRecipeMap.put(ingredient.getId(), outputs);
+ }
+ outputs.add(stoneCuttingData.getResult().getId());
+ }
+ }
+
session.sendUpstreamPacket(craftingDataPacket);
+ session.setCraftingRecipes(recipeMap);
+ session.getUnlockedRecipes().clear();
+ session.setStonecutterRecipes(stonecutterRecipeMap);
}
//TODO: rewrite
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java
index e9a1901d..292cfc74 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java
@@ -55,12 +55,12 @@ public class JavaJoinGameTranslator extends PacketTranslator
@Override
public void translate(ServerRespawnPacket packet, GeyserSession session) {
Entity entity = session.getPlayerEntity();
+ if (entity == null)
+ return;
float maxHealth = entity.getAttributes().containsKey(AttributeType.MAX_HEALTH) ? entity.getAttributes().get(AttributeType.MAX_HEALTH).getValue() : 20f;
// Max health must be divisible by two in bedrock
entity.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(maxHealth, (maxHealth % 2 == 1 ? maxHealth + 1 : maxHealth)));
- session.getInventoryCache().setOpenInventory(null);
+ session.setOpenInventory(null);
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
playerGameTypePacket.setGamemode(packet.getGamemode().ordinal());
@@ -64,24 +66,18 @@ public class JavaRespawnTranslator extends PacketTranslator
session.setRaining(false);
}
- if (session.isThunder()) {
- LevelEventPacket stopThunderPacket = new LevelEventPacket();
- stopThunderPacket.setType(LevelEventType.STOP_THUNDERSTORM);
- stopThunderPacket.setData(0);
- stopThunderPacket.setPosition(Vector3f.ZERO);
- session.sendUpstreamPacket(stopThunderPacket);
- session.setThunder(false);
- }
-
String newDimension = DimensionUtils.getNewDimension(packet.getDimension());
- if (!session.getDimension().equals(newDimension) || !packet.getWorldName().equals(session.getWorldName())) {
- if (!packet.getWorldName().equals(session.getWorldName()) && session.getDimension().equals(newDimension)) {
- // Switching to a new world (based off the world name change); send a fake dimension change
- String fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), newDimension);
- DimensionUtils.switchDimension(session, fakeDim);
- }
- session.setWorldName(packet.getWorldName());
+ if (!session.getDimension().equals(newDimension)) {
DimensionUtils.switchDimension(session, newDimension);
+ } else {
+ if (session.isManyDimPackets()) { //reloading world
+ String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD;
+ DimensionUtils.switchDimension(session, fakeDim);
+ DimensionUtils.switchDimension(session, newDimension);
+ } else {
+ // Handled in JavaPlayerPositionRotationTranslator
+ session.setSpawned(false);
+ }
}
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaUnlockRecipesTranslator.java
similarity index 68%
rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java
rename to connector/src/main/java/org/geysermc/connector/network/translators/java/JavaUnlockRecipesTranslator.java
index 17a3b379..0a0ba4d2 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaUnlockRecipesTranslator.java
@@ -25,21 +25,27 @@
package org.geysermc.connector.network.translators.java;
-import com.github.steveice10.mc.protocol.packet.ingame.server.ServerAdvancementTabPacket;
+import com.github.steveice10.mc.protocol.data.game.UnlockRecipesAction;
+import com.github.steveice10.mc.protocol.packet.ingame.server.ServerUnlockRecipesPacket;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.session.cache.AdvancementsCache;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
+import java.util.Arrays;
+
/**
- * Indicates that the client should open a particular advancement tab
+ * Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage)
*/
-@Translator(packet = ServerAdvancementTabPacket.class)
-public class JavaAdvancementsTabTranslator extends PacketTranslator {
+@Translator(packet = ServerUnlockRecipesPacket.class)
+public class JavaUnlockRecipesTranslator extends PacketTranslator {
@Override
- public void translate(ServerAdvancementTabPacket packet, GeyserSession session) {
- session.getAdvancementsCache().setCurrentAdvancementCategoryId(packet.getTabId());
- session.sendForm(session.getAdvancementsCache().buildListForm(), AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID);
+ public void translate(ServerUnlockRecipesPacket packet, GeyserSession session) {
+ if (packet.getAction() == UnlockRecipesAction.REMOVE) {
+ session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes()));
+ } else {
+ session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes()));
+ }
}
}
+
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java
index 59ea2992..10728264 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java
@@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
-import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
+import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
import org.geysermc.connector.entity.Entity;
@@ -183,19 +183,6 @@ public class JavaEntityStatusTranslator extends PacketTranslator {
+ PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket();
+ hotbarPacket.setContainerId(0);
+ hotbarPacket.setSelectedHotbarSlot(packet.getSlot());
+ hotbarPacket.setSelectHotbarSlot(true);
+ session.sendUpstreamPacket(hotbarPacket);
- session.getInventory().setHeldItemSlot(packet.getSlot());
+ session.getPlayerInventory().setHeldItemSlot(packet.getSlot());
+ });
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java
index b379443e..10d451df 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java
@@ -46,10 +46,12 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator {
+public class JavaPlayerStopSoundTranslator extends PacketTranslator {
@Override
public void translate(ServerStopSoundPacket packet, GeyserSession session) {
- // Runs if all sounds are stopped
- if (packet.getSound() == null) {
- StopSoundPacket stopPacket = new StopSoundPacket();
- stopPacket.setStoppingAllSound(true);
- stopPacket.setSoundName("");
- session.sendUpstreamPacket(stopPacket);
- return;
- }
-
String packetSound;
- if (packet.getSound() instanceof BuiltinSound) {
+ if(packet.getSound() instanceof BuiltinSound) {
packetSound = ((BuiltinSound) packet.getSound()).getName();
- } else if (packet.getSound() instanceof CustomSound) {
+ } else if(packet.getSound() instanceof CustomSound) {
packetSound = ((CustomSound) packet.getSound()).getName();
} else {
session.getConnector().getLogger().debug("Unknown sound packet, we were unable to map this. " + packet.toString());
return;
}
- SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound.replace("minecraft:", ""));
+ SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound);
session.getConnector().getLogger()
.debug("[StopSound] Sound mapping " + packetSound + " -> "
+ soundMapping + (soundMapping == null ? "[not found]" : "")
+ " - " + packet.toString());
String playsound;
- if (soundMapping == null || soundMapping.getPlaysound() == null) {
+ if(soundMapping == null || soundMapping.getPlaysound() == null) {
// no mapping
session.getConnector().getLogger()
.debug("[StopSound] Defaulting to sound server gave us.");
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java
index 770e73cc..b78dc32f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java
@@ -36,7 +36,6 @@ public class JavaCloseWindowTranslator extends PacketTranslator InventoryUtils.closeInventory(session, packet.getWindowId()));
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java
index cc0d153b..05347fd0 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java
@@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.java.window;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerConfirmTransactionPacket;
+import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@@ -36,9 +37,12 @@ public class JavaConfirmTransactionTranslator extends PacketTranslator {
+ if (!packet.isAccepted()) {
+ ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true);
+ session.sendDownstreamPacket(confirmPacket);
+ System.out.println(packet);
+ }
+ });
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java
new file mode 100644
index 00000000..e5748616
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.java.window;
+
+import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenHorseWindowPacket;
+import com.nukkitx.nbt.NbtMap;
+import com.nukkitx.nbt.NbtMapBuilder;
+import com.nukkitx.nbt.NbtType;
+import com.nukkitx.protocol.bedrock.data.entity.EntityData;
+import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
+import com.nukkitx.protocol.bedrock.packet.UpdateEquipPacket;
+import org.geysermc.connector.entity.Entity;
+import org.geysermc.connector.entity.living.animal.horse.ChestedHorseEntity;
+import org.geysermc.connector.entity.living.animal.horse.LlamaEntity;
+import org.geysermc.connector.inventory.Container;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.PacketTranslator;
+import org.geysermc.connector.network.translators.Translator;
+import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.horse.DonkeyInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.horse.HorseInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.horse.LlamaInventoryTranslator;
+import org.geysermc.connector.utils.InventoryUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Translator(packet = ServerOpenHorseWindowPacket.class)
+public class JavaOpenHorseWindowTranslator extends PacketTranslator {
+
+ private static final NbtMap ARMOR_SLOT;
+ private static final NbtMap CARPET_SLOT;
+ private static final NbtMap SADDLE_SLOT;
+
+ static {
+ // Build the NBT mappings that Bedrock wants to lay out the GUI
+ String[] acceptedHorseArmorIdentifiers = new String[] {"minecraft:horsearmorleather", "minecraft:horsearmoriron",
+ "minecraft:horsearmorgold", "minecraft:horsearmordiamond"};
+ NbtMapBuilder armorBuilder = NbtMap.builder();
+ List acceptedArmors = new ArrayList<>();
+ for (String identifier : acceptedHorseArmorIdentifiers) {
+ NbtMapBuilder acceptedItemBuilder = NbtMap.builder()
+ .putShort("Aux", Short.MAX_VALUE)
+ .putString("Name", identifier);
+ acceptedArmors.add(NbtMap.builder().putCompound("slotItem", acceptedItemBuilder.build()).build());
+ }
+ armorBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedArmors);
+ NbtMapBuilder armorItem = NbtMap.builder()
+ .putShort("Aux", Short.MAX_VALUE)
+ .putString("Name", "minecraft:horsearmoriron");
+ armorBuilder.putCompound("item", armorItem.build());
+ armorBuilder.putInt("slotNumber", 1);
+ ARMOR_SLOT = armorBuilder.build();
+
+ NbtMapBuilder carpetBuilder = NbtMap.builder();
+ NbtMapBuilder carpetItem = NbtMap.builder()
+ .putShort("Aux", Short.MAX_VALUE)
+ .putString("Name", "minecraft:carpet");
+ List acceptedCarpet = Collections.singletonList(NbtMap.builder().putCompound("slotItem", carpetItem.build()).build());
+ carpetBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedCarpet);
+ carpetBuilder.putCompound("item", carpetItem.build());
+ carpetBuilder.putInt("slotNumber", 1);
+ CARPET_SLOT = carpetBuilder.build();
+
+ NbtMapBuilder saddleBuilder = NbtMap.builder();
+ NbtMapBuilder acceptedSaddle = NbtMap.builder()
+ .putShort("Aux", Short.MAX_VALUE)
+ .putString("Name", "minecraft:saddle");
+ List acceptedItem = Collections.singletonList(NbtMap.builder().putCompound("slotItem", acceptedSaddle.build()).build());
+ saddleBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedItem);
+ saddleBuilder.putCompound("item", acceptedSaddle.build());
+ saddleBuilder.putInt("slotNumber", 0);
+ SADDLE_SLOT = saddleBuilder.build();
+ }
+
+ @Override
+ public void translate(ServerOpenHorseWindowPacket packet, GeyserSession session) {
+ System.out.println(packet.toString());
+ Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
+ if (entity == null) {
+ return;
+ }
+
+ UpdateEquipPacket updateEquipPacket = new UpdateEquipPacket();
+ updateEquipPacket.setWindowId((short) packet.getWindowId());
+ updateEquipPacket.setWindowType((short) ContainerType.HORSE.getId());
+ updateEquipPacket.setUniqueEntityId(entity.getGeyserId());
+
+ NbtMapBuilder builder = NbtMap.builder();
+ List slots = new ArrayList<>();
+
+ InventoryTranslator inventoryTranslator;
+ if (entity instanceof LlamaEntity) {
+ inventoryTranslator = new LlamaInventoryTranslator(packet.getNumberOfSlots());
+ slots.add(CARPET_SLOT);
+ } else if (entity instanceof ChestedHorseEntity) {
+ inventoryTranslator = new DonkeyInventoryTranslator(packet.getNumberOfSlots());
+ slots.add(SADDLE_SLOT);
+ } else {
+ inventoryTranslator = new HorseInventoryTranslator(packet.getNumberOfSlots());
+ slots.add(SADDLE_SLOT);
+ slots.add(ARMOR_SLOT);
+ }
+
+ // Build the NbtMap that sets the icons for Bedrock (e.g. sets the saddle outline on the saddle slot)
+ builder.putList("slots", NbtType.COMPOUND, slots);
+
+ updateEquipPacket.setTag(builder.build());
+ System.out.println(updateEquipPacket);
+ session.sendUpstreamPacket(updateEquipPacket);
+
+ session.setInventoryTranslator(inventoryTranslator);
+ InventoryUtils.openInventory(session, new Container(entity.getMetadata().getString(EntityData.NAMETAG), packet.getWindowId(), packet.getNumberOfSlots(), session.getPlayerInventory()));
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java
index 4c50b313..99af2e73 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java
@@ -41,35 +41,36 @@ public class JavaOpenWindowTranslator extends PacketTranslator {
+ if (packet.getWindowId() == 0) {
+ return;
+ }
+
+ InventoryTranslator newTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(packet.getType());
+ Inventory openInventory = session.getOpenInventory();
+ //No translator exists for this window type. Close all windows and return.
+ if (newTranslator == null) {
+ if (openInventory != null) {
+ InventoryUtils.closeInventory(session, openInventory.getId());
+ }
+ ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId());
+ session.sendDownstreamPacket(closeWindowPacket);
+ return;
+ }
+
+ String name = MessageTranslator.convertMessageLenient(packet.getName(), session.getLocale());
+ name = LocaleUtils.getLocaleString(name, session.getLocale());
+
+ Inventory newInventory = newTranslator.createInventory(name, packet.getWindowId(), packet.getType(), session.getPlayerInventory());
if (openInventory != null) {
- InventoryUtils.closeWindow(session, openInventory.getId());
- InventoryUtils.closeInventory(session, openInventory.getId());
+ InventoryTranslator openTranslator = session.getInventoryTranslator();
+ if (!openTranslator.getClass().equals(newTranslator.getClass())) {
+ InventoryUtils.closeInventory(session, openInventory.getId());
+ }
}
- ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId());
- session.sendDownstreamPacket(closeWindowPacket);
- return;
- }
- String name = MessageTranslator.convertMessageLenient(packet.getName(), session.getLocale());
-
- name = LocaleUtils.getLocaleString(name, session.getLocale());
-
- Inventory newInventory = new Inventory(name, packet.getWindowId(), packet.getType(), newTranslator.size + 36);
- session.getInventoryCache().cacheInventory(newInventory);
- if (openInventory != null) {
- InventoryTranslator openTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(openInventory.getWindowType());
- if (!openTranslator.getClass().equals(newTranslator.getClass())) {
- InventoryUtils.closeWindow(session, openInventory.getId());
- InventoryUtils.closeInventory(session, openInventory.getId());
- }
- }
-
- InventoryUtils.openInventory(session, newInventory);
+ session.setInventoryTranslator(newTranslator);
+ InventoryUtils.openInventory(session, newInventory);
+ });
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java
index 8caa2518..754cfea3 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java
@@ -26,6 +26,7 @@
package org.geysermc.connector.network.translators.java.window;
import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket;
+import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
@@ -38,23 +39,26 @@ public class JavaSetSlotTranslator extends PacketTranslator
@Override
public void translate(ServerSetSlotPacket packet, GeyserSession session) {
- if (packet.getWindowId() == 255 && packet.getSlot() == -1) { //cursor
- if (session.getCraftSlot() != 0)
+ System.out.println(packet.toString());
+ session.addInventoryTask(() -> {
+ if (packet.getWindowId() == 255) { //cursor
+ GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
+ session.getPlayerInventory().setCursor(newItem, session);
+ InventoryUtils.updateCursor(session);
+ return;
+ }
+
+ //TODO: support window id -2, should update player inventory
+ Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId());
+ if (inventory == null)
return;
- session.getInventory().setCursor(packet.getItem());
- InventoryUtils.updateCursor(session);
- return;
- }
-
- Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId());
- if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null))
- return;
-
- InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType());
- if (translator != null) {
- inventory.setItem(packet.getSlot(), packet.getItem());
- translator.updateSlot(session, inventory, packet.getSlot());
- }
+ InventoryTranslator translator = session.getInventoryTranslator();
+ if (translator != null) {
+ GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
+ inventory.setItem(packet.getSlot(), newItem, session);
+ translator.updateSlot(session, inventory, packet.getSlot());
+ }
+ });
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java
index a50518e8..7d29f54b 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java
@@ -26,32 +26,33 @@
package org.geysermc.connector.network.translators.java.window;
import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket;
+import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
-
-import java.util.Arrays;
+import org.geysermc.connector.utils.InventoryUtils;
@Translator(packet = ServerWindowItemsPacket.class)
public class JavaWindowItemsTranslator extends PacketTranslator {
@Override
public void translate(ServerWindowItemsPacket packet, GeyserSession session) {
- Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId());
- if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null))
- return;
+ session.addInventoryTask(() -> {
+ Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId());
+ if (inventory == null)
+ return;
- if (packet.getItems().length < inventory.getSize()) {
- inventory.setItems(Arrays.copyOf(packet.getItems(), inventory.getSize()));
- } else {
- inventory.setItems(packet.getItems());
- }
+ for (int i = 0; i < packet.getItems().length; i++) {
+ GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]);
+ inventory.setItem(i, newItem, session);
+ }
- InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType());
- if (translator != null) {
- translator.updateInventory(session, inventory);
- }
+ InventoryTranslator translator = session.getInventoryTranslator();
+ if (translator != null) {
+ translator.updateInventory(session, inventory);
+ }
+ });
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java
index d325f36d..94ec8784 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java
@@ -31,19 +31,23 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+import org.geysermc.connector.utils.InventoryUtils;
@Translator(packet = ServerWindowPropertyPacket.class)
public class JavaWindowPropertyTranslator extends PacketTranslator {
@Override
public void translate(ServerWindowPropertyPacket packet, GeyserSession session) {
- Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId());
- if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null))
- return;
+ System.out.println(packet.toString());
+ session.addInventoryTask(() -> {
+ Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId());
+ if (inventory == null)
+ return;
- InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType());
- if (translator != null) {
- translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue());
- }
+ InventoryTranslator translator = session.getInventoryTranslator();
+ if (translator != null) {
+ translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue());
+ }
+ });
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java
index d74165b1..7f2b3561 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java
@@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
-import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
@@ -38,17 +37,17 @@ import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHan
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.ChunkUtils;
+import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket;
+
@Translator(packet = ServerBlockChangePacket.class)
public class JavaBlockChangeTranslator extends PacketTranslator {
@Override
public void translate(ServerBlockChangePacket packet, GeyserSession session) {
Position pos = packet.getRecord().getPosition();
- boolean updatePlacement = session.getConnector().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event
- !(session.getConnector().getConfig().isCacheChunks() &&
- session.getConnector().getWorldManager().getBlockAt(session, pos) == packet.getRecord().getBlock());
- ChunkUtils.updateBlock(session, packet.getRecord().getBlock(), pos);
- if (updatePlacement) {
+ boolean updatePlacement = !(session.getConnector().getConfig().isCacheChunks() && session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()) == packet.getRecord().getBlock());
+ ChunkUtils.updateBlock(session, packet.getRecord().getBlock(), packet.getRecord().getPosition());
+ if (updatePlacement && session.getConnector().getPlatformType() != PlatformType.SPIGOT) {
this.checkPlace(session, packet);
}
this.checkInteract(session, packet);
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java
index e8b244b3..8a0ddb3b 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java
@@ -42,7 +42,7 @@ import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.network.translators.inventory.PlayerInventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.PlayerInventoryTranslator;
import org.geysermc.connector.utils.LocaleUtils;
@Translator(packet = ServerNotifyClientPacket.class)
@@ -111,7 +111,7 @@ public class JavaNotifyClientTranslator extends PacketTranslator {
+public class JavaPlayerPlaySoundTranslator extends PacketTranslator {
@Override
public void translate(ServerPlaySoundPacket packet, GeyserSession session) {
String packetSound;
- if (packet.getSound() instanceof BuiltinSound) {
+ if(packet.getSound() instanceof BuiltinSound) {
packetSound = ((BuiltinSound) packet.getSound()).getName();
- } else if (packet.getSound() instanceof CustomSound) {
+ } else if(packet.getSound() instanceof CustomSound) {
packetSound = ((CustomSound) packet.getSound()).getName();
} else {
session.getConnector().getLogger().debug("Unknown sound packet, we were unable to map this. " + packet.toString());
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java
index 228a2534..1874fc5e 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java
@@ -38,6 +38,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateTradePacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.MerchantContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@@ -53,8 +54,14 @@ public class JavaTradeListTranslator extends PacketTranslator tags = new ArrayList<>();
- for (VillagerTrade trade : packet.getTrades()) {
+ for (int i = 0; i < packet.getTrades().length; i++) {
+ VillagerTrade trade = packet.getTrades()[i];
NbtMapBuilder recipe = NbtMap.builder();
- recipe.putInt("maxUses", trade.getMaxUses());
+ recipe.putInt("netId", i + 1);
+ 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));
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java
index b768a207..6ebbd6c3 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java
@@ -44,10 +44,19 @@ public class JavaUnloadChunkTranslator extends PacketTranslator iterator = session.getSkullCache().keySet().iterator();
while (iterator.hasNext()) {
Vector3i position = iterator.next();
- if (Math.floor(position.getX() / 16) == packet.getX() && Math.floor(position.getZ() / 16) == packet.getZ()) {
+ if (Math.floor((double) position.getX() / 16) == packet.getX() && Math.floor((double) position.getZ() / 16) == packet.getZ()) {
session.getSkullCache().get(position).despawnEntity(session);
iterator.remove();
}
}
+
+ // Do the same thing with lecterns
+ iterator = session.getLecternCache().iterator();
+ while (iterator.hasNext()) {
+ Vector3i position = iterator.next();
+ if (Math.floor((double) position.getX() / 16) == packet.getX() && Math.floor((double) position.getZ() / 16) == packet.getZ()) {
+ iterator.remove();
+ }
+ }
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java
index 461d8139..dd1ec68a 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java
@@ -46,10 +46,17 @@ public class JavaUpdateTimeTranslator extends PacketTranslator= 0) {
// Client thinks there is no daylight cycle but there is
- session.setDaylightCycle(true);
+ setDoDaylightCycleGamerule(session, true);
} else if (session.isDaylightCycle() && time < 0) {
// Client thinks there is daylight cycle but there isn't
- session.setDaylightCycle(false);
+ setDoDaylightCycleGamerule(session, false);
}
}
+
+ private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
+ session.sendGameRule("dodaylightcycle", doCycle);
+ // Save the value so we don't have to constantly send a daylight cycle gamerule update
+ session.setDaylightCycle(doCycle);
+ }
+
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java
index 5ef00449..2172fd9e 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java
@@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.sound;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.nukkitx.math.vector.Vector3f;
+import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
@@ -60,12 +61,12 @@ public interface BlockSoundInteractionHandler extends SoundInteractionHandler
+ *
+ * So, on Java Edition, the lectern is an inventory. Java opens it and gets the contents of the book there.
+ * On Bedrock, the lectern contents are part of the block entity tag. Therefore, Bedrock expects to have the contents
+ * of the lectern ready and present in the world. If the contents are not there, it takes at least two clicks for the
+ * lectern to update the tag and then present itself.
+ *
+ * We solve this problem by querying all loaded lecterns, where possible, and sending their information in a block entity
+ * tag.
+ *
+ * @param session the session of the player
+ * @param x the x coordinate of the lectern
+ * @param y the y coordinate of the lectern
+ * @param z the z coordinate of the lectern
+ * @param isChunkLoad if this is called during a chunk load or not. Changes behavior in certain instances.
+ * @return the Bedrock lectern block entity tag. This may not be the exact block entity tag - for example, Spigot's
+ * block handled must be done on the server thread, so we send the tag manually there.
+ */
+ public abstract NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad);
+
+ /**
+ * @return whether we should expect lectern data to update, or if we have to fall back on a workaround.
+ */
+ public abstract boolean shouldExpectLecternHandled();
+
/**
* Updates a gamerule value on the Java server
*
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
index 4ffb96d0..406d0fdf 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
@@ -42,6 +42,7 @@ public class BlockStateValues {
private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
private static final Map FLOWER_POT_BLOCKS = new HashMap<>();
+ private static final Int2BooleanMap LECTERN_BOOK_STATES = new Int2BooleanOpenHashMap();
private static final Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap();
private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap();
private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap();
@@ -88,6 +89,11 @@ public class BlockStateValues {
return;
}
+ if (entry.getKey().startsWith("minecraft:lectern")) {
+ LECTERN_BOOK_STATES.put(javaBlockState, entry.getKey().contains("has_book=true"));
+ return;
+ }
+
JsonNode notePitch = entry.getValue().get("note_pitch");
if (notePitch != null) {
NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue());
@@ -197,6 +203,10 @@ public class BlockStateValues {
return FLOWER_POT_BLOCKS;
}
+ public static Int2BooleanMap getLecternBookStates() {
+ return LECTERN_BOOK_STATES;
+ }
+
/**
* The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock.
* This gives an integer pitch that Bedrock can use.
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java
index b047999e..db6f43fe 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java
@@ -87,9 +87,7 @@ public class BlockTranslator {
*/
public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID;
- /**
- * A list of all Java runtime wool IDs, for use with block breaking math and shears
- */
+ // For block breaking animation math
public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet();
public static final int JAVA_RUNTIME_COBWEB_ID;
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java
index 07760c46..12c0c9b6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java
@@ -31,7 +31,7 @@ import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.item.translators.BannerTranslator;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
-@BlockEntity(name = "Banner", regex = "banner")
+@BlockEntity(name = "Banner")
public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public boolean isBlock(int blockState) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java
new file mode 100644
index 00000000..147651a1
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019-2021 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.connector.network.translators.world.block.entity;
+
+import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
+import com.nukkitx.nbt.NbtMapBuilder;
+
+@BlockEntity(name = "Beacon")
+public class BeaconBlockEntityTranslator extends BlockEntityTranslator {
+ @Override
+ public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
+ int primary = getOrDefault(tag.get("Primary"), 0);
+ // The effects here generally map one-to-one Java <-> Bedrock. Only the newer ones get more complicated
+ builder.putInt("primary", primary == -1 ? 0 : primary);
+ int secondary = getOrDefault(tag.get("Secondary"), 0);
+ builder.putInt("secondary", secondary == -1 ? 0 : secondary);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java
index 7d9dee98..28cb52f6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java
@@ -29,7 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
-@BlockEntity(name = "Bed", regex = "bed")
+@BlockEntity(name = "Bed")
public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public boolean isBlock(int blockState) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java
index acd0c87f..a2b48b1c 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java
@@ -36,10 +36,4 @@ public @interface BlockEntity {
* @return the name of the block entity
*/
String name();
-
- /**
- * The search term used in BlockTranslator
- * @return the search term used in BlockTranslator
- */
- String regex();
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java
index 7f2e16ef..f109ed6b 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java
@@ -109,7 +109,7 @@ public abstract class BlockEntityTranslator {
int y = ((IntTag) tag.getValue().get("y")).getValue();
int z = ((IntTag) tag.getValue().get("z")).getValue();
- NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder();
+ NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z);
translateTag(tagBuilder, tag, blockState);
return tagBuilder.build();
}
@@ -123,13 +123,12 @@ public abstract class BlockEntityTranslator {
return tag;
}
- protected NbtMap getConstantBedrockTag(String bedrockId, int x, int y, int z) {
+ protected NbtMapBuilder getConstantBedrockTag(String bedrockId, int x, int y, int z) {
return NbtMap.builder()
.putInt("x", x)
.putInt("y", y)
.putInt("z", z)
- .putString("id", bedrockId)
- .build();
+ .putString("id", bedrockId);
}
@SuppressWarnings("unchecked")
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java
index 7ae4f315..e25bb826 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java
@@ -32,7 +32,7 @@ import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
-@BlockEntity(name = "Campfire", regex = "campfire")
+@BlockEntity(name = "Campfire")
public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
@@ -47,7 +47,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
protected NbtMap getItem(CompoundTag tag) {
ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue());
NbtMapBuilder tagBuilder = NbtMap.builder()
- .putString("Name", entry.getBedrockIdentifier())
+ .putShort("id", (short) entry.getBedrockId())
.putByte("Count", (byte) tag.get("Count").getValue())
.putShort("Damage", (short) entry.getBedrockData());
tagBuilder.put("tag", NbtMap.builder().build());
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java
index fe988854..a4bb3e69 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java
@@ -30,7 +30,7 @@ import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
-@BlockEntity(name = "CommandBlock", regex = "command_block")
+@BlockEntity(name = "CommandBlock")
public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java
index 991fb266..1a68903e 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java
@@ -36,7 +36,7 @@ import org.geysermc.connector.utils.BlockEntityUtils;
/**
* Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity
*/
-@BlockEntity(name = "Chest", regex = "chest")
+@BlockEntity(name = "Chest")
public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState {
@Override
public boolean isBlock(int blockState) {
@@ -46,7 +46,7 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl
@Override
public void updateBlock(GeyserSession session, int blockState, Vector3i position) {
CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ());
- NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder();
+ NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ());
translateTag(tagBuilder, javaTag, blockState);
BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position);
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java
index 12afd530..eb68337d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java
@@ -28,7 +28,7 @@ package org.geysermc.connector.network.translators.world.block.entity;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMapBuilder;
-@BlockEntity(name = "Empty", regex = "")
+@BlockEntity(name = "Empty")
public class EmptyBlockEntityTranslator extends BlockEntityTranslator {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java
index 2dbd7585..5280124c 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java
@@ -35,7 +35,7 @@ import it.unimi.dsi.fastutil.ints.IntList;
import java.util.LinkedHashMap;
-@BlockEntity(name = "EndGateway", regex = "end_gateway")
+@BlockEntity(name = "EndGateway")
public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java
index 10582c7b..18c28a11 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java
@@ -29,7 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.nukkitx.nbt.NbtMapBuilder;
-@BlockEntity(name = "JigsawBlock", regex = "jigsaw")
+@BlockEntity(name = "JigsawBlock")
public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java
index 70fde3e4..c09d2f99 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java
@@ -29,7 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
-@BlockEntity(name = "ShulkerBox", regex = "shulker_box")
+@BlockEntity(name = "ShulkerBox")
public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java
index 2a3950b3..a92fac59 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java
@@ -30,7 +30,7 @@ import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.utils.SignUtils;
-@BlockEntity(name = "Sign", regex = "sign")
+@BlockEntity(name = "Sign")
public class SignBlockEntityTranslator extends BlockEntityTranslator {
/**
* Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code.
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
index c41d4515..b3e25b21 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java
@@ -46,7 +46,7 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
-@BlockEntity(name = "Skull", regex = "skull")
+@BlockEntity(name = "Skull")
public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
public static boolean ALLOW_CUSTOM_SKULLS;
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java
index 5a6f974b..a9a37b60 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java
@@ -30,7 +30,7 @@ import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.connector.entity.type.EntityType;
-@BlockEntity(name = "MobSpawner", regex = "mob_spawner")
+@BlockEntity(name = "MobSpawner")
public class SpawnerBlockEntityTranslator extends BlockEntityTranslator {
@Override
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java
index c859b9f6..89ea9298 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java
@@ -50,7 +50,6 @@ public class BlockUtils {
if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0;
if (toolType.equals("")) return 1.0;
switch (toolTier) {
- // https://minecraft.gamepedia.com/Breaking#Speed
case "wooden":
return 2.0;
case "stone":
@@ -59,8 +58,6 @@ public class BlockUtils {
return 6.0;
case "diamond":
return 8.0;
- case "netherite":
- return 9.0;
case "golden":
return 12.0;
default:
@@ -135,7 +132,7 @@ public class BlockUtils {
&& BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == BlockTranslator.BEDROCK_WATER_ID;
boolean insideOfWaterWithoutAquaAffinity = isInWater &&
- ItemUtils.getEnchantmentLevel(Optional.ofNullable(session.getInventory().getItem(5)).map(ItemStack::getNbt).orElse(null), "minecraft:aqua_affinity") < 1;
+ ItemUtils.getEnchantmentLevel(session.getPlayerInventory().getItem(5).getNbt(), "minecraft:aqua_affinity") < 1;
boolean outOfWaterButNotOnGround = (!isInWater) && (!session.getPlayerEntity().isOnGround());
boolean insideWaterNotOnGround = isInWater && !session.getPlayerEntity().isOnGround();
diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
index e5e3f36d..1b13d5de 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
@@ -53,6 +53,7 @@ import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.ItemFrameEntity;
import org.geysermc.connector.entity.player.SkullPlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity;
@@ -282,7 +283,6 @@ public class ChunkUtils {
}
String id = BlockEntityUtils.getBedrockBlockEntityId(tagName);
- BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id);
Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue());
// Get Java blockstate ID from block entity position
@@ -292,6 +292,14 @@ public class ChunkUtils {
blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF);
}
+ if (tagName.equals("minecraft:lectern") && BlockStateValues.getLecternBookStates().get(blockState)) {
+ // If getLecternBookStates is false, let's just treat it like a normal block entity
+ bedrockBlockEntities[i] = session.getConnector().getWorldManager().getLecternDataAt(session, pos.getX(), pos.getY(), pos.getZ(), true);
+ i++;
+ continue;
+ }
+
+ BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id);
bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState);
// Check for custom skulls
@@ -388,6 +396,29 @@ public class ChunkUtils {
}
session.sendUpstreamPacket(waterPacket);
+ if (BlockStateValues.getLecternBookStates().containsKey(blockState)) {
+ boolean lecternCachedHasBook = session.getLecternCache().contains(position);
+ boolean newLecternHasBook = BlockStateValues.getLecternBookStates().get(blockState);
+ if (!session.getConnector().getWorldManager().shouldExpectLecternHandled() && lecternCachedHasBook != newLecternHasBook) {
+ // Refresh the block entirely - it either has a book or no longer has a book
+ session.getConnector().getLogger().warning("Refreshing lectern entirely");
+ NbtMap newLecternTag;
+ if (newLecternHasBook) {
+ newLecternTag = session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
+ } else {
+ session.getLecternCache().remove(position);
+ newLecternTag = LecternInventoryTranslator.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build();
+ }
+ BlockEntityUtils.updateBlockEntity(session, newLecternTag, position);
+ } else {
+ // As of right now, no tag can be added asynchronously
+ session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
+ }
+ } else {
+ // Lectern has been destroyed, if it existed
+ session.getLecternCache().remove(position);
+ }
+
// Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag
// This is the only place I could find that interacts with the Java block state and block updates
// Iterates through all block entity translators and determines if the block state needs to be saved
diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
index f193a61d..0efa8a5a 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
@@ -57,10 +57,20 @@ public class DimensionUtils {
public static void switchDimension(GeyserSession session, String javaDimension) {
int bedrockDimension = javaToBedrock(javaDimension);
Entity player = session.getPlayerEntity();
+ if (javaDimension.equals(session.getDimension()))
+ return;
+
+ if (session.getMovementSendIfIdle() != null) {
+ session.getMovementSendIfIdle().cancel(true);
+ }
session.getEntityCache().removeAllEntities();
session.getItemFrameCache().clear();
+ session.getLecternCache().clear();
session.getSkullCache().clear();
+ if (session.getPendingDimSwitches().getAndIncrement() > 0) {
+ ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
+ }
Vector3i pos = Vector3i.from(0, Short.MAX_VALUE, 0);
@@ -141,20 +151,4 @@ public class DimensionUtils {
// Change dimension ID to the End to allow for building above Bedrock
BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? 2 : 1;
}
-
- /**
- * Gets the fake, temporary dimension we send clients to so we aren't switching to the same dimension without an additional
- * dimension switch.
- *
- * @param currentDimension the current dimension of the player
- * @param newDimension the new dimension that the player will be transferred to
- * @return the fake dimension to transfer to
- */
- public static String getTemporaryDimension(String currentDimension, String newDimension) {
- if (BEDROCK_NETHER_ID == 2) {
- // Prevents rare instances of Bedrock locking up
- return javaToBedrock(newDimension) == 2 ? OVERWORLD : NETHER;
- }
- return currentDimension.equals(OVERWORLD) ? NETHER : OVERWORLD;
- }
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
index 862af548..fd3e9a8d 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
@@ -36,7 +36,6 @@ import org.reflections.util.ConfigurationBuilder;
import java.io.*;
import java.net.URL;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.function.Function;
@@ -63,8 +62,7 @@ public class FileUtils {
}
public static T loadJson(InputStream src, Class valueType) throws IOException {
- // Read specifically with UTF-8 to allow any non-UTF-encoded JSON to read
- return GeyserConnector.JSON_MAPPER.readValue(new InputStreamReader(src, StandardCharsets.UTF_8), valueType);
+ return GeyserConnector.JSON_MAPPER.readValue(src, valueType);
}
/**
diff --git a/connector/src/main/java/org/geysermc/connector/utils/GeyserAdvancement.java b/connector/src/main/java/org/geysermc/connector/utils/GeyserAdvancement.java
deleted file mode 100644
index 31560498..00000000
--- a/connector/src/main/java/org/geysermc/connector/utils/GeyserAdvancement.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) 2019-2021 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.connector.utils;
-
-import com.github.steveice10.mc.protocol.data.game.advancement.Advancement;
-import lombok.NonNull;
-import org.geysermc.connector.network.session.cache.AdvancementsCache;
-
-import java.util.List;
-
-/**
- * A wrapper around MCProtocolLib's {@link Advancement} class so we can control the parent of an advancement
- */
-public class GeyserAdvancement {
- private final Advancement advancement;
- private String rootId = null;
-
- public static GeyserAdvancement from(Advancement advancement) {
- return new GeyserAdvancement(advancement);
- }
-
- private GeyserAdvancement(Advancement advancement) {
- this.advancement = advancement;
- }
-
- @NonNull
- public String getId() {
- return this.advancement.getId();
- }
-
- @NonNull
- public List getCriteria() {
- return this.advancement.getCriteria();
- }
-
- @NonNull
- public List> getRequirements() {
- return this.advancement.getRequirements();
- }
-
- public String getParentId() {
- return this.advancement.getParentId();
- }
-
- public Advancement.DisplayData getDisplayData() {
- return this.advancement.getDisplayData();
- }
-
- public String getRootId(AdvancementsCache advancementsCache) {
- if (rootId == null) {
- if (this.advancement.getParentId() == null) {
- // We are the root ID
- this.rootId = this.advancement.getId();
- } else {
- // Go through our cache, and descend until we find the root ID
- GeyserAdvancement advancement = advancementsCache.getStoredAdvancements().get(this.advancement.getParentId());
- if (advancement.getParentId() == null) {
- this.rootId = advancement.getId();
- } else {
- this.rootId = advancement.getRootId(advancementsCache);
- }
- }
- }
- return rootId;
- }
-}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java
index a4d72226..81f59cf3 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java
@@ -39,31 +39,30 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.ChatColor;
+import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
+import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
-import org.geysermc.connector.network.translators.inventory.DoubleChestInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
+import org.geysermc.connector.network.translators.inventory.translators.chest.DoubleChestInventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
-import org.geysermc.connector.network.translators.item.ItemTranslator;
-import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class InventoryUtils {
- public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new CompoundTag("")); //TODO: stop using this
+ public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new CompoundTag(""));
public static void openInventory(GeyserSession session, Inventory inventory) {
- InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType());
+ InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) {
- session.getInventoryCache().setOpenInventory(inventory);
+ session.setOpenInventory(inventory);
translator.prepareInventory(session, inventory);
//Ensure at least half a second passes between closing and opening a new window
//The client will not open the new window if it is still closing the old one
long delay = 700 - (System.currentTimeMillis() - session.getLastWindowCloseTime());
- //TODO: find better way to handle double chest delay
if (translator instanceof DoubleChestInventoryTranslator) {
delay = Math.max(delay, 200);
}
@@ -80,53 +79,45 @@ public class InventoryUtils {
}
public static void closeInventory(GeyserSession session, int windowId) {
- if (windowId != 0) {
- Inventory inventory = session.getInventoryCache().getInventories().get(windowId);
- Inventory openInventory = session.getInventoryCache().getOpenInventory();
- session.getInventoryCache().uncacheInventory(windowId);
- if (inventory != null && openInventory != null && inventory.getId() == openInventory.getId()) {
- InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType());
- translator.closeInventory(session, inventory);
- session.getInventoryCache().setOpenInventory(null);
- } else {
- return;
- }
- } else {
- Inventory inventory = session.getInventory();
- inventory.setOpen(false);
- InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType());
- translator.updateInventory(session, inventory);
- }
-
- session.setCraftSlot(0);
- session.getInventory().setCursor(null);
+ session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
updateCursor(session);
- }
- public static void closeWindow(GeyserSession session, int windowId) {
- //TODO: Investigate client crash when force closing window and opening a new one
- //Instead, the window will eventually close by removing the fake blocks
- session.setLastWindowCloseTime(System.currentTimeMillis());
-
- /*
- //Spamming close window packets can bug the client
- if (System.currentTimeMillis() - session.getLastWindowCloseTime() > 500) {
- ContainerClosePacket closePacket = new ContainerClosePacket();
- closePacket.setId((byte) windowId);
- session.sendUpstreamPacket(closePacket);
+ Inventory inventory = getInventory(session, windowId);
+ if (inventory != null) {
+ InventoryTranslator translator = session.getInventoryTranslator();
+ translator.closeInventory(session, inventory);
session.setLastWindowCloseTime(System.currentTimeMillis());
}
- */
+ session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
+ session.setOpenInventory(null);
+ }
+
+ public static Inventory getInventory(GeyserSession session, int windowId) {
+ if (windowId == 0) {
+ return session.getPlayerInventory();
+ } else {
+ Inventory openInventory = session.getOpenInventory();
+ if (openInventory != null && windowId == openInventory.getId()) {
+ return openInventory;
+ }
+ return null;
+ }
}
public static void updateCursor(GeyserSession session) {
InventorySlotPacket cursorPacket = new InventorySlotPacket();
cursorPacket.setContainerId(ContainerId.UI);
cursorPacket.setSlot(0);
- cursorPacket.setItem(ItemTranslator.translateToBedrock(session, session.getInventory().getCursor()));
+ cursorPacket.setItem(session.getPlayerInventory().getCursor().getItemData(session));
session.sendUpstreamPacket(cursorPacket);
}
+ public static boolean canStack(GeyserItemStack item1, GeyserItemStack item2) {
+ if (item1.isEmpty() || item2.isEmpty())
+ return false;
+ return item1.getJavaId() == item2.getJavaId() && Objects.equals(item1.getNbt(), item2.getNbt());
+ }
+
public static boolean canStack(ItemStack item1, ItemStack item2) {
if (item1 == null || item2 == null)
return false;
@@ -171,10 +162,7 @@ public class InventoryUtils {
*/
public static void findOrCreateItem(GeyserSession session, String itemName) {
// Get the inventory to choose a slot to pick
- Inventory inventory = session.getInventoryCache().getOpenInventory();
- if (inventory == null) {
- inventory = session.getInventory();
- }
+ PlayerInventory inventory = session.getPlayerInventory();
if (itemName.equals("minecraft:air")) {
return;
@@ -182,12 +170,12 @@ public class InventoryUtils {
// Check hotbar for item
for (int i = 36; i < 45; i++) {
- if (inventory.getItem(i) == null) {
+ GeyserItemStack geyserItem = inventory.getItem(i);
+ if (geyserItem.isEmpty()) {
continue;
}
- ItemEntry item = ItemRegistry.getItem(inventory.getItem(i));
// If this isn't the item we're looking for
- if (!item.getJavaIdentifier().equals(itemName)) {
+ if (!geyserItem.getItemEntry().getJavaIdentifier().equals(itemName)) {
continue;
}
@@ -198,12 +186,12 @@ public class InventoryUtils {
// Check inventory for item
for (int i = 9; i < 36; i++) {
- if (inventory.getItem(i) == null) {
+ GeyserItemStack geyserItem = inventory.getItem(i);
+ if (geyserItem.isEmpty()) {
continue;
}
- ItemEntry item = ItemRegistry.getItem(inventory.getItem(i));
// If this isn't the item we're looking for
- if (!item.getJavaIdentifier().equals(itemName)) {
+ if (!geyserItem.getItemEntry().getJavaIdentifier().equals(itemName)) {
continue;
}
@@ -214,10 +202,10 @@ public class InventoryUtils {
// If we still have not found the item, and we're in creative, ask for the item from the server.
if (session.getGameMode() == GameMode.CREATIVE) {
- int slot = session.getInventory().getHeldItemSlot() + 36;
- if (session.getInventory().getItemInHand() != null) { // Otherwise we should just use the current slot
+ int slot = inventory.getHeldItemSlot() + 36;
+ if (!inventory.getItemInHand().isEmpty()) { // Otherwise we should just use the current slot
for (int i = 36; i < 45; i++) {
- if (inventory.getItem(i) == null) {
+ if (inventory.getItem(i).isEmpty()) {
slot = i;
break;
}
@@ -228,7 +216,7 @@ public class InventoryUtils {
if (entry != null) {
ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot,
new ItemStack(entry.getJavaId()));
- if ((slot - 36) != session.getInventory().getHeldItemSlot()) {
+ if ((slot - 36) != inventory.getHeldItemSlot()) {
setHotbarItem(session, slot);
}
session.sendDownstreamPacket(actionPacket);
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java
index 1a1f758d..c6ab715d 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java
@@ -187,11 +187,7 @@ public class LanguageUtils {
if (FileUtils.class.getResource("/languages/texts/" + locale + ".properties") == null) {
result = false;
if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getLogger() != null) { // Could be too early for these to be initialized
- if (locale.equals("en_US")) {
- GeyserConnector.getInstance().getLogger().error("English locale not found in Geyser. Did you clone the submodules? (git submodule update --init)");
- } else {
- GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language
- }
+ GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language
}
} else {
if (!LOCALE_MAPPINGS.containsKey(locale)) {
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java
index fd7ef4e6..9e16c428 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java
@@ -29,18 +29,19 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
-import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
import com.nimbusds.jose.JWSObject;
import com.nukkitx.network.util.Preconditions;
import com.nukkitx.protocol.bedrock.packet.LoginPacket;
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
-import org.geysermc.common.window.*;
+import org.geysermc.common.window.CustomFormBuilder;
+import org.geysermc.common.window.CustomFormWindow;
+import org.geysermc.common.window.FormWindow;
+import org.geysermc.common.window.SimpleFormWindow;
import org.geysermc.common.window.button.FormButton;
import org.geysermc.common.window.component.InputComponent;
import org.geysermc.common.window.component.LabelComponent;
import org.geysermc.common.window.response.CustomFormResponse;
-import org.geysermc.common.window.response.ModalFormResponse;
import org.geysermc.common.window.response.SimpleFormResponse;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
@@ -155,21 +156,13 @@ public class LoginEncryptionUtils {
session.sendUpstreamPacketImmediately(packet);
}
- private static final int AUTH_MSA_DETAILS_FORM_ID = 1334;
- private static final int AUTH_MSA_CODE_FORM_ID = 1335;
- private static final int AUTH_FORM_ID = 1336;
- private static final int AUTH_DETAILS_FORM_ID = 1337;
+ private static int AUTH_FORM_ID = 1336;
+ private static int AUTH_DETAILS_FORM_ID = 1337;
public static void showLoginWindow(GeyserSession session) {
- // Set DoDaylightCycle to false so the time doesn't accelerate while we're here
- session.setDaylightCycle(false);
-
String userLanguage = session.getLocale();
SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage));
- if (session.getConnector().getConfig().getRemote().isPasswordAuthentication()) {
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.mojang", userLanguage)));
- }
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage)));
+ window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login", userLanguage)));
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
session.sendForm(window, AUTH_FORM_ID);
@@ -186,33 +179,12 @@ public class LoginEncryptionUtils {
session.sendForm(window, AUTH_DETAILS_FORM_ID);
}
- /**
- * Prompts the user between either OAuth code login or manual password authentication
- */
- public static void showMicrosoftAuthenticationWindow(GeyserSession session) {
- String userLanguage = session.getLocale();
- SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage), "");
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.browser", userLanguage)));
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.password", userLanguage))); // This form won't show if password authentication is disabled
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
- session.sendForm(window, AUTH_MSA_DETAILS_FORM_ID);
- }
-
- /**
- * Shows the code that a user must input into their browser
- */
- public static void showMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse response) {
- ModalFormWindow msaCodeWindow = new ModalFormWindow("%xbox.signin", "%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" +
- response.user_code, "Done", "%menu.disconnect");
- session.sendForm(msaCodeWindow, LoginEncryptionUtils.AUTH_MSA_CODE_FORM_ID);
- }
-
public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) {
WindowCache windowCache = session.getWindowCache();
if (!windowCache.getWindows().containsKey(formId))
return false;
- if (formId == AUTH_MSA_DETAILS_FORM_ID || formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID || formId == AUTH_MSA_CODE_FORM_ID) {
+ if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) {
FormWindow window = windowCache.getWindows().remove(formId);
window.setResponse(formData.trim());
@@ -226,57 +198,23 @@ public class LoginEncryptionUtils {
String password = response.getInputResponses().get(2);
session.authenticate(email, password);
-
- // Clear windows so authentication data isn't accidentally cached
- windowCache.getWindows().clear();
} else {
showLoginDetailsWindow(session);
}
+
+ // Clear windows so authentication data isn't accidentally cached
+ windowCache.getWindows().clear();
} else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) {
- boolean isPasswordAuthentication = session.getConnector().getConfig().getRemote().isPasswordAuthentication();
- int microsoftButton = isPasswordAuthentication ? 1 : 0;
- int disconnectButton = isPasswordAuthentication ? 2 : 1;
- SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
- if (response != null) {
- if (isPasswordAuthentication && response.getClickedButtonId() == 0) {
- session.setMicrosoftAccount(false);
- showLoginDetailsWindow(session);
- } else if (response.getClickedButtonId() == microsoftButton) {
- session.setMicrosoftAccount(true);
- if (isPasswordAuthentication) {
- showMicrosoftAuthenticationWindow(session);
- } else {
- // Just show the OAuth code
- session.authenticateWithMicrosoftCode();
- }
- } else if (response.getClickedButtonId() == disconnectButton) {
- session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
- }
- } else {
- showLoginWindow(session);
- }
- } else if (formId == AUTH_MSA_DETAILS_FORM_ID && window instanceof SimpleFormWindow) {
SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
if (response != null) {
if (response.getClickedButtonId() == 0) {
- session.authenticateWithMicrosoftCode();
- } else if (response.getClickedButtonId() == 1) {
showLoginDetailsWindow(session);
- } else if (response.getClickedButtonId() == 2) {
+ } else if(response.getClickedButtonId() == 1) {
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
}
} else {
showLoginWindow(session);
}
- } else if (formId == AUTH_MSA_CODE_FORM_ID && window instanceof ModalFormWindow) {
- ModalFormResponse response = (ModalFormResponse) window.getResponse();
- if (response != null) {
- if (response.getClickedButtonId() == 1) {
- session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
- }
- } else {
- showMicrosoftAuthenticationWindow(session);
- }
}
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java
index bcb1ffd5..16a1812e 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java
@@ -63,7 +63,7 @@ public class ResourcePack {
// As we just created the directory it will be empty
return;
}
-
+
for (File file : directory.listFiles()) {
if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) {
ResourcePack pack = new ResourcePack();
@@ -77,15 +77,12 @@ public class ResourcePack {
if (x.getName().contains("manifest.json")) {
try {
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
- // Sometimes a pack_manifest file is present and not in a valid format,
- // but a manifest file is, so we null check through that one
- if (manifest.getHeader().getUuid() != null) {
- pack.file = file;
- pack.manifest = manifest;
- pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
- PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
- }
+ pack.file = file;
+ pack.manifest = manifest;
+ pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
+
+ PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml
index 07b73173..ac9ec753 100644
--- a/connector/src/main/resources/config.yml
+++ b/connector/src/main/resources/config.yml
@@ -32,14 +32,10 @@ remote:
port: 25565
# Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate).
auth-type: online
- # Allow for password-based authentication methods through Geyser. Only useful in online mode.
- # If this is false, users must authenticate to Microsoft using a code provided by Geyser on their desktop.
- allow-password-authentication: true
# Whether to enable PROXY protocol or not while connecting to the server.
# This is useful only when:
# 1) Your server supports PROXY protocol (it probably doesn't)
- # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config.
- # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!
+ # 2) You run Velocity or BungeeCord with respective option enabled.
use-proxy-protocol: false
# Floodgate uses encryption to ensure use from authorised sources.
@@ -55,12 +51,10 @@ floodgate-key-file: public-key.pem
# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username
# email: javaccountemail@example.com # Your Minecraft: Java Edition email
# password: javaccountpassword123 # Your Minecraft: Java Edition password
-# microsoft-account: true # Whether the account is a Mojang or Microsoft account.
#
# bluerkelp2:
# email: not_really_my_email_address_mr_minecrafter53267@gmail.com
# password: "this isn't really my password"
-# microsoft-account: false
# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands.
# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.
@@ -138,7 +132,8 @@ above-bedrock-nether-building: false
force-resource-packs: true
# Allows Xbox achievements to be unlocked.
-# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating.
+# This disables certain commands so the Bedrock client can't to "cheat" to get them.
+# Commands such as /gamemode and /give will not work from Bedrock with this enabled
xbox-achievements-enabled: false
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages
index 8141bc6a..1a007668 160000
--- a/connector/src/main/resources/languages
+++ b/connector/src/main/resources/languages
@@ -1 +1 @@
-Subproject commit 8141bc6aed878a95ed9ee3ca83a2381f9906c4b4
+Subproject commit 1a00766840baf1f512d98f5a75c177c8bcfba6f3
diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings
index dd0347bd..07f65c38 160000
--- a/connector/src/main/resources/mappings
+++ b/connector/src/main/resources/mappings
@@ -1 +1 @@
-Subproject commit dd0347bd51e00e42ea58faaf68b562526c4d2817
+Subproject commit 07f65c3803dcd3f83358ee574e54bf129cad0840
diff --git a/licenseheader.txt b/licenseheader.txt
index 8ef205a3..c22c426c 100644
--- a/licenseheader.txt
+++ b/licenseheader.txt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
+ * Copyright (c) 2019-2020 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
diff --git a/pom.xml b/pom.xml
index 011b320f..1b544f9e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,19 @@
+
+
+ releases
+ opencollab-releases
+ https://repo.opencollab.dev/maven-releases
+
+
+ snapshots
+ opencollab-snapshots
+ https://repo.opencollab.dev/maven-snapshots
+
+
+
org.projectlombok