Merge remote-tracking branch 'origin/master' into floodgate-2.0

# Conflicts:
#	connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
#	connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java
#	connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java
#	connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
This commit is contained in:
Tim203 2020-12-15 22:06:59 +01:00
commit cd13e03730
No known key found for this signature in database
GPG Key ID: 064EE9F5BF7C3EE8
125 changed files with 3459 additions and 723 deletions

View File

@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here!
### Currently supporting Minecraft Bedrock v1.16.100 and Minecraft Java v1.16.4.
### Currently supporting Minecraft Bedrock v1.16.100/v1.16.101/v1.16.200 and Minecraft Java v1.16.4.
## Setting Up
Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser.
@ -34,16 +34,24 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
- Test Server: `test.geysermc.org` port `25565` for Java and `19132` for Bedrock
## What's Left to be Added/Fixed
- The Following Inventories
- [ ] Enchantment Table (as a proper GUI)
- [ ] Beacon
- [ ] Cartography Table
- [ ] Stonecutter
- [ ] Structure Block
- [ ] Horse Inventory
- [ ] Loom
- [ ] Smithing Table
- Lecterns
- Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you)
- Resource pack conversion/CustomModelData
- Some Entity Flags
- The Following Inventories
- Enchantment Table (as a proper GUI)
- Beacon
- Cartography Table
- Stonecutter
- Structure Block
- Horse Inventory
- Loom
- Smithing Table
## What can't be fixed
The following things can't be fixed because of Bedrock limitations. They might be fixable in the future, but not as of now.
- Custom heads in inventories
## Compiling
1. Clone the repo to your computer

View File

@ -47,14 +47,12 @@ import java.util.concurrent.CompletableFuture;
@AllArgsConstructor
public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener {
private static final GeyserPendingConnection PENDING_CONNECTION = new GeyserPendingConnection();
private final ProxyServer proxyServer;
@Override
public GeyserPingInfo getPingInformation() {
public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
CompletableFuture<ProxyPingEvent> future = new CompletableFuture<>();
proxyServer.getPluginManager().callEvent(new ProxyPingEvent(PENDING_CONNECTION, getPingInfo(), (event, throwable) -> {
proxyServer.getPluginManager().callEvent(new ProxyPingEvent(new GeyserPendingConnection(inetSocketAddress), getPingInfo(), (event, throwable) -> {
if (throwable != null) future.completeExceptionally(throwable);
else future.complete(event);
}));
@ -89,7 +87,12 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
private static class GeyserPendingConnection implements PendingConnection {
private static final UUID FAKE_UUID = UUID.nameUUIDFromBytes("geyser!internal".getBytes());
private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69);
private final InetSocketAddress remote;
public GeyserPendingConnection(InetSocketAddress remote) {
this.remote = remote;
}
@Override
public String getName() {
@ -143,7 +146,7 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
@Override
public InetSocketAddress getAddress() {
return FAKE_REMOTE;
return remote;
}
@Override

View File

@ -67,7 +67,7 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
if (args.length == 1) {
return Arrays.asList("?", "help", "reload", "shutdown", "stop");
return connector.getCommandManager().getCommandNames();
}
return new ArrayList<>();
}

View File

@ -29,6 +29,11 @@
<version>3.2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.geysermc.adapters</groupId>
<artifactId>spigot-all</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<finalName>${outputName}-Spigot</finalName>

View File

@ -35,6 +35,7 @@ import org.geysermc.connector.common.ping.GeyserPingInfo;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Iterator;
@ -44,9 +45,9 @@ public class GeyserSpigotPingPassthrough implements IGeyserPingPassthrough {
private final GeyserSpigotLogger logger;
@Override
public GeyserPingInfo getPingInformation() {
public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
try {
ServerListPingEvent event = new GeyserPingEvent(InetAddress.getLocalHost(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers());
ServerListPingEvent event = new GeyserPingEvent(inetSocketAddress.getAddress(), Bukkit.getMotd(), Bukkit.getOnlinePlayers().size(), Bukkit.getMaxPlayers());
Bukkit.getPluginManager().callEvent(event);
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(event.getMotd(),
new GeyserPingInfo.Players(event.getMaxPlayers(), event.getNumPlayers()),

View File

@ -25,8 +25,10 @@
package org.geysermc.platform.spigot;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.adapters.spigot.SpigotAdapters;
import org.geysermc.common.PlatformType;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
@ -42,18 +44,23 @@ import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor;
import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager;
import org.geysermc.platform.spigot.command.SpigotCommandSender;
import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener;
import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager;
import org.geysermc.platform.spigot.world.manager.*;
import us.myles.ViaVersion.api.Pair;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.data.MappingData;
import us.myles.ViaVersion.api.protocol.Protocol;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
import us.myles.ViaVersion.api.protocol.ProtocolVersion;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
private GeyserSpigotCommandManager geyserCommandManager;
private GeyserSpigotConfiguration geyserConfig;
private GeyserSpigotLogger geyserLogger;
@ -142,8 +149,48 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// Set if we need to use a different method for getting a player's locale
SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0"));
this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion);
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion);
if (connector.getConfig().isUseAdapters()) {
try {
String name = Bukkit.getServer().getClass().getPackage().getName();
String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
SpigotAdapters.registerWorldAdapter(nmsVersion);
if (isViaVersion && isViaVersionNeeded()) {
if (isLegacy) {
// Pre-1.13
this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager();
} else {
// Post-1.13
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes);
}
} else {
// No ViaVersion
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(use3dBiomes);
}
geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion);
} catch (Exception e) {
if (geyserConfig.isDebugMode()) {
geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
e.printStackTrace();
}
}
} else {
geyserLogger.debug("Not using NMS adapter as it is disabled in the config.");
}
if (this.geyserWorldManager == null) {
// No NMS adapter
if (isLegacy && isViaVersion) {
// Use ViaVersion for converting pre-1.13 block states
this.geyserWorldManager = new GeyserSpigot1_12WorldManager();
} 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();
} else {
// Post-1.13
this.geyserWorldManager = new GeyserSpigotWorldManager(use3dBiomes);
}
geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass());
}
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
@ -152,8 +199,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
@Override
public void onDisable() {
if (connector != null)
if (connector != null) {
connector.shutdown();
}
}
@Override
@ -186,6 +234,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
return getDataFolder().toPath();
}
@Override
public BootstrapDumpInfo getDumpInfo() {
return new GeyserSpigotDumpInfo();
}
public boolean isCompatible(String version, String whichVersion) {
int[] currentVersion = parseVersion(version);
int[] otherVersion = parseVersion(whichVersion);
@ -213,15 +266,43 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
String t = stringArray[index].replaceAll("\\D", "");
try {
temp[index] = Integer.parseInt(t);
} catch(NumberFormatException ex) {
} catch (NumberFormatException ex) {
temp[index] = 0;
}
}
return temp;
}
@Override
public BootstrapDumpInfo getDumpInfo() {
return new GeyserSpigotDumpInfo();
/**
* @return the server version before ViaVersion finishes initializing
*/
public ProtocolVersion getServerProtocolVersion() {
String bukkitVersion = Bukkit.getServer().getVersion();
// Turn "(MC: 1.16.4)" into 1.16.4.
String version = bukkitVersion.split("\\(MC: ")[1].split("\\)")[0];
return ProtocolVersion.getClosest(version);
}
/**
* This function should not run unless ViaVersion is installed on the server.
*
* @return true if there is any block mappings difference between the server and client.
*/
private boolean isViaVersionNeeded() {
ProtocolVersion serverVersion = getServerProtocolVersion();
List<Pair<Integer, Protocol>> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION,
serverVersion.getVersion());
if (protocolList == null) {
// No translation needed!
return false;
}
for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getValue().getMappingData();
if (mappingData != null) {
return true;
}
}
// All mapping data is null, which means client and server block states are the same
return false;
}
}

View File

@ -67,7 +67,7 @@ public class GeyserSpigotCommandExecutor implements TabExecutor {
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 1) {
return Arrays.asList("?", "help", "reload", "shutdown", "stop");
return connector.getCommandManager().getCommandNames();
}
return new ArrayList<>();
}

View File

@ -36,13 +36,13 @@ import org.bukkit.event.block.BlockPlaceEvent;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.platform.spigot.world.manager.GeyserSpigotWorldManager;
@AllArgsConstructor
public class GeyserSpigotBlockPlaceListener implements Listener {
private final GeyserConnector connector;
private final boolean isLegacy;
private final boolean isViaVersion;
private final GeyserSpigotWorldManager worldManager;
@EventHandler
public void place(final BlockPlaceEvent event) {
@ -52,14 +52,13 @@ public class GeyserSpigotBlockPlaceListener implements Listener {
placeBlockSoundPacket.setSound(SoundEvent.PLACE);
placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()));
placeBlockSoundPacket.setBabySound(false);
String javaBlockId;
if (isLegacy) {
javaBlockId = BlockTranslator.getJavaIdBlockMap().inverse().get(GeyserSpigotWorldManager.getLegacyBlock(session,
event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ(), isViaVersion));
if (worldManager.isLegacy()) {
placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(worldManager.getBlockAt(session,
event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())));
} else {
javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
String javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID)));
}
placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, 0)));
placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null);

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-2020 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.platform.spigot.world.manager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.geysermc.adapters.spigot.SpigotAdapters;
import org.geysermc.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
/**
* Used with ViaVersion and pre-1.13.
*/
public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager {
private final SpigotWorldAdapter adapter;
public GeyserSpigot1_12NativeWorldManager() {
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 BlockTranslator.JAVA_AIR_ID;
}
// Get block entity storage
BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class);
int blockId = adapter.getBlockAt(player.getWorld(), x, y, z);
return getLegacyBlock(storage, blockId, x, y, z);
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2019-2020 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.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import us.myles.ViaVersion.api.Pair;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.data.MappingData;
import us.myles.ViaVersion.api.minecraft.Position;
import us.myles.ViaVersion.api.protocol.Protocol;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
import us.myles.ViaVersion.api.protocol.ProtocolVersion;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
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<Pair<Integer, Protocol>> protocolList;
public GeyserSpigot1_12WorldManager() {
super(false);
this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData();
this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION,
ProtocolVersion.v1_13.getVersion());
}
@Override
@SuppressWarnings("deprecation")
public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
if (player == null) {
return BlockTranslator.JAVA_AIR_ID;
}
// Get block entity storage
BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class);
Block block = player.getWorld().getBlockAt(x, y, z);
// Black magic that gets the old block state ID
int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
return getLegacyBlock(storage, blockId, 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
*/
@SuppressWarnings("deprecation")
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).getValue().getMappingData();
if (mappingData != null) {
blockId = mappingData.getNewBlockStateId(blockId);
}
}
return blockId;
}
@SuppressWarnings("deprecation")
@Override
public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) {
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
if (player == null) {
return;
}
World world = player.getWorld();
// Get block entity storage
BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class);
for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order
for (int blockZ = 0; blockZ < 16; blockZ++) {
for (int blockX = 0; blockX < 16; blockX++) {
Block block = world.getBlockAt(x, y, z);
// Black magic that gets the old block state ID
int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, blockId, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ));
}
}
}
}
@Override
public boolean isLegacy() {
return true;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2019-2020 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.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
/**
* 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() {
// Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes.
super(false);
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
return BlockTranslator.JAVA_AIR_ID;
}
@Override
public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) {
// Do nothing, since we can't do anything with the chunk
}
@Override
public boolean hasMoreBlockDataThanChunkCache() {
return false;
}
@Override
public boolean isLegacy() {
return true;
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2019-2020 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.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntList;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.platform.spigot.GeyserSpigotPlugin;
import us.myles.ViaVersion.api.Pair;
import us.myles.ViaVersion.api.data.MappingData;
import us.myles.ViaVersion.api.protocol.Protocol;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
import us.myles.ViaVersion.api.protocol.ProtocolVersion;
import java.util.List;
/**
* Used when block IDs need to be translated to the latest version
*/
public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorldManager {
private final Int2IntMap oldToNewBlockId;
public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) {
super(use3dBiomes);
IntList allBlockStates = adapter.getAllBlockStates();
oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size());
ProtocolVersion serverVersion = plugin.getServerProtocolVersion();
List<Pair<Integer, Protocol>> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION,
serverVersion.getVersion());
for (int oldBlockId : allBlockStates) {
int newBlockId = oldBlockId;
// protocolList should *not* be null; we checked for that before initializing this class
for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getValue().getMappingData();
if (mappingData != null) {
newBlockId = mappingData.getNewBlockStateId(newBlockId);
}
}
oldToNewBlockId.put(oldBlockId, newBlockId);
}
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
int nativeBlockId = super.getBlockAt(session, x, y, z);
return oldToNewBlockId.getOrDefault(nativeBlockId, nativeBlockId);
}
@Override
public boolean isLegacy() {
return true;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2019-2020 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.platform.spigot.world.manager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.geysermc.adapters.spigot.SpigotAdapters;
import org.geysermc.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
protected final SpigotWorldAdapter adapter;
public GeyserSpigotNativeWorldManager(boolean use3dBiomes) {
super(use3dBiomes);
adapter = SpigotAdapters.getWorldAdapter();
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
if (player == null) {
return BlockTranslator.JAVA_AIR_ID;
}
return adapter.getBlockAt(player.getWorld(), x, y, z);
}
}

View File

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.platform.spigot.world;
package org.geysermc.platform.spigot.world.manager;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.steveice10.mc.protocol.MinecraftConstants;
@ -34,6 +34,7 @@ import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
@ -42,38 +43,22 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.GameRule;
import org.geysermc.connector.utils.LanguageUtils;
import us.myles.ViaVersion.api.Pair;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.data.MappingData;
import us.myles.ViaVersion.api.minecraft.Position;
import us.myles.ViaVersion.api.protocol.Protocol;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
import us.myles.ViaVersion.api.protocol.ProtocolVersion;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
import java.io.InputStream;
import java.util.List;
/**
* The base world manager to use when there is no supported NMS revision
*/
public class GeyserSpigotWorldManager extends GeyserWorldManager {
/**
* The current client protocol version for ViaVersion usage.
*/
private static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION;
protected static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION;
/**
* Whether the server is pre-1.13.
*/
private final boolean isLegacy;
/**
* Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed.
*/
private final boolean use3dBiomes;
/**
* You need ViaVersion to connect to an older server with Geyser.
* However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server
*/
private final boolean isViaVersion;
/**
* Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs.
*
@ -87,10 +72,8 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
*/
private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length);
public GeyserSpigotWorldManager(boolean isLegacy, boolean use3dBiomes, boolean isViaVersion) {
this.isLegacy = isLegacy;
public GeyserSpigotWorldManager(boolean use3dBiomes) {
this.use3dBiomes = use3dBiomes;
this.isViaVersion = isViaVersion;
// Load the values into the biome-to-ID map
InputStream biomeStream = FileUtils.getResource("biomes.json");
@ -116,83 +99,26 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player bukkitPlayer;
if ((this.isLegacy && !this.isViaVersion)
|| session.getPlayerEntity() == null
|| (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return BlockTranslator.JAVA_AIR_ID;
}
World world = bukkitPlayer.getWorld();
if (isLegacy) {
return getLegacyBlock(session, x, y, z, true);
}
//TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below
return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), 0);
}
public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) {
if (isViaVersion) {
Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
// Get block entity storage
BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class);
return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z);
} else {
return BlockTranslator.JAVA_AIR_ID;
}
}
@SuppressWarnings("deprecation")
public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) {
Block block = world.getBlockAt(x, y, z);
// Black magic that gets the old block state ID
int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
// Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2
blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId);
List<Pair<Integer, Protocol>> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION,
ProtocolVersion.v1_13.getId());
// 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).getValue().getMappingData();
if (mappingData != null) {
blockId = mappingData.getNewBlockStateId(blockId);
}
}
return blockId;
return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID);
}
@Override
public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) {
Player bukkitPlayer;
if ((this.isLegacy && !this.isViaVersion)
|| session.getPlayerEntity() == null
|| (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return;
}
World world = bukkitPlayer.getWorld();
if (this.isLegacy) {
// Get block entity storage
BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class);
for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order
for (int blockZ = 0; blockZ < 16; blockZ++) {
for (int blockX = 0; blockX < 16; blockX++) {
chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ));
}
}
}
} else {
//TODO: see above TODO in getBlockAt
for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order
for (int blockZ = 0; blockZ < 16; blockZ++) {
for (int blockX = 0; blockX < 16; blockX++) {
Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ);
int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID);
chunk.set(blockX, blockY, blockZ, id);
}
for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order
for (int blockZ = 0; blockZ < 16; blockZ++) {
for (int blockX = 0; blockX < 16; blockX++) {
Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ);
int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID);
chunk.set(blockX, blockY, blockZ, id);
}
}
}
@ -254,4 +180,16 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
public boolean hasPermission(GeyserSession session, String permission) {
return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission);
}
/**
* 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
* to the current one.
*
* @return whether there is a difference between client block state and server block state that requires extra processing
*/
public boolean isLegacy() {
return false;
}
}

