Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-20 06:50:09 +01:00
Merge remote-tracking branch 'upstream/master' into fabric
Dieser Commit ist enthalten in:
Commit
4dfb728b4d
@ -35,7 +35,7 @@ public enum BedrockPlatform {
|
|||||||
AMAZON("Amazon"),
|
AMAZON("Amazon"),
|
||||||
GEARVR("Gear VR"),
|
GEARVR("Gear VR"),
|
||||||
HOLOLENS("Hololens"),
|
HOLOLENS("Hololens"),
|
||||||
UWP("Windows 10"),
|
UWP("Windows"),
|
||||||
WIN32("Windows x86"),
|
WIN32("Windows x86"),
|
||||||
DEDICATED("Dedicated"),
|
DEDICATED("Dedicated"),
|
||||||
TVOS("Apple TV"),
|
TVOS("Apple TV"),
|
||||||
|
@ -137,6 +137,24 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force-disable query if enabled, or else Geyser won't enable
|
||||||
|
for (ListenerInfo info : getProxy().getConfig().getListeners()) {
|
||||||
|
if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) {
|
||||||
|
try {
|
||||||
|
Field queryField = ListenerInfo.class.getDeclaredField("queryEnabled");
|
||||||
|
queryField.setAccessible(true);
|
||||||
|
queryField.setBoolean(info, false);
|
||||||
|
geyserLogger.warning("We force-disabled query on port " + info.getQueryPort() + " in order for Geyser to boot up successfully. " +
|
||||||
|
"To remove this message, disable query in your proxy's config.");
|
||||||
|
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||||
|
geyserLogger.warning("Could not force-disable query. Geyser may not start correctly!");
|
||||||
|
if (geyserLogger.isDebug()) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) {
|
if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) {
|
||||||
geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
|
geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
|
||||||
return;
|
return;
|
||||||
|
@ -60,7 +60,6 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
|||||||
import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport;
|
import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport;
|
||||||
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor;
|
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor;
|
||||||
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
|
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
|
||||||
import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource;
|
|
||||||
import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener;
|
import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener;
|
||||||
import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener;
|
import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener;
|
||||||
import org.geysermc.geyser.platform.spigot.world.manager.*;
|
import org.geysermc.geyser.platform.spigot.world.manager.*;
|
||||||
@ -196,6 +195,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
|
|
||||||
geyserConfig.loadFloodgate(this);
|
geyserConfig.loadFloodgate(this);
|
||||||
|
|
||||||
|
if (!INITIALIZED) {
|
||||||
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
|
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
|
||||||
Bukkit.getPluginManager().registerEvents(new Listener() {
|
Bukkit.getPluginManager().registerEvents(new Listener() {
|
||||||
|
|
||||||
@ -229,6 +229,12 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (INITIALIZED) {
|
||||||
|
// Reload; continue with post startup
|
||||||
|
postStartup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void postStartup() {
|
private void postStartup() {
|
||||||
GeyserImpl.start();
|
GeyserImpl.start();
|
||||||
|
|
||||||
@ -262,14 +268,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Used to determine if Block.getBlockData() is present.
|
|
||||||
boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0");
|
|
||||||
if (isLegacy)
|
|
||||||
geyserLogger.debug("Legacy version of Minecraft (1.12.2 or older) detected; falling back to ViaVersion for block state retrieval.");
|
|
||||||
|
|
||||||
boolean isPre1_12 = !isCompatible(Bukkit.getServer().getVersion(), "1.12.0");
|
|
||||||
// Set if we need to use a different method for getting a player's locale
|
|
||||||
SpigotCommandSource.setUseLegacyLocaleMethod(isPre1_12);
|
|
||||||
|
|
||||||
// We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib
|
// We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib
|
||||||
// To do their job injecting, then connect into *that*
|
// To do their job injecting, then connect into *that*
|
||||||
@ -282,13 +280,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
|
String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
|
||||||
SpigotAdapters.registerWorldAdapter(nmsVersion);
|
SpigotAdapters.registerWorldAdapter(nmsVersion);
|
||||||
if (isViaVersion && isViaVersionNeeded()) {
|
if (isViaVersion && isViaVersionNeeded()) {
|
||||||
if (isLegacy) {
|
|
||||||
// Pre-1.13
|
|
||||||
this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this);
|
|
||||||
} else {
|
|
||||||
// Post-1.13
|
|
||||||
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this);
|
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// No ViaVersion
|
// No ViaVersion
|
||||||
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this);
|
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this);
|
||||||
@ -305,17 +297,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
}
|
}
|
||||||
if (this.geyserWorldManager == null) {
|
if (this.geyserWorldManager == null) {
|
||||||
// No NMS adapter
|
// No NMS adapter
|
||||||
if (isLegacy && isViaVersion) {
|
|
||||||
// Use ViaVersion for converting pre-1.13 block states
|
|
||||||
this.geyserWorldManager = new GeyserSpigot1_12WorldManager(this);
|
|
||||||
} else if (isLegacy) {
|
|
||||||
// Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air
|
|
||||||
this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this);
|
|
||||||
} else {
|
|
||||||
// Post-1.13
|
|
||||||
this.geyserWorldManager = new GeyserSpigotWorldManager(this);
|
this.geyserWorldManager = new GeyserSpigotWorldManager(this);
|
||||||
}
|
geyserLogger.debug("Using default world manager.");
|
||||||
geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginCommand geyserCommand = this.getCommand("geyser");
|
PluginCommand geyserCommand = this.getCommand("geyser");
|
||||||
|
@ -29,31 +29,17 @@ import net.kyori.adventure.text.Component;
|
|||||||
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
|
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
|
||||||
import org.bukkit.command.ConsoleCommandSender;
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
|
||||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||||
import org.geysermc.geyser.platform.spigot.PaperAdventure;
|
import org.geysermc.geyser.platform.spigot.PaperAdventure;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class SpigotCommandSource implements GeyserCommandSource {
|
public class SpigotCommandSource implements GeyserCommandSource {
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to use {@code Player.getLocale()} or {@code Player.spigot().getLocale()}, depending on version.
|
|
||||||
* 1.12 or greater should not use the legacy method.
|
|
||||||
*/
|
|
||||||
private static boolean USE_LEGACY_METHOD = false;
|
|
||||||
private static Method LOCALE_METHOD;
|
|
||||||
|
|
||||||
private final org.bukkit.command.CommandSender handle;
|
private final org.bukkit.command.CommandSender handle;
|
||||||
private final String locale;
|
|
||||||
|
|
||||||
public SpigotCommandSource(org.bukkit.command.CommandSender handle) {
|
public SpigotCommandSource(org.bukkit.command.CommandSender handle) {
|
||||||
this.handle = handle;
|
this.handle = handle;
|
||||||
this.locale = getSpigotLocale();
|
|
||||||
// Ensure even Java players' languages are loaded
|
// Ensure even Java players' languages are loaded
|
||||||
GeyserLocale.loadGeyserLocale(locale);
|
GeyserLocale.loadGeyserLocale(locale());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -84,50 +70,15 @@ public class SpigotCommandSource implements GeyserCommandSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String locale() {
|
public String locale() {
|
||||||
return locale;
|
if (this.handle instanceof Player player) {
|
||||||
|
return player.getLocale();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GeyserLocale.getDefaultLocale();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPermission(String permission) {
|
public boolean hasPermission(String permission) {
|
||||||
return handle.hasPermission(permission);
|
return handle.hasPermission(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get
|
|
||||||
* {@code player.spigot().getLocale()}.
|
|
||||||
*
|
|
||||||
* @param useLegacyMethod if we are running pre-1.12 and therefore need to use reflection to get the player locale
|
|
||||||
*/
|
|
||||||
public static void setUseLegacyLocaleMethod(boolean useLegacyMethod) {
|
|
||||||
USE_LEGACY_METHOD = useLegacyMethod;
|
|
||||||
if (USE_LEGACY_METHOD) {
|
|
||||||
try {
|
|
||||||
//noinspection JavaReflectionMemberAccess - of course it doesn't exist; that's why we're doing it
|
|
||||||
LOCALE_METHOD = Player.Spigot.class.getMethod("getLocale");
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
GeyserImpl.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* So we only have to do nasty reflection stuff once per command
|
|
||||||
*
|
|
||||||
* @return the locale of the Spigot player
|
|
||||||
*/
|
|
||||||
private String getSpigotLocale() {
|
|
||||||
if (handle instanceof Player player) {
|
|
||||||
if (USE_LEGACY_METHOD) {
|
|
||||||
try {
|
|
||||||
// sigh
|
|
||||||
// This was the only option on older Spigot instances and now it's gone
|
|
||||||
return (String) LOCALE_METHOD.invoke(player.spigot());
|
|
||||||
} catch (IllegalAccessException | InvocationTargetException ignored) {
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return player.getLocale();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GeyserLocale.getDefaultLocale();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -105,13 +105,10 @@ public class GeyserPistonListener implements Listener {
|
|||||||
// Trying to grab the blocks from the world like other platforms would result in the moving piston block
|
// Trying to grab the blocks from the world like other platforms would result in the moving piston block
|
||||||
// being returned instead.
|
// being returned instead.
|
||||||
if (!blocksFilled) {
|
if (!blocksFilled) {
|
||||||
// Blocks currently require a player for 1.12, so let's just leech off one player to get all blocks
|
|
||||||
// and call it a day for the rest of the sessions (mostly to save on execution time)
|
|
||||||
List<Block> blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks();
|
List<Block> blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks();
|
||||||
for (Block block : blocks) {
|
for (Block block : blocks) {
|
||||||
Location attachedLocation = block.getLocation();
|
Location attachedLocation = block.getLocation();
|
||||||
int blockId = worldManager.getBlockNetworkId(player, block,
|
int blockId = worldManager.getBlockNetworkId(block);
|
||||||
attachedLocation.getBlockX(), attachedLocation.getBlockY(), attachedLocation.getBlockZ());
|
|
||||||
// Ignore blocks that will be destroyed
|
// Ignore blocks that will be destroyed
|
||||||
if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) {
|
if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) {
|
||||||
attachedBlocks.put(getVector(attachedLocation), blockId);
|
attachedBlocks.put(getVector(attachedLocation), blockId);
|
||||||
@ -120,7 +117,7 @@ public class GeyserPistonListener implements Listener {
|
|||||||
blocksFilled = true;
|
blocksFilled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pistonBlockId = worldManager.getBlockNetworkId(player, event.getBlock(), location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
int pistonBlockId = worldManager.getBlockNetworkId(event.getBlock());
|
||||||
// event.getDirection() is unreliable
|
// event.getDirection() is unreliable
|
||||||
Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId);
|
Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId);
|
||||||
|
|
||||||
|
@ -1,61 +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.platform.spigot.world.manager;
|
|
||||||
|
|
||||||
import com.viaversion.viaversion.api.Via;
|
|
||||||
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.plugin.Plugin;
|
|
||||||
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
|
|
||||||
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
|
|
||||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used with ViaVersion and pre-1.13.
|
|
||||||
*/
|
|
||||||
public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager {
|
|
||||||
private final SpigotWorldAdapter adapter;
|
|
||||||
|
|
||||||
public GeyserSpigot1_12NativeWorldManager(Plugin plugin) {
|
|
||||||
super(plugin);
|
|
||||||
this.adapter = SpigotAdapters.getWorldAdapter();
|
|
||||||
// Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
|
||||||
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
|
|
||||||
if (player == null) {
|
|
||||||
return BlockStateValues.JAVA_AIR_ID;
|
|
||||||
}
|
|
||||||
// Get block entity storage
|
|
||||||
BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class);
|
|
||||||
int blockId = adapter.getBlockAt(player.getWorld(), x, y, z);
|
|
||||||
return getLegacyBlock(storage, blockId, x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,125 +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.platform.spigot.world.manager;
|
|
||||||
|
|
||||||
import com.viaversion.viaversion.api.Via;
|
|
||||||
import com.viaversion.viaversion.api.data.MappingData;
|
|
||||||
import com.viaversion.viaversion.api.minecraft.Position;
|
|
||||||
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
|
|
||||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
|
||||||
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2;
|
|
||||||
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.block.Block;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.plugin.Plugin;
|
|
||||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should be used when ViaVersion is present, no NMS adapter is being used, and we are pre-1.13.
|
|
||||||
*
|
|
||||||
* You need ViaVersion to connect to an older server with the Geyser-Spigot plugin.
|
|
||||||
*/
|
|
||||||
public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager {
|
|
||||||
/**
|
|
||||||
* Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 block into the 1.13 block state.
|
|
||||||
* (Block IDs did not change between server versions until 1.13 and after)
|
|
||||||
*/
|
|
||||||
private final MappingData mappingData1_12to1_13;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of all protocols from the client's version to 1.13.
|
|
||||||
*/
|
|
||||||
private final List<ProtocolPathEntry> protocolList;
|
|
||||||
|
|
||||||
public GeyserSpigot1_12WorldManager(Plugin plugin) {
|
|
||||||
super(plugin);
|
|
||||||
this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData();
|
|
||||||
this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(CLIENT_PROTOCOL_VERSION,
|
|
||||||
ProtocolVersion.v1_13.getVersion());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
|
||||||
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
|
|
||||||
if (player == null) {
|
|
||||||
return BlockStateValues.JAVA_AIR_ID;
|
|
||||||
}
|
|
||||||
if (!player.getWorld().isChunkLoaded(x >> 4, z >> 4)) {
|
|
||||||
// Prevent nasty async errors if a player is loading in
|
|
||||||
return BlockStateValues.JAVA_AIR_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Block block = player.getWorld().getBlockAt(x, y, z);
|
|
||||||
return getBlockNetworkId(player, block, x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public int getBlockNetworkId(Player player, Block block, int x, int y, int z) {
|
|
||||||
// Get block entity storage
|
|
||||||
BlockStorage storage = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId()).get(BlockStorage.class);
|
|
||||||
// Black magic that gets the old block state ID
|
|
||||||
int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
|
|
||||||
return getLegacyBlock(storage, oldBlockId, x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param storage ViaVersion's block entity storage (used to fix block entity state differences)
|
|
||||||
* @param blockId the pre-1.13 block id
|
|
||||||
* @param x X coordinate of block
|
|
||||||
* @param y Y coordinate of block
|
|
||||||
* @param z Z coordinate of block
|
|
||||||
* @return the block state updated to the latest Minecraft version
|
|
||||||
*/
|
|
||||||
public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z) {
|
|
||||||
// Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2
|
|
||||||
blockId = mappingData1_12to1_13.getNewBlockId(blockId);
|
|
||||||
// Translate block entity differences - some information was stored in block tags and not block states
|
|
||||||
if (storage.isWelcome(blockId)) { // No getOrDefault method
|
|
||||||
BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z));
|
|
||||||
if (data != null && data.getReplacement() != -1) {
|
|
||||||
blockId = data.getReplacement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = protocolList.size() - 1; i >= 0; i--) {
|
|
||||||
MappingData mappingData = protocolList.get(i).getProtocol().getMappingData();
|
|
||||||
if (mappingData != null) {
|
|
||||||
blockId = mappingData.getNewBlockStateId(blockId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return blockId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLegacy() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +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.platform.spigot.world.manager;
|
|
||||||
|
|
||||||
import org.bukkit.plugin.Plugin;
|
|
||||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should only be used when we know {@link GeyserSpigotWorldManager#getBlockAt(GeyserSession, int, int, int)}
|
|
||||||
* cannot be accurate. Typically, this is when ViaVersion is not installed but a client still manages to connect.
|
|
||||||
* If this occurs to you somehow, please let us know!!
|
|
||||||
*/
|
|
||||||
public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager {
|
|
||||||
public GeyserSpigotFallbackWorldManager(Plugin plugin) {
|
|
||||||
super(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
|
||||||
return BlockStateValues.JAVA_AIR_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasOwnChunkCache() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLegacy() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,7 +33,6 @@ import org.bukkit.Bukkit;
|
|||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.Lectern;
|
import org.bukkit.block.Lectern;
|
||||||
import org.bukkit.block.data.BlockData;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.BookMeta;
|
import org.bukkit.inventory.meta.BookMeta;
|
||||||
@ -41,7 +40,6 @@ import org.bukkit.plugin.Plugin;
|
|||||||
import org.geysermc.geyser.level.GameRule;
|
import org.geysermc.geyser.level.GameRule;
|
||||||
import org.geysermc.geyser.level.GeyserWorldManager;
|
import org.geysermc.geyser.level.GeyserWorldManager;
|
||||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||||
import org.geysermc.geyser.network.GameProtocol;
|
|
||||||
import org.geysermc.geyser.registry.BlockRegistries;
|
import org.geysermc.geyser.registry.BlockRegistries;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
|
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
|
||||||
@ -54,11 +52,6 @@ import java.util.List;
|
|||||||
* The base world manager to use when there is no supported NMS revision
|
* The base world manager to use when there is no supported NMS revision
|
||||||
*/
|
*/
|
||||||
public class GeyserSpigotWorldManager extends GeyserWorldManager {
|
public class GeyserSpigotWorldManager extends GeyserWorldManager {
|
||||||
/**
|
|
||||||
* The current client protocol version for ViaVersion usage.
|
|
||||||
*/
|
|
||||||
protected static final int CLIENT_PROTOCOL_VERSION = GameProtocol.getJavaProtocolVersion();
|
|
||||||
|
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
|
|
||||||
public GeyserSpigotWorldManager(Plugin plugin) {
|
public GeyserSpigotWorldManager(Plugin plugin) {
|
||||||
@ -77,10 +70,10 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
|
|||||||
return BlockStateValues.JAVA_AIR_ID;
|
return BlockStateValues.JAVA_AIR_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getBlockNetworkId(bukkitPlayer, world.getBlockAt(x, y, z), x, y, z);
|
return getBlockNetworkId(world.getBlockAt(x, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBlockNetworkId(Player player, Block block, int x, int y, int z) {
|
public int getBlockNetworkId(Block block) {
|
||||||
return BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID);
|
return BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,8 +174,6 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This must be set to true if we are pre-1.13, and {@link BlockData#getAsString() does not exist}.
|
|
||||||
*
|
|
||||||
* This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id
|
* This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id
|
||||||
* to the current one.
|
* to the current one.
|
||||||
*
|
*
|
||||||
|
@ -40,7 +40,7 @@ public enum DeviceOs {
|
|||||||
AMAZON("Amazon"),
|
AMAZON("Amazon"),
|
||||||
GEARVR("Gear VR"),
|
GEARVR("Gear VR"),
|
||||||
HOLOLENS("Hololens"),
|
HOLOLENS("Hololens"),
|
||||||
UWP("Windows 10"),
|
UWP("Windows"),
|
||||||
WIN32("Windows x86"),
|
WIN32("Windows x86"),
|
||||||
DEDICATED("Dedicated"),
|
DEDICATED("Dedicated"),
|
||||||
TVOS("Apple TV"),
|
TVOS("Apple TV"),
|
||||||
|
@ -30,7 +30,6 @@ import com.nukkitx.nbt.NbtMapBuilder;
|
|||||||
import com.nukkitx.nbt.NbtType;
|
import com.nukkitx.nbt.NbtType;
|
||||||
import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData;
|
import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData;
|
||||||
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||||
import org.geysermc.geyser.api.item.custom.CustomRenderOffsets;
|
import org.geysermc.geyser.api.item.custom.CustomRenderOffsets;
|
||||||
@ -43,6 +42,7 @@ import org.geysermc.geyser.registry.type.ItemMapping;
|
|||||||
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration;
|
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
@ -85,7 +85,7 @@ public class CustomItemRegistryPopulator {
|
|||||||
.maxDamage(customItemData.maxDamage())
|
.maxDamage(customItemData.maxDamage())
|
||||||
.repairMaterials(customItemData.repairMaterials())
|
.repairMaterials(customItemData.repairMaterials())
|
||||||
.hasSuspiciousStewEffect(false)
|
.hasSuspiciousStewEffect(false)
|
||||||
.customItemOptions(Object2IntMaps.emptyMap())
|
.customItemOptions(Collections.emptyList())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
|
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
|
||||||
|
@ -95,7 +95,10 @@ public class ItemRegistryPopulator {
|
|||||||
|
|
||||||
boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems();
|
boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems();
|
||||||
|
|
||||||
Multimap<String, CustomItemData> customItems = MultimapBuilder.hashKeys().hashSetValues().build();
|
// List values here is important compared to HashSet - we need to preserve the order of what's given to us
|
||||||
|
// (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom
|
||||||
|
// of the list first, then ascends.
|
||||||
|
Multimap<String, CustomItemData> customItems = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
List<NonVanillaCustomItemData> nonVanillaCustomItems;
|
List<NonVanillaCustomItemData> nonVanillaCustomItems;
|
||||||
|
|
||||||
MappingsConfigReader mappingsConfigReader = new MappingsConfigReader();
|
MappingsConfigReader mappingsConfigReader = new MappingsConfigReader();
|
||||||
@ -468,10 +471,10 @@ public class ItemRegistryPopulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the custom item properties, if applicable
|
// Add the custom item properties, if applicable
|
||||||
Object2IntMap<CustomItemOptions> customItemOptions;
|
List<ObjectIntPair<CustomItemOptions>> customItemOptions;
|
||||||
Collection<CustomItemData> customItemsToLoad = customItems.get(javaIdentifier);
|
Collection<CustomItemData> customItemsToLoad = customItems.get(javaIdentifier);
|
||||||
if (customItemsAllowed && !customItemsToLoad.isEmpty()) {
|
if (customItemsAllowed && !customItemsToLoad.isEmpty()) {
|
||||||
customItemOptions = new Object2IntOpenHashMap<>(customItemsToLoad.size());
|
customItemOptions = new ObjectArrayList<>(customItemsToLoad.size());
|
||||||
|
|
||||||
for (CustomItemData customItem : customItemsToLoad) {
|
for (CustomItemData customItem : customItemsToLoad) {
|
||||||
int customProtocolId = nextFreeBedrockId++;
|
int customProtocolId = nextFreeBedrockId++;
|
||||||
@ -491,12 +494,15 @@ public class ItemRegistryPopulator {
|
|||||||
entries.put(customMapping.stringId(), customMapping.startGamePacketItemEntry());
|
entries.put(customMapping.stringId(), customMapping.startGamePacketItemEntry());
|
||||||
// ComponentItemData - used to register some custom properties
|
// ComponentItemData - used to register some custom properties
|
||||||
componentItemData.add(customMapping.componentItemData());
|
componentItemData.add(customMapping.componentItemData());
|
||||||
customItemOptions.put(customItem.customItemOptions(), customProtocolId);
|
customItemOptions.add(ObjectIntPair.of(customItem.customItemOptions(), customProtocolId));
|
||||||
|
|
||||||
customIdMappings.put(customMapping.integerId(), customMapping.stringId());
|
customIdMappings.put(customMapping.integerId(), customMapping.stringId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Important for later to find the best match and accurately replicate Java behavior
|
||||||
|
Collections.reverse(customItemOptions);
|
||||||
} else {
|
} else {
|
||||||
customItemOptions = Object2IntMaps.emptyMap();
|
customItemOptions = Collections.emptyList();
|
||||||
}
|
}
|
||||||
mappingBuilder.customItemOptions(customItemOptions);
|
mappingBuilder.customItemOptions(customItemOptions);
|
||||||
|
|
||||||
@ -550,7 +556,7 @@ public class ItemRegistryPopulator {
|
|||||||
.bedrockData(0)
|
.bedrockData(0)
|
||||||
.bedrockBlockId(-1)
|
.bedrockBlockId(-1)
|
||||||
.stackSize(1)
|
.stackSize(1)
|
||||||
.customItemOptions(Object2IntMaps.emptyMap())
|
.customItemOptions(Collections.emptyList())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (customItemsAllowed) {
|
if (customItemsAllowed) {
|
||||||
@ -567,7 +573,7 @@ public class ItemRegistryPopulator {
|
|||||||
.bedrockData(0)
|
.bedrockData(0)
|
||||||
.bedrockBlockId(-1)
|
.bedrockBlockId(-1)
|
||||||
.stackSize(1)
|
.stackSize(1)
|
||||||
.customItemOptions(Object2IntMaps.emptyMap()) // TODO check for custom items with furnace minecart
|
.customItemOptions(Collections.emptyList()) // TODO check for custom items with furnace minecart
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
creativeItems.add(ItemData.builder()
|
creativeItems.add(ItemData.builder()
|
||||||
|
@ -25,15 +25,15 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.registry.type;
|
package org.geysermc.geyser.registry.type;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||||
import org.geysermc.geyser.network.GameProtocol;
|
|
||||||
import org.geysermc.geyser.registry.BlockRegistries;
|
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@ -41,8 +41,8 @@ import java.util.Set;
|
|||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class ItemMapping {
|
public class ItemMapping {
|
||||||
public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0,
|
public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0,
|
||||||
BlockRegistries.BLOCKS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(),
|
0, // Air is never sent in full over the network for this to serialize.
|
||||||
64, null, null, null, Object2IntMaps.emptyMap(), 0, null, false);
|
64, null, null, null, Collections.emptyList(), 0, null, false);
|
||||||
|
|
||||||
String javaIdentifier;
|
String javaIdentifier;
|
||||||
String bedrockIdentifier;
|
String bedrockIdentifier;
|
||||||
@ -62,7 +62,8 @@ public class ItemMapping {
|
|||||||
|
|
||||||
String translationString;
|
String translationString;
|
||||||
|
|
||||||
Object2IntMap<CustomItemOptions> customItemOptions;
|
@NonNull
|
||||||
|
List<ObjectIntPair<CustomItemOptions>> customItemOptions;
|
||||||
|
|
||||||
int maxDamage;
|
int maxDamage;
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.text;
|
package org.geysermc.geyser.text;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrays;
|
||||||
import org.geysermc.geyser.GeyserBootstrap;
|
import org.geysermc.geyser.GeyserBootstrap;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
|
||||||
@ -173,6 +174,16 @@ public class GeyserLocale {
|
|||||||
return localeProp.isEmpty() ? null : locale;
|
return localeProp.isEmpty() ? null : locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a formatted language string with the default locale for Geyser
|
||||||
|
*
|
||||||
|
* @param key Language string to translate
|
||||||
|
* @return Translated string or the original message if it was not found in the given locale
|
||||||
|
*/
|
||||||
|
public static String getLocaleStringLog(String key) {
|
||||||
|
return getLocaleStringLog(key, ObjectArrays.EMPTY_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a formatted language string with the default locale for Geyser
|
* Get a formatted language string with the default locale for Geyser
|
||||||
*
|
*
|
||||||
@ -184,6 +195,17 @@ public class GeyserLocale {
|
|||||||
return getPlayerLocaleString(key, getDefaultLocale(), values);
|
return getPlayerLocaleString(key, getDefaultLocale(), values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a formatted language string with the given locale for Geyser
|
||||||
|
*
|
||||||
|
* @param key Language string to translate
|
||||||
|
* @param locale Locale to translate to
|
||||||
|
* @return Translated string or the original message if it was not found in the given locale
|
||||||
|
*/
|
||||||
|
public static String getPlayerLocaleString(String key, String locale) {
|
||||||
|
return getPlayerLocaleString(key, locale, ObjectArrays.EMPTY_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a formatted language string with the given locale for Geyser
|
* Get a formatted language string with the given locale for Geyser
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* 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.opennbt.tag.builtin.CompoundTag;
|
||||||
|
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||||
|
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
|
||||||
|
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||||
|
import org.geysermc.geyser.api.util.TriState;
|
||||||
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator.
|
||||||
|
*/
|
||||||
|
final class CustomItemTranslator {
|
||||||
|
|
||||||
|
static int getCustomItem(CompoundTag nbt, ItemMapping mapping) {
|
||||||
|
if (nbt == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
List<ObjectIntPair<CustomItemOptions>> customMappings = mapping.getCustomItemOptions();
|
||||||
|
if (customMappings.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0;
|
||||||
|
boolean checkDamage = mapping.getMaxDamage() > 0;
|
||||||
|
int damage = !checkDamage ? 0 : nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0;
|
||||||
|
boolean unbreakable = checkDamage && !isDamaged(nbt, damage);
|
||||||
|
|
||||||
|
for (ObjectIntPair<CustomItemOptions> mappingTypes : customMappings) {
|
||||||
|
CustomItemOptions options = mappingTypes.key();
|
||||||
|
|
||||||
|
// Code note: there may be two or more conditions that a custom item must follow, hence the "continues"
|
||||||
|
// here with the return at the end.
|
||||||
|
|
||||||
|
// Implementation details: Java's predicate system works exclusively on comparing float numbers.
|
||||||
|
// A value doesn't necessarily have to match 100%; it just has to be the first to meet all predicate conditions.
|
||||||
|
// This is also why the order of iteration is important as the first to match will be the chosen display item.
|
||||||
|
// For example, if CustomModelData is set to 2f as the requirement, then the NBT can be any number greater or equal (2, 3, 4...)
|
||||||
|
// The same behavior exists for Damage (in fraction form instead of whole numbers),
|
||||||
|
// and Damaged/Unbreakable handles no damage as 0f and damaged as 1f.
|
||||||
|
|
||||||
|
if (checkDamage) {
|
||||||
|
if (unbreakable && options.unbreakable() == TriState.FALSE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionalInt damagePredicate = options.damagePredicate();
|
||||||
|
if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (options.unbreakable() != TriState.NOT_SET || options.damagePredicate().isPresent()) {
|
||||||
|
// These will never match on this item. 1.19.2 behavior
|
||||||
|
// Maybe move this to CustomItemRegistryPopulator since it'll be the same for every item? If so, add a test.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionalInt customModelDataOption = options.customModelData();
|
||||||
|
if (customModelDataOption.isPresent() && customModelData < customModelDataOption.getAsInt()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappingTypes.valueInt();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* These two functions are based off their Mojmap equivalents from 1.19.2 */
|
||||||
|
|
||||||
|
private static boolean isDamaged(CompoundTag nbt, int damage) {
|
||||||
|
return isDamagableItem(nbt) && damage > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDamagableItem(CompoundTag nbt) {
|
||||||
|
// mapping.getMaxDamage > 0 should also be checked (return false if not true) but we already check prior to this function
|
||||||
|
Tag unbreakableTag = nbt.get("Unbreakable");
|
||||||
|
// Tag must either not be present or be set to false
|
||||||
|
return unbreakableTag == null || !(unbreakableTag.getValue() instanceof Number number) || number.byteValue() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CustomItemTranslator() {
|
||||||
|
}
|
||||||
|
}
|
@ -34,12 +34,9 @@ import com.nukkitx.nbt.NbtType;
|
|||||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
|
||||||
import org.geysermc.geyser.api.util.TriState;
|
|
||||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||||
import org.geysermc.geyser.registry.BlockRegistries;
|
import org.geysermc.geyser.registry.BlockRegistries;
|
||||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||||
@ -173,19 +170,19 @@ public abstract class ItemTranslator {
|
|||||||
builder.blockRuntimeId(bedrockItem.getBedrockBlockId());
|
builder.blockRuntimeId(bedrockItem.getBedrockBlockId());
|
||||||
}
|
}
|
||||||
|
|
||||||
translateCustomItem(nbt, builder, bedrockItem);
|
|
||||||
|
|
||||||
if (nbt != null) {
|
if (nbt != null) {
|
||||||
// Translate the canDestroy and canPlaceOn Java NBT
|
// Translate the canDestroy and canPlaceOn Java NBT
|
||||||
ListTag canDestroy = nbt.get("CanDestroy");
|
ListTag canDestroy = nbt.get("CanDestroy");
|
||||||
String[] canBreak = new String[0];
|
|
||||||
ListTag canPlaceOn = nbt.get("CanPlaceOn");
|
ListTag canPlaceOn = nbt.get("CanPlaceOn");
|
||||||
String[] canPlace = new String[0];
|
String[] canBreak = getCanModify(canDestroy);
|
||||||
canBreak = getCanModify(canDestroy, canBreak);
|
String[] canPlace = getCanModify(canPlaceOn);
|
||||||
canPlace = getCanModify(canPlaceOn, canPlace);
|
if (canBreak != null) {
|
||||||
builder.canBreak(canBreak);
|
builder.canBreak(canBreak);
|
||||||
|
}
|
||||||
|
if (canPlace != null) {
|
||||||
builder.canPlace(canPlace);
|
builder.canPlace(canPlace);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
@ -246,12 +243,11 @@ public abstract class ItemTranslator {
|
|||||||
* In Java, this is treated as normal NBT, but in Bedrock, these arguments are extra parts of the item data itself.
|
* In Java, this is treated as normal NBT, but in Bedrock, these arguments are extra parts of the item data itself.
|
||||||
*
|
*
|
||||||
* @param canModifyJava the list of items in Java
|
* @param canModifyJava the list of items in Java
|
||||||
* @param canModifyBedrock the empty list of items in Bedrock
|
|
||||||
* @return the new list of items in Bedrock
|
* @return the new list of items in Bedrock
|
||||||
*/
|
*/
|
||||||
private static String[] getCanModify(ListTag canModifyJava, String[] canModifyBedrock) {
|
private static String[] getCanModify(ListTag canModifyJava) {
|
||||||
if (canModifyJava != null && canModifyJava.size() > 0) {
|
if (canModifyJava != null && canModifyJava.size() > 0) {
|
||||||
canModifyBedrock = new String[canModifyJava.size()];
|
String[] canModifyBedrock = new String[canModifyJava.size()];
|
||||||
for (int i = 0; i < canModifyBedrock.length; i++) {
|
for (int i = 0; i < canModifyBedrock.length; i++) {
|
||||||
// Get the Java identifier of the block that can be placed
|
// Get the Java identifier of the block that can be placed
|
||||||
String block = ((StringTag) canModifyJava.get(i)).getValue();
|
String block = ((StringTag) canModifyJava.get(i)).getValue();
|
||||||
@ -261,9 +257,10 @@ public abstract class ItemTranslator {
|
|||||||
// This will unfortunately be limited - for example, beds and banners will be translated weirdly
|
// This will unfortunately be limited - for example, beds and banners will be translated weirdly
|
||||||
canModifyBedrock[i] = BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(block, block).replace("minecraft:", "");
|
canModifyBedrock[i] = BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(block, block).replace("minecraft:", "");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return canModifyBedrock;
|
return canModifyBedrock;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an item stack, determine the Bedrock item ID that should be applied to Bedrock players.
|
* Given an item stack, determine the Bedrock item ID that should be applied to Bedrock players.
|
||||||
@ -276,7 +273,7 @@ public abstract class ItemTranslator {
|
|||||||
ItemMapping mapping = ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR)
|
ItemMapping mapping = ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR)
|
||||||
.getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings());
|
.getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings());
|
||||||
|
|
||||||
int customItemId = getCustomItem(itemStack.getNbt(), mapping);
|
int customItemId = CustomItemTranslator.getCustomItem(itemStack.getNbt(), mapping);
|
||||||
if (customItemId == -1) {
|
if (customItemId == -1) {
|
||||||
// No custom item
|
// No custom item
|
||||||
return mapping.getBedrockId();
|
return mapping.getBedrockId();
|
||||||
@ -329,66 +326,26 @@ public abstract class ItemTranslator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected NbtMap translateNbtToBedrock(CompoundTag tag) {
|
protected NbtMap translateNbtToBedrock(CompoundTag tag) {
|
||||||
|
if (!tag.getValue().isEmpty()) {
|
||||||
NbtMapBuilder builder = NbtMap.builder();
|
NbtMapBuilder builder = NbtMap.builder();
|
||||||
if (tag.getValue() != null && !tag.getValue().isEmpty()) {
|
for (Tag javaTag : tag.values()) {
|
||||||
for (String str : tag.getValue().keySet()) {
|
|
||||||
Tag javaTag = tag.get(str);
|
|
||||||
Object translatedTag = translateToBedrockNBT(javaTag);
|
Object translatedTag = translateToBedrockNBT(javaTag);
|
||||||
if (translatedTag == null)
|
if (translatedTag == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
builder.put(javaTag.getName(), translatedTag);
|
builder.put(javaTag.getName(), translatedTag);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
return NbtMap.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
private Object translateToBedrockNBT(Tag tag) {
|
private Object translateToBedrockNBT(Tag tag) {
|
||||||
if (tag instanceof ByteArrayTag) {
|
if (tag instanceof CompoundTag compoundTag) {
|
||||||
return ((ByteArrayTag) tag).getValue();
|
return translateNbtToBedrock(compoundTag);
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof ByteTag) {
|
|
||||||
return ((ByteTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof DoubleTag) {
|
|
||||||
return ((DoubleTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof FloatTag) {
|
|
||||||
return ((FloatTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof IntArrayTag) {
|
|
||||||
return ((IntArrayTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof IntTag) {
|
|
||||||
return ((IntTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof LongArrayTag) {
|
|
||||||
//Long array tag does not exist in BE
|
|
||||||
//LongArrayTag longArrayTag = (LongArrayTag) tag;
|
|
||||||
//return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof LongTag) {
|
|
||||||
return ((LongTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof ShortTag) {
|
|
||||||
return ((ShortTag) tag).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag instanceof StringTag) {
|
|
||||||
return ((StringTag) tag).getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag instanceof ListTag listTag) {
|
if (tag instanceof ListTag listTag) {
|
||||||
|
|
||||||
List<Object> tagList = new ArrayList<>();
|
List<Object> tagList = new ArrayList<>();
|
||||||
for (Tag value : listTag) {
|
for (Tag value : listTag) {
|
||||||
tagList.add(translateToBedrockNBT(value));
|
tagList.add(translateToBedrockNBT(value));
|
||||||
@ -400,11 +357,14 @@ public abstract class ItemTranslator {
|
|||||||
return new NbtList(type, tagList);
|
return new NbtList(type, tagList);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag instanceof CompoundTag compoundTag) {
|
if (tag instanceof LongArrayTag) {
|
||||||
return translateNbtToBedrock(compoundTag);
|
//Long array tag does not exist in BE
|
||||||
|
//LongArrayTag longArrayTag = (LongArrayTag) tag;
|
||||||
|
//return new com.nukkitx.nbt.tag.LongArrayTag(longArrayTag.getName(), longArrayTag.getValue());
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return tag.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompoundTag translateToJavaNBT(String name, NbtMap tag) {
|
private CompoundTag translateToJavaNBT(String name, NbtMap tag) {
|
||||||
@ -544,87 +504,10 @@ public abstract class ItemTranslator {
|
|||||||
* Translates the custom model data of an item
|
* Translates the custom model data of an item
|
||||||
*/
|
*/
|
||||||
private static void translateCustomItem(CompoundTag nbt, ItemData.Builder builder, ItemMapping mapping) {
|
private static void translateCustomItem(CompoundTag nbt, ItemData.Builder builder, ItemMapping mapping) {
|
||||||
int bedrockId = getCustomItem(nbt, mapping);
|
int bedrockId = CustomItemTranslator.getCustomItem(nbt, mapping);
|
||||||
if (bedrockId != -1) {
|
if (bedrockId != -1) {
|
||||||
builder.id(bedrockId);
|
builder.id(bedrockId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getCustomItem(CompoundTag nbt, ItemMapping mapping) {
|
|
||||||
if (nbt == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
Object2IntMap<CustomItemOptions> customMappings = mapping.getCustomItemOptions();
|
|
||||||
if (customMappings.isEmpty()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0;
|
|
||||||
TriState unbreakable = TriState.fromBoolean(nbt.get("Unbreakable") instanceof ByteTag unbreakableTag && unbreakableTag.getValue() == 1);
|
|
||||||
int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0;
|
|
||||||
for (Object2IntMap.Entry<CustomItemOptions> mappingTypes : customMappings.object2IntEntrySet()) {
|
|
||||||
CustomItemOptions options = mappingTypes.getKey();
|
|
||||||
|
|
||||||
TriState unbreakableOption = options.unbreakable();
|
|
||||||
if (unbreakableOption == unbreakable) { // Implementation note: if the option is NOT_SET then this comparison will always be false because of how the item unbreaking TriState is created
|
|
||||||
return mappingTypes.getIntValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
OptionalInt customModelDataOption = options.customModelData();
|
|
||||||
if (customModelDataOption.isPresent() && customModelDataOption.getAsInt() == customModelData) {
|
|
||||||
return mappingTypes.getIntValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
OptionalInt damagePredicate = options.damagePredicate();
|
|
||||||
if (damagePredicate.isPresent() && damagePredicate.getAsInt() == damage) {
|
|
||||||
return mappingTypes.getIntValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an {@link ItemStack} is equal to another item stack
|
|
||||||
*
|
|
||||||
* @param itemStack the item stack to check
|
|
||||||
* @param equalsItemStack the item stack to check if equal to
|
|
||||||
* @param checkAmount if the amount should be taken into account
|
|
||||||
* @param trueIfAmountIsGreater if this should return true if the amount of the
|
|
||||||
* first item stack is greater than that of the second
|
|
||||||
* @param checkNbt if NBT data should be checked
|
|
||||||
* @return if an item stack is equal to another item stack
|
|
||||||
*/
|
|
||||||
public boolean equals(ItemStack itemStack, ItemStack equalsItemStack, boolean checkAmount, boolean trueIfAmountIsGreater, boolean checkNbt) {
|
|
||||||
if (itemStack.getId() != equalsItemStack.getId()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (checkAmount) {
|
|
||||||
if (trueIfAmountIsGreater) {
|
|
||||||
if (itemStack.getAmount() < equalsItemStack.getAmount()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (itemStack.getAmount() != equalsItemStack.getAmount()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkNbt) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ((itemStack.getNbt() == null || itemStack.getNbt().isEmpty()) && (equalsItemStack.getNbt() != null && !equalsItemStack.getNbt().isEmpty())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((itemStack.getNbt() != null && !itemStack.getNbt().isEmpty() && (equalsItemStack.getNbt() == null || !equalsItemStack.getNbt().isEmpty()))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemStack.getNbt() != null && equalsItemStack.getNbt() != null) {
|
|
||||||
return itemStack.getNbt().equals(equalsItemStack.getNbt());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,8 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
|||||||
if (needsSpawnPacket) {
|
if (needsSpawnPacket) {
|
||||||
// The player has yet to spawn so let's do that using some of the information in this Java packet
|
// The player has yet to spawn so let's do that using some of the information in this Java packet
|
||||||
session.setDimension(newDimension);
|
session.setDimension(newDimension);
|
||||||
|
session.setDimensionType(dimensions.get(newDimension));
|
||||||
|
ChunkUtils.loadDimension(session);
|
||||||
session.connect();
|
session.connect();
|
||||||
|
|
||||||
// It is now safe to send these packets
|
// It is now safe to send these packets
|
||||||
@ -145,8 +147,5 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
|||||||
// If the player is spawning into the "fake" nether, send them some fog
|
// If the player is spawning into the "fake" nether, send them some fog
|
||||||
session.sendFog("minecraft:fog_hell");
|
session.sendFog("minecraft:fog_hell");
|
||||||
}
|
}
|
||||||
|
|
||||||
session.setDimensionType(dimensions.get(newDimension));
|
|
||||||
ChunkUtils.loadDimension(session);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ import org.geysermc.geyser.session.GeyserSession;
|
|||||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.geyser.util.ChunkUtils;
|
|
||||||
import org.geysermc.geyser.util.DimensionUtils;
|
import org.geysermc.geyser.util.DimensionUtils;
|
||||||
|
|
||||||
@Translator(packet = ClientboundRespawnPacket.class)
|
@Translator(packet = ClientboundRespawnPacket.class)
|
||||||
@ -93,9 +92,6 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
|
|||||||
}
|
}
|
||||||
session.setWorldName(packet.getWorldName());
|
session.setWorldName(packet.getWorldName());
|
||||||
DimensionUtils.switchDimension(session, newDimension);
|
DimensionUtils.switchDimension(session, newDimension);
|
||||||
|
|
||||||
session.setDimensionType(session.getDimensions().get(newDimension));
|
|
||||||
ChunkUtils.loadDimension(session);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,6 @@ import com.nukkitx.math.vector.Vector3i;
|
|||||||
import com.nukkitx.nbt.NBTOutputStream;
|
import com.nukkitx.nbt.NBTOutputStream;
|
||||||
import com.nukkitx.nbt.NbtMap;
|
import com.nukkitx.nbt.NbtMap;
|
||||||
import com.nukkitx.nbt.NbtUtils;
|
import com.nukkitx.nbt.NbtUtils;
|
||||||
import com.nukkitx.network.VarInts;
|
|
||||||
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
@ -284,6 +283,9 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
}
|
}
|
||||||
sectionCount++;
|
sectionCount++;
|
||||||
|
|
||||||
|
// As of 1.18.30, the amount of biomes read is dependent on how high Bedrock thinks the dimension is
|
||||||
|
int biomeCount = bedrockDimension.height() >> 4;
|
||||||
|
|
||||||
// Estimate chunk size
|
// Estimate chunk size
|
||||||
int size = 0;
|
int size = 0;
|
||||||
for (int i = 0; i < sectionCount; i++) {
|
for (int i = 0; i < sectionCount; i++) {
|
||||||
@ -294,9 +296,8 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
size += SERIALIZED_CHUNK_DATA.length;
|
size += SERIALIZED_CHUNK_DATA.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data
|
size += ChunkUtils.EMPTY_BIOME_DATA.length * biomeCount;
|
||||||
size += 1; // Border blocks
|
size += 1; // Border blocks
|
||||||
size += 1; // Extra data length (always 0)
|
|
||||||
size += bedrockBlockEntities.size() * 64; // Conservative estimate of 64 bytes per tile entity
|
size += bedrockBlockEntities.size() * 64; // Conservative estimate of 64 bytes per tile entity
|
||||||
|
|
||||||
// Allocate output buffer
|
// Allocate output buffer
|
||||||
@ -310,8 +311,6 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// As of 1.18.30, the amount of biomes read is dependent on how high Bedrock thinks the dimension is
|
|
||||||
int biomeCount = bedrockDimension.height() >> 4;
|
|
||||||
int dimensionOffset = bedrockDimension.minY() >> 4;
|
int dimensionOffset = bedrockDimension.minY() >> 4;
|
||||||
for (int i = 0; i < biomeCount; i++) {
|
for (int i = 0; i < biomeCount; i++) {
|
||||||
int biomeYOffset = dimensionOffset + i;
|
int biomeYOffset = dimensionOffset + i;
|
||||||
@ -331,7 +330,6 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||||||
}
|
}
|
||||||
|
|
||||||
byteBuf.writeByte(0); // Border blocks - Edu edition only
|
byteBuf.writeByte(0); // Border blocks - Edu edition only
|
||||||
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
|
|
||||||
|
|
||||||
// Encode tile entities into buffer
|
// Encode tile entities into buffer
|
||||||
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
|
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
|
||||||
|
@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
|||||||
import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
|
import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import it.unimi.dsi.fastutil.ints.IntLists;
|
import it.unimi.dsi.fastutil.ints.IntLists;
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
@ -54,10 +55,6 @@ public class ChunkUtils {
|
|||||||
* An empty subchunk.
|
* An empty subchunk.
|
||||||
*/
|
*/
|
||||||
public static final byte[] SERIALIZED_CHUNK_DATA;
|
public static final byte[] SERIALIZED_CHUNK_DATA;
|
||||||
/**
|
|
||||||
* An empty chunk that can be safely passed on to a LevelChunkPacket with subcounts set to 0.
|
|
||||||
*/
|
|
||||||
public static final byte[] EMPTY_CHUNK_DATA;
|
|
||||||
public static final byte[] EMPTY_BIOME_DATA;
|
public static final byte[] EMPTY_BIOME_DATA;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -81,20 +78,6 @@ public class ChunkUtils {
|
|||||||
} finally {
|
} finally {
|
||||||
byteBuf.release();
|
byteBuf.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
byteBuf = Unpooled.buffer();
|
|
||||||
try {
|
|
||||||
for (int i = 0; i < 32; i++) {
|
|
||||||
byteBuf.writeBytes(EMPTY_BIOME_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
byteBuf.writeByte(0); // Border
|
|
||||||
|
|
||||||
EMPTY_CHUNK_DATA = new byte[byteBuf.readableBytes()];
|
|
||||||
byteBuf.readBytes(EMPTY_CHUNK_DATA);
|
|
||||||
} finally {
|
|
||||||
byteBuf.release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int indexYZXtoXZY(int yzx) {
|
public static int indexYZXtoXZY(int yzx) {
|
||||||
@ -185,11 +168,32 @@ public class ChunkUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void sendEmptyChunk(GeyserSession session, int chunkX, int chunkZ, boolean forceUpdate) {
|
public static void sendEmptyChunk(GeyserSession session, int chunkX, int chunkZ, boolean forceUpdate) {
|
||||||
|
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
|
||||||
|
int bedrockSubChunkCount = bedrockDimension.height() >> 4;
|
||||||
|
|
||||||
|
byte[] payload;
|
||||||
|
|
||||||
|
// Allocate output buffer
|
||||||
|
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(ChunkUtils.EMPTY_BIOME_DATA.length * bedrockSubChunkCount + 1); // Consists only of biome data and border blocks
|
||||||
|
try {
|
||||||
|
byteBuf.writeBytes(EMPTY_BIOME_DATA);
|
||||||
|
for (int i = 1; i < bedrockSubChunkCount; i++) {
|
||||||
|
byteBuf.writeByte((127 << 1) | 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
byteBuf.writeByte(0); // Border blocks - Edu edition only
|
||||||
|
|
||||||
|
payload = new byte[byteBuf.readableBytes()];
|
||||||
|
byteBuf.readBytes(payload);
|
||||||
|
} finally {
|
||||||
|
byteBuf.release();
|
||||||
|
}
|
||||||
|
|
||||||
LevelChunkPacket data = new LevelChunkPacket();
|
LevelChunkPacket data = new LevelChunkPacket();
|
||||||
data.setChunkX(chunkX);
|
data.setChunkX(chunkX);
|
||||||
data.setChunkZ(chunkZ);
|
data.setChunkZ(chunkZ);
|
||||||
data.setSubChunksLength(0);
|
data.setSubChunksLength(0);
|
||||||
data.setData(EMPTY_CHUNK_DATA);
|
data.setData(payload);
|
||||||
data.setCachingEnabled(false);
|
data.setCachingEnabled(false);
|
||||||
session.sendUpstreamPacket(data);
|
session.sendUpstreamPacket(data);
|
||||||
|
|
||||||
|
@ -94,6 +94,8 @@ public class DimensionUtils {
|
|||||||
changeDimensionPacket.setPosition(pos);
|
changeDimensionPacket.setPosition(pos);
|
||||||
session.sendUpstreamPacket(changeDimensionPacket);
|
session.sendUpstreamPacket(changeDimensionPacket);
|
||||||
session.setDimension(javaDimension);
|
session.setDimension(javaDimension);
|
||||||
|
session.setDimensionType(session.getDimensions().get(javaDimension));
|
||||||
|
ChunkUtils.loadDimension(session);
|
||||||
player.setPosition(pos);
|
player.setPosition(pos);
|
||||||
session.setSpawned(false);
|
session.setSpawned(false);
|
||||||
session.setLastChunkPosition(null);
|
session.setLastChunkPosition(null);
|
||||||
|
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* 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.opennbt.tag.builtin.ByteTag;
|
||||||
|
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||||
|
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
|
||||||
|
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||||
|
import org.geysermc.geyser.api.util.TriState;
|
||||||
|
import org.geysermc.geyser.item.GeyserCustomItemOptions;
|
||||||
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
|
public class CustomItemsTest {
|
||||||
|
private ItemMapping testMappingWithDamage;
|
||||||
|
private Object2IntMap<CompoundTag> tagToCustomItemWithDamage;
|
||||||
|
private ItemMapping testMappingWithNoDamage;
|
||||||
|
private Object2IntMap<CompoundTag> tagToCustomItemWithNoDamage;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
CustomItemOptions a = new GeyserCustomItemOptions(TriState.TRUE, OptionalInt.of(2), OptionalInt.empty());
|
||||||
|
CustomItemOptions b = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.of(5), OptionalInt.empty());
|
||||||
|
CustomItemOptions c = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.empty(), OptionalInt.of(3));
|
||||||
|
CustomItemOptions d = new GeyserCustomItemOptions(TriState.TRUE, OptionalInt.empty(), OptionalInt.of(8));
|
||||||
|
CustomItemOptions e = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.empty(), OptionalInt.of(12));
|
||||||
|
CustomItemOptions f = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.of(8), OptionalInt.of(6));
|
||||||
|
CustomItemOptions g = new GeyserCustomItemOptions(TriState.NOT_SET, OptionalInt.of(20), OptionalInt.empty());
|
||||||
|
|
||||||
|
Object2IntMap<CustomItemOptions> optionsToId = new Object2IntArrayMap<>();
|
||||||
|
// Order here is important, hence why we're using an array map
|
||||||
|
optionsToId.put(g, 7);
|
||||||
|
optionsToId.put(f, 6);
|
||||||
|
optionsToId.put(e, 5);
|
||||||
|
optionsToId.put(d, 4);
|
||||||
|
optionsToId.put(c, 3);
|
||||||
|
optionsToId.put(b, 2);
|
||||||
|
optionsToId.put(a, 1);
|
||||||
|
|
||||||
|
tagToCustomItemWithDamage = new Object2IntOpenHashMap<>();
|
||||||
|
|
||||||
|
CompoundTag tag = new CompoundTag("");
|
||||||
|
tag.put(new IntTag("CustomModelData", 6));
|
||||||
|
// Test item with no damage should be treated as unbreakable
|
||||||
|
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a));
|
||||||
|
|
||||||
|
tag = new CompoundTag("");
|
||||||
|
addCustomModelData(20, tag);
|
||||||
|
// Test that an unbreakable item isn't tested for Damaged if there is no damaged predicate
|
||||||
|
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(g));
|
||||||
|
|
||||||
|
tag = new CompoundTag("");
|
||||||
|
addCustomModelData(3, tag);
|
||||||
|
setUnbreakable(true, tag);
|
||||||
|
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a));
|
||||||
|
|
||||||
|
tag = new CompoundTag("");
|
||||||
|
addDamage(16, tag);
|
||||||
|
setUnbreakable(false, tag);
|
||||||
|
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(e));
|
||||||
|
|
||||||
|
tag = new CompoundTag("");
|
||||||
|
addCustomModelData(7, tag);
|
||||||
|
addDamage(6, tag);
|
||||||
|
setUnbreakable(false, tag);
|
||||||
|
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(c));
|
||||||
|
|
||||||
|
tag = new CompoundTag("");
|
||||||
|
addCustomModelData(9, tag);
|
||||||
|
addDamage(6, tag);
|
||||||
|
setUnbreakable(true, tag);
|
||||||
|
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a));
|
||||||
|
|
||||||
|
tag = new CompoundTag("");
|
||||||
|
addCustomModelData(9, tag);
|
||||||
|
addDamage(6, tag);
|
||||||
|
setUnbreakable(false, tag);
|
||||||
|
tagToCustomItemWithDamage.put(tag, optionsToId.getInt(f));
|
||||||
|
|
||||||
|
List<ObjectIntPair<CustomItemOptions>> customItemOptions = optionsToId.object2IntEntrySet().stream().map(entry -> ObjectIntPair.of(entry.getKey(), entry.getIntValue())).toList();
|
||||||
|
|
||||||
|
testMappingWithDamage = ItemMapping.builder()
|
||||||
|
.customItemOptions(customItemOptions)
|
||||||
|
.maxDamage(100)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Test differences with items with no max damage
|
||||||
|
|
||||||
|
tagToCustomItemWithNoDamage = new Object2IntOpenHashMap<>();
|
||||||
|
|
||||||
|
tag = new CompoundTag("");
|
||||||
|
tag.put(new IntTag("CustomModelData", 2));
|
||||||
|
// Damage predicates existing mean an item will never match if the item mapping has no max damage
|
||||||
|
tagToCustomItemWithNoDamage.put(tag, -1);
|
||||||
|
|
||||||
|
testMappingWithNoDamage = ItemMapping.builder()
|
||||||
|
.customItemOptions(customItemOptions)
|
||||||
|
.maxDamage(0)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCustomModelData(int value, CompoundTag tag) {
|
||||||
|
tag.put(new IntTag("CustomModelData", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDamage(int value, CompoundTag tag) {
|
||||||
|
tag.put(new IntTag("Damage", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUnbreakable(boolean value, CompoundTag tag) {
|
||||||
|
tag.put(new ByteTag("Unbreakable", (byte) (value ? 1 : 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomItems() {
|
||||||
|
for (Object2IntMap.Entry<CompoundTag> entry : this.tagToCustomItemWithDamage.object2IntEntrySet()) {
|
||||||
|
int id = CustomItemTranslator.getCustomItem(entry.getKey(), this.testMappingWithDamage);
|
||||||
|
Assert.assertEquals(entry.getKey() + " did not produce the correct custom item", entry.getIntValue(), id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Object2IntMap.Entry<CompoundTag> entry : this.tagToCustomItemWithNoDamage.object2IntEntrySet()) {
|
||||||
|
int id = CustomItemTranslator.getCustomItem(entry.getKey(), this.testMappingWithNoDamage);
|
||||||
|
Assert.assertEquals(entry.getKey() + " did not produce the correct custom item", entry.getIntValue(), id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren