3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-11-19 22:40:18 +01:00

Merge remote-tracking branch 'upstream/master' into feature/blocky

Dieser Commit ist enthalten in:
Joshua Castle 2023-03-30 21:22:52 -07:00
Commit 024feed4c6
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: F674F38216C35D5D
43 geänderte Dateien mit 1114 neuen und 462 gelöschten Zeilen

Datei anzeigen

@ -23,25 +23,19 @@ jobs:
with: with:
submodules: recursive submodules: recursive
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
java-version: 17 java-version: 17
distribution: temurin distribution: temurin
- name: Cache Gradle Packages
uses: actions/cache@v3
with:
path: |
~/.m2
~/.gradle/caches
~/.gradle/wrapper
key: ${{ github.ref_name }}-gradle-${{ hashFiles('*.gradle.kts', 'gradle.properties', 'gradlew', 'gradle/*', 'gradle/**/*', 'build-logic/*', 'build-logic/**/**/**/*', '**/*.gradle.kts', '**/**/*.gradle.kts') }}
restore-keys: ${{ github.ref_name }}-gradle-
- name: Build - name: Build
uses: gradle/gradle-build-action@v2 uses: gradle/gradle-build-action@v2
with: with:
arguments: build --no-daemon arguments: build
gradle-home-cache-cleanup: true
- name: Archive artifacts (Geyser Fabric) - name: Archive artifacts (Geyser Fabric)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -87,7 +81,7 @@ jobs:
if-no-files-found: error if-no-files-found: error
- name: Publish to Maven Repository - name: Publish to Maven Repository
if: ${{ job.status == 'success' && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
uses: gradle/gradle-build-action@v2 uses: gradle/gradle-build-action@v2
env: env:
ORG_GRADLE_PROJECT_geysermcUsername: ${{ vars.DEPLOY_USER }} ORG_GRADLE_PROJECT_geysermcUsername: ${{ vars.DEPLOY_USER }}
@ -96,7 +90,7 @@ jobs:
arguments: publish arguments: publish
- name: Publish to Downloads API - name: Publish to Downloads API
if: ${{ github.ref_name == 'master' && job.status == 'success' && github.repository == 'GeyserMC/Geyser' }} if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
shell: bash shell: bash
env: env:
DOWNLOADS_USERNAME: ${{ vars.DOWNLOADS_USERNAME }} DOWNLOADS_USERNAME: ${{ vars.DOWNLOADS_USERNAME }}
@ -119,6 +113,15 @@ jobs:
echo "{\"project\": \"$project\", \"version\": \"$version\", \"id\": $GITHUB_RUN_NUMBER, \"commit\": \"$GITHUB_SHA\"}" > metadata.json echo "{\"project\": \"$project\", \"version\": \"$version\", \"id\": $GITHUB_RUN_NUMBER, \"commit\": \"$GITHUB_SHA\"}" > metadata.json
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/ rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/
- name: Publish to Modrinth
uses: gradle/gradle-build-action@v2
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
with:
arguments: fabric:modrinth
gradle-home-cache-cleanup: true
- name: Notify Discord - name: Notify Discord
if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }} if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }}
uses: Tim203/actions-git-discord-webhook@main uses: Tim203/actions-git-discord-webhook@main

Datei anzeigen

@ -1,31 +0,0 @@
name: Publish
on:
workflow_dispatch:
push:
paths-ignore:
- '.github/ISSUE_TEMPLATE/*.yml'
- '.github/actions/pullrequest.yml'
- '.idea/copyright/*.xml'
- '.gitignore'
- 'CONTRIBUTING.md'
- 'LICENSE'
- 'Jenkinsfile '
- 'README.md'
- 'licenseheader.txt'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
- name: Publish to Modrinth
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
run: ./gradlew fabric:modrinth

Datei anzeigen

@ -94,6 +94,7 @@ tasks {
} }
modrinth { modrinth {
token.set(System.getenv("MODRINTH_TOKEN")) // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
projectId.set("wKkoqHrH") projectId.set("wKkoqHrH")
versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER")) versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER"))
versionType.set("beta") versionType.set("beta")

Datei anzeigen

