mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-23 15:00:37 +01:00
lecterns
This commit is contained in:
parent
790c695b27
commit
2265de3ae9
19 changed files with 502 additions and 20 deletions
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -153,6 +153,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;
|
||||
|
|
|
@ -50,7 +50,7 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
|
|||
if (openInventory != null && windowId == openInventory.getId()) {
|
||||
System.out.println(packet);
|
||||
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
|
||||
session.getDownstream().getSession().send(closeWindowPacket);
|
||||
session.sendDownstreamPacket(closeWindowPacket);
|
||||
InventoryUtils.closeInventory(session, windowId);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -97,6 +97,9 @@ public abstract class InventoryTranslator {
|
|||
/* 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));
|
||||
|
||||
/* Lectern */
|
||||
put(WindowType.LECTERN, new LecternInventoryTranslator());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* 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());
|
||||
System.out.println(lecternContainer.getBlockEntityTag());
|
||||
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
|
||||
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);
|
||||
System.out.println(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 {
|
||||
builder.putByte("hasBook", (byte) 0);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static class LecternInventoryUpdater extends InventoryUpdater {
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,13 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
|||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.nbt.NbtMapBuilder;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.session.cache.ChunkCache;
|
||||
import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator;
|
||||
import org.geysermc.connector.utils.GameRule;
|
||||
|
||||
public class GeyserWorldManager extends WorldManager {
|
||||
|
@ -88,6 +91,29 @@ public class GeyserWorldManager extends WorldManager {
|
|||
return new int[1024];
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) {
|
||||
// Without direct server access, we can't get lectern information on-the-fly.
|
||||
// I should have set this up so it's only called when there is a book in the block state. - Camotoy
|
||||
NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, 1);
|
||||
lecternTag.putCompound("book", NbtMap.builder()
|
||||
.putByte("Count", (byte) 1)
|
||||
.putShort("Damage", (short) 0)
|
||||
.putString("Name", "minecraft:written_book")
|
||||
.putCompound("tag", NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", "")
|
||||
.build())
|
||||
.build());
|
||||
lecternTag.putInt("page", -1); // I'm surprisingly glad this exists - it forces Bedrock to stop reading immediately
|
||||
return lecternTag.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpectLecternHandled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGameRule(GeyserSession session, String name, Object value) {
|
||||
session.sendDownstreamPacket(new ClientChatPacket("/gamerule " + name + " " + value));
|
||||
|
|
|
@ -30,6 +30,7 @@ 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.setting.Difficulty;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.GameRule;
|
||||
|
||||
|
@ -106,6 +107,32 @@ public abstract class WorldManager {
|
|||
*/
|
||||
public abstract int[] getBiomeDataAt(GeyserSession session, int x, int z);
|
||||
|
||||
/**
|
||||
* Sigh. <br>
|
||||
*
|
||||
* So, on Java Edition, the lectern is an inventory. Java opens it and gets the contents of the book there.
|
||||
* On Bedrock, the lectern contents are part of the block entity tag. Therefore, Bedrock expects to have the contents
|
||||
* of the lectern ready and present in the world. If the contents are not there, it takes at least two clicks for the
|
||||
* lectern to update the tag and then present itself. <br>
|
||||
*
|
||||
* We solve this problem by querying all loaded lecterns, where possible, and sending their information in a block entity
|
||||
* tag.
|
||||
*
|
||||
* @param session the session of the player
|
||||
* @param x the x coordinate of the lectern
|
||||
* @param y the y coordinate of the lectern
|
||||
* @param z the z coordinate of the lectern
|
||||
* @param isChunkLoad if this is called during a chunk load or not. Changes behavior in certain instances.
|
||||
* @return the Bedrock lectern block entity tag. This may not be the exact block entity tag - for example, Spigot's
|
||||
* block handled must be done on the server thread, so we send the tag manually there.
|
||||
*/
|
||||
public abstract NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad);
|
||||
|
||||
/**
|
||||
* @return whether we should expect lectern data to update, or if we have to fall back on a workaround.
|
||||
*/
|
||||
public abstract boolean shouldExpectLecternHandled();
|
||||
|
||||
/**
|
||||
* Updates a gamerule value on the Java server
|
||||
*
|
||||
|
|
|
@ -42,6 +42,7 @@ public class BlockStateValues {
|
|||
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
private static final Map<String, NbtMap> FLOWER_POT_BLOCKS = new HashMap<>();
|
||||
private static final Int2BooleanMap LECTERN_BOOK_STATES = new Int2BooleanOpenHashMap();
|
||||
private static final Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap();
|
||||
private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap();
|
||||
private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap();
|
||||
|
@ -88,6 +89,11 @@ public class BlockStateValues {
|
|||
return;
|
||||
}
|
||||
|
||||
if (entry.getKey().startsWith("minecraft:lectern")) {
|
||||
LECTERN_BOOK_STATES.put(javaBlockState, entry.getKey().contains("has_book=true"));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode notePitch = entry.getValue().get("note_pitch");
|
||||
if (notePitch != null) {
|
||||
NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue());
|
||||
|
@ -197,6 +203,10 @@ public class BlockStateValues {
|
|||
return FLOWER_POT_BLOCKS;
|
||||
}
|
||||
|
||||
public static Int2BooleanMap getLecternBookStates() {
|
||||
return LECTERN_BOOK_STATES;
|
||||
}
|
||||
|
||||
/**
|
||||
* The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock.
|
||||
* This gives an integer pitch that Bedrock can use.
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.geysermc.connector.entity.Entity;
|
|||
import org.geysermc.connector.entity.ItemFrameEntity;
|
||||
import org.geysermc.connector.entity.player.SkullPlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity;
|
||||
|
@ -282,7 +283,6 @@ public class ChunkUtils {
|
|||
}
|
||||
|
||||
String id = BlockEntityUtils.getBedrockBlockEntityId(tagName);
|
||||
BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id);
|
||||
Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue());
|
||||
|
||||
// Get Java blockstate ID from block entity position
|
||||
|
@ -292,6 +292,14 @@ public class ChunkUtils {
|
|||
blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF);
|
||||
}
|
||||
|
||||
if (tagName.equals("minecraft:lectern") && BlockStateValues.getLecternBookStates().get(blockState)) {
|
||||
// If getLecternBookStates is false, let's just treat it like a normal block entity
|
||||
bedrockBlockEntities[i] = session.getConnector().getWorldManager().getLecternDataAt(session, pos.getX(), pos.getY(), pos.getZ(), true);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id);
|
||||
bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState);
|
||||
|
||||
// Check for custom skulls
|
||||
|
@ -388,6 +396,29 @@ public class ChunkUtils {
|
|||
}
|
||||
session.sendUpstreamPacket(waterPacket);
|
||||
|
||||
if (BlockStateValues.getLecternBookStates().containsKey(blockState)) {
|
||||
boolean lecternCachedHasBook = session.getLecternCache().contains(position);
|
||||
boolean newLecternHasBook = BlockStateValues.getLecternBookStates().get(blockState);
|
||||
if (!session.getConnector().getWorldManager().shouldExpectLecternHandled() && lecternCachedHasBook != newLecternHasBook) {
|
||||
// Refresh the block entirely - it either has a book or no longer has a book
|
||||
session.getConnector().getLogger().warning("Refreshing lectern entirely");
|
||||
NbtMap newLecternTag;
|
||||
if (newLecternHasBook) {
|
||||
newLecternTag = session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
|
||||
} else {
|
||||
session.getLecternCache().remove(position);
|
||||
newLecternTag = LecternInventoryTranslator.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build();
|
||||
}
|
||||
BlockEntityUtils.updateBlockEntity(session, newLecternTag, position);
|
||||
} else {
|
||||
// As of right now, no tag can be added asynchronously
|
||||
session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
|
||||
}
|
||||
} else {
|
||||
// Lectern has been destroyed, if it existed
|
||||
session.getLecternCache().remove(position);
|
||||
}
|
||||
|
||||
// Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag
|
||||
// This is the only place I could find that interacts with the Java block state and block updates
|
||||
// Iterates through all block entity translators and determines if the block state needs to be saved
|
||||
|
|
|
@ -66,6 +66,7 @@ public class DimensionUtils {
|
|||
|
||||
session.getEntityCache().removeAllEntities();
|
||||
session.getItemFrameCache().clear();
|
||||
session.getLecternCache().clear();
|
||||
session.getSkullCache().clear();
|
||||
if (session.getPendingDimSwitches().getAndIncrement() > 0) {
|
||||
ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
|
||||
|
|
Loading…
Reference in a new issue