Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/extensions

This commit is contained in:
Camotoy 2022-04-07 19:22:46 -04:00
commit 1ebc3fd8f6
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
44 changed files with 611 additions and 432 deletions

View file

@ -1,20 +1,25 @@
val paperVersion = "1.17.1-R0.1-SNAPSHOT" // Needed because we do not support Java 17 yet
val viaVersion = "4.0.0"
val adaptersVersion = "1.4-SNAPSHOT"
val commodoreVersion = "1.13"
dependencies {
api(projects.core)
implementation("org.geysermc.geyser.adapters", "spigot-all", adaptersVersion)
implementation("me.lucko", "commodore", commodoreVersion)
}
platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("com.fasterxml.jackson")
platformRelocate("net.kyori")
platformRelocate("org.objectweb.asm")
platformRelocate("me.lucko.commodore")
// These dependencies are already present on the platform
provided("io.papermc.paper", "paper-api", paperVersion)
provided("io.papermc.paper", "paper-mojangapi", paperVersion)
provided("com.viaversion", "viaversion", viaVersion)
application {
@ -42,5 +47,8 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("io.netty:netty-codec-dns:.*"))
exclude(dependency("io.netty:netty-resolver-dns:.*"))
exclude(dependency("io.netty:netty-resolver-dns-native-macos:.*"))
// Commodore includes Brigadier
exclude(dependency("com.mojang:.*"))
}
}

View file

@ -29,7 +29,9 @@ import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.Constants;
@ -43,6 +45,7 @@ import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport;
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor;
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource;
@ -235,7 +238,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(geyser));
PluginCommand pluginCommand = this.getCommand("geyser");
pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser));
boolean brigadierSupported = CommodoreProvider.isSupported();
geyserLogger.debug("Brigadier supported? " + brigadierSupported);
if (brigadierSupported) {
GeyserBrigadierSupport.loadBrigadier(this, pluginCommand);
}
// Check to ensure the current setup can support the protocol version Geyser uses
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.spigot.command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin;
/**
* Needs to be a separate class so pre-1.13 loads correctly.
*/
public final class GeyserBrigadierSupport {
public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) {
// Enable command completions if supported
// This is beneficial because this is sent over the network and Bedrock can see it
Commodore commodore = CommodoreProvider.getCommodore(plugin);
LiteralArgumentBuilder<?> builder = LiteralArgumentBuilder.literal("geyser");
for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) {
builder.then(LiteralArgumentBuilder.literal(command));
}
commodore.register(pluginCommand, builder);
try {
Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent");
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin);
plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener.");
} catch (ClassNotFoundException e) {
plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener.");
}
}
private GeyserBrigadierSupport() {
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.spigot.command;
import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent;
import com.mojang.brigadier.tree.CommandNode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.Map;
public final class GeyserPaperCommandListener implements Listener {
@EventHandler
@SuppressWarnings("deprecation") // Used to indicate an unstable event
public void onCommandSend(AsyncPlayerSendCommandsEvent<?> event) {
// Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2
// event.hasFiredAsync is never true
if (event.isAsynchronous()) {
CommandNode<?> geyserBrigadier = event.getCommandNode().getChild("geyser");
if (geyserBrigadier != null) {
Player player = event.getPlayer();
boolean isJavaPlayer = isProbablyJavaPlayer(player);
Map<String, Command> commands = GeyserImpl.getInstance().commandManager().getCommands();
Iterator<? extends CommandNode<?>> it = geyserBrigadier.getChildren().iterator();
while (it.hasNext()) {
CommandNode<?> subnode = it.next();
Command command = commands.get(subnode.getName());
if (command != null) {
if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.permission())) {
// Remove this from the node as we don't have permission to use it
it.remove();
}
}
}
}
}
}
/**
* This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that
* chance, though.
*/
private boolean isProbablyJavaPlayer(Player player) {
if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) {
// For sure this is a Bedrock player
return false;
}
if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) {
InetSocketAddress address = player.getAddress();
if (address != null) {
return address.getPort() != 0;
}
}
return true;
}
}

View file