@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.fabric.world; 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.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder; 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.BannerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.LecternBlockEntity; 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.level.GeyserWorldManager;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
import org.geysermc.geyser.util.BlockEntityUtils; import org.geysermc.geyser.util.BlockEntityUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class GeyserFabricWorldManager extends GeyserWorldManager { public class GeyserFabricWorldManager extends GeyserWorldManager {
private final MinecraftServer server; private final MinecraftServer server;
@ -59,24 +60,55 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
} }
@Override @Override
public boolean shouldExpectLecternHandled() { public boolean shouldExpectLecternHandled(GeyserSession session) {
return true; return true;
} }
@Override @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) {
Runnable lecternGet = () -> { server.execute(() -> {
// Mostly a reimplementation of Spigot lectern support
ServerPlayer player = getPlayer(session); ServerPlayer player = getPlayer(session);
if (player != null) { if (player == null) {
return;
}
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)); 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)) { if (!(blockEntity instanceof LecternBlockEntity lectern)) {
return; return;
} }
int x = blockEntity.getBlockPos().getX();
int y = blockEntity.getBlockPos().getY();
int z = blockEntity.getBlockPos().getZ();
if (!lectern.hasBook()) { if (!lectern.hasBook()) {
if (!isChunkLoad) { if (!isChunkLoad) {
BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z)); BlockEntityUtils.updateBlockEntity(session, LecternUtils.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
} }
return; return;
} }
@ -84,7 +116,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
ItemStack book = lectern.getBook(); ItemStack book = lectern.getBook();
int pageCount = WrittenBookItem.getPageCount(book); int pageCount = WrittenBookItem.getPageCount(book);
boolean hasBookPages = pageCount > 0; boolean hasBookPages = pageCount > 0;
NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1); NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1);
lecternTag.putInt("page", lectern.getPage() / 2); lecternTag.putInt("page", lectern.getPage() / 2);
NbtMapBuilder bookTag = NbtMap.builder() NbtMapBuilder bookTag = NbtMap.builder()
.putByte("Count", (byte) book.getCount()) .putByte("Count", (byte) book.getCount())
@ -114,15 +146,6 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
NbtMap blockEntityTag = lecternTag.build(); NbtMap blockEntityTag = lecternTag.build();
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z)); BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z));
} }
};
if (isChunkLoad) {
// Hacky hacks to allow lectern loading to be delayed
session.scheduleInEventLoop(() -> server.execute(lecternGet), 1, TimeUnit.SECONDS);
} else {
server.execute(lecternGet);
}
return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build();
}
@Override @Override
public boolean hasPermission(GeyserSession session, String permission) { public boolean hasPermission(GeyserSession session, String permission) {

Datei anzeigen

@ -1,5 +1,8 @@
dependencies { dependencies {
api(projects.core) api(projects.core)
api(libs.erosion.bukkit.common) {
isTransitive = false
}
implementation(libs.adapters.spigot) implementation(libs.adapters.spigot)
@ -7,8 +10,8 @@ dependencies {
implementation(libs.adventure.text.serializer.bungeecord) implementation(libs.adventure.text.serializer.bungeecord)
// Both paper-api and paper-mojangapi only provide Java 17 versions for 1.19 // Both folia-api and paper-mojangapi only provide Java 17 versions for 1.19
compileOnly(libs.paper.api) { compileOnly(libs.folia.api) {
attributes { attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
} }

Datei anzeigen

@ -59,13 +59,13 @@ public final class GeyserPaperPingPassthrough implements IGeyserPingPassthrough
// runtime because we still have to shade in our own Adventure class. For now. // runtime because we still have to shade in our own Adventure class. For now.
PaperServerListPingEvent event; PaperServerListPingEvent event;
if (OLD_CONSTRUCTOR != null) { if (OLD_CONSTRUCTOR != null) {
// Approximately pre-1.19 // 1.19, removed in 1.19.4
event = OLD_CONSTRUCTOR.newInstance(new GeyserStatusClient(inetSocketAddress), event = OLD_CONSTRUCTOR.newInstance(new GeyserStatusClient(inetSocketAddress),
Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(),
Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null); Bukkit.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null);
} else { } else {
event = new PaperServerListPingEvent(new GeyserStatusClient(inetSocketAddress), 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.getMaxPlayers(), Bukkit.getVersion(), GameProtocol.getJavaProtocolVersion(), null);
} }
Bukkit.getPluginManager().callEvent(event); Bukkit.getPluginManager().callEvent(event);

Datei anzeigen

@ -66,7 +66,7 @@ public class GeyserSpigotPingPassthrough implements IGeyserPingPassthrough {
private static class GeyserPingEvent extends ServerListPingEvent { private static class GeyserPingEvent extends ServerListPingEvent {
public GeyserPingEvent(InetAddress address, String motd, int numPlayers, int maxPlayers) { public GeyserPingEvent(InetAddress address, String motd, int numPlayers, int maxPlayers) {
super(address, motd, Bukkit.shouldSendChatPreviews(), numPlayers, maxPlayers); super("", address, motd, numPlayers, maxPlayers);
} }
@Override @Override

Datei anzeigen

@ -60,16 +60,16 @@ public final class ReflectedNames {
} }
static Constructor<PaperServerListPingEvent> getOldPaperPingConstructor() { 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) { 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 // @NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon
// New constructor is present // New constructor is present
return null; 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 // @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); String.class, int.class, CachedServerIcon.class);
} }

Datei anzeigen

@ -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.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType;
import com.nukkitx.math.vector.Vector3i; 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.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
@ -85,7 +85,7 @@ public class GeyserPistonListener implements Listener {
PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING; PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING;
boolean sticky = event.isSticky(); boolean sticky = event.isSticky();
Object2IntMap<Vector3i> attachedBlocks = new Object2IntOpenHashMap<>(); Object2IntMap<Vector3i> attachedBlocks = new Object2IntArrayMap<>();
boolean blocksFilled = false; boolean blocksFilled = false;
for (Map.Entry<UUID, GeyserSession> entry : geyser.getSessionManager().getSessions().entrySet()) { for (Map.Entry<UUID, GeyserSession> entry : geyser.getSessionManager().getSessions().entrySet()) {

Datei anzeigen

@ -25,33 +25,28 @@
package org.geysermc.geyser.platform.spigot.world.manager; 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.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.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.*; import org.bukkit.block.Block;
import org.bukkit.block.banner.Pattern;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.plugin.Plugin; 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.GameRule;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession; 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.geysermc.geyser.util.BlockEntityUtils;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -60,9 +55,11 @@ import java.util.concurrent.CompletableFuture;
*/ */
public class GeyserSpigotWorldManager extends WorldManager { public class GeyserSpigotWorldManager extends WorldManager {
private final Plugin plugin; private final Plugin plugin;
private final BukkitLecterns lecterns;
public GeyserSpigotWorldManager(Plugin plugin) { public GeyserSpigotWorldManager(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
this.lecterns = new BukkitLecterns(plugin);
} }
@Override @Override
@ -81,6 +78,12 @@ public class GeyserSpigotWorldManager extends WorldManager {
} }
public int getBlockNetworkId(Block block) { 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); return BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID);
} }
@ -90,71 +93,64 @@ public class GeyserSpigotWorldManager extends WorldManager {
} }
@Override @Override
public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { public void sendLecternData(GeyserSession session, int x, int y, int z) {
// Run as a task to prevent async issues
Runnable lecternInfoGet = () -> {
Player bukkitPlayer; Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return; return;
} }
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z); Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
if (!(block.getState() instanceof Lectern lectern)) { // Run as a task to prevent async issues
session.getGeyser().getLogger().error("Lectern expected at: " + Vector3i.from(x, y, z).toString() + " but was not! " + block.toString()); 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; return;
} }
if (SchedulerUtils.FOLIA) {
ItemStack itemStack = lectern.getInventory().getItem(0); Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
if (itemStack == null || !(itemStack.getItemMeta() instanceof BookMeta bookMeta)) { if (chunk == null) {
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; return;
} }
Bukkit.getRegionScheduler().execute(this.plugin, bukkitPlayer.getWorld(), x, z, () ->
// 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 sendLecternData(session, chunk, blockEntityInfos));
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 { } else {
// Empty page Bukkit.getScheduler().runTask(this.plugin, () -> {
NbtMapBuilder pageBuilder = NbtMap.builder() Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
.putString("photoname", "") if (chunk == null) {
.putString("text", ""); return;
pages.add(pageBuilder.build()); }
sendLecternData(session, chunk, blockEntityInfos);
});
}
} }
bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build()); private Chunk getChunk(World world, int x, int z) {
lecternTag.putCompound("book", bookTag.build()); if (!world.isChunkLoaded(x, z)) {
NbtMap blockEntityTag = lecternTag.build(); return null;
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z)); }
}; return world.getChunkAt(x, z);
}
if (isChunkLoad) {
// Delay to ensure the chunk is sent first, and then the lectern data private void sendLecternData(GeyserSession session, Chunk chunk, List<BlockEntityInfo> blockEntityInfos) {
Bukkit.getScheduler().runTaskLater(this.plugin, lecternInfoGet, 5); for (int i = 0; i < blockEntityInfos.size(); i++) {
} else { BlockEntityInfo info = blockEntityInfos.get(i);
Bukkit.getScheduler().runTask(this.plugin, lecternInfoGet); 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 @Override
public boolean shouldExpectLecternHandled() { public boolean shouldExpectLecternHandled(GeyserSession session) {
return true; return true;
} }
@ -184,40 +180,16 @@ public class GeyserSpigotWorldManager extends WorldManager {
@Override @Override
public CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { public CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>(); CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>();
// Paper 1.19.3 complains about async access otherwise.
// java.lang.IllegalStateException: Tile is null, asynchronous access?
Bukkit.getScheduler().runTask(this.plugin, () -> {
Player bukkitPlayer; Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) { if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
future.complete(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);
});
return future; return future;
} }
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
private CompoundTag addToBlockEntityTag(Tag tag) { // Paper 1.19.3 complains about async access otherwise.
CompoundTag compoundTag = new CompoundTag(""); // java.lang.IllegalStateException: Tile is null, asynchronous access?
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
blockEntityTag.put(tag); return future;
compoundTag.put(blockEntityTag);
return compoundTag;
} }
/** /**

Datei anzeigen

@ -5,6 +5,7 @@ website: ${url}
version: ${version} version: ${version}
softdepend: ["ViaVersion", "floodgate"] softdepend: ["ViaVersion", "floodgate"]
api-version: 1.13 api-version: 1.13
folia-supported: true
commands: commands:
geyser: geyser:
description: The main command for Geyser. description: The main command for Geyser.

Datei anzeigen

@ -49,6 +49,10 @@ dependencies {
// Adventure text serialization // Adventure text serialization
api(libs.bundles.adventure) api(libs.bundles.adventure)
api(libs.erosion.common) {
isTransitive = false
}
// Test // Test
testImplementation(libs.junit) testImplementation(libs.junit)

Datei anzeigen

@ -50,6 +50,7 @@ import org.geysermc.api.Geyser;
import org.geysermc.common.PlatformType; import org.geysermc.common.PlatformType;
import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder; import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.erosion.packet.Packets;
import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer; import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping; 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.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.erosion.UnixSocketClientListener;
import org.geysermc.geyser.event.GeyserEventBus; import org.geysermc.geyser.event.GeyserEventBus;
import org.geysermc.geyser.extension.GeyserExtensionManager; import org.geysermc.geyser.extension.GeyserExtensionManager;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
@ -140,6 +142,8 @@ public class GeyserImpl implements GeyserApi {
private FloodgateSkinUploader skinUploader; private FloodgateSkinUploader skinUploader;
private NewsHandler newsHandler; private NewsHandler newsHandler;
private UnixSocketClientListener erosionUnixListener;
private volatile boolean shuttingDown = false; private volatile boolean shuttingDown = false;
private ScheduledExecutorService scheduledThread; private ScheduledExecutorService scheduledThread;
@ -293,6 +297,14 @@ public class GeyserImpl implements GeyserApi {
this.newsHandler = new NewsHandler(BRANCH, this.buildNumber()); 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()); CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
@ -570,6 +582,10 @@ public class GeyserImpl implements GeyserApi {
newsHandler.shutdown(); newsHandler.shutdown();
this.commandManager().getCommands().clear(); this.commandManager().getCommands().clear();
if (this.erosionUnixListener != null) {
this.erosionUnixListener.close();
}
ResourcePack.PACKS.clear(); ResourcePack.PACKS.clear();
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus)); this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));

Datei anzeigen

@ -148,6 +148,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<AbstractSkeletonEntity> STRAY; public static final EntityDefinition<AbstractSkeletonEntity> STRAY;
public static final EntityDefinition<StriderEntity> STRIDER; public static final EntityDefinition<StriderEntity> STRIDER;
public static final EntityDefinition<TadpoleEntity> TADPOLE; public static final EntityDefinition<TadpoleEntity> TADPOLE;
public static final EntityDefinition<TextDisplayEntity> TEXT_DISPLAY;
public static final EntityDefinition<TNTEntity> TNT; public static final EntityDefinition<TNTEntity> TNT;
public static final EntityDefinition<MinecartEntity> TNT_MINECART; public static final EntityDefinition<MinecartEntity> TNT_MINECART;
public static final EntityDefinition<TraderLlamaEntity> TRADER_LLAMA; public static final EntityDefinition<TraderLlamaEntity> TRADER_LLAMA;
@ -295,6 +296,28 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.INT, TNTEntity::setFuseLength) .addTranslator(MetadataType.INT, TNTEntity::setFuseLength)
.build(); .build();
EntityDefinition<Entity> displayBase = EntityDefinition.inherited(entityBase.factory(), entityBase)
.addTranslator(null) // Interpolation start ticks
.addTranslator(null) // Interpolation duration ID
.addTranslator(null) // Translation
.addTranslator(null) // Scale
.addTranslator(null) // Left rotation
.addTranslator(null) // Right rotation
.addTranslator(null) // Billboard render constraints
.addTranslator(null) // Brightness override
.addTranslator(null) // View range
.addTranslator(null) // Shadow radius
.addTranslator(null) // Shadow strength
.addTranslator(null) // Width
.addTranslator(null) // Height
.addTranslator(null) // Glow color override
.build();
TEXT_DISPLAY = EntityDefinition.inherited(TextDisplayEntity::new, displayBase)
.type(EntityType.TEXT_DISPLAY)
.identifier("minecraft:armor_stand")
.addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
.build();
EntityDefinition<FireballEntity> fireballBase = EntityDefinition.inherited(FireballEntity::new, entityBase) EntityDefinition<FireballEntity> fireballBase = EntityDefinition.inherited(FireballEntity::new, entityBase)
.addTranslator(null) // Item .addTranslator(null) // Item
.build(); .build();

Datei anzeigen

@ -30,12 +30,11 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
import lombok.Getter; import lombok.Getter;
import org.geysermc.erosion.util.BlockPositionIterator;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.player.PlayerEntity; 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.block.BlockStateValues;
import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.level.physics.BoundingBox;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.collision.BlockCollision; import org.geysermc.geyser.translator.collision.BlockCollision;
import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.util.BlockUtils;

Datei anzeigen

@ -40,11 +40,12 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class ItemEntity extends ThrowableEntity { public class ItemEntity extends ThrowableEntity {
protected ItemData item; 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) { 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); super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@ -111,15 +112,15 @@ public class ItemEntity extends ThrowableEntity {
@Override @Override
protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
float offset = definition.offset(); 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 // Move the item entity down so it doesn't float above the water
offset = -definition.offset(); offset = -definition.offset();
} }
super.moveAbsoluteImmediate(position.add(0, offset, 0), 0, 0, 0, isOnGround, teleported); super.moveAbsoluteImmediate(position.add(0, offset, 0), 0, 0, 0, isOnGround, teleported);
this.position = position; this.position = position;
int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt()); waterLevel = session.getGeyser().getWorldManager().getBlockAtAsync(session, position.getFloorX(), position.getFloorY(), position.getFloorZ())
waterLevel = BlockStateValues.getWaterLevel(block); .thenApply(BlockStateValues::getWaterLevel);
} }
@Override @Override
@ -144,6 +145,6 @@ public class ItemEntity extends ThrowableEntity {
@Override @Override
protected boolean isInWater() { protected boolean isInWater() {
return waterLevel != -1; return waterLevel.join() != -1;
} }
} }

Datei anzeigen

@ -0,0 +1,55 @@
/*
* 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.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import net.kyori.adventure.text.Component;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import java.util.UUID;
// Note: 1.19.4 requires that the billboard is set to something in order to show, on Java Edition
public class TextDisplayEntity extends Entity {
public TextDisplayEntity(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);
}
@Override
protected void initializeMetadata() {
super.initializeMetadata();
// Remove armor stand body
this.dirtyMetadata.put(EntityData.SCALE, 0f);
this.dirtyMetadata.put(EntityData.NAMETAG_ALWAYS_SHOW, (byte) 1);
}
public void setText(EntityMetadata<Component, ?> entityMetadata) {
this.dirtyMetadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(entityMetadata.getValue()));
}
}

Datei anzeigen

@ -34,12 +34,13 @@ import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class SquidEntity extends WaterEntity implements Tickable { public class SquidEntity extends WaterEntity implements Tickable {
private float targetPitch; private float targetPitch;
private float targetYaw; 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) { 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); 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 pitchChanged;
boolean yawChanged; boolean yawChanged;
float oldPitch = pitch; float oldPitch = pitch;
if (inWater) { if (inWater.join()) {
float oldYaw = yaw; float oldYaw = yaw;
pitch += (targetPitch - pitch) * 0.1f; pitch += (targetPitch - pitch) * 0.1f;
yaw += (targetYaw - yaw) * 0.1f; yaw += (targetYaw - yaw) * 0.1f;
@ -93,7 +94,7 @@ public class SquidEntity extends WaterEntity implements Tickable {
@Override @Override
public void setYaw(float yaw) { public void setYaw(float yaw) {
// Let the Java server control yaw when the squid is out of water // Let the Java server control yaw when the squid is out of water
if (!inWater) { if (!inWater.join()) {
this.yaw = yaw; this.yaw = yaw;
} }
} }
@ -127,10 +128,10 @@ public class SquidEntity extends WaterEntity implements Tickable {
private void checkInWater() { private void checkInWater() {
if (getFlag(EntityFlag.RIDING)) { if (getFlag(EntityFlag.RIDING)) {
inWater = false; inWater = CompletableFuture.completedFuture(false);
} else { } else {
int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt()); inWater = session.getGeyser().getWorldManager().getBlockAtAsync(session, position.toInt())
inWater = BlockStateValues.getWaterLevel(block) != -1; .thenApply(block -> BlockStateValues.getWaterLevel(block) != -1);
} }
} }
} }

Datei anzeigen

@ -43,6 +43,15 @@ public class ShulkerEntity extends GolemEntity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
// Indicate that invisibility should be fixed through the resource pack // Indicate that invisibility should be fixed through the resource pack
setFlag(EntityFlag.BRIBED, true); setFlag(EntityFlag.BRIBED, true);
}
@Override
protected void initializeMetadata() {
super.initializeMetadata();
// As of 1.19.4, it seems Java no longer sends the shulker color if it's the default color on initial spawn
// We still need the special case for 16 color in setShulkerColor though as it will send it for an entity metadata update
dirtyMetadata.put(EntityData.VARIANT, 16);
} }
public void setAttachedFace(EntityMetadata<Direction, ?> entityMetadata) { public void setAttachedFace(EntityMetadata<Direction, ?> entityMetadata) {

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -25,20 +25,64 @@
package org.geysermc.geyser.level; 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.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtMapBuilder;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 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.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 { public class GeyserWorldManager extends WorldManager {
private final Object2ObjectMap<String, String> gameruleCache = new Object2ObjectOpenHashMap<>(); private final Object2ObjectMap<String, String> gameruleCache = new Object2ObjectOpenHashMap<>();
@Override @Override
public int getBlockAt(GeyserSession session, int x, int y, int z) { public int getBlockAt(GeyserSession session, int x, int y, int z) {
var erosionHandler = session.getErosionHandler().getAsActive();
if (erosionHandler == null) {
return session.getChunkCache().getBlockAt(x, y, z); 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 @Override
public boolean hasOwnChunkCache() { public boolean hasOwnChunkCache() {
@ -47,10 +91,31 @@ public class GeyserWorldManager extends WorldManager {
} }
@Override @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. // 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 // 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() lecternTag.putCompound("book", NbtMap.builder()
.putByte("Count", (byte) 1) .putByte("Count", (byte) 1)
.putShort("Damage", (short) 0) .putShort("Damage", (short) 0)
@ -61,12 +126,12 @@ public class GeyserWorldManager extends WorldManager {
.build()) .build())
.build()); .build());
lecternTag.putInt("page", -1); // I'm surprisingly glad this exists - it forces Bedrock to stop reading immediately. Usually. 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 @Override
public boolean shouldExpectLecternHandled() { public boolean shouldExpectLecternHandled(GeyserSession session) {
return false; return session.getErosionHandler().isActive();
} }
@Override @Override
@ -99,4 +164,17 @@ public class GeyserWorldManager extends WorldManager {
public boolean hasPermission(GeyserSession session, String permission) { public boolean hasPermission(GeyserSession session, String permission) {
return false; 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;
}
} }

Datei anzeigen

@ -26,14 +26,16 @@
package org.geysermc.geyser.level; package org.geysermc.geyser.level;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; 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.mc.protocol.data.game.setting.Difficulty;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3i; 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.geysermc.geyser.session.GeyserSession;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CompletableFuture; 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 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. * Checks whether or not this world manager requires a separate chunk cache/has access to more block data than the chunk cache.
* <p> * <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 * We solve this problem by querying all loaded lecterns, where possible, and sending their information in a block entity
* tag. * tag.
* *
* Note that the lectern data may be sent asynchronously.
*
* @param session the session of the player * @param session the session of the player
* @param x the x coordinate of the lectern * @param x the x coordinate of the lectern
* @param y the y coordinate of the lectern * @param y the y coordinate of the lectern
* @param z the z 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. * @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 * Updates a gamerule value on the Java server

Datei anzeigen

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

Datei anzeigen

@ -33,10 +33,10 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.geysermc.erosion.util.BlockPositionIterator;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.player.PlayerEntity; 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.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache; 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 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); 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() { public BlockPositionIterator playerCollidableBlocksIterator() {
@ -235,8 +235,9 @@ public class CollisionManager {
// Used when correction code needs to be run before the main correction // Used when correction code needs to be run before the main correction
BlockPositionIterator iter = session.getCollisionManager().playerCollidableBlocksIterator(); BlockPositionIterator iter = session.getCollisionManager().playerCollidableBlocksIterator();
for (; iter.hasNext(); iter.next()) { int[] blocks = session.getGeyser().getWorldManager().getBlocksAt(session, iter);
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, iter.getX(), iter.getY(), iter.getZ()); for (iter.reset(); iter.hasNext(); iter.next()) {
BlockCollision blockCollision = BlockUtils.getCollision(blocks[iter.getIteration()]);
if (blockCollision != null) { if (blockCollision != null) {
blockCollision.beforeCorrectPosition(iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox); blockCollision.beforeCorrectPosition(iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox);
} }
@ -244,7 +245,7 @@ public class CollisionManager {
// Main correction code // Main correction code
for (iter.reset(); iter.hasNext(); iter.next()) { 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 != null) {
if (!blockCollision.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) { if (!blockCollision.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) {
return false; return false;

Datei anzeigen

@ -108,7 +108,14 @@ public class ResourcePack {
try (ZipFile zip = new ZipFile(file); try (ZipFile zip = new ZipFile(file);
Stream<? extends ZipEntry> stream = zip.stream()) { Stream<? extends ZipEntry> stream = zip.stream()) {
stream.forEach((x) -> { 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 { try {
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class); ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
// Sometimes a pack_manifest file is present and not in a valid format, // Sometimes a pack_manifest file is present and not in a valid format,

Datei anzeigen

@ -112,6 +112,8 @@ import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.entity.type.Tickable; import org.geysermc.geyser.entity.type.Tickable;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; 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.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe; 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.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.DimensionUtils;
import org.geysermc.geyser.util.LoginEncryptionUtils; import org.geysermc.geyser.util.LoginEncryptionUtils;
import org.jetbrains.annotations.NotNull;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -168,6 +171,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter @Setter
private JsonNode certChainData; private JsonNode certChainData;
@NotNull
@Setter
private AbstractGeyserboundPacketHandler erosionHandler;
@Accessors(fluent = true) @Accessors(fluent = true)
@Setter @Setter
private RemoteServer remoteServer; 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. * 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. * for more information.
*/ */
private final Set<Vector3i> lecternCache; private final Set<Vector3i> lecternCache;
@ -557,6 +564,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.upstream = new UpstreamSession(bedrockServerSession); this.upstream = new UpstreamSession(bedrockServerSession);
this.eventLoop = eventLoop; this.eventLoop = eventLoop;
this.erosionHandler = new GeyserboundHandshakePacketHandler(this);
this.advancementsCache = new AdvancementsCache(this); this.advancementsCache = new AdvancementsCache(this);
this.bookEditCache = new BookEditCache(this); this.bookEditCache = new BookEditCache(this);
this.chunkCache = new ChunkCache(this); this.chunkCache = new ChunkCache(this);
@ -585,7 +594,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.spawned = false; this.spawned = false;
this.loggedIn = false; this.loggedIn = false;
if (geyser.getWorldManager().shouldExpectLecternHandled()) { if (geyser.getWorldManager().shouldExpectLecternHandled(this)) {
// Unneeded on these platforms // Unneeded on these platforms
this.lecternCache = null; this.lecternCache = null;
} else { } else {
@ -1094,6 +1103,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
tickThread.cancel(false); tickThread.cancel(false);
} }
erosionHandler.close();
closed = true; closed = true;
} }

