1
0
Fork 0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-03-23 23:39:29 +01:00

Merge remote-tracking branch 'origin/master' into feature/floodgate-merge

# Conflicts:
#	bootstrap/spigot/base/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java
#	core/src/main/java/org/geysermc/geyser/GeyserImpl.java
#	core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java
#	core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java
#	gradle.properties
#	gradle/libs.versions.toml
This commit is contained in:
Tim203 2025-02-24 14:09:17 +01:00
commit 79a2b22541
No known key found for this signature in database
387 changed files with 57141 additions and 34464 deletions
.github/ISSUE_TEMPLATE
README.md
api/src/main/java/org/geysermc/geyser/api
bootstrap
bungeecord/base/src/main/java/org/geysermc/geyser/platform/bungeecord
mod
fabric
build.gradle.kts
src/main
java/org/geysermc/geyser/platform/fabric
resources
neoforge
build.gradle.kts
src/main/java/org/geysermc/geyser/platform/neoforge
src/main/java/org/geysermc/geyser/platform/mod
spigot/base
standalone
build.gradle.kts
src/main
java/org/geysermc/geyser/platform/standalone
resources
velocity/base
build.gradle.kts
src/main/java/org/geysermc/geyser/platform/velocity
viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy
build-logic/src/main/kotlin
core/src/main/java/org/geysermc/geyser

View file

@ -1,11 +1,12 @@
name: Bug report
description: Create a report to help us improve
type: Bug
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report for Geyser! Fill out the following form to your best ability to help us fix the problem.
Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues).
Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://geysermc.org/wiki/geyser/faq) or the [Common Issues](https://geysermc.org/wiki/geyser/common-issues).
- type: textarea
attributes:
label: Describe the bug

View file

@ -1,12 +1,13 @@
name: Feature request
description: Suggest an idea for this project
labels: "Feature Request"
type: Feature
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request for Geyser! Please fill out the following form to your best ability to help us understand your feature request and significantly improve the chance of getting added.
For anything else than a feature request, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or [the Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues).
For anything else than a feature request, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://geysermc.org/wiki/geyser/faq) or the [Common Issues](https://geysermc.org/wiki/geyser/common-issues).
- type: textarea
attributes:
label: What feature do you want to see added?
@ -18,4 +19,4 @@ body:
label: Are there any alternatives?
description: List any alternatives you might have tried
validations:
required: true
required: true

View file

@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
## Supported Versions
Geyser is currently supporting Minecraft Bedrock 1.20.80 - 1.21.30 and Minecraft Java 1.21/1.21.1. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
Geyser is currently supporting Minecraft Bedrock 1.21.40 - 1.21.60 and Minecraft Java 1.21.4. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
## Setting Up
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.

View file

@ -1,7 +0,0 @@
package org.geysermc.geyser.api.block.custom.nonvanilla;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
public record JavaBlockItem(@NonNull String identifier, @NonNegative int javaId, @NonNegative int stackSize) {
}

View file

@ -70,7 +70,7 @@ public interface JavaBlockState {
@Nullable String pistonBehavior();
/**
* Gets whether the block state has block entity
* Gets whether the block state has a block entity
*
* @return whether the block state has block entity
* @deprecated Does not have an effect. If you were using this to

View file

@ -70,6 +70,11 @@ public interface GeyserConnection extends Connection, CommandSource {
*/
void closeForm();
/**
* Gets the Bedrock protocol version of the player.
*/
int protocolVersion();
/**
* @param javaId the Java entity ID to look up.
* @return a {@link GeyserEntity} if present in this connection's entity tracker.

View file

@ -31,9 +31,9 @@ import org.geysermc.geyser.api.entity.type.GeyserEntity;
public interface GeyserPlayerEntity extends GeyserEntity {
/**
* Gets the position of the player.
* Gets the position of the player, as it is known to the Java server.
*
* @return the position of the player.
* @return the player's position
*/
Vector3f position();
}

View file

@ -50,7 +50,7 @@ public class SessionDisconnectEvent extends ConnectionEvent {
}
/**
* Sets the disconnect reason, thereby overriding th original reason.
* Sets the disconnect message shown to the Bedrock client.
*
* @param disconnectReason the reason for the disconnect
*/

View file

