Compare commits

...

51 Commits

Author SHA1 Message Date
Camotoy c8016647f2
Clean up and add mobile button for horse opening 2021-01-05 19:08:54 -05:00
Camotoy a88678a5c1
Add manual recipes as Recipe classes for future usage 2021-01-04 22:15:55 -05:00
Camotoy 1d47dc3f18
Merge branch 'server-inventory' of https://github.com/GeyserMC/Geyser into server-inventory 2021-01-04 20:30:19 -05:00
AJ Ferguson ff69752d2c Remove unnecessary is_block null check 2021-01-04 15:16:21 -09:00
AJ Ferguson 57e176efd6 Optimize stackSize
Co-Authored-By: Camotoy <20743703+Camotoy@users.noreply.github.com>
2021-01-04 15:12:26 -09:00
AJ Ferguson a160e3694b Add stackSize to ItemEntry 2021-01-04 14:47:48 -09:00
Camotoy 50f295b4cd
Remove unneeded GeyserItemStack constructor and add comments 2021-01-04 18:19:43 -05:00
AJ Ferguson 8928d554a1 WIP autocrafting using java recipe book
work in progress. many edge cases are currently unhandled. will not work at all pre 1.12. (support is planned)
2021-01-03 17:54:26 -09:00
AJ Ferguson 528a9a4431 Crafting table slot mappings 2021-01-01 18:37:33 -09:00
Camotoy 7a82852134
Merge branch 'server-inventory' of https://github.com/GeyserMC/Geyser into server-inventory 2021-01-01 14:22:44 -05:00
Camotoy 2a5c134ea7
Update server inventory copyrights to 2021 2021-01-01 14:22:26 -05:00
Camotoy 8317961340
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2021-01-01 14:20:52 -05:00
AJ Ferguson b7b3278d8b item painting. cursor net id 2020-12-31 17:39:54 -09:00
Camotoy c4fc604e0c
Optimize stonecutter button code 2020-12-29 20:31:48 -05:00
Camotoy 3c1a40c56a
Better net ID handling 2020-12-29 19:59:22 -05:00
Camotoy f4b1d470c3
Merge branch 'server-inventory' of https://github.com/AJ-Ferguson/Geyser into server-inventory 2020-12-29 19:02:11 -05:00
Camotoy ac4f6eceac
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-29 19:01:59 -05:00
Camotoy a5c020e7ee
More comments 2020-12-28 13:16:17 -05:00
Camotoy 3ba396e625
Fix some temporary inventory blocks not disappearing 2020-12-28 00:47:10 -05:00
Camotoy 2265de3ae9
lecterns 2020-12-28 00:29:27 -05:00
Camotoy 790c695b27
Add TODO 2020-12-27 11:08:48 -05:00
Camotoy 956d264c3e
Anvil renaming works; other things 2020-12-26 22:55:14 -05:00
AJ Ferguson 60da3b9432 Temp slot 2020-12-26 18:44:48 -09:00
Camotoy 4b461e5e0a
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-26 19:52:16 -05:00
Camotoy 7c4e95625a
Cleanup and delete FilterTextPacket - PRing to the main branch 2020-12-26 19:16:35 -05:00
AJ Ferguson 6df89ed679
Merge pull request #1 from Camotoy/server-inventory
Progress
2020-12-26 14:07:50 -09:00
Camotoy 078af59249
Streamline Item Net ID getting; cartography table finished 2020-12-26 16:41:50 -05:00
Camotoy 06f346b30b
Fix offset issue 2020-12-26 13:24:50 -05:00
Camotoy c1f5380ed1
Add horse inventory support 2020-12-26 12:20:59 -05:00
Camotoy c7fade295e
Add swap support for creative mode; start on cartography table 2020-12-24 20:43:24 -05:00
Camotoy 3d0b0a1076
Stonecutter fixed; Loom improved 2020-12-24 18:29:25 -05:00
Camotoy 617a1216d5
Initial work on stonecutters 2020-12-24 11:23:47 -05:00
Camotoy 6ae81cce52
Full banner loom support 2020-12-23 22:53:54 -05:00
Camotoy 94febd6a2f
Merge pull request #15 from D3ATHBRINGER13/server-inventory
Initial loom functionality
2020-12-23 21:02:02 -05:00
D3ATHBRINGER13 4d80edf6d9 Initial loom funtionality 2020-12-24 00:48:31 +00:00
Camotoy 0f735a8330
Block entity cleanup 2020-12-23 12:37:55 -05:00
Camotoy e4ecd1a092
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-23 12:32:39 -05:00
Camotoy ff4f712eda
Implement beacon 2020-12-23 12:30:36 -05:00
Camotoy f4f804e1ca
Enchantment table works; anvil is almost there 2020-12-23 01:21:00 -05:00
Camotoy 009905184e
Add grindstone and smithing table 2020-12-21 22:44:01 -05:00
Camotoy c6b4d163a1
Improve creative support 2020-12-21 21:09:14 -05:00
DoctorMacc f47cf32d90
Make my life tolerable 2020-12-19 19:19:44 -05:00
DoctorMacc 1705f1034c
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-18 18:37:54 -05:00
DoctorMacc aa4a1058e3
Brewing stand support; other attempts 2020-12-17 21:47:18 -05:00
DoctorMacc 33a86485dc
Implement dropper/dispenser, hopper, shulker 2020-12-17 11:46:11 -05:00
DoctorMacc 929b0ba80c
Refactors, and add furnace 2020-12-16 23:52:45 -05:00
DoctorMacc 988e697a70
Crafting table works 2020-12-15 23:33:17 -05:00
DoctorMacc 3b3e72d5c3
Initial crafting table support 2020-12-15 23:01:27 -05:00
DoctorMacc f167ed2583
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-09 11:57:46 -05:00
DoctorMacc 049242db4b
Update to 1.16.200 2020-12-09 01:12:02 -05:00
AJ Ferguson 7f4b588cdf server inventory. WORK IN PROGRESS
wip commit of implementing server authoritative inventories. there is a lot of experimental and debug code. this is NOT ready for testing or review.
2020-10-16 15:25:05 -08:00
123 changed files with 4900 additions and 1607 deletions