Datei anzeigen

@ -35,6 +35,7 @@ import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType; import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData; 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.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.LecternContainer; import org.geysermc.geyser.inventory.LecternContainer;
@ -110,13 +111,13 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator {
Vector3i position = session.getLastInteractionBlockPosition(); Vector3i position = session.getLastInteractionBlockPosition();
// If shouldExpectLecternHandled returns true, this is already handled for us // 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 // 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; NbtMap blockEntityTag;
if (tag != null) { if (tag != null) {
int pagesSize = ((ListTag) tag.get("pages")).size(); int pagesSize = ((ListTag) tag.get("pages")).size();
ItemData itemData = book.getItemData(session); 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() lecternTag.putCompound("book", NbtMap.builder()
.putByte("Count", (byte) itemData.getCount()) .putByte("Count", (byte) itemData.getCount())
.putShort("Damage", (short) 0) .putShort("Damage", (short) 0)
@ -127,7 +128,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator {
blockEntityTag = lecternTag.build(); blockEntityTag = lecternTag.build();
} else { } else {
// There is *a* book here, but... no NBT. // 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() NbtMapBuilder bookTag = NbtMap.builder()
.putByte("Count", (byte) 1) .putByte("Count", (byte) 1)
.putShort("Damage", (short) 0) .putShort("Damage", (short) 0)
@ -162,20 +163,4 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator {
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) { public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new LecternContainer(name, windowId, this.size, containerType, 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;
}
} }

Datei anzeigen

@ -48,6 +48,8 @@ public class PotionTranslator extends ItemTranslator {
if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings); if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings);
Tag potionTag = itemStack.getNbt().get("Potion"); Tag potionTag = itemStack.getNbt().get("Potion");
if (potionTag instanceof StringTag) { if (potionTag instanceof StringTag) {
int customItemId = CustomItemTranslator.getCustomItem(itemStack.getNbt(), mapping);
if (customItemId == -1) {
Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue()); Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue());
if (potion != null) { if (potion != null) {
return ItemData.builder() return ItemData.builder()
@ -57,6 +59,12 @@ public class PotionTranslator extends ItemTranslator {
.tag(translateNbtToBedrock(itemStack.getNbt())); .tag(translateNbtToBedrock(itemStack.getNbt()));
} }
GeyserImpl.getInstance().getLogger().debug("Unknown Java potion: " + potionTag.getValue()); GeyserImpl.getInstance().getLogger().debug("Unknown Java potion: " + potionTag.getValue());
} else {
return ItemData.builder()
.id(customItemId)
.count(itemStack.getAmount())
.tag(translateNbtToBedrock(itemStack.getNbt()));
}
} }
return super.translateToBedrock(itemStack, mapping, mappings); return super.translateToBedrock(itemStack, mapping, mappings);
} }