@ -33,10 +33,10 @@ import org.geysermc.event.Event;
import java.net.InetSocketAddress;
/**
* Called whenever Geyser gets pinged
* Called whenever Geyser gets pinged by a Bedrock client.
* <p>
* This event allows you to modify/obtain the MOTD, maximum player count, and current number of players online,
* Geyser will reply to the client with what was given.
* This event allows you to modify/obtain the MOTD, maximum player count, and current number of players online.
* Geyser will reply to the client with the information provided in this event.
*/
public interface GeyserBedrockPingEvent extends Event {

View file

@ -37,7 +37,7 @@ import java.util.Set;
* <br>
* This event is mapped to the existence of Brigadier on the server.
*/
public class ServerDefineCommandsEvent extends ConnectionEvent implements Cancellable {
public final class ServerDefineCommandsEvent extends ConnectionEvent implements Cancellable {
private final Set<? extends CommandInfo> commands;
private boolean cancelled;

View file

@ -37,7 +37,7 @@ import java.util.Map;
* Fired when the Java server sends a transfer request to a different Java server.
* Geyser Extensions can listen to this event and set a target server ip/port for Bedrock players to be transferred to.
*/
public class ServerTransferEvent extends ConnectionEvent {
public final class ServerTransferEvent extends ConnectionEvent {
private final String host;
private final int port;

View file

@ -80,10 +80,9 @@ public interface NonVanillaCustomItemData extends CustomItemData {
@Nullable String toolType();
/**
* Gets the tool tier of the item.
*
* @return the tool tier of the item
* @deprecated no longer used
*/
@Deprecated(forRemoval = true)
@Nullable String toolTier();
/**
@ -108,10 +107,9 @@ public interface NonVanillaCustomItemData extends CustomItemData {
@Nullable String translationString();
/**
* Gets the repair materials of the item.
*
* @return the repair materials of the item
* @deprecated No longer used.
*/
@Deprecated(forRemoval = true)
@Nullable Set<String> repairMaterials();
/**

View file

@ -28,6 +28,7 @@ package org.geysermc.geyser.platform.bungeecord;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import net.md_5.bungee.netty.LengthPrependerAndCompressor;
import net.md_5.bungee.protocol.packet.LoginSuccess;
import net.md_5.bungee.protocol.packet.SetCompression;
@ -40,8 +41,9 @@ public class GeyserBungeeCompressionDisabler extends ChannelOutboundHandlerAdapt
// Fixes https://github.com/GeyserMC/Geyser/issues/4281
// The server may send a LoginDisconnect packet after compression is set.
if (!compressionDisabled) {
if (ctx.pipeline().get("compress") != null) {
ctx.pipeline().remove("compress");
LengthPrependerAndCompressor compressor = ctx.pipeline().get(LengthPrependerAndCompressor.class);
if (compressor.isCompress()) {
compressor.setCompress(false);
compressionDisabled = true;
}
if (ctx.pipeline().get("decompress") != null) {

View file

@ -73,6 +73,14 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
throw new UnsupportedOperationException("Geyser does not currently support multiple listeners with injection! " +
"Please reach out to us on our Discord at https://discord.gg/GeyserMC so we can hear feedback on your setup.");
}
// TODO remove
try {
ProxyServer.class.getMethod("unsafe");
} catch (NoSuchMethodException e) {
throw new UnsupportedOperationException("You're using an outdated version of BungeeCord - please update. Thank you!");
}
ListenerInfo listenerInfo = proxy.getConfig().getListeners().stream().findFirst().orElseThrow(IllegalStateException::new);
Class<? extends ProxyServer> proxyClass = proxy.getClass();
@ -138,7 +146,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
if (channelInitializer == null) {
// Proxy has finished initializing; we can safely grab this variable without fear of plugins modifying it
// (Older versions of ViaVersion replace this to inject)
channelInitializer = PipelineUtils.SERVER_CHILD;
channelInitializer = proxy.unsafe().getFrontendChannelInitializer().getChannelInitializer();
}
initChannel.invoke(channelInitializer, ch);

View file

@ -196,6 +196,21 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
return false;
}
@Override
public boolean isTransferred() {
return false;
}
@Override
public CompletableFuture<byte[]> retrieveCookie(String s) {
throw new UnsupportedOperationException();
}
@Override
public CompletableFuture<byte[]> sendData(String s, byte[] bytes) {
throw new UnsupportedOperationException();
}
@Override
public Unsafe unsafe() {
throw new UnsupportedOperationException();

View file

@ -43,6 +43,7 @@ import org.geysermc.geyser.command.CommandSourceConverter;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
@ -60,6 +61,7 @@ import java.net.SocketAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -82,18 +84,19 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
public void onGeyserInitialize() {
GeyserLocale.init(this);
// Copied from ViaVersion.
// https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43
try {
ProtocolConstants.class.getField("MINECRAFT_1_21");
} catch (NoSuchFieldException e) {
geyserLogger.error(" / \\");
geyserLogger.error(" / \\");
geyserLogger.error(" / | \\");
geyserLogger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName()));
geyserLogger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
geyserLogger.error(" / o \\");
geyserLogger.error("/_____________\\");
List<Integer> supportedProtocols = ProtocolConstants.SUPPORTED_VERSION_IDS;
if (!supportedProtocols.contains(GameProtocol.getJavaProtocolVersion())) {
geyserLogger.error(" / \\");
geyserLogger.error(" / \\");
geyserLogger.error(" / | \\");
geyserLogger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName()));
geyserLogger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
geyserLogger.error(" / o \\");
geyserLogger.error("/_____________\\");
}
} catch (Throwable e) {
geyserLogger.warning("Unable to check the versions supported by this proxy! " + e.getMessage());
}
if (!this.loadConfig()) {

View file

@ -37,6 +37,7 @@ dependencies {
modImplementation(libs.cloud.fabric)
include(libs.cloud.fabric)
include(libs.fabric.permissions.api)
}
tasks.withType<Jar> {
@ -45,7 +46,6 @@ tasks.withType<Jar> {
relocate("org.cloudburstmc.netty")
relocate("org.cloudburstmc.protocol")
relocate("com.github.steveice10.mc.auth")
tasks {
remapJar {

View file

@ -32,7 +32,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import net.minecraft.server.level.ServerPlayer;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.CommandSourceConverter;
@ -80,7 +80,7 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit
var sourceConverter = CommandSourceConverter.layered(
CommandSourceStack.class,
id -> getServer().getPlayerList().getPlayer(id),
Player::createCommandSourceStack,
ServerPlayer::createCommandSourceStack,
() -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet
ModCommandSource::new
);

View file

@ -23,8 +23,8 @@
"geyser.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.11",
"fabric": "*",
"minecraft": ">=1.21"
"fabricloader": ">=0.16.7",
"fabric-api": "*",
"minecraft": ">=1.21.4"
}
}

View file

@ -13,6 +13,9 @@ architectury {
provided("org.cloudburstmc.math", "api")
provided("com.google.errorprone", "error_prone_annotations")
// Jackson shipped by Minecraft is too old, so we shade & relocate our newer version
relocate("com.fasterxml.jackson")
val includeTransitive: Configuration = configurations.getByName("includeTransitive")
dependencies {
@ -31,6 +34,12 @@ dependencies {
}
shadow(projects.core) { isTransitive = false }
// Minecraft (1.21.2+) includes jackson. But an old version!
shadow(libs.jackson.core) { isTransitive = false }
shadow(libs.jackson.databind) { isTransitive = false }
shadow(libs.jackson.dataformat.yaml) { isTransitive = false }
shadow(libs.jackson.annotations) { isTransitive = false }
// Let's shade in our own api
shadow(projects.api) { isTransitive = false }
@ -53,6 +62,11 @@ tasks {
remapModrinthJar {
archiveBaseName.set("geyser-neoforge")
}
shadowJar {
// Without this, jackson's service files are not relocated
mergeServiceFiles()
}
}
modrinth {

View file

@ -26,7 +26,7 @@
package org.geysermc.geyser.platform.neoforge;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.EventPriority;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
@ -72,7 +72,7 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
var sourceConverter = CommandSourceConverter.layered(
CommandSourceStack.class,
id -> getServer().getPlayerList().getPlayer(id),
Player::createCommandSourceStack,
ServerPlayer::createCommandSourceStack,
() -> getServer().createCommandSourceStack(),
ModCommandSource::new
);
@ -104,7 +104,9 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
}
private void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
GeyserModUpdateListener.onPlayReady(event.getEntity());
if (event.getEntity() instanceof ServerPlayer player) {
GeyserModUpdateListener.onPlayReady(player);
}
}
@Override

View file

@ -28,8 +28,8 @@ package org.geysermc.geyser.platform.mod;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import net.minecraft.network.protocol.login.ClientboundGameProfilePacket;
import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket;
import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket;
/**
* Disables the compression packet (and the compression handlers from being added to the pipeline) for Geyser clients
@ -45,7 +45,7 @@ public class GeyserModCompressionDisabler extends ChannelOutboundHandlerAdapter
Class<?> msgClass = msg.getClass();
// Don't let any compression packet get through
if (!ClientboundLoginCompressionPacket.class.isAssignableFrom(msgClass)) {
if (ClientboundGameProfilePacket.class.isAssignableFrom(msgClass)) {
if (ClientboundLoginFinishedPacket.class.isAssignableFrom(msgClass)) {
// We're past the point that a compression packet can be sent, so we can safely yeet ourselves away
ctx.channel().pipeline().remove(this);

View file

@ -25,13 +25,13 @@
package org.geysermc.geyser.platform.mod;
import net.minecraft.world.entity.player.Player;
import net.minecraft.server.level.ServerPlayer;
import org.geysermc.geyser.Permissions;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserModUpdateListener {
public static void onPlayReady(Player player) {
public static void onPlayReady(ServerPlayer player) {
// Should be creating this in the supplier, but we need it for the permission check.
// Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it.
ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());

View file

@ -28,36 +28,24 @@ package org.geysermc.geyser.platform.mod.world;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BannerBlockEntity;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.DecoratedPotBlockEntity;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.MinecraftKey;
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class GeyserModWorldManager extends GeyserWorldManager {
@ -84,16 +72,17 @@ public class GeyserModWorldManager extends GeyserWorldManager {
}
Level level = player.level();
if (y < level.getMinBuildHeight()) {
if (y < level.getMinY()) {
return 0;
}
ChunkAccess chunk = level.getChunkSource().getChunk(x >> 4, z >> 4, ChunkStatus.FULL, false);
// Only loads active chunks, and doesn't delegate to main thread
ChunkAccess chunk = ((ServerChunkCache) level.getChunkSource()).chunkMap.getChunkToSend(ChunkPos.asLong(x >> 4, z >> 4));
if (chunk == null) {
return 0;
}
int worldOffset = level.getMinBuildHeight() >> 4;
int worldOffset = level.getMinY() >> 4;
int chunkOffset = (y >> 4) - worldOffset;
if (chunkOffset < chunk.getSections().length) {
LevelChunkSection section = chunk.getSections()[chunkOffset];
@ -115,49 +104,6 @@ public class GeyserModWorldManager extends GeyserWorldManager {
return GameMode.byId(server.getDefaultGameType().getId());
}
@NonNull
@Override
public CompletableFuture<org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
CompletableFuture<org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents> future = new CompletableFuture<>();
server.execute(() -> {
ServerPlayer player = getPlayer(session);
if (player == null) {
future.complete(null);
return;
}
BlockPos pos = new BlockPos(x, y, z);
// Don't create a new block entity if invalid
//noinspection resource - level() is just a getter
BlockEntity blockEntity = player.level().getChunkAt(pos).getBlockEntity(pos);
if (blockEntity instanceof BannerBlockEntity banner) {
// Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and*
// the banner might have a custom name, both of which a Java client knows and caches
ItemStack itemStack = banner.getItem();
org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents components =
new org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents(new HashMap<>());
components.put(DataComponentType.DAMAGE, itemStack.getDamageValue());
Component customName = itemStack.getComponents().get(DataComponents.CUSTOM_NAME);
if (customName != null) {
components.put(DataComponentType.CUSTOM_NAME, toKyoriComponent(customName));
}
BannerPatternLayers pattern = itemStack.get(DataComponents.BANNER_PATTERNS);
if (pattern != null) {
components.put(DataComponentType.BANNER_PATTERNS, toPatternList(pattern));
}
future.complete(components);
return;
}
future.complete(null);
});
return future;
}
@Override
public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer<List<String>> apply) {
server.execute(() -> {
@ -182,20 +128,4 @@ public class GeyserModWorldManager extends GeyserWorldManager {
private ServerPlayer getPlayer(GeyserSession session) {
return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid());
}
private static net.kyori.adventure.text.Component toKyoriComponent(Component component) {
String json = Component.Serializer.toJson(component, RegistryAccess.EMPTY);
return GSON_SERIALIZER.deserializeOr(json, net.kyori.adventure.text.Component.empty());
}
private static List<BannerPatternLayer> toPatternList(BannerPatternLayers patternLayers) {
return patternLayers.layers().stream()
.map(layer -> {
BannerPatternLayer.BannerPattern pattern = new BannerPatternLayer.BannerPattern(
MinecraftKey.key(layer.pattern().value().assetId().toString()), layer.pattern().value().translationKey()
);
return new BannerPatternLayer(Holder.ofCustom(pattern), layer.color().getId());
})
.toList();
}
}

View file

@ -1,6 +1,7 @@
plugins {
id("geyser.platform-conventions")
id("geyser.modrinth-uploading-conventions")
alias(libs.plugins.runpaper)
}
dependencies {
@ -90,3 +91,9 @@ modrinth {
"1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6")
loaders.addAll("spigot", "paper")
}
tasks {
runServer {
minecraftVersion(libs.versions.runpaperversion.get())
}
}

View file

@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.spigot;
import org.geysermc.mcprotocollib.protocol.MinecraftConstants;
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
@ -186,11 +187,10 @@ public class GeyserSpigotInjector extends GeyserInjector {
*/
private void workAroundWeirdBug(GeyserBootstrap bootstrap) {
MinecraftProtocol protocol = new MinecraftProtocol();
LocalSession session = new LocalSession(null, bootstrap.getGeyserConfig().getRemote().address(),
bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress,
InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper());
LocalSession session = new LocalSession(null, this.serverSocketAddress, InetAddress.getLoopbackAddress().getHostAddress(), protocol, Runnable::run);
session.setFlag(MinecraftConstants.CLIENT_HOST, bootstrap.getGeyserConfig().getRemote().address());
session.setFlag(MinecraftConstants.CLIENT_PORT, bootstrap.getGeyserConfig().getRemote().port());
session.connect();
session.disconnect("");
}
@Override

View file

@ -29,6 +29,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.RemoteConsoleCommandSender;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -76,7 +77,7 @@ public class SpigotCommandSource implements GeyserCommandSource {
@Override
public boolean isConsole() {
return handle instanceof ConsoleCommandSender;
return handle instanceof ConsoleCommandSender || handle instanceof RemoteConsoleCommandSender;
}
@Override

View file

@ -33,7 +33,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.adapters.WorldAdapter;
import org.geysermc.geyser.adapters.paper.PaperAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;

View file

@ -25,18 +25,14 @@
package org.geysermc.geyser.platform.spigot.world.manager;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.DecoratedPot;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.erosion.bukkit.BukkitUtils;
import org.geysermc.erosion.bukkit.PickBlockUtils;
import org.geysermc.erosion.bukkit.SchedulerUtils;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.GameRule;
@ -44,7 +40,6 @@ import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import java.util.List;
import java.util.Objects;
@ -128,20 +123,6 @@ public class GeyserSpigotWorldManager extends WorldManager {
return GameMode.byId(Bukkit.getDefaultGameMode().ordinal());
}
@Override
public @NonNull CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
return CompletableFuture.completedFuture(null);
}
CompletableFuture<Int2ObjectMap<byte[]>> future = new CompletableFuture<>();
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
// Paper 1.19.3 complains about async access otherwise.
// java.lang.IllegalStateException: Tile is null, asynchronous access?
SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
return future.thenApply(RAW_TRANSFORMER);
}
public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer<List<String>> apply) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {

View file

@ -5,9 +5,6 @@ plugins {
id("geyser.platform-conventions")
}
val terminalConsoleVersion = "1.2.0"
val jlineVersion = "3.21.0"
dependencies {
api(projects.api)
api(projects.core)

View file

@ -33,10 +33,24 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.text.ChatColor;
import org.jline.reader.Candidate;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
@Slf4j
public class GeyserStandaloneLogger extends SimpleTerminalConsole implements GeyserLogger, GeyserCommandSource {
@Override
protected LineReader buildReader(LineReaderBuilder builder) {
builder.completer((reader, line, candidates) -> {
var suggestions = GeyserImpl.getInstance().commandRegistry().suggestionsFor(this, line.line());
for (var suggestion : suggestions.list()) {
candidates.add(new Candidate(suggestion.suggestion()));
}
});
return super.buildReader(builder);
}
@Override
protected boolean isRunning() {
return !GeyserImpl.getInstance().isShuttingDown();

View file

@ -20,6 +20,9 @@
<AppenderRef ref="TerminalConsole"/>
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
<filters>
<MarkerFilter marker="packet_logging" onMatch="DENY" onMismatch="ACCEPT" />
</filters>
</Root>
</Loggers>
</Configuration>
</Configuration>

View file

@ -1,6 +1,7 @@
plugins {
id("geyser.platform-conventions")
id("geyser.modrinth-uploading-conventions")
alias(libs.plugins.runvelocity)
}
dependencies {
@ -80,3 +81,9 @@ modrinth {
uploadFile.set(tasks.getByPath("shadowJar"))
loaders.addAll("velocity")
}
tasks {
runVelocity {
version(libs.versions.runvelocityversion.get())
}
}

View file

@ -31,8 +31,10 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.api.proxy.server.ServerPing.Version;
import lombok.AllArgsConstructor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
@ -51,7 +53,9 @@ public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough {
try {
event = server.getEventManager().fire(new ProxyPingEvent(new GeyserInboundConnection(inetSocketAddress), ServerPing.builder()
.description(server.getConfiguration().getMotd()).onlinePlayers(server.getPlayerCount())
.maximumPlayers(server.getConfiguration().getShowMaxPlayers()).build())).get();
.maximumPlayers(server.getConfiguration().getShowMaxPlayers())
.version(new Version(GameProtocol.getJavaProtocolVersion(), GameProtocol.getJavaMinecraftVersion()))
.build())).get();
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}

View file

@ -24,15 +24,19 @@
*/
package org.geysermc.geyser.platform.viaproxy;
import io.netty.channel.AbstractChannel;
import net.lenni0451.lambdaevents.EventHandler;
import net.lenni0451.reflect.stream.RStream;
import net.raphimc.vialegacy.api.LegacyProtocolVersion;
import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.plugins.PluginManager;
import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
import net.raphimc.viaproxy.plugins.events.Client2ProxyChannelInitializeEvent;
import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent;
import net.raphimc.viaproxy.plugins.events.ProxyStartEvent;
import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent;
import net.raphimc.viaproxy.plugins.events.types.ITyped;
import org.apache.logging.log4j.LogManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap;
@ -56,6 +60,7 @@ import org.geysermc.geyser.util.LoopbackUtil;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
@ -109,6 +114,27 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
}
}
@EventHandler
private void onClient2ProxyChannelInitialize(Client2ProxyChannelInitializeEvent event) {
if (event.getType() != ITyped.Type.POST || event.isLegacyPassthrough()) {
return;
}
if (System.getProperty("geyser.viaproxy.disableIpPassthrough") != null) { // Temporary until Configurate branch is merged
return;
}
final GeyserSession session = GeyserImpl.getInstance().onlineConnections().stream()
.filter(c -> c.getDownstream() != null)
.filter(c -> c.getDownstream().getSession().getLocalAddress().equals(event.getChannel().remoteAddress()))
.findAny().orElse(null);
if (session != null) {
final SocketAddress realAddress = session.getSocketAddress();
if (event.getChannel() instanceof AbstractChannel) {
RStream.of(AbstractChannel.class, event.getChannel()).fields().by("remoteAddress").set(realAddress);
}
}
}
@EventHandler
private void onProxyStart(final ProxyStartEvent event) {
this.onGeyserEnable();

View file

@ -7,12 +7,13 @@ tasks.modrinth.get().dependsOn(tasks.modrinthSyncBody)
modrinth {
token.set(System.getenv("MODRINTH_TOKEN") ?: "") // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
debugMode.set(System.getenv("MODRINTH_TOKEN") == null)
projectId.set("geyser")
versionName.set(versionName(project))
versionNumber.set(projectVersion(project))
versionType.set("beta")
changelog.set(System.getenv("CHANGELOG") ?: "")
gameVersions.addAll("1.21", libs.minecraft.get().version as String)
gameVersions.add(libs.minecraft.get().version as String)
failSilently.set(true)
syncBodyFrom.set(rootProject.file("README.md").readText())

View file

@ -36,9 +36,6 @@ public final class Constants {
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://geysermc.org/download#floodgate";
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
@Deprecated
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
static final String SAVED_AUTH_CHAINS_FILE = "saved-auth-chains.json";
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";

View file

@ -29,7 +29,6 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import io.netty.channel.epoll.Epoll;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.DefaultThreadFactory;
@ -39,8 +38,6 @@ import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
import net.raphimc.minecraftauth.step.msa.StepMsaToken;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -70,6 +67,7 @@ import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.erosion.UnixSocketClientListener;
import org.geysermc.geyser.event.GeyserEventBus;
import org.geysermc.geyser.event.type.SessionDisconnectEventImpl;
import org.geysermc.geyser.extension.GeyserExtensionManager;
import org.geysermc.geyser.floodgate.FloodgateProvider;
import org.geysermc.geyser.floodgate.IntegratedFloodgateProvider;
@ -86,6 +84,7 @@ import org.geysermc.geyser.registry.provider.ProviderSupplier;
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
import org.geysermc.geyser.session.SessionDisconnectListener;
import org.geysermc.geyser.session.SessionManager;
import org.geysermc.geyser.session.cache.RegistryCache;
import org.geysermc.geyser.skin.BedrockSkinUploader;
@ -97,10 +96,8 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.AssetUtils;
import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.geyser.util.Metrics;
import org.geysermc.geyser.util.MinecraftAuthLogger;
import org.geysermc.geyser.util.VersionCheckUtils;
import org.geysermc.geyser.util.WebUtils;
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
import java.io.File;
import java.io.FileWriter;
@ -241,9 +238,14 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
}
logger.info("******************************************");
/* Initialize registries */
Registries.init();
BlockRegistries.init();
/*
First load the registries and then populate them.
Both the block registries and the common registries depend on each other,
so maintaining this order is crucial for Geyser to load.
*/
Registries.load();
BlockRegistries.populate();
Registries.populate();
RegistryCache.init();
@ -271,6 +273,8 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
// Register our general permissions when possible
eventBus.subscribe(this, GeyserRegisterPermissionsEvent.class, Permissions::register);
// Replace disconnect messages whenever necessary
eventBus.subscribe(this, SessionDisconnectEventImpl.class, SessionDisconnectListener::onSessionDisconnect);
startInstance();
@ -420,9 +424,6 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
}
}
// Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves
TcpSession.USE_EVENT_LOOP_FOR_PACKETS = false;
pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout());
Packets.initGeyser();
@ -567,53 +568,6 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
savedAuthChains = new ConcurrentHashMap<>();
// TODO Remove after a while - just a migration help
//noinspection deprecation
File refreshTokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
if (refreshTokensFile.exists()) {
logger.info("Migrating refresh tokens to auth chains...");
TypeReference<Map<String, String>> type = new TypeReference<>() { };
Map<String, String> refreshTokens = null;
try {
refreshTokens = JSON_MAPPER.readValue(refreshTokensFile, type);
} catch (IOException e) {
// ignored - we'll just delete this file :))
}
if (refreshTokens != null) {
List<String> validUsers = config.getSavedUserLogins();
final Gson gson = new Gson();
for (Map.Entry<String, String> entry : refreshTokens.entrySet()) {
String user = entry.getKey();
if (!validUsers.contains(user)) {
continue;
}
// Migrate refresh tokens to auth chains
try {
StepFullJavaSession javaSession = PendingMicrosoftAuthentication.AUTH_FLOW.apply(false, 10);
StepFullJavaSession.FullJavaSession fullJavaSession = javaSession.getFromInput(
MinecraftAuthLogger.INSTANCE,
PendingMicrosoftAuthentication.AUTH_CLIENT,
new StepMsaToken.RefreshToken(entry.getValue())
);
String authChain = gson.toJson(javaSession.toJson(fullJavaSession));
savedAuthChains.put(user, authChain);
} catch (Exception e) {
GeyserImpl.getInstance().getLogger().warning("Could not migrate " + entry.getKey() + " to an auth chain! " +
"They will need to sign in the next time they join Geyser.");
}
// Ensure the new additions are written to the file
scheduleAuthChainsWrite();
}
}
// Finally: Delete it. Goodbye!
refreshTokensFile.delete();
}
File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
if (authChainsFile.exists()) {
TypeReference<Map<String, String>> type = new TypeReference<>() { };
@ -726,7 +680,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
runIfNonNull(skinUploader, BedrockSkinUploader::close);
runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
Registries.RESOURCE_PACKS.get().clear();
if (Registries.RESOURCE_PACKS.loaded()) {
Registries.RESOURCE_PACKS.get().clear();
}
this.setEnabled(false);
}

View file

@ -27,6 +27,13 @@ package org.geysermc.geyser.command;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.protocol.bedrock.data.command.CommandData;
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumConstraint;
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
import org.cloudburstmc.protocol.bedrock.data.command.CommandOverloadData;
import org.cloudburstmc.protocol.bedrock.data.command.CommandParam;
import org.cloudburstmc.protocol.bedrock.data.command.CommandParamData;
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.event.EventRegistrar;
@ -51,14 +58,29 @@ import org.geysermc.geyser.command.defaults.StopCommand;
import org.geysermc.geyser.command.defaults.VersionCommand;
import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl;
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.incendo.cloud.Command.Builder;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.internal.CommandNode;
import org.incendo.cloud.parser.standard.EnumParser;
import org.incendo.cloud.parser.standard.IntegerParser;
import org.incendo.cloud.parser.standard.LiteralParser;
import org.incendo.cloud.parser.standard.StringArrayParser;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.Suggestions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static org.geysermc.geyser.command.GeyserCommand.DEFAULT_ROOT_COMMAND;
@ -299,4 +321,90 @@ public class CommandRegistry implements EventRegistrar {
public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) {
cloud.commandExecutor().executeCommand(source, command);
}
public Suggestions<GeyserCommandSource, ? extends Suggestion> suggestionsFor(GeyserCommandSource source, String input) {
return cloud.suggestionFactory().suggestImmediately(source, input);
}
public void export(GeyserSession session, List<CommandData> bedrockCommands, Set<String> knownAliases) {
cloud.commandTree().rootNodes().forEach(commandTree -> {
var command = commandTree.command();
// Command null happens if you register an extension command with custom Cloud parameters...
if (command == null || session.hasPermission(command.commandPermission().permissionString())) {
var rootComponent = commandTree.component();
String name = rootComponent.name();
if (!knownAliases.add(name)) {
// If the server already defined the command, let's not crash.
return;
}
LinkedHashMap<String, Set<CommandEnumConstraint>> values = new LinkedHashMap<>();
for (String s : rootComponent.aliases()) {
values.put(s, EnumSet.of(CommandEnumConstraint.ALLOW_ALIASES));
}
CommandEnumData aliases = new CommandEnumData(name + "Aliases", values, false);
List<CommandOverloadData> data = new ArrayList<>();
for (var node : commandTree.children()) {
List<List<CommandParamData>> params = createParamData(session, node);
params.forEach(param -> data.add(new CommandOverloadData(false, param.toArray(CommandParamData[]::new))));
}
CommandData bedrockCommand = new CommandData(name, rootComponent.description().textDescription(),
Set.of(CommandData.Flag.NOT_CHEAT), CommandPermission.ANY, aliases,
Collections.emptyList(), data.toArray(new CommandOverloadData[0]));
bedrockCommands.add(bedrockCommand);
}
});
}
private List<List<CommandParamData>> createParamData(GeyserSession session, CommandNode<GeyserCommandSource> node) {
var command = node.command();
if (command != null && !session.hasPermission(command.commandPermission().permissionString())) {
// Triggers with subcommands like Geyser dump, stop, etc.
return Collections.emptyList();
}
CommandParamData data = new CommandParamData();
var component = node.component();
data.setName(component.name());
data.setOptional(component.optional());
var suggestionProvider = component.suggestionProvider();
if (suggestionProvider instanceof LiteralParser<GeyserCommandSource> parser) {
Map<String, Set<CommandEnumConstraint>> values = new LinkedHashMap<>();
for (String alias : parser.aliases()) {
values.put(alias, Set.of());
}
data.setEnumData(new CommandEnumData(component.name(), values, false));
} else if (suggestionProvider instanceof IntegerParser<GeyserCommandSource>) {
data.setType(CommandParam.INT);
} else if (suggestionProvider instanceof EnumParser<?,?> parser) {
LinkedHashMap<String, Set<CommandEnumConstraint>> map = new LinkedHashMap<>();
for (Enum<?> e : parser.acceptedValues()) {
map.put(e.name().toLowerCase(Locale.ROOT), Set.of());
}
data.setEnumData(new CommandEnumData(component.name().toLowerCase(Locale.ROOT), map, false));
} else if (component.parser() instanceof StringArrayParser<?>) {
data.setType(CommandParam.TEXT);
} else {
data.setType(CommandParam.STRING);
}
var children = node.children();
if (children.isEmpty()) {
List<CommandParamData> list = new ArrayList<>(); // Must be mutable; parents will be added to list.
list.add(data);
return Collections.singletonList(list); // Safe to do; will be consumed in an addAll call.
}
List<List<CommandParamData>> collectiveData = new ArrayList<>();
// If a node has multiple children, this will need to be represented
// by creating a new list/branch for each and cloning this node down each line.
for (var child : children) {
collectiveData.addAll(createParamData(session, child));
}
collectiveData.forEach(list -> list.add(0, data));
return collectiveData;
}
}

View file

@ -26,9 +26,6 @@
package org.geysermc.geyser.entity;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.geysermc.geyser.GeyserImpl;
@ -37,11 +34,14 @@ import org.geysermc.geyser.entity.properties.GeyserEntityProperties;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.translator.entity.EntityMetadataTranslator;
import org.geysermc.geyser.util.EnvironmentUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;
/**
* Represents data for an entity. This includes properties such as height and width, as well as the list of entity
* metadata translators needed to translate the properties sent from the server. The translators are structured in such
@ -146,13 +146,8 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
return this;
}
/**
* Build the given entity. If a testing environment has been discovered the entity is not registered,
* otherwise it is. This is to prevent all the registries from loading, which will fail (and should
* not be loaded) while testing
*/
public EntityDefinition<T> build() {
return build(!EnvironmentUtils.isUnitTesting);
return build(true);
}
/**

View file

@ -38,6 +38,7 @@ import org.geysermc.geyser.entity.type.ChestBoatEntity;
import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity;
import org.geysermc.geyser.entity.type.DisplayBaseEntity;
import org.geysermc.geyser.entity.type.EnderCrystalEntity;
import org.geysermc.geyser.entity.type.EnderEyeEntity;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.EvokerFangsEntity;
import org.geysermc.geyser.entity.type.ExpOrbEntity;
@ -116,6 +117,7 @@ import org.geysermc.geyser.entity.type.living.monster.BasePiglinEntity;
import org.geysermc.geyser.entity.type.living.monster.BlazeEntity;
import org.geysermc.geyser.entity.type.living.monster.BoggedEntity;
import org.geysermc.geyser.entity.type.living.monster.BreezeEntity;
import org.geysermc.geyser.entity.type.living.monster.CreakingEntity;
import org.geysermc.geyser.entity.type.living.monster.CreeperEntity;
import org.geysermc.geyser.entity.type.living.monster.ElderGuardianEntity;
import org.geysermc.geyser.entity.type.living.monster.EnderDragonEntity;
@ -145,36 +147,44 @@ import org.geysermc.geyser.entity.type.living.monster.raid.VindicatorEntity;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.EnvironmentUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataTypes;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
public final class EntityDefinitions {
public static final EntityDefinition<BoatEntity> ACACIA_BOAT;
public static final EntityDefinition<ChestBoatEntity> ACACIA_CHEST_BOAT;
public static final EntityDefinition<AllayEntity> ALLAY;
public static final EntityDefinition<AreaEffectCloudEntity> AREA_EFFECT_CLOUD;
public static final EntityDefinition<ArmadilloEntity> ARMADILLO;
public static final EntityDefinition<ArmorStandEntity> ARMOR_STAND;
public static final EntityDefinition<ArrowEntity> ARROW;
public static final EntityDefinition<AxolotlEntity> AXOLOTL;
public static final EntityDefinition<BoatEntity> BAMBOO_RAFT;
public static final EntityDefinition<ChestBoatEntity> BAMBOO_CHEST_RAFT;
public static final EntityDefinition<BatEntity> BAT;
public static final EntityDefinition<BeeEntity> BEE;
public static final EntityDefinition<BoatEntity> BIRCH_BOAT;
public static final EntityDefinition<ChestBoatEntity> BIRCH_CHEST_BOAT;
public static final EntityDefinition<BlazeEntity> BLAZE;
public static final EntityDefinition<BoatEntity> BOAT;
public static final EntityDefinition<BoggedEntity> BOGGED;
public static final EntityDefinition<BreezeEntity> BREEZE;
public static final EntityDefinition<AbstractWindChargeEntity> BREEZE_WIND_CHARGE;
public static final EntityDefinition<CamelEntity> CAMEL;
public static final EntityDefinition<CatEntity> CAT;
public static final EntityDefinition<SpiderEntity> CAVE_SPIDER;
public static final EntityDefinition<BoatEntity> CHERRY_BOAT;
public static final EntityDefinition<ChestBoatEntity> CHERRY_CHEST_BOAT;
public static final EntityDefinition<MinecartEntity> CHEST_MINECART;
public static final EntityDefinition<ChickenEntity> CHICKEN;
public static final EntityDefinition<ChestBoatEntity> CHEST_BOAT;
public static final EntityDefinition<AbstractFishEntity> COD;
public static final EntityDefinition<CommandBlockMinecartEntity> COMMAND_BLOCK_MINECART;
public static final EntityDefinition<CowEntity> COW;
public static final EntityDefinition<CreakingEntity> CREAKING;
public static final EntityDefinition<CreeperEntity> CREEPER;
public static final EntityDefinition<BoatEntity> DARK_OAK_BOAT;
public static final EntityDefinition<ChestBoatEntity> DARK_OAK_CHEST_BOAT;
public static final EntityDefinition<DolphinEntity> DOLPHIN;
public static final EntityDefinition<ChestedHorseEntity> DONKEY;
public static final EntityDefinition<FireballEntity> DRAGON_FIREBALL;
@ -190,7 +200,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<EvokerFangsEntity> EVOKER_FANGS;
public static final EntityDefinition<ThrowableItemEntity> EXPERIENCE_BOTTLE;
public static final EntityDefinition<ExpOrbEntity> EXPERIENCE_ORB;
public static final EntityDefinition<Entity> EYE_OF_ENDER;
public static final EntityDefinition<EnderEyeEntity> EYE_OF_ENDER;
public static final EntityDefinition<FallingBlockEntity> FALLING_BLOCK;
public static final EntityDefinition<FireballEntity> FIREBALL;
public static final EntityDefinition<FireworkEntity> FIREWORK_ROCKET;
@ -213,16 +223,24 @@ public final class EntityDefinitions {
public static final EntityDefinition<IronGolemEntity> IRON_GOLEM;
public static final EntityDefinition<ItemEntity> ITEM;
public static final EntityDefinition<ItemFrameEntity> ITEM_FRAME;
public static final EntityDefinition<BoatEntity> JUNGLE_BOAT;
public static final EntityDefinition<ChestBoatEntity> JUNGLE_CHEST_BOAT;
public static final EntityDefinition<LeashKnotEntity> LEASH_KNOT;
public static final EntityDefinition<LightningEntity> LIGHTNING_BOLT;
public static final EntityDefinition<LlamaEntity> LLAMA;
public static final EntityDefinition<ThrowableEntity> LLAMA_SPIT;
public static final EntityDefinition<MagmaCubeEntity> MAGMA_CUBE;
public static final EntityDefinition<BoatEntity> MANGROVE_BOAT;
public static final EntityDefinition<ChestBoatEntity> MANGROVE_CHEST_BOAT;
public static final EntityDefinition<MinecartEntity> MINECART;
public static final EntityDefinition<MooshroomEntity> MOOSHROOM;
public static final EntityDefinition<ChestedHorseEntity> MULE;
public static final EntityDefinition<BoatEntity> OAK_BOAT;
public static final EntityDefinition<ChestBoatEntity> OAK_CHEST_BOAT;
public static final EntityDefinition<OcelotEntity> OCELOT;
public static final EntityDefinition<PaintingEntity> PAINTING;
public static final EntityDefinition<BoatEntity> PALE_OAK_BOAT;
public static final EntityDefinition<ChestBoatEntity> PALE_OAK_CHEST_BOAT;
public static final EntityDefinition<PandaEntity> PANDA;
public static final EntityDefinition<ParrotEntity> PARROT;
public static final EntityDefinition<PhantomEntity> PHANTOM;
@ -251,6 +269,8 @@ public final class EntityDefinitions {
public static final EntityDefinition<SpawnerMinecartEntity> SPAWNER_MINECART; // Not present on Bedrock
public static final EntityDefinition<AbstractArrowEntity> SPECTRAL_ARROW;
public static final EntityDefinition<SpiderEntity> SPIDER;
public static final EntityDefinition<BoatEntity> SPRUCE_BOAT;
public static final EntityDefinition<ChestBoatEntity> SPRUCE_CHEST_BOAT;
public static final EntityDefinition<SquidEntity> SQUID;
public static final EntityDefinition<AbstractSkeletonEntity> STRAY;
public static final EntityDefinition<StriderEntity> STRIDER;
@ -290,14 +310,14 @@ public final class EntityDefinitions {
static {
EntityDefinition<Entity> entityBase = EntityDefinition.builder(Entity::new)
.addTranslator(MetadataType.BYTE, Entity::setFlags)
.addTranslator(MetadataType.INT, Entity::setAir) // Air/bubbles
.addTranslator(MetadataType.OPTIONAL_CHAT, Entity::setDisplayName)
.addTranslator(MetadataType.BOOLEAN, Entity::setDisplayNameVisible)
.addTranslator(MetadataType.BOOLEAN, Entity::setSilent)
.addTranslator(MetadataType.BOOLEAN, Entity::setGravity)
.addTranslator(MetadataType.POSE, (entity, entityMetadata) -> entity.setPose(entityMetadata.getValue()))
.addTranslator(MetadataType.INT, Entity::setFreezing)
.addTranslator(MetadataTypes.BYTE, Entity::setFlags)
.addTranslator(MetadataTypes.INT, Entity::setAir) // Air/bubbles
.addTranslator(MetadataTypes.OPTIONAL_CHAT, Entity::setDisplayName)
.addTranslator(MetadataTypes.BOOLEAN, Entity::setDisplayNameVisible)
.addTranslator(MetadataTypes.BOOLEAN, Entity::setSilent)
.addTranslator(MetadataTypes.BOOLEAN, Entity::setGravity)
.addTranslator(MetadataTypes.POSE, (entity, entityMetadata) -> entity.setPose(entityMetadata.getValue()))
.addTranslator(MetadataTypes.INT, Entity::setFreezing)
.build();
// Extends entity
@ -305,26 +325,9 @@ public final class EntityDefinitions {
AREA_EFFECT_CLOUD = EntityDefinition.inherited(AreaEffectCloudEntity::new, entityBase)
.type(EntityType.AREA_EFFECT_CLOUD)
.height(0.5f).width(1.0f)
.addTranslator(MetadataType.FLOAT, AreaEffectCloudEntity::setRadius)
.addTranslator(MetadataTypes.FLOAT, AreaEffectCloudEntity::setRadius)
.addTranslator(null) // Waiting
.addTranslator(MetadataType.PARTICLE, AreaEffectCloudEntity::setParticle)
.build();
BOAT = EntityDefinition.inherited(BoatEntity::new, entityBase)
.type(EntityType.BOAT)
.height(0.6f).width(1.6f)
.offset(0.35f)
.addTranslator(MetadataType.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_TICKS, entityMetadata.getValue())) // Time since last hit
.addTranslator(MetadataType.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_DIRECTION, entityMetadata.getValue())) // Rocking direction
.addTranslator(MetadataType.FLOAT, (boatEntity, entityMetadata) ->
// 'Health' in Bedrock, damage taken in Java - it makes motion in Bedrock
boatEntity.getDirtyMetadata().put(EntityDataTypes.STRUCTURAL_INTEGRITY, 40 - ((int) ((FloatEntityMetadata) entityMetadata).getPrimitiveValue())))
.addTranslator(MetadataType.INT, BoatEntity::setVariant)
.addTranslator(MetadataType.BOOLEAN, BoatEntity::setPaddlingLeft)
.addTranslator(MetadataType.BOOLEAN, BoatEntity::setPaddlingRight)
.addTranslator(MetadataType.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.BOAT_BUBBLE_TIME, entityMetadata.getValue())) // May not actually do anything
.build();
CHEST_BOAT = EntityDefinition.inherited(ChestBoatEntity::new, BOAT)
.type(EntityType.CHEST_BOAT)
.addTranslator(MetadataTypes.PARTICLE, AreaEffectCloudEntity::setParticle)
.build();
DRAGON_FIREBALL = EntityDefinition.inherited(FireballEntity::new, entityBase)
.type(EntityType.DRAGON_FIREBALL)
@ -334,8 +337,8 @@ public final class EntityDefinitions {
.type(EntityType.END_CRYSTAL)
.heightAndWidth(2.0f)
.identifier("minecraft:ender_crystal")
.addTranslator(MetadataType.OPTIONAL_POSITION, EnderCrystalEntity::setBlockTarget)
.addTranslator(MetadataType.BOOLEAN,
.addTranslator(MetadataTypes.OPTIONAL_POSITION, EnderCrystalEntity::setBlockTarget)
.addTranslator(MetadataTypes.BOOLEAN,
(enderCrystalEntity, entityMetadata) -> enderCrystalEntity.setFlag(EntityFlag.SHOW_BOTTOM, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) // There is a base located on the ender crystal
.build();
EXPERIENCE_ORB = EntityDefinition.inherited(ExpOrbEntity::new, entityBase)
@ -347,7 +350,7 @@ public final class EntityDefinitions {
.height(0.8f).width(0.5f)
.identifier("minecraft:evocation_fang")
.build();
EYE_OF_ENDER = EntityDefinition.inherited(Entity::new, entityBase)
EYE_OF_ENDER = EntityDefinition.inherited(EnderEyeEntity::new, entityBase)
.type(EntityType.EYE_OF_ENDER)
.heightAndWidth(0.25f)
.identifier("minecraft:eye_of_ender_signal")
@ -362,21 +365,21 @@ public final class EntityDefinitions {
.type(EntityType.FIREWORK_ROCKET)
.heightAndWidth(0.25f)
.identifier("minecraft:fireworks_rocket")
.addTranslator(MetadataType.ITEM, FireworkEntity::setFireworkItem)
.addTranslator(MetadataType.OPTIONAL_VARINT, FireworkEntity::setPlayerGliding)
.addTranslator(MetadataTypes.ITEM, FireworkEntity::setFireworkItem)
.addTranslator(MetadataTypes.OPTIONAL_VARINT, FireworkEntity::setPlayerGliding)
.addTranslator(null) // Shot at angle
.build();
FISHING_BOBBER = EntityDefinition.<FishingHookEntity>inherited(null, entityBase)
.type(EntityType.FISHING_BOBBER)
.identifier("minecraft:fishing_hook")
.addTranslator(MetadataType.INT, FishingHookEntity::setHookedEntity)
.addTranslator(MetadataTypes.INT, FishingHookEntity::setHookedEntity)
.addTranslator(null) // Biting TODO check
.build();
ITEM = EntityDefinition.inherited(ItemEntity::new, entityBase)
.type(EntityType.ITEM)
.heightAndWidth(0.25f)
.offset(0.125f)
.addTranslator(MetadataType.ITEM, ItemEntity::setItem)
.addTranslator(MetadataTypes.ITEM, ItemEntity::setItem)
.build();
LEASH_KNOT = EntityDefinition.inherited(LeashKnotEntity::new, entityBase)
.type(EntityType.LEASH_KNOT)
@ -391,7 +394,7 @@ public final class EntityDefinitions {
.build();
PAINTING = EntityDefinition.<PaintingEntity>inherited(null, entityBase)
.type(EntityType.PAINTING)
.addTranslator(MetadataType.PAINTING_VARIANT, PaintingEntity::setPaintingType)
.addTranslator(MetadataTypes.PAINTING_VARIANT, PaintingEntity::setPaintingType)
.build();
SHULKER_BULLET = EntityDefinition.inherited(ThrowableEntity::new, entityBase)
.type(EntityType.SHULKER_BULLET)
@ -401,14 +404,14 @@ public final class EntityDefinitions {
.type(EntityType.TNT)
.heightAndWidth(0.98f)
.offset(0.49f)
.addTranslator(MetadataType.INT, TNTEntity::setFuseLength)
.addTranslator(MetadataTypes.INT, TNTEntity::setFuseLength)
.build();
EntityDefinition<DisplayBaseEntity> displayBase = EntityDefinition.inherited(DisplayBaseEntity::new, entityBase)
.addTranslator(null) // Interpolation delay
.addTranslator(null) // Transformation interpolation duration
.addTranslator(null) // Position/Rotation interpolation duration
.addTranslator(MetadataType.VECTOR3, DisplayBaseEntity::setTranslation) // Translation
.addTranslator(MetadataTypes.VECTOR3, DisplayBaseEntity::setTranslation) // Translation
.addTranslator(null) // Scale
.addTranslator(null) // Left rotation
.addTranslator(null) // Right rotation
@ -425,7 +428,7 @@ public final class EntityDefinitions {
.type(EntityType.TEXT_DISPLAY)
.identifier("minecraft:armor_stand")
.offset(-0.5f)
.addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
.addTranslator(MetadataTypes.CHAT, TextDisplayEntity::setText)
.addTranslator(null) // Line width
.addTranslator(null) // Background color
.addTranslator(null) // Text opacity
@ -436,9 +439,9 @@ public final class EntityDefinitions {
.type(EntityType.INTERACTION)
.heightAndWidth(1.0f) // default size until server specifies otherwise
.identifier("minecraft:armor_stand")
.addTranslator(MetadataType.FLOAT, InteractionEntity::setWidth)
.addTranslator(MetadataType.FLOAT, InteractionEntity::setHeight)
.addTranslator(MetadataType.BOOLEAN, InteractionEntity::setResponse)
.addTranslator(MetadataTypes.FLOAT, InteractionEntity::setWidth)
.addTranslator(MetadataTypes.FLOAT, InteractionEntity::setHeight)
.addTranslator(MetadataTypes.BOOLEAN, InteractionEntity::setResponse)
.build();
EntityDefinition<FireballEntity> fireballBase = EntityDefinition.inherited(FireballEntity::new, entityBase)
@ -454,7 +457,7 @@ public final class EntityDefinitions {
.build();
EntityDefinition<ThrowableItemEntity> throwableItemBase = EntityDefinition.inherited(ThrowableItemEntity::new, entityBase)
.addTranslator(MetadataType.ITEM, ThrowableItemEntity::setItem)
.addTranslator(MetadataTypes.ITEM, ThrowableItemEntity::setItem)
.build();
EGG = EntityDefinition.inherited(ThrowableItemEntity::new, throwableItemBase)
.type(EntityType.EGG)
@ -492,13 +495,14 @@ public final class EntityDefinitions {
.build();
EntityDefinition<AbstractArrowEntity> abstractArrowBase = EntityDefinition.inherited(AbstractArrowEntity::new, entityBase)
.addTranslator(MetadataType.BYTE, AbstractArrowEntity::setArrowFlags)
.addTranslator(MetadataTypes.BYTE, AbstractArrowEntity::setArrowFlags)
.addTranslator(null) // "Piercing level"
.addTranslator(null) // If the arrow is in the ground
.build();
ARROW = EntityDefinition.inherited(ArrowEntity::new, abstractArrowBase)
.type(EntityType.ARROW)
.heightAndWidth(0.25f)
.addTranslator(MetadataType.INT, ArrowEntity::setPotionEffectColor)
.addTranslator(MetadataTypes.INT, ArrowEntity::setPotionEffectColor)
.build();
SPECTRAL_ARROW = EntityDefinition.inherited(abstractArrowBase.factory(), abstractArrowBase)
.type(EntityType.SPECTRAL_ARROW)
@ -509,14 +513,14 @@ public final class EntityDefinitions {
.type(EntityType.TRIDENT)
.identifier("minecraft:thrown_trident")
.addTranslator(null) // Loyalty
.addTranslator(MetadataType.BOOLEAN, (tridentEntity, entityMetadata) -> tridentEntity.setFlag(EntityFlag.ENCHANTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataTypes.BOOLEAN, (tridentEntity, entityMetadata) -> tridentEntity.setFlag(EntityFlag.ENCHANTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.build();
// Item frames are handled differently as they are blocks, not items, in Bedrock
ITEM_FRAME = EntityDefinition.<ItemFrameEntity>inherited(null, entityBase)
.type(EntityType.ITEM_FRAME)
.addTranslator(MetadataType.ITEM, ItemFrameEntity::setItemInFrame)
.addTranslator(MetadataType.INT, ItemFrameEntity::setItemRotation)
.addTranslator(MetadataTypes.ITEM, ItemFrameEntity::setItemInFrame)
.addTranslator(MetadataTypes.INT, ItemFrameEntity::setItemRotation)
.build();
GLOW_ITEM_FRAME = EntityDefinition.inherited(ITEM_FRAME.factory(), ITEM_FRAME)
.type(EntityType.GLOW_ITEM_FRAME)
@ -526,27 +530,27 @@ public final class EntityDefinitions {
.type(EntityType.MINECART)
.height(0.7f).width(0.98f)
.offset(0.35f)
.addTranslator(MetadataType.INT, (minecartEntity, entityMetadata) -> minecartEntity.getDirtyMetadata().put(EntityDataTypes.STRUCTURAL_INTEGRITY, entityMetadata.getValue()))
.addTranslator(MetadataType.INT, (minecartEntity, entityMetadata) -> minecartEntity.getDirtyMetadata().put(EntityDataTypes.HURT_DIRECTION, entityMetadata.getValue())) // Direction in which the minecart is shaking
.addTranslator(MetadataType.FLOAT, (minecartEntity, entityMetadata) ->
.addTranslator(MetadataTypes.INT, (minecartEntity, entityMetadata) -> minecartEntity.getDirtyMetadata().put(EntityDataTypes.STRUCTURAL_INTEGRITY, entityMetadata.getValue()))
.addTranslator(MetadataTypes.INT, (minecartEntity, entityMetadata) -> minecartEntity.getDirtyMetadata().put(EntityDataTypes.HURT_DIRECTION, entityMetadata.getValue())) // Direction in which the minecart is shaking
.addTranslator(MetadataTypes.FLOAT, (minecartEntity, entityMetadata) ->
// Power in Java, hurt ticks in Bedrock
minecartEntity.getDirtyMetadata().put(EntityDataTypes.HURT_TICKS, Math.min((int) ((FloatEntityMetadata) entityMetadata).getPrimitiveValue(), 15)))
.addTranslator(MetadataType.INT, MinecartEntity::setCustomBlock)
.addTranslator(MetadataType.INT, MinecartEntity::setCustomBlockOffset)
.addTranslator(MetadataType.BOOLEAN, MinecartEntity::setShowCustomBlock)
.addTranslator(MetadataTypes.INT, MinecartEntity::setCustomBlock)
.addTranslator(MetadataTypes.INT, MinecartEntity::setCustomBlockOffset)
.addTranslator(MetadataTypes.BOOLEAN, MinecartEntity::setShowCustomBlock)
.build();
CHEST_MINECART = EntityDefinition.inherited(MINECART.factory(), MINECART)
.type(EntityType.CHEST_MINECART)
.build();
COMMAND_BLOCK_MINECART = EntityDefinition.inherited(CommandBlockMinecartEntity::new, MINECART)
.type(EntityType.COMMAND_BLOCK_MINECART)
.addTranslator(MetadataType.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.COMMAND_BLOCK_NAME, entityMetadata.getValue()))
.addTranslator(MetadataType.CHAT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue())))
.addTranslator(MetadataTypes.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.COMMAND_BLOCK_NAME, entityMetadata.getValue()))
.addTranslator(MetadataTypes.CHAT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue())))
.build();
FURNACE_MINECART = EntityDefinition.inherited(FurnaceMinecartEntity::new, MINECART)
.type(EntityType.FURNACE_MINECART)
.identifier("minecraft:minecart")
.addTranslator(MetadataType.BOOLEAN, FurnaceMinecartEntity::setHasFuel)
.addTranslator(MetadataTypes.BOOLEAN, FurnaceMinecartEntity::setHasFuel)
.build();
HOPPER_MINECART = EntityDefinition.inherited(MINECART.factory(), MINECART)
.type(EntityType.HOPPER_MINECART)
@ -562,48 +566,89 @@ public final class EntityDefinitions {
WITHER_SKULL = EntityDefinition.inherited(WitherSkullEntity::new, entityBase)
.type(EntityType.WITHER_SKULL)
.heightAndWidth(0.3125f)
.addTranslator(MetadataType.BOOLEAN, WitherSkullEntity::setDangerous)
.addTranslator(MetadataTypes.BOOLEAN, WitherSkullEntity::setDangerous)
.build();
WITHER_SKULL_DANGEROUS = EntityDefinition.inherited(WITHER_SKULL.factory(), WITHER_SKULL)
.build(false);
}
// Boats
{
EntityDefinition<BoatEntity> boatBase = EntityDefinition.<BoatEntity>inherited(null, entityBase)
.height(0.6f).width(1.6f)
.offset(0.35f)
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_TICKS, entityMetadata.getValue())) // Time since last hit
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_DIRECTION, entityMetadata.getValue())) // Rocking direction
.addTranslator(MetadataTypes.FLOAT, (boatEntity, entityMetadata) ->
// 'Health' in Bedrock, damage taken in Java - it makes motion in Bedrock
boatEntity.getDirtyMetadata().put(EntityDataTypes.STRUCTURAL_INTEGRITY, 40 - ((int) ((FloatEntityMetadata) entityMetadata).getPrimitiveValue())))
.addTranslator(MetadataTypes.BOOLEAN, BoatEntity::setPaddlingLeft)
.addTranslator(MetadataTypes.BOOLEAN, BoatEntity::setPaddlingRight)
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.BOAT_BUBBLE_TIME, entityMetadata.getValue())) // May not actually do anything
.build();
ACACIA_BOAT = buildBoat(boatBase, EntityType.ACACIA_BOAT, BoatEntity.BoatVariant.ACACIA);
BAMBOO_RAFT = buildBoat(boatBase, EntityType.BAMBOO_RAFT, BoatEntity.BoatVariant.BAMBOO);
BIRCH_BOAT = buildBoat(boatBase, EntityType.BIRCH_BOAT, BoatEntity.BoatVariant.BIRCH);
CHERRY_BOAT = buildBoat(boatBase, EntityType.CHERRY_BOAT, BoatEntity.BoatVariant.CHERRY);
DARK_OAK_BOAT = buildBoat(boatBase, EntityType.DARK_OAK_BOAT, BoatEntity.BoatVariant.DARK_OAK);
JUNGLE_BOAT = buildBoat(boatBase, EntityType.JUNGLE_BOAT, BoatEntity.BoatVariant.JUNGLE);
MANGROVE_BOAT = buildBoat(boatBase, EntityType.MANGROVE_BOAT, BoatEntity.BoatVariant.MANGROVE);
OAK_BOAT = buildBoat(boatBase, EntityType.OAK_BOAT, BoatEntity.BoatVariant.OAK);
SPRUCE_BOAT = buildBoat(boatBase, EntityType.SPRUCE_BOAT, BoatEntity.BoatVariant.SPRUCE);
PALE_OAK_BOAT = buildBoat(boatBase, EntityType.PALE_OAK_BOAT, BoatEntity.BoatVariant.PALE_OAK);
EntityDefinition<ChestBoatEntity> chestBoatBase = EntityDefinition.<ChestBoatEntity>inherited(null, boatBase)
.build();
ACACIA_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.ACACIA_CHEST_BOAT, BoatEntity.BoatVariant.ACACIA);
BAMBOO_CHEST_RAFT = buildChestBoat(chestBoatBase, EntityType.BAMBOO_CHEST_RAFT, BoatEntity.BoatVariant.BAMBOO);
BIRCH_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.BIRCH_CHEST_BOAT, BoatEntity.BoatVariant.BIRCH);
CHERRY_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.CHERRY_CHEST_BOAT, BoatEntity.BoatVariant.CHERRY);
DARK_OAK_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.DARK_OAK_CHEST_BOAT, BoatEntity.BoatVariant.DARK_OAK);
JUNGLE_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.JUNGLE_CHEST_BOAT, BoatEntity.BoatVariant.JUNGLE);
MANGROVE_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.MANGROVE_CHEST_BOAT, BoatEntity.BoatVariant.MANGROVE);
OAK_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.OAK_CHEST_BOAT, BoatEntity.BoatVariant.OAK);
SPRUCE_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.SPRUCE_CHEST_BOAT, BoatEntity.BoatVariant.SPRUCE);
PALE_OAK_CHEST_BOAT = buildChestBoat(chestBoatBase, EntityType.PALE_OAK_CHEST_BOAT, BoatEntity.BoatVariant.PALE_OAK);
}
EntityDefinition<LivingEntity> livingEntityBase = EntityDefinition.inherited(LivingEntity::new, entityBase)
.addTranslator(MetadataType.BYTE, LivingEntity::setLivingEntityFlags)
.addTranslator(MetadataType.FLOAT, LivingEntity::setHealth)
.addTranslator(MetadataType.PARTICLES, LivingEntity::setParticles)
.addTranslator(MetadataType.BOOLEAN,
.addTranslator(MetadataTypes.BYTE, LivingEntity::setLivingEntityFlags)
.addTranslator(MetadataTypes.FLOAT, LivingEntity::setHealth)
.addTranslator(MetadataTypes.PARTICLES, LivingEntity::setParticles)
.addTranslator(MetadataTypes.BOOLEAN,
(livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityDataTypes.EFFECT_AMBIENCE, (byte) (((BooleanEntityMetadata) entityMetadata).getPrimitiveValue() ? 1 : 0)))
.addTranslator(null) // Arrow count
.addTranslator(null) // Stinger count
.addTranslator(MetadataType.OPTIONAL_POSITION, LivingEntity::setBedPosition)
.addTranslator(MetadataTypes.OPTIONAL_POSITION, LivingEntity::setBedPosition)
.build();
ARMOR_STAND = EntityDefinition.inherited(ArmorStandEntity::new, livingEntityBase)
.type(EntityType.ARMOR_STAND)
.height(1.975f).width(0.5f)
.addTranslator(MetadataType.BYTE, ArmorStandEntity::setArmorStandFlags)
.addTranslator(MetadataType.ROTATION, ArmorStandEntity::setHeadRotation)
.addTranslator(MetadataType.ROTATION, ArmorStandEntity::setBodyRotation)
.addTranslator(MetadataType.ROTATION, ArmorStandEntity::setLeftArmRotation)
.addTranslator(MetadataType.ROTATION, ArmorStandEntity::setRightArmRotation)
.addTranslator(MetadataType.ROTATION, ArmorStandEntity::setLeftLegRotation)
.addTranslator(MetadataType.ROTATION, ArmorStandEntity::setRightLegRotation)
.addTranslator(MetadataTypes.BYTE, ArmorStandEntity::setArmorStandFlags)
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setHeadRotation)
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setBodyRotation)
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setLeftArmRotation)
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setRightArmRotation)
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setLeftLegRotation)
.addTranslator(MetadataTypes.ROTATION, ArmorStandEntity::setRightLegRotation)
.build();
PLAYER = EntityDefinition.<PlayerEntity>inherited(null, livingEntityBase)
.type(EntityType.PLAYER)
.height(1.8f).width(0.6f)
.offset(1.62f)
.addTranslator(MetadataType.FLOAT, PlayerEntity::setAbsorptionHearts)
.addTranslator(MetadataTypes.FLOAT, PlayerEntity::setAbsorptionHearts)
.addTranslator(null) // Player score
.addTranslator(MetadataType.BYTE, PlayerEntity::setSkinVisibility)
.addTranslator(MetadataTypes.BYTE, PlayerEntity::setSkinVisibility)
.addTranslator(null) // Player main hand
.addTranslator(MetadataType.NBT_TAG, PlayerEntity::setLeftParrot)
.addTranslator(MetadataType.NBT_TAG, PlayerEntity::setRightParrot)
.addTranslator(MetadataTypes.NBT_TAG, PlayerEntity::setLeftParrot)
.addTranslator(MetadataTypes.NBT_TAG, PlayerEntity::setRightParrot)
.build();
EntityDefinition<MobEntity> mobEntityBase = EntityDefinition.inherited(MobEntity::new, livingEntityBase)
.addTranslator(MetadataType.BYTE, MobEntity::setMobFlags)
.addTranslator(MetadataTypes.BYTE, MobEntity::setMobFlags)
.build();
// Extends mob
@ -611,50 +656,59 @@ public final class EntityDefinitions {
ALLAY = EntityDefinition.inherited(AllayEntity::new, mobEntityBase)
.type(EntityType.ALLAY)
.height(0.6f).width(0.35f)
.addTranslator(MetadataType.BOOLEAN, AllayEntity::setDancing)
.addTranslator(MetadataType.BOOLEAN, AllayEntity::setCanDuplicate)
.addTranslator(MetadataTypes.BOOLEAN, AllayEntity::setDancing)
.addTranslator(MetadataTypes.BOOLEAN, AllayEntity::setCanDuplicate)
.build();
BAT = EntityDefinition.inherited(BatEntity::new, mobEntityBase)
.type(EntityType.BAT)
.height(0.9f).width(0.5f)
.addTranslator(MetadataType.BYTE, BatEntity::setBatFlags)
.addTranslator(MetadataTypes.BYTE, BatEntity::setBatFlags)
.build();
BOGGED = EntityDefinition.inherited(BoggedEntity::new, mobEntityBase)
.type(EntityType.BOGGED)
.height(1.99f).width(0.6f)
.addTranslator(MetadataType.BOOLEAN, BoggedEntity::setSheared)
.addTranslator(MetadataTypes.BOOLEAN, BoggedEntity::setSheared)
.build();
BLAZE = EntityDefinition.inherited(BlazeEntity::new, mobEntityBase)
.type(EntityType.BLAZE)
.height(1.8f).width(0.6f)
.addTranslator(MetadataType.BYTE, BlazeEntity::setBlazeFlags)
.addTranslator(MetadataTypes.BYTE, BlazeEntity::setBlazeFlags)
.build();
BREEZE = EntityDefinition.inherited(BreezeEntity::new, mobEntityBase)
.type(EntityType.BREEZE)
.height(1.77f).width(0.6f)
.build();
CREAKING = EntityDefinition.inherited(CreakingEntity::new, mobEntityBase)
.type(EntityType.CREAKING)
.height(2.7f).width(0.9f)
.addTranslator(MetadataTypes.BOOLEAN, CreakingEntity::setCanMove)
.addTranslator(MetadataTypes.BOOLEAN, CreakingEntity::setActive)
.addTranslator(MetadataTypes.BOOLEAN, CreakingEntity::setIsTearingDown)
.addTranslator(MetadataTypes.OPTIONAL_POSITION, CreakingEntity::setHomePos)
.properties(new GeyserEntityProperties.Builder()
.addEnum(CreakingEntity.CREAKING_STATE,
"neutral",
"hostile_observed",
"hostile_unobserved",
"twitching",
"crumbling")
.addInt(CreakingEntity.CREAKING_SWAYING_TICKS, 0, 6)
.build())
.build();
CREEPER = EntityDefinition.inherited(CreeperEntity::new, mobEntityBase)
.type(EntityType.CREEPER)
.height(1.7f).width(0.6f)
.offset(1.62f)
.addTranslator(MetadataType.INT, CreeperEntity::setSwelling)
.addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.POWERED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataType.BOOLEAN, CreeperEntity::setIgnited)
.build();
DOLPHIN = EntityDefinition.inherited(DolphinEntity::new, mobEntityBase)
.type(EntityType.DOLPHIN)
.height(0.6f).width(0.9f)
//TODO check
.addTranslator(null) // treasure position
.addTranslator(null) // "got fish"
.addTranslator(null) // "moistness level"
.addTranslator(MetadataTypes.INT, CreeperEntity::setSwelling)
.addTranslator(MetadataTypes.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.POWERED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataTypes.BOOLEAN, CreeperEntity::setIgnited)
.build();
ENDERMAN = EntityDefinition.inherited(EndermanEntity::new, mobEntityBase)
.type(EntityType.ENDERMAN)
.height(2.9f).width(0.6f)
.addTranslator(MetadataType.OPTIONAL_BLOCK_STATE, EndermanEntity::setCarriedBlock)
.addTranslator(MetadataType.BOOLEAN, EndermanEntity::setScreaming)
.addTranslator(MetadataType.BOOLEAN, EndermanEntity::setAngry)
.addTranslator(MetadataTypes.OPTIONAL_BLOCK_STATE, EndermanEntity::setCarriedBlock)
.addTranslator(MetadataTypes.BOOLEAN, EndermanEntity::setScreaming)
.addTranslator(MetadataTypes.BOOLEAN, EndermanEntity::setAngry)
.build();
ENDERMITE = EntityDefinition.inherited(MonsterEntity::new, mobEntityBase)
.type(EntityType.ENDERMITE)
@ -662,12 +716,12 @@ public final class EntityDefinitions {
.build();
ENDER_DRAGON = EntityDefinition.inherited(EnderDragonEntity::new, mobEntityBase)
.type(EntityType.ENDER_DRAGON)
.addTranslator(MetadataType.INT, EnderDragonEntity::setPhase)
.addTranslator(MetadataTypes.INT, EnderDragonEntity::setPhase)
.build();
GHAST = EntityDefinition.inherited(GhastEntity::new, mobEntityBase)
.type(EntityType.GHAST)
.heightAndWidth(4.0f)
.addTranslator(MetadataType.BOOLEAN, GhastEntity::setGhastAttacking)
.addTranslator(MetadataTypes.BOOLEAN, GhastEntity::setGhastAttacking)
.build();
GIANT = EntityDefinition.inherited(GiantEntity::new, mobEntityBase)
.type(EntityType.GIANT)
@ -684,7 +738,7 @@ public final class EntityDefinitions {
.type(EntityType.PHANTOM)
.height(0.5f).width(0.9f)
.offset(0.6f)
.addTranslator(MetadataType.INT, PhantomEntity::setPhantomScale)
.addTranslator(MetadataTypes.INT, PhantomEntity::setPhantomScale)
.build();
SILVERFISH = EntityDefinition.inherited(MonsterEntity::new, mobEntityBase)
.type(EntityType.SILVERFISH)
@ -693,35 +747,31 @@ public final class EntityDefinitions {
SHULKER = EntityDefinition.inherited(ShulkerEntity::new, mobEntityBase)
.type(EntityType.SHULKER)
.heightAndWidth(1f)
.addTranslator(MetadataType.DIRECTION, ShulkerEntity::setAttachedFace)
.addTranslator(MetadataType.BYTE, ShulkerEntity::setShulkerHeight)
.addTranslator(MetadataType.BYTE, ShulkerEntity::setShulkerColor)
.addTranslator(MetadataTypes.DIRECTION, ShulkerEntity::setAttachedFace)
.addTranslator(MetadataTypes.BYTE, ShulkerEntity::setShulkerHeight)
.addTranslator(MetadataTypes.BYTE, ShulkerEntity::setShulkerColor)
.build();
SKELETON = EntityDefinition.inherited(SkeletonEntity::new, mobEntityBase)
.type(EntityType.SKELETON)
.height(1.8f).width(0.6f)
.offset(1.62f)
.addTranslator(MetadataType.BOOLEAN, SkeletonEntity::setConvertingToStray)
.addTranslator(MetadataTypes.BOOLEAN, SkeletonEntity::setConvertingToStray)
.build();
SNOW_GOLEM = EntityDefinition.inherited(SnowGolemEntity::new, mobEntityBase)
.type(EntityType.SNOW_GOLEM)
.height(1.9f).width(0.7f)
.addTranslator(MetadataType.BYTE, SnowGolemEntity::setSnowGolemFlags)
.addTranslator(MetadataTypes.BYTE, SnowGolemEntity::setSnowGolemFlags)
.build();
SPIDER = EntityDefinition.inherited(SpiderEntity::new, mobEntityBase)
.type(EntityType.SPIDER)
.height(0.9f).width(1.4f)
.offset(1f)
.addTranslator(MetadataType.BYTE, SpiderEntity::setSpiderFlags)
.addTranslator(MetadataTypes.BYTE, SpiderEntity::setSpiderFlags)
.build();
CAVE_SPIDER = EntityDefinition.inherited(SpiderEntity::new, SPIDER)
.type(EntityType.CAVE_SPIDER)
.height(0.5f).width(0.7f)
.build();
SQUID = EntityDefinition.inherited(SquidEntity::new, mobEntityBase)
.type(EntityType.SQUID)
.heightAndWidth(0.8f)
.build();
STRAY = EntityDefinition.inherited(AbstractSkeletonEntity::new, mobEntityBase)
.type(EntityType.STRAY)
.height(1.8f).width(0.6f)
@ -730,20 +780,20 @@ public final class EntityDefinitions {
VEX = EntityDefinition.inherited(VexEntity::new, mobEntityBase)
.type(EntityType.VEX)
.height(0.8f).width(0.4f)
.addTranslator(MetadataType.BYTE, VexEntity::setVexFlags)
.addTranslator(MetadataTypes.BYTE, VexEntity::setVexFlags)
.build();
WARDEN = EntityDefinition.inherited(WardenEntity::new, mobEntityBase)
.type(EntityType.WARDEN)
.height(2.9f).width(0.9f)
.addTranslator(MetadataType.INT, WardenEntity::setAngerLevel)
.addTranslator(MetadataTypes.INT, WardenEntity::setAngerLevel)
.build();
WITHER = EntityDefinition.inherited(WitherEntity::new, mobEntityBase)
.type(EntityType.WITHER)
.height(3.5f).width(0.9f)
.addTranslator(MetadataType.INT, WitherEntity::setTarget1)
.addTranslator(MetadataType.INT, WitherEntity::setTarget2)
.addTranslator(MetadataType.INT, WitherEntity::setTarget3)
.addTranslator(MetadataType.INT, WitherEntity::setInvulnerableTicks)
.addTranslator(MetadataTypes.INT, WitherEntity::setTarget1)
.addTranslator(MetadataTypes.INT, WitherEntity::setTarget2)
.addTranslator(MetadataTypes.INT, WitherEntity::setTarget3)
.addTranslator(MetadataTypes.INT, WitherEntity::setInvulnerableTicks)
.build();
WITHER_SKELETON = EntityDefinition.inherited(AbstractSkeletonEntity::new, mobEntityBase)
.type(EntityType.WITHER_SKELETON)
@ -752,23 +802,23 @@ public final class EntityDefinitions {
ZOGLIN = EntityDefinition.inherited(ZoglinEntity::new, mobEntityBase)
.type(EntityType.ZOGLIN)
.height(1.4f).width(1.3965f)
.addTranslator(MetadataType.BOOLEAN, ZoglinEntity::setBaby)
.addTranslator(MetadataTypes.BOOLEAN, ZoglinEntity::setBaby)
.build();
ZOMBIE = EntityDefinition.inherited(ZombieEntity::new, mobEntityBase)
.type(EntityType.ZOMBIE)
.height(1.8f).width(0.6f)
.offset(1.62f)
.addTranslator(MetadataType.BOOLEAN, ZombieEntity::setZombieBaby)
.addTranslator(MetadataTypes.BOOLEAN, ZombieEntity::setZombieBaby)
.addTranslator(null) // "set special type", doesn't do anything
.addTranslator(MetadataType.BOOLEAN, ZombieEntity::setConvertingToDrowned)
.addTranslator(MetadataTypes.BOOLEAN, ZombieEntity::setConvertingToDrowned)
.build();
ZOMBIE_VILLAGER = EntityDefinition.inherited(ZombieVillagerEntity::new, ZOMBIE)
.type(EntityType.ZOMBIE_VILLAGER)
.height(1.8f).width(0.6f)
.offset(1.62f)
.identifier("minecraft:zombie_villager_v2")
.addTranslator(MetadataType.BOOLEAN, ZombieVillagerEntity::setTransforming)
.addTranslator(MetadataType.VILLAGER_DATA, ZombieVillagerEntity::setZombieVillagerData)
.addTranslator(MetadataTypes.BOOLEAN, ZombieVillagerEntity::setTransforming)
.addTranslator(MetadataTypes.VILLAGER_DATA, ZombieVillagerEntity::setZombieVillagerData)
.build();
ZOMBIFIED_PIGLIN = EntityDefinition.inherited(ZombifiedPiglinEntity::new, ZOMBIE) //TODO test how zombie entity metadata is handled?
.type(EntityType.ZOMBIFIED_PIGLIN)
@ -789,7 +839,7 @@ public final class EntityDefinitions {
.type(EntityType.GUARDIAN)
.heightAndWidth(0.85f)
.addTranslator(null) // Moving //TODO
.addTranslator(MetadataType.INT, GuardianEntity::setGuardianTarget)
.addTranslator(MetadataTypes.INT, GuardianEntity::setGuardianTarget)
.build();
ELDER_GUARDIAN = EntityDefinition.inherited(ElderGuardianEntity::new, GUARDIAN)
.type(EntityType.ELDER_GUARDIAN)
@ -799,7 +849,7 @@ public final class EntityDefinitions {
SLIME = EntityDefinition.inherited(SlimeEntity::new, mobEntityBase)
.type(EntityType.SLIME)
.heightAndWidth(0.51f)
.addTranslator(MetadataType.INT, SlimeEntity::setSlimeScale)
.addTranslator(MetadataTypes.INT, SlimeEntity::setSlimeScale)
.build();
MAGMA_CUBE = EntityDefinition.inherited(MagmaCubeEntity::new, SLIME)
.type(EntityType.MAGMA_CUBE)
@ -815,11 +865,12 @@ public final class EntityDefinitions {
PUFFERFISH = EntityDefinition.inherited(PufferFishEntity::new, abstractFishEntityBase)
.type(EntityType.PUFFERFISH)
.heightAndWidth(0.7f)
.addTranslator(MetadataType.INT, PufferFishEntity::setPufferfishSize)
.addTranslator(MetadataTypes.INT, PufferFishEntity::setPufferfishSize)
.build();
SALMON = EntityDefinition.inherited(abstractFishEntityBase.factory(), abstractFishEntityBase)
.type(EntityType.SALMON)
.height(0.5f).width(0.7f)
.addTranslator(null) // Scale/variant - TODO
.build();
TADPOLE = EntityDefinition.inherited(TadpoleEntity::new, abstractFishEntityBase)
.type(EntityType.TADPOLE)
@ -829,34 +880,29 @@ public final class EntityDefinitions {
.type(EntityType.TROPICAL_FISH)
.heightAndWidth(0.6f)
.identifier("minecraft:tropicalfish")
.addTranslator(MetadataType.INT, TropicalFishEntity::setFishVariant)
.addTranslator(MetadataTypes.INT, TropicalFishEntity::setFishVariant)
.build();
EntityDefinition<BasePiglinEntity> abstractPiglinEntityBase = EntityDefinition.inherited(BasePiglinEntity::new, mobEntityBase)
.addTranslator(MetadataType.BOOLEAN, BasePiglinEntity::setImmuneToZombification)
.addTranslator(MetadataTypes.BOOLEAN, BasePiglinEntity::setImmuneToZombification)
.build();
PIGLIN = EntityDefinition.inherited(PiglinEntity::new, abstractPiglinEntityBase)
.type(EntityType.PIGLIN)
.height(1.95f).width(0.6f)
.addTranslator(MetadataType.BOOLEAN, PiglinEntity::setBaby)
.addTranslator(MetadataType.BOOLEAN, PiglinEntity::setChargingCrossbow)
.addTranslator(MetadataType.BOOLEAN, PiglinEntity::setDancing)
.addTranslator(MetadataTypes.BOOLEAN, PiglinEntity::setBaby)
.addTranslator(MetadataTypes.BOOLEAN, PiglinEntity::setChargingCrossbow)
.addTranslator(MetadataTypes.BOOLEAN, PiglinEntity::setDancing)
.build();
PIGLIN_BRUTE = EntityDefinition.inherited(abstractPiglinEntityBase.factory(), abstractPiglinEntityBase)
.type(EntityType.PIGLIN_BRUTE)
.height(1.95f).width(0.6f)
.build();
GLOW_SQUID = EntityDefinition.inherited(GlowSquidEntity::new, SQUID)
.type(EntityType.GLOW_SQUID)
.addTranslator(null) // Set dark ticks remaining, possible TODO
.build();
EntityDefinition<RaidParticipantEntity> raidParticipantEntityBase = EntityDefinition.inherited(RaidParticipantEntity::new, mobEntityBase)
.addTranslator(null) // Celebrating //TODO
.build();
EntityDefinition<SpellcasterIllagerEntity> spellcasterEntityBase = EntityDefinition.inherited(SpellcasterIllagerEntity::new, raidParticipantEntityBase)
.addTranslator(MetadataType.BYTE, SpellcasterIllagerEntity::setSpellType)
.addTranslator(MetadataTypes.BYTE, SpellcasterIllagerEntity::setSpellType)
.build();
EVOKER = EntityDefinition.inherited(spellcasterEntityBase.factory(), spellcasterEntityBase)
.type(EntityType.EVOKER)
@ -872,7 +918,7 @@ public final class EntityDefinitions {
.type(EntityType.PILLAGER)
.height(1.8f).width(0.6f)
.offset(1.62f)
.addTranslator(MetadataType.BOOLEAN, PillagerEntity::setChargingCrossbow)
.addTranslator(MetadataTypes.BOOLEAN, PillagerEntity::setChargingCrossbow)
.build();
RAVAGER = EntityDefinition.inherited(RavagerEntity::new, raidParticipantEntityBase)
.type(EntityType.RAVAGER)
@ -892,7 +938,7 @@ public final class EntityDefinitions {
}
EntityDefinition<AgeableEntity> ageableEntityBase = EntityDefinition.inherited(AgeableEntity::new, mobEntityBase)
.addTranslator(MetadataType.BOOLEAN, AgeableEntity::setBaby)
.addTranslator(MetadataTypes.BOOLEAN, AgeableEntity::setBaby)
.build();
// Extends ageable
@ -909,13 +955,13 @@ public final class EntityDefinitions {
"rolled_up_relaxing",
"rolled_up_unrolling")
.build())
.addTranslator(MetadataType.ARMADILLO_STATE, ArmadilloEntity::setArmadilloState)
.addTranslator(MetadataTypes.ARMADILLO_STATE, ArmadilloEntity::setArmadilloState)
.build();
AXOLOTL = EntityDefinition.inherited(AxolotlEntity::new, ageableEntityBase)
.type(EntityType.AXOLOTL)
.height(0.42f).width(0.7f)
.addTranslator(MetadataType.INT, AxolotlEntity::setVariant)
.addTranslator(MetadataType.BOOLEAN, AxolotlEntity::setPlayingDead)
.addTranslator(MetadataTypes.INT, AxolotlEntity::setVariant)
.addTranslator(MetadataTypes.BOOLEAN, AxolotlEntity::setPlayingDead)
.addTranslator(null) // From bucket
.build();
BEE = EntityDefinition.inherited(BeeEntity::new, ageableEntityBase)
@ -924,8 +970,8 @@ public final class EntityDefinitions {
.properties(new GeyserEntityProperties.Builder()
.addBoolean("minecraft:has_nectar")
.build())
.addTranslator(MetadataType.BYTE, BeeEntity::setBeeFlags)
.addTranslator(MetadataType.INT, BeeEntity::setAngerTime)
.addTranslator(MetadataTypes.BYTE, BeeEntity::setBeeFlags)
.addTranslator(MetadataTypes.INT, BeeEntity::setAngerTime)
.build();
CHICKEN = EntityDefinition.inherited(ChickenEntity::new, ageableEntityBase)
.type(EntityType.CHICKEN)
@ -938,89 +984,89 @@ public final class EntityDefinitions {
FOX = EntityDefinition.inherited(FoxEntity::new, ageableEntityBase)
.type(EntityType.FOX)
.height(0.7f).width(0.6f)
.addTranslator(MetadataType.INT, FoxEntity::setFoxVariant)
.addTranslator(MetadataType.BYTE, FoxEntity::setFoxFlags)
.addTranslator(MetadataTypes.INT, FoxEntity::setFoxVariant)
.addTranslator(MetadataTypes.BYTE, FoxEntity::setFoxFlags)
.addTranslator(null) // Trusted player 1
.addTranslator(null) // Trusted player 2
.build();
FROG = EntityDefinition.inherited(FrogEntity::new, ageableEntityBase)
.type(EntityType.FROG)
.heightAndWidth(0.5f)
.addTranslator(MetadataType.FROG_VARIANT, FrogEntity::setFrogVariant)
.addTranslator(MetadataType.OPTIONAL_VARINT, FrogEntity::setTongueTarget)
.addTranslator(MetadataTypes.FROG_VARIANT, FrogEntity::setFrogVariant)
.addTranslator(MetadataTypes.OPTIONAL_VARINT, FrogEntity::setTongueTarget)
.build();
HOGLIN = EntityDefinition.inherited(HoglinEntity::new, ageableEntityBase)
.type(EntityType.HOGLIN)
.height(1.4f).width(1.3965f)
.addTranslator(MetadataType.BOOLEAN, HoglinEntity::setImmuneToZombification)
.addTranslator(MetadataTypes.BOOLEAN, HoglinEntity::setImmuneToZombification)
.build();
GOAT = EntityDefinition.inherited(GoatEntity::new, ageableEntityBase)
.type(EntityType.GOAT)
.height(1.3f).width(0.9f)
.addTranslator(MetadataType.BOOLEAN, GoatEntity::setScreamer)
.addTranslator(MetadataType.BOOLEAN, GoatEntity::setHasLeftHorn)
.addTranslator(MetadataType.BOOLEAN, GoatEntity::setHasRightHorn)
.addTranslator(MetadataTypes.BOOLEAN, GoatEntity::setScreamer)
.addTranslator(MetadataTypes.BOOLEAN, GoatEntity::setHasLeftHorn)
.addTranslator(MetadataTypes.BOOLEAN, GoatEntity::setHasRightHorn)
.build();
MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase)
.type(EntityType.MOOSHROOM)
.height(1.4f).width(0.9f)
.addTranslator(MetadataType.STRING, MooshroomEntity::setVariant)
.addTranslator(MetadataTypes.STRING, MooshroomEntity::setVariant)
.build();
OCELOT = EntityDefinition.inherited(OcelotEntity::new, ageableEntityBase)
.type(EntityType.OCELOT)
.height(0.7f).width(0.6f)
.addTranslator(MetadataType.BOOLEAN, (ocelotEntity, entityMetadata) -> ocelotEntity.setFlag(EntityFlag.TRUSTING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataTypes.BOOLEAN, (ocelotEntity, entityMetadata) -> ocelotEntity.setFlag(EntityFlag.TRUSTING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.build();
PANDA = EntityDefinition.inherited(PandaEntity::new, ageableEntityBase)
.type(EntityType.PANDA)
.height(1.25f).width(1.125f)
.addTranslator(null) // Unhappy counter
.addTranslator(null) // Sneeze counter
.addTranslator(MetadataType.INT, PandaEntity::setEatingCounter)
.addTranslator(MetadataType.BYTE, PandaEntity::setMainGene)
.addTranslator(MetadataType.BYTE, PandaEntity::setHiddenGene)
.addTranslator(MetadataType.BYTE, PandaEntity::setPandaFlags)
.addTranslator(MetadataTypes.INT, PandaEntity::setEatingCounter)
.addTranslator(MetadataTypes.BYTE, PandaEntity::setMainGene)
.addTranslator(MetadataTypes.BYTE, PandaEntity::setHiddenGene)
.addTranslator(MetadataTypes.BYTE, PandaEntity::setPandaFlags)
.build();
PIG = EntityDefinition.inherited(PigEntity::new, ageableEntityBase)
.type(EntityType.PIG)
.heightAndWidth(0.9f)
.addTranslator(MetadataType.BOOLEAN, (pigEntity, entityMetadata) -> pigEntity.setFlag(EntityFlag.SADDLED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataType.INT, PigEntity::setBoost)
.addTranslator(MetadataTypes.BOOLEAN, (pigEntity, entityMetadata) -> pigEntity.setFlag(EntityFlag.SADDLED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataTypes.INT, PigEntity::setBoost)
.build();
POLAR_BEAR = EntityDefinition.inherited(PolarBearEntity::new, ageableEntityBase)
.type(EntityType.POLAR_BEAR)
.height(1.4f).width(1.3f)
.addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.STANDING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataTypes.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.STANDING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.build();
RABBIT = EntityDefinition.inherited(RabbitEntity::new, ageableEntityBase)
.type(EntityType.RABBIT)
.height(0.5f).width(0.4f)
.addTranslator(MetadataType.INT, RabbitEntity::setRabbitVariant)
.addTranslator(MetadataTypes.INT, RabbitEntity::setRabbitVariant)
.build();
SHEEP = EntityDefinition.inherited(SheepEntity::new, ageableEntityBase)
.type(EntityType.SHEEP)
.height(1.3f).width(0.9f)
.addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags)
.addTranslator(MetadataTypes.BYTE, SheepEntity::setSheepFlags)
.build();
SNIFFER = EntityDefinition.inherited(SnifferEntity::new, ageableEntityBase)
.type(EntityType.SNIFFER)
.height(1.75f).width(1.9f)
.addTranslator(MetadataType.SNIFFER_STATE, SnifferEntity::setSnifferState)
.addTranslator(MetadataTypes.SNIFFER_STATE, SnifferEntity::setSnifferState)
.addTranslator(null) // Integer, drop seed at tick
.build();
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
.type(EntityType.STRIDER)
.height(1.7f).width(0.9f)
.addTranslator(MetadataType.INT, StriderEntity::setBoost)
.addTranslator(MetadataType.BOOLEAN, StriderEntity::setCold)
.addTranslator(MetadataType.BOOLEAN, StriderEntity::setSaddled)
.addTranslator(MetadataTypes.INT, StriderEntity::setBoost)
.addTranslator(MetadataTypes.BOOLEAN, StriderEntity::setCold)
.addTranslator(MetadataTypes.BOOLEAN, StriderEntity::setSaddled)
.build();
TURTLE = EntityDefinition.inherited(TurtleEntity::new, ageableEntityBase)
.type(EntityType.TURTLE)
.height(0.4f).width(1.2f)
.addTranslator(null) // Home position
.addTranslator(MetadataType.BOOLEAN, TurtleEntity::setPregnant)
.addTranslator(MetadataType.BOOLEAN, TurtleEntity::setLayingEgg)
.addTranslator(MetadataTypes.BOOLEAN, TurtleEntity::setPregnant)
.addTranslator(MetadataTypes.BOOLEAN, TurtleEntity::setLayingEgg)
.addTranslator(null) // Travel position
.addTranslator(null) // Going home
.addTranslator(null) // Travelling
@ -1034,7 +1080,7 @@ public final class EntityDefinitions {
.height(1.8f).width(0.6f)
.offset(1.62f)
.identifier("minecraft:villager_v2")
.addTranslator(MetadataType.VILLAGER_DATA, VillagerEntity::setVillagerData)
.addTranslator(MetadataTypes.VILLAGER_DATA, VillagerEntity::setVillagerData)
.build();
WANDERING_TRADER = EntityDefinition.inherited(abstractVillagerEntityBase.factory(), abstractVillagerEntityBase)
.type(EntityType.WANDERING_TRADER)
@ -1043,21 +1089,41 @@ public final class EntityDefinitions {
.build();
}
// Water creatures (AgeableWaterCreature)
{
DOLPHIN = EntityDefinition.inherited(DolphinEntity::new, ageableEntityBase)
.type(EntityType.DOLPHIN)
.height(0.6f).width(0.9f)
//TODO check
.addTranslator(null) // treasure position
.addTranslator(null) // "got fish"
.addTranslator(null) // "moistness level"
.build();
SQUID = EntityDefinition.inherited(SquidEntity::new, ageableEntityBase)
.type(EntityType.SQUID)
.heightAndWidth(0.8f)
.build();
GLOW_SQUID = EntityDefinition.inherited(GlowSquidEntity::new, SQUID)
.type(EntityType.GLOW_SQUID)
.addTranslator(null) // Set dark ticks remaining, possible TODO
.build();
}
// Horses
{
EntityDefinition<AbstractHorseEntity> abstractHorseEntityBase = EntityDefinition.inherited(AbstractHorseEntity::new, ageableEntityBase)
.addTranslator(MetadataType.BYTE, AbstractHorseEntity::setHorseFlags)
.addTranslator(MetadataTypes.BYTE, AbstractHorseEntity::setHorseFlags)
.build();
CAMEL = EntityDefinition.inherited(CamelEntity::new, abstractHorseEntityBase)
.type(EntityType.CAMEL)
.height(2.375f).width(1.7f)
.addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing)
.addTranslator(MetadataType.LONG, CamelEntity::setLastPoseTick)
.addTranslator(MetadataTypes.BOOLEAN, CamelEntity::setDashing)
.addTranslator(MetadataTypes.LONG, CamelEntity::setLastPoseTick)
.build();
HORSE = EntityDefinition.inherited(HorseEntity::new, abstractHorseEntityBase)
.type(EntityType.HORSE)
.height(1.6f).width(1.3965f)
.addTranslator(MetadataType.INT, HorseEntity::setHorseVariant)
.addTranslator(MetadataTypes.INT, HorseEntity::setHorseVariant)
.build();
SKELETON_HORSE = EntityDefinition.inherited(SkeletonHorseEntity::new, abstractHorseEntityBase)
.type(EntityType.SKELETON_HORSE)
@ -1068,7 +1134,7 @@ public final class EntityDefinitions {
.height(1.6f).width(1.3965f)
.build();
EntityDefinition<ChestedHorseEntity> chestedHorseEntityBase = EntityDefinition.inherited(ChestedHorseEntity::new, abstractHorseEntityBase)
.addTranslator(MetadataType.BOOLEAN, (horseEntity, entityMetadata) -> horseEntity.setFlag(EntityFlag.CHESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataTypes.BOOLEAN, (horseEntity, entityMetadata) -> horseEntity.setFlag(EntityFlag.CHESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.build();
DONKEY = EntityDefinition.inherited(chestedHorseEntityBase.factory(), chestedHorseEntityBase)
.type(EntityType.DONKEY)
@ -1081,8 +1147,8 @@ public final class EntityDefinitions {
LLAMA = EntityDefinition.inherited(LlamaEntity::new, chestedHorseEntityBase)
.type(EntityType.LLAMA)
.height(1.87f).width(0.9f)
.addTranslator(MetadataType.INT, LlamaEntity::setStrength)
.addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.VARIANT, entityMetadata.getValue()))
.addTranslator(MetadataTypes.INT, LlamaEntity::setStrength)
.addTranslator(MetadataTypes.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.VARIANT, entityMetadata.getValue()))
.build();
TRADER_LLAMA = EntityDefinition.inherited(TraderLlamaEntity::new, LLAMA)
.type(EntityType.TRADER_LLAMA)
@ -1091,30 +1157,30 @@ public final class EntityDefinitions {
}
EntityDefinition<TameableEntity> tameableEntityBase = EntityDefinition.<TameableEntity>inherited(null, ageableEntityBase) // No factory, is abstract
.addTranslator(MetadataType.BYTE, TameableEntity::setTameableFlags)
.addTranslator(MetadataType.OPTIONAL_UUID, TameableEntity::setOwner)
.addTranslator(MetadataTypes.BYTE, TameableEntity::setTameableFlags)
.addTranslator(MetadataTypes.OPTIONAL_UUID, TameableEntity::setOwner)
.build();
CAT = EntityDefinition.inherited(CatEntity::new, tameableEntityBase)
.type(EntityType.CAT)
.height(0.35f).width(0.3f)
.addTranslator(MetadataType.CAT_VARIANT, CatEntity::setCatVariant)
.addTranslator(MetadataType.BOOLEAN, CatEntity::setResting)
.addTranslator(MetadataTypes.CAT_VARIANT, CatEntity::setCatVariant)
.addTranslator(MetadataTypes.BOOLEAN, CatEntity::setResting)
.addTranslator(null) // "resting state one" //TODO
.addTranslator(MetadataType.INT, CatEntity::setCollarColor)
.addTranslator(MetadataTypes.INT, CatEntity::setCollarColor)
.build();
PARROT = EntityDefinition.inherited(ParrotEntity::new, tameableEntityBase)
.type(EntityType.PARROT)
.height(0.9f).width(0.5f)
.addTranslator(MetadataType.INT, (parrotEntity, entityMetadata) -> parrotEntity.getDirtyMetadata().put(EntityDataTypes.VARIANT, entityMetadata.getValue())) // Parrot color
.addTranslator(MetadataTypes.INT, (parrotEntity, entityMetadata) -> parrotEntity.getDirtyMetadata().put(EntityDataTypes.VARIANT, entityMetadata.getValue())) // Parrot color
.build();
WOLF = EntityDefinition.inherited(WolfEntity::new, tameableEntityBase)
.type(EntityType.WOLF)
.height(0.85f).width(0.6f)
// "Begging" on wiki.vg, "Interested" in Nukkit - the tilt of the head
.addTranslator(MetadataType.BOOLEAN, (wolfEntity, entityMetadata) -> wolfEntity.setFlag(EntityFlag.INTERESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataType.INT, WolfEntity::setCollarColor)
.addTranslator(MetadataType.INT, WolfEntity::setWolfAngerTime)
.addTranslator(MetadataType.WOLF_VARIANT, WolfEntity::setWolfVariant)
.addTranslator(MetadataTypes.BOOLEAN, (wolfEntity, entityMetadata) -> wolfEntity.setFlag(EntityFlag.INTERESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataTypes.INT, WolfEntity::setCollarColor)
.addTranslator(MetadataTypes.INT, WolfEntity::setWolfAngerTime)
.addTranslator(MetadataTypes.WOLF_VARIANT, WolfEntity::setWolfVariant)
.build();
// As of 1.18 these don't track entity data at all
@ -1122,10 +1188,23 @@ public final class EntityDefinitions {
.identifier("minecraft:armor_stand") // Emulated
.build(false); // Never sent over the network
// causes the registries to load
if (!EnvironmentUtils.isUnitTesting) {
Registries.JAVA_ENTITY_IDENTIFIERS.get().put("minecraft:marker", null); // We don't need an entity definition for this as it is never sent over the network
}
Registries.JAVA_ENTITY_IDENTIFIERS.get().put("minecraft:marker", null); // We don't need an entity definition for this as it is never sent over the network
}
private static EntityDefinition<BoatEntity> buildBoat(EntityDefinition<BoatEntity> base, EntityType entityType, BoatEntity.BoatVariant variant) {
return EntityDefinition.inherited((session, javaId, bedrockId, uuid, definition, position, motion, yaw, pitch, headYaw) ->
new BoatEntity(session, javaId, bedrockId, uuid, definition, position, motion, yaw, variant), base)
.type(entityType)
.identifier("minecraft:boat")
.build();
}
private static EntityDefinition<ChestBoatEntity> buildChestBoat(EntityDefinition<ChestBoatEntity> base, EntityType entityType, BoatEntity.BoatVariant variant) {
return EntityDefinition.inherited((session, javaId, bedrockId, uuid, definition, position, motion, yaw, pitch, headYaw) ->
new ChestBoatEntity(session, javaId, bedrockId, uuid, definition, position, motion, yaw, variant), base)
.type(entityType)
.identifier("minecraft:chest_boat")
.build();
}
public static void init() {

View file

@ -32,7 +32,7 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataType;
import java.util.Map;
/**
* A write-only wrapper for temporarily storing entity metadata that will be sent to Bedrock.
* A wrapper for temporarily storing entity metadata that will be sent to Bedrock.
*/
public final class GeyserDirtyMetadata {
private final Map<EntityDataType<?>, Object> metadata = new Object2ObjectLinkedOpenHashMap<>();
@ -53,6 +53,14 @@ public final class GeyserDirtyMetadata {
return !metadata.isEmpty();
}
/**
* Intended for testing purposes only
*/
public <T> T get(EntityDataType<T> entityData) {
//noinspection unchecked
return (T) metadata.get(entityData);
}
@Override
public String toString() {
return metadata.toString();

View file

@ -35,22 +35,25 @@ import lombok.Getter;
public enum GeyserAttributeType {
// Universal Attributes
FOLLOW_RANGE("minecraft:generic.follow_range", "minecraft:follow_range", 0f, 2048f, 32f),
KNOCKBACK_RESISTANCE("minecraft:generic.knockback_resistance", "minecraft:knockback_resistance", 0f, 1f, 0f),
MOVEMENT_SPEED("minecraft:generic.movement_speed", "minecraft:movement", 0f, 1024f, 0.1f),
FLYING_SPEED("minecraft:generic.flying_speed", "minecraft:movement", 0.0f, 1024.0f, 0.4000000059604645f),
ATTACK_DAMAGE("minecraft:generic.attack_damage", "minecraft:attack_damage", 0f, 2048f, 1f),
HORSE_JUMP_STRENGTH("minecraft:horse.jump_strength", "minecraft:horse.jump_strength", 0.0f, 2.0f, 0.7f),
LUCK("minecraft:generic.luck", "minecraft:luck", -1024f, 1024f, 0f),
FOLLOW_RANGE("minecraft:follow_range", "minecraft:follow_range", 0f, 2048f, 32f),
KNOCKBACK_RESISTANCE("minecraft:knockback_resistance", "minecraft:knockback_resistance", 0f, 1f, 0f),
MOVEMENT_SPEED("minecraft:movement_speed", "minecraft:movement", 0f, 1024f, 0.1f),
FLYING_SPEED("minecraft:flying_speed", "minecraft:movement", 0.0f, 1024.0f, 0.4000000059604645f),
ATTACK_DAMAGE("minecraft:attack_damage", "minecraft:attack_damage", 0f, 2048f, 1f),
HORSE_JUMP_STRENGTH("minecraft:jump_strength", "minecraft:horse.jump_strength", 0.0f, 2.0f, 0.7f),
LUCK("minecraft:luck", "minecraft:luck", -1024f, 1024f, 0f),
// Java Attributes
ARMOR("minecraft:generic.armor", null, 0f, 30f, 0f),
ARMOR_TOUGHNESS("minecraft:generic.armor_toughness", null, 0F, 20f, 0f),
ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f),
ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f),
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f),
BLOCK_INTERACTION_RANGE("minecraft:player.block_interaction_range", null, 0.0f, 64f, 4.5f),
ARMOR("minecraft:armor", null, 0f, 30f, 0f),
ARMOR_TOUGHNESS("minecraft:armor_toughness", null, 0F, 20f, 0f),
ATTACK_KNOCKBACK("minecraft:attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f),
ATTACK_SPEED("minecraft:attack_speed", null, 0f, 1024f, 4f),
MAX_HEALTH("minecraft:max_health", null, 0f, 1024f, 20f),
SCALE("minecraft:scale", null, 0.0625f, 16f, 1f),
BLOCK_INTERACTION_RANGE("minecraft:block_interaction_range", null, 0.0f, 64f, 4.5f),
MINING_EFFICIENCY("minecraft:mining_efficiency", null, 0f, 1024f, 0f),
BLOCK_BREAK_SPEED("minecraft:block_break_speed", null, 0f, 1024f, 1f),
SUBMERGED_MINING_SPEED("minecraft:submerged_mining_speed", null, 0f, 20f, 0.2f),
// Bedrock Attributes
ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),

View file

@ -32,12 +32,13 @@ import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundPaddleBoatPacket;
import java.util.UUID;
@ -63,16 +64,24 @@ public class BoatEntity extends Entity implements Leashable, Tickable {
* Saved for using the "pick" functionality on a boat.
*/
@Getter
private int variant;
protected final BoatVariant variant;
private long leashHolderBedrockId = -1;
// Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it
private final float ROWING_SPEED = 0.1f;
public BoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
public BoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, BoatVariant variant) {
// Initial rotation is incorrect
super(session, entityId, geyserId, uuid, definition, position.add(0d, definition.offset(), 0d), motion, yaw + 90, 0, yaw + 90);
this.variant = variant;
// TODO remove once 1.21.40 is dropped
if (variant == BoatVariant.PALE_OAK && GameProtocol.isPreWinterDrop(session)) {
variant = BoatVariant.BIRCH;
}
dirtyMetadata.put(EntityDataTypes.VARIANT, variant.ordinal());
// Required to be able to move on land 1.16.200+ or apply gravity not in the water 1.16.100+
dirtyMetadata.put(EntityDataTypes.IS_BUOYANT, true);
@ -124,15 +133,6 @@ public class BoatEntity extends Entity implements Leashable, Tickable {
moveRelative(0, 0, 0, yaw + 90, 0, 0, isOnGround);
}
public void setVariant(IntEntityMetadata entityMetadata) {
variant = entityMetadata.getPrimitiveValue();
dirtyMetadata.put(EntityDataTypes.VARIANT, switch (variant) {
case 6, 7, 8 -> variant - 1; // dark_oak, mangrove, bamboo
case 5 -> 8; // cherry
default -> variant;
});
}
public void setPaddlingLeft(BooleanEntityMetadata entityMetadata) {
isPaddlingLeft = entityMetadata.getPrimitiveValue();
if (!isPaddlingLeft) {
@ -187,7 +187,13 @@ public class BoatEntity extends Entity implements Leashable, Tickable {
@Override
public void tick() {
// Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing
doTick = !doTick; // Run every 100 ms
if (session.getPlayerEntity().getVehicle() == this) {
// For packet timing accuracy, we'll send the packets here, as that's what Java Edition 1.21.3 does.
ServerboundPaddleBoatPacket steerPacket = new ServerboundPaddleBoatPacket(session.isSteeringLeft(), session.isSteeringRight());
session.sendDownstreamGamePacket(steerPacket);
return;
}
doTick = !doTick; // Run every other tick
if (!doTick || passengers.isEmpty()) {
return;
}
@ -219,4 +225,22 @@ public class BoatEntity extends Entity implements Leashable, Tickable {
packet.setRowingTime(rowTime);
session.sendUpstreamPacket(packet);
}
/**
* Ordered by Bedrock ordinal
*/
public enum BoatVariant {
OAK,
SPRUCE,
BIRCH,
JUNGLE,
ACACIA,
DARK_OAK,
MANGROVE,
BAMBOO,
CHERRY,
PALE_OAK;
BoatVariant() {}
}
}

View file

@ -35,8 +35,8 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
public class ChestBoatEntity extends BoatEntity {
public ChestBoatEntity(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);
public ChestBoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, BoatVariant variant) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, variant);
}
@Override

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2024 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 org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;
public class EnderEyeEntity extends Entity {
public EnderEyeEntity(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();
// Correct sizing
dirtyMetadata.put(EntityDataTypes.SCALE, 0.5f);
}
}

View file

@ -25,12 +25,6 @@
package org.geysermc.geyser.entity.type;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@ -67,6 +61,13 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEnt
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@Getter
@Setter
public class Entity implements GeyserEntity {
@ -89,6 +90,7 @@ public class Entity implements GeyserEntity {
/**
* x = Yaw, y = Pitch, z = HeadYaw
* Java: Y = Yaw, X = Pitch
*/
protected float yaw;
protected float pitch;
@ -174,6 +176,7 @@ public class Entity implements GeyserEntity {
setFlag(EntityFlag.HAS_COLLISION, true);
setFlag(EntityFlag.CAN_SHOW_NAME, true);
setFlag(EntityFlag.CAN_CLIMB, true);
setFlag(EntityFlag.HIDDEN_WHEN_INVISIBLE, true);
// Let the Java server (or us) supply all sounds for an entity
setClientSideSilent();
}
@ -699,9 +702,4 @@ public class Entity implements GeyserEntity {
packet.setData(data);
session.sendUpstreamPacket(packet);
}
@SuppressWarnings("unchecked")
public <I extends Entity> @Nullable I as(Class<I> entityClass) {
return entityClass.isInstance(this) ? (I) this : null;
}
}

View file

@ -51,7 +51,7 @@ public class FireworkEntity extends Entity {
if (item == null) {
return;
}
DataComponents components = item.getDataComponents();
DataComponents components = item.getDataComponentsPatch();
if (components == null) {
return;
}

View file

@ -113,14 +113,23 @@ public class ItemFrameEntity extends Entity {
if (entityMetadata.getValue() != null) {
this.heldItem = entityMetadata.getValue();
ItemData itemData = ItemTranslator.translateToBedrock(session, heldItem);
String customIdentifier = session.getItemMappings().getCustomIdMappings().get(itemData.getDefinition().getRuntimeId());
NbtMapBuilder builder = NbtMap.builder();
builder.putByte("Count", (byte) itemData.getCount());
if (itemData.getTag() != null) {
builder.put("tag", itemData.getTag());
NbtMap itemDataTag = itemData.getTag();
if (itemDataTag != null) {
// Remove custom name that Geyser sets for items due to translating non-"custom_name" components
String customName = ItemTranslator.getCustomName(session, heldItem.getDataComponentsPatch(),
session.getItemMappings().getMapping(heldItem), 'f', true, false);
if (customName == null) {
// No custom name found, must modify tag if custom name exists
NbtMapBuilder copy = itemDataTag.toBuilder();
copy.remove("display"); // Also removes lore, but, should not matter
itemDataTag = copy.build();
}
builder.put("tag", itemDataTag);
}
builder.putShort("Damage", (short) itemData.getDamage());
builder.putString("Name", customIdentifier != null ? customIdentifier : session.getItemMappings().getMapping(entityMetadata.getValue()).getBedrockIdentifier());

View file

@ -25,11 +25,6 @@
package org.geysermc.geyser.entity.type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@ -66,11 +61,17 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEnt
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.EntityEffectParticleData;
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle;
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Getter
@Setter
public class LivingEntity extends Entity {
@ -343,7 +344,7 @@ public class LivingEntity extends Entity {
*/
// Implementation note for 1.20.5: this code was moved to the NameTag item.
protected final InteractionResult checkInteractWithNameTag(GeyserItemStack itemStack) {
if (itemStack.getComponent(DataComponentType.CUSTOM_NAME) != null) {
if (itemStack.getComponent(DataComponentTypes.CUSTOM_NAME) != null) {
// The mob shall be named
return InteractionResult.SUCCESS;
}
@ -445,35 +446,35 @@ public class LivingEntity extends Entity {
protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) {
if (javaAttribute.getType() instanceof AttributeType.Builtin type) {
switch (type) {
case GENERIC_MAX_HEALTH -> {
case MAX_HEALTH -> {
// Since 1.18.0, setting the max health to 0 or below causes the entity to die on Bedrock but not on Java
// See https://github.com/GeyserMC/Geyser/issues/2971
this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f);
newAttributes.add(createHealthAttribute());
}
case GENERIC_MOVEMENT_SPEED -> {
case MOVEMENT_SPEED -> {
AttributeData attributeData = calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED);
newAttributes.add(attributeData);
if (this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setMoveSpeed(attributeData.getValue());
}
}
case GENERIC_STEP_HEIGHT -> {
case STEP_HEIGHT -> {
if (this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setStepHeight((float) AttributeUtils.calculateValue(javaAttribute));
}
}
case GENERIC_GRAVITY -> {
case GRAVITY -> {
if (this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setGravity(AttributeUtils.calculateValue(javaAttribute));
}
}
case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE));
case GENERIC_FLYING_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED));
case GENERIC_FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE));
case GENERIC_KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE));
case GENERIC_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));
case GENERIC_SCALE -> {
case ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE));
case FLYING_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED));
case FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE));
case KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE));
case JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));
case SCALE -> {
// Attribute on Java, entity data on Bedrock
setAttributeScale((float) AttributeUtils.calculateValue(javaAttribute));
updateBedrockMetadata();

View file

@ -25,20 +25,40 @@
package org.geysermc.geyser.entity.type;
import org.cloudburstmc.math.vector.Vector3d;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.MinecartStep;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundMoveMinecartPacket;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public class MinecartEntity extends Entity {
public class MinecartEntity extends Entity implements Tickable {
private static final int POS_ROT_LERP_TICKS = 3;
private final List<MinecartStep> lerpSteps = new LinkedList<>();
private final List<MinecartStep> currentLerpSteps = new LinkedList<>();
private MinecartStep lastCompletedStep = new MinecartStep(Vector3d.ZERO, Vector3d. ZERO, 0.0F, 0.0F, 0.0F);
private float currentStepsTotalWeight = 0.0F;
private int lerpDelay = 0;
private PartialStep cachedPartialStep;
private int cachedStepDelay;
private float cachedDelta;
public MinecartEntity(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.add(0d, definition.offset(), 0d), motion, yaw, pitch, headYaw);
@ -58,6 +78,131 @@ public class MinecartEntity extends Entity {
dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0));
}
@Override
public void tick() {
if (!session.isUsingExperimentalMinecartLogic()) {
return;
}
// All minecart lerp code here and in the methods below has been based off of the code in the Java NewMinecartBehavior class
lerpDelay--;
if (lerpDelay <= 0) {
updateCompletedStep();
currentLerpSteps.clear();
if (!lerpSteps.isEmpty()) {
currentLerpSteps.addAll(lerpSteps);
lerpSteps.clear();
currentStepsTotalWeight = 0.0F;
for (MinecartStep step : currentLerpSteps) {
currentStepsTotalWeight += step.weight();
}
lerpDelay = currentStepsTotalWeight == 0.0F ? 0 : POS_ROT_LERP_TICKS;
}
}
if (isLerping()) {
float delta = 1.0F; // This is always 1, maybe it should be removed
Vector3f position = getCurrentLerpPosition(delta).toFloat();
Vector3f movement = getCurrentLerpMovement(delta).toFloat();
setPosition(position);
setMotion(movement);
setYaw(180.0F - getCurrentLerpYaw(delta));
setPitch(getCurrentLerpPitch(delta));
MoveEntityDeltaPacket moveEntityPacket = new MoveEntityDeltaPacket();
moveEntityPacket.setRuntimeEntityId(geyserId);
moveEntityPacket.setX(position.getX());
moveEntityPacket.setY(position.getY() + definition.offset());
moveEntityPacket.setZ(position.getZ());
moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X);
moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y);
moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z);
moveEntityPacket.setYaw(getYaw());
moveEntityPacket.setPitch(getPitch());
moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
moveEntityPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket();
entityMotionPacket.setRuntimeEntityId(geyserId);
entityMotionPacket.setMotion(movement);
session.sendUpstreamPacket(moveEntityPacket);
session.sendUpstreamPacket(entityMotionPacket);
}
}
public void handleMinecartMovePacket(ClientboundMoveMinecartPacket packet) {
lerpSteps.addAll(packet.getLerpSteps());
}
private boolean isLerping() {
return !currentLerpSteps.isEmpty();
}
private float getCurrentLerpPitch(float delta) {
PartialStep partialStep = getCurrentLerpStep(delta);
return lerpRotation(partialStep.delta, partialStep.previousStep.xRot(), partialStep.currentStep.xRot());
}
private float getCurrentLerpYaw(float delta) {
PartialStep partialStep = getCurrentLerpStep(delta);
return lerpRotation(partialStep.delta, partialStep.previousStep.yRot(), partialStep.currentStep.yRot());
}
private Vector3d getCurrentLerpPosition(float delta) {
PartialStep partialStep = getCurrentLerpStep(delta);
return lerp(partialStep.delta, partialStep.previousStep.position(), partialStep.currentStep.position());
}
private Vector3d getCurrentLerpMovement(float delta) {
PartialStep partialStep = getCurrentLerpStep(delta);
return lerp(partialStep.delta, partialStep.previousStep.movement(), partialStep.currentStep.movement());
}
private PartialStep getCurrentLerpStep(float delta) {
if (cachedDelta != delta || lerpDelay != cachedStepDelay || cachedPartialStep == null) {
float g = ((POS_ROT_LERP_TICKS - lerpDelay) + delta) / POS_ROT_LERP_TICKS;
float totalWeight = 0.0F;
float stepDelta = 1.0F;
boolean foundStep = false;
int step;
for (step = 0; step < currentLerpSteps.size(); step++) {
float currentWeight = currentLerpSteps.get(step).weight();
if (!(currentWeight <= 0.0F)) {
totalWeight += currentWeight;
if ((double) totalWeight >= currentStepsTotalWeight * (double) g) {
float h = totalWeight - currentWeight;
stepDelta = (g * currentStepsTotalWeight - h) / currentWeight;
foundStep = true;
break;
}
}
}
if (!foundStep) {
step = currentLerpSteps.size() - 1;
}
MinecartStep currentStep = currentLerpSteps.get(step);
MinecartStep previousStep = step > 0 ? currentLerpSteps.get(step - 1) : lastCompletedStep;
cachedPartialStep = new PartialStep(stepDelta, currentStep, previousStep);
cachedStepDelay = lerpDelay;
cachedDelta = delta;
}
return cachedPartialStep;
}
private void updateCompletedStep() {
lastCompletedStep = new MinecartStep(position.toDouble(), motion.toDouble(), yaw, pitch, 0.0F);
}
@Override
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
super.moveAbsolute(position.add(0d, this.definition.offset(), 0d), yaw, pitch, headYaw, isOnGround, teleported);
@ -103,4 +248,19 @@ public class MinecartEntity extends Entity {
}
}
}
private static Vector3d lerp(double delta, Vector3d start, Vector3d end) {
return Vector3d.from(lerp(delta, start.getX(), end.getX()), lerp(delta, start.getY(), end.getY()), lerp(delta, start.getZ(), end.getZ()));
}
public static double lerp(double delta, double start, double end) {
return start + delta * (end - start);
}
private static float lerpRotation(float delta, float start, float end) {
return start + delta * MathUtils.wrapDegrees(end - start);
}
private record PartialStep(float delta, MinecartStep currentStep, MinecartStep previousStep) {
}
}

