Fix inconsistencies with movement and position (#699)

Movement is now significant better, especially on slabs, stairs, and other half-blocks.

Co-authored-by: RednedEpic <redned235@gmail.com>
Co-authored-by: DoctorMacc <toy.fighter1@gmail.com>
Co-authored-by: Tim203 <mctim203@gmail.com>
Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com>
This commit is contained in:
circuit10 2020-11-20 19:56:39 +00:00 committed by GitHub
parent 4297215420
commit a70d3e2150
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 2117 additions and 248 deletions

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

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

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

@ -257,6 +257,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,7 @@ 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.utils.DimensionUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
@ -134,6 +135,7 @@ public class GeyserConnector {
EntityIdentifierRegistry.init();
ItemRegistry.init();
ItemTranslator.init();
CollisionTranslator.init();
LocaleUtils.init();
PotionMixRegistry.init();
RecipeRegistry.init();

View file

@ -135,6 +135,8 @@ public interface GeyserConfiguration {
int getMtu();
boolean isUseAdapters();
int getConfigVersion();
static void checkGeyserConfiguration(GeyserConfiguration geyserConfig, GeyserLogger geyserLogger) {

View file

@ -170,6 +170,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

@ -36,7 +36,6 @@ 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;
@ -49,11 +48,11 @@ import lombok.Setter;
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;

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

@ -23,7 +23,7 @@
* @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;
@ -43,12 +43,13 @@ import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
import lombok.Getter;
import lombok.Setter;
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;
@ -66,7 +67,6 @@ public class PlayerEntity extends LivingEntity {
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 +83,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 +157,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 +232,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

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

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

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;
@ -63,7 +67,7 @@ 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.inventory.PlayerInventory;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.network.remote.RemoteServer;
@ -73,6 +77,7 @@ import org.geysermc.connector.network.session.cache.*;
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.collision.CollisionManager;
import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.utils.*;
@ -100,16 +105,21 @@ 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 WindowCache windowCache;
@Setter
private TeleportCache teleportCache;
private final Int2ObjectMap<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
/**
* Stores session collision
*/
private final CollisionManager collisionManager;
@Getter
private final Long2ObjectMap<ClientboundMapItemDataPacket> storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
@ -136,7 +146,6 @@ public class GeyserSession implements CommandSender {
private final AtomicInteger pendingDimSwitches = new AtomicInteger(0);
@Setter
private boolean sneaking;
@Setter
@ -313,11 +322,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.windowCache = new WindowCache(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;
@ -561,6 +573,7 @@ public class GeyserSession implements CommandSender {
this.chunkCache = null;
this.entityCache = null;
this.effectCache = null;
this.worldCache = null;
this.inventoryCache = null;
this.windowCache = null;
@ -576,6 +589,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();
@ -679,24 +698,81 @@ 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;
}
/**
* Queue a packet to be sent to player.
*
*
* @param packet the bedrock packet from the NukkitX protocol lib
*/
public void sendUpstreamPacket(BedrockPacket packet) {
@ -709,7 +785,7 @@ public class GeyserSession implements CommandSender {
/**
* Send a packet immediately to the player.
*
*
* @param packet the bedrock packet from the NukkitX protocol lib
*/
public void sendUpstreamPacketImmediately(BedrockPacket packet) {

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

@ -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,7 +25,7 @@
package org.geysermc.connector.network.translators.bedrock;
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

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

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

@ -34,7 +34,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePack
import com.nukkitx.protocol.bedrock.data.GameRuleData;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
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

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

@ -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,7 +26,7 @@
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;

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,7 +28,7 @@ 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;

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

@ -71,6 +71,9 @@ public class BlockTranslator {
public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap();
public static final Int2ObjectMap<String> JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>();
// The index of the collision data in collision.json
public static final Int2IntMap JAVA_RUNTIME_ID_TO_COLLISION_INDEX = new Int2IntOpenHashMap();
/**
* Java numeric ID to java unique identifier, used for block names in the statistics screen
*/
@ -165,6 +168,11 @@ public class BlockTranslator {
JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue());
}
JsonNode collisionIndexNode = entry.getValue().get("collision_index");
if (hardnessNode != null) {
JAVA_RUNTIME_ID_TO_COLLISION_INDEX.put(javaRuntimeId, collisionIndexNode.intValue());
}
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
// Used for adding all "special" Java block states to block state map

View file

@ -125,8 +125,8 @@ public class BlockUtils {
return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, false, false);
}
hasteLevel = session.getPlayerEntity().getEffectCache().getEffectLevel(Effect.FASTER_DIG);
miningFatigueLevel = session.getPlayerEntity().getEffectCache().getEffectLevel(Effect.SLOWER_DIG);
hasteLevel = session.getEffectCache().getEffectLevel(Effect.FASTER_DIG);
miningFatigueLevel = session.getEffectCache().getEffectLevel(Effect.SLOWER_DIG);
boolean isInWater = session.getConnector().getConfig().isCacheChunks()
&& BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == BlockTranslator.BEDROCK_WATER_ID;

View file

@ -82,7 +82,7 @@ public class DimensionUtils {
session.setSpawned(false);
session.setLastChunkPosition(null);
for (Effect effect : session.getPlayerEntity().getEffectCache().getEntityEffects().keySet()) {
for (Effect effect : session.getEffectCache().getEntityEffects().keySet()) {
MobEffectPacket mobEffectPacket = new MobEffectPacket();
mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE);
mobEffectPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
@ -90,7 +90,7 @@ public class DimensionUtils {
session.sendUpstreamPacket(mobEffectPacket);
}
// Effects are re-sent from server
session.getPlayerEntity().getEffectCache().getEntityEffects().clear();
session.getEffectCache().getEntityEffects().clear();
//let java server handle portal travel sound
StopSoundPacket stopSoundPacket = new StopSoundPacket();

View file

@ -35,7 +35,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.connector.GeyserConnector;
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.session.auth.BedrockClientData;

View file

@ -94,13 +94,16 @@ show-cooldown: true
# default-locale: en_us
# Configures if chunk caching should be enabled or not. This keeps an individual
# record of each block the client loads in. While this feature does allow for a few
# things such as block break animations to show up in creative mode and among others,
# it is HIGHLY recommended you disable this on a production environment as it can eat
# up a lot of RAM. However, when using the Spigot version of Geyser, support for features
# or implementations this allows is automatically enabled without the additional caching as
# Geyser has direct access to the server itself.
cache-chunks: false
# record of each block the client loads in. This feature does allow for a few things
# such as more accurate movement that causes less problems with anticheat (meaning
# you're less likely to be banned) and allows block break animations to show up in
# creative mode (and other features). Although this increases RAM usage, it likely
# won't have much of an effect for the vast majority of people. However, if you're
# running out of RAM or are in a RAM-sensitive environment, you may want to disable
# this. When using the Spigot version of Geyser, support for features or
# implementations this allows is automatically enabled without the additional caching
# as Geyser has direct access to the server itself.
cache-chunks: true
# Specify how many days images will be cached to disk to save downloading them from the internet.
# A value of 0 is disabled. (Default: 0)
@ -148,4 +151,8 @@ enable-proxy-connections: false
# 1400 is the default.
# mtu: 1400
# Whether to use direct server methods to retrieve information such as block states.
# Turning this off for Spigot will stop NMS from being used but will have a performance impact.
use-adapters: true
config-version: 4

@ -1 +1 @@
Subproject commit bf4b0b7103193154dd0b06e0459dc375c753069a
Subproject commit b7ef31bd9c45aa3a0735883764c231f30cb55bfa

@ -1 +1 @@
Subproject commit 618a9b981398647125b1b63494cb49ad93433243
Subproject commit 2d14c9dc3d75df7463fc7605a6cff63b5926a03e