View File

@ -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());
}

View File

@ -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
}

View File

@ -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<Pair<Integer, Protocol>> 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());

View File

@ -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

View File

@ -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();

View File

@ -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();
}

View File

@ -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<NbtMap> 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()));
}

View File

@ -30,9 +30,9 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.CloudburstMC.Protocol</groupId>
<groupId>com.nukkitx.protocol</groupId>
<artifactId>bedrock-v422</artifactId>
<version>d41b84e86c</version>
<version>2.6.1-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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<EnchantData> 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<String> 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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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];
}
}

View File

@ -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<Inventory> 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);
}
}

View File

@ -32,6 +32,7 @@ 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;
@ -53,12 +54,15 @@ 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;
@ -70,6 +74,7 @@ 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;
@ -80,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.*;
@ -93,8 +98,7 @@ 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.*;
import java.util.concurrent.atomic.AtomicInteger;
@Getter
@ -110,16 +114,32 @@ public class GeyserSession implements CommandSender {
private BedrockClientData clientData;
private final SessionPlayerEntity playerEntity;
private PlayerInventory inventory;
private ChunkCache chunkCache;
private EntityCache entityCache;
private EntityEffectCache effectCache;
private InventoryCache inventoryCache;
private WorldCache worldCache;
private WindowCache windowCache;
private final Int2ObjectMap<TeleportCache> 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<Void> inventoryFuture;
/**
* Stores session collision
*/
@ -134,6 +154,16 @@ public class GeyserSession implements CommandSender {
*/
private final Object2LongMap<Vector3i> 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<Vector3i> lecternCache = new ArrayList<>();
@Setter
private boolean droppingLecternBook;
@Setter
private Vector2i lastChunkPosition = null;
private int renderDistance;
@ -191,9 +221,6 @@ public class GeyserSession implements CommandSender {
@Setter
private Entity ridingVehicleEntity;
@Setter
private int craftSlot = 0;
@Setter
private long lastWindowCloseTime = 0;
@ -202,10 +229,16 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastInteractedVillagerEid;
@Setter
private Int2ObjectMap<Recipe> craftingRecipes;
private final Set<String> 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<IntList> stonecutterRecipes;
/**
* The current attack speed of the player. Used for sending proper cooldown timings.
@ -338,20 +371,24 @@ public class GeyserSession implements CommandSender {
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 -> {
@ -599,7 +636,6 @@ public class GeyserSession implements CommandSender {
this.entityCache = null;
this.effectCache = null;
this.worldCache = null;
this.inventoryCache = null;
this.windowCache = null;
closed = true;
@ -712,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);

View File

@ -38,24 +38,24 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
@Override
public void translate(ContainerClosePacket packet, GeyserSession session) {
session.setLastWindowCloseTime(0);
byte windowId = packet.getId();
Inventory openInventory = session.getInventoryCache().getOpenInventory();
if (windowId == -1) { //player inventory or crafting table
if (openInventory != null) {
windowId = (byte) openInventory.getId();
} else {
windowId = 0;
session.addInventoryTask(() -> {
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);
});
}
}

View File

@ -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<FilterTextPack
@Override
public void translate(FilterTextPacket packet, GeyserSession session) {
if (session.getOpenInventory() instanceof CartographyContainer) {
// We don't want to be able to rename in the cartography table
return;
}
packet.setFromServer(true);
session.sendUpstreamPacket(packet);
if (session.getOpenInventory() instanceof AnvilContainer) {
// Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now
ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(packet.getText());
System.out.println(renameItemPacket);
session.sendDownstreamPacket(renameItemPacket);
}
}
}

View File

@ -25,7 +25,6 @@
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.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
@ -43,6 +42,8 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.connector.entity.CommandBlockMinecartEntity;
@ -50,17 +51,15 @@ import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.ItemFrameEntity;
import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.GeyserItemStack;
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.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.concurrent.TimeUnit;
@ -71,15 +70,36 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
public void translate(InventoryTransactionPacket packet, GeyserSession session) {
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()) {
@ -165,9 +185,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.setInteracting(true);
break;
case 1:
ItemStack shieldSlot = session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36);
// Handled in Entity.java
if (shieldSlot != null && shieldSlot.getId() == ItemRegistry.SHIELD.getJavaId()) {
if (session.getPlayerInventory().getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) {
break;
}

View File

@ -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.bedrock;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
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;
@Translator(packet = ItemStackRequestPacket.class)
public class BedrockItemStackRequestTranslator extends PacketTranslator<ItemStackRequestPacket> {
@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()));
}
}

View File

@ -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<LecternUpdatePacket> {
@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);
}
}
}
}
}
}

View File

@ -40,12 +40,12 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
@Override
public void translate(MobEquipmentPacket packet, GeyserSession session) {
if (!session.isSpawned() || packet.getHotbarSlot() > 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;
}
session.getInventory().setHeldItemSlot(packet.getHotbarSlot());
session.getPlayerInventory().setHeldItemSlot(packet.getHotbarSlot());
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
session.sendDownstreamPacket(changeHeldItemPacket);

View File

@ -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<EntityEventPa
session.sendUpstreamPacket(packet);
return;
case COMPLETE_TRADE:
ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData());
session.sendDownstreamPacket(selectTradePacket);
session.addInventoryTask(() -> {
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());

View File

@ -38,7 +38,10 @@ import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import com.nukkitx.protocol.bedrock.packet.InteractPacket;
import lombok.Getter;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity;
import org.geysermc.connector.entity.living.animal.horse.HorseEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -98,7 +101,7 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
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<InteractPacket>
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<InteractPacket>
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<InteractPacket>
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<InteractPacket>
}
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<InteractPacket>
} 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;
}

View File

@ -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<InventoryActionData> 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<InventoryActionData> 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);
}
}

View File

@ -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.network.translators.inventory;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import lombok.Value;
@Value
public class BedrockContainerSlot {
ContainerSlotType container;
int slot;
}

View File

@ -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<InventoryActionData> 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<ItemData> 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());
}
}
}

View File

@ -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<WindowType, InventoryTranslator> INVENTORY_TRANSLATORS = new HashMap<WindowType, InventoryTranslator>() {
{
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<InventoryActionData> 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<ItemStackRequestPacket.Request> 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<Integer> 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<Integer> 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<ItemStackResponsePacket.ContainerEntry> 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<GeyserItemStack> 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<ItemStackResponsePacket.ContainerEntry> makeContainerEntries(GeyserSession session, Inventory inventory, Set<Integer> affectedSlots) {
Map<ContainerSlotType, List<ItemStackResponsePacket.ItemEntry>> containerMap = new HashMap<>();
for (int slot : affectedSlots) {
BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot);
List<ItemStackResponsePacket.ItemEntry> list = containerMap.computeIfAbsent(bedrockSlot.getContainer(), k -> new ArrayList<>());
list.add(makeItemEntry(bedrockSlot.getSlot(), inventory.getItem(slot)));
}
List<ItemStackResponsePacket.ContainerEntry> containerEntries = new ArrayList<>();
for (Map.Entry<ContainerSlotType, List<ItemStackResponsePacket.ItemEntry>> 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
}
}

View File

@ -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<ClickAction> 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<ClickAction> 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;
}
}
}

View File

@ -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<InventoryActionData> 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<Integer> 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<ItemStack> 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;
}
}

View File

@ -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;
}

View File

@ -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<ClickAction> plan = new ArrayList<>();
private final Int2ObjectMap<GeyserItemStack> 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<ClickAction> 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<GeyserItemStack> 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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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<InventoryActionData> 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<InventoryActionData> 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);
}
}

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 {
}
}

View File

@ -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<String> 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<NbtMap> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<InventoryActionData> 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<ItemStackRequestPacket.Request> 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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<InventoryActionData> 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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<InventoryActionData> actions) {
if (actions.stream().anyMatch(a -> a.getSource().getContainerId() == -31)) {
return;
}
super.translateActions(session, inventory, actions);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<ItemData> 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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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<String> 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;
@ -147,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(
@ -154,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":
@ -209,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");

View File

@ -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<Recipe> 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<CraftingData> 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<CraftingData> 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<String> 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<String, ItemData> letterToRecipe = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> iterator = node.get("input").fields();
Iterator<Map.Entry<String, JsonNode>> iterator = node.get("inputs").fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> 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<ItemData> 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<Ingredient> 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<ItemData> 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<Ingredient> 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() {

View File

@ -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;
}

View File

@ -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<NbtMap> 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;

View File

@ -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<ServerDeclareRecipesPacket> {
@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<Recipe> recipeMap = new Int2ObjectOpenHashMap<>(RecipeRegistry.ALL_CRAFTING_RECIPES);
Int2ObjectMap<List<StoneCuttingRecipeData>> unsortedStonecutterData = new Int2ObjectOpenHashMap<>();
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
craftingDataPacket.setCleanRecipes(true);
for (Recipe recipe : packet.getRecipes()) {
@ -65,7 +73,8 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
for (ItemData[] inputs : inputCombinations) {
UUID uuid = UUID.randomUUID();
craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(),
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, networkId++));
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId));
recipeMap.put(netId++, recipe);
}
break;
}
@ -78,7 +87,8 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
UUID uuid = UUID.randomUUID();
craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(),
shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs),
Collections.singletonList(output), uuid, "crafting_table", 0, networkId++));
Collections.singletonList(output), uuid, "crafting_table", 0, netId));
recipeMap.put(netId++, recipe);
}
break;
}
@ -131,10 +141,55 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.LEATHER_DYEING_RECIPES);
break;
}
case STONECUTTING: {
StoneCuttingRecipeData stoneCuttingData = (StoneCuttingRecipeData) recipe.getData();
ItemStack ingredient = stoneCuttingData.getIngredient().getOptions()[0];
List<StoneCuttingRecipeData> 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<IntList> stonecutterRecipeMap = new Int2ObjectOpenHashMap<>();
for (Int2ObjectMap.Entry<List<StoneCuttingRecipeData>> 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

View File

@ -50,7 +50,7 @@ public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket>
// 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());

View File

@ -0,0 +1,51 @@
/*
* 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.UnlockRecipesAction;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerUnlockRecipesPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import java.util.Arrays;
/**
* Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage)
*/
@Translator(packet = ServerUnlockRecipesPacket.class)
public class JavaUnlockRecipesTranslator extends PacketTranslator<ServerUnlockRecipesPacket> {
@Override
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()));
}
}
}