View file

@ -25,18 +25,25 @@
package org.geysermc.geyser.entity.type;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
// Note: 1.19.4 requires that the billboard is set to something in order to show, on Java Edition
@Getter
public class TextDisplayEntity extends DisplayBaseEntity {
private int lineCount;
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.add(0, definition.offset(), 0), motion, yaw, pitch, headYaw);
}
@ -60,6 +67,15 @@ public class TextDisplayEntity extends DisplayBaseEntity {
}
public void setText(EntityMetadata<Component, ?> entityMetadata) {
this.dirtyMetadata.put(EntityDataTypes.NAME, MessageTranslator.convertMessage(entityMetadata.getValue()));
this.dirtyMetadata.put(EntityDataTypes.NAME, MessageTranslator.convertMessage(entityMetadata.getValue(), session.locale()));
calculateLineCount(entityMetadata.getValue());
}
private void calculateLineCount(@Nullable Component text) {
if (text == null) {
lineCount = 0;
return;
}
lineCount = PlainTextComponentSerializer.plainText().serialize(text).split("\n").length;
}
}

View file

@ -25,12 +25,14 @@
package org.geysermc.geyser.entity.type;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import java.util.UUID;
@ -39,7 +41,7 @@ import java.util.UUID;
*/
public class ThrowableItemEntity extends ThrowableEntity {
/**
* Number of ticks since the entity was spawned by the Java server
* Number of draw ticks since the entity was spawned by the Java server
*/
private int age;
private boolean invisible;
@ -48,29 +50,38 @@ public class ThrowableItemEntity extends ThrowableEntity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
setFlag(EntityFlag.INVISIBLE, true);
invisible = false;
}
private void checkVisibility() {
if (invisible != getFlag(EntityFlag.INVISIBLE)) {
if (!invisible) {
Vector3f playerPos = session.getPlayerEntity().getPosition();
// Prevent projectiles from blocking the player's screen
if (age >= 4 || position.distanceSquared(playerPos) > 16) {
setFlag(EntityFlag.INVISIBLE, false);
updateBedrockMetadata();
}
} else {
setFlag(EntityFlag.INVISIBLE, true);
updateBedrockMetadata();
}
}
age++;
age = 0;
}
@Override
public void tick() {
protected void initializeMetadata() {
super.initializeMetadata();
// Correct sizing
dirtyMetadata.put(EntityDataTypes.SCALE, 0.5f);
}
private void checkVisibility() {
age++;
// Prevent projectiles from blocking the player's screen
if (session.isTickingFrozen()) {
// This may seem odd, but it matches java edition
Vector3f playerPos = session.getPlayerEntity().getPosition().down(EntityDefinitions.PLAYER.offset());
setInvisible(playerPos.distanceSquared(position.add(0, definition.offset(), 0)) < 12.25);
} else {
setInvisible(age < 2);
}
if (invisible != getFlag(EntityFlag.INVISIBLE)) {
setFlag(EntityFlag.INVISIBLE, invisible);
updateBedrockMetadata();
}
}
@Override
public void drawTick() {
checkVisibility();
super.tick();
super.drawTick();
}
@Override

View file

@ -36,7 +36,7 @@ import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.PotionContents;
@ -59,9 +59,9 @@ public class ThrownPotionEntity extends ThrowableItemEntity {
setFlag(EntityFlag.LINGERING, false);
} else {
// As of Java 1.19.3, the server/client doesn't seem to care of the item is actually a potion?
DataComponents components = itemStack.getDataComponents();
DataComponents components = itemStack.getDataComponentsPatch();
if (components != null) {
PotionContents potionContents = components.get(DataComponentType.POTION_CONTENTS);
PotionContents potionContents = components.get(DataComponentTypes.POTION_CONTENTS);
if (potionContents != null) {
Potion potion = Potion.getByJavaId(potionContents.getPotionId());
if (potion != null) {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2024 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
@ -26,8 +26,21 @@
package org.geysermc.geyser.entity.type;
/**
* Implemented onto anything that should have code ran every Minecraft tick - 50 milliseconds.
* Implemented onto anything that should have code ran every Minecraft tick.
* By default, the Java server runs at 20 TPS, 50 milliseconds for each tick.
*/
public interface Tickable {
/**
* This function gets called every tick at all times, even when the server requests that
* the game should be frozen. This should be used for updating things that are always
* client side updated on Java, regardless of if the server is frozen or not.
*/
default void drawTick() {
}
/**
* This function gets called every game tick as long as the
* game tick loop isn't frozen.
*/
void tick();
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2024 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
@ -23,27 +23,21 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.registry.type;
package org.geysermc.geyser.entity.type.living;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.geysermc.geyser.item.type.Item;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
/**
* Implements ItemDefinition while also providing a reference to our item mappings.
*/
public record GeyserItemDefinition(Item javaItem, String identifier, boolean componentBased, int runtimeId) implements ItemDefinition {
@Override
public String getIdentifier() {
return identifier;
import java.util.UUID;
public abstract class AgeableWaterEntity extends AgeableEntity {
public AgeableWaterEntity(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
public boolean isComponentBased() {
return componentBased;
}
@Override
public int getRuntimeId() {
return runtimeId;
public boolean canBeLeashed() {
return false;
}
}

View file

@ -27,6 +27,7 @@ package org.geysermc.geyser.entity.type.living;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
@ -36,6 +37,7 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.scoreboard.Team;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.MathUtils;
@ -123,6 +125,12 @@ public class ArmorStandEntity extends LivingEntity {
this.position = position;
}
@Override
public void updateNametag(@Nullable Team team) {
// unlike all other LivingEntities, armor stands are not affected by team nametag visibility
super.updateNametag(team, true);
}
@Override
public void setDisplayName(EntityMetadata<Optional<Component>, ?> entityMetadata) {
super.setDisplayName(entityMetadata);

View file

@ -37,7 +37,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
public class DolphinEntity extends WaterEntity {
public class DolphinEntity extends AgeableWaterEntity {
public DolphinEntity(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);
}

View file

@ -36,7 +36,7 @@ import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class SquidEntity extends WaterEntity implements Tickable {
public class SquidEntity extends AgeableWaterEntity implements Tickable {
private float targetPitch;
private float targetYaw;

View file

@ -33,8 +33,9 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.AgeableEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
@ -48,7 +49,7 @@ public abstract class AnimalEntity extends AgeableEntity {
}
protected final boolean canEat(GeyserItemStack itemStack) {
ItemTag tag = getFoodTag();
Tag<Item> tag = getFoodTag();
if (tag == null) {
return false;
}
@ -58,7 +59,7 @@ public abstract class AnimalEntity extends AgeableEntity {
/**
* @return the tag associated with this animal for eating food. Null for nothing or different behavior.
*/
protected abstract @Nullable ItemTag getFoodTag();
protected abstract @Nullable Tag<Item> getFoodTag();
@NonNull
@Override

View file

@ -28,8 +28,10 @@ package org.geysermc.geyser.entity.type.living.animal;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.ArmadilloState;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
@ -75,7 +77,7 @@ public class ArmadilloEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.ARMADILLO_FOOD;
}
}

View file

@ -32,8 +32,10 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
@ -62,7 +64,7 @@ public class AxolotlEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.AXOLOTL_FOOD;
}

View file

@ -32,8 +32,10 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
@ -69,7 +71,7 @@ public class BeeEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.BEE_FOOD;
}
}

View file

@ -28,8 +28,10 @@ package org.geysermc.geyser.entity.type.living.animal;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import java.util.UUID;
@ -41,7 +43,7 @@ public class ChickenEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.CHICKEN_FOOD;
}
}

View file

@ -33,8 +33,10 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
@ -69,7 +71,7 @@ public class CowEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.COW_FOOD;
}
}

View file

@ -30,8 +30,10 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
@ -57,7 +59,7 @@ public class FoxEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.FOX_FOOD;
}
}

View file

@ -31,8 +31,10 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
@ -77,7 +79,7 @@ public class FrogEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.FROG_FOOD;
}
}

View file

@ -34,8 +34,10 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
@ -99,7 +101,7 @@ public class GoatEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.GOAT_FOOD;
}
}

View file

@ -30,8 +30,10 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
@ -58,7 +60,7 @@ public class HoglinEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.HOGLIN_FOOD;
}

View file

@ -31,8 +31,10 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
@ -47,7 +49,7 @@ public class OcelotEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.OCELOT_FOOD;
}

View file

@ -34,8 +34,10 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
@ -90,7 +92,7 @@ public class PandaEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.PANDA_FOOD;
}

View file

@ -38,9 +38,11 @@ import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
@ -58,7 +60,7 @@ public class PigEntity extends AnimalEntity implements Tickable, ClientVehicle {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.PIG_FOOD;
}

View file

@ -28,8 +28,9 @@ package org.geysermc.geyser.entity.type.living.animal;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import java.util.UUID;
@ -41,7 +42,7 @@ public class PolarBearEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return null;
}
}

View file

@ -31,8 +31,10 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
@ -79,7 +81,7 @@ public class RabbitEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.RABBIT_FOOD;
}
}

View file

@ -34,8 +34,10 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.DyeItem;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
@ -59,7 +61,7 @@ public class SheepEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.SHEEP_FOOD;
}
@ -103,4 +105,4 @@ public class SheepEntity extends AnimalEntity {
private boolean canDye(GeyserItemStack item) {
return item.asItem() instanceof DyeItem dyeItem && dyeItem.dyeColor() != this.color && !getFlag(EntityFlag.SHEARED);
}
}
}

View file