View File

@ -38,31 +38,29 @@ import org.spongepowered.api.network.status.StatusClient;
import org.spongepowered.api.profile.GameProfile;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.util.Optional;
public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough {
private static final GeyserStatusClient STATUS_CLIENT = new GeyserStatusClient();
private static final Cause CAUSE = Cause.of(EventContext.empty(), Sponge.getServer());
private static Method SpongeStatusResponse_create;
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public GeyserPingInfo getPingInformation() {
public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
// come on Sponge, this is in commons, why not expose it :(
ClientPingServerEvent event;
try {
if(SpongeStatusResponse_create == null) {
if (SpongeStatusResponse_create == null) {
Class SpongeStatusResponse = Class.forName("org.spongepowered.common.network.status.SpongeStatusResponse");
Class MinecraftServer = Class.forName("net.minecraft.server.MinecraftServer");
SpongeStatusResponse_create = SpongeStatusResponse.getDeclaredMethod("create", MinecraftServer);
}
Object response = SpongeStatusResponse_create.invoke(null, Sponge.getServer());
event = SpongeEventFactory.createClientPingServerEvent(CAUSE, STATUS_CLIENT, (ClientPingServerEvent.Response) response);
event = SpongeEventFactory.createClientPingServerEvent(CAUSE, new GeyserStatusClient(inetSocketAddress), (ClientPingServerEvent.Response) response);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
@ -76,7 +74,7 @@ public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough {
new GeyserPingInfo.Version(
event.getResponse().getVersion().getName(),
MinecraftConstants.PROTOCOL_VERSION) // thanks for also not exposing this sponge
);
);
event.getResponse().getPlayers().get().getProfiles().stream()
.map(GameProfile::getName)
.map(op -> op.orElseThrow(IllegalStateException::new))
@ -87,11 +85,15 @@ public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough {
@SuppressWarnings("NullableProblems")
private static class GeyserStatusClient implements StatusClient {
private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69);
private final InetSocketAddress remote;
public GeyserStatusClient(InetSocketAddress remote) {
this.remote = remote;
}
@Override
public InetSocketAddress getAddress() {
return FAKE_REMOTE;
return this.remote;
}
@Override

View File

@ -70,7 +70,7 @@ public class GeyserSpongeCommandExecutor implements CommandCallable {
@Override
public List<String> getSuggestions(CommandSource source, String arguments, @Nullable Location<World> targetPosition) throws CommandException {
if (arguments.split(" ").length == 1) {
return Arrays.asList("?", "help", "reload", "shutdown", "stop");
return connector.getCommandManager().getCommandNames();
}
return new ArrayList<>();
}

View File

@ -204,7 +204,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
} catch (IOException ex) {
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.config.failed"), ex);
System.exit(0);
if (gui == null) {
System.exit(1);
} else {
// Leave the process running so the GUI is still visible
return;
}
}
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);

View File

@ -93,7 +93,16 @@
<artifactSet>
<excludes>
<exclude>com.google.code.gson:*</exclude>
<exclude>io.netty:*</exclude>
<!-- Needed because Velocity provides every dependency except netty-resolver-dns -->
<exclude>io.netty:netty-transport-native-epoll:*</exclude>
<exclude>io.netty:netty-transport-native-unix-common:*</exclude>
<exclude>io.netty:netty-transport-native-kqueue:*</exclude>
<exclude>io.netty:netty-handler:*</exclude>
<exclude>io.netty:netty-common:*</exclude>
<exclude>io.netty:netty-buffer:*</exclude>
<exclude>io.netty:netty-resolver:*</exclude>
<exclude>io.netty:netty-transport:*</exclude>
<exclude>io.netty:netty-codec:*</exclude>
<exclude>org.slf4j:*</exclude>
<exclude>org.ow2.asm:*</exclude>
</excludes>

View File

@ -43,15 +43,13 @@ import java.util.concurrent.ExecutionException;
@AllArgsConstructor
public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough {
private static final GeyserInboundConnection FAKE_INBOUND_CONNECTION = new GeyserInboundConnection();
private final ProxyServer server;
@Override
public GeyserPingInfo getPingInformation() {
public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
ProxyPingEvent event;
try {
event = server.getEventManager().fire(new ProxyPingEvent(FAKE_INBOUND_CONNECTION, ServerPing.builder()
event = server.getEventManager().fire(new ProxyPingEvent(new GeyserInboundConnection(inetSocketAddress), ServerPing.builder()
.description(server.getConfiguration().getMotdComponent()).onlinePlayers(server.getPlayerCount())
.maximumPlayers(server.getConfiguration().getShowMaxPlayers()).build())).get();
} catch (ExecutionException | InterruptedException e) {
@ -74,11 +72,15 @@ public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough {
private static class GeyserInboundConnection implements InboundConnection {
private static final InetSocketAddress FAKE_REMOTE = new InetSocketAddress(Inet4Address.getLoopbackAddress(), 69);
private final InetSocketAddress remote;
public GeyserInboundConnection(InetSocketAddress remote) {
this.remote = remote;
}
@Override
public InetSocketAddress getRemoteAddress() {
return FAKE_REMOTE;
return this.remote;
}
@Override

View File

@ -25,8 +25,8 @@
package org.geysermc.platform.velocity.command;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import lombok.AllArgsConstructor;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
@ -34,29 +34,39 @@ import org.geysermc.connector.command.GeyserCommand;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@AllArgsConstructor
public class GeyserVelocityCommandExecutor implements Command {
public class GeyserVelocityCommandExecutor implements SimpleCommand {
private final GeyserConnector connector;
@Override
public void execute(CommandSource source, String[] args) {
if (args.length > 0) {
if (getCommand(args[0]) != null) {
if (!source.hasPermission(getCommand(args[0]).getPermission())) {
CommandSender sender = new VelocityCommandSender(source);
public void execute(Invocation invocation) {
if (invocation.arguments().length > 0) {
if (getCommand(invocation.arguments()[0]) != null) {
if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) {
CommandSender sender = new VelocityCommandSender(invocation.source());
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale()));
return;
}
getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
getCommand(invocation.arguments()[0]).execute(new VelocityCommandSender(invocation.source()), invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]);
}
} else {
getCommand("help").execute(new VelocityCommandSender(source), new String[0]);
getCommand("help").execute(new VelocityCommandSender(invocation.source()), new String[0]);
}
}
@Override
public List<String> suggest(Invocation invocation) {
if (invocation.arguments().length == 0) {
return connector.getCommandManager().getCommandNames();
}
return new ArrayList<>();
}
private GeyserCommand getCommand(String label) {
return connector.getCommandManager().getCommands().get(label);
}

View File

@ -30,16 +30,27 @@
</dependency>
<dependency>
<groupId>com.github.CloudburstMC.Protocol</groupId>
<artifactId>bedrock-v419</artifactId>
<version>ce59d39118</version>
<artifactId>bedrock-v422</artifactId>
<version>87d862d69d</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>net.sf.trove4j</groupId>
<artifactId>trove</artifactId>
</exclusion>
<!-- Stay on the older version of Network while it's rewritten -->
<exclusion>
<groupId>com.nukkitx.network</groupId>
<artifactId>raknet</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.nukkitx.network</groupId>
<artifactId>raknet</artifactId>
<version>1.6.20</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-int-maps</artifactId>
@ -109,13 +120,17 @@
<dependency>
<groupId>com.github.steveice10</groupId>
<artifactId>mcprotocollib</artifactId>
<version>86e1901be5</version>
<version>26201a4</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -137,19 +152,25 @@
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-api</artifactId>
<version>4.1.1</version>
<version>4.3.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.kyoripowered.adventure</groupId>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4d8a67d798</version>
<version>4.3.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.kyoripowered.adventure</groupId>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-legacy</artifactId>
<version>0599048</version>
<version>4.3.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
<version>4.3.0</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -214,8 +235,12 @@
</includes>
<replacements>
<replacement>
<token>VERSION = ".*"</token>
<value>VERSION = "${project.version} (git-${git.branch}-${git.commit.id.abbrev})"</value>
<token>String VERSION = ".*"</token>
<value>String VERSION = "${project.version} (" + GIT_VERSION + ")"</value>
</replacement>
<replacement>
<token>String GIT_VERSION = ".*"</token>
<value>String GIT_VERSION = "git-${git.branch}-${git.commit.id.abbrev}"</value>
</replacement>
</replacements>
</configuration>
@ -233,8 +258,12 @@
</includes>
<replacements>
<replacement>
<token>VERSION = ".*"</token>
<value>VERSION = "DEV"</value>
<token>String VERSION = ".*"</token>
<value>String VERSION = "DEV"</value>
</replacement>
<replacement>
<token>String GIT_VERSION = ".*"</token>
<value>String GIT_VERSION = "DEV"</value>
</replacement>
</replacements>
</configuration>
@ -256,6 +285,8 @@
<script><![CDATA[
new org.reflections.Reflections("org.geysermc.connector.network.translators")
.save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators-reflections.xml")
new org.reflections.Reflections("org.geysermc.connector.network.translators.collision.translators")
.save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators.collision.translators-reflections.xml")
new org.reflections.Reflections("org.geysermc.connector.network.translators.item")
.save("${project.artifactId}/target/classes/META-INF/reflections/org.geysermc.connector.network.translators.item-reflections.xml")
new org.reflections.Reflections("org.geysermc.connector.network.translators.sound")

View File

@ -54,6 +54,8 @@ import org.geysermc.connector.network.translators.sound.SoundRegistry;
import org.geysermc.connector.network.translators.world.WorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
import org.geysermc.connector.network.translators.collision.CollisionTranslator;
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
import org.geysermc.connector.utils.DimensionUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
@ -82,9 +84,14 @@ import java.util.concurrent.TimeUnit;
@Getter
public class GeyserConnector {
public static final ObjectMapper JSON_MAPPER = new ObjectMapper().enable(JsonParser.Feature.IGNORE_UNDEFINED).enable(JsonParser.Feature.ALLOW_COMMENTS).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
public static final ObjectMapper JSON_MAPPER = new ObjectMapper()
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
.enable(JsonParser.Feature.ALLOW_COMMENTS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
public static final String NAME = "Geyser";
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
public static final String VERSION = "DEV"; // A fallback for running in IDEs
private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";
@ -141,6 +148,7 @@ public class GeyserConnector {
EntityIdentifierRegistry.init();
ItemRegistry.init();
ItemTranslator.init();
CollisionTranslator.init();
LocaleUtils.init();
PotionMixRegistry.init();
RecipeRegistry.init();
@ -198,6 +206,7 @@ public class GeyserConnector {
}
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls();
// https://github.com/GeyserMC/Geyser/issues/957
RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu();
@ -216,7 +225,6 @@ public class GeyserConnector {
if (config.getMetrics().isEnabled()) {
metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger(""));
metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1));
metrics.addCustomChart(new Metrics.SingleLineChart("players", players::size));
// Prevent unwanted words best we can
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString().toLowerCase()));

View File

@ -31,9 +31,7 @@ import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.defaults.*;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
public abstract class CommandManager {
@ -52,6 +50,7 @@ public abstract class CommandManager {
registerCommand(new OffhandCommand(connector, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand"));
registerCommand(new DumpCommand(connector, "dump", "geyser.commands.dump.desc", "geyser.command.dump"));
registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version"));
registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings"));
registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
}
@ -92,6 +91,13 @@ public abstract class CommandManager {
cmd.execute(sender, args);
}
/**
* @return a list of all subcommands under {@code /geyser}.
*/
public List<String> getCommandNames() {
return Arrays.asList(connector.getCommandManager().getCommands().keySet().toArray(new String[0]));
}
/**
* Returns the description of the given command
*

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2019-2020 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.connector.command.defaults;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.command.GeyserCommand;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.SettingsUtils;
public class SettingsCommand extends GeyserCommand {
private final GeyserConnector connector;
public SettingsCommand(GeyserConnector connector, String name, String description, String permission) {
super(name, description, permission);
this.connector = connector;
}
@Override
public void execute(CommandSender sender, String[] args) {
// Make sure the sender is a Bedrock edition client
GeyserSession session = null;
if (sender instanceof GeyserSession) {
session = (GeyserSession) sender;
} else {
// Needed for Spigot - sender is not an instance of GeyserSession
for (GeyserSession otherSession : connector.getPlayers()) {
if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) {
session = otherSession;
break;
}
}
}
if (session == null) return;
SettingsUtils.buildForm(session);
session.sendForm(session.getSettingsForm(), SettingsUtils.SETTINGS_FORM_ID);
}
@Override
public boolean isExecutableOnConsole() {
return false;
}
}

View File

@ -71,6 +71,8 @@ public interface GeyserConfiguration {
boolean isShowCooldown();
boolean isShowCoordinates();
String getDefaultLocale();
Path getFloodgateKeyPath();
@ -85,6 +87,8 @@ public interface GeyserConfiguration {
int getCacheImages();
boolean isAllowCustomSkulls();
IMetricsInfo getMetrics();
interface IBedrockConfiguration {
@ -107,7 +111,7 @@ public interface GeyserConfiguration {
String getAddress();
int getPort();
void setAddress(String address);
void setPort(int port);
@ -135,6 +139,8 @@ public interface GeyserConfiguration {
int getMtu();
boolean isUseAdapters();
int getConfigVersion();
static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) {

View File

@ -89,6 +89,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("show-cooldown")
private boolean showCooldown = true;
@JsonProperty("show-coordinates")
private boolean showCoordinates = true;
@JsonProperty("allow-third-party-ears")
private boolean allowThirdPartyEars = false;
@ -101,6 +104,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("cache-images")
private int cacheImages = 0;
@JsonProperty("allow-custom-skulls")
private boolean allowCustomSkulls = true;
@JsonProperty("above-bedrock-nether-building")
private boolean aboveBedrockNetherBuilding = false;
@ -170,6 +176,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("mtu")
private int mtu = 1400;
@JsonProperty("use-adapters")
private boolean useAdapters = true;
@JsonProperty("config-version")
private int configVersion = 0;
}

View File

@ -35,6 +35,16 @@ import java.util.concurrent.TimeUnit;
public class BoatEntity extends Entity {
/**
* Required when IS_BUOYANT is sent in order for boats to work in the water. <br>
*
* Taken from BDS 1.16.200, with the modification of <code>simulate_waves</code> since Java doesn't bob the boat up and down
* like Bedrock.
*/
private static final String BUOYANCY_DATA = "{\"apply_gravity\":true,\"base_buoyancy\":1.0,\"big_wave_probability\":0.02999999932944775," +
"\"big_wave_speed\":10.0,\"drag_down_on_buoyancy_removed\":0.0,\"liquid_blocks\":[\"minecraft:water\"," +
"\"minecraft:flowing_water\"],\"simulate_waves\":false}}";
private boolean isPaddlingLeft;
private float paddleTimeLeft;
private boolean isPaddlingRight;
@ -45,6 +55,10 @@ public class BoatEntity extends Entity {
public BoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation.add(90, 0, 90));
// Required to be able to move on land 1.16.200+ or apply gravity not in the water 1.16.100+
metadata.put(EntityData.IS_BUOYANT, (byte) 1);
metadata.put(EntityData.BUOYANCY_DATA, BUOYANCY_DATA);
}
@Override

View File

@ -28,6 +28,7 @@ package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import net.kyori.adventure.text.Component;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
@ -50,7 +51,7 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue());
}
if (entityMetadata.getId() == 14) {
metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue().toString()));
metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage((Component) entityMetadata.getValue()));
}
super.updateBedrockMetadata(entityMetadata, session);
}

View File

@ -39,6 +39,8 @@ public class EnderCrystalEntity extends Entity {
public EnderCrystalEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
// Bedrock 1.16.100+ - prevents the entity from appearing on fire itself when fire is underneath it
metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
}
@Override

View File

@ -32,11 +32,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.data.message.Message;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;
@ -46,14 +44,15 @@ import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.geysermc.connector.entity.attribute.Attribute;
import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.entity.living.ArmorStandEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.connector.utils.ChunkUtils;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import java.util.ArrayList;
@ -314,11 +313,11 @@ public class Entity {
}
break;
case 2: // custom name
if (entityMetadata.getValue() instanceof Message) {
Message message = (Message) entityMetadata.getValue();
if (entityMetadata.getValue() instanceof Component) {
Component message = (Component) entityMetadata.getValue();
if (message != null)
// Always translate even if it's a TextMessage since there could be translatable parameters
metadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(message.toString(), session.getLocale()));
metadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(message, session.getLocale()));
}
break;
case 3: // is custom name visible

View File

@ -36,6 +36,7 @@ import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.FireworkColor;

View File

@ -36,7 +36,20 @@ import org.geysermc.connector.network.translators.item.ItemTranslator;
public class ItemEntity extends Entity {
public ItemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation);
}
@Override
public void setMotion(Vector3f motion) {
if (isOnGround())
motion = Vector3f.from(motion.getX(), 0, motion.getZ());
super.setMotion(motion);
}
@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), rotation, isOnGround, teleported);
}
@Override
@ -44,7 +57,7 @@ public class ItemEntity extends Entity {
if (entityMetadata.getId() == 7) {
AddItemEntityPacket itemPacket = new AddItemEntityPacket();
itemPacket.setRuntimeEntityId(geyserId);
itemPacket.setPosition(position);
itemPacket.setPosition(position.add(0d, this.entityType.getOffset(), 0d));
itemPacket.setMotion(motion);
itemPacket.setUniqueEntityId(geyserId);
itemPacket.setFromFishing(false);

View File

@ -77,6 +77,9 @@ public class LivingEntity extends Entity {
getHand().equals(ItemData.AIR) && getOffHand().getId() == ItemRegistry.SHIELD.getBedrockId());
metadata.getFlags().setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield);
metadata.getFlags().setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01);
// Riptide spin attack
metadata.getFlags().setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04);
break;
case 8:
metadata.put(EntityData.HEALTH, entityMetadata.getValue());

View File

@ -53,7 +53,7 @@ public class ArmorStandEntity extends LivingEntity {
position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d);
}
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
super.moveAbsolute(session, position, Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()), isOnGround, teleported);
}
@Override
@ -95,4 +95,10 @@ public class ArmorStandEntity extends LivingEntity {
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void spawnEntity(GeyserSession session) {
this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX());
super.spawnEntity(session);
}
}

View File

@ -60,6 +60,11 @@ public class WolfEntity extends TameableEntity {
// Relies on EntityData.OWNER_EID being set in TameableEntity.java
if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) {
metadata.put(EntityData.COLOR, collarColor = (byte) (int) entityMetadata.getValue());
if (!metadata.containsKey(EntityData.OWNER_EID)) {
// If a color is set and there is no owner entity ID, set one.
// Otherwise, the entire wolf is set to that color: https://user-images.githubusercontent.com/9083212/99209989-92691200-2792-11eb-911d-9a315c955be9.png
metadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId());
}
}
// Wolf anger (1.16+)

View File

@ -23,11 +23,10 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.entity;
package org.geysermc.connector.entity.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.message.TextMessage;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
@ -43,12 +42,14 @@ import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.LivingEntity;
import org.geysermc.connector.entity.attribute.Attribute;
import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.EntityEffectCache;
import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
@ -64,9 +65,7 @@ public class PlayerEntity extends LivingEntity {
private GameProfile profile;
private UUID uuid;
private String username;
private long lastSkinUpdate = -1;
private boolean playerList = true; // Player is in the player list
private final EntityEffectCache effectCache;
/**
* Saves the parrot currently on the player's left shoulder; otherwise null
@ -83,14 +82,10 @@ public class PlayerEntity extends LivingEntity {
profile = gameProfile;
uuid = gameProfile.getId();
username = gameProfile.getName();
effectCache = new EntityEffectCache();
if (geyserId == 1) valid = true;
}
@Override
public void spawnEntity(GeyserSession session) {
if (geyserId == 1) return;
AddPlayerPacket addPlayerPacket = new AddPlayerPacket();
addPlayerPacket.setUuid(uuid);
addPlayerPacket.setUsername(username);
@ -161,6 +156,10 @@ public class PlayerEntity extends LivingEntity {
setRotation(rotation);
this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ);
// If this is the player logged in through this Geyser session
if (geyserId == 1) {
session.getCollisionManager().updatePlayerBoundingBox(position);
}
setOnGround(isOnGround);
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
@ -232,7 +231,18 @@ public class PlayerEntity extends LivingEntity {
@Override
public void setPosition(Vector3f position) {
this.position = position.add(0, entityType.getOffset(), 0);
setPosition(position, true);
}
/**
* Set the player position and specify if the entity type's offset should be added. Set to false when the player
* sends us a move packet where the offset is already added
*
* @param position the new position of the Bedrock player
* @param includeOffset whether to include the offset
*/
public void setPosition(Vector3f position, boolean includeOffset) {
this.position = includeOffset ? position.add(0, entityType.getOffset(), 0) : position;
}
@Override
@ -241,9 +251,9 @@ public class PlayerEntity extends LivingEntity {
if (entityMetadata.getId() == 2) {
String username = this.username;
TextMessage name = (TextMessage) entityMetadata.getValue();
Component name = (Component) entityMetadata.getValue();
if (name != null) {
username = MessageTranslator.convertMessage(name.toString());
username = MessageTranslator.convertMessage(name);
}
Team team = session.getWorldCache().getScoreboard().getTeamFor(username);
if (team != null) {

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2019-2020 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.connector.entity.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.UUID;
/**
* The entity class specifically for a {@link GeyserSession}'s player.
*/
public class SessionPlayerEntity extends PlayerEntity {
private final GeyserSession session;
public SessionPlayerEntity(GeyserSession session) {
super(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
valid = true;
this.session = session;
this.session.getCollisionManager().updatePlayerBoundingBox(position);
}
@Override
public void spawnEntity(GeyserSession session) {
// Already logged in
}
@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
session.getCollisionManager().updatePlayerBoundingBox(position);
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
}
@Override
public void setPosition(Vector3f position) {
if (session != null) { // null during entity initialization
session.getCollisionManager().updatePlayerBoundingBox(position);
}
super.setPosition(position);
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2019-2020 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.connector.entity.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.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.network.session.GeyserSession;
/**
* A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no
* custom player skulls in Bedrock.
*/
public class SkullPlayerEntity extends PlayerEntity {
/**
* Stores the block state that the skull is associated with. Used to determine if the block in the skull's position
* has changed
*/
@Getter
@Setter
private int blockState;
public SkullPlayerEntity(GameProfile gameProfile, long geyserId, Vector3f position, Vector3f rotation) {
super(gameProfile, 0, geyserId, position, Vector3f.ZERO, rotation);
setPlayerList(false);
//Set bounding box to almost nothing so the skull is able to be broken and not cause entity to cast a shadow
metadata.clear();
metadata.put(EntityData.SCALE, 1.08f);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.001f);
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.001f);
metadata.getOrCreateFlags().setFlag(EntityFlag.CAN_SHOW_NAME, false);
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); // Until the skin is loaded
}
public void despawnEntity(GeyserSession session, Vector3i position) {
this.despawnEntity(session);
session.getSkullCache().remove(position, this);
}
}

View File

@ -37,6 +37,7 @@ import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity;
import org.geysermc.connector.entity.living.monster.raid.PillagerEntity;
import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity;
import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
@Getter
public enum EntityType {
@ -99,7 +100,7 @@ public enum EntityType {
ARMOR_STAND(ArmorStandEntity.class, 61, 1.975f, 0.5f),
TRIPOD_CAMERA(Entity.class, 62, 0f),
PLAYER(PlayerEntity.class, 63, 1.8f, 0.6f, 0.6f, 1.62f),
ITEM(ItemEntity.class, 64, 0.25f, 0.25f),
ITEM(ItemEntity.class, 64, 0.25f, 0.25f, 0.25f, 0.125f),
PRIMED_TNT(TNTEntity.class, 65, 0.98f, 0.98f, 0.98f, 0f, "minecraft:tnt"),
FALLING_BLOCK(FallingBlockEntity.class, 66, 0.98f, 0.98f),
MOVING_BLOCK(Entity.class, 67, 0f),

View File

@ -27,6 +27,7 @@ package org.geysermc.connector.network;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.v419.Bedrock_v419;
import com.nukkitx.protocol.bedrock.v422.Bedrock_v422;
import java.util.ArrayList;
import java.util.List;
@ -39,13 +40,16 @@ public class BedrockProtocol {
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
*/
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v419.V419_CODEC;
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v422.V422_CODEC;
/**
* A list of all supported Bedrock versions that can join Geyser
*/
public static final List<BedrockPacketCodec> SUPPORTED_BEDROCK_CODECS = new ArrayList<>();
static {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v419.V419_CODEC.toBuilder()
.minecraftVersion("1.16.100/1.16.101") // We change this as 1.16.100.60 is a beta
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
}

View File

@ -63,7 +63,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
GeyserPingInfo pingInfo = null;
if (config.isPassthroughMotd() || config.isPassthroughPlayerCounts()) {
IGeyserPingPassthrough pingPassthrough = connector.getBootstrap().getGeyserPingPassthrough();
pingInfo = pingPassthrough.getPingInformation();
pingInfo = pingPassthrough.getPingInformation(inetSocketAddress);
}
BedrockPong pong = new BedrockPong();

View File

@ -174,7 +174,7 @@ public class QueryPacketHandler {
gameData.put("hostname", motd);
gameData.put("gametype", "SMP");
gameData.put("game_id", "MINECRAFT");
gameData.put("version", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion());
gameData.put("version", GeyserConnector.NAME + " (" + GeyserConnector.GIT_VERSION + ") " + BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion());
gameData.put("plugins", "");
gameData.put("map", map);
gameData.put("numplayers", currentPlayerCount);

View File

@ -80,7 +80,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
for(ResourcePack resourcePack : ResourcePack.PACKS.values()) {
ResourcePackManifest.Header header = resourcePack.getManifest().getHeader();
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(), "", "", "", false));
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(
header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(),
"", "", "", false, false));
}
resourcePacksInfo.setForcedToAccept(GeyserConnector.getInstance().getConfig().isForceResourcePacks());
session.sendUpstreamPacket(resourcePacksInfo);
@ -178,7 +180,13 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public boolean handle(MovePlayerPacket packet) {
if (session.isLoggingIn()) {
session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale()));
SetTitlePacket titlePacket = new SetTitlePacket();
titlePacket.setType(SetTitlePacket.Type.ACTIONBAR);
titlePacket.setText(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale()));
titlePacket.setFadeInTime(0);
titlePacket.setFadeOutTime(1);
titlePacket.setStayTime(2);
session.sendUpstreamPacket(titlePacket);
}
return translateAndDefault(packet);

View File

@ -35,6 +35,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.statistic.Statistic;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket;
@ -49,11 +50,14 @@ import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.*;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
@ -61,7 +65,8 @@ import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.SessionPlayerEntity;
import org.geysermc.connector.entity.player.SkullPlayerEntity;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.remote.RemoteServer;
import org.geysermc.connector.network.session.auth.AuthData;
@ -71,8 +76,10 @@ import org.geysermc.connector.network.translators.BiomeTranslator;
import org.geysermc.connector.network.translators.EntityIdentifierRegistry;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.skin.SkinManager;
import org.geysermc.connector.utils.*;
import org.geysermc.cumulus.Form;
import org.geysermc.cumulus.util.FormBuilder;
@ -82,6 +89,7 @@ import org.geysermc.floodgate.util.BedrockData;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
@ -97,18 +105,23 @@ public class GeyserSession implements CommandSender {
@Setter
private BedrockClientData clientData;
private PlayerEntity playerEntity;
private final SessionPlayerEntity playerEntity;
private PlayerInventory inventory;
private ChunkCache chunkCache;
private EntityCache entityCache;
private EntityEffectCache effectCache;
private InventoryCache inventoryCache;
private WorldCache worldCache;
private FormCache formCache;
@Setter
private TeleportCache teleportCache;
private final Int2ObjectMap<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
@Getter
/**
* Stores session collision
*/
private final CollisionManager collisionManager;
private final Map<Vector3i, SkullPlayerEntity> skullCache = new ConcurrentHashMap<>();
private final Long2ObjectMap<ClientboundMapItemDataPacket> storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
/**
@ -133,7 +146,6 @@ public class GeyserSession implements CommandSender {
private final AtomicInteger pendingDimSwitches = new AtomicInteger(0);
@Setter
private boolean sneaking;
@Setter
@ -181,12 +193,6 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastWindowCloseTime = 0;
/**
* Saves the timestamp of the last keep alive packet
*/
@Setter
private long lastKeepAliveTimestamp = 0;
@Setter
private VillagerTrade[] villagerTrades;
@Setter
@ -210,6 +216,17 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastHitTime;
/**
* Saves if the client is steering left on a boat.
*/
@Setter
private boolean steeringLeft;
/**
* Saves if the client is steering right on a boat.
*/
@Setter
private boolean steeringRight;
/**
* Store the last time the player interacted. Used to fix a right-click spam bug.
* See https://github.com/GeyserMC/Geyser/issues/503 for context.
@ -307,11 +324,14 @@ public class GeyserSession implements CommandSender {
this.chunkCache = new ChunkCache(this);
this.entityCache = new EntityCache(this);
this.effectCache = new EntityEffectCache();
this.inventoryCache = new InventoryCache(this);
this.worldCache = new WorldCache(this);
this.formCache = new FormCache(this);
this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
this.collisionManager = new CollisionManager(this);
this.playerEntity = new SessionPlayerEntity(this);
this.inventory = new PlayerInventory();
this.spawned = false;
@ -333,6 +353,9 @@ public class GeyserSession implements CommandSender {
startGame();
this.remoteServer = remoteServer;
// Set the hardcoded shield ID to the ID we just defined in StartGamePacket
upstream.getSession().getHardcodedBlockingId().set(ItemRegistry.SHIELD.getBedrockId());
ChunkUtils.sendEmptyChunks(this, playerEntity.getPosition().toInt(), 0, false);
BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket();
@ -367,6 +390,8 @@ public class GeyserSession implements CommandSender {
// Don't let the client modify the inventory on death
// Setting this to true allows keep inventory to work if enabled but doesn't break functionality being false
gamerulePacket.getGameRules().add(new GameRuleData<>("keepinventory", true));
// Ensure client doesn't try and do anything funky; the server handles this for us
gamerulePacket.getGameRules().add(new GameRuleData<>("spawnradius", 0));
upstream.sendPacket(gamerulePacket);
}
@ -508,7 +533,7 @@ public class GeyserSession implements CommandSender {
// Check if they are not using a linked account
if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
SkinUtils.handleBedrockSkin(playerEntity, clientData);
SkinManager.handleBedrockSkin(playerEntity, clientData);
}
}
@ -550,6 +575,7 @@ public class GeyserSession implements CommandSender {
this.chunkCache = null;
this.entityCache = null;
this.effectCache = null;
this.worldCache = null;
this.inventoryCache = null;
this.formCache = null;
@ -565,6 +591,12 @@ public class GeyserSession implements CommandSender {
this.authData = authData;
}
public void setSneaking(boolean sneaking) {
this.sneaking = sneaking;
collisionManager.updatePlayerBoundingBox();
collisionManager.updateScaffoldingFlags();
}
@Override
public String getName() {
return authData.getName();
@ -637,7 +669,7 @@ public class GeyserSession implements CommandSender {
startGamePacket.setLightningLevel(0);
startGamePacket.setMultiplayerGame(true);
startGamePacket.setBroadcastingToLan(true);
startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true));
startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", connector.getConfig().isShowCoordinates()));
startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setCommandsEnabled(!connector.getConfig().isXboxAchievementsEnabled());
@ -668,18 +700,75 @@ public class GeyserSession implements CommandSender {
upstream.sendPacket(startGamePacket);
}
public boolean confirmTeleport(Vector3d position) {
if (teleportCache != null) {
if (!teleportCache.canConfirm(position)) {
GeyserConnector.getInstance().getLogger().debug("Unconfirmed Teleport " + teleportCache.getTeleportConfirmId()
+ " Ignore movement " + position + " expected " + teleportCache);
return false;
public void addTeleport(TeleportCache teleportCache) {
teleportMap.put(teleportCache.getTeleportConfirmId(), teleportCache);
ObjectIterator<Int2ObjectMap.Entry<TeleportCache>> it = teleportMap.int2ObjectEntrySet().iterator();
// Remove any teleports with a higher number - maybe this is a world change that reset the ID to 0?
while (it.hasNext()) {
Int2ObjectMap.Entry<TeleportCache> entry = it.next();
int nextID = entry.getValue().getTeleportConfirmId();
if (nextID > teleportCache.getTeleportConfirmId()) {
it.remove();
}
int teleportId = teleportCache.getTeleportConfirmId();
teleportCache = null;
ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(teleportId);
sendDownstreamPacket(teleportConfirmPacket);
}
}
public boolean confirmTeleport(Vector3d position) {
if (teleportMap.size() == 0) {
return true;
}
int teleportID = -1;
for (Int2ObjectMap.Entry<TeleportCache> entry : teleportMap.int2ObjectEntrySet()) {
if (entry.getValue().canConfirm(position)) {
if (entry.getValue().getTeleportConfirmId() > teleportID) {
teleportID = entry.getValue().getTeleportConfirmId();
}
}
}
ObjectIterator<Int2ObjectMap.Entry<TeleportCache>> it = teleportMap.int2ObjectEntrySet().iterator();
if (teleportID != -1) {
// Confirm the current teleport and any earlier ones
while (it.hasNext()) {
TeleportCache entry = it.next().getValue();
int nextID = entry.getTeleportConfirmId();
if (nextID <= teleportID) {
ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(nextID);
sendDownstreamPacket(teleportConfirmPacket);
// Servers (especially ones like Hypixel) expect exact coordinates given back to them.
ClientPlayerPositionRotationPacket positionPacket = new ClientPlayerPositionRotationPacket(playerEntity.isOnGround(),
entry.getX(), entry.getY(), entry.getZ(), entry.getYaw(), entry.getPitch());
sendDownstreamPacket(positionPacket);
it.remove();
connector.getLogger().debug("Confirmed teleport " + nextID);
}
}
}
if (teleportMap.size() > 0) {
int resendID = -1;
for (Int2ObjectMap.Entry<TeleportCache> entry : teleportMap.int2ObjectEntrySet()) {
TeleportCache teleport = entry.getValue();
teleport.incrementUnconfirmedFor();
if (teleport.shouldResend()) {
if (teleport.getTeleportConfirmId() >= resendID) {
resendID = teleport.getTeleportConfirmId();
}
}
}
if (resendID != -1) {
connector.getLogger().debug("Resending teleport " + resendID);
TeleportCache teleport = teleportMap.get(resendID);
getPlayerEntity().moveAbsolute(this, Vector3f.from(teleport.getX(), teleport.getY(), teleport.getZ()),
teleport.getYaw(), teleport.getPitch(), playerEntity.isOnGround(), true);
}
}
return true;
}

View File

@ -25,13 +25,13 @@
package org.geysermc.connector.network.session.cache;
import com.github.steveice10.mc.protocol.data.message.Message;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import com.nukkitx.protocol.bedrock.packet.BossEventPacket;
import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket;
import lombok.AllArgsConstructor;
import net.kyori.adventure.text.Component;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
@ -41,7 +41,7 @@ public class BossBar {
private GeyserSession session;
private long entityId;
private Message title;
private Component title;
private float health;
private int color;
private int overlay;
@ -58,7 +58,7 @@ public class BossBar {
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setAction(BossEventPacket.Action.CREATE);
bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale()));
bossEventPacket.setTitle(MessageTranslator.convertMessage(title, session.getLocale()));
bossEventPacket.setHealthPercentage(health);
bossEventPacket.setColor(color); //ignored by client
bossEventPacket.setOverlay(overlay);
@ -67,12 +67,12 @@ public class BossBar {
session.sendUpstreamPacket(bossEventPacket);
}
public void updateTitle(Message title) {
public void updateTitle(Component title) {
this.title = title;
BossEventPacket bossEventPacket = new BossEventPacket();
bossEventPacket.setBossUniqueEntityId(entityId);
bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME);
bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale()));
bossEventPacket.setTitle(MessageTranslator.convertMessage(title, session.getLocale()));
session.sendUpstreamPacket(bossEventPacket);
}

View File

@ -29,7 +29,7 @@ import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.*;

View File

@ -26,23 +26,47 @@
package org.geysermc.connector.network.session.cache;
import com.nukkitx.math.vector.Vector3d;
import com.nukkitx.math.vector.Vector3f;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@AllArgsConstructor
/**
* Represents a teleport ID and corresponding coordinates that need to be confirmed. <br>
*
* The vanilla Java client, after getting a
* {@link com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket},
* adjusts the player's positions and immediately sends a teleport back. However, we want to acknowledge that the
* Bedrock player actually moves close to that point, so we store the teleport until we get a movement packet from
* Bedrock that the teleport was successful.
*/
@RequiredArgsConstructor
@Data
public class TeleportCache {
private static final double ERROR = 0.2;
private static final double ERROR_Y = 0.5;
private static final double ERROR_X_AND_Z = 0.1;
private static final double ERROR_Y = 0.1;
private double x, y, z;
private int teleportConfirmId;
/**
* How many move packets the teleport can be unconfirmed for before it gets resent to the client
*/
private static final int RESEND_THRESHOLD = 5;
private final double x, y, z;
private final float pitch, yaw;
private final int teleportConfirmId;
private int unconfirmedFor = 0;
public boolean canConfirm(Vector3d position) {
return (Math.abs(this.x - position.getX()) < ERROR &&
return (Math.abs(this.x - position.getX()) < ERROR_X_AND_Z &&
Math.abs(this.y - position.getY()) < ERROR_Y &&
Math.abs(this.z - position.getZ()) < ERROR);
Math.abs(this.z - position.getZ()) < ERROR_X_AND_Z);
}
public void incrementUnconfirmedFor() {
unconfirmedFor++;
}
public boolean shouldResend() {
return unconfirmedFor >= RESEND_THRESHOLD;
}
}

View File

@ -25,23 +25,19 @@
package org.geysermc.connector.network.translators.bedrock;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerSwingArmPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientSteerBoatPacket;
import com.nukkitx.protocol.bedrock.packet.AnimatePacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import java.util.concurrent.TimeUnit;
@Translator(packet = AnimatePacket.class)
public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
private boolean isSteeringLeft;
private boolean isSteeringRight;
@Override
public void translate(AnimatePacket packet, GeyserSession session) {
// Stop the player sending animations before they have fully spawned into the server
@ -61,13 +57,13 @@ public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
// These two might need to be flipped, but my recommendation is getting moving working first
case ROW_LEFT:
// Packet value is a float of how long one has been rowing, so we convert that into a boolean
isSteeringLeft = packet.getRowingTime() > 0.0;
ClientSteerBoatPacket steerLeftPacket = new ClientSteerBoatPacket(isSteeringRight, isSteeringLeft);
session.setSteeringLeft(packet.getRowingTime() > 0.0);
ClientSteerBoatPacket steerLeftPacket = new ClientSteerBoatPacket(session.isSteeringLeft(), session.isSteeringRight());
session.sendDownstreamPacket(steerLeftPacket);
break;
case ROW_RIGHT:
isSteeringRight = packet.getRowingTime() > 0.0;
ClientSteerBoatPacket steerRightPacket = new ClientSteerBoatPacket(isSteeringRight, isSteeringLeft);
session.setSteeringRight(packet.getRowingTime() > 0.0);
ClientSteerBoatPacket steerRightPacket = new ClientSteerBoatPacket(session.isSteeringLeft(), session.isSteeringRight());
session.sendDownstreamPacket(steerRightPacket);
break;
}

View File

@ -27,19 +27,28 @@ package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientVehicleMovePacket;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import org.geysermc.connector.entity.BoatEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
// Used for horses
/**
* Sent by the client when moving a horse.
*/
@Translator(packet = MoveEntityAbsolutePacket.class)
public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator<MoveEntityAbsolutePacket> {
@Override
public void translate(MoveEntityAbsolutePacket packet, GeyserSession session) {
float y = packet.getPosition().getY();
if (session.getRidingVehicleEntity() instanceof BoatEntity) {
// Remove some Y position to prevents boats from looking like they're floating in water
// Not by the full boat offset because 1.16.100 complains and that's probably not good for the future
y -= (EntityType.BOAT.getOffset() - 0.5f);
}
ClientVehicleMovePacket clientVehicleMovePacket = new ClientVehicleMovePacket(
packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ(),
packet.getPosition().getX(), y, packet.getPosition().getZ(),
packet.getRotation().getY() - 90, packet.getRotation().getX()
);
session.sendDownstreamPacket(clientVehicleMovePacket);

View File

@ -34,6 +34,7 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.floodgate.util.DeviceOs;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@ -46,9 +47,20 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
@Override
public void translate(NetworkStackLatencyPacket packet, GeyserSession session) {
long pingId;
// so apparently, as of 1.16.200
// PS4 divides the network stack latency timestamp FOR US!!!
// WTF
if (session.getClientData().getDeviceOs().equals(DeviceOs.PS4)) {
// Ignore the weird DeviceOS, our order is wrong and will be fixed in Floodgate 2.0
pingId = packet.getTimestamp();
} else {
pingId = packet.getTimestamp() / 1000;
}
// negative timestamps are used as hack to fix the url image loading bug
if (packet.getTimestamp() > 0) {
// The client sends a timestamp back but it's rounded and therefore unreliable when we need the exact number
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(session.getLastKeepAliveTimestamp());
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId);
session.sendDownstreamPacket(keepAlivePacket);
return;
}

View File

@ -31,7 +31,9 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
// Makes minecarts respond to player input
/**
* Sent by the client for minecarts and boats.
*/
@Translator(packet = PlayerInputPacket.class)
public class BedrockPlayerInputTranslator extends PacketTranslator<PlayerInputPacket> {

View File

@ -31,7 +31,7 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import com.nukkitx.protocol.bedrock.packet.RespawnPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;

View File

@ -25,13 +25,14 @@
package org.geysermc.connector.network.translators.bedrock;
import org.geysermc.connector.entity.PlayerEntity;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.SkinUtils;
import com.nukkitx.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket;
import org.geysermc.connector.skin.SkinManager;
import org.geysermc.connector.skin.SkullSkinManager;
@Translator(packet = SetLocalPlayerAsInitializedPacket.class)
public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslator<SetLocalPlayerAsInitializedPacket> {
@ -44,10 +45,20 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) {
if (!entity.isValid()) {
SkinUtils.requestAndHandleSkinAndCape(entity, session, null);
SkinManager.requestAndHandleSkinAndCape(entity, session, null);
entity.sendPlayer(session);
}
}
// Send Skulls
for (PlayerEntity entity : session.getSkullCache().values()) {
entity.spawnEntity(session);
SkullSkinManager.requestAndHandleSkin(entity, session, (skin) -> {
entity.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
entity.updateBedrockMetadata(session);
});
}
}
}
}

View File

@ -42,13 +42,13 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
@Override
public void translate(EntityEventPacket packet, GeyserSession session) {
switch (packet.getType()) {
// Resend the packet so we get the eating sounds
case EATING_ITEM:
// Resend the packet so we get the eating sounds
session.sendUpstreamPacket(packet);
return;
case COMPLETE_TRADE:
ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData());
session.getDownstream().getSession().send(selectTradePacket);
session.sendDownstreamPacket(selectTradePacket);
Entity villager = session.getPlayerEntity();
Inventory openInventory = session.getInventoryCache().getOpenInventory();

View File

@ -43,6 +43,7 @@ import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;

View File

@ -27,18 +27,20 @@ package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerRotationPacket;
import com.github.steveice10.packetlib.packet.Packet;
import com.nukkitx.math.vector.Vector3d;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import java.util.concurrent.TimeUnit;
@ -48,7 +50,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
@Override
public void translate(MovePlayerPacket packet, GeyserSession session) {
PlayerEntity entity = session.getPlayerEntity();
if (entity == null || !session.isSpawned() || session.getPendingDimSwitches().get() > 0) return;
if (!session.isSpawned() || session.getPendingDimSwitches().get() > 0) return;
if (!session.getUpstream().isInitialized()) {
MoveEntityAbsolutePacket moveEntityBack = new MoveEntityAbsolutePacket();
@ -65,27 +67,53 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
session.getMovementSendIfIdle().cancel(true);
}
Vector3d position = adjustBedrockPosition(packet.getPosition(), packet.isOnGround());
if (session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityType.PLAYER.getOffset(), 0))) {
// head yaw, pitch, head yaw
Vector3f rotation = Vector3f.from(packet.getRotation().getY(), packet.getRotation().getX(), packet.getRotation().getY());
if(!session.confirmTeleport(position)){
return;
boolean positionChanged = !entity.getPosition().equals(packet.getPosition());
boolean rotationChanged = !entity.getRotation().equals(rotation);
// If only the pitch and yaw changed
// This isn't needed, but it makes the packets closer to vanilla
// It also means you can't "lag back" while only looking, in theory
if (!positionChanged && rotationChanged) {
ClientPlayerRotationPacket playerRotationPacket = new ClientPlayerRotationPacket(
packet.isOnGround(), packet.getRotation().getY(), packet.getRotation().getX());
entity.setRotation(rotation);
entity.setOnGround(packet.isOnGround());
session.sendDownstreamPacket(playerRotationPacket);
} else {
Vector3d position = adjustBedrockPosition(session, packet.getPosition(), packet.isOnGround());
if (position != null) { // A null return value cancels the packet
if (isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) {
Packet movePacket;
if (rotationChanged) {
// Send rotation updates as well
movePacket = new ClientPlayerPositionRotationPacket(packet.isOnGround(), position.getX(), position.getY(), position.getZ(),
packet.getRotation().getY(), packet.getRotation().getX());
entity.setRotation(rotation);
} else {
// Rotation did not change; don't send an update with rotation
movePacket = new ClientPlayerPositionPacket(packet.isOnGround(), position.getX(), position.getY(), position.getZ());
}
entity.setPosition(packet.getPosition(), false);
entity.setOnGround(packet.isOnGround());
// Send final movement changes
session.sendDownstreamPacket(movePacket);
} else {
// Not a valid move
session.getConnector().getLogger().debug("Recalculating position...");
recalculatePosition(session);
}
}
}
}
if (!isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) {
session.getConnector().getLogger().debug("Recalculating position...");
recalculatePosition(session, entity, entity.getPosition());
return;
}
ClientPlayerPositionRotationPacket playerPositionRotationPacket = new ClientPlayerPositionRotationPacket(
packet.isOnGround(), position.getX(), position.getY(), position.getZ(), packet.getRotation().getY(), packet.getRotation().getX()
);
// head yaw, pitch, head yaw
Vector3f rotation = Vector3f.from(packet.getRotation().getY(), packet.getRotation().getX(), packet.getRotation().getY());
entity.setPosition(packet.getPosition().sub(0, EntityType.PLAYER.getOffset(), 0));
entity.setRotation(rotation);
entity.setOnGround(packet.isOnGround());
// Move parrots to match if applicable
if (entity.getLeftParrot() != null) {
entity.getLeftParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false);
@ -94,42 +122,11 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
entity.getRightParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false);
}
/*
boolean colliding = false;
Position position = new Position((int) packet.getPosition().getX(),
(int) Math.ceil(javaY * 2) / 2, (int) packet.getPosition().getZ());
BlockEntry block = session.getChunkCache().getBlockAt(position);
if (!block.getJavaIdentifier().contains("air"))
colliding = true;
if (!colliding)
*/
session.sendDownstreamPacket(playerPositionRotationPacket);
// Schedule a position send loop if the player is idle
session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session),
3, TimeUnit.SECONDS));
}
/**
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
* the two versions.
*
* @param position the current Bedrock position of the client
* @param onGround whether the Bedrock player is on the ground
* @return the position to send to the Java server.
*/
private Vector3d adjustBedrockPosition(Vector3f position, boolean onGround) {
// We need to parse the float as a string since casting a float to a double causes us to
// lose precision and thus, causes players to get stuck when walking near walls
double javaY = position.getY() - EntityType.PLAYER.getOffset();
if (onGround) javaY = Math.ceil(javaY * 2) / 2;
return Vector3d.from(Double.parseDouble(Float.toString(position.getX())), javaY,
Double.parseDouble(Float.toString(position.getZ())));
}
public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
if (mode != MovePlayerPacket.Mode.NORMAL)
return true;
@ -155,7 +152,53 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
return true;
}
public void recalculatePosition(GeyserSession session, Entity entity, Vector3f currentPosition) {
/**
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
* the two versions.
*
* @param session the current GeyserSession
* @param bedrockPosition the current Bedrock position of the client
* @param onGround whether the Bedrock player is on the ground
* @return the position to send to the Java server, or null to cancel sending the packet
*/
private Vector3d adjustBedrockPosition(GeyserSession session, Vector3f bedrockPosition, boolean onGround) {
// We need to parse the float as a string since casting a float to a double causes us to
// lose precision and thus, causes players to get stuck when walking near walls
double javaY = bedrockPosition.getY() - EntityType.PLAYER.getOffset();
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
if (session.getConnector().getConfig().isCacheChunks()) {
// With chunk caching, we can do some proper collision checks
CollisionManager collisionManager = session.getCollisionManager();
collisionManager.updatePlayerBoundingBox(position);
// Correct player position
if (!collisionManager.correctPlayerPosition()) {
// Cancel the movement if it needs to be cancelled
recalculatePosition(session);
return null;
}
position = Vector3d.from(collisionManager.getPlayerBoundingBox().getMiddleX(),
collisionManager.getPlayerBoundingBox().getMiddleY() - (collisionManager.getPlayerBoundingBox().getSizeY() / 2),
collisionManager.getPlayerBoundingBox().getMiddleZ());
} else {
// When chunk caching is off, we have to rely on this
// It rounds the Y position up to the nearest 0.5
// This snaps players to snap to the top of stairs and slabs like on Java Edition
// However, it causes issues such as the player floating on carpets
if (onGround) javaY = Math.ceil(javaY * 2) / 2;
position = position.up(javaY - position.getY());
}
return position;
}
// TODO: This makes the player look upwards for some reason, rotation values must be wrong
public void recalculatePosition(GeyserSession session) {
PlayerEntity entity = session.getPlayerEntity();
// Gravity might need to be reset...
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
@ -166,7 +209,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
movePlayerPacket.setPosition(entity.getPosition());
movePlayerPacket.setRotation(entity.getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN);
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
session.sendUpstreamPacket(movePlayerPacket);
}
@ -174,11 +217,15 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
if (session.isClosed()) return;
PlayerEntity entity = session.getPlayerEntity();
// Recalculate in case something else changed position
Vector3d position = adjustBedrockPosition(entity.getPosition(), entity.isOnGround());
ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(session.getPlayerEntity().isOnGround(),
position.getX(), position.getY(), position.getZ());
session.sendDownstreamPacket(packet);
Vector3d position = adjustBedrockPosition(session, entity.getPosition(), entity.isOnGround());
// A null return value cancels the packet
if (position != null) {
ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(session.getPlayerEntity().isOnGround(),
position.getX(), position.getY(), position.getZ());
session.sendDownstreamPacket(packet);
}
session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session),
3, TimeUnit.SECONDS));
}
}