View File

@ -32,6 +32,7 @@ import com.github.steveice10.opennbt.tag.builtin.*;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
@ -71,15 +72,9 @@ public class JavaPlayerActionAckTranslator extends PacketTranslator<ServerPlayer
packet.getPosition().getY(),
packet.getPosition().getZ()
));
PlayerInventory inventory = session.getInventory();
ItemStack item = inventory.getItemInHand();
ItemEntry itemEntry = null;
CompoundTag nbtData = new CompoundTag("");
if (item != null) {
itemEntry = ItemRegistry.getItem(item);
nbtData = item.getNbt();
}
double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState(), itemEntry, nbtData, session) * 20);
PlayerInventory inventory = session.getPlayerInventory();
GeyserItemStack item = inventory.getItemInHand();
double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState(), item.getItemEntry(), item.getNbt(), session) * 20);
levelEvent.setData((int) (65535 / breakTime));
session.setBreakingBlock(packet.getNewState());
session.sendUpstreamPacket(levelEvent);

View File

@ -36,12 +36,14 @@ public class JavaPlayerChangeHeldItemTranslator extends PacketTranslator<ServerP
@Override
public void translate(ServerPlayerChangeHeldItemPacket packet, GeyserSession session) {
PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket();
hotbarPacket.setContainerId(0);
hotbarPacket.setSelectedHotbarSlot(packet.getSlot());
hotbarPacket.setSelectHotbarSlot(true);
session.sendUpstreamPacket(hotbarPacket);
session.addInventoryTask(() -> {
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());
});
}
}