@ -35,8 +35,10 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.Tickable;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.SnifferState;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
@ -73,7 +75,7 @@ public class SnifferEntity extends AnimalEntity implements Tickable {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.SNIFFER_FOOD;
}

View file

@ -39,9 +39,11 @@ import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
@ -105,7 +107,7 @@ public class StriderEntity extends AnimalEntity implements Tickable, ClientVehic
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.STRIDER_FOOD;
}

View file

@ -29,8 +29,10 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
@ -51,7 +53,7 @@ public class TurtleEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.TURTLE_FOOD;
}

View file

@ -39,8 +39,10 @@ import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
@ -119,7 +121,7 @@ public class AbstractHorseEntity extends AnimalEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.HORSE_FOOD;
}

View file

@ -35,12 +35,14 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.vehicle.CamelVehicleComponent;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
@ -100,7 +102,7 @@ public class CamelEntity extends AbstractHorseEntity implements ClientVehicle {
}
@Override
protected @Nullable ItemTag getFoodTag() {
protected @Nullable Tag<Item> getFoodTag() {
return ItemTag.CAMEL_FOOD;
}
@ -141,7 +143,7 @@ public class CamelEntity extends AbstractHorseEntity implements ClientVehicle {
@Override
protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) {
AttributeData attributeData = super.calculateAttribute(javaAttribute, type);
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_JUMP_STRENGTH) {
if (javaAttribute.getType() == AttributeType.Builtin.JUMP_STRENGTH) {
vehicleComponent.setHorseJumpStrength(attributeData.getValue());
}
return attributeData;

View file

@ -30,8 +30,10 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
@ -56,7 +58,7 @@ public class LlamaEntity extends ChestedHorseEntity {
}
@Override
protected @Nullable ItemTag getFoodTag() {
protected @Nullable Tag<Item> getFoodTag() {
return ItemTag.LLAMA_FOOD;
}
}

View file

@ -32,8 +32,10 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
@ -109,7 +111,7 @@ public class CatEntity extends TameableEntity {
}
@Override
protected @Nullable ItemTag getFoodTag() {
protected @Nullable Tag<Item> getFoodTag() {
return ItemTag.CAT_FOOD;
}

View file

@ -34,6 +34,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
@ -47,7 +48,7 @@ public class ParrotEntity extends TameableEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return null;
}

View file

@ -36,8 +36,10 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.enchantment.EnchantmentComponent;
import org.geysermc.geyser.item.type.DyeItem;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.ItemUtils;
@ -49,6 +51,8 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.Object
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet;
import java.util.Collections;
import java.util.Locale;
@ -56,7 +60,7 @@ import java.util.UUID;
public class WolfEntity extends TameableEntity {
private byte collarColor = 14; // Red - default
private HolderSet repairableItems = null;
private boolean isCurseOfBinding = false;
public WolfEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
@ -116,14 +120,16 @@ public class WolfEntity extends TameableEntity {
@Override
@Nullable
protected ItemTag getFoodTag() {
protected Tag<Item> getFoodTag() {
return ItemTag.WOLF_FOOD;
}
@Override
public void setChestplate(ItemStack stack) {
super.setChestplate(stack);
isCurseOfBinding = ItemUtils.hasEffect(session, stack, EnchantmentComponent.PREVENT_ARMOR_CHANGE); // TODO test
public void setBody(ItemStack stack) {
super.setBody(stack);
isCurseOfBinding = ItemUtils.hasEffect(session, stack, EnchantmentComponent.PREVENT_ARMOR_CHANGE);
// Not using ItemStack#getDataComponents as that wouldn't include default item components
repairableItems = GeyserItemStack.from(stack).getComponent(DataComponentTypes.REPAIRABLE);
}
@Override
@ -150,16 +156,17 @@ public class WolfEntity extends TameableEntity {
return super.testMobInteraction(hand, itemInHand);
}
}
if (itemInHand.asItem() == Items.WOLF_ARMOR && !this.chestplate.isValid() && !getFlag(EntityFlag.BABY)) {
if (itemInHand.asItem() == Items.WOLF_ARMOR && !this.body.isValid() && !getFlag(EntityFlag.BABY)) {
return InteractiveTag.EQUIP_WOLF_ARMOR;
}
if (itemInHand.asItem() == Items.SHEARS && this.chestplate.isValid()
if (itemInHand.asItem() == Items.SHEARS && this.body.isValid()
&& (!isCurseOfBinding || session.getGameMode().equals(GameMode.CREATIVE))) {
return InteractiveTag.REMOVE_WOLF_ARMOR;
}
if (Items.WOLF_ARMOR.isValidRepairItem(itemInHand.asItem()) && getFlag(EntityFlag.SITTING) &&
this.chestplate.isValid() && this.chestplate.getTag() != null &&
this.chestplate.getTag().getInt("Damage") > 0) {
if (getFlag(EntityFlag.SITTING) &&
session.getTagCache().isItem(repairableItems, itemInHand.asItem()) &&
this.body.isValid() && this.body.getTag() != null &&
this.body.getTag().getInt("Damage") > 0) {
return InteractiveTag.REPAIR_WOLF_ARMOR;
}
// Tamed and owned by player - can sit/stand

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2024 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.living.monster;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
import java.util.Optional;
import java.util.UUID;
public class CreakingEntity extends MonsterEntity {
public static final String CREAKING_STATE = "minecraft:creaking_state";
public static final String CREAKING_SWAYING_TICKS = "minecraft:creaking_swaying_ticks";
private Vector3i homePosition;
public CreakingEntity(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();
setFlag(EntityFlag.FIRE_IMMUNE, true);
}
@Override
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
propertyManager.add(CREAKING_STATE, "neutral");
// also, the creaking seems to have this minecraft:creaking_swaying_ticks thingy
// which i guess is responsible for some animation?
// it's sent over the network, all 6 "stages" 50ms in between of each other.
// no clue what it's used for tbh, so i'm not gonna bother implementing it
// - chris
propertyManager.add(CREAKING_SWAYING_TICKS, 0);
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
}
public void setCanMove(EntityMetadata<Boolean,? extends MetadataType<Boolean>> booleanEntityMetadata) {
setFlag(EntityFlag.BODY_ROTATION_BLOCKED, !booleanEntityMetadata.getValue());
propertyManager.add(CREAKING_STATE, booleanEntityMetadata.getValue() ? "hostile_unobserved" : "hostile_observed");
updateBedrockEntityProperties();
}
public void setActive(EntityMetadata<Boolean,? extends MetadataType<Boolean>> booleanEntityMetadata) {
if (!booleanEntityMetadata.getValue()) {
propertyManager.add(CREAKING_STATE, "neutral");
}
}
public void setIsTearingDown(EntityMetadata<Boolean,? extends MetadataType<Boolean>> booleanEntityMetadata) {
if (booleanEntityMetadata.getValue()) {
propertyManager.add(CREAKING_STATE, "crumbling");
updateBedrockEntityProperties();
}
}
public void setHomePos(EntityMetadata<Optional<Vector3i>,? extends MetadataType<Optional<Vector3i>>> optionalEntityMetadata) {
if (optionalEntityMetadata.getValue().isPresent()) {
this.homePosition = optionalEntityMetadata.getValue().get();
} else {
this.homePosition = null;
}
}
public void createParticleBeam() {
if (this.homePosition != null) {
LevelEventGenericPacket levelEventGenericPacket = new LevelEventGenericPacket();
levelEventGenericPacket.setType(LevelEvent.PARTICLE_CREAKING_HEART_TRIAL);
levelEventGenericPacket.setTag(
NbtMap.builder()
.putInt("CreakingAmount", 20)
.putFloat("CreakingX", position.getX())
.putFloat("CreakingY", position.getY())
.putFloat("CreakingZ", position.getZ())
.putInt("HeartAmount", 20)
.putFloat("HeartX", homePosition.getX())
.putFloat("HeartY", homePosition.getY())
.putFloat("HeartZ", homePosition.getZ())
.build()
);
session.sendUpstreamPacket(levelEventGenericPacket);
}
}
}

View file

@ -25,12 +25,6 @@
package org.geysermc.geyser.entity.type.player;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
@ -43,7 +37,6 @@ import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
import org.cloudburstmc.protocol.bedrock.data.GameType;
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataMap;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
@ -66,6 +59,13 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.Boolea
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Getter @Setter
public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public static final float SNEAKING_POSE_HEIGHT = 1.5f;
@ -97,11 +97,11 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
/**
* Saves the parrot currently on the player's left shoulder; otherwise null
*/
private ParrotEntity leftParrot;
private @Nullable ParrotEntity leftParrot;
/**
* Saves the parrot currently on the player's right shoulder; otherwise null
*/
private ParrotEntity rightParrot;
private @Nullable ParrotEntity rightParrot;
public PlayerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, Vector3f position,
Vector3f motion, float yaw, float pitch, float headYaw, String username, @Nullable String texturesProperty) {
@ -112,20 +112,6 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
this.texturesProperty = texturesProperty;
}
/**
* Do not use! For testing purposes only
*/
public PlayerEntity(GeyserSession session, long geyserId, UUID uuid, String username) {
super(session, -1, geyserId, uuid, EntityDefinitions.PLAYER, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0);
this.username = username;
this.nametag = username;
this.texturesProperty = null;
// clear initial metadata
dirtyMetadata.apply(new EntityDataMap());
setFlagsDirty(false);
}
@Override
protected void initializeMetadata() {
super.initializeMetadata();
@ -193,11 +179,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
if (session.getEntityCache().getPlayerEntity(uuid) == null)
return;
if (session.getEntityCache().getEntityByGeyserId(geyserId) == null) {
session.getEntityCache().spawnEntity(this);
} else {
spawnEntity();
}
session.getEntityCache().spawnEntity(this);
}
@Override
@ -269,10 +251,6 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
}
}
public void updateRotation(float yaw, float pitch, float headYaw, boolean isOnGround) {
moveRelative(0, 0, 0, yaw, pitch, headYaw, isOnGround);
}
@Override
public void setPosition(Vector3f position) {
if (this.bedPosition != null) {
@ -357,7 +335,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
parrot.updateBedrockMetadata();
SetEntityLinkPacket linkPacket = new SetEntityLinkPacket();
EntityLinkData.Type type = isLeft ? EntityLinkData.Type.RIDER : EntityLinkData.Type.PASSENGER;
linkPacket.setEntityLink(new EntityLinkData(geyserId, parrot.getGeyserId(), type, false, false));
linkPacket.setEntityLink(new EntityLinkData(geyserId, parrot.getGeyserId(), type, false, false, 0f));
// Delay, or else spawned-in players won't get the link
// TODO: Find a better solution.
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(linkPacket), 500, TimeUnit.MILLISECONDS);
@ -472,6 +450,6 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
@Override
public Vector3f position() {
return this.position.clone();
return this.position.down(definition.offset());
}
}

View file

@ -38,7 +38,6 @@ import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.AttributeUtils;
@ -143,9 +142,32 @@ public class SessionPlayerEntity extends PlayerEntity {
this.position = position.add(0, definition.offset(), 0);
}
/**
* Special method used only when updating the session player's rotation.
* For some reason, Mode#NORMAL ignored rotation. Yay.
* @param yaw the new yaw
* @param pitch the new pitch
* @param headYaw the head yaw
*/
public void updateOwnRotation(float yaw, float pitch, float headYaw) {
setYaw(yaw);
setPitch(pitch);
setHeadYaw(headYaw);
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(geyserId);
movePlayerPacket.setPosition(position);
movePlayerPacket.setRotation(getBedrockRotation());
movePlayerPacket.setOnGround(isOnGround());
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
session.sendUpstreamPacket(movePlayerPacket);
}
/**
* Set the player's position without applying an offset or moving the bounding box
* This is used in BedrockMovePlayerTranslator which receives the player's position
* This is used in BedrockMovePlayer which receives the player's position
* with the offset pre-applied
*
* @param position the new position of the Bedrock player
@ -212,12 +234,7 @@ public class SessionPlayerEntity extends PlayerEntity {
// the bubbles visually pop
setFlag(EntityFlag.BREATHING, amount >= this.lastAirSupply);
this.lastAirSupply = amount;
if (amount == getMaxAir() && GameProtocol.isPre1_21_0(session)) {
super.setAirSupply(0); // Hide the bubble counter from the UI for the player
} else {
super.setAirSupply(amount);
}
super.setAirSupply(amount);
}
@Override
@ -247,9 +264,9 @@ public class SessionPlayerEntity extends PlayerEntity {
@Override
protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) {
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) {
if (javaAttribute.getType() == AttributeType.Builtin.ATTACK_SPEED) {
session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute));
} else if (javaAttribute.getType() == AttributeType.Builtin.PLAYER_BLOCK_INTERACTION_RANGE) {
} else if (javaAttribute.getType() == AttributeType.Builtin.BLOCK_INTERACTION_RANGE) {
this.blockInteractionRange = AttributeUtils.calculateValue(javaAttribute);
} else {
super.updateAttribute(javaAttribute, newAttributes);
@ -263,6 +280,15 @@ public class SessionPlayerEntity extends PlayerEntity {
return attributeData;
}
public float attributeOrDefault(GeyserAttributeType type) {
var attribute = this.attributes.get(type);
if (attribute == null) {
return type.getDefaultValue();
}
return attribute.getValue();
}
public void setLastDeathPosition(@Nullable GlobalPos pos) {
if (pos != null) {
dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_POS, pos.getPosition());

View file

@ -102,17 +102,6 @@ public class SkullPlayerEntity extends PlayerEntity {
session.sendUpstreamPacket(addPlayerPacket);
}
/**
* Hide the player entity so that it can be reused for a different skull.
*/
public void free() {
setFlag(EntityFlag.INVISIBLE, true);
updateBedrockMetadata();
// Move skull entity out of the way
moveAbsolute(session.getPlayerEntity().getPosition().up(128), 0, 0, 0, false, true);
}
public void updateSkull(SkullCache.Skull skull) {
skullPosition = skull.getPosition();

View file

@ -76,8 +76,8 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
public VehicleComponent(T vehicle, float stepHeight) {
this.vehicle = vehicle;
this.stepHeight = stepHeight;
this.moveSpeed = (float) AttributeType.Builtin.GENERIC_MOVEMENT_SPEED.getDef();
this.gravity = AttributeType.Builtin.GENERIC_GRAVITY.getDef();
this.moveSpeed = (float) AttributeType.Builtin.MOVEMENT_SPEED.getDef();
this.gravity = AttributeType.Builtin.GRAVITY.getDef();
double width = vehicle.getBoundingBoxWidth();
double height = vehicle.getBoundingBoxHeight();
@ -105,6 +105,10 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
boundingBox.setMiddleZ(z);
}
public void moveAbsolute(Vector3d vec) {
moveAbsolute(vec.getX(), vec.getY(), vec.getZ());
}
public void moveRelative(double x, double y, double z) {
boundingBox.translate(x, y, z);
}
@ -756,9 +760,8 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
vehicle.getSession().sendUpstreamPacket(moveEntityDeltaPacket);
}
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos.getX(), javaPos.getY(), javaPos.getZ(), rotation.getX(), rotation.getY());
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos, rotation.getX(), rotation.getY(), vehicle.isOnGround());
vehicle.getSession().sendDownstreamPacket(moveVehiclePacket);
vehicle.getSession().setLastVehicleMoveTimestamp(System.currentTimeMillis());
}
protected double getGravity() {

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2025 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.event.type;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.event.bedrock.SessionDisconnectEvent;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
/**
* A wrapper around the {@link SessionDisconnectEvent} that allows
* Geyser to access the underlying component when replacing disconnect messages.
*/
@Getter
public class SessionDisconnectEventImpl extends SessionDisconnectEvent {
private final Component reasonComponent;
public SessionDisconnectEventImpl(@NonNull GeyserSession session, Component reason) {
super(session, MessageTranslator.convertMessageRaw(reason, session.locale()));
this.reasonComponent = reason;
}
}

View file

@ -36,14 +36,14 @@ import java.util.UUID;
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent {
private final Map<String, ResourcePack> packs;
private final Map<UUID, ResourcePack> packs;
public SessionLoadResourcePacksEventImpl(GeyserSession session, Map<String, ResourcePack> packMap) {
public SessionLoadResourcePacksEventImpl(GeyserSession session, Map<UUID, ResourcePack> packMap) {
super(session);
this.packs = packMap;
}
public @NonNull Map<String, ResourcePack> getPacks() {
public @NonNull Map<UUID, ResourcePack> getPacks() {
return packs;
}
@ -54,16 +54,16 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
@Override
public boolean register(@NonNull ResourcePack resourcePack) {
String packID = resourcePack.manifest().header().uuid().toString();
UUID packID = resourcePack.manifest().header().uuid();
if (packs.containsValue(resourcePack) || packs.containsKey(packID)) {
return false;
}
packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack);
packs.put(resourcePack.manifest().header().uuid(), resourcePack);
return true;
}
@Override
public boolean unregister(@NonNull UUID uuid) {
return packs.remove(uuid.toString()) != null;
return packs.remove(uuid) != null;
}
}

View file

@ -31,6 +31,7 @@ import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.api.util.ApiVersion;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.event.ExtensionEventBus;
import org.geysermc.geyser.api.extension.Extension;
@ -42,6 +43,7 @@ import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
import org.geysermc.geyser.api.extension.exception.InvalidExtensionException;
import org.geysermc.geyser.extension.event.GeyserExtensionEventBus;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.ThrowingBiConsumer;
import java.io.IOException;
import java.io.Reader;
@ -51,10 +53,12 @@ import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
@RequiredArgsConstructor
@ -155,6 +159,7 @@ public class GeyserExtensionLoader extends ExtensionLoader {
@Override
protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) {
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
try {
if (Files.notExists(extensionsDirectory)) {
Files.createDirectory(extensionsDirectory);
@ -163,55 +168,68 @@ public class GeyserExtensionLoader extends ExtensionLoader {
Map<String, Path> extensions = new LinkedHashMap<>();
Map<String, GeyserExtensionContainer> loadedExtensions = new LinkedHashMap<>();
Pattern[] extensionFilters = this.extensionFilters();
List<Path> extensionPaths = Files.walk(extensionsDirectory).toList();
extensionPaths.forEach(path -> {
if (Files.isDirectory(path)) {
return;
}
Path updateDirectory = extensionsDirectory.resolve("update");
if (Files.isDirectory(updateDirectory)) {
// Step 1: Collect the extension files that currently exist so they can be replaced
Map<String, List<Path>> extensionFiles = new HashMap<>();
this.processExtensionsFolder(extensionsDirectory, (path, description) -> {
extensionFiles.computeIfAbsent(description.id(), k -> new ArrayList<>()).add(path);
}, (path, e) -> {
// this file will throw again when we actually try to load extensions, and it will be handled there
});
for (Pattern filter : extensionFilters) {
if (!filter.matcher(path.getFileName().toString()).matches()) {
return;
}
}
try {
GeyserExtensionDescription description = this.extensionDescription(path);
String name = description.name();
String id = description.id();
if (extensions.containsKey(id) || extensionManager.extension(id) != null) {
GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, path.toString()));
return;
}
// Check whether an extensions' requested api version is compatible
ApiVersion.Compatibility compatibility = GeyserApi.api().geyserApiVersion().supportsRequestedVersion(
description.humanApiVersion(),
description.majorApiVersion(),
description.minorApiVersion()
);
if (compatibility != ApiVersion.Compatibility.COMPATIBLE) {
// Workaround for the switch to the Geyser API version instead of the Base API version in extensions
if (compatibility == ApiVersion.Compatibility.HUMAN_DIFFER && description.humanApiVersion() == 1) {
GeyserImpl.getInstance().getLogger().warning("The extension %s requested the Base API version %s, which is deprecated in favor of specifying the Geyser API version. Please update the extension, or contact its developer."
.formatted(name, description.apiVersion()));
} else {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion()));
return;
// Step 2: Move the updated/new extensions
this.processExtensionsFolder(updateDirectory, (path, description) -> {
// Remove the old extension files with the same ID if it exists
List<Path> oldExtensionFiles = extensionFiles.get(description.id());
if (oldExtensionFiles != null) {
for (Path oldExtensionFile : oldExtensionFiles) {
Files.delete(oldExtensionFile);
}
}
GeyserExtensionContainer container = this.loadExtension(path, description);
extensions.put(id, path);
loadedExtensions.put(id, container);
} catch (Throwable e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", path.getFileName(), path.toAbsolutePath()), e);
// Overwrite the extension with the new jar
Files.move(path, extensionsDirectory.resolve(path.getFileName()), StandardCopyOption.REPLACE_EXISTING);
}, (path, e) -> {
logger.error(GeyserLocale.getLocaleStringLog("geyser.extensions.update.failed", path.getFileName()), e);
});
}
// Step 3: Load the extensions
this.processExtensionsFolder(extensionsDirectory, (path, description) -> {
String name = description.name();
String id = description.id();
if (extensions.containsKey(id) || extensionManager.extension(id) != null) {
logger.warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, path.toString()));
return;
}
// Check whether an extensions' requested api version is compatible
ApiVersion.Compatibility compatibility = GeyserApi.api().geyserApiVersion().supportsRequestedVersion(
description.humanApiVersion(),
description.majorApiVersion(),
description.minorApiVersion()
);
if (compatibility != ApiVersion.Compatibility.COMPATIBLE) {
// Workaround for the switch to the Geyser API version instead of the Base API version in extensions
if (compatibility == ApiVersion.Compatibility.HUMAN_DIFFER && description.humanApiVersion() == 1) {
logger.warning("The extension %s requested the Base API version %s, which is deprecated in favor of specifying the Geyser API version. Please update the extension, or contact its developer."
.formatted(name, description.apiVersion()));
} else {
logger.error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion()));
return;
}
}
GeyserExtensionContainer container = this.loadExtension(path, description);
extensions.put(id, path);
loadedExtensions.put(id, container);
}, (path, e) -> {
logger.error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", path.getFileName(), path.toAbsolutePath()), e);
});
// Step 4: Register the extensions
for (GeyserExtensionContainer container : loadedExtensions.values()) {
this.extensionContainers.put(container.extension(), container);
this.register(container.extension(), extensionManager);
@ -221,6 +239,40 @@ public class GeyserExtensionLoader extends ExtensionLoader {
}
}
/**
* Process extension jars in a folder and call the accept or reject consumer based on the result
*
* @param directory the directory to process
* @param accept the consumer to call when an extension is accepted
* @param reject the consumer to call when an extension is rejected
* @throws IOException if an I/O error occurs
*/
private void processExtensionsFolder(Path directory, ThrowingBiConsumer<Path, GeyserExtensionDescription> accept, BiConsumer<Path, Throwable> reject) throws IOException {
List<Path> extensionPaths = Files.list(directory).toList();
Pattern[] extensionFilters = this.extensionFilters();
extensionPaths.forEach(path -> {
if (Files.isDirectory(path)) {
return;
}
// Only look at files that meet the extension filter
for (Pattern filter : extensionFilters) {
if (!filter.matcher(path.getFileName().toString()).matches()) {
return;
}
}
try {
// Try load the description, so we know it's a valid extension
GeyserExtensionDescription description = this.extensionDescription(path);
accept.acceptThrows(path, description);
} catch (Throwable e) {
reject.accept(path, e);
}
});
}
@Override
protected boolean isEnabled(@NonNull Extension extension) {
return this.extensionContainers.get(extension).enabled;

View file

@ -43,13 +43,14 @@ public class CameraDefinitions {
static {
CAMERA_PRESETS = List.of(
new CameraPreset(CameraPerspective.FIRST_PERSON.id(), "", null, null, null, null, null, null, OptionalBoolean.empty(), null, OptionalBoolean.empty(), null),
new CameraPreset(CameraPerspective.FREE.id(), "", null, null, null, null, null, null, OptionalBoolean.empty(), null, OptionalBoolean.empty(), null),
new CameraPreset(CameraPerspective.THIRD_PERSON.id(), "", null, null, null, null, null, null, OptionalBoolean.empty(), null, OptionalBoolean.empty(), null),
new CameraPreset(CameraPerspective.THIRD_PERSON_FRONT.id(), "", null, null, null, null, null, null, OptionalBoolean.empty(), null, OptionalBoolean.empty(), null),
new CameraPreset("geyser:free_audio", "minecraft:free", null, null, null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.empty(), null, OptionalBoolean.of(false), null),
new CameraPreset("geyser:free_effects", "minecraft:free", null, null, null, null, null, CameraAudioListener.CAMERA, OptionalBoolean.empty(), null, OptionalBoolean.of(true), null),
new CameraPreset("geyser:free_audio_effects", "minecraft:free", null, null, null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.empty(), null, OptionalBoolean.of(true), null));
CameraPreset.builder().identifier(CameraPerspective.FIRST_PERSON.id()).build(),
CameraPreset.builder().identifier(CameraPerspective.FREE.id()).build(),
CameraPreset.builder().identifier(CameraPerspective.THIRD_PERSON.id()).build(),
CameraPreset.builder().identifier(CameraPerspective.THIRD_PERSON_FRONT.id()).build(),
CameraPreset.builder().identifier("geyser:free_audio").parentPreset(CameraPerspective.FREE.id()).listener(CameraAudioListener.PLAYER).playEffect(OptionalBoolean.of(false)).build(),
CameraPreset.builder().identifier("geyser:free_effects").parentPreset(CameraPerspective.FREE.id()).listener(CameraAudioListener.CAMERA).playEffect(OptionalBoolean.of(true)).build(),
CameraPreset.builder().identifier("geyser:free_audio_effects").parentPreset(CameraPerspective.FREE.id()).listener(CameraAudioListener.PLAYER).playEffect(OptionalBoolean.of(true)).build()
);
SimpleDefinitionRegistry.Builder<NamedDefinition> builder = SimpleDefinitionRegistry.builder();
for (int i = 0; i < CAMERA_PRESETS.size(); i++) {

View file

@ -25,15 +25,15 @@
package org.geysermc.geyser.inventory;
import net.kyori.adventure.text.Component;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ItemUtils;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket;
/**
* Used to determine if rename packets should be sent and stores
@ -73,7 +73,7 @@ public class AnvilContainer extends Container {
String correctRename;
newName = rename;
Component originalName = ItemUtils.getCustomName(getInput().getComponents());
Component originalName = getInput().getComponent(DataComponentTypes.CUSTOM_NAME);
String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.locale());
String plainNewName = MessageTranslator.convertToPlainText(rename);

View file

@ -25,12 +25,12 @@
package org.geysermc.geyser.inventory;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.jetbrains.annotations.Range;
/**

View file

@ -25,21 +25,33 @@
package org.geysermc.geyser.inventory;
import lombok.*;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.BundleCache;
import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemStackSlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
import java.util.HashMap;
import java.util.function.Supplier;
@Data
public class GeyserItemStack {
@ -50,19 +62,23 @@ public class GeyserItemStack {
private DataComponents components;
private int netId;
@EqualsAndHashCode.Exclude
private BundleCache.BundleData bundleData;
@Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Item item;
private GeyserItemStack(int javaId, int amount, DataComponents components) {
this(javaId, amount, components, 1);
this(javaId, amount, components, 1, null);
}
private GeyserItemStack(int javaId, int amount, DataComponents components, int netId) {
private GeyserItemStack(int javaId, int amount, DataComponents components, int netId, BundleCache.BundleData bundleData) {
this.javaId = javaId;
this.amount = amount;
this.components = components;
this.netId = netId;
this.bundleData = bundleData;
}
public static @NonNull GeyserItemStack of(int javaId, int amount) {
@ -74,7 +90,21 @@ public class GeyserItemStack {
}
public static @NonNull GeyserItemStack from(@Nullable ItemStack itemStack) {
return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getDataComponents());
return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getDataComponentsPatch());
}
public static @NonNull GeyserItemStack from(@NonNull SlotDisplay slotDisplay) {
if (slotDisplay instanceof EmptySlotDisplay) {
return GeyserItemStack.EMPTY;
}
if (slotDisplay instanceof ItemSlotDisplay itemSlotDisplay) {
return GeyserItemStack.of(itemSlotDisplay.item(), 1);
}
if (slotDisplay instanceof ItemStackSlotDisplay itemStackSlotDisplay) {
return GeyserItemStack.from(itemStackSlotDisplay.itemStack());
}
GeyserImpl.getInstance().getLogger().warning("Unsure how to convert to ItemStack: " + slotDisplay);
return GeyserItemStack.EMPTY;
}
public int getJavaId() {
@ -85,10 +115,31 @@ public class GeyserItemStack {
return isEmpty() ? 0 : amount;
}
/**
* Returns all components of this item - base and additional components sent over the network.
* These are NOT modifiable! To add components, use {@link #getOrCreateComponents()}.
*
* @return the item's base data components and the "additional" ones that may exist.
*/
public @Nullable DataComponents getAllComponents() {
return isEmpty() ? null : asItem().gatherComponents(components);
}
/**
* @return the {@link DataComponents} that aren't the base/default components.
*/
public @Nullable DataComponents getComponents() {
return isEmpty() ? null : components;
}
/**
* @return whether this GeyserItemStack has any additional components on top of
* the base item components.
*/
public boolean hasNonBaseComponents() {
return components != null;
}
@NonNull
public DataComponents getOrCreateComponents() {
if (components == null) {
@ -97,42 +148,56 @@ public class GeyserItemStack {
return components;
}
/**
* Returns the stored data component for a given {@link DataComponentType}, or null.
* <p>
* This method will first check the additional components that may exist,
* and fallback to the item's default (or, "base") components if need be.
* @param type the {@link DataComponentType} to query
* @return the value for said type, or null.
* @param <T> the value's type
*/
@Nullable
public <T> T getComponent(@NonNull DataComponentType<T> type) {
if (components == null) {
return null;
return asItem().getComponent(type);
}
return components.get(type);
T value = components.get(type);
if (value == null) {
return asItem().getComponent(type);
}
return value;
}
public <T extends Boolean> boolean getComponent(@NonNull DataComponentType<T> type, boolean def) {
if (components == null) {
return def;
}
Boolean result = components.get(type);
if (result != null) {
return result;
}
return def;
}
public <T extends Integer> int getComponent(@NonNull DataComponentType<T> type, int def) {
if (components == null) {
return def;
}
Integer result = components.get(type);
if (result != null) {
return result;
}
return def;
public <T> T getComponentElseGet(@NonNull DataComponentType<T> type, Supplier<T> supplier) {
T value = getComponent(type);
return value == null ? supplier.get() : value;
}
public int getNetId() {
return isEmpty() ? 0 : netId;
}
public int getBundleId() {
if (isEmpty()) {
return -1;
}
return bundleData == null ? -1 : bundleData.bundleId();
}
public void mergeBundleData(GeyserSession session, BundleCache.BundleData oldBundleData) {
if (oldBundleData != null && this.bundleData != null) {
// Old bundle; re-use old IDs
this.bundleData.updateNetIds(session, oldBundleData);
} else if (this.bundleData != null) {
// New bundle; allocate new ID
session.getBundleCache().markNewBundle(this.bundleData);
}
}
public void add(int add) {
amount += add;
}
@ -146,6 +211,21 @@ public class GeyserItemStack {
}
public @Nullable ItemStack getItemStack(int newAmount) {
if (isEmpty()) {
return null;
}
// Sync our updated bundle data to server, if applicable
// Not fresh from server? Then we have changes to apply!~
if (bundleData != null && !bundleData.freshFromServer()) {
if (!bundleData.contents().isEmpty()) {
getOrCreateComponents().put(DataComponentTypes.BUNDLE_CONTENTS, bundleData.toComponent());
} else {
if (components != null) {
// Empty list = no component = should delete
components.getDataComponents().remove(DataComponentTypes.BUNDLE_CONTENTS);
}
}
}
return isEmpty() ? null : new ItemStack(javaId, newAmount, components);
}
@ -156,14 +236,25 @@ public class GeyserItemStack {
ItemData.Builder itemData = ItemTranslator.translateToBedrock(session, javaId, amount, components);
itemData.netId(getNetId());
itemData.usingNetId(true);
return itemData.build();
return session.getBundleCache().checkForBundle(this, itemData);
}
public ItemMapping getMapping(GeyserSession session) {
return session.getItemMappings().getMapping(this.javaId);
}
public SlotDisplay asSlotDisplay() {
if (isEmpty()) {
return EmptySlotDisplay.INSTANCE;
}
return new ItemStackSlotDisplay(this.getItemStack());
}
public Item asItem() {
if (isEmpty()) {
return Items.AIR;
}
if (item == null) {
return (item = Registries.JAVA_ITEMS.get().get(javaId));
}
@ -179,6 +270,6 @@ public class GeyserItemStack {
}
public GeyserItemStack copy(int newAmount) {
return isEmpty() ? EMPTY : new GeyserItemStack(javaId, newAmount, components == null ? null : components.clone(), netId);
return isEmpty() ? EMPTY : new GeyserItemStack(javaId, newAmount, components == null ? null : components.clone(), netId, bundleData == null ? null : bundleData.copy());
}
}

View file

@ -36,7 +36,7 @@ import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.jetbrains.annotations.Range;
import java.util.Arrays;
@ -135,22 +135,28 @@ public abstract class Inventory {
// Lodestone caching
if (newItem.asItem() == Items.COMPASS) {
var tracker = newItem.getComponent(DataComponentType.LODESTONE_TRACKER);
var tracker = newItem.getComponent(DataComponentTypes.LODESTONE_TRACKER);
if (tracker != null) {
session.getLodestoneCache().cacheInventoryItem(newItem, tracker);
}
}
}
protected void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) {
public static void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) {
if (!newItem.isEmpty()) {
ItemDefinition oldMapping = ItemTranslator.getBedrockItemDefinition(session, oldItem);
ItemDefinition newMapping = ItemTranslator.getBedrockItemDefinition(session, newItem);
if (oldMapping.equals(newMapping)) {
newItem.setNetId(oldItem.getNetId());
newItem.mergeBundleData(session, oldItem.getBundleData());
} else {
newItem.setNetId(session.getNextItemNetId());
session.getBundleCache().markNewBundle(newItem.getBundleData());
session.getBundleCache().onOldItemDelete(oldItem);
}
} else {
// Empty item means no more bundle if one existed.
session.getBundleCache().onOldItemDelete(oldItem);
}
}
@ -165,4 +171,12 @@ public abstract class Inventory {
public void resetNextStateId() {
nextStateId = -1;
}
/**
* Whether we should be sending a {@link org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket}
* when closing the inventory.
*/
public boolean shouldConfirmContainerClose() {
return true;
}
}

View file

@ -34,30 +34,28 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.java.inventory.JavaOpenBookTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@Getter
public class LecternContainer extends Container {
@Getter @Setter
@Setter
private int currentBedrockPage = 0;
@Getter @Setter
@Setter
private NbtMap blockEntityTag;
@Getter @Setter
@Setter
private Vector3i position;
// Sigh. When the lectern container is created, we don't know (yet) if it's fake or not.
// So... time for a manual check :/
@Getter
private boolean isFakeLectern = false;
private boolean isBookInPlayerInventory = false;
public LecternContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
super(title, id, size, containerType, playerInventory);
}
/**
* When we are using a fake lectern, the Java server expects us to still be in a player inventory.
* We can't use {@link #isUsingRealBlock()} as that may not be determined yet.
* When the Java server asks the client to open a book in their hotbar, we create a fake lectern to show it to the client.
* We can't use the {@link #isUsingRealBlock()} check as we may also be dealing with a real virtual lectern (with its own inventory).
*/
@Override
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
if (isFakeLectern) {
if (isBookInPlayerInventory) {
session.getPlayerInventory().setItem(slot, newItem, session);
} else {
super.setItem(slot, newItem, session);
@ -69,7 +67,12 @@ public class LecternContainer extends Container {
* See {@link LecternContainer#setItem(int, GeyserItemStack, GeyserSession)} as for why this is separate.
*/
public void setFakeLecternBook(GeyserItemStack book, GeyserSession session) {
this.isFakeLectern = true;
this.isBookInPlayerInventory = true;
super.setItem(0, book, session);
}
@Override
public boolean shouldConfirmContainerClose() {
return !isBookInPlayerInventory;
}
}

View file

@ -25,11 +25,11 @@
package org.geysermc.geyser.inventory;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
public class StonecutterContainer extends Container {
/**

View file

@ -31,7 +31,10 @@ import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum Click {
LEFT(ContainerActionType.CLICK_ITEM, ClickItemAction.LEFT_CLICK),
LEFT_BUNDLE(ContainerActionType.CLICK_ITEM, ClickItemAction.LEFT_CLICK),
LEFT_BUNDLE_FROM_CURSOR(ContainerActionType.CLICK_ITEM, ClickItemAction.LEFT_CLICK),
RIGHT(ContainerActionType.CLICK_ITEM, ClickItemAction.RIGHT_CLICK),
RIGHT_BUNDLE(ContainerActionType.CLICK_ITEM, ClickItemAction.RIGHT_CLICK),
LEFT_SHIFT(ContainerActionType.SHIFT_CLICK_ITEM, ShiftClickItemAction.LEFT_CLICK),
DROP_ONE(ContainerActionType.DROP_ITEM, DropItemAction.DROP_FROM_SELECTED),
DROP_ALL(ContainerActionType.DROP_ITEM, DropItemAction.DROP_SELECTED_STACK),

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