View File

@ -25,49 +25,51 @@
package org.geysermc.connector.network.translators.chat;
import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import com.github.steveice10.mc.protocol.data.message.style.ChatColor;
import com.github.steveice10.mc.protocol.data.message.style.ChatFormat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.gson.legacyimpl.NBTLegacyHoverEventSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.translation.TranslationRegistry;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.*;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class MessageTranslator {
// These are used for handling the translations of the messages
private static final TranslationRegistry REGISTRY = new MinecraftTranslationRegistry();
private static final TranslatableComponentRenderer<Locale> RENDERER = TranslatableComponentRenderer.usingTranslationSource(REGISTRY);
private static final TranslatableComponentRenderer<Locale> RENDERER = TranslatableComponentRenderer.usingTranslationSource(new MinecraftTranslationRegistry());
// Construct our own {@link GsonComponentSerializer} since we need to change a setting
private static final GsonComponentSerializer GSON_SERIALIZER = GsonComponentSerializer.builder()
// Specify that we may be expecting legacy hover events
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.build();
// Store team colors for player names
private static final Map<TeamColor, String> TEAM_COLORS = new HashMap<>();
private static final Map<TeamColor, TextDecoration> TEAM_FORMATS = new HashMap<>();
// Legacy formatting character
private static final String BASE = "\u00a7";
// Reset character
private static final String RESET = BASE + "r";
static {
TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK));
TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE));
TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN));
TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA));
TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED));
TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE));
TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD));
TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY));
TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY));
TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE));
TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN));
TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA));
TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED));
TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE));
TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW));
TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE));
TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(ChatFormat.OBFUSCATED));
TEAM_COLORS.put(TeamColor.BOLD, getFormat(ChatFormat.BOLD));
TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(ChatFormat.STRIKETHROUGH));
TEAM_COLORS.put(TeamColor.ITALIC, getFormat(ChatFormat.ITALIC));
TEAM_FORMATS.put(TeamColor.OBFUSCATED, TextDecoration.OBFUSCATED);
TEAM_FORMATS.put(TeamColor.BOLD, TextDecoration.BOLD);
TEAM_FORMATS.put(TeamColor.STRIKETHROUGH, TextDecoration.STRIKETHROUGH);
TEAM_FORMATS.put(TeamColor.ITALIC, TextDecoration.ITALIC);
// Tell MCProtocolLib to use our serializer
DefaultComponentSerializer.set(GSON_SERIALIZER);
}
/**
@ -77,20 +79,43 @@ public class MessageTranslator {
* @param locale Locale to use for translation strings
* @return Parsed and formatted message for bedrock
*/
public static String convertMessage(Component message, String locale) {
try {
// Get a Locale from the given locale string
Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-'));
message = RENDERER.render(message, localeCode);
String legacy = LegacyComponentSerializer.legacySection().serialize(message);
// Strip strikethrough and underline as they are not supported on bedrock
legacy = legacy.replaceAll("\u00a7[mn]", "");
// Make color codes reset formatting like Java
// See https://minecraft.gamepedia.com/Formatting_codes#Usage
legacy = legacy.replaceAll("\u00a7([0-9a-f])", "\u00a7r\u00a7$1");
legacy = legacy.replaceAll("\u00a7r\u00a7r", "\u00a7r");
return legacy;
} catch (Exception e) {
GeyserConnector.getInstance().getLogger().debug(GSON_SERIALIZER.serialize(message));
GeyserConnector.getInstance().getLogger().error("Failed to parse message", e);
return "";
}
}
public static String convertMessage(String message, String locale) {
Component component = GsonComponentSerializer.gson().deserialize(message);
// Get a Locale from the given locale string
Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-'));
component = RENDERER.render(component, localeCode);
return LegacyComponentSerializer.legacySection().serialize(component);
return convertMessage(GSON_SERIALIZER.deserialize(message), locale);
}
public static String convertMessage(String message) {
return convertMessage(message, LanguageUtils.getDefaultLocale());
}
public static String convertMessage(Component message) {
return convertMessage(message, LanguageUtils.getDefaultLocale());
}
/**
* Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode.
* See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency.
@ -100,14 +125,18 @@ public class MessageTranslator {
* @return Bedrock formatted message
*/
public static String convertMessageLenient(String message, String locale) {
if (isMessage(message)) {
if (message.trim().isEmpty()) {
return message;
}
try {
return convertMessage(message, locale);
} else {
} catch (Exception ignored) {
String convertedMessage = convertMessage(convertToJavaMessage(message), locale);
// We have to do this since Adventure strips the starting reset character
if (message.startsWith(getColor(ChatColor.RESET))) {
convertedMessage = getColor(ChatColor.RESET) + convertedMessage;
if (message.startsWith(RESET) && !convertedMessage.startsWith(RESET)) {
convertedMessage = RESET + convertedMessage;
}
return convertedMessage;
@ -126,138 +155,104 @@ public class MessageTranslator {
*/
public static String convertToJavaMessage(String message) {
Component component = LegacyComponentSerializer.legacySection().deserialize(message);
return GsonComponentSerializer.gson().serialize(component);
return GSON_SERIALIZER.serialize(component);
}
/**
* Checks if the given text string is a JSON message
* Convert a {@link NamedTextColor} into a string for inserting into messages
*
* @param text String to test
* @return True if its a valid message JSON string, false if not
*/
public static boolean isMessage(String text) {
if (text.trim().isEmpty()) {
return false;
}
try {
GsonComponentSerializer.gson().deserialize(text);
} catch (Exception ex) {
return false;
}
return true;
}
/**
* Convert a {@link ChatColor} into a string for inserting into messages
*
* @param color {@link ChatColor} to convert
* @param color {@link NamedTextColor} to convert
* @return The converted color string
*/
private static String getColor(String color) {
String base = "\u00a7";
switch (color) {
case ChatColor.BLACK:
base += "0";
break;
case ChatColor.DARK_BLUE:
base += "1";
break;
case ChatColor.DARK_GREEN:
base += "2";
break;
case ChatColor.DARK_AQUA:
base += "3";
break;
case ChatColor.DARK_RED:
base += "4";
break;
case ChatColor.DARK_PURPLE:
base += "5";
break;
case ChatColor.GOLD:
base += "6";
break;
case ChatColor.GRAY:
base += "7";
break;
case ChatColor.DARK_GRAY:
base += "8";
break;
case ChatColor.BLUE:
base += "9";
break;
case ChatColor.GREEN:
base += "a";
break;
case ChatColor.AQUA:
base += "b";
break;
case ChatColor.RED:
base += "c";
break;
case ChatColor.LIGHT_PURPLE:
base += "d";
break;
case ChatColor.YELLOW:
base += "e";
break;
case ChatColor.WHITE:
base += "f";
break;
case ChatColor.RESET:
base += "r";
break;
default:
return "";
private static String getColor(NamedTextColor color) {
StringBuilder str = new StringBuilder(BASE);
if (color.equals(NamedTextColor.BLACK)) {
str.append("0");
} else if (color.equals(NamedTextColor.DARK_BLUE)) {
str.append("1");
} else if (color.equals(NamedTextColor.DARK_GREEN)) {
str.append("2");
} else if (color.equals(NamedTextColor.DARK_AQUA)) {
str.append("3");
} else if (color.equals(NamedTextColor.DARK_RED)) {
str.append("4");
} else if (color.equals(NamedTextColor.DARK_PURPLE)) {
str.append("5");
} else if (color.equals(NamedTextColor.GOLD)) {
str.append("6");
} else if (color.equals(NamedTextColor.GRAY)) {
str.append("7");
} else if (color.equals(NamedTextColor.DARK_GRAY)) {
str.append("8");
} else if (color.equals(NamedTextColor.BLUE)) {
str.append("9");
} else if (color.equals(NamedTextColor.GREEN)) {
str.append("a");
} else if (color.equals(NamedTextColor.AQUA)) {
str.append("b");
} else if (color.equals(NamedTextColor.RED)) {
str.append("c");
} else if (color.equals(NamedTextColor.LIGHT_PURPLE)) {
str.append("d");
} else if (color.equals(NamedTextColor.YELLOW)) {
str.append("e");
} else if (color.equals(NamedTextColor.WHITE)) {
str.append("f");
} else {
return "";
}
return base;
return str.toString();
}
/**
* Convert a {@link ChatFormat} into a string for inserting into messages
* Convert a {@link TextDecoration} into a string for inserting into messages
*
* @param format {@link ChatFormat} to convert
* @param format {@link TextDecoration} to convert
* @return The converted chat formatting string
*/
private static String getFormat(ChatFormat format) {
StringBuilder str = new StringBuilder();
String base = "\u00a7";
private static String getFormat(TextDecoration format) {
StringBuilder str = new StringBuilder(BASE);
switch (format) {
case OBFUSCATED:
base += "k";
str.append("k");
break;
case BOLD:
base += "l";
str.append("l");
break;
case STRIKETHROUGH:
base += "m";
str.append("m");
break;
case UNDERLINED:
base += "n";
str.append("n");
break;
case ITALIC:
base += "o";
str.append("o");
break;
default:
return "";
}
str.append(base);
return str.toString();
}
/**
* Convert a team color to a chat color
*
* @param teamColor
* @param teamColor Color or format to convert
* @return The chat color character
*/
public static String toChatColor(TeamColor teamColor) {
return TEAM_COLORS.getOrDefault(teamColor, "");
if (teamColor.equals(TeamColor.NONE)) {
return "";
}
NamedTextColor textColor = NamedTextColor.NAMES.value(teamColor.name().toLowerCase());
if (textColor != null) {
return getColor(textColor);
}
return getFormat(TEAM_FORMATS.get(teamColor));
}
/**

View File

@ -26,7 +26,7 @@
package org.geysermc.connector.network.translators.chat;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.translation.Translator;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.connector.utils.LocaleUtils;
@ -40,10 +40,10 @@ import java.util.regex.Pattern;
* This class is used for mapping a translation key with the already loaded Java locale data
* Used in MessageTranslator.java as part of the KyoriPowered/Adventure library
*/
public class MinecraftTranslationRegistry implements TranslationRegistry {
public class MinecraftTranslationRegistry implements Translator {
@Override
public @NonNull Key name() {
return Key.key("", "");
return Key.key("geyser", "minecraft_translations");
}
@Override
@ -61,21 +61,16 @@ public class MinecraftTranslationRegistry implements TranslationRegistry {
}
m.appendTail(sb);
// Replace the `%x$s` with numbered inserts `{x}`
p = Pattern.compile("%([0-9]+)\\$s");
m = p.matcher(sb.toString());
sb = new StringBuffer();
while (m.find()) {
i = Integer.parseInt(m.group(1)) - 1;
m.appendReplacement(sb, "{" + i + "}");
}
m.appendTail(sb);
return new MessageFormat(sb.toString(), locale);
}
@Override
public void defaultLocale(@NonNull Locale locale) {
}
@Override
public void register(@NonNull String key, @NonNull Locale locale, @NonNull MessageFormat format) {
}
@Override
public void unregister(@NonNull String key) {
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision;
import lombok.*;
@Data
@AllArgsConstructor
public class BoundingBox {
private double middleX;
private double middleY;
private double middleZ;
private double sizeX;
private double sizeY;
private double sizeZ;
public void translate(double x, double y, double z) {
middleX += x;
middleY += y;
middleZ += z;
}
public boolean checkIntersection(int offsetX, int offsetY, int offsetZ, BoundingBox otherBox) {
return (Math.abs((middleX + offsetX) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX())) &&
(Math.abs((middleY + offsetY) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY())) &&
(Math.abs((middleZ + offsetZ) - otherBox.getMiddleZ()) * 2 < (sizeZ + otherBox.getSizeZ()));
}
}

View File

@ -0,0 +1,208 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision;
import com.nukkitx.math.vector.Vector3d;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
import java.util.ArrayList;
import java.util.List;
public class CollisionManager {
private final GeyserSession session;
@Getter
private BoundingBox playerBoundingBox;
/**
* Whether the player is inside scaffolding
*/
@Setter
private boolean touchingScaffolding;
/**
* Whether the player is on top of scaffolding
*/
@Setter
private boolean onScaffolding;
/**
* Additional space where blocks are checked, which is helpful for fixing NoCheatPlus's Passable check.
* This check doesn't allow players right up against the block, so they must be pushed slightly away.
*/
public static final double COLLISION_TOLERANCE = 0.00001;
public CollisionManager(GeyserSession session) {
this.session = session;
this.playerBoundingBox = new BoundingBox(0, 0, 0, 0.6, 1.8, 0.6);
}
/**
* Updates the stored bounding box
* @param position The new position of the player
*/
public void updatePlayerBoundingBox(Vector3f position) {
updatePlayerBoundingBox(position.toDouble());
}
/**
* Updates the stored bounding box
* @param position The new position of the player
*/
public void updatePlayerBoundingBox(Vector3d position) {
updatePlayerBoundingBox();
playerBoundingBox.setMiddleX(position.getX());
playerBoundingBox.setMiddleY(position.getY() + (playerBoundingBox.getSizeY() / 2));
playerBoundingBox.setMiddleZ(position.getZ());
}
/**
* Updates the stored bounding box without passing a position, which currently just changes the height depending on if the player is sneaking.
*/
public void updatePlayerBoundingBox() {
if (playerBoundingBox == null) {
Vector3f playerPosition;
if (session.getPlayerEntity() == null) {
// Temporary position to prevent NullPointerException
playerPosition = Vector3f.ZERO;
} else {
playerPosition = session.getPlayerEntity().getPosition();
}
playerBoundingBox = new BoundingBox(playerPosition.getX(), playerPosition.getY() + 0.9, playerPosition.getZ(), 0.6, 1.8, 0.6);
} else {
// According to the Minecraft Wiki, when sneaking:
// - In Bedrock Edition, the height becomes 1.65 blocks, allowing movement through spaces as small as 1.75 (2 - 14) blocks high.
// - In Java Edition, the height becomes 1.5 blocks.
if (session.isSneaking()) {
playerBoundingBox.setSizeY(1.5);
} else {
playerBoundingBox.setSizeY(1.8);
}
}
}
public List<Vector3i> getPlayerCollidableBlocks() {
List<Vector3i> blocks = new ArrayList<>();
Vector3d position = Vector3d.from(playerBoundingBox.getMiddleX(),
playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2),
playerBoundingBox.getMiddleZ());
// Loop through all blocks that could collide with the player
int minCollisionX = (int) Math.floor(position.getX() - ((playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE));
int maxCollisionX = (int) Math.floor(position.getX() + (playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE);
// Y extends 0.5 blocks down because of fence hitboxes
int minCollisionY = (int) Math.floor(position.getY() - 0.5);
int maxCollisionY = (int) Math.floor(position.getY() + playerBoundingBox.getSizeY());
int minCollisionZ = (int) Math.floor(position.getZ() - ((playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE));
int maxCollisionZ = (int) Math.floor(position.getZ() + (playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE);
for (int y = minCollisionY; y < maxCollisionY + 1; y++) {
for (int x = minCollisionX; x < maxCollisionX + 1; x++) {
for (int z = minCollisionZ; z < maxCollisionZ + 1; z++) {
blocks.add(Vector3i.from(x, y, z));
}
}
}
return blocks;
}
/**
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
* cancelled
* See {@link BlockCollision#correctPosition(GeyserSession, BoundingBox)} for more info
*/
public boolean correctPlayerPosition() {
// These may be set to true by the correctPosition method in ScaffoldingCollision
touchingScaffolding = false;
onScaffolding = false;
List<Vector3i> collidableBlocks = getPlayerCollidableBlocks();
// Used when correction code needs to be run before the main correction
for (Vector3i blockPos : collidableBlocks) {
BlockCollision blockCollision = CollisionTranslator.getCollisionAt(
session, blockPos.getX(), blockPos.getY(), blockPos.getZ()
);
if (blockCollision != null) {
blockCollision.beforeCorrectPosition(playerBoundingBox);
}
}
// Main correction code
for (Vector3i blockPos : collidableBlocks) {
BlockCollision blockCollision = CollisionTranslator.getCollisionAt(
session, blockPos.getX(), blockPos.getY(), blockPos.getZ()
);
if (blockCollision != null) {
if (!blockCollision.correctPosition(session, playerBoundingBox)) {
return false;
}
}
}
updateScaffoldingFlags();
return true;
}
/**
* Updates scaffolding entity flags
* Scaffolding needs to be checked per-move since it's a flag in Bedrock but Java does it client-side
*/
public void updateScaffoldingFlags() {
EntityFlags flags = session.getPlayerEntity().getMetadata().getFlags();
boolean flagsChanged;
boolean isSneakingWithScaffolding = (touchingScaffolding || onScaffolding) && session.isSneaking();
flagsChanged = flags.getFlag(EntityFlag.FALL_THROUGH_SCAFFOLDING) != isSneakingWithScaffolding;
flagsChanged |= flags.getFlag(EntityFlag.OVER_SCAFFOLDING) != isSneakingWithScaffolding;
flags.setFlag(EntityFlag.FALL_THROUGH_SCAFFOLDING, isSneakingWithScaffolding);
flags.setFlag(EntityFlag.OVER_SCAFFOLDING, isSneakingWithScaffolding);
flagsChanged |= flags.getFlag(EntityFlag.IN_SCAFFOLDING) != touchingScaffolding;
flags.setFlag(EntityFlag.IN_SCAFFOLDING, touchingScaffolding);
if (flagsChanged) {
session.getPlayerEntity().updateBedrockMetadata(session);
}
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value = RetentionPolicy.RUNTIME)
public @interface CollisionRemapper {
/**
* Regex of block identifiers to apply this collision to
* Matches against just the block ID name, not including the namespace or parameters
*/
String regex();
/**
* Regex of block state parameters to apply this collision to
* Defaults to matching any value
*/
String paramRegex() default ".*";
/**
* Signals if a new instance needs to created for every block state
*/
boolean usesParams() default false;
/**
* Signals if the default bounding boxes of this block as defined in collision.json should be passed to the
* constructor
*/
boolean passDefaultBoxes() default false;
}

View File

@ -0,0 +1,192 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.collect.BiMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
import org.geysermc.connector.network.translators.collision.translators.EmptyCollision;
import org.geysermc.connector.network.translators.collision.translators.OtherCollision;
import org.geysermc.connector.network.translators.collision.translators.SolidCollision;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.FileUtils;
import org.reflections.Reflections;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.regex.Pattern;
public class CollisionTranslator {
private static final Int2ObjectMap<BlockCollision> COLLISION_MAP = new Int2ObjectOpenHashMap<>();
public static void init() {
// If chunk caching is off then don't initialize
if (!GeyserConnector.getInstance().getConfig().isCacheChunks()) {
return;
}
List<Class<?>> collisionTypes = new ArrayList<>();
Map<Class<?>, CollisionRemapper> annotationMap = new HashMap<>();
Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.collision.translators") : new Reflections("org.geysermc.connector.network.translators.collision.translators");
for (Class<?> clazz : ref.getTypesAnnotatedWith(CollisionRemapper.class)) {
GeyserConnector.getInstance().getLogger().debug("Found annotated collision translator: " + clazz.getCanonicalName());
collisionTypes.add(clazz);
annotationMap.put(clazz, clazz.getAnnotation(CollisionRemapper.class));
}
// Load collision mappings file
InputStream stream = FileUtils.getResource("mappings/collision.json");
ArrayNode collisionList;
try {
collisionList = (ArrayNode) GeyserConnector.JSON_MAPPER.readTree(stream);
} catch (Exception e) {
throw new AssertionError("Unable to load collision data", e);
}
BiMap<String, Integer> javaIdBlockMap = BlockTranslator.getJavaIdBlockMap();
// Map of classes that don't change based on parameters that have already been created
Map<Class<?>, BlockCollision> instantiatedCollision = new HashMap<>();
for (Map.Entry<String, Integer> entry : javaIdBlockMap.entrySet()) {
BlockCollision newCollision = instantiateCollision(entry.getKey(), entry.getValue(), collisionTypes, annotationMap, instantiatedCollision, collisionList);
if (newCollision != null) {
instantiatedCollision.put(newCollision.getClass(), newCollision);
}
COLLISION_MAP.put(entry.getValue().intValue(), newCollision);
}
}
private static BlockCollision instantiateCollision(String blockID, int numericBlockID, List<Class<?>> collisionTypes, Map<Class<?>, CollisionRemapper> annotationMap, Map<Class<?>, BlockCollision> instantiatedCollision, ArrayNode collisionList) {
String blockName = blockID.split("\\[")[0].replace("minecraft:", "");
String params = "";
if (blockID.contains("[")) {
params = "[" + blockID.split("\\[")[1];
}
int collisionIndex = BlockTranslator.JAVA_RUNTIME_ID_TO_COLLISION_INDEX.get(numericBlockID);
for (Class<?> type : collisionTypes) {
CollisionRemapper annotation = annotationMap.get(type);
Pattern pattern = Pattern.compile(annotation.regex());
Pattern paramsPattern = Pattern.compile(annotation.paramRegex());
if (pattern.matcher(blockName).find() && paramsPattern.matcher(params).find()) {
try {
if (!annotation.usesParams() && instantiatedCollision.containsKey(type)) {
return instantiatedCollision.get(type);
}
// Return null when empty to save unnecessary checks
if (type == EmptyCollision.class) {
return null;
}
BlockCollision collision;
if (annotation.passDefaultBoxes()) {
// Create an OtherCollision instance and get the bounding boxes
BoundingBox[] defaultBoxes = new OtherCollision((ArrayNode) collisionList.get(collisionIndex)).getBoundingBoxes();
collision = (BlockCollision) type.getDeclaredConstructor(String.class, BoundingBox[].class).newInstance(params, defaultBoxes);
} else {
collision = (BlockCollision) type.getDeclaredConstructor(String.class).newInstance(params);
}
// If there's an existing instance equal to this one, use that instead
for (Map.Entry<Class<?>, BlockCollision> entry : instantiatedCollision.entrySet()) {
if (entry.getValue().equals(collision)) {
collision = entry.getValue();
break;
}
}
return collision;
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
}
}
// Unless some of the low IDs are changed, which is unlikely, the first item should always be empty collision
if (collisionIndex == 0) {
if (instantiatedCollision.containsKey(EmptyCollision.class)) {
return instantiatedCollision.get(EmptyCollision.class);
} else {
return new EmptyCollision(params);
}
}
// Unless some of the low IDs are changed, which is unlikely, the second item should always be full collision
if (collisionIndex == 1) {
if (instantiatedCollision.containsKey(SolidCollision.class)) {
return instantiatedCollision.get(SolidCollision.class);
} else {
return new SolidCollision(params);
}
}
BlockCollision collision = new OtherCollision((ArrayNode) collisionList.get(collisionIndex));
// If there's an existing instance equal to this one, use that instead
for (Map.Entry<Class<?>, BlockCollision> entry : instantiatedCollision.entrySet()) {
if (entry.getValue().equals(collision)) {
collision = entry.getValue();
break;
}
}
return collision;
}
// Note: these reuse classes, so don't try to store more than once instance or coordinates will get overwritten
public static BlockCollision getCollision(int blockID, int x, int y, int z) {
BlockCollision collision = COLLISION_MAP.get(blockID);
if (collision != null) {
collision.setPosition(x, y, z);
}
return collision;
}
public static BlockCollision getCollisionAt(GeyserSession session, int x, int y, int z) {
try {
return getCollision(session.getConnector().getWorldManager().getBlockAt(session, x, y, z), x, y, z);
} catch (ArrayIndexOutOfBoundsException e) {
// Block out of world
return null;
}
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import com.nukkitx.math.vector.Vector3d;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.collision.BoundingBox;
@EqualsAndHashCode
public class BlockCollision {
@Getter
protected BoundingBox[] boundingBoxes;
protected int x;
protected int y;
protected int z;
/**
* This is used for the step up logic.
* Usually, the player can only step up a block if they are on the same Y level as its bottom face or higher
* For snow layers, due to its beforeCorrectPosition method the player can be slightly below (0.125 blocks) and
* still need to step up
* This used to be 0 but for now this has been set to 1 as it fixes bed collision
* I didn't just set it for beds because other collision may also be slightly raised off the ground.
* If this causes any problems, change this back to 0 and add an exception for beds.
*/
@EqualsAndHashCode.Exclude
protected double pushUpTolerance = 1;
public void setPosition(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
/**
* Overridden in classes like SnowCollision and GrassPathCollision when correction code needs to be run before the
* main correction
*/
public void beforeCorrectPosition(BoundingBox playerCollision) {}
/**
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
* cancelled
* While the Java server should do this, it could result in false flags by anticheat
* This functionality is currently only used in 6 or 7 layer snow
*/
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) {
double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
for (BoundingBox b : this.boundingBoxes) {
double boxMinY = (b.getMiddleY() + y) - (b.getSizeY() / 2);
double boxMaxY = (b.getMiddleY() + y) + (b.getSizeY() / 2);
if (b.checkIntersection(x, y, z, playerCollision) && (playerMinY + pushUpTolerance) >= boxMinY) {
// Max steppable distance in Minecraft as far as we know is 0.5625 blocks (for beds)
if (boxMaxY - playerMinY <= 0.5625) {
playerCollision.translate(0, boxMaxY - playerMinY, 0);
// Update player Y for next collision box
playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
}
}
// Make player collision slightly bigger to pick up on blocks that could cause problems with Passable
playerCollision.setSizeX(playerCollision.getSizeX() + CollisionManager.COLLISION_TOLERANCE * 2);
playerCollision.setSizeZ(playerCollision.getSizeZ() + CollisionManager.COLLISION_TOLERANCE * 2);
// If the player still intersects the block, then push them out
// This fixes NoCheatPlus's Passable check
// This check doesn't allow players right up against the block, so they must be pushed slightly away
if (b.checkIntersection(x, y, z, playerCollision)) {
Vector3d relativePlayerPosition = Vector3d.from(playerCollision.getMiddleX() - x,
playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2) - y,
playerCollision.getMiddleZ() - z);
Vector3d northFacePos = Vector3d.from(b.getMiddleX(),
b.getMiddleY(),
b.getMiddleZ() - (b.getSizeZ() / 2));
Vector3d southFacePos = Vector3d.from(b.getMiddleX(),
b.getMiddleY(),
b.getMiddleZ() + (b.getSizeZ() / 2));
Vector3d eastFacePos = Vector3d.from(b.getMiddleX() + (b.getSizeX() / 2),
b.getMiddleY(),
b.getMiddleZ());
Vector3d westFacePos = Vector3d.from(b.getMiddleX() - (b.getSizeX() / 2),
b.getMiddleY(),
b.getMiddleZ());
double translateDistance = northFacePos.getZ() - relativePlayerPosition.getZ() - (playerCollision.getSizeZ() / 2);
if (Math.abs(translateDistance) < CollisionManager.COLLISION_TOLERANCE * 1.1) {
playerCollision.translate(0, 0, translateDistance);
}
translateDistance = southFacePos.getZ() - relativePlayerPosition.getZ() + (playerCollision.getSizeZ() / 2);
if (Math.abs(translateDistance) < CollisionManager.COLLISION_TOLERANCE * 1.1) {
playerCollision.translate(0, 0, translateDistance);
}
translateDistance = eastFacePos.getX() - relativePlayerPosition.getX() + (playerCollision.getSizeX() / 2);
if (Math.abs(translateDistance) < CollisionManager.COLLISION_TOLERANCE * 1.1) {
playerCollision.translate(translateDistance, 0, 0);
}
translateDistance = westFacePos.getX() - relativePlayerPosition.getX() - (playerCollision.getSizeX() / 2);
if (Math.abs(translateDistance) < CollisionManager.COLLISION_TOLERANCE * 1.1) {
playerCollision.translate(translateDistance, 0, 0);
}
}
// Set the collision size back to normal
playerCollision.setSizeX(0.6);
playerCollision.setSizeZ(0.6);
}
return true;
}
public boolean checkIntersection(BoundingBox playerCollision) {
for (BoundingBox b : boundingBoxes) {
if (b.checkIntersection(x, y, z, playerCollision)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionRemapper;
@CollisionRemapper(regex = "_door$", usesParams = true, passDefaultBoxes = true)
public class DoorCollision extends BlockCollision {
/**
* 1 = north
* 2 = east
* 3 = south
* 4 = west
*/
private int facing;
public DoorCollision(String params, BoundingBox[] defaultBoxes) {
super();
boundingBoxes = defaultBoxes;
if (params.contains("facing=north")) {
facing = 1;
} else if (params.contains("facing=east")) {
facing = 2;
} else if (params.contains("facing=south")) {
facing = 3;
} else if (params.contains("facing=west")) {
facing = 4;
}
// If the door is open it changes direction
if (params.contains("open=true")) {
facing = facing % 2 + 1;
}
}
@Override
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) {
boolean result = super.correctPosition(session, playerCollision);
// Hack to prevent false positives
playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001);
playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001);
// Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock)
if (this.checkIntersection(playerCollision)) {
switch (facing) {
case 1: // North
playerCollision.setMiddleZ(Math.floor(playerCollision.getMiddleZ()) + 0.5125);
break;
case 2: // East
playerCollision.setMiddleX(Math.floor(playerCollision.getMiddleX()) + 0.5125);
break;
case 3: // South
playerCollision.setMiddleZ(Math.floor(playerCollision.getMiddleZ()) + 0.4875);
break;
case 4: // West
playerCollision.setMiddleX(Math.floor(playerCollision.getMiddleX()) + 0.4875);
break;
}
}
playerCollision.setSizeX(playerCollision.getSizeX() + 0.0001);
playerCollision.setSizeY(playerCollision.getSizeY() + 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() + 0.0001);
return result;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import org.geysermc.connector.network.translators.collision.BoundingBox;
public class EmptyCollision extends BlockCollision {
public EmptyCollision(String params) {
super();
boundingBoxes = new BoundingBox[0];
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionRemapper;
@CollisionRemapper(regex = "^grass_path$", passDefaultBoxes = true)
public class GrassPathCollision extends BlockCollision {
public GrassPathCollision(String params, BoundingBox[] defaultBoxes) {
super();
boundingBoxes = defaultBoxes;
}
// Needs to run before the main correction code or it can move the player into blocks
// This is counteracted by the main collision code pushing them out
@Override
public void beforeCorrectPosition(BoundingBox playerCollision) {
// In Bedrock, grass paths are small blocks so the player must be pushed down
double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
// If the player is in the buggy area, push them down
if (playerMinY == y + 1) {
playerCollision.translate(0, -0.0625, 0);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import java.util.Arrays;
import java.util.Comparator;
public class OtherCollision extends BlockCollision {
public OtherCollision(ArrayNode collisionList) {
super();
boundingBoxes = new BoundingBox[collisionList.size()];
for (int i = 0; i < collisionList.size(); i++) {
ArrayNode collisionBoxArray = (ArrayNode) collisionList.get(i);
boundingBoxes[i] = new BoundingBox(collisionBoxArray.get(0).asDouble(),
collisionBoxArray.get(1).asDouble(),
collisionBoxArray.get(2).asDouble(),
collisionBoxArray.get(3).asDouble(),
collisionBoxArray.get(4).asDouble(),
collisionBoxArray.get(5).asDouble());
}
// Sorting by lowest Y first fixes some bugs
Arrays.sort(boundingBoxes, Comparator.comparingDouble(BoundingBox::getMiddleY));
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionRemapper;
/**
* In order for scaffolding to work on Bedrock, entity flags need to be sent to the player
*/
@CollisionRemapper(regex = "^scaffolding$", usesParams = true, passDefaultBoxes = true)
public class ScaffoldingCollision extends BlockCollision {
public ScaffoldingCollision(String params, BoundingBox[] defaultBoxes) {
super();
boundingBoxes = defaultBoxes;
}
@Override
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) {
// Hack to not check below the player
playerCollision.setSizeY(playerCollision.getSizeY() - 0.001);
playerCollision.setMiddleY(playerCollision.getMiddleY() + 0.002);
boolean intersected = this.checkIntersection(playerCollision);
playerCollision.setSizeY(playerCollision.getSizeY() + 0.001);
playerCollision.setMiddleY(playerCollision.getMiddleY() - 0.002);
if (intersected) {
session.getCollisionManager().setTouchingScaffolding(true);
session.getCollisionManager().setOnScaffolding(true);
} else {
// Hack to check slightly below the player
playerCollision.setSizeY(playerCollision.getSizeY() + 0.001);
playerCollision.setMiddleY(playerCollision.getMiddleY() - 0.002);
if (this.checkIntersection(playerCollision)) {
session.getCollisionManager().setOnScaffolding(true);
}
playerCollision.setSizeY(playerCollision.getSizeY() - 0.001);
playerCollision.setMiddleY(playerCollision.getMiddleY() + 0.002);
}
// Normal move correction isn't really needed for scaffolding
return true;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionRemapper;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
@CollisionRemapper(regex = "^snow$", usesParams = true)
public class SnowCollision extends BlockCollision {
private final int layers;
public SnowCollision(String params) {
super();
Pattern layersPattern = Pattern.compile("layers=([0-8])");
Matcher matcher = layersPattern.matcher(params);
//noinspection ResultOfMethodCallIgnored
matcher.find();
// Hitbox is 1 layer less (you sink in 1 layer)
layers = Integer.parseInt(matcher.group(1));
if (layers > 1) {
boundingBoxes = new BoundingBox[] {
// Take away 1 because you can go 1 layer into snow layers
new BoundingBox(0.5, ((layers - 1) * 0.125) / 2, 0.5,
1, (layers - 1) * 0.125, 1)
};
} else {
// Single layers have no collision
boundingBoxes = new BoundingBox[0];
}
pushUpTolerance = 0.125;
}
// Needs to run before the main correction code or it can move the player into blocks
// This is counteracted by the main collision code pushing them out
@Override
public void beforeCorrectPosition(BoundingBox playerCollision) {
// In Bedrock, snow layers round down to half blocks but you can't sink into them at all
// This means the collision each half block reaches above where it should be on Java so the player has to be
// pushed down
if (layers == 4 || layers == 8) {
double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
double boxMaxY = (boundingBoxes[0].getMiddleY() + y) + (boundingBoxes[0].getSizeY() / 2);
// If the player is in the buggy area, push them down
if (playerMinY > boxMaxY &&
playerMinY <= (boxMaxY + 0.125)) {
playerCollision.translate(0, boxMaxY - playerMinY, 0);
}
}
}
@Override
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) {
// Hack to prevent false positives
playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001);
playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001);
if (this.checkIntersection(playerCollision)) {
double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
double boxMaxY = (boundingBoxes[0].getMiddleY() + y) + (boundingBoxes[0].getSizeY() / 2);
// If the player actually can't step onto it (they can step onto it from other snow layers)
if ((boxMaxY - playerMinY) > 0.5) {
// Cancel the movement
return false;
}
}
playerCollision.setSizeX(playerCollision.getSizeX() + 0.0001);
playerCollision.setSizeY(playerCollision.getSizeY() + 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() + 0.0001);
return super.correctPosition(session, playerCollision);
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionRemapper;
@CollisionRemapper(regex = "shulker_box$") // These have no collision in the mappings as it depends on the NBT data
public class SolidCollision extends BlockCollision {
public SolidCollision(String params) {
super();
boundingBoxes = new BoundingBox[]{
new BoundingBox(0.5, 0.5, 0.5, 1, 1, 1)
};
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.collision.translators;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionRemapper;
@CollisionRemapper(regex = "_trapdoor$", usesParams = true, passDefaultBoxes = true)
public class TrapdoorCollision extends BlockCollision {
/**
* 1 = north
* 2 = east
* 3 = south
* 4 = west
* 5 = up
* 6 = down
*/
private int facing;
public TrapdoorCollision(String params, BoundingBox[] defaultBoxes) {
super();
boundingBoxes = defaultBoxes;
if (params.contains("open=true")) {
if (params.contains("facing=north")) {
facing = 1;
} else if (params.contains("facing=east")) {
facing = 2;
} else if (params.contains("facing=south")) {
facing = 3;
} else if (params.contains("facing=west")) {
facing = 4;
}
} else {
if (params.contains("half=bottom")) {
// Up
facing = 5;
} else {
// Down
facing = 6;
}
}
}
@Override
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) {
boolean result = super.correctPosition(session, playerCollision);
// Hack to prevent false positives
playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001);
playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001);
// Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock)
if (this.checkIntersection(playerCollision)) {
switch (facing) {
case 1: // North
playerCollision.setMiddleZ(Math.floor(playerCollision.getMiddleZ()) + 0.5125);
break;
case 3: // South
playerCollision.setMiddleZ(Math.floor(playerCollision.getMiddleZ()) + 0.4875);
break;
case 4: // West
playerCollision.setMiddleX(Math.floor(playerCollision.getMiddleX()) + 0.4875);
break;
case 6: // Down
playerCollision.setMiddleY(Math.floor(
playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2)
) + 0.0125 + (playerCollision.getSizeY() / 2));
break;
case 2:
case 5:
// Up-facing and east-facing trapdoors work fine
break;
}
}
playerCollision.setSizeX(playerCollision.getSizeX() + 0.0001);
playerCollision.setSizeY(playerCollision.getSizeY() + 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() + 0.0001);
return result;
}
}

View File

@ -47,9 +47,7 @@ import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LocaleUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.*;
/**
* A temporary reconstruction of the enchantment table UI until our inventory rewrite is complete.
@ -100,11 +98,11 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator {
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
super.updateInventory(session, inventory);
ItemData[] items = new ItemData[5];
items[0] = ItemTranslator.translateToBedrock(session, inventory.getItem(0));
items[1] = ItemTranslator.translateToBedrock(session, inventory.getItem(1));
List<ItemData> items = new ArrayList<>(5);
items.add(ItemTranslator.translateToBedrock(session, inventory.getItem(0)));
items.add(ItemTranslator.translateToBedrock(session, inventory.getItem(1)));
for (int i = 0; i < 3; i++) {
items[i + 2] = session.getEnchantmentSlotData()[i].getItem() != null ? session.getEnchantmentSlotData()[i].getItem() : createEnchantmentBook();
items.add(session.getEnchantmentSlotData()[i].getItem() != null ? session.getEnchantmentSlotData()[i].getItem() : createEnchantmentBook());
}
InventoryContentPacket contentPacket = new InventoryContentPacket();

View File

@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.inventory;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;

View File

@ -41,6 +41,8 @@ import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class PlayerInventoryTranslator extends InventoryTranslator {
@ -65,7 +67,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
for (int i = 36; i < 45; i++) {
contents[i - 36] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
}
inventoryContentPacket.setContents(contents);
inventoryContentPacket.setContents(Arrays.asList(contents));
session.sendUpstreamPacket(inventoryContentPacket);
// Armor
@ -75,13 +77,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
for (int i = 5; i < 9; i++) {
contents[i - 5] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
}
armorContentPacket.setContents(contents);
armorContentPacket.setContents(Arrays.asList(contents));
session.sendUpstreamPacket(armorContentPacket);
// Offhand
InventoryContentPacket offhandPacket = new InventoryContentPacket();
offhandPacket.setContainerId(ContainerId.OFFHAND);
offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(45))});
offhandPacket.setContents(Collections.singletonList(ItemTranslator.translateToBedrock(session, inventory.getItem(45))));
session.sendUpstreamPacket(offhandPacket);
}
@ -130,7 +132,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
} else if (slot == 45) {
InventoryContentPacket offhandPacket = new InventoryContentPacket();
offhandPacket.setContainerId(ContainerId.OFFHAND);
offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(slot))});
offhandPacket.setContents(Collections.singletonList(ItemTranslator.translateToBedrock(session, inventory.getItem(slot))));
session.sendUpstreamPacket(offhandPacket);
}
}

View File

@ -36,6 +36,9 @@ import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LanguageUtils;
import java.util.ArrayList;
import java.util.List;
@AllArgsConstructor
public class ChestInventoryUpdater extends InventoryUpdater {
private static final ItemData UNUSUABLE_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(LanguageUtils.getLocaleStringLog("geyser.inventory.unusable_item.slot"));
@ -46,12 +49,12 @@ public class ChestInventoryUpdater extends InventoryUpdater {
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
super.updateInventory(translator, session, inventory);
ItemData[] bedrockItems = new ItemData[paddedSize];
for (int i = 0; i < bedrockItems.length; i++) {
List<ItemData> bedrockItems = new ArrayList<>(paddedSize);
for (int i = 0; i < paddedSize; i++) {
if (i < translator.size) {
bedrockItems[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
bedrockItems.add(ItemTranslator.translateToBedrock(session, inventory.getItem(i)));
} else {
bedrockItems[i] = UNUSUABLE_SPACE_BLOCK;
bedrockItems.add(UNUSUABLE_SPACE_BLOCK);
}
}

View File

@ -33,6 +33,8 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import java.util.Arrays;
public class ContainerInventoryUpdater extends InventoryUpdater {
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
@ -45,7 +47,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater {
InventoryContentPacket contentPacket = new InventoryContentPacket();
contentPacket.setContainerId(inventory.getId());
contentPacket.setContents(bedrockItems);
contentPacket.setContents(Arrays.asList(bedrockItems));
session.sendUpstreamPacket(contentPacket);
}

View File

@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import java.util.Arrays;
public abstract class InventoryUpdater {
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
ItemData[] bedrockItems = new ItemData[36];
@ -43,7 +45,7 @@ public abstract class InventoryUpdater {
}
InventoryContentPacket contentPacket = new InventoryContentPacket();
contentPacket.setContainerId(ContainerId.INVENTORY);
contentPacket.setContents(bedrockItems);
contentPacket.setContents(Arrays.asList(bedrockItems));
session.sendUpstreamPacket(contentPacket);
}

View File

@ -52,6 +52,12 @@ public class ItemRegistry {
private static final Map<String, ItemEntry> JAVA_IDENTIFIER_MAP = new HashMap<>();
/**
* A list of all identifiers that only exist on Java. Used to prevent creative items from becoming these unintentionally.
*/
private static final List<String> JAVA_ONLY_ITEMS = Arrays.asList("minecraft:spectral_arrow", "minecraft:debug_stick",
"minecraft:knowledge_book");
public static final ItemData[] CREATIVE_ITEMS;
public static final List<StartGamePacket.ItemEntry> ITEMS = new ArrayList<>();
@ -245,7 +251,10 @@ public class ItemRegistry {
if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() ||
// Make exceptions for potions and tipped arrows, whose damage values can vary
(itemEntry.getJavaIdentifier().endsWith("potion") || itemEntry.getJavaIdentifier().equals("minecraft:arrow")))) {
return itemEntry;
if (!JAVA_ONLY_ITEMS.contains(itemEntry.getJavaIdentifier())) {
// From a Bedrock item data, we aren't getting one of these items
return itemEntry;
}
}
}

View File

@ -111,6 +111,10 @@ public abstract class ItemTranslator {
translator.translateToJava(itemStack.getNbt(), javaItem);
}
}
if (itemStack.getNbt().isEmpty()) {
// Otherwise, seems to causes issues with villagers accepting books, and I don't see how this will break anything else. - Camotoy
itemStack = new ItemStack(itemStack.getId(), itemStack.getAmount(), null);
}
}
return itemStack;
}
@ -126,7 +130,7 @@ public abstract class ItemTranslator {
// This is a fallback for maps with no nbt
if (nbt == null && bedrockItem.getJavaIdentifier().equals("minecraft:filled_map")) {
nbt = new com.github.steveice10.opennbt.tag.builtin.CompoundTag("");
nbt = new CompoundTag("");
nbt.put(new IntTag("map", 0));
}
@ -218,7 +222,7 @@ public abstract class ItemTranslator {
NbtMapBuilder builder = NbtMap.builder();
if (tag.getValue() != null && !tag.getValue().isEmpty()) {
for (String str : tag.getValue().keySet()) {
com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str);
Tag javaTag = tag.get(str);
Object translatedTag = translateToBedrockNBT(javaTag);
if (translatedTag == null)
continue;
@ -287,21 +291,21 @@ public abstract class ItemTranslator {
return new NbtList(type, tagList);
}
if (tag instanceof com.github.steveice10.opennbt.tag.builtin.CompoundTag) {
com.github.steveice10.opennbt.tag.builtin.CompoundTag compoundTag = (com.github.steveice10.opennbt.tag.builtin.CompoundTag) tag;
if (tag instanceof CompoundTag) {
CompoundTag compoundTag = (CompoundTag) tag;
return translateNbtToBedrock(compoundTag);
}
return null;
}
public com.github.steveice10.opennbt.tag.builtin.CompoundTag translateToJavaNBT(String name, NbtMap tag) {
com.github.steveice10.opennbt.tag.builtin.CompoundTag javaTag = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(name);
Map<String, com.github.steveice10.opennbt.tag.builtin.Tag> javaValue = javaTag.getValue();
public CompoundTag translateToJavaNBT(String name, NbtMap tag) {
CompoundTag javaTag = new CompoundTag(name);
Map<String, Tag> javaValue = javaTag.getValue();
if (tag != null && !tag.isEmpty()) {
for (String str : tag.keySet()) {
Object bedrockTag = tag.get(str);
com.github.steveice10.opennbt.tag.builtin.Tag translatedTag = translateToJavaNBT(str, bedrockTag);
Tag translatedTag = translateToJavaNBT(str, bedrockTag);
if (translatedTag == null)
continue;
@ -313,7 +317,7 @@ public abstract class ItemTranslator {
return javaTag;
}
private com.github.steveice10.opennbt.tag.builtin.Tag translateToJavaNBT(String name, Object object) {
private Tag translateToJavaNBT(String name, Object object) {
if (object instanceof int[]) {
return new IntArrayTag(name, (int[]) object);
}
@ -355,10 +359,10 @@ public abstract class ItemTranslator {
}
if (object instanceof List) {
List<com.github.steveice10.opennbt.tag.builtin.Tag> tags = new ArrayList<>();
List<Tag> tags = new ArrayList<>();
for (Object value : (List<?>) object) {
com.github.steveice10.opennbt.tag.builtin.Tag javaTag = translateToJavaNBT("", value);
Tag javaTag = translateToJavaNBT("", value);
if (javaTag != null)
tags.add(javaTag);
}

View File

@ -41,6 +41,12 @@ import java.util.*;
*/
public class RecipeRegistry {
/**
* Stores the last used recipe network ID. Since 1.16.200 (and for server-authoritative inventories),
* each recipe needs a unique network ID (or else in .200 the client crashes).
*/
public static int LAST_RECIPE_NET_ID = 0;
/**
* A list of all possible leather armor dyeing recipes.
* Created manually.
@ -72,20 +78,36 @@ public class RecipeRegistry {
*/
public static final List<CraftingData> TIPPED_ARROW_RECIPES = new ObjectArrayList<>();
// TODO: These are the other "multi" UUIDs that supposedly enable various recipes. Find out what each enables.
// 442d85ed-8272-4543-a6f1-418f90ded05d 8b36268c-1829-483c-a0f1-993b7156a8f2 602234e4-cac1-4353-8bb7-b1ebff70024b 98c84b38-1085-46bd-b1ce-dd38c159e6cc
// d81aaeaf-e172-4440-9225-868df030d27b b5c5d105-75a2-4076-af2b-923ea2bf4bf0 00000000-0000-0000-0000-000000000002 85939755-ba10-4d9d-a4cc-efb7a8e943c4
// d392b075-4ba1-40ae-8789-af868d56f6ce aecd2294-4b94-434b-8667-4499bb2c9327
/**
* Recipe data that, when sent to the client, enables book cloning
*/
public static final CraftingData BOOK_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"));
public static final CraftingData BOOK_CLONING_RECIPE_DATA;
/**
* Recipe data that, when sent to the client, enables tool repairing in a crafting table
*/
public static final CraftingData TOOL_REPAIRING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("00000000-0000-0000-0000-000000000001"));
public static final CraftingData TOOL_REPAIRING_RECIPE_DATA;
/**
* Recipe data that, when sent to the client, enables map extending in a crafting table
*/
public static final CraftingData MAP_EXTENDING_RECIPE_DATA;
/**
* Recipe data that, when sent to the client, enables map cloning in a crafting table
*/
public static final CraftingData MAP_CLONING_RECIPE_DATA;
/**
* Recipe data that, when sent to the client, enables banner duplicating
*/
public static final CraftingData BANNER_DUPLICATING_RECIPE_DATA;
static {
BOOK_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), LAST_RECIPE_NET_ID++);
TOOL_REPAIRING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("00000000-0000-0000-0000-000000000001"), LAST_RECIPE_NET_ID++);
MAP_EXTENDING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), LAST_RECIPE_NET_ID++);
MAP_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), LAST_RECIPE_NET_ID++);
BANNER_DUPLICATING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("b5c5d105-75a2-4076-af2b-923ea2bf4bf0"), LAST_RECIPE_NET_ID++);
// https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/inventory/MultiRecipe.php
// Get all recipes that are not directly sent from a Java server
InputStream stream = FileUtils.getResource("mappings/recipes.json");
@ -142,19 +164,19 @@ public class RecipeRegistry {
letterToRecipe.put(entry.getKey(), ItemRegistry.getBedrockItemFromJson(entry.getValue()));
}
ItemData[] inputs = new ItemData[shape.size() * shape.get(0).length()];
List<ItemData> inputs = new ArrayList<>(shape.size() * shape.get(0).length());
int i = 0;
// Create a linear array of items from the "cube" of the shape
for (int j = 0; i < shape.size() * shape.get(0).length(); j++) {
for (char c : shape.get(j).toCharArray()) {
ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR);
inputs[i] = data;
inputs.add(data);
i++;
}
}
return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(),
inputs, new ItemData[]{output}, uuid, "crafting_table", 0);
inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++);
}
List<ItemData> inputs = new ObjectArrayList<>();
for (JsonNode entry : node.get("input")) {
@ -163,10 +185,10 @@ public class RecipeRegistry {
if (node.get("type").asInt() == 5) {
// Shulker box
return CraftingData.fromShulkerBox(uuid.toString(),
inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0);
inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++);
}
return CraftingData.fromShapeless(uuid.toString(),
inputs.toArray(new ItemData[0]), new ItemData[]{output}, uuid, "crafting_table", 0);
inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++);
}
public static void init() {

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2020 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.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.utils.LocaleUtils;
@ItemRemapper
public class PlayerHeadTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("display") || !((CompoundTag) itemTag.get("display")).contains("Name")) {
if (itemTag.contains("SkullOwner")) {
StringTag name;
Tag skullOwner = itemTag.get("SkullOwner");
if (skullOwner instanceof StringTag) {
name = (StringTag) skullOwner;
} else {
StringTag skullName;
if (skullOwner instanceof CompoundTag && (skullName = ((CompoundTag) skullOwner).get("Name")) != null) {
name = skullName;
} else {
session.getConnector().getLogger().debug("Not sure how to handle skull head item display. " + itemTag);
return;
}
}
// Add correct name of player skull
// TODO: It's always yellow, even with a custom name. Handle?
String displayName = "\u00a7r\u00a7e" + LocaleUtils.getLocaleString("block.minecraft.player_head.named", session.getLocale()).replace("%s", name.getValue());
if (!itemTag.contains("display")) {
itemTag.put(new CompoundTag("display"));
}
((CompoundTag) itemTag.get("display")).put(new StringTag("Name", displayName));
}
}
}
@Override
public boolean acceptItem(ItemEntry itemEntry) {
return itemEntry.getJavaIdentifier().equals("minecraft:player_head");
}
}

View File

@ -57,7 +57,7 @@ public class JavaChatTranslator extends PacketTranslator<ServerChatPacket> {
}
textPacket.setNeedsTranslation(false);
textPacket.setMessage(MessageTranslator.convertMessage(packet.getMessage().toString(), session.getLocale()));
textPacket.setMessage(MessageTranslator.convertMessage(packet.getMessage(), session.getLocale()));
session.sendUpstreamPacket(textPacket);
}

View File

@ -51,6 +51,8 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
@Override
public void translate(ServerDeclareRecipesPacket packet, GeyserSession session) {
// Get the last known network ID (first used for the pregenerated recipes) and increment from there.
int networkId = RecipeRegistry.LAST_RECIPE_NET_ID;
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
craftingDataPacket.setCleanRecipes(true);
for (Recipe recipe : packet.getRecipes()) {
@ -63,7 +65,7 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
for (ItemData[] inputs : inputCombinations) {
UUID uuid = UUID.randomUUID();
craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(),
inputs, new ItemData[]{output}, uuid, "crafting_table", 0));
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, networkId++));
}
break;
}
@ -75,8 +77,8 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
for (ItemData[] inputs : inputCombinations) {
UUID uuid = UUID.randomUUID();
craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(),
shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), inputs,
new ItemData[]{output}, uuid, "crafting_table", 0));
shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs),
Collections.singletonList(output), uuid, "crafting_table", 0, networkId++));
}
break;
}
@ -90,6 +92,18 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
craftingDataPacket.getCraftingData().add(RecipeRegistry.TOOL_REPAIRING_RECIPE_DATA);
break;
}
case CRAFTING_SPECIAL_MAPCLONING: {
craftingDataPacket.getCraftingData().add(RecipeRegistry.MAP_CLONING_RECIPE_DATA);
break;
}
case CRAFTING_SPECIAL_MAPEXTENDING: {
craftingDataPacket.getCraftingData().add(RecipeRegistry.MAP_EXTENDING_RECIPE_DATA);
break;
}
case CRAFTING_SPECIAL_BANNERDUPLICATE: {
craftingDataPacket.getCraftingData().add(RecipeRegistry.BANNER_DUPLICATING_RECIPE_DATA);
break;
}
// Java doesn't actually tell us the recipes so we need to calculate this ahead of time.
case CRAFTING_SPECIAL_FIREWORK_ROCKET: {

View File

@ -36,6 +36,6 @@ public class JavaDisconnectPacket extends PacketTranslator<ServerDisconnectPacke
@Override
public void translate(ServerDisconnectPacket packet, GeyserSession session) {
session.disconnect(MessageTranslator.convertMessage(packet.getReason().toString(), session.getLocale()));
session.disconnect(MessageTranslator.convertMessage(packet.getReason(), session.getLocale()));
}
}

View File

@ -35,7 +35,7 @@ import com.nukkitx.protocol.bedrock.data.GameRuleData;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.packet.*;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;

View File

@ -39,10 +39,9 @@ public class JavaKeepAliveTranslator extends PacketTranslator<ServerKeepAlivePac
@Override
public void translate(ServerKeepAlivePacket packet, GeyserSession session) {
session.setLastKeepAliveTimestamp(packet.getPingId());
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
latencyPacket.setFromServer(true);
latencyPacket.setTimestamp(packet.getPingId());
latencyPacket.setTimestamp(packet.getPingId() * 1000);
session.sendUpstreamPacket(latencyPacket);
}
}

View File

@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator<LoginDisconn
@Override
public void translate(LoginDisconnectPacket packet, GeyserSession session) {
// The client doesn't manually get disconnected so we have to do it ourselves
session.disconnect(MessageTranslator.convertMessage(packet.getReason().toString(), session.getLocale()));
session.disconnect(MessageTranslator.convertMessage(packet.getReason(), session.getLocale()));
}
}

View File

@ -45,7 +45,7 @@ public class JavaTitleTranslator extends PacketTranslator<ServerTitlePacket> {
if (packet.getTitle() == null) {
text = " ";
} else {
text = MessageTranslator.convertMessage(packet.getTitle().toString(), locale);
text = MessageTranslator.convertMessage(packet.getTitle(), locale);
}
switch (packet.getAction()) {

View File

@ -25,16 +25,14 @@
package org.geysermc.connector.network.translators.java.entity;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityEffectPacket;
import com.nukkitx.protocol.bedrock.packet.MobEffectPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.EntityUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityEffectPacket;
import com.nukkitx.protocol.bedrock.packet.MobEffectPacket;
@Translator(packet = ServerEntityEffectPacket.class)
public class JavaEntityEffectTranslator extends PacketTranslator<ServerEntityEffectPacket> {
@ -43,7 +41,7 @@ public class JavaEntityEffectTranslator extends PacketTranslator<ServerEntityEff
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) {
entity = session.getPlayerEntity();
((PlayerEntity) entity).getEffectCache().addEffect(packet.getEffect(), packet.getAmplifier());
session.getEffectCache().addEffect(packet.getEffect(), packet.getAmplifier());
}
if (entity == null)
return;

View File

@ -25,16 +25,14 @@
package org.geysermc.connector.network.translators.java.entity;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRemoveEffectPacket;
import com.nukkitx.protocol.bedrock.packet.MobEffectPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.EntityUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRemoveEffectPacket;
import com.nukkitx.protocol.bedrock.packet.MobEffectPacket;
@Translator(packet = ServerEntityRemoveEffectPacket.class)
public class JavaEntityRemoveEffectTranslator extends PacketTranslator<ServerEntityRemoveEffectPacket> {
@ -43,7 +41,7 @@ public class JavaEntityRemoveEffectTranslator extends PacketTranslator<ServerEnt
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) {
entity = session.getPlayerEntity();
((PlayerEntity) entity).getEffectCache().removeEffect(packet.getEffect());
session.getEffectCache().removeEffect(packet.getEffect());
}
if (entity == null)
return;

View File

@ -61,6 +61,10 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
if (passengerId == session.getPlayerEntity().getEntityId()) {
passenger = session.getPlayerEntity();
session.setRidingVehicleEntity(entity);
// We need to confirm teleports before entering a vehicle, or else we will likely exit right out
if (session.getConnector().getConfig().isCacheChunks()) {
session.confirmTeleport(passenger.getPosition().sub(0, EntityType.PLAYER.getOffset(), 0).toDouble());
}
}
// Passenger hasn't loaded in and entity link needs to be set later
if (passenger == null && passengerId != 0) {
@ -82,9 +86,9 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 90f);
passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, !passengers.isEmpty() ? -90f : 0f);
} else {
passenger.getMetadata().remove(EntityData.RIDER_ROTATION_LOCKED);
passenger.getMetadata().remove(EntityData.RIDER_MAX_ROTATION);
passenger.getMetadata().remove(EntityData.RIDER_MIN_ROTATION);
passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0);
passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f);
passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, 0f);
}
passenger.updateBedrockMetadata(session);
@ -106,6 +110,9 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
linkPacket.setEntityLink(new EntityLinkData(entity.getGeyserId(), passenger.getGeyserId(), EntityLinkData.Type.REMOVE, false));
session.sendUpstreamPacket(linkPacket);
passengers.remove(passenger.getEntityId());
passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 0);
passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 0f);
passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, 0f);
this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1));
} else {

View File

@ -26,19 +26,11 @@
package org.geysermc.connector.network.translators.java.entity.player;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerAbilitiesPacket;
import com.nukkitx.protocol.bedrock.data.AdventureSetting;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import java.util.Set;
@Translator(packet = ServerPlayerAbilitiesPacket.class)
public class JavaPlayerAbilitiesTranslator extends PacketTranslator<ServerPlayerAbilitiesPacket> {

View File

@ -26,11 +26,11 @@
package org.geysermc.connector.network.translators.java.entity.player;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.SkinUtils;
import org.geysermc.connector.skin.SkinManager;
import com.github.steveice10.mc.protocol.data.game.PlayerListEntry;
import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction;
@ -57,7 +57,8 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
if (self) {
// Entity is ourself
playerEntity = session.getPlayerEntity();
SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
//TODO: playerEntity.setProfile(entry.getProfile()); seems to help with online mode skins but needs more testing to ensure Floodgate skins aren't overwritten
SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data"));
} else {
playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId());
@ -81,7 +82,7 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
playerEntity.setPlayerList(true);
playerEntity.setValid(true);
PlayerListPacket.Entry playerListEntry = SkinUtils.buildCachedEntry(session, entry.getProfile(), playerEntity.getGeyserId());
PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity);
translate.getEntries().add(playerListEntry);
break;

View File

@ -32,7 +32,7 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import com.nukkitx.protocol.bedrock.packet.RespawnPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.TeleportCache;
@ -91,7 +91,8 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator<Serve
// Ignore certain move correction packets for smoother movement
// These are never relative
if (packet.getRelative().isEmpty()) {
// When chunk caching is enabled this isn't needed as we shouldn't get these
if (!session.getConnector().getConfig().isCacheChunks() && packet.getRelative().isEmpty()) {
double xDis = Math.abs(entity.getPosition().getX() - packet.getX());
double yDis = entity.getPosition().getY() - packet.getY();
double zDis = Math.abs(entity.getPosition().getZ() - packet.getZ());
@ -111,13 +112,16 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator<Serve
double newZ = packet.getZ() +
(packet.getRelative().contains(PositionElement.Z) ? entity.getPosition().getZ() : 0);
double newPitch = packet.getPitch() +
float newPitch = packet.getPitch() +
(packet.getRelative().contains(PositionElement.PITCH) ? entity.getBedrockRotation().getX() : 0);
double newYaw = packet.getYaw() +
float newYaw = packet.getYaw() +
(packet.getRelative().contains(PositionElement.YAW) ? entity.getBedrockRotation().getY() : 0);
session.getConnector().getLogger().debug("Teleport from " + entity.getPosition().getX() + " " + (entity.getPosition().getY() - EntityType.PLAYER.getOffset()) + " " + entity.getPosition().getZ());
session.setTeleportCache(new TeleportCache(newX, newY, newZ, packet.getTeleportId()));
entity.moveAbsolute(session, Vector3f.from(newX, newY, newZ), (float) newYaw, (float) newPitch, true, true);
session.addTeleport(new TeleportCache(newX, newY, newZ, newPitch, newYaw, packet.getTeleportId()));
entity.moveAbsolute(session, Vector3f.from(newX, newY, newZ), newYaw, newPitch, true, true);
session.getConnector().getLogger().debug("to " + entity.getPosition().getX() + " " + (entity.getPosition().getY() - EntityType.PLAYER.getOffset()) + " " + entity.getPosition().getZ());
}
}

View File

@ -28,12 +28,12 @@ package org.geysermc.connector.network.translators.java.entity.spawn;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.SkinUtils;
import org.geysermc.connector.skin.SkinManager;
@Translator(packet = ServerSpawnPlayerPacket.class)
public class JavaSpawnPlayerTranslator extends PacketTranslator<ServerSpawnPlayerPacket> {
@ -62,7 +62,7 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator<ServerSpawnPlaye
if (session.getUpstream().isInitialized()) {
entity.sendPlayer(session);
SkinUtils.requestAndHandleSkinAndCape(entity, session, null);
SkinManager.requestAndHandleSkinAndCape(entity, session, null);
}
}
}