View File

@ -36,7 +36,6 @@ public class JavaCloseWindowTranslator extends PacketTranslator<ServerCloseWindo
@Override
public void translate(ServerCloseWindowPacket packet, GeyserSession session) {
InventoryUtils.closeWindow(session, packet.getWindowId());
InventoryUtils.closeInventory(session, packet.getWindowId());
session.addInventoryTask(() -> InventoryUtils.closeInventory(session, packet.getWindowId()));
}
}

View File

@ -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<ServerCon
@Override
public void translate(ServerConfirmTransactionPacket packet, GeyserSession session) {
if (!packet.isAccepted()) {
ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true);
session.sendDownstreamPacket(confirmPacket);
}
session.addInventoryTask(() -> {
if (!packet.isAccepted()) {
ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true);
session.sendDownstreamPacket(confirmPacket);
System.out.println(packet);
}
});
}
}

View File

@ -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<ServerOpenHorseWindowPacket> {
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<NbtMap> 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<NbtMap> 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<NbtMap> 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<NbtMap> 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()));
}
}

View File

@ -41,35 +41,36 @@ public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowP
@Override
public void translate(ServerOpenWindowPacket packet, GeyserSession session) {
if (packet.getWindowId() == 0) {
return;
}
InventoryTranslator newTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(packet.getType());
Inventory openInventory = session.getInventoryCache().getOpenInventory();
if (newTranslator == null) {
session.addInventoryTask(() -> {
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);
});
}
}