Datei anzeigen

@ -40,6 +40,8 @@ import javax.annotation.Nonnull;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.geysermc.erosion.util.BannerUtils.getJavaPatternTag;
@ItemRemapper @ItemRemapper
public class BannerTranslator extends NbtItemStackTranslator { public class BannerTranslator extends NbtItemStackTranslator {
/** /**
@ -66,15 +68,6 @@ public class BannerTranslator extends NbtItemStackTranslator {
OMINOUS_BANNER_PATTERN.add(getJavaPatternTag("bo", 15)); 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() { public BannerTranslator() {
appliedItems = Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getItems()) appliedItems = Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getItems())
.filter(entry -> entry.getJavaIdentifier().endsWith("banner")) .filter(entry -> entry.getJavaIdentifier().endsWith("banner"))
@ -117,7 +110,7 @@ public class BannerTranslator extends NbtItemStackTranslator {
* @return The Java edition format pattern nbt * @return The Java edition format pattern nbt
*/ */
public static CompoundTag getJavaBannerPattern(NbtMap pattern) { 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"));
} }
/** /**

Datei anzeigen

@ -224,7 +224,7 @@ public class PistonBlockEntity {
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, blockInFront); int blockId = session.getGeyser().getWorldManager().getBlockAt(session, blockInFront);
if (BlockStateValues.isPistonHead(blockId)) { if (BlockStateValues.isPistonHead(blockId)) {
ChunkUtils.updateBlock(session, BlockStateValues.JAVA_AIR_ID, blockInFront); 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 // 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); ChunkUtils.updateBlock(session, BlockStateValues.JAVA_AIR_ID, blockInFront);
} }

Datei anzeigen

@ -71,13 +71,12 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
boolean addNbtData = packet.isAddUserData() && blockMapping.isBlockEntity(); // Holding down CTRL boolean addNbtData = packet.isAddUserData() && blockMapping.isBlockEntity(); // Holding down CTRL
if (BlockStateValues.getBannerColor(blockToPick) != -1 || addNbtData) { if (BlockStateValues.getBannerColor(blockToPick) != -1 || addNbtData) {
session.getGeyser().getWorldManager().getPickItemNbt(session, vector.getX(), vector.getY(), vector.getZ(), addNbtData) session.getGeyser().getWorldManager().getPickItemNbt(session, vector.getX(), vector.getY(), vector.getZ(), addNbtData)
.whenComplete((tag, ex) -> { .whenComplete((tag, ex) -> session.ensureInEventLoop(() -> {
if (tag == null) { if (tag == null) {
pickItem(session, blockMapping); pickItem(session, blockMapping);
return; return;
} }
session.ensureInEventLoop(() -> {
if (addNbtData) { if (addNbtData) {
ListTag lore = new ListTag("Lore"); ListTag lore = new ListTag("Lore");
lore.add(new StringTag("", "\"(+NBT)\"")); lore.add(new StringTag("", "\"(+NBT)\""));
@ -93,8 +92,7 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
ItemStack itemStack = new ItemStack(mapping.getJavaId(), 1, tag); ItemStack itemStack = new ItemStack(mapping.getJavaId(), 1, tag);
InventoryUtils.findOrCreateItem(session, itemStack); InventoryUtils.findOrCreateItem(session, itemStack);
}); }));
});
return; return;
} }

Datei anzeigen

@ -35,10 +35,13 @@ import io.netty.buffer.Unpooled;
import org.geysermc.cumulus.Forms; import org.geysermc.cumulus.Forms;
import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormType; 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.floodgate.pluginmessage.PluginMessageChannels;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
@ -51,14 +54,17 @@ public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCus
@Override @Override
public void translate(GeyserSession session, ClientboundCustomPayloadPacket packet) { public void translate(GeyserSession session, ClientboundCustomPayloadPacket packet) {
// The only plugin messages it has to listen for are Floodgate plugin messages String channel = packet.getChannel();
if (session.remoteServer().authType() != AuthType.FLOODGATE) {
if (channel.equals(Constants.PLUGIN_MESSAGE)) {
ByteBuf buf = Unpooled.wrappedBuffer(packet.getData());
ErosionPacket<?> erosionPacket = Packets.decode(buf);
((GeyserboundPacket) erosionPacket).handle(session.getErosionHandler());
return; return;
} }
String channel = packet.getChannel();
if (channel.equals(PluginMessageChannels.FORM)) { if (channel.equals(PluginMessageChannels.FORM)) {
session.ensureInEventLoop(() -> {
byte[] data = packet.getData(); byte[] data = packet.getData();
// receive: first byte is form type, second and third are the id, remaining is the form data // receive: first byte is form type, second and third are the id, remaining is the form data
@ -89,8 +95,10 @@ public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCus
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, finalData)); session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, finalData));
}); });
session.sendForm(form); session.sendForm(form);
});
} else if (channel.equals(PluginMessageChannels.TRANSFER)) { } else if (channel.equals(PluginMessageChannels.TRANSFER)) {
session.ensureInEventLoop(() -> {
byte[] data = packet.getData(); byte[] data = packet.getData();
// port (4 bytes), address (remaining data) // port (4 bytes), address (remaining data)
@ -109,8 +117,10 @@ public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCus
transferPacket.setAddress(address); transferPacket.setAddress(address);
transferPacket.setPort(port); transferPacket.setPort(port);
session.sendUpstreamPacket(transferPacket); session.sendUpstreamPacket(transferPacket);
});
} else if (channel.equals(PluginMessageChannels.PACKET)) { } else if (channel.equals(PluginMessageChannels.PACKET)) {
session.ensureInEventLoop(() -> {
logger.debug("A packet has been sent using the Floodgate api"); logger.debug("A packet has been sent using the Floodgate api");
byte[] data = packet.getData(); byte[] data = packet.getData();
@ -127,6 +137,13 @@ public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCus
toSend.setPayload(packetData); toSend.setPayload(packetData);
session.sendUpstreamPacket(toSend); session.sendUpstreamPacket(toSend);
});
} }
} }
@Override
public boolean shouldExecuteInEventLoop() {
// For Erosion packets
return false;
}
} }

Datei anzeigen

@ -36,6 +36,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.TextDecoration; import org.geysermc.geyser.text.TextDecoration;
@ -57,6 +58,11 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
SessionPlayerEntity entity = session.getPlayerEntity(); SessionPlayerEntity entity = session.getPlayerEntity();
entity.setEntityId(packet.getEntityId()); entity.setEntityId(packet.getEntityId());
if (session.getErosionHandler().isActive()) {
session.getErosionHandler().close();
session.setErosionHandler(new GeyserboundHandshakePacketHandler(session));
}
Map<String, JavaDimension> dimensions = session.getDimensions(); Map<String, JavaDimension> dimensions = session.getDimensions();
dimensions.clear(); dimensions.clear();
@ -129,6 +135,10 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData())); 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 // register the plugin messaging channels used in Floodgate
if (session.remoteServer().authType() == AuthType.FLOODGATE) { if (session.remoteServer().authType() == AuthType.FLOODGATE) {
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:register", PluginMessageChannels.getFloodgateRegisterData())); session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:register", PluginMessageChannels.getFloodgateRegisterData()));

Datei anzeigen

@ -58,15 +58,16 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
blockEventPacket.setEventType(1); blockEventPacket.setEventType(1);
session.sendUpstreamPacket(blockEventPacket); session.sendUpstreamPacket(blockEventPacket);
} else if (packet.getValue() instanceof NoteBlockValue) { } else if (packet.getValue() instanceof NoteBlockValue) {
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, position); session.getGeyser().getWorldManager().getBlockAtAsync(session, position).thenAccept(blockState -> {
blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState)); blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState));
session.sendUpstreamPacket(blockEventPacket); session.sendUpstreamPacket(blockEventPacket);
});
} else if (packet.getValue() instanceof PistonValue pistonValue) { } else if (packet.getValue() instanceof PistonValue pistonValue) {
PistonValueType action = (PistonValueType) packet.getType(); PistonValueType action = (PistonValueType) packet.getType();
Direction direction = Direction.fromPistonValue(pistonValue.getDirection()); Direction direction = Direction.fromPistonValue(pistonValue.getDirection());
PistonCache pistonCache = session.getPistonCache(); 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 // 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 // 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 // See https://github.com/PaperMC/Paper/blob/6fa1983e9ce177a4a412d5b950fd978620174777/patches/server/0304-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch

Datei anzeigen

@ -43,7 +43,7 @@ public class JavaBlockUpdateTranslator extends PacketTranslator<ClientboundBlock
public void translate(GeyserSession session, ClientboundBlockUpdatePacket packet) { public void translate(GeyserSession session, ClientboundBlockUpdatePacket packet) {
Vector3i pos = packet.getEntry().getPosition(); Vector3i pos = packet.getEntry().getPosition();
boolean updatePlacement = session.getGeyser().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event 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()); session.getWorldCache().updateServerCorrectBlockState(pos, packet.getEntry().getBlock());
if (updatePlacement) { if (updatePlacement) {
this.checkPlace(session, packet); this.checkPlace(session, packet);

Datei anzeigen

@ -52,7 +52,7 @@ public class JavaForgetLevelChunkTranslator extends PacketTranslator<Clientbound
} }
removedSkulls.forEach(session.getSkullCache()::removeSkull); removedSkulls.forEach(session.getSkullCache()::removeSkull);
if (!session.getGeyser().getWorldManager().shouldExpectLecternHandled()) { if (!session.getGeyser().getWorldManager().shouldExpectLecternHandled(session)) {
// Do the same thing with lecterns // Do the same thing with lecterns
Iterator<Vector3i> iterator = session.getLecternCache().iterator(); Iterator<Vector3i> iterator = session.getLecternCache().iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {

Datei anzeigen

@ -38,6 +38,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NBTOutputStream;
import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtUtils; import com.nukkitx.nbt.NbtUtils;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -49,6 +50,7 @@ import it.unimi.dsi.fastutil.ints.IntImmutableList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists; import it.unimi.dsi.fastutil.ints.IntLists;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.geysermc.erosion.util.LecternUtils;
import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
@ -95,6 +97,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
final BlockEntityInfo[] blockEntities = packet.getBlockEntities(); final BlockEntityInfo[] blockEntities = packet.getBlockEntities();
final List<NbtMap> bedrockBlockEntities = new ObjectArrayList<>(blockEntities.length); final List<NbtMap> bedrockBlockEntities = new ObjectArrayList<>(blockEntities.length);
final List<BlockEntityInfo> lecterns = new ObjectArrayList<>();
BitSet waterloggedPaletteIds = new BitSet(); BitSet waterloggedPaletteIds = new BitSet();
BitSet bedrockOnlyBlockEntityIds = new BitSet(); BitSet bedrockOnlyBlockEntityIds = new BitSet();
@ -328,7 +331,9 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
extendedCollisionNextSection = thisExtendedCollisionNextSection; extendedCollisionNextSection = thisExtendedCollisionNextSection;
} }
if (!session.getErosionHandler().isActive()) {
session.getChunkCache().addToCache(packet.getX(), packet.getZ(), javaChunks); session.getChunkCache().addToCache(packet.getX(), packet.getZ(), javaChunks);
}
final int chunkBlockX = packet.getX() << 4; final int chunkBlockX = packet.getX() << 4;
final int chunkBlockZ = packet.getZ() << 4; final int chunkBlockZ = packet.getZ() << 4;
@ -350,8 +355,15 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
if (type == BlockEntityType.LECTERN && BlockStateValues.getLecternBookStates().get(blockState)) { if (type == BlockEntityType.LECTERN && BlockStateValues.getLecternBookStates().get(blockState)) {
// If getLecternBookStates is false, let's just treat it like a normal block entity // If getLecternBookStates is false, let's just treat it like a normal block entity
bedrockBlockEntities.add(session.getGeyser().getWorldManager().getLecternDataAt( // Fill in tag with a default value
session, x + chunkBlockX, y, z + chunkBlockZ, true)); 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; continue;
} }
@ -455,6 +467,10 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
levelChunkPacket.setData(payload); levelChunkPacket.setData(payload);
session.sendUpstreamPacket(levelChunkPacket); 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()) { for (Map.Entry<Vector3i, ItemFrameEntity> entry : session.getItemFrameCache().entrySet()) {
Vector3i position = entry.getKey(); Vector3i position = entry.getKey();
if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) { if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) {

Datei anzeigen

@ -27,9 +27,9 @@ package org.geysermc.geyser.util.collection;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMap;
import org.geysermc.erosion.util.LecternUtils;
import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
import org.geysermc.geyser.util.BlockEntityUtils; import org.geysermc.geyser.util.BlockEntityUtils;
/** /**
@ -47,27 +47,24 @@ public class LecternHasBookMap extends FixedInt2BooleanMap {
int offset = blockState - this.start; int offset = blockState - this.start;
if (offset < 0 || offset >= this.value.length) { if (offset < 0 || offset >= this.value.length) {
// Block state is out of bounds of this map - lectern has been destroyed, if it existed // 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); session.getLecternCache().remove(position);
} }
return; return;
} }
boolean newLecternHasBook; boolean newLecternHasBook;
if (worldManager.shouldExpectLecternHandled()) { if (worldManager.shouldExpectLecternHandled(session)) {
// As of right now, no tag can be added asynchronously worldManager.sendLecternData(session, position.getX(), position.getY(), position.getZ());
worldManager.getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
} else if ((newLecternHasBook = this.value[offset]) != this.get(worldManager.getBlockAt(session, position))) { } 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. // newLecternHasBook = the new lectern block state's "has book" toggle.
if (newLecternHasBook) { if (newLecternHasBook) {
newLecternTag = worldManager.getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false); worldManager.sendLecternData(session, position.getX(), position.getY(), position.getZ());
} else { } else {
session.getLecternCache().remove(position); 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);
} }
} }
}
} }

Datei anzeigen

@ -1,6 +1,7 @@
[versions] [versions]
base-api = "1.0.0-SNAPSHOT" base-api = "1.0.0-SNAPSHOT"
cumulus = "1.1.1" cumulus = "1.1.1"
erosion = "1.0-20230330.170602-3"
events = "1.0-SNAPSHOT" events = "1.0-SNAPSHOT"
jackson = "2.14.0" jackson = "2.14.0"
fastutil = "8.5.2" fastutil = "8.5.2"
@ -19,9 +20,9 @@ checkerframework = "3.19.0"
log4j = "2.17.1" log4j = "2.17.1"
jline = "3.21.0" jline = "3.21.0"
terminalconsoleappender = "1.2.0" terminalconsoleappender = "1.2.0"
paper = "1.19-R0.1-SNAPSHOT" folia = "1.19.4-R0.1-SNAPSHOT"
viaversion = "4.0.0" viaversion = "4.0.0"
adapters = "1.6-SNAPSHOT" adapters = "1.7-SNAPSHOT"
commodore = "2.2" commodore = "2.2"
bungeecord = "a7c6ede" bungeecord = "a7c6ede"
velocity = "3.0.0" 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" } cumulus = { group = "org.geysermc.cumulus", name = "cumulus", version.ref = "cumulus" }
events = { group = "org.geysermc.event", name = "events", version.ref = "events" } 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-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-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" } 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-terminal-jna = { group = "org.jline", name = "jline-terminal-jna", version.ref = "jline" }
jline-reader = { group = "org.jline", name = "jline-reader", 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" } folia-api = { group = "dev.folia", name = "folia-api", version.ref = "folia" }
paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "paper" } paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "folia" }
# check these on https://modmuss50.me/fabric.html # check these on https://modmuss50.me/fabric.html
fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" } fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" }