mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Folia support and preparations for future changes
This commit is contained in:
parent
cd80ee893c
commit
e2535108e6
36 changed files with 999 additions and 422 deletions
|
@ -25,6 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.platform.fabric.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.nbt.NbtMapBuilder;
|
||||
|
@ -40,16 +41,16 @@ import net.minecraft.world.item.WrittenBookItem;
|
|||
import net.minecraft.world.level.block.entity.BannerBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.LecternBlockEntity;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.geysermc.erosion.util.LecternUtils;
|
||||
import org.geysermc.geyser.level.GeyserWorldManager;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GeyserFabricWorldManager extends GeyserWorldManager {
|
||||
private final MinecraftServer server;
|
||||
|
@ -59,69 +60,91 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpectLecternHandled() {
|
||||
public boolean shouldExpectLecternHandled(GeyserSession session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) {
|
||||
Runnable lecternGet = () -> {
|
||||
// Mostly a reimplementation of Spigot lectern support
|
||||
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
|
||||
server.execute(() -> {
|
||||
ServerPlayer player = getPlayer(session);
|
||||
if (player != null) {
|
||||
BlockEntity blockEntity = player.level.getBlockEntity(new BlockPos(x, y, z));
|
||||
if (!(blockEntity instanceof LecternBlockEntity lectern)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lectern.hasBook()) {
|
||||
if (!isChunkLoad) {
|
||||
BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack book = lectern.getBook();
|
||||
int pageCount = WrittenBookItem.getPageCount(book);
|
||||
boolean hasBookPages = pageCount > 0;
|
||||
NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1);
|
||||
lecternTag.putInt("page", lectern.getPage() / 2);
|
||||
NbtMapBuilder bookTag = NbtMap.builder()
|
||||
.putByte("Count", (byte) book.getCount())
|
||||
.putShort("Damage", (short) 0)
|
||||
.putString("Name", "minecraft:writable_book");
|
||||
List<NbtMap> pages = new ArrayList<>(hasBookPages ? pageCount : 1);
|
||||
if (hasBookPages && WritableBookItem.makeSureTagIsValid(book.getTag())) {
|
||||
ListTag listTag = book.getTag().getList("pages", 8);
|
||||
|
||||
for (int i = 0; i < listTag.size(); i++) {
|
||||
String page = listTag.getString(i);
|
||||
NbtMapBuilder pageBuilder = NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", page);
|
||||
pages.add(pageBuilder.build());
|
||||
}
|
||||
} else {
|
||||
// Empty page
|
||||
NbtMapBuilder pageBuilder = NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", "");
|
||||
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));
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if (isChunkLoad) {
|
||||
// Hacky hacks to allow lectern loading to be delayed
|
||||
session.scheduleInEventLoop(() -> server.execute(lecternGet), 1, TimeUnit.SECONDS);
|
||||
} else {
|
||||
server.execute(lecternGet);
|
||||
|
||||
LevelChunk chunk = player.getLevel().getChunk(x, z);
|
||||
final int chunkBlockX = x << 4;
|
||||
final int chunkBlockZ = z << 4;
|
||||
for (int i = 0; i < blockEntityInfos.size(); i++) {
|
||||
BlockEntityInfo blockEntityInfo = blockEntityInfos.get(i);
|
||||
BlockEntity blockEntity = chunk.getBlockEntity(new BlockPos(chunkBlockX + blockEntityInfo.getX(),
|
||||
blockEntityInfo.getY(), chunkBlockZ + blockEntityInfo.getZ()));
|
||||
sendLecternData(session, blockEntity, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLecternData(GeyserSession session, int x, int y, int z) {
|
||||
server.execute(() -> {
|
||||
ServerPlayer player = getPlayer(session);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockEntity blockEntity = player.level.getBlockEntity(new BlockPos(x, y, z));
|
||||
sendLecternData(session, blockEntity, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void sendLecternData(GeyserSession session, BlockEntity blockEntity, boolean isChunkLoad) {
|
||||
if (!(blockEntity instanceof LecternBlockEntity lectern)) {
|
||||
return;
|
||||
}
|
||||
return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build();
|
||||
|
||||
int x = blockEntity.getBlockPos().getX();
|
||||
int y = blockEntity.getBlockPos().getY();
|
||||
int z = blockEntity.getBlockPos().getZ();
|
||||
|
||||
if (!lectern.hasBook()) {
|
||||
if (!isChunkLoad) {
|
||||
BlockEntityUtils.updateBlockEntity(session, LecternUtils.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack book = lectern.getBook();
|
||||
int pageCount = WrittenBookItem.getPageCount(book);
|
||||
boolean hasBookPages = pageCount > 0;
|
||||
NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1);
|
||||
lecternTag.putInt("page", lectern.getPage() / 2);
|
||||
NbtMapBuilder bookTag = NbtMap.builder()
|
||||
.putByte("Count", (byte) book.getCount())
|
||||
.putShort("Damage", (short) 0)
|
||||
.putString("Name", "minecraft:writable_book");
|
||||
List<NbtMap> pages = new ArrayList<>(hasBookPages ? pageCount : 1);
|
||||
if (hasBookPages && WritableBookItem.makeSureTagIsValid(book.getTag())) {
|
||||
ListTag listTag = book.getTag().getList("pages", 8);
|
||||
|
||||
for (int i = 0; i < listTag.size(); i++) {
|
||||
String page = listTag.getString(i);
|
||||
NbtMapBuilder pageBuilder = NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", page);
|
||||
pages.add(pageBuilder.build());
|
||||
}
|
||||
} else {
|
||||
// Empty page
|
||||
NbtMapBuilder pageBuilder = NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", "");
|
||||
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));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
dependencies {
|
||||
api(projects.core)
|
||||
api(libs.erosion.bukkit.common) {
|
||||
isTransitive = false
|
||||
}
|
||||
|
||||
implementation(libs.adapters.spigot)
|
||||
|
||||
|
@ -7,8 +10,8 @@ dependencies {
|
|||
|
||||
implementation(libs.adventure.text.serializer.bungeecord)
|
||||
|
||||
// Both paper-api and paper-mojangapi only provide Java 17 versions for 1.19
|
||||
compileOnly(libs.paper.api) {
|
||||
// Both folia-api and paper-mojangapi only provide Java 17 versions for 1.19
|
||||
compileOnly(libs.folia.api) {
|
||||
attributes {
|
||||
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
|
||||
}
|
||||
|
|
|
@ -59,13 +59,13 @@ public final class GeyserPaperPingPassthrough implements IGeyserPingPassthrough
|
|||
// runtime because we still have to shade in our own Adventure class. For now.
|
||||
PaperServerListPingEvent event;
|
||||
if (OLD_CONSTRUCTOR != null) {
|
||||
// Approximately pre-1.19
|
||||
// 1.19, removed in 1.19.4
|
||||
event = OLD_CONSTRUCTOR.newInstance(new GeyserStatusClient(inetSocketAddress),
|
||||
Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(),
|
||||
Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null);
|
||||
} else {
|
||||
event = new PaperServerListPingEvent(new GeyserStatusClient(inetSocketAddress),
|
||||
Bukkit.getMotd(), Bukkit.shouldSendChatPreviews(), Bukkit.getOnlinePlayers().size(),
|
||||
Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(),
|
||||
Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null);
|
||||
}
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
|
|
|
@ -66,7 +66,7 @@ public class GeyserSpigotPingPassthrough implements IGeyserPingPassthrough {
|
|||
private static class GeyserPingEvent extends ServerListPingEvent {
|
||||
|
||||
public GeyserPingEvent(InetAddress address, String motd, int numPlayers, int maxPlayers) {
|
||||
super(address, motd, Bukkit.shouldSendChatPreviews(), numPlayers, maxPlayers);
|
||||
super("", address, motd, numPlayers, maxPlayers);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -60,16 +60,16 @@ public final class ReflectedNames {
|
|||
}
|
||||
|
||||
static Constructor<PaperServerListPingEvent> getOldPaperPingConstructor() {
|
||||
if (getConstructor(PaperServerListPingEvent.class, StatusClient.class, String.class, boolean.class, int.class,
|
||||
if (getConstructor(PaperServerListPingEvent.class, StatusClient.class, String.class, int.class,
|
||||
int.class, String.class, int.class, CachedServerIcon.class) != null) {
|
||||
// @NotNull StatusClient client, @NotNull String motd, boolean shouldSendChatPreviews, int numPlayers, int maxPlayers,
|
||||
// @NotNull StatusClient client, @NotNull String motd, int numPlayers, int maxPlayers,
|
||||
// @NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon
|
||||
// New constructor is present
|
||||
return null;
|
||||
}
|
||||
// @NotNull StatusClient client, @NotNull String motd, int numPlayers, int maxPlayers,
|
||||
// @NotNull StatusClient client, @NotNull String motd, boolean shouldSendChatPreviews, int numPlayers, int maxPlayers,
|
||||
// @NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon
|
||||
return getConstructor(PaperServerListPingEvent.class, StatusClient.class, String.class, int.class, int.class,
|
||||
return getConstructor(PaperServerListPingEvent.class, StatusClient.class, String.class, boolean.class, int.class, int.class,
|
||||
String.class, int.class, CachedServerIcon.class);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ package org.geysermc.geyser.platform.spigot.world;
|
|||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
@ -85,7 +85,7 @@ public class GeyserPistonListener implements Listener {
|
|||
PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING;
|
||||
boolean sticky = event.isSticky();
|
||||
|
||||
Object2IntMap<Vector3i> attachedBlocks = new Object2IntOpenHashMap<>();
|
||||
Object2IntMap<Vector3i> attachedBlocks = new Object2IntArrayMap<>();
|
||||
boolean blocksFilled = false;
|
||||
|
||||
for (Map.Entry<UUID, GeyserSession> entry : geyser.getSessionManager().getSessions().entrySet()) {
|
||||
|
|
|
@ -25,33 +25,28 @@
|
|||
|
||||
package org.geysermc.geyser.platform.spigot.world.manager;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.nbt.NbtMapBuilder;
|
||||
import com.nukkitx.nbt.NbtType;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.*;
|
||||
import org.bukkit.block.banner.Pattern;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.geysermc.erosion.bukkit.BukkitLecterns;
|
||||
import org.geysermc.erosion.bukkit.BukkitUtils;
|
||||
import org.geysermc.erosion.bukkit.PickBlockUtils;
|
||||
import org.geysermc.erosion.bukkit.SchedulerUtils;
|
||||
import org.geysermc.geyser.level.GameRule;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.item.nbt.BannerTranslator;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
@ -60,9 +55,11 @@ import java.util.concurrent.CompletableFuture;
|
|||
*/
|
||||
public class GeyserSpigotWorldManager extends WorldManager {
|
||||
private final Plugin plugin;
|
||||
private final BukkitLecterns lecterns;
|
||||
|
||||
public GeyserSpigotWorldManager(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.lecterns = new BukkitLecterns(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -81,6 +78,12 @@ public class GeyserSpigotWorldManager extends WorldManager {
|
|||
}
|
||||
|
||||
public int getBlockNetworkId(Block block) {
|
||||
if (SchedulerUtils.FOLIA && !Bukkit.isOwnedByCurrentRegion(block)) {
|
||||
// Terrible behavior, but this is basically what's always been happening behind the scenes anyway.
|
||||
CompletableFuture<String> blockData = new CompletableFuture<>();
|
||||
Bukkit.getRegionScheduler().execute(this.plugin, block.getLocation(), () -> blockData.complete(block.getBlockData().getAsString()));
|
||||
return BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(blockData.join(), BlockStateValues.JAVA_AIR_ID);
|
||||
}
|
||||
return BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID);
|
||||
}
|
||||
|
||||
|
@ -90,71 +93,64 @@ public class GeyserSpigotWorldManager extends WorldManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) {
|
||||
// Run as a task to prevent async issues
|
||||
Runnable lecternInfoGet = () -> {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
|
||||
if (!(block.getState() instanceof Lectern lectern)) {
|
||||
session.getGeyser().getLogger().error("Lectern expected at: " + Vector3i.from(x, y, z).toString() + " but was not! " + block.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack itemStack = lectern.getInventory().getItem(0);
|
||||
if (itemStack == null || !(itemStack.getItemMeta() instanceof BookMeta 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;
|
||||
}
|
||||
|
||||
// On the count: allow the book to show/open even there are no pages. We know there is a book here, after all, and this matches Java behavior
|
||||
boolean hasBookPages = bookMeta.getPageCount() > 0;
|
||||
NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? bookMeta.getPageCount() : 1);
|
||||
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<>(bookMeta.getPageCount());
|
||||
if (hasBookPages) {
|
||||
for (String page : bookMeta.getPages()) {
|
||||
NbtMapBuilder pageBuilder = NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", page);
|
||||
pages.add(pageBuilder.build());
|
||||
}
|
||||
} else {
|
||||
// Empty page
|
||||
NbtMapBuilder pageBuilder = NbtMap.builder()
|
||||
.putString("photoname", "")
|
||||
.putString("text", "");
|
||||
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));
|
||||
};
|
||||
|
||||
if (isChunkLoad) {
|
||||
// Delay to ensure the chunk is sent first, and then the lectern data
|
||||
Bukkit.getScheduler().runTaskLater(this.plugin, lecternInfoGet, 5);
|
||||
} else {
|
||||
Bukkit.getScheduler().runTask(this.plugin, lecternInfoGet);
|
||||
public void sendLecternData(GeyserSession session, int x, int y, int z) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
|
||||
// Run as a task to prevent async issues
|
||||
SchedulerUtils.runTask(this.plugin, () -> sendLecternData(session, block, false), block);
|
||||
}
|
||||
|
||||
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return;
|
||||
}
|
||||
if (SchedulerUtils.FOLIA) {
|
||||
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getRegionScheduler().execute(this.plugin, bukkitPlayer.getWorld(), x, z, () ->
|
||||
sendLecternData(session, chunk, blockEntityInfos));
|
||||
} else {
|
||||
Bukkit.getScheduler().runTask(this.plugin, () -> {
|
||||
Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
sendLecternData(session, chunk, blockEntityInfos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Chunk getChunk(World world, int x, int z) {
|
||||
if (!world.isChunkLoaded(x, z)) {
|
||||
return null;
|
||||
}
|
||||
return world.getChunkAt(x, z);
|
||||
}
|
||||
|
||||
private void sendLecternData(GeyserSession session, Chunk chunk, List<BlockEntityInfo> blockEntityInfos) {
|
||||
for (int i = 0; i < blockEntityInfos.size(); i++) {
|
||||
BlockEntityInfo info = blockEntityInfos.get(i);
|
||||
Block block = chunk.getBlock(info.getX(), info.getY(), info.getZ());
|
||||
sendLecternData(session, block, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendLecternData(GeyserSession session, Block block, boolean isChunkLoad) {
|
||||
NbtMap blockEntityTag = this.lecterns.getLecternData(block, isChunkLoad);
|
||||
if (blockEntityTag != null) {
|
||||
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, BukkitUtils.getVector(block.getLocation()));
|
||||
}
|
||||
return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); // Will be updated later
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpectLecternHandled() {
|
||||
public boolean shouldExpectLecternHandled(GeyserSession session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -184,42 +180,18 @@ public class GeyserSpigotWorldManager extends WorldManager {
|
|||
@Override
|
||||
public CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
|
||||
CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>();
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
|
||||
future.complete(null);
|
||||
return future;
|
||||
}
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
|
||||
// Paper 1.19.3 complains about async access otherwise.
|
||||
// java.lang.IllegalStateException: Tile is null, asynchronous access?
|
||||
Bukkit.getScheduler().runTask(this.plugin, () -> {
|
||||
Player bukkitPlayer;
|
||||
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
|
||||
future.complete(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
|
||||
BlockState state = block.getState();
|
||||
if (state instanceof Banner banner) {
|
||||
ListTag list = new ListTag("Patterns");
|
||||
for (int i = 0; i < banner.numberOfPatterns(); i++) {
|
||||
Pattern pattern = banner.getPattern(i);
|
||||
list.add(BannerTranslator.getJavaPatternTag(pattern.getPattern().getIdentifier(), pattern.getColor().ordinal()));
|
||||
}
|
||||
|
||||
CompoundTag root = addToBlockEntityTag(list);
|
||||
|
||||
future.complete(root);
|
||||
return;
|
||||
}
|
||||
future.complete(null);
|
||||
});
|
||||
SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
|
||||
return future;
|
||||
}
|
||||
|
||||
private CompoundTag addToBlockEntityTag(Tag tag) {
|
||||
CompoundTag compoundTag = new CompoundTag("");
|
||||
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag");
|
||||
blockEntityTag.put(tag);
|
||||
compoundTag.put(blockEntityTag);
|
||||
return compoundTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id
|
||||
* to the current one.
|
||||
|
|
|
@ -5,6 +5,7 @@ website: ${url}
|
|||
version: ${version}
|
||||
softdepend: ["ViaVersion", "floodgate"]
|
||||
api-version: 1.13
|
||||
folia-supported: true
|
||||
commands:
|
||||
geyser:
|
||||
description: The main command for Geyser.
|
||||
|
|
|
@ -49,6 +49,10 @@ dependencies {
|
|||
// Adventure text serialization
|
||||
api(libs.bundles.adventure)
|
||||
|
||||
api(libs.erosion.common) {
|
||||
isTransitive = false
|
||||
}
|
||||
|
||||
// Test
|
||||
testImplementation(libs.junit)
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.geysermc.api.Geyser;
|
|||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
import org.geysermc.erosion.packet.Packets;
|
||||
import org.geysermc.floodgate.crypto.AesCipher;
|
||||
import org.geysermc.floodgate.crypto.AesKeyProducer;
|
||||
import org.geysermc.floodgate.crypto.Base64Topping;
|
||||
|
@ -67,6 +68,7 @@ import org.geysermc.geyser.api.network.RemoteServer;
|
|||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.erosion.UnixSocketClientListener;
|
||||
import org.geysermc.geyser.event.GeyserEventBus;
|
||||
import org.geysermc.geyser.extension.GeyserExtensionManager;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
|
@ -140,6 +142,8 @@ public class GeyserImpl implements GeyserApi {
|
|||
private FloodgateSkinUploader skinUploader;
|
||||
private NewsHandler newsHandler;
|
||||
|
||||
private UnixSocketClientListener erosionUnixListener;
|
||||
|
||||
private volatile boolean shuttingDown = false;
|
||||
|
||||
private ScheduledExecutorService scheduledThread;
|
||||
|
@ -293,6 +297,14 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
this.newsHandler = new NewsHandler(BRANCH, this.buildNumber());
|
||||
|
||||
Packets.initGeyser();
|
||||
|
||||
if (Epoll.isAvailable()) {
|
||||
this.erosionUnixListener = new UnixSocketClientListener();
|
||||
} else {
|
||||
logger.debug("Epoll is not available; Erosion's Unix socket handling will not work.");
|
||||
}
|
||||
|
||||
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
|
||||
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
|
||||
|
||||
|
@ -570,6 +582,10 @@ public class GeyserImpl implements GeyserApi {
|
|||
newsHandler.shutdown();
|
||||
this.commandManager().getCommands().clear();
|
||||
|
||||
if (this.erosionUnixListener != null) {
|
||||
this.erosionUnixListener.close();
|
||||
}
|
||||
|
||||
ResourcePack.PACKS.clear();
|
||||
|
||||
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
|
||||
|
|
|
@ -30,12 +30,11 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.erosion.util.BlockPositionIterator;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.level.block.BlockPositionIterator;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
import org.geysermc.geyser.util.BlockUtils;
|
||||
|
|
|
@ -40,11 +40,12 @@ import org.geysermc.geyser.session.GeyserSession;
|
|||
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ItemEntity extends ThrowableEntity {
|
||||
protected ItemData item;
|
||||
|
||||
private int waterLevel = -1;
|
||||
private CompletableFuture<Integer> waterLevel = CompletableFuture.completedFuture(-1);
|
||||
|
||||
public ItemEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
|
@ -111,15 +112,15 @@ public class ItemEntity extends ThrowableEntity {
|
|||
@Override
|
||||
protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||
float offset = definition.offset();
|
||||
if (waterLevel == 0) { // Item is in a full block of water
|
||||
if (waterLevel.join() == 0) { // Item is in a full block of water
|
||||
// Move the item entity down so it doesn't float above the water
|
||||
offset = -definition.offset();
|
||||
}
|
||||
super.moveAbsoluteImmediate(position.add(0, offset, 0), 0, 0, 0, isOnGround, teleported);
|
||||
this.position = position;
|
||||
|
||||
int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt());
|
||||
waterLevel = BlockStateValues.getWaterLevel(block);
|
||||
waterLevel = session.getGeyser().getWorldManager().getBlockAtAsync(session, position.getFloorX(), position.getFloorY(), position.getFloorZ())
|
||||
.thenApply(BlockStateValues::getWaterLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -144,6 +145,6 @@ public class ItemEntity extends ThrowableEntity {
|
|||
|
||||
@Override
|
||||
protected boolean isInWater() {
|
||||
return waterLevel != -1;
|
||||
return waterLevel.join() != -1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,12 +34,13 @@ import org.geysermc.geyser.level.block.BlockStateValues;
|
|||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class SquidEntity extends WaterEntity implements Tickable {
|
||||
private float targetPitch;
|
||||
private float targetYaw;
|
||||
|
||||
private boolean inWater;
|
||||
private CompletableFuture<Boolean> inWater = CompletableFuture.completedFuture(Boolean.FALSE);
|
||||
|
||||
public SquidEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
|
@ -50,7 +51,7 @@ public class SquidEntity extends WaterEntity implements Tickable {
|
|||
boolean pitchChanged;
|
||||
boolean yawChanged;
|
||||
float oldPitch = pitch;
|
||||
if (inWater) {
|
||||
if (inWater.join()) {
|
||||
float oldYaw = yaw;
|
||||
pitch += (targetPitch - pitch) * 0.1f;
|
||||
yaw += (targetYaw - yaw) * 0.1f;
|
||||
|
@ -93,7 +94,7 @@ public class SquidEntity extends WaterEntity implements Tickable {
|
|||
@Override
|
||||
public void setYaw(float yaw) {
|
||||
// Let the Java server control yaw when the squid is out of water
|
||||
if (!inWater) {
|
||||
if (!inWater.join()) {
|
||||
this.yaw = yaw;
|
||||
}
|
||||
}
|
||||
|
@ -127,10 +128,10 @@ public class SquidEntity extends WaterEntity implements Tickable {
|
|||
|
||||
private void checkInWater() {
|
||||
if (getFlag(EntityFlag.RIDING)) {
|
||||
inWater = false;
|
||||
inWater = CompletableFuture.completedFuture(false);
|
||||
} else {
|
||||
int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt());
|
||||
inWater = BlockStateValues.getWaterLevel(block) != -1;
|
||||
inWater = session.getGeyser().getWorldManager().getBlockAtAsync(session, position.toInt())
|
||||
.thenApply(block -> BlockStateValues.getWaterLevel(block) != -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.erosion;
|
||||
|
||||
import org.geysermc.erosion.packet.geyserbound.*;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class AbstractGeyserboundPacketHandler implements GeyserboundPacketHandler {
|
||||
protected final GeyserSession session;
|
||||
|
||||
public AbstractGeyserboundPacketHandler(GeyserSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBatchBlockId(GeyserboundBatchBlockIdPacket packet) {
|
||||
illegalPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBlockEntity(GeyserboundBlockEntityPacket packet) {
|
||||
illegalPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBlockId(GeyserboundBlockIdPacket packet) {
|
||||
illegalPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBlockLookupFail(GeyserboundBlockLookupFailPacket packet) {
|
||||
illegalPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBlockPlace(GeyserboundBlockPlacePacket packet) {
|
||||
illegalPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePistonEvent(GeyserboundPistonEventPacket packet) {
|
||||
illegalPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePickBlock(GeyserboundPickBlockPacket packet) {
|
||||
illegalPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this handler actually listening to any packets?
|
||||
*/
|
||||
public abstract boolean isActive();
|
||||
|
||||
@Nullable
|
||||
public abstract GeyserboundPacketHandlerImpl getAsActive();
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
protected final void illegalPacket(GeyserboundPacket packet) {
|
||||
session.getGeyser().getLogger().warning("Illegal packet sent from backend server! " + packet);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.erosion;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import org.geysermc.erosion.Constants;
|
||||
import org.geysermc.erosion.packet.ErosionPacketSender;
|
||||
import org.geysermc.erosion.packet.Packets;
|
||||
import org.geysermc.erosion.packet.backendbound.BackendboundPacket;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.PluginMessageUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public record GeyserErosionPacketSender(GeyserSession session) implements ErosionPacketSender<BackendboundPacket> {
|
||||
|
||||
@Override
|
||||
public void sendPacket(BackendboundPacket packet) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
try {
|
||||
Packets.encode(buf, packet);
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
PluginMessageUtils.sendMessage(session, Constants.PLUGIN_MESSAGE, bytes);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChannel(Channel channel) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.erosion;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import org.geysermc.erosion.netty.NettyPacketSender;
|
||||
import org.geysermc.erosion.packet.ErosionPacketHandler;
|
||||
import org.geysermc.erosion.packet.geyserbound.GeyserboundHandshakePacket;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class GeyserboundHandshakePacketHandler extends AbstractGeyserboundPacketHandler {
|
||||
|
||||
public GeyserboundHandshakePacketHandler(GeyserSession session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHandshake(GeyserboundHandshakePacket packet) {
|
||||
boolean useTcp = packet.getTransportType().getSocketAddress() == null;
|
||||
GeyserboundPacketHandlerImpl handler = new GeyserboundPacketHandlerImpl(session, useTcp ? new GeyserErosionPacketSender(session) : new NettyPacketSender<>());
|
||||
session.setErosionHandler(handler);
|
||||
if (!useTcp) {
|
||||
if (session.getGeyser().getErosionUnixListener() == null) {
|
||||
session.disconnect("Erosion configurations using Unix socket handling are not supported on this hardware!");
|
||||
return;
|
||||
}
|
||||
session.getGeyser().getErosionUnixListener().createClient(handler, packet.getTransportType().getSocketAddress());
|
||||
} else {
|
||||
handler.onConnect();
|
||||
}
|
||||
session.ensureInEventLoop(() -> session.getChunkCache().clear());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GeyserboundPacketHandlerImpl getAsActive() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErosionPacketHandler setChannel(Channel channel) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.erosion;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import io.netty.channel.Channel;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrays;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.erosion.packet.ErosionPacketHandler;
|
||||
import org.geysermc.erosion.packet.ErosionPacketSender;
|
||||
import org.geysermc.erosion.packet.backendbound.BackendboundInitializePacket;
|
||||
import org.geysermc.erosion.packet.backendbound.BackendboundPacket;
|
||||
import org.geysermc.erosion.packet.geyserbound.*;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacketHandler {
|
||||
private final ErosionPacketSender<BackendboundPacket> packetSender;
|
||||
@Setter
|
||||
private CompletableFuture<Integer> pendingLookup = null;
|
||||
@Getter
|
||||
private final Int2ObjectMap<CompletableFuture<Integer>> asyncPendingLookups = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>(4));
|
||||
@Setter
|
||||
private CompletableFuture<int[]> pendingBatchLookup = null;
|
||||
@Setter
|
||||
private CompletableFuture<CompoundTag> pickBlockLookup = null;
|
||||
|
||||
private final AtomicInteger nextTransactionId = new AtomicInteger(1);
|
||||
|
||||
public GeyserboundPacketHandlerImpl(GeyserSession session, ErosionPacketSender<BackendboundPacket> packetSender) {
|
||||
super(session);
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBatchBlockId(GeyserboundBatchBlockIdPacket packet) {
|
||||
if (this.pendingBatchLookup != null) {
|
||||
this.pendingBatchLookup.complete(packet.getBlocks());
|
||||
} else {
|
||||
session.getGeyser().getLogger().warning("Batch block ID packet received with no future to complete.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBlockEntity(GeyserboundBlockEntityPacket packet) {
|
||||
NbtMap nbt = packet.getNbt();
|
||||
BlockEntityUtils.updateBlockEntity(session, nbt, Vector3i.from(nbt.getInt("x"), nbt.getInt("y"), nbt.getInt("z")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBlockId(GeyserboundBlockIdPacket packet) {
|
||||
if (packet.getTransactionId() == 0) {
|
||||
if (this.pendingLookup != null) {
|
||||
this.pendingLookup.complete(packet.getBlockId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
CompletableFuture<Integer> future = this.asyncPendingLookups.remove(packet.getTransactionId());
|
||||
if (future != null) {
|
||||
future.complete(packet.getBlockId());
|
||||
return;
|
||||
}
|
||||
session.getGeyser().getLogger().warning("Block ID packet received with no future to complete.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBlockLookupFail(GeyserboundBlockLookupFailPacket packet) {
|
||||
if (packet.getTransactionId() == 0) {
|
||||
if (this.pendingBatchLookup != null) {
|
||||
this.pendingBatchLookup.complete(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
int transactionId = packet.getTransactionId() - 1;
|
||||
if (transactionId == 0) {
|
||||
if (this.pendingLookup != null) {
|
||||
this.pendingLookup.complete(0);
|
||||
}
|
||||
}
|
||||
CompletableFuture<Integer> future = this.asyncPendingLookups.remove(transactionId);
|
||||
if (future != null) {
|
||||
future.complete(BlockStateValues.JAVA_AIR_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBlockPlace(GeyserboundBlockPlacePacket packet) {
|
||||
LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket();
|
||||
placeBlockSoundPacket.setSound(SoundEvent.PLACE);
|
||||
placeBlockSoundPacket.setPosition(packet.getPos().toFloat());
|
||||
placeBlockSoundPacket.setBabySound(false);
|
||||
placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(packet.getBlockId()));
|
||||
placeBlockSoundPacket.setIdentifier(":");
|
||||
session.sendUpstreamPacket(placeBlockSoundPacket);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
session.setLastBlockPlacedId(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePickBlock(GeyserboundPickBlockPacket packet) {
|
||||
if (this.pickBlockLookup != null) {
|
||||
this.pickBlockLookup.complete(packet.getTag());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePistonEvent(GeyserboundPistonEventPacket packet) {
|
||||
Direction orientation = BlockStateValues.getPistonOrientation(packet.getBlockId());
|
||||
Vector3i position = packet.getPos();
|
||||
boolean isExtend = packet.isExtend();
|
||||
|
||||
var stream = packet.getAttachedBlocks()
|
||||
.object2IntEntrySet()
|
||||
.stream()
|
||||
.filter(entry -> BlockStateValues.canPistonMoveBlock(entry.getIntValue(), isExtend));
|
||||
Object2IntMap<Vector3i> attachedBlocks = new Object2IntArrayMap<>();
|
||||
stream.forEach(entry -> attachedBlocks.put(entry.getKey(), entry.getIntValue()));
|
||||
|
||||
session.executeInEventLoop(() -> {
|
||||
PistonCache pistonCache = session.getPistonCache();
|
||||
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos ->
|
||||
new PistonBlockEntity(session, position, orientation, packet.isSticky(), !isExtend));
|
||||
blockEntity.setAction(isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING, attachedBlocks);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHandshake(GeyserboundHandshakePacket packet) {
|
||||
this.close();
|
||||
var handler = new GeyserboundHandshakePacketHandler(this.session);
|
||||
session.setErosionHandler(handler);
|
||||
handler.handleHandshake(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserboundPacketHandlerImpl getAsActive() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnect() {
|
||||
sendPacket(new BackendboundInitializePacket(session.getPlayerEntity().getUuid(), GameProtocol.getJavaProtocolVersion()));
|
||||
}
|
||||
|
||||
public void sendPacket(BackendboundPacket packet) {
|
||||
this.packetSender.sendPacket(packet);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.packetSender.close();
|
||||
}
|
||||
|
||||
public int getNextTransactionId() {
|
||||
return nextTransactionId.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErosionPacketHandler setChannel(Channel channel) {
|
||||
this.packetSender.setChannel(channel);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.erosion;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollDomainSocketChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import org.geysermc.erosion.netty.impl.AbstractUnixSocketListener;
|
||||
import org.geysermc.erosion.packet.geyserbound.GeyserboundPacketHandler;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
public final class UnixSocketClientListener extends AbstractUnixSocketListener {
|
||||
private EventLoopGroup eventLoopGroup;
|
||||
|
||||
public void initializeEventLoopGroup() {
|
||||
if (this.eventLoopGroup == null) {
|
||||
this.eventLoopGroup = new EpollEventLoopGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public void createClient(GeyserboundPacketHandler handler, SocketAddress address) {
|
||||
initializeEventLoopGroup();
|
||||
(new Bootstrap()
|
||||
.channel(EpollDomainSocketChannel.class)
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) {
|
||||
initPipeline(ch, handler);
|
||||
}
|
||||
})
|
||||
.group(this.eventLoopGroup.next())
|
||||
.connect(address))
|
||||
.syncUninterruptibly()
|
||||
.channel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.eventLoopGroup != null) {
|
||||
this.eventLoopGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,19 +25,63 @@
|
|||
|
||||
package org.geysermc.geyser.level;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
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 it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import org.geysermc.erosion.packet.backendbound.*;
|
||||
import org.geysermc.erosion.util.BlockPositionIterator;
|
||||
import org.geysermc.erosion.util.LecternUtils;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class GeyserWorldManager extends WorldManager {
|
||||
private final Object2ObjectMap<String, String> gameruleCache = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
return session.getChunkCache().getBlockAt(x, y, z);
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
return session.getChunkCache().getBlockAt(x, y, z);
|
||||
}
|
||||
CompletableFuture<Integer> future = new CompletableFuture<>(); // Boxes
|
||||
erosionHandler.setPendingLookup(future);
|
||||
erosionHandler.sendPacket(new BackendboundBlockRequestPacket(0, Vector3i.from(x, y, z)));
|
||||
return future.join();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Integer> getBlockAtAsync(GeyserSession session, int x, int y, int z) {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
return super.getBlockAtAsync(session, x, y, z);
|
||||
}
|
||||
CompletableFuture<Integer> future = new CompletableFuture<>(); // Boxes
|
||||
int transactionId = erosionHandler.getNextTransactionId();
|
||||
erosionHandler.getAsyncPendingLookups().put(transactionId, future);
|
||||
erosionHandler.sendPacket(new BackendboundBlockRequestPacket(transactionId, Vector3i.from(x, y, z)));
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getBlocksAt(GeyserSession session, BlockPositionIterator iter) {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
return super.getBlocksAt(session, iter);
|
||||
}
|
||||
CompletableFuture<int[]> future = new CompletableFuture<>();
|
||||
erosionHandler.setPendingBatchLookup(future);
|
||||
erosionHandler.sendPacket(new BackendboundBatchBlockRequestPacket(iter));
|
||||
return future.join();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,10 +91,31 @@ public class GeyserWorldManager extends WorldManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) {
|
||||
public void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos) {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
// No-op - don't send any additional information other than what the chunk has already sent
|
||||
return;
|
||||
}
|
||||
List<Vector3i> vectors = new ObjectArrayList<>(blockEntityInfos.size());
|
||||
for (int i = 0; i < blockEntityInfos.size(); i++) {
|
||||
BlockEntityInfo info = blockEntityInfos.get(i);
|
||||
vectors.add(Vector3i.from(info.getX(), info.getY(), info.getZ()));
|
||||
}
|
||||
erosionHandler.sendPacket(new BackendboundBatchBlockEntityPacket(x, z, vectors));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendLecternData(GeyserSession session, int x, int y, int z) {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler != null) {
|
||||
erosionHandler.sendPacket(new BackendboundBlockEntityPacket(Vector3i.from(x, y, z)));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(x, y, z, 1);
|
||||
lecternTag.putCompound("book", NbtMap.builder()
|
||||
.putByte("Count", (byte) 1)
|
||||
.putShort("Damage", (short) 0)
|
||||
|
@ -61,12 +126,12 @@ public class GeyserWorldManager extends WorldManager {
|
|||
.build())
|
||||
.build());
|
||||
lecternTag.putInt("page", -1); // I'm surprisingly glad this exists - it forces Bedrock to stop reading immediately. Usually.
|
||||
return lecternTag.build();
|
||||
BlockEntityUtils.updateBlockEntity(session, lecternTag.build(), Vector3i.from(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpectLecternHandled() {
|
||||
return false;
|
||||
public boolean shouldExpectLecternHandled(GeyserSession session) {
|
||||
return session.getErosionHandler().isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,4 +164,17 @@ public class GeyserWorldManager extends WorldManager {
|
|||
public boolean hasPermission(GeyserSession session, String permission) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
|
||||
var erosionHandler = session.getErosionHandler().getAsActive();
|
||||
if (erosionHandler == null) {
|
||||
return super.getPickItemNbt(session, x, y, z, addNbtData);
|
||||
}
|
||||
CompletableFuture<CompoundTag> future = new CompletableFuture<>();
|
||||
erosionHandler.setPickBlockLookup(future);
|
||||
erosionHandler.sendPacket(new BackendboundPickBlockPacket(Vector3i.from(x, y, z)));
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,14 +26,16 @@
|
|||
package org.geysermc.geyser.level;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import org.geysermc.erosion.util.BlockPositionIterator;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
@ -68,6 +70,23 @@ public abstract class WorldManager {
|
|||
*/
|
||||
public abstract int getBlockAt(GeyserSession session, int x, int y, int z);
|
||||
|
||||
public final CompletableFuture<Integer> getBlockAtAsync(GeyserSession session, Vector3i vector) {
|
||||
return this.getBlockAtAsync(session, vector.getX(), vector.getY(), vector.getZ());
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> getBlockAtAsync(GeyserSession session, int x, int y, int z) {
|
||||
return CompletableFuture.completedFuture(this.getBlockAt(session, x, y, z));
|
||||
}
|
||||
|
||||
public int[] getBlocksAt(GeyserSession session, BlockPositionIterator iter) {
|
||||
int[] blocks = new int[iter.getMaxIterations()];
|
||||
for (; iter.hasNext(); iter.next()) {
|
||||
int networkId = this.getBlockAt(session, iter.getX(), iter.getY(), iter.getZ());
|
||||
blocks[iter.getIteration()] = networkId;
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not this world manager requires a separate chunk cache/has access to more block data than the chunk cache.
|
||||
* <p>
|
||||
|
@ -89,20 +108,28 @@ public abstract class WorldManager {
|
|||
* We solve this problem by querying all loaded lecterns, where possible, and sending their information in a block entity
|
||||
* tag.
|
||||
*
|
||||
* Note that the lectern data may be sent asynchronously.
|
||||
*
|
||||
* @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);
|
||||
public abstract void sendLecternData(GeyserSession session, int x, int y, int z);
|
||||
|
||||
/**
|
||||
* {@link #sendLecternData(GeyserSession, int, int, int)} but batched for chunks.
|
||||
*
|
||||
* @param x chunk x
|
||||
* @param z chunk z
|
||||
* @param blockEntityInfos a list of coordinates (chunk local) to grab lecterns from.
|
||||
*/
|
||||
public abstract void sendLecternData(GeyserSession session, int x, int z, List<BlockEntityInfo> blockEntityInfos);
|
||||
|
||||
/**
|
||||
* @return whether we should expect lectern data to update, or if we have to fall back on a workaround.
|
||||
*/
|
||||
public abstract boolean shouldExpectLecternHandled();
|
||||
public abstract boolean shouldExpectLecternHandled(GeyserSession session);
|
||||
|
||||
/**
|
||||
* Updates a gamerule value on the Java server
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.level.block;
|
||||
|
||||
import com.nukkitx.network.util.Preconditions;
|
||||
|
||||
public class BlockPositionIterator {
|
||||
private final int minX;
|
||||
private final int minY;
|
||||
private final int minZ;
|
||||
|
||||
private final int sizeX;
|
||||
private final int sizeZ;
|
||||
|
||||
private int i = 0;
|
||||
private final int maxI;
|
||||
|
||||
public BlockPositionIterator(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
Preconditions.checkArgument(maxX >= minX, "maxX is not greater than or equal to minX");
|
||||
Preconditions.checkArgument(maxY >= minY, "maxY is not greater than or equal to minY");
|
||||
Preconditions.checkArgument(maxZ >= minZ, "maxZ is not greater than or equal to minZ");
|
||||
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
this.minZ = minZ;
|
||||
|
||||
this.sizeX = maxX - minX + 1;
|
||||
int sizeY = maxY - minY + 1;
|
||||
this.sizeZ = maxZ - minZ + 1;
|
||||
this.maxI = sizeX * sizeY * sizeZ;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return i < maxI;
|
||||
}
|
||||
|
||||
public void next() {
|
||||
// Iterate in zxy order
|
||||
i++;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return ((i / sizeZ) % sizeX) + minX;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return (i / sizeZ / sizeX) + minY;
|
||||
}
|
||||
|
||||
public int getZ() {
|
||||
return (i % sizeZ) + minZ;
|
||||
}
|
||||
}
|
|
@ -33,10 +33,10 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
|||
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.erosion.util.BlockPositionIterator;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.level.block.BlockPositionIterator;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
|
@ -215,7 +215,7 @@ public class CollisionManager {
|
|||
int minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE + pistonExpand));
|
||||
int maxCollisionZ = (int) Math.floor(position.getZ() + (box.getSizeZ() / 2) + COLLISION_TOLERANCE + pistonExpand);
|
||||
|
||||
return new BlockPositionIterator(minCollisionX, minCollisionY, minCollisionZ, maxCollisionX, maxCollisionY, maxCollisionZ);
|
||||
return BlockPositionIterator.fromMinMax(minCollisionX, minCollisionY, minCollisionZ, maxCollisionX, maxCollisionY, maxCollisionZ);
|
||||
}
|
||||
|
||||
public BlockPositionIterator playerCollidableBlocksIterator() {
|
||||
|
@ -235,8 +235,9 @@ public class CollisionManager {
|
|||
|
||||
// Used when correction code needs to be run before the main correction
|
||||
BlockPositionIterator iter = session.getCollisionManager().playerCollidableBlocksIterator();
|
||||
for (; iter.hasNext(); iter.next()) {
|
||||
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, iter.getX(), iter.getY(), iter.getZ());
|
||||
int[] blocks = session.getGeyser().getWorldManager().getBlocksAt(session, iter);
|
||||
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||
BlockCollision blockCollision = BlockUtils.getCollision(blocks[iter.getIteration()]);
|
||||
if (blockCollision != null) {
|
||||
blockCollision.beforeCorrectPosition(iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox);
|
||||
}
|
||||
|
@ -244,7 +245,7 @@ public class CollisionManager {
|
|||
|
||||
// Main correction code
|
||||
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, iter.getX(), iter.getY(), iter.getZ());
|
||||
BlockCollision blockCollision = BlockUtils.getCollision(blocks[iter.getIteration()]);
|
||||
if (blockCollision != null) {
|
||||
if (!blockCollision.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) {
|
||||
return false;
|
||||
|
|
|
@ -102,13 +102,17 @@ public class ResourcePack {
|
|||
|
||||
pack.sha256 = FileUtils.calculateSHA256(file);
|
||||
|
||||
Stream<? extends ZipEntry> stream = null;
|
||||
try {
|
||||
ZipFile zip = new ZipFile(file);
|
||||
|
||||
stream = zip.stream();
|
||||
try (ZipFile zip = new ZipFile(file);
|
||||
Stream<? extends ZipEntry> stream = zip.stream()) {
|
||||
stream.forEach((x) -> {
|
||||
if (x.getName().contains("manifest.json")) {
|
||||
String name = x.getName();
|
||||
if (name.length() >= 80) {
|
||||
GeyserImpl.getInstance().getLogger().warning("The resource pack " + file.getName()
|
||||
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
|
||||
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
|
||||
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
|
||||
}
|
||||
if (name.contains("manifest.json")) {
|
||||
try {
|
||||
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
|
||||
// Sometimes a pack_manifest file is present and not in a valid format,
|
||||
|
@ -133,10 +137,6 @@ public class ResourcePack {
|
|||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,8 @@ import org.geysermc.geyser.entity.type.Entity;
|
|||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
import org.geysermc.geyser.entity.type.Tickable;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.erosion.AbstractGeyserboundPacketHandler;
|
||||
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.PlayerInventory;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
|
@ -136,6 +138,7 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
|
|||
import org.geysermc.geyser.util.ChunkUtils;
|
||||
import org.geysermc.geyser.util.DimensionUtils;
|
||||
import org.geysermc.geyser.util.LoginEncryptionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
@ -168,6 +171,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
@Setter
|
||||
private JsonNode certChainData;
|
||||
|
||||
@NotNull
|
||||
@Setter
|
||||
private AbstractGeyserboundPacketHandler erosionHandler;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@Setter
|
||||
private RemoteServer remoteServer;
|
||||
|
@ -255,7 +262,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
|
||||
/**
|
||||
* Stores a list of all lectern locations and their block entity tags.
|
||||
* See {@link WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)}
|
||||
* See {@link WorldManager#sendLecternData(GeyserSession, int, int, int)}
|
||||
* for more information.
|
||||
*/
|
||||
private final Set<Vector3i> lecternCache;
|
||||
|
@ -551,6 +558,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
this.upstream = new UpstreamSession(bedrockServerSession);
|
||||
this.eventLoop = eventLoop;
|
||||
|
||||
this.erosionHandler = new GeyserboundHandshakePacketHandler(this);
|
||||
|
||||
this.advancementsCache = new AdvancementsCache(this);
|
||||
this.bookEditCache = new BookEditCache(this);
|
||||
this.chunkCache = new ChunkCache(this);
|
||||
|
@ -579,7 +588,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
this.spawned = false;
|
||||
this.loggedIn = false;
|
||||
|
||||
if (geyser.getWorldManager().shouldExpectLecternHandled()) {
|
||||
if (geyser.getWorldManager().shouldExpectLecternHandled(this)) {
|
||||
// Unneeded on these platforms
|
||||
this.lecternCache = null;
|
||||
} else {
|
||||
|
@ -1088,6 +1097,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
tickThread.cancel(false);
|
||||
}
|
||||
|
||||
erosionHandler.close();
|
||||
|
||||
closed = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import com.nukkitx.nbt.NbtMap;
|
|||
import com.nukkitx.nbt.NbtMapBuilder;
|
||||
import com.nukkitx.nbt.NbtType;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.erosion.util.LecternUtils;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.LecternContainer;
|
||||
|
@ -110,13 +111,13 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator {
|
|||
Vector3i position = session.getLastInteractionBlockPosition();
|
||||
// If shouldExpectLecternHandled returns true, this is already handled for us
|
||||
// shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet
|
||||
boolean shouldRefresh = !session.getGeyser().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position);
|
||||
boolean shouldRefresh = !session.getGeyser().getWorldManager().shouldExpectLecternHandled(session) && !session.getLecternCache().contains(position);
|
||||
|
||||
NbtMap blockEntityTag;
|
||||
if (tag != null) {
|
||||
int pagesSize = ((ListTag) tag.get("pages")).size();
|
||||
ItemData itemData = book.getItemData(session);
|
||||
NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize);
|
||||
NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize);
|
||||
lecternTag.putCompound("book", NbtMap.builder()
|
||||
.putByte("Count", (byte) itemData.getCount())
|
||||
.putShort("Damage", (short) 0)
|
||||
|
@ -127,7 +128,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator {
|
|||
blockEntityTag = lecternTag.build();
|
||||
} else {
|
||||
// There is *a* book here, but... no NBT.
|
||||
NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1);
|
||||
NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1);
|
||||
NbtMapBuilder bookTag = NbtMap.builder()
|
||||
.putByte("Count", (byte) 1)
|
||||
.putShort("Damage", (short) 0)
|
||||
|
@ -162,20 +163,4 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator {
|
|||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new LecternContainer(name, windowId, this.size, containerType, 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ import javax.annotation.Nonnull;
|
|||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.geysermc.erosion.util.BannerUtils.getJavaPatternTag;
|
||||
|
||||
@ItemRemapper
|
||||
public class BannerTranslator extends NbtItemStackTranslator {
|
||||
/**
|
||||
|
@ -66,15 +68,6 @@ public class BannerTranslator extends NbtItemStackTranslator {
|
|||
OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("bo", 15));
|
||||
}
|
||||
|
||||
public static CompoundTag getJavaPatternTag(String pattern, int color) {
|
||||
StringTag patternType = new StringTag("Pattern", pattern);
|
||||
IntTag colorTag = new IntTag("Color", color);
|
||||
CompoundTag tag = new CompoundTag("");
|
||||
tag.put(patternType);
|
||||
tag.put(colorTag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public BannerTranslator() {
|
||||
appliedItems = Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getItems())
|
||||
.filter(entry -> entry.getJavaIdentifier().endsWith("banner"))
|
||||
|
@ -117,7 +110,7 @@ public class BannerTranslator extends NbtItemStackTranslator {
|
|||
* @return The Java edition format pattern nbt
|
||||
*/
|
||||
public static CompoundTag getJavaBannerPattern(NbtMap pattern) {
|
||||
return BannerTranslator.getJavaPatternTag(pattern.getString("Pattern"), 15 - pattern.getInt("Color"));
|
||||
return getJavaPatternTag(pattern.getString("Pattern"), 15 - pattern.getInt("Color"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -224,7 +224,7 @@ public class PistonBlockEntity {
|
|||
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, blockInFront);
|
||||
if (BlockStateValues.isPistonHead(blockId)) {
|
||||
ChunkUtils.updateBlock(session, BlockStateValues.JAVA_AIR_ID, blockInFront);
|
||||
} else if (session.getGeyser().getPlatformType() == PlatformType.SPIGOT && blockId == BlockStateValues.JAVA_AIR_ID) {
|
||||
} else if ((session.getGeyser().getPlatformType() == PlatformType.SPIGOT || session.getErosionHandler().isActive()) && blockId == BlockStateValues.JAVA_AIR_ID) {
|
||||
// Spigot removes the piston head from the cache, but we need to send the block update ourselves
|
||||
ChunkUtils.updateBlock(session, BlockStateValues.JAVA_AIR_ID, blockInFront);
|
||||
}
|
||||
|
|
|
@ -71,30 +71,28 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
|
|||
boolean addNbtData = packet.isAddUserData() && blockMapping.isBlockEntity(); // Holding down CTRL
|
||||
if (BlockStateValues.getBannerColor(blockToPick) != -1 || addNbtData) {
|
||||
session.getGeyser().getWorldManager().getPickItemNbt(session, vector.getX(), vector.getY(), vector.getZ(), addNbtData)
|
||||
.whenComplete((tag, ex) -> {
|
||||
.whenComplete((tag, ex) -> session.ensureInEventLoop(() -> {
|
||||
if (tag == null) {
|
||||
pickItem(session, blockMapping);
|
||||
return;
|
||||
}
|
||||
|
||||
session.ensureInEventLoop(() -> {
|
||||
if (addNbtData) {
|
||||
ListTag lore = new ListTag("Lore");
|
||||
lore.add(new StringTag("", "\"(+NBT)\""));
|
||||
CompoundTag display = tag.get("display");
|
||||
if (display == null) {
|
||||
display = new CompoundTag("display");
|
||||
tag.put(display);
|
||||
}
|
||||
display.put(lore);
|
||||
if (addNbtData) {
|
||||
ListTag lore = new ListTag("Lore");
|
||||
lore.add(new StringTag("", "\"(+NBT)\""));
|
||||
CompoundTag display = tag.get("display");
|
||||
if (display == null) {
|
||||
display = new CompoundTag("display");
|
||||
tag.put(display);
|
||||
}
|
||||
// I don't really like this... I'd rather get an ID from the block mapping I think
|
||||
ItemMapping mapping = session.getItemMappings().getMapping(blockMapping.getPickItem());
|
||||
display.put(lore);
|
||||
}
|
||||
// I don't really like this... I'd rather get an ID from the block mapping I think
|
||||
ItemMapping mapping = session.getItemMappings().getMapping(blockMapping.getPickItem());
|
||||
|
||||
ItemStack itemStack = new ItemStack(mapping.getJavaId(), 1, tag);
|
||||
InventoryUtils.findOrCreateItem(session, itemStack);
|
||||
});
|
||||
});
|
||||
ItemStack itemStack = new ItemStack(mapping.getJavaId(), 1, tag);
|
||||
InventoryUtils.findOrCreateItem(session, itemStack);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,13 @@ import io.netty.buffer.Unpooled;
|
|||
import org.geysermc.cumulus.Forms;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
import org.geysermc.cumulus.form.util.FormType;
|
||||
import org.geysermc.erosion.Constants;
|
||||
import org.geysermc.erosion.packet.ErosionPacket;
|
||||
import org.geysermc.erosion.packet.Packets;
|
||||
import org.geysermc.erosion.packet.geyserbound.GeyserboundPacket;
|
||||
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -51,82 +54,96 @@ public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCus
|
|||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundCustomPayloadPacket packet) {
|
||||
// The only plugin messages it has to listen for are Floodgate plugin messages
|
||||
if (session.remoteServer().authType() != AuthType.FLOODGATE) {
|
||||
String channel = packet.getChannel();
|
||||
|
||||
if (channel.equals(Constants.PLUGIN_MESSAGE)) {
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(packet.getData());
|
||||
ErosionPacket<?> erosionPacket = Packets.decode(buf);
|
||||
((GeyserboundPacket) erosionPacket).handle(session.getErosionHandler());
|
||||
return;
|
||||
}
|
||||
|
||||
String channel = packet.getChannel();
|
||||
|
||||
if (channel.equals(PluginMessageChannels.FORM)) {
|
||||
byte[] data = packet.getData();
|
||||
session.ensureInEventLoop(() -> {
|
||||
byte[] data = packet.getData();
|
||||
|
||||
// receive: first byte is form type, second and third are the id, remaining is the form data
|
||||
// respond: first and second byte id, remaining is form response data
|
||||
// receive: first byte is form type, second and third are the id, remaining is the form data
|
||||
// respond: first and second byte id, remaining is form response data
|
||||
|
||||
FormType type = FormType.fromOrdinal(data[0]);
|
||||
if (type == null) {
|
||||
throw new NullPointerException("Got type " + data[0] + " which isn't a valid form type!");
|
||||
}
|
||||
|
||||
String dataString = new String(data, 3, data.length - 3, Charsets.UTF_8);
|
||||
|
||||
Form form = Forms.fromJson(dataString, type, (ignored, response) -> {
|
||||
byte[] finalData;
|
||||
if (response == null) {
|
||||
// Response data can be null as of 1.19.20 (same behaviour as empty response data)
|
||||
// Only need to send the form id
|
||||
finalData = new byte[]{data[1], data[2]};
|
||||
} else {
|
||||
byte[] raw = response.getBytes(StandardCharsets.UTF_8);
|
||||
finalData = new byte[raw.length + 2];
|
||||
|
||||
finalData[0] = data[1];
|
||||
finalData[1] = data[2];
|
||||
System.arraycopy(raw, 0, finalData, 2, raw.length);
|
||||
FormType type = FormType.fromOrdinal(data[0]);
|
||||
if (type == null) {
|
||||
throw new NullPointerException("Got type " + data[0] + " which isn't a valid form type!");
|
||||
}
|
||||
|
||||
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, finalData));
|
||||
String dataString = new String(data, 3, data.length - 3, Charsets.UTF_8);
|
||||
|
||||
Form form = Forms.fromJson(dataString, type, (ignored, response) -> {
|
||||
byte[] finalData;
|
||||
if (response == null) {
|
||||
// Response data can be null as of 1.19.20 (same behaviour as empty response data)
|
||||
// Only need to send the form id
|
||||
finalData = new byte[]{data[1], data[2]};
|
||||
} else {
|
||||
byte[] raw = response.getBytes(StandardCharsets.UTF_8);
|
||||
finalData = new byte[raw.length + 2];
|
||||
|
||||
finalData[0] = data[1];
|
||||
finalData[1] = data[2];
|
||||
System.arraycopy(raw, 0, finalData, 2, raw.length);
|
||||
}
|
||||
|
||||
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, finalData));
|
||||
});
|
||||
session.sendForm(form);
|
||||
});
|
||||
session.sendForm(form);
|
||||
|
||||
} else if (channel.equals(PluginMessageChannels.TRANSFER)) {
|
||||
byte[] data = packet.getData();
|
||||
session.ensureInEventLoop(() -> {
|
||||
byte[] data = packet.getData();
|
||||
|
||||
// port (4 bytes), address (remaining data)
|
||||
if (data.length < 5) {
|
||||
throw new NullPointerException("Transfer data should be at least 5 bytes long");
|
||||
}
|
||||
// port (4 bytes), address (remaining data)
|
||||
if (data.length < 5) {
|
||||
throw new NullPointerException("Transfer data should be at least 5 bytes long");
|
||||
}
|
||||
|
||||
int port = data[0] << 24 | (data[1] & 0xFF) << 16 | (data[2] & 0xFF) << 8 | data[3] & 0xFF;
|
||||
String address = new String(data, 4, data.length - 4);
|
||||
int port = data[0] << 24 | (data[1] & 0xFF) << 16 | (data[2] & 0xFF) << 8 | data[3] & 0xFF;
|
||||
String address = new String(data, 4, data.length - 4);
|
||||
|
||||
if (logger.isDebug()) {
|
||||
logger.info("Transferring client to: " + address + ":" + port);
|
||||
}
|
||||
if (logger.isDebug()) {
|
||||
logger.info("Transferring client to: " + address + ":" + port);
|
||||
}
|
||||
|
||||
TransferPacket transferPacket = new TransferPacket();
|
||||
transferPacket.setAddress(address);
|
||||
transferPacket.setPort(port);
|
||||
session.sendUpstreamPacket(transferPacket);
|
||||
TransferPacket transferPacket = new TransferPacket();
|
||||
transferPacket.setAddress(address);
|
||||
transferPacket.setPort(port);
|
||||
session.sendUpstreamPacket(transferPacket);
|
||||
});
|
||||
|
||||
} else if (channel.equals(PluginMessageChannels.PACKET)) {
|
||||
logger.debug("A packet has been sent using the Floodgate api");
|
||||
byte[] data = packet.getData();
|
||||
session.ensureInEventLoop(() -> {
|
||||
logger.debug("A packet has been sent using the Floodgate api");
|
||||
byte[] data = packet.getData();
|
||||
|
||||
// packet id, packet data
|
||||
if (data.length < 2) {
|
||||
throw new IllegalStateException("Packet data should be at least 2 bytes long");
|
||||
}
|
||||
// packet id, packet data
|
||||
if (data.length < 2) {
|
||||
throw new IllegalStateException("Packet data should be at least 2 bytes long");
|
||||
}
|
||||
|
||||
int packetId = data[0] & 0xFF;
|
||||
ByteBuf packetData = Unpooled.wrappedBuffer(data, 1, data.length - 1);
|
||||
int packetId = data[0] & 0xFF;
|
||||
ByteBuf packetData = Unpooled.wrappedBuffer(data, 1, data.length - 1);
|
||||
|
||||
var toSend = new UnknownPacket();
|
||||
toSend.setPacketId(packetId);
|
||||
toSend.setPayload(packetData);
|
||||
var toSend = new UnknownPacket();
|
||||
toSend.setPacketId(packetId);
|
||||
toSend.setPayload(packetData);
|
||||
|
||||
session.sendUpstreamPacket(toSend);
|
||||
session.sendUpstreamPacket(toSend);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExecuteInEventLoop() {
|
||||
// For Erosion packets
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|||
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
||||
import org.geysermc.geyser.level.JavaDimension;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.TextDecoration;
|
||||
|
@ -57,6 +58,11 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
|||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
entity.setEntityId(packet.getEntityId());
|
||||
|
||||
if (session.getErosionHandler().isActive()) {
|
||||
session.getErosionHandler().close();
|
||||
session.setErosionHandler(new GeyserboundHandshakePacketHandler(session));
|
||||
}
|
||||
|
||||
Map<String, JavaDimension> dimensions = session.getDimensions();
|
||||
dimensions.clear();
|
||||
|
||||
|
@ -129,6 +135,10 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
|||
|
||||
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
|
||||
|
||||
// TODO don't send two packets
|
||||
// if (true) {
|
||||
// session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:register", Constants.PLUGIN_MESSAGE.getBytes(StandardCharsets.UTF_8)));
|
||||
// }
|
||||
// register the plugin messaging channels used in Floodgate
|
||||
if (session.remoteServer().authType() == AuthType.FLOODGATE) {
|
||||
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:register", PluginMessageChannels.getFloodgateRegisterData()));
|
||||
|
|
|
@ -58,15 +58,16 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
|
|||
blockEventPacket.setEventType(1);
|
||||
session.sendUpstreamPacket(blockEventPacket);
|
||||
} else if (packet.getValue() instanceof NoteBlockValue) {
|
||||
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, position);
|
||||
blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState));
|
||||
session.sendUpstreamPacket(blockEventPacket);
|
||||
session.getGeyser().getWorldManager().getBlockAtAsync(session, position).thenAccept(blockState -> {
|
||||
blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState));
|
||||
session.sendUpstreamPacket(blockEventPacket);
|
||||
});
|
||||
} else if (packet.getValue() instanceof PistonValue pistonValue) {
|
||||
PistonValueType action = (PistonValueType) packet.getType();
|
||||
Direction direction = Direction.fromPistonValue(pistonValue.getDirection());
|
||||
PistonCache pistonCache = session.getPistonCache();
|
||||
|
||||
if (session.getGeyser().getPlatformType() == PlatformType.SPIGOT) {
|
||||
if (session.getGeyser().getPlatformType() == PlatformType.SPIGOT || session.getErosionHandler().isActive()) {
|
||||
// Mostly handled in the GeyserPistonEvents class
|
||||
// Retracting sticky pistons is an exception, since the event is not called on Spigot from 1.13.2 - 1.17.1
|
||||
// See https://github.com/PaperMC/Paper/blob/6fa1983e9ce177a4a412d5b950fd978620174777/patches/server/0304-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch
|
||||
|
|
|
@ -43,7 +43,7 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
|
|||
public void translate(GeyserSession session, ClientboundBlockUpdatePacket packet) {
|
||||
Vector3i pos = packet.getEntry().getPosition();
|
||||
boolean updatePlacement = session.getGeyser().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event
|
||||
session.getGeyser().getWorldManager().getBlockAt(session, pos) != packet.getEntry().getBlock();
|
||||
!session.getErosionHandler().isActive() && session.getGeyser().getWorldManager().getBlockAt(session, pos) != packet.getEntry().getBlock();
|
||||
session.getWorldCache().updateServerCorrectBlockState(pos, packet.getEntry().getBlock());
|
||||
if (updatePlacement) {
|
||||
this.checkPlace(session, packet);
|
||||
|
|
|
@ -52,7 +52,7 @@ public class JavaForgetLevelChunkTranslator extends PacketTranslator<Clientbound
|
|||
}
|
||||
removedSkulls.forEach(session.getSkullCache()::removeSkull);
|
||||
|
||||
if (!session.getGeyser().getWorldManager().shouldExpectLecternHandled()) {
|
||||
if (!session.getGeyser().getWorldManager().shouldExpectLecternHandled(session)) {
|
||||
// Do the same thing with lecterns
|
||||
Iterator<Vector3i> iterator = session.getLecternCache().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
|
|
|
@ -38,6 +38,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
|||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NBTOutputStream;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.nbt.NbtMapBuilder;
|
||||
import com.nukkitx.nbt.NbtUtils;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
@ -48,6 +49,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.ints.IntLists;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import org.geysermc.erosion.util.LecternUtils;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
import org.geysermc.geyser.level.BedrockDimension;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
|
@ -94,6 +96,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
|
||||
final BlockEntityInfo[] blockEntities = packet.getBlockEntities();
|
||||
final List<NbtMap> bedrockBlockEntities = new ObjectArrayList<>(blockEntities.length);
|
||||
final List<BlockEntityInfo> lecterns = new ObjectArrayList<>();
|
||||
|
||||
BitSet waterloggedPaletteIds = new BitSet();
|
||||
BitSet bedrockOnlyBlockEntityIds = new BitSet();
|
||||
|
@ -240,7 +243,9 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
sections[bedrockSectionY] = new GeyserChunkSection(layers);
|
||||
}
|
||||
|
||||
session.getChunkCache().addToCache(packet.getX(), packet.getZ(), javaChunks);
|
||||
if (!session.getErosionHandler().isActive()) {
|
||||
session.getChunkCache().addToCache(packet.getX(), packet.getZ(), javaChunks);
|
||||
}
|
||||
|
||||
final int chunkBlockX = packet.getX() << 4;
|
||||
final int chunkBlockZ = packet.getZ() << 4;
|
||||
|
@ -262,8 +267,15 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
|
||||
if (type == BlockEntityType.LECTERN && BlockStateValues.getLecternBookStates().get(blockState)) {
|
||||
// If getLecternBookStates is false, let's just treat it like a normal block entity
|
||||
bedrockBlockEntities.add(session.getGeyser().getWorldManager().getLecternDataAt(
|
||||
session, x + chunkBlockX, y, z + chunkBlockZ, true));
|
||||
// Fill in tag with a default value
|
||||
NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(x + chunkBlockX, y, z + chunkBlockZ, 1);
|
||||
lecternTag.putCompound("book", NbtMap.builder()
|
||||
.putByte("Count", (byte) 1)
|
||||
.putShort("Damage", (short) 0)
|
||||
.putString("Name", "minecraft:written_book").build());
|
||||
lecternTag.putInt("page", -1);
|
||||
bedrockBlockEntities.add(lecternTag.build());
|
||||
lecterns.add(blockEntity);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -356,6 +368,10 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
levelChunkPacket.setData(payload);
|
||||
session.sendUpstreamPacket(levelChunkPacket);
|
||||
|
||||
if (!lecterns.isEmpty()) {
|
||||
session.getGeyser().getWorldManager().sendLecternData(session, packet.getX(), packet.getZ(), lecterns);
|
||||
}
|
||||
|
||||
for (Map.Entry<Vector3i, ItemFrameEntity> entry : session.getItemFrameCache().entrySet()) {
|
||||
Vector3i position = entry.getKey();
|
||||
if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) {
|
||||
|
|
|
@ -27,9 +27,9 @@ package org.geysermc.geyser.util.collection;
|
|||
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import org.geysermc.erosion.util.LecternUtils;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
|
||||
/**
|
||||
|
@ -47,27 +47,24 @@ public class LecternHasBookMap extends FixedInt2BooleanMap {
|
|||
int offset = blockState - this.start;
|
||||
if (offset < 0 || offset >= this.value.length) {
|
||||
// Block state is out of bounds of this map - lectern has been destroyed, if it existed
|
||||
if (!worldManager.shouldExpectLecternHandled()) {
|
||||
if (!worldManager.shouldExpectLecternHandled(session)) {
|
||||
session.getLecternCache().remove(position);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean newLecternHasBook;
|
||||
if (worldManager.shouldExpectLecternHandled()) {
|
||||
// As of right now, no tag can be added asynchronously
|
||||
worldManager.getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
|
||||
if (worldManager.shouldExpectLecternHandled(session)) {
|
||||
worldManager.sendLecternData(session, position.getX(), position.getY(), position.getZ());
|
||||
} else if ((newLecternHasBook = this.value[offset]) != this.get(worldManager.getBlockAt(session, position))) {
|
||||
// If the lectern block was updated, or it previously had a book
|
||||
NbtMap newLecternTag;
|
||||
// newLecternHasBook = the new lectern block state's "has book" toggle.
|
||||
if (newLecternHasBook) {
|
||||
newLecternTag = worldManager.getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
|
||||
worldManager.sendLecternData(session, position.getX(), position.getY(), position.getZ());
|
||||
} else {
|
||||
session.getLecternCache().remove(position);
|
||||
newLecternTag = LecternInventoryTranslator.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build();
|
||||
NbtMap newLecternTag = LecternUtils.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build();
|
||||
BlockEntityUtils.updateBlockEntity(session, newLecternTag, position);
|
||||
}
|
||||
BlockEntityUtils.updateBlockEntity(session, newLecternTag, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[versions]
|
||||
base-api = "1.0.0-SNAPSHOT"
|
||||
cumulus = "1.1.1"
|
||||
erosion = "1.0-20230330.170602-3"
|
||||
events = "1.0-SNAPSHOT"
|
||||
jackson = "2.14.0"
|
||||
fastutil = "8.5.2"
|
||||
|
@ -19,9 +20,9 @@ checkerframework = "3.19.0"
|
|||
log4j = "2.17.1"
|
||||
jline = "3.21.0"
|
||||
terminalconsoleappender = "1.2.0"
|
||||
paper = "1.19-R0.1-SNAPSHOT"
|
||||
folia = "1.19.4-R0.1-SNAPSHOT"
|
||||
viaversion = "4.0.0"
|
||||
adapters = "1.6-SNAPSHOT"
|
||||
adapters = "1.7-SNAPSHOT"
|
||||
commodore = "2.2"
|
||||
bungeecord = "a7c6ede"
|
||||
velocity = "3.0.0"
|
||||
|
@ -35,6 +36,9 @@ base-api = { group = "org.geysermc.api", name = "base-api", version.ref = "base-
|
|||
cumulus = { group = "org.geysermc.cumulus", name = "cumulus", version.ref = "cumulus" }
|
||||
events = { group = "org.geysermc.event", name = "events", version.ref = "events" }
|
||||
|
||||
erosion-bukkit-common = { group = "org.geysermc.erosion", name = "bukkit-common", version.ref = "erosion" }
|
||||
erosion-common = { group = "org.geysermc.erosion", name = "common", version.ref = "erosion" }
|
||||
|
||||
jackson-annotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jackson" }
|
||||
jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" }
|
||||
jackson-dataformat-yaml = { group = "com.fasterxml.jackson.dataformat", name = "jackson-dataformat-yaml", version.ref = "jackson" }
|
||||
|
@ -66,8 +70,8 @@ jline-terminal = { group = "org.jline", name = "jline-terminal", version.ref = "
|
|||
jline-terminal-jna = { group = "org.jline", name = "jline-terminal-jna", version.ref = "jline" }
|
||||
jline-reader = { group = "org.jline", name = "jline-reader", version.ref = "jline" }
|
||||
|
||||
paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" }
|
||||
paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "paper" }
|
||||
folia-api = { group = "dev.folia", name = "folia-api", version.ref = "folia" }
|
||||
paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "folia" }
|
||||
|
||||
# check these on https://modmuss50.me/fabric.html
|
||||
fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" }
|
||||
|
|
Loading…
Reference in a new issue