View File

@ -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<ServerSetSlotPacket>
@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());
}
});
}
}

View File

@ -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<ServerWindowItemsPacket> {
@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);
}
});
}
}

View File

@ -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<ServerWindowPropertyPacket> {
@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());
}
});
}
}

View File

@ -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<ServerNotifyCli
session.sendAdventureSettings();
// Update the crafting grid to add/remove barriers for creative inventory
PlayerInventoryTranslator.updateCraftingGrid(session, session.getInventory());
PlayerInventoryTranslator.updateCraftingGrid(session, session.getPlayerInventory());
break;
case ENTER_CREDITS:
switch ((EnterCreditsValue) packet.getValue()) {

View File

@ -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<ServerTradeListPac
@Override
public void translate(ServerTradeListPacket packet, GeyserSession session) {
Entity villager = session.getPlayerEntity();
session.setVillagerTrades(packet.getTrades());
Inventory openInventory = session.getOpenInventory();
if (!(openInventory instanceof MerchantContainer && openInventory.getId() == packet.getWindowId())) {
return;
}
MerchantContainer merchantInventory = (MerchantContainer) openInventory;
merchantInventory.setVillagerTrades(packet.getTrades());
Entity villager = merchantInventory.getVillager();
villager.getMetadata().put(EntityData.TRADE_TIER, packet.getVillagerLevel() - 1);
villager.getMetadata().put(EntityData.MAX_TRADE_TIER, 4);
villager.getMetadata().put(EntityData.TRADE_XP, packet.getExperience());
@ -64,30 +71,28 @@ public class JavaTradeListTranslator extends PacketTranslator<ServerTradeListPac
updateTradePacket.setTradeTier(packet.getVillagerLevel() - 1);
updateTradePacket.setContainerId((short) packet.getWindowId());
updateTradePacket.setContainerType(ContainerType.TRADE);
Inventory openInv = session.getInventoryCache().getOpenInventory();
String displayName;
if (openInv != null && openInv.getId() == packet.getWindowId()) {
displayName = openInv.getTitle();
//TODO: verify correct window title behavior
Entity realVillager = session.getEntityCache().getEntityByGeyserId(session.getLastInteractedVillagerEid());
if (realVillager != null && realVillager.getMetadata().containsKey(EntityData.NAMETAG) && realVillager.getMetadata().getString(EntityData.NAMETAG) != null) {
displayName = realVillager.getMetadata().getString(EntityData.NAMETAG);
} else {
Entity realVillager = session.getEntityCache().getEntityByGeyserId(session.getLastInteractedVillagerEid());
if (realVillager != null && realVillager.getMetadata().containsKey(EntityData.NAMETAG) && realVillager.getMetadata().getString(EntityData.NAMETAG) != null) {
displayName = realVillager.getMetadata().getString(EntityData.NAMETAG);
} else {
displayName = realVillager != null &&
realVillager.getEntityType() == EntityType.WANDERING_TRADER ? "Wandering Trader" : "Villager";
}
displayName = realVillager != null &&
realVillager.getEntityType() == EntityType.WANDERING_TRADER ? "Wandering Trader" : "Villager";
}
updateTradePacket.setDisplayName(displayName);
updateTradePacket.setSize(0);
updateTradePacket.setNewTradingUi(true);
updateTradePacket.setUsingEconomyTrade(true);
updateTradePacket.setPlayerUniqueEntityId(session.getPlayerEntity().getGeyserId());
updateTradePacket.setTraderUniqueEntityId(session.getPlayerEntity().getGeyserId());
updateTradePacket.setTraderUniqueEntityId(villager.getGeyserId());
NbtMapBuilder builder = NbtMap.builder();
List<NbtMap> 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));

View File

@ -44,10 +44,19 @@ public class JavaUnloadChunkTranslator extends PacketTranslator<ServerUnloadChun
Iterator<Vector3i> 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();
}
}
}
}