View File

@ -54,7 +54,7 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator<ServerSc
switch (packet.getAction()) {
case ADD:
case UPDATE:
objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName().toString()))
objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setType(packet.getType().ordinal());
break;
case REMOVE:

View File

@ -59,11 +59,11 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
switch (packet.getAction()) {
case CREATE:
scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers()))
.setName(MessageTranslator.convertMessage(packet.getDisplayName().toString()))
.setName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
.setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale()))
.setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale()));
.setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale()))
.setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale()));
break;
case UPDATE:
if (team == null) {
@ -74,11 +74,11 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
return;
}
team.setName(MessageTranslator.convertMessage(packet.getDisplayName().toString()))
team.setName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setColor(packet.getColor())
.setNameTagVisibility(packet.getNameTagVisibility())
.setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale()))
.setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale()))
.setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale()))
.setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale()))
.setUpdateType(UpdateType.UPDATE);
break;
case ADD_PLAYER:

View File

@ -29,12 +29,10 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSl
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.Objects;
@Translator(packet = ServerSetSlotPacket.class)
public class JavaSetSlotTranslator extends PacketTranslator<ServerSetSlotPacket> {

View File

@ -38,7 +38,7 @@ import com.nukkitx.protocol.bedrock.data.GameRuleData;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.packet.*;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;

View File

@ -78,7 +78,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator<ServerSpawnPar
int r = (int) (data.getRed()*255);
int g = (int) (data.getGreen()*255);
int b = (int) (data.getBlue()*255);
particle.setType(LevelEventType.PARTICLE_REDSTONE);
particle.setType(LevelEventType.PARTICLE_FALLING_DUST);
particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff));
particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()));
session.sendUpstreamPacket(particle);

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