@ -26,6 +26,7 @@
package org.geysermc.geyser.platform.spigot.command;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.geysermc.geyser.GeyserImpl;
@ -35,16 +36,24 @@ import java.lang.reflect.Field;
public class GeyserSpigotCommandManager extends GeyserCommandManager {
private static CommandMap COMMAND_MAP;
private static final CommandMap COMMAND_MAP;
static {
CommandMap commandMap = null;
try {
Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
cmdMapField.setAccessible(true);
COMMAND_MAP = (CommandMap) cmdMapField.get(Bukkit.getServer());
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
// Paper-only
Server.class.getMethod("getCommandMap");
commandMap = Bukkit.getServer().getCommandMap();
} catch (NoSuchMethodException e) {
try {
Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
cmdMapField.setAccessible(true);
commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer());
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
COMMAND_MAP = commandMap;
}
public GeyserSpigotCommandManager(GeyserImpl geyser) {

View file

@ -24,7 +24,7 @@
*/
object Versions {
const val jacksonVersion = "2.12.4"
const val jacksonVersion = "2.13.2"
const val fastutilVersion = "8.5.2"
const val nettyVersion = "4.1.66.Final"
const val guavaVersion = "29.0-jre"

View file

@ -12,7 +12,7 @@ dependencies {
// Jackson JSON and YAML serialization
api("com.fasterxml.jackson.core", "jackson-annotations", Versions.jacksonVersion)
api("com.fasterxml.jackson.core", "jackson-databind", Versions.jacksonVersion)
api("com.fasterxml.jackson.core", "jackson-databind", Versions.jacksonVersion + ".1") // Extra .1 as databind is a slightly different version
api("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", Versions.jacksonVersion)
api("com.google.guava", "guava", Versions.guavaVersion)

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
@ -61,15 +60,21 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Getter @Setter
public class PlayerEntity extends LivingEntity {
public static final float SNEAKING_POSE_HEIGHT = 1.5f;
private GameProfile profile;
private String username;
private boolean playerList = true; // Player is in the player list
private boolean playerList = true; // Player is in the player list
/**
* The textures property from the GameProfile.
*/
@Nullable
private String texturesProperty;
private Vector3i bedPosition;
@ -82,11 +87,12 @@ public class PlayerEntity extends LivingEntity {
*/
private ParrotEntity rightParrot;
public PlayerEntity(GeyserSession session, int entityId, long geyserId, GameProfile gameProfile, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, gameProfile.getId(), EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);
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) {
super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);
profile = gameProfile;
username = gameProfile.getName();
this.username = username;
this.texturesProperty = texturesProperty;
}
@Override

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
@ -71,7 +70,7 @@ public class SessionPlayerEntity extends PlayerEntity {
private int fakeTradeXp;
public SessionPlayerEntity(GeyserSession session) {
super(session, -1, 1, new GameProfile(UUID.randomUUID(), "unknown"), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0);
super(session, -1, 1, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "unknown", null);
valid = true;
}

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
@ -36,6 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket;
import lombok.Getter;
import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;
/**
* A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no
* custom player skulls in Bedrock.
@ -48,8 +49,8 @@ public class SkullPlayerEntity extends PlayerEntity {
@Getter
private final int blockState;
public SkullPlayerEntity(GeyserSession session, long geyserId, GameProfile gameProfile, Vector3f position, float rotation, int blockState) {
super(session, 0, geyserId, gameProfile, position, Vector3f.ZERO, rotation, 0, rotation);
public SkullPlayerEntity(GeyserSession session, long geyserId, Vector3f position, float rotation, int blockState, String texturesProperty) {
super(session, 0, geyserId, UUID.randomUUID(), position, Vector3f.ZERO, rotation, 0, rotation, "", texturesProperty);
this.blockState = blockState;
setPlayerList(false);
}

View file

@ -391,7 +391,7 @@ public final class ClickPlan {
public IntSet getAffectedSlots() {
IntSet affectedSlots = new IntOpenHashSet();
for (ClickAction action : plan) {
if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) {
if (translator.getSlotType(action.slot) != SlotType.OUTPUT && action.slot != Click.OUTSIDE_SLOT) {
affectedSlots.add(action.slot);
if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) {
//TODO won't work if offhand is added

View file

@ -52,7 +52,6 @@ public class StoredItemMappings {
private final int goldIngot;
private final int ironIngot;
private final int lead;
private final ItemMapping lodestoneCompass;
private final ItemMapping milkBucket;
private final int nameTag;
private final ItemMapping powderSnowBucket;
@ -80,7 +79,6 @@ public class StoredItemMappings {
this.goldIngot = load(itemMappings, "gold_ingot").getJavaId();
this.ironIngot = load(itemMappings, "iron_ingot").getJavaId();
this.lead = load(itemMappings, "lead").getJavaId();
this.lodestoneCompass = load(itemMappings, "lodestone_compass");
this.milkBucket = load(itemMappings, "milk_bucket");
this.nameTag = load(itemMappings, "name_tag").getJavaId();
this.powderSnowBucket = load(itemMappings, "powder_snow_bucket");

View file

@ -1,92 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.level.chunk;
import com.nukkitx.network.util.Preconditions;
public class NibbleArray implements Cloneable {
private final byte[] data;
public NibbleArray(int length) {
data = new byte[length / 2];
}
public NibbleArray(byte[] array) {
data = array;
}
public byte get(int index) {
Preconditions.checkElementIndex(index, data.length * 2);
byte val = data[index / 2];
if ((index & 1) == 0) {
return (byte) (val & 0x0f);
} else {
return (byte) ((val & 0xf0) >>> 4);
}
}
public void set(int index, byte value) {
Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15.");
Preconditions.checkElementIndex(index, data.length * 2);
value &= 0xf;
int half = index / 2;
byte previous = data[half];
if ((index & 1) == 0) {
data[half] = (byte) (previous & 0xf0 | value);
} else {
data[half] = (byte) (previous & 0x0f | value << 4);
}
}
public void fill(byte value) {
Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15.");
value &= 0xf;
for (int i = 0; i < data.length; i++) {
data[i] = (byte) ((value << 4) | value);
}
}
public void copyFrom(byte[] bytes) {
Preconditions.checkNotNull(bytes, "bytes");
Preconditions.checkArgument(bytes.length == data.length, "length of provided byte array is %s but expected %s", bytes.length,
data.length);
System.arraycopy(bytes, 0, data, 0, data.length);
}
public void copyFrom(NibbleArray array) {
Preconditions.checkNotNull(array, "array");
copyFrom(array.data);
}
public byte[] getData() {
return data;
}
public NibbleArray copy() {
return new NibbleArray(getData().clone());
}
}

View file

@ -128,7 +128,7 @@ public class ItemRegistryPopulator {
IntList spawnEggs = new IntArrayList();
List<ItemData> carpets = new ObjectArrayList<>();
Int2ObjectMap<ItemMapping> mappings = new Int2ObjectOpenHashMap<>();
List<ItemMapping> mappings = new ObjectArrayList<>();
// Temporary mapping to create stored items
Map<String, ItemMapping> identifierToMapping = new Object2ObjectOpenHashMap<>();
@ -166,6 +166,9 @@ public class ItemRegistryPopulator {
if (identifier.equals("minecraft:debug_stick")) {
// Just shows an empty texture; either way it doesn't exist in the creative menu on Java
continue;
} else if (identifier.equals("minecraft:empty_map") && damage == 2) {
// Bedrock-only as its own item
continue;
}
StartGamePacket.ItemEntry entry = entries.get(identifier);
int id = -1;
@ -240,6 +243,8 @@ public class ItemRegistryPopulator {
if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) {
javaFurnaceMinecartId = itemIndex;
itemIndex++;
// Will be added later
mappings.add(null);
continue;
}
@ -416,7 +421,7 @@ public class ItemRegistryPopulator {
spawnEggs.add(mapping.getBedrockId());
}
mappings.put(itemIndex, mapping);
mappings.add(mapping);
identifierToMapping.put(javaIdentifier, mapping);
itemNames.add(javaIdentifier);
@ -437,16 +442,14 @@ public class ItemRegistryPopulator {
// Add the lodestone compass since it doesn't exist on java but we need it for item conversion
ItemMapping lodestoneEntry = ItemMapping.builder()
.javaIdentifier("minecraft:lodestone_compass")
.javaIdentifier("")
.bedrockIdentifier("minecraft:lodestone_compass")
.javaId(itemIndex)
.javaId(-1)
.bedrockId(lodestoneCompassId)
.bedrockData(0)
.bedrockBlockId(-1)
.stackSize(1)
.build();
mappings.put(itemIndex, lodestoneEntry);
identifierToMapping.put(lodestoneEntry.getJavaIdentifier(), lodestoneEntry);
ComponentItemData furnaceMinecartData = null;
if (usingFurnaceMinecart) {
@ -455,7 +458,7 @@ public class ItemRegistryPopulator {
entries.put("geysermc:furnace_minecart", new StartGamePacket.ItemEntry("geysermc:furnace_minecart", (short) furnaceMinecartId, true));
mappings.put(javaFurnaceMinecartId, ItemMapping.builder()
mappings.set(javaFurnaceMinecartId, ItemMapping.builder()
.javaIdentifier("minecraft:furnace_minecart")
.bedrockIdentifier("geysermc:furnace_minecart")
.javaId(javaFurnaceMinecartId)
@ -506,9 +509,9 @@ public class ItemRegistryPopulator {
}
ItemMappings itemMappings = ItemMappings.builder()
.items(mappings)
.items(mappings.toArray(new ItemMapping[0]))
.creativeItems(creativeItems.toArray(new ItemData[0]))
.itemEntries(new ArrayList<>(entries.values()))
.itemEntries(List.copyOf(entries.values()))
.itemNames(itemNames.toArray(new String[0]))
.storedItems(new StoredItemMappings(identifierToMapping))
.javaOnlyItems(javaOnlyItems)
@ -517,6 +520,7 @@ public class ItemRegistryPopulator {
.spawnEggIds(spawnEggs)
.carpets(carpets)
.furnaceMinecartData(furnaceMinecartData)
.lodestoneCompass(lodestoneEntry)
.build();
Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings);

View file

@ -29,13 +29,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Builder;
import lombok.Value;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.item.StoredItemMappings;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
@ -48,7 +48,12 @@ public class ItemMappings {
Map<String, ItemMapping> cachedJavaMappings = new WeakHashMap<>();
Int2ObjectMap<ItemMapping> items;
ItemMapping[] items;
/**
* A unique exception as this is an item in Bedrock, but not in Java.
*/
ItemMapping lodestoneCompass;
ItemData[] creativeItems;
List<StartGamePacket.ItemEntry> itemEntries;
@ -70,6 +75,7 @@ public class ItemMappings {
* @param itemStack the itemstack
* @return an item entry from the given java edition identifier
*/
@Nonnull
public ItemMapping getMapping(ItemStack itemStack) {
return this.getMapping(itemStack.getId());
}
@ -81,8 +87,9 @@ public class ItemMappings {
* @param javaId the id
* @return an item entry from the given java edition identifier
*/
@Nonnull
public ItemMapping getMapping(int javaId) {
return this.items.get(javaId);
return javaId >= 0 && javaId < this.items.length ? this.items[javaId] : ItemMapping.AIR;
}
/**
@ -94,7 +101,7 @@ public class ItemMappings {
*/
public ItemMapping getMapping(String javaIdentifier) {
return this.cachedJavaMappings.computeIfAbsent(javaIdentifier, key -> {
for (ItemMapping mapping : this.items.values()) {
for (ItemMapping mapping : this.items) {
if (mapping.getJavaIdentifier().equals(key)) {
return mapping;
}
@ -110,11 +117,18 @@ public class ItemMappings {
* @return an item entry from the given item data
*/
public ItemMapping getMapping(ItemData data) {
int id = data.getId();
if (id == 0) {
return ItemMapping.AIR;
} else if (id == lodestoneCompass.getBedrockId()) {
return lodestoneCompass;
}
boolean isBlock = data.getBlockRuntimeId() != 0;
boolean hasDamage = data.getDamage() != 0;
for (ItemMapping mapping : this.items.values()) {
if (mapping.getBedrockId() == data.getId()) {
for (ItemMapping mapping : this.items) {
if (mapping.getBedrockId() == id) {
if (isBlock && !hasDamage) { // Pre-1.16.220 will not use block runtime IDs at all, so we shouldn't check either
if (data.getBlockRuntimeId() != mapping.getBedrockBlockId()) {
continue;
@ -135,7 +149,7 @@ public class ItemMappings {
}
// This will hide the message when the player clicks with an empty hand
if (data.getId() != 0 && data.getDamage() != 0) {
if (id != 0 && data.getDamage() != 0) {
GeyserImpl.getInstance().getLogger().debug("Missing mapping for bedrock item " + data.getId() + ":" + data.getDamage());
}
return ItemMapping.AIR;

View file

@ -26,24 +26,36 @@
package org.geysermc.geyser.session.cache;
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.scoreboard.Scoreboard;
import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession;
import org.geysermc.geyser.session.GeyserSession;
@Getter
public class WorldCache {
public final class WorldCache {
private final GeyserSession session;
@Getter
private final ScoreboardSession scoreboardSession;
@Getter
private Scoreboard scoreboard;
@Getter
@Setter
private Difficulty difficulty = Difficulty.EASY;
/**
* Whether our cooldown changed the title time, and the true title times need to be re-sent.
*/
private boolean titleTimesNeedReset = false;
private int trueTitleFadeInTime;
private int trueTitleStayTime;
private int trueTitleFadeOutTime;
public WorldCache(GeyserSession session) {
this.session = session;
this.scoreboard = new Scoreboard(session);
scoreboardSession = new ScoreboardSession(session);
resetTitleTimes(false);
}
public void removeScoreboard() {
@ -58,4 +70,55 @@ public class WorldCache {
int pps = scoreboardSession.getPacketsPerSecond();
return Math.max(pps, pendingPps);
}
public void markTitleTimesAsIncorrect() {
titleTimesNeedReset = true;
}
/**
* Store the true active title times.
*/
public void setTitleTimes(int fadeInTime, int stayTime, int fadeOutTime) {
trueTitleFadeInTime = fadeInTime;
trueTitleStayTime = stayTime;
trueTitleFadeOutTime = fadeOutTime;
// The translator will sync this for us
titleTimesNeedReset = false;
}
/**
* If needed, ensure that the Bedrock client will use the correct timings for titles.
*/
public void synchronizeCorrectTitleTimes() {
if (titleTimesNeedReset) {
forceSyncCorrectTitleTimes();
}
}
private void forceSyncCorrectTitleTimes() {
SetTitlePacket titlePacket = new SetTitlePacket();
titlePacket.setType(SetTitlePacket.Type.TIMES);
titlePacket.setText("");
titlePacket.setFadeInTime(trueTitleFadeInTime);
titlePacket.setStayTime(trueTitleStayTime);
titlePacket.setFadeOutTime(trueTitleFadeOutTime);
titlePacket.setPlatformOnlineId("");
titlePacket.setXuid("");
session.sendUpstreamPacket(titlePacket);
titleTimesNeedReset = false;
}
/**
* Reset the true active title times to the (Java Edition 1.18.2) defaults.
*/
public void resetTitleTimes(boolean clientSync) {
trueTitleFadeInTime = 10;
trueTitleStayTime = 70;
trueTitleFadeOutTime = 20;
if (clientSync) {
forceSyncCorrectTitleTimes();
}
}
}

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.skin;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@ -106,7 +105,7 @@ public class FakeHeadProvider {
session.getPlayerWithCustomHeads().add(entity.getUuid());
GameProfile.Property texturesProperty = entity.getProfile().getProperty("textures");
String texturesProperty = entity.getTexturesProperty();
SkinProvider.EXECUTOR_SERVICE.execute(() -> {
try {
@ -182,7 +181,7 @@ public class FakeHeadProvider {
@Getter
@Setter
private static class FakeHeadEntry {
private final GameProfile.Property texturesProperty;
private final String texturesProperty;
private final String fakeHeadSkinUrl;
private PlayerEntity entity;
@ -192,18 +191,7 @@ public class FakeHeadProvider {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FakeHeadEntry that = (FakeHeadEntry) o;
return equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl);
}
private boolean equals(GameProfile.Property a, GameProfile.Property b) {
//TODO actually fix this in MCAuthLib
if (a == b) {
return true;
}
if (a == null || b == null) {
return false;
}
return Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getValue(), b.getValue()) && Objects.equals(a.getSignature(), b.getSignature());
return Objects.equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl);
}
@Override

View file

@ -26,7 +26,6 @@
package org.geysermc.geyser.skin;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
@ -54,7 +53,7 @@ public class SkinManager {
* Builds a Bedrock player list entry from our existing, cached Bedrock skin information
*/
public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) {
GameProfileData data = GameProfileData.from(playerEntity.getProfile());
GameProfileData data = GameProfileData.from(playerEntity);
SkinProvider.Cape cape = SkinProvider.getCachedCape(data.capeUrl());
SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex());
@ -65,8 +64,8 @@ public class SkinManager {
return buildEntryManually(
session,
playerEntity.getProfile().getId(),
playerEntity.getProfile().getName(),
playerEntity.getUuid(),
playerEntity.getUsername(),
playerEntity.getGeyserId(),
skin.getTextureUrl(),
skin.getSkinData(),
@ -227,31 +226,31 @@ public class SkinManager {
}
/**
* Generate the GameProfileData from the given GameProfile
* Generate the GameProfileData from the given player entity
*
* @param profile GameProfile to build the GameProfileData from
* @param entity entity to build the GameProfileData from
* @return The built GameProfileData
*/
public static GameProfileData from(GameProfile profile) {
public static GameProfileData from(PlayerEntity entity) {
try {
GameProfile.Property skinProperty = profile.getProperty("textures");
String texturesProperty = entity.getTexturesProperty();
if (skinProperty == null) {
if (texturesProperty == null) {
// Likely offline mode
return loadBedrockOrOfflineSkin(profile);
return loadBedrockOrOfflineSkin(entity);
}
GameProfileData data = loadFromJson(skinProperty.getValue());
GameProfileData data = loadFromJson(texturesProperty);
if (data != null) {
return data;
} else {
return loadBedrockOrOfflineSkin(profile);
return loadBedrockOrOfflineSkin(entity);
}
} catch (IOException exception) {
GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName());
GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername());
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
exception.printStackTrace();
}
return loadBedrockOrOfflineSkin(profile);
return loadBedrockOrOfflineSkin(entity);
}
}
@ -280,14 +279,15 @@ public class SkinManager {
* @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this
* is a Bedrock player.
*/
private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) {
private static GameProfileData loadBedrockOrOfflineSkin(PlayerEntity entity) {
// Fallback to the offline mode of working it out
boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1);
UUID uuid = entity.getUuid();
boolean isAlex = (Math.abs(uuid.hashCode() % 2) == 1);
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl();
if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserImpl.getInstance().getConfig().getRemote().getAuthType() != AuthType.ONLINE) {
GeyserSession session = GeyserImpl.getInstance().connectionByUuid(profile.getId());
GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid);
if (session != null) {
skinUrl = session.getClientData().getSkinId();

View file

@ -27,7 +27,6 @@ package org.geysermc.geyser.skin;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
@ -53,7 +52,6 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Predicate;
@ -157,7 +155,7 @@ public class SkinProvider {
}
public static CompletableFuture<SkinProvider.SkinData> requestSkinData(PlayerEntity entity) {
SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity.getProfile());
SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity);
return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl())
.thenApplyAsync(skinAndCape -> {
@ -546,12 +544,11 @@ public class SkinProvider {
* @param skullOwner the CompoundTag of the skull with no textures
* @return a completable GameProfile with textures included
*/
public static CompletableFuture<GameProfile> requestTexturesFromUsername(CompoundTag skullOwner) {
public static CompletableFuture<String> requestTexturesFromUsername(CompoundTag skullOwner) {
return CompletableFuture.supplyAsync(() -> {
Tag uuidTag = skullOwner.get("Id");
String uuidToString = "";
JsonNode node;
GameProfile gameProfile = new GameProfile(UUID.randomUUID(), "");
boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check
if (!retrieveUuidFromInternet) {
@ -577,15 +574,12 @@ public class SkinProvider {
// Get textures from UUID
node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString);
List<GameProfile.Property> profileProperties = new ArrayList<>();
JsonNode properties = node.get("properties");
if (properties == null) {
GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString);
return null;
}
profileProperties.add(new GameProfile.Property("textures", node.get("properties").get(0).get("value").asText()));
gameProfile.setProperties(profileProperties);
return gameProfile;
return node.get("properties").get(0).get("value").asText();
} catch (Exception e) {
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
e.printStackTrace();

View file

@ -50,7 +50,7 @@ public class SkullSkinManager extends SkinManager {
public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session,
Consumer<SkinProvider.Skin> skinConsumer) {
GameProfileData data = GameProfileData.from(entity.getProfile());
GameProfileData data = GameProfileData.from(entity);
SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true)
.whenCompleteAsync((skin, throwable) -> {

View file

@ -41,13 +41,13 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequ
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.SlotType;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.translator.inventory.item.BannerTranslator;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.nbt.BannerTranslator;
import java.util.Collections;
import java.util.List;

View file

@ -35,28 +35,18 @@ import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@ItemRemapper
public class CompassTranslator extends ItemTranslator {
private final List<ItemMapping> appliedItems;
public CompassTranslator() {
appliedItems = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getItems()
.values()
.stream()
.filter(entry -> entry.getJavaIdentifier().endsWith("compass"))
.collect(Collectors.toList());
}
@Override
protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (isLodestoneCompass(itemStack.getNbt())) {
// NBT will be translated in nbt/LodestoneCompassTranslator if applicable
return super.translateToBedrock(itemStack, mappings.getStoredItems().lodestoneCompass(), mappings);
return super.translateToBedrock(itemStack, mappings.getLodestoneCompass(), mappings);
}
return super.translateToBedrock(itemStack, mapping, mappings);
}
@ -64,7 +54,7 @@ public class CompassTranslator extends ItemTranslator {
@Override
protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) {
if (isLodestoneCompass(nbt)) {
return mappings.getStoredItems().lodestoneCompass();
return mappings.getLodestoneCompass();
}
return super.getItemMapping(javaId, nbt, mappings);
}
@ -89,6 +79,9 @@ public class CompassTranslator extends ItemTranslator {
@Override
public List<ItemMapping> getAppliedItems() {
return appliedItems;
return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getItems())
.filter(entry -> entry.getJavaIdentifier().endsWith("compass"))
.collect(Collectors.toList());
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.inventory.item;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings;
import java.util.Collections;
import java.util.List;
@ItemRemapper
public class FilledMapTranslator extends ItemTranslator {
@Override
protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings);
CompoundTag nbt = itemStack.getNbt();
if (nbt != null && nbt.get("display") instanceof CompoundTag display) {
// Note: damage 5 treasure map, 6 ???
Tag mapColor = display.get("MapColor");
if (mapColor != null && mapColor.getValue() instanceof Number color) {
// Java Edition allows any color; Bedrock only allows some. So let's take what colors we can get
switch (color.intValue()) {
case 3830373 -> builder.damage(3); // Ocean Monument
case 5393476 -> builder.damage(4); // Woodland explorer
}
}
}
return builder;
}
@Override
public List<ItemMapping> getAppliedItems() {
return Collections.singletonList(
Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getMapping("minecraft:filled_map")
);
}
}

View file

@ -47,6 +47,7 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.FileUtils;
import javax.annotation.Nonnull;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;
@ -71,11 +72,11 @@ public abstract class ItemTranslator {
try {
if (NbtItemStackTranslator.class.isAssignableFrom(clazz)) {
NbtItemStackTranslator nbtItemTranslator = (NbtItemStackTranslator) clazz.newInstance();
NbtItemStackTranslator nbtItemTranslator = (NbtItemStackTranslator) clazz.getDeclaredConstructor().newInstance();
loadedNbtItemTranslators.put(nbtItemTranslator, priority);
continue;
}
ItemTranslator itemStackTranslator = (ItemTranslator) clazz.newInstance();
ItemTranslator itemStackTranslator = (ItemTranslator) clazz.getDeclaredConstructor().newInstance();
List<ItemMapping> appliedItems = itemStackTranslator.getAppliedItems();
for (ItemMapping item : appliedItems) {
ItemTranslator registered = ITEM_STACK_TRANSLATORS.get(item.getJavaId());
@ -87,7 +88,7 @@ public abstract class ItemTranslator {
}
ITEM_STACK_TRANSLATORS.put(item.getJavaId(), itemStackTranslator);
}
} catch (InstantiationException | IllegalAccessException e) {
} catch (InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
GeyserImpl.getInstance().getLogger().error("Could not instantiate annotated item translator " + clazz.getCanonicalName());
}
}
@ -302,13 +303,16 @@ public abstract class ItemTranslator {
return new ItemStack(mapping.getJavaId(), itemData.getCount(), this.translateToJavaNBT("", itemData.getTag()));
}
/**
* Used for initialization only and only called once.
*/
public abstract List<ItemMapping> getAppliedItems();
protected ItemMapping getItemMapping(int javaId, CompoundTag nbt, ItemMappings mappings) {
return mappings.getMapping(javaId);
}
public NbtMap translateNbtToBedrock(CompoundTag tag) {
protected NbtMap translateNbtToBedrock(CompoundTag tag) {
NbtMapBuilder builder = NbtMap.builder();
if (tag.getValue() != null && !tag.getValue().isEmpty()) {
for (String str : tag.getValue().keySet()) {
@ -387,7 +391,7 @@ public abstract class ItemTranslator {
return null;
}
public CompoundTag translateToJavaNBT(String name, NbtMap tag) {
private CompoundTag translateToJavaNBT(String name, NbtMap tag) {
CompoundTag javaTag = new CompoundTag(name);
Map<String, Tag> javaValue = javaTag.getValue();
if (tag != null && !tag.isEmpty()) {

View file

@ -29,12 +29,12 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
public class NbtItemStackTranslator {
public abstract class NbtItemStackTranslator {
/**
* Translate the item NBT to Bedrock
* @param session the client's current session
* @param itemTag the item's CompoundTag
* @param itemTag the item's CompoundTag (cloned from Geyser's cached copy)
* @param mapping Geyser's item mapping
*/
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) {

View file

@ -36,23 +36,13 @@ import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@ItemRemapper
public class PotionTranslator extends ItemTranslator {
private final List<ItemMapping> appliedItems;
public PotionTranslator() {
appliedItems = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getItems()
.values()
.stream()
.filter(entry -> entry.getJavaIdentifier().endsWith("potion"))
.collect(Collectors.toList());
}
@Override
protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, mapping, mappings);
@ -84,6 +74,9 @@ public class PotionTranslator extends ItemTranslator {
@Override
public List<ItemMapping> getAppliedItems() {
return appliedItems;
return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getItems())
.filter(entry -> entry.getJavaIdentifier().endsWith("potion"))
.collect(Collectors.toList());
}
}

View file

@ -36,28 +36,16 @@ import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@ItemRemapper
public class TippedArrowTranslator extends ItemTranslator {
private final List<ItemMapping> appliedItems;
private static final int TIPPED_ARROW_JAVA_ID = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getMapping("minecraft:tipped_arrow")
.getJavaId();
public TippedArrowTranslator() {
appliedItems = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getItems()
.values()
.stream()
.filter(entry -> entry.getJavaIdentifier().contains("arrow")
&& !entry.getJavaIdentifier().contains("spectral"))
.collect(Collectors.toList());
}
@Override
protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (!mapping.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) {
@ -93,6 +81,10 @@ public class TippedArrowTranslator extends ItemTranslator {
@Override
public List<ItemMapping> getAppliedItems() {
return appliedItems;
return Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getItems())
.filter(entry -> entry.getJavaIdentifier().contains("arrow")
&& !entry.getJavaIdentifier().contains("spectral"))
.collect(Collectors.toList());
}
}

View file

@ -23,29 +23,25 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.inventory.item;
package org.geysermc.geyser.translator.inventory.item.nbt;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.*;
import com.nukkitx.nbt.NbtList;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.ItemRemapper;
import org.geysermc.geyser.translator.inventory.item.NbtItemStackTranslator;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
@ItemRemapper
public class BannerTranslator extends ItemTranslator {
public class BannerTranslator extends NbtItemStackTranslator {
/**
* Holds what a Java ominous banner pattern looks like.
*
@ -80,10 +76,8 @@ public class BannerTranslator extends ItemTranslator {
}
public BannerTranslator() {
appliedItems = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getItems()
.values()
.stream()
appliedItems = Arrays.stream(Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
.getItems())
.filter(entry -> entry.getJavaIdentifier().endsWith("banner"))
.collect(Collectors.toList());
}
@ -117,21 +111,6 @@ public class BannerTranslator extends ItemTranslator {
.build();
}
/**
* Convert a list of patterns from Bedrock nbt to Java nbt
*
* @param patterns The patterns to convert
* @return The new converted patterns
*/
public static ListTag convertBannerPattern(List<NbtMap> patterns) {
List<Tag> tagsList = new ArrayList<>();
for (NbtMap patternTag : patterns) {
tagsList.add(getJavaBannerPattern(patternTag));
}
return new ListTag("Patterns", tagsList);
}
/**
* Convert the Bedrock edition banner pattern nbt to Java edition
*
@ -146,62 +125,54 @@ public class BannerTranslator extends ItemTranslator {
return new CompoundTag("", tags);
}
@Override
protected ItemData.Builder translateToBedrock(ItemStack itemStack, ItemMapping mapping, ItemMappings mappings) {
if (itemStack.getNbt() == null) {
return super.translateToBedrock(itemStack, mapping, mappings);
/**
* Convert a list of patterns from Java nbt to Bedrock nbt, or vice versa (we just need to invert the color)
*
* @param patterns The patterns to convert
*/
private void invertBannerColors(ListTag patterns) {
for (Tag patternTag : patterns.getValue()) {
IntTag color = ((CompoundTag) patternTag).get("Color");
color.setValue(15 - color.getValue());
}
ItemData.Builder builder = super.translateToBedrock(itemStack, mapping, mappings);
CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag");
if (blockEntityTag != null && blockEntityTag.get("Patterns") instanceof ListTag patterns) {
NbtMapBuilder nbtBuilder = builder.build().getTag().toBuilder(); //TODO fix ugly hack
if (patterns.equals(OMINOUS_BANNER_PATTERN)) {
// Remove the current patterns and set the ominous banner type
nbtBuilder.remove("Patterns");
nbtBuilder.putInt("Type", 1);
} else {
nbtBuilder.put("Patterns", convertBannerPattern(patterns));
}
builder.tag(nbtBuilder.build());
}
return builder;
}
@Override
public ItemStack translateToJava(ItemData itemData, ItemMapping mapping, ItemMappings mappings) {
if (itemData.getTag() == null) {
return super.translateToJava(itemData, mapping, mappings);
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) {
CompoundTag blockEntityTag = itemTag.get("BlockEntityTag");
if (blockEntityTag != null && blockEntityTag.get("Patterns") instanceof ListTag patterns) {
if (patterns.equals(OMINOUS_BANNER_PATTERN)) {
// Remove the current patterns and set the ominous banner type
itemTag.put(new IntTag("Type", 1));
} else {
invertBannerColors(patterns);
itemTag.put(patterns);
}
itemTag.remove("BlockEntityTag");
}
}
ItemStack itemStack = super.translateToJava(itemData, mapping, mappings);
NbtMap nbtTag = itemData.getTag();
if (nbtTag.containsKey("Type", NbtType.INT) && nbtTag.getInt("Type") == 1) {
@Override
public void translateToJava(CompoundTag itemTag, ItemMapping mapping) {
if (itemTag.get("Type") instanceof IntTag type && type.getValue() == 1) {
// Ominous banner pattern
itemStack.getNbt().remove("Type");
itemTag.remove("Type");
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag");
blockEntityTag.put(OMINOUS_BANNER_PATTERN);
itemStack.getNbt().put(blockEntityTag);
} else if (nbtTag.containsKey("Patterns", NbtType.LIST)) {
List<NbtMap> patterns = nbtTag.getList("Patterns", NbtType.COMPOUND);
itemTag.put(blockEntityTag);
} else if (itemTag.get("Patterns") instanceof ListTag patterns) {
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag");
blockEntityTag.put(convertBannerPattern(patterns));
invertBannerColors(patterns);
blockEntityTag.put(patterns);
itemStack.getNbt().put(blockEntityTag);
itemStack.getNbt().remove("Patterns"); // Remove the old Bedrock patterns list
itemTag.put(blockEntityTag);
itemTag.remove("Patterns"); // Remove the old Bedrock patterns list
}
return itemStack;
}
@Override
public List<ItemMapping> getAppliedItems() {
return appliedItems;
public boolean acceptItem(ItemMapping mapping) {
return appliedItems.contains(mapping);
}
}

View file

@ -121,10 +121,6 @@ public class EnchantmentTranslator extends NbtItemStackTranslator {
private CompoundTag remapEnchantment(CompoundTag tag) {
Tag javaEnchLvl = tag.get("lvl");
if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag))
return null;
Tag javaEnchId = tag.get("id");
if (!(javaEnchId instanceof StringTag))
return null;
@ -135,9 +131,12 @@ public class EnchantmentTranslator extends NbtItemStackTranslator {
return null;
}
Tag javaEnchLvl = tag.get("lvl");
CompoundTag bedrockTag = new CompoundTag("");
bedrockTag.put(new ShortTag("id", (short) enchantment.ordinal()));
bedrockTag.put(new ShortTag("lvl", ((Number) javaEnchLvl.getValue()).shortValue()));
// If the tag cannot parse, Java Edition 1.18.2 sets to 0
bedrockTag.put(new ShortTag("lvl", javaEnchLvl != null && javaEnchLvl.getValue() instanceof Number lvl ? lvl.shortValue() : (short) 0));
return bedrockTag;
}

View file

@ -28,9 +28,10 @@ package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.geyser.translator.inventory.item.BannerTranslator;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.translator.inventory.item.nbt.BannerTranslator;
@BlockEntity(type = BlockEntityType.BANNER)
public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@ -45,8 +46,7 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement
return;
}
if (tag.contains("Patterns")) {
ListTag patterns = tag.get("Patterns");
if (tag.get("Patterns") instanceof ListTag patterns) {
if (patterns.equals(BannerTranslator.OMINOUS_BANNER_PATTERN)) {
// This is an ominous banner; don't try to translate the raw patterns (it doesn't translate correctly)
// and tell the Bedrock client that this is an ominous banner
@ -56,8 +56,9 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement
}
}
if (tag.contains("CustomName")) {
builder.put("CustomName", tag.get("CustomName").getValue());
Tag customName = tag.get("CustomName");
if (customName != null) {
builder.put("CustomName", customName.getValue());
}
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.BlockEventPacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.level.block.BlockStateValues;
/**
* Does not implement BlockEntityTranslator because it's only a block entity in Bedrock
*/
public class NoteblockBlockEntityTranslator {
public static void translate(GeyserSession session, Position position) {
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, position);
BlockEventPacket blockEventPacket = new BlockEventPacket();
blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ()));
blockEventPacket.setEventType(0);
blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState));
session.sendUpstreamPacket(blockEventPacket);
}
}

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
@ -35,15 +34,12 @@ import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinProvider;
import org.geysermc.geyser.skin.SkullSkinManager;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@ -62,7 +58,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
builder.put("SkullType", skullVariant);
}
public static CompletableFuture<GameProfile> getProfile(CompoundTag tag) {
private static CompletableFuture<String> getTextures(CompoundTag tag) {
CompoundTag owner = tag.get("SkullOwner");
if (owner != null) {
CompoundTag properties = owner.get("Properties");
@ -73,13 +69,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
ListTag textures = properties.get("textures");
LinkedHashMap<?,?> tag1 = (LinkedHashMap<?,?>) textures.get(0).getValue();
StringTag texture = (StringTag) tag1.get("Value");
List<GameProfile.Property> profileProperties = new ArrayList<>();
GameProfile gameProfile = new GameProfile(UUID.randomUUID(), "");
profileProperties.add(new GameProfile.Property("textures", texture.getValue()));
gameProfile.setProperties(profileProperties);
return CompletableFuture.completedFuture(gameProfile);
return CompletableFuture.completedFuture(texture.getValue());
}
return CompletableFuture.completedFuture(null);
}
@ -108,21 +98,21 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
Vector3i blockPosition = Vector3i.from(posX, posY, posZ);
Vector3f entityPosition = Vector3f.from(x, y, z);
getProfile(tag).whenComplete((gameProfile, throwable) -> {
if (gameProfile == null) {
getTextures(tag).whenComplete((texturesProperty, throwable) -> {
if (texturesProperty == null) {
session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag);
return;
}
if (session.getEventLoop().inEventLoop()) {
spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState);
spawnPlayer(session, texturesProperty, blockPosition, entityPosition, rotation, blockState);
} else {
session.executeInEventLoop(() -> spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState));
session.executeInEventLoop(() -> spawnPlayer(session, texturesProperty, blockPosition, entityPosition, rotation, blockState));
}
});
}
private static void spawnPlayer(GeyserSession session, GameProfile profile, Vector3i blockPosition,
private static void spawnPlayer(GeyserSession session, String texturesProperty, Vector3i blockPosition,
Vector3f entityPosition, float rotation, int blockState) {
long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet();
@ -132,7 +122,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
existingSkull.despawnEntity(blockPosition);
}
SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, profile, entityPosition, rotation, blockState);
SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, entityPosition, rotation, blockState, texturesProperty);
// Cache entity
session.getSkullCache().put(blockPosition, player);

View file

@ -139,7 +139,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
case ITEM_USE:
switch (packet.getActionType()) {
case 0 -> {
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
final Vector3i packetBlockPosition = packet.getBlockPosition();
Vector3i blockPos = BlockUtils.getBlockPosition(packetBlockPosition, packet.getBlockFace());
if (session.getGeyser().getConfig().isDisableBedrockScaffolding()) {
float yaw = session.getPlayerEntity().getYaw();
@ -159,8 +160,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
// Check to make sure the client isn't spamming interaction
// Based on Nukkit 1.0, with changes to ensure holding down still works
boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 &&
packet.getBlockPosition().distanceSquared(session.getLastInteractionBlockPosition()) < 0.00001;
session.setLastInteractionBlockPosition(packet.getBlockPosition());
packetBlockPosition.distanceSquared(session.getLastInteractionBlockPosition()) < 0.00001;
session.setLastInteractionBlockPosition(packetBlockPosition);
session.setLastInteractionPlayerPosition(session.getPlayerEntity().getPosition());
if (hasAlreadyClicked) {
break;
@ -204,22 +205,48 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
playerPosition = playerPosition.sub(0, (EntityDefinitions.PLAYER.offset() - 0.2f), 0);
} // else, we don't have to modify the position
float diffX = playerPosition.getX() - packet.getBlockPosition().getX();
float diffY = playerPosition.getY() - packet.getBlockPosition().getY();
float diffZ = playerPosition.getZ() - packet.getBlockPosition().getZ();
boolean creative = session.getGameMode() == GameMode.CREATIVE;
float diffX = playerPosition.getX() - packetBlockPosition.getX();
float diffY = playerPosition.getY() - packetBlockPosition.getY();
float diffZ = playerPosition.getZ() - packetBlockPosition.getZ();
if (((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) >
(session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
(creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
restoreCorrectBlock(session, blockPos, packet);
return;
}
double clickPositionFullX = (double) packetBlockPosition.getX() + (double) packet.getClickPosition().getX();
double clickPositionFullY = (double) packetBlockPosition.getY() + (double) packet.getClickPosition().getY();
double clickPositionFullZ = (double) packetBlockPosition.getZ() + (double) packet.getClickPosition().getZ();
// More recent Paper check - https://github.com/PaperMC/Paper/blob/87e11bf7fdf48ecdf3e1cae383c368b9b61d7df9/patches/server/0470-Move-range-check-for-block-placing-up.patch
double clickDiffX = playerPosition.getX() - clickPositionFullX;
double clickDiffY = playerPosition.getY() - clickPositionFullY;
double clickDiffZ = playerPosition.getZ() - clickPositionFullZ;
if (((clickDiffX * clickDiffX) + (clickDiffY * clickDiffY) + (clickDiffZ * clickDiffZ)) >
(creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
restoreCorrectBlock(session, blockPos, packet);
return;
}
Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f);
// Vanilla check
if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0)
.distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) {
.distanceSquared(blockCenter) < MAXIMUM_BLOCK_PLACING_DISTANCE)) {
// The client thinks that its blocks have been successfully placed. Restore the server's blocks instead.
restoreCorrectBlock(session, blockPos, packet);
return;
}
// More recent vanilla check (as of 1.18.2)
double clickDistanceX = clickPositionFullX - blockCenter.getX();
double clickDistanceY = clickPositionFullY - blockCenter.getY();
double clickDistanceZ = clickPositionFullZ - blockCenter.getZ();
if (!(Math.abs(clickDistanceX) < 1.0000001D && Math.abs(clickDistanceY) < 1.0000001D && Math.abs(clickDistanceZ) < 1.0000001D)) {
restoreCorrectBlock(session, blockPos, packet);
return;
}
/*
Block place checks end - client is good to go
*/
@ -284,7 +311,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
}
}
}
ItemMapping handItem = mappings.getMapping(packet.getItemInHand());
ItemMapping handItem = session.getPlayerInventory().getItemInHand().getMapping(session);
if (handItem.isBlock()) {
session.setLastBlockPlacePosition(blockPos);
session.setLastBlockPlacedId(handItem.getJavaIdentifier());

View file

@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.Server
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -42,8 +43,9 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
@Override
public void translate(GeyserSession session, MobEquipmentPacket packet) {
if (!session.isSpawned() || packet.getHotbarSlot() > 8 ||
packet.getContainerId() != ContainerId.INVENTORY || session.getPlayerInventory().getHeldItemSlot() == packet.getHotbarSlot()) {
int newSlot = packet.getHotbarSlot();
if (!session.isSpawned() || newSlot > 8 || packet.getContainerId() != ContainerId.INVENTORY
|| session.getPlayerInventory().getHeldItemSlot() == newSlot) {
// For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention
return;
}
@ -51,12 +53,15 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
// Send book update before switching hotbar slot
session.getBookEditCache().checkForSend();
session.getPlayerInventory().setHeldItemSlot(packet.getHotbarSlot());
GeyserItemStack oldItem = session.getPlayerInventory().getItemInHand();
session.getPlayerInventory().setHeldItemSlot(newSlot);
ServerboundSetCarriedItemPacket setCarriedItemPacket = new ServerboundSetCarriedItemPacket(packet.getHotbarSlot());
ServerboundSetCarriedItemPacket setCarriedItemPacket = new ServerboundSetCarriedItemPacket(newSlot);
session.sendDownstreamPacket(setCarriedItemPacket);
if (session.isSneaking() && session.getPlayerInventory().getItemInHand().getJavaId() == session.getItemMappings().getStoredItems().shield().getJavaId()) {
GeyserItemStack newItem = session.getPlayerInventory().getItemInHand();
if (session.isSneaking() && newItem.getJavaId() == session.getItemMappings().getStoredItems().shield().getJavaId()) {
// Activate shield since we are already sneaking
// (No need to send a release item packet - Java doesn't do this when swapping items)
// Required to do it a tick later or else it doesn't register
@ -64,8 +69,10 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
50, TimeUnit.MILLISECONDS);
}
// Java sends a cooldown indicator whenever you switch an item
CooldownUtils.sendCooldown(session);
if (oldItem.getJavaId() != newItem.getJavaId()) {
// Java sends a cooldown indicator whenever you switch to a new item type
CooldownUtils.sendCooldown(session);
}
// Update the interactive tag, if an entity is present
if (session.getMouseoverEntity() != null) {

View file

@ -172,8 +172,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
// Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore
// We can get the correct order for button pressing
data.getValue().sort(Comparator.comparing((stoneCuttingRecipeData ->
session.getItemMappings().getItems()
.getOrDefault(stoneCuttingRecipeData.getResult().getId(), ItemMapping.AIR)
session.getItemMappings().getMapping(stoneCuttingRecipeData.getResult())
.getJavaIdentifier())));
// Now that it's sorted, let's translate these recipes
@ -229,7 +228,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
GroupedItem groupedItem = entry.getKey();
int idCount = 0;
//not optimal
for (ItemMapping mapping : session.getItemMappings().getItems().values()) {
for (ItemMapping mapping : session.getItemMappings().getItems()) {
if (mapping.getBedrockId() == groupedItem.id) {
idCount++;
}

View file

@ -25,6 +25,7 @@
package org.geysermc.geyser.translator.protocol.java.entity.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.PlayerListEntry;
import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoPacket;
@ -50,31 +51,38 @@ public class JavaPlayerInfoTranslator extends PacketTranslator<ClientboundPlayer
for (PlayerListEntry entry : packet.getEntries()) {
switch (packet.getAction()) {
case ADD_PLAYER -> {
GameProfile profile = entry.getProfile();
PlayerEntity playerEntity;
boolean self = entry.getProfile().getId().equals(session.getPlayerEntity().getUuid());
boolean self = profile.getId().equals(session.getPlayerEntity().getUuid());
if (self) {
// Entity is ourself
playerEntity = session.getPlayerEntity();
} else {
playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId());
playerEntity = session.getEntityCache().getPlayerEntity(profile.getId());
}
GameProfile.Property textures = profile.getProperty("textures");
String texturesProperty = textures == null ? null : textures.getValue();
if (playerEntity == null) {
// It's a new player
playerEntity = new PlayerEntity(
session,
-1,
session.getEntityCache().getNextEntityId().incrementAndGet(),
entry.getProfile(),
profile.getId(),
Vector3f.ZERO,
Vector3f.ZERO,
0, 0, 0
0, 0, 0,
profile.getName(),
texturesProperty
);
session.getEntityCache().addPlayerEntity(playerEntity);
} else {
playerEntity.setProfile(entry.getProfile());
playerEntity.setUsername(profile.getName());
playerEntity.setTexturesProperty(texturesProperty);
}
playerEntity.setPlayerList(true);

View file

@ -48,7 +48,9 @@ public class JavaAddPlayerTranslator extends PacketTranslator<ClientboundAddPlay
PlayerEntity entity;
if (packet.getUuid().equals(session.getPlayerEntity().getUuid())) {
// Server is sending a fake version of the current player
entity = new PlayerEntity(session, packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), session.getPlayerEntity().getProfile(), position, Vector3f.ZERO, yaw, pitch, headYaw);
entity = new PlayerEntity(session, packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
session.getPlayerEntity().getUuid(), position, Vector3f.ZERO, yaw, pitch, headYaw, session.getPlayerEntity().getUsername(),
session.getPlayerEntity().getTexturesProperty());
} else {
entity = session.getEntityCache().getPlayerEntity(packet.getUuid());
if (entity == null) {

View file

@ -35,14 +35,13 @@ import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.BlockEventPacket;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.translator.level.block.entity.NoteblockBlockEntityTranslator;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
import org.geysermc.geyser.level.physics.Direction;
@Translator(packet = ClientboundBlockEventPacket.class)
public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockEventPacket> {
@ -50,8 +49,9 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
@Override
public void translate(GeyserSession session, ClientboundBlockEventPacket packet) {
BlockEventPacket blockEventPacket = new BlockEventPacket();
blockEventPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(),
packet.getPosition().getY(), packet.getPosition().getZ()));
Position position = packet.getPosition();
Vector3i vector = Vector3i.from(position.getX(), position.getY(), position.getZ());
blockEventPacket.setBlockPosition(vector);
if (packet.getValue() instanceof ChestValue value) {
blockEventPacket.setEventType(1);
blockEventPacket.setEventData(value.getViewers() > 0 ? 1 : 0);
@ -60,11 +60,12 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
blockEventPacket.setEventType(1);
session.sendUpstreamPacket(blockEventPacket);
} else if (packet.getValue() instanceof NoteBlockValue) {
NoteblockBlockEntityTranslator.translate(session, packet.getPosition());
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, position);
blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState));
session.sendUpstreamPacket(blockEventPacket);
} else if (packet.getValue() instanceof PistonValue pistonValue) {
PistonValueType action = (PistonValueType) packet.getType();
Direction direction = Direction.fromPistonValue(pistonValue);
Vector3i position = Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ());
PistonCache pistonCache = session.getPistonCache();
if (session.getGeyser().getPlatformType() == PlatformType.SPIGOT) {
@ -77,20 +78,20 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
return;
}
if (action != PistonValueType.CANCELLED_MID_PUSH) {
Vector3i blockInFrontPos = position.add(direction.getUnitVector());
Vector3i blockInFrontPos = vector.add(direction.getUnitVector());
int blockInFront = session.getGeyser().getWorldManager().getBlockAt(session, blockInFrontPos);
if (blockInFront != BlockStateValues.JAVA_AIR_ID) {
// Piston pulled something
return;
}
}
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> new PistonBlockEntity(session, pos, direction, true, true));
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(vector, pos -> new PistonBlockEntity(session, pos, direction, true, true));
if (blockEntity.getAction() != action) {
blockEntity.setAction(action, Object2IntMaps.emptyMap());
}
}
} else {
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> {
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(vector, pos -> {
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, position);
boolean sticky = BlockStateValues.isStickyPiston(blockId);
boolean extended = action != PistonValueType.PUSHING;
@ -106,10 +107,8 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
session.sendUpstreamPacket(blockEventPacket);
} else if (packet.getValue() instanceof GenericBlockValue bellValue && packet.getBlockId() == BlockStateValues.JAVA_BELL_ID) {
// Bells - needed to show ring from other players
Position position = packet.getPosition();
BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket();
blockEntityPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ()));
blockEntityPacket.setBlockPosition(vector);
NbtMapBuilder builder = NbtMap.builder();
builder.putInt("x", position.getX());

View file

@ -37,11 +37,14 @@ public class JavaClearTitlesTranslator extends PacketTranslator<ClientboundClear
@Override
public void translate(GeyserSession session, ClientboundClearTitlesPacket packet) {
SetTitlePacket titlePacket = new SetTitlePacket();
// TODO handle packet.isResetTimes()
titlePacket.setType(SetTitlePacket.Type.CLEAR);
titlePacket.setText("");
titlePacket.setXuid("");
titlePacket.setPlatformOnlineId("");
session.sendUpstreamPacket(titlePacket);
if (packet.isResetTimes()) {
session.getWorldCache().resetTitleTimes(true);
}
}
}

View file

@ -38,6 +38,8 @@ public class JavaSetTitleTextTranslator extends PacketTranslator<ClientboundSetT
@Override
public void translate(GeyserSession session, ClientboundSetTitleTextPacket packet) {
session.getWorldCache().synchronizeCorrectTitleTimes();
String text;
if (packet.getText() == null || Component.empty().equals(packet.getText())) { // This can happen, see https://github.com/KyoriPowered/adventure/issues/447
text = " ";

View file

@ -36,12 +36,17 @@ public class JavaSetTitlesAnimationTranslator extends PacketTranslator<Clientbou
@Override
public void translate(GeyserSession session, ClientboundSetTitlesAnimationPacket packet) {
int fadeInTime = packet.getFadeIn();
int stayTime = packet.getStay();
int fadeOutTime = packet.getFadeOut();
session.getWorldCache().setTitleTimes(fadeInTime, stayTime, fadeOutTime);
SetTitlePacket titlePacket = new SetTitlePacket();
titlePacket.setType(SetTitlePacket.Type.TIMES);
titlePacket.setText("");
titlePacket.setFadeInTime(packet.getFadeIn());
titlePacket.setFadeOutTime(packet.getFadeOut());
titlePacket.setStayTime(packet.getStay());
titlePacket.setFadeInTime(fadeInTime);
titlePacket.setFadeOutTime(fadeOutTime);
titlePacket.setStayTime(stayTime);
titlePacket.setXuid("");
titlePacket.setPlatformOnlineId("");
session.sendUpstreamPacket(titlePacket);

View file

@ -57,8 +57,19 @@ public class CooldownUtils {
if (sessionPreference == CooldownType.DISABLED) return;
if (session.getAttackSpeed() == 0.0 || session.getAttackSpeed() > 20) return; // 0.0 usually happens on login and causes issues with visuals; anything above 20 means a plugin like OldCombatMechanics is being used
// Needs to be sent or no subtitle packet is recognized by the client
// Set the times to stay a bit with no fade in nor out
SetTitlePacket titlePacket = new SetTitlePacket();
titlePacket.setType(SetTitlePacket.Type.TIMES);
titlePacket.setStayTime(1000);
titlePacket.setText("");
titlePacket.setXuid("");
titlePacket.setPlatformOnlineId("");
session.sendUpstreamPacket(titlePacket);
session.getWorldCache().markTitleTimesAsIncorrect();
// Needs to be sent or no subtitle packet is recognized by the client
titlePacket = new SetTitlePacket();
titlePacket.setType(SetTitlePacket.Type.TITLE);
titlePacket.setText(" ");
titlePacket.setXuid("");
@ -85,9 +96,6 @@ public class CooldownUtils {
titlePacket.setType(SetTitlePacket.Type.SUBTITLE);
}
titlePacket.setText(getTitle(session));
titlePacket.setFadeInTime(0);
titlePacket.setFadeOutTime(5);
titlePacket.setStayTime(2);
titlePacket.setXuid("");
titlePacket.setPlatformOnlineId("");
session.sendUpstreamPacket(titlePacket);
@ -96,11 +104,7 @@ public class CooldownUtils {
computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50
} else {
SetTitlePacket removeTitlePacket = new SetTitlePacket();
if (sessionPreference == CooldownType.ACTIONBAR) {
removeTitlePacket.setType(SetTitlePacket.Type.ACTIONBAR);
} else {
removeTitlePacket.setType(SetTitlePacket.Type.SUBTITLE);
}
removeTitlePacket.setType(SetTitlePacket.Type.CLEAR);
removeTitlePacket.setText(" ");
removeTitlePacket.setXuid("");
removeTitlePacket.setPlatformOnlineId("");

View file

@ -29,21 +29,27 @@ import com.github.steveice10.opennbt.tag.builtin.*;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.geysermc.geyser.session.GeyserSession;
import javax.annotation.Nullable;
public class ItemUtils {
private static Int2IntMap DYE_COLORS = null;
public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) {
ListTag enchantments = (itemNBTData == null ? null : itemNBTData.get("Enchantments"));
public static int getEnchantmentLevel(@Nullable CompoundTag itemNBTData, String enchantmentId) {
if (itemNBTData == null) {
return 0;
}
ListTag enchantments = itemNBTData.get("Enchantments");
if (enchantments != null) {
int enchantmentLevel = 0;
for (Tag tag : enchantments) {
CompoundTag enchantment = (CompoundTag) tag;
StringTag enchantId = enchantment.get("id");
if (enchantId.getValue().equals(enchantmentId)) {
enchantmentLevel = (int) ((ShortTag) enchantment.get("lvl")).getValue();
Tag lvl = enchantment.get("lvl");
if (lvl != null && lvl.getValue() instanceof Number number) {
return number.intValue();
}
}
}
return enchantmentLevel;
}
return 0;
}

View file

@ -62,7 +62,7 @@ public class SettingsUtils {
// Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
if (session.getPreferencesCache().isAllowShowCoordinates()) {
builder.toggle("geyser.settings.option.coordinates", session.getPreferencesCache().isPrefersShowCoordinates());
builder.toggle("%createWorldScreen.showCoordinates", session.getPreferencesCache().isPrefersShowCoordinates());
}
if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) {
@ -175,6 +175,10 @@ public class SettingsUtils {
}
private static String translateEntry(String key, String locale) {
if (key.startsWith("%")) {
// Bedrock will translate
return key;
}
if (key.startsWith("geyser.")) {
return GeyserLocale.getPlayerLocaleString(key, locale);
}