View File

@ -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<St
}
if (!contains) continue;
}
ItemStack itemInHand = session.getInventory().getItemInHand();
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand();
if (interactionEntry.getKey().items().length != 0) {
if (itemInHand == null || itemInHand.getId() == 0) {
if (itemInHand.isEmpty()) {
continue;
}
String handIdentifier = ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier();
String handIdentifier = itemInHand.getItemEntry().getJavaIdentifier();
boolean contains = false;
for (String itemIdentifier : interactionEntry.getKey().items()) {
if (handIdentifier.contains(itemIdentifier)) {
@ -76,7 +77,7 @@ public interface BlockSoundInteractionHandler extends SoundInteractionHandler<St
if (!contains) continue;
}
if (session.isSneaking() && !interactionEntry.getKey().ignoreSneakingWhileHolding()) {
if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() != 0) {
if (!itemInHand.isEmpty()) {
continue;
}
}

View File

@ -28,6 +28,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.entity.Entity;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
@ -61,12 +62,12 @@ public interface EntitySoundInteractionHandler extends SoundInteractionHandler<E
}
if (!contains) continue;
}
ItemStack itemInHand = session.getInventory().getItemInHand();
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand();
if (interactionEntry.getKey().items().length != 0) {
if (itemInHand == null || itemInHand.getId() == 0) {
if (itemInHand.isEmpty()) {
continue;
}
String handIdentifier = ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier();
String handIdentifier = itemInHand.getItemEntry().getJavaIdentifier();
boolean contains = false;
for (String itemIdentifier : interactionEntry.getKey().items()) {
if (handIdentifier.contains(itemIdentifier)) {
@ -77,7 +78,7 @@ public interface EntitySoundInteractionHandler extends SoundInteractionHandler<E
if (!contains) continue;
}
if (session.isSneaking() && !interactionEntry.getKey().ignoreSneakingWhileHolding()) {
if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() != 0) {
if (!itemInHand.isEmpty()) {
continue;
}
}

View File

@ -39,7 +39,7 @@ public class BucketSoundInteractionHandler implements BlockSoundInteractionHandl
@Override
public void handleInteraction(GeyserSession session, Vector3f position, String identifier) {
if (session.getBucketScheduledFuture() == null) return; // No bucket was really interacted with
String handItemIdentifier = ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier();
String handItemIdentifier = session.getPlayerInventory().getItemInHand().getItemEntry().getJavaIdentifier();
LevelSoundEventPacket soundEventPacket = new LevelSoundEventPacket();
soundEventPacket.setPosition(position);
soundEventPacket.setIdentifier(":");

View File

@ -39,7 +39,7 @@ public class MilkCowSoundInteractionHandler implements EntitySoundInteractionHan
@Override
public void handleInteraction(GeyserSession session, Vector3f position, Entity value) {
if (!ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier().equals("minecraft:bucket")) {
if (!session.getPlayerInventory().getItemInHand().getItemEntry().getJavaIdentifier().equals("minecraft:bucket")) {
return;
}
LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket();

Some files were not shown because too many files have changed in this diff Show More