Merge branch 'master' of https://github.com/GeyserMC/Geyser into scoreboard-changes

This commit is contained in:
Camotoy 2020-12-30 15:02:49 -05:00
commit 1611e26ec0
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
29 changed files with 399 additions and 106 deletions

2
Jenkinsfile vendored
View file

@ -66,7 +66,7 @@ pipeline {
}
deleteDir()
withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) {
discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.opencollab.dev/job/GeyserMC/job/Geyser)", footer: 'Open Collaboration Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
}
}
}

View file

@ -3,7 +3,7 @@
[![forthebadge made-with-java](http://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Build Status](https://ci.nukkitx.com/job/Geyser/job/master/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/master/)
[![Build Status](https://ci.opencollab.dev/job/Geyser/job/master/badge/icon)](https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/master/)
[![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/)
[![HitCount](http://hits.dwyl.io/Geyser/GeyserMC.svg)](http://hits.dwyl.io/Geyser/GeyserMC)
[![Crowdin](https://badges.crowdin.net/geyser/localized.svg)](https://translate.geysermc.org/)
@ -52,6 +52,8 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
The following things can't be fixed because of Bedrock limitations. They might be fixable in the future, but not as of now.
- Custom heads in inventories
- Clickable links in chat
- Glowing effect
## Compiling
1. Clone the repo to your computer

View file

@ -125,7 +125,7 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager {
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);
Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ);
// 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));

View file

@ -32,7 +32,7 @@
<dependency>
<groupId>com.github.CloudburstMC.Protocol</groupId>
<artifactId>bedrock-v422</artifactId>
<version>87d862d69d</version>
<version>d41b84e86c</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@ -124,14 +124,26 @@
<version>26201a4</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.steveice10</groupId>
<artifactId>packetlib</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.steveice10</groupId>
<artifactId>PacketLib</artifactId>
<version>54f761c</version>
<scope>compile</scope>
<exclusions>
<exclusion> <!-- Move this exclusion back to MCProtocolLib it gets the latest PacketLib -->
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -140,6 +152,12 @@
<version>4.1.43.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-haproxy</artifactId>
<version>4.1.56.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>

View file

@ -44,6 +44,7 @@ import org.geysermc.connector.network.session.GeyserSession;
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.CollisionTranslator;
import org.geysermc.connector.network.translators.effect.EffectRegistry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
@ -54,7 +55,6 @@ import org.geysermc.connector.network.translators.sound.SoundRegistry;
import org.geysermc.connector.network.translators.world.WorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
import org.geysermc.connector.network.translators.collision.CollisionTranslator;
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
import org.geysermc.connector.scoreboard.ScoreboardUpdater;
import org.geysermc.connector.utils.DimensionUtils;
@ -68,10 +68,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@ -328,6 +325,38 @@ public class GeyserConnector {
players.remove(player);
}
/**
* Gets a player by their current UUID
*
* @param uuid the uuid
* @return the player or <code>null</code> if there is no player online with this UUID
*/
public GeyserSession getPlayerByUuid(UUID uuid) {
for (GeyserSession session : players) {
if (session.getPlayerEntity().getUuid().equals(uuid)) {
return session;
}
}
return null;
}
/**
* Gets a player by their Xbox user identifier
*
* @param xuid the Xbox user identifier
* @return the player or <code>null</code> if there is no player online with this xuid
*/
public GeyserSession getPlayerByXuid(String xuid) {
for (GeyserSession session : players) {
if (session.getAuthData() != null && session.getAuthData().getXboxUUID().equals(xuid)) {
return session;
}
}
return null;
}
public static GeyserConnector start(PlatformType platformType, GeyserBootstrap bootstrap) {
return new GeyserConnector(platformType, bootstrap);
}

View file

@ -71,7 +71,7 @@ public class VersionCommand extends GeyserCommand {
Properties gitProp = new Properties();
gitProp.load(FileUtils.getResource("git.properties"));
String buildXML = WebUtils.getBody("https://ci.nukkitx.com/job/GeyserMC/job/Geyser/job/" + URLEncoder.encode(gitProp.getProperty("git.branch"), StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber");
String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + URLEncoder.encode(gitProp.getProperty("git.branch"), StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber");
if (buildXML.startsWith("<buildNumber>")) {
int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim());
int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number"));

View file

@ -34,7 +34,7 @@ import java.util.Map;
public interface GeyserConfiguration {
// Modify this when you update the config
// Modify this when you introduce breaking changes into the config
int CURRENT_CONFIG_VERSION = 4;
IBedrockConfiguration getBedrock();
@ -117,6 +117,8 @@ public interface GeyserConfiguration {
void setPort(int port);
String getAuthType();
boolean isUseProxyProtocol();
}
interface IUserAuthenticationInfo {

View file

@ -148,6 +148,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@Setter
@JsonProperty("auth-type")
private String authType = "online";
@JsonProperty("use-proxy-protocol")
private boolean useProxyProtocol = false;
}
@Getter

View file

@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
import com.github.steveice10.mc.protocol.data.game.world.particle.Particle;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.effect.EffectRegistry;
@ -45,6 +46,8 @@ public class AreaEffectCloudEntity extends Entity {
metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, 0.0f);
metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_RATE, -0.005f);
metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, -0.5f);
metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
}
@Override

View file

@ -0,0 +1,77 @@
/*
* 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;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.Potion;
import java.util.EnumSet;
public class ThrownPotionEntity extends ThrowableEntity {
private static final EnumSet<Potion> NON_ENCHANTED_POTIONS = EnumSet.of(Potion.WATER, Potion.MUNDANE, Potion.THICK, Potion.AWKWARD);
public ThrownPotionEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 7 && entityMetadata.getType() == MetadataType.ITEM) {
ItemStack itemStack = (ItemStack) entityMetadata.getValue();
ItemEntry itemEntry = ItemRegistry.getItem(itemStack);
if (itemEntry.getJavaIdentifier().endsWith("potion") && itemStack.getNbt() != null) {
Tag potionTag = itemStack.getNbt().get("Potion");
if (potionTag instanceof StringTag) {
Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue());
if (potion != null) {
metadata.put(EntityData.POTION_AUX_VALUE, potion.getBedrockId());
metadata.getFlags().setFlag(EntityFlag.ENCHANTED, !NON_ENCHANTED_POTIONS.contains(potion));
} else {
metadata.put(EntityData.POTION_AUX_VALUE, 0);
GeyserConnector.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue());
}
}
boolean isLingering = itemEntry.getJavaIdentifier().equals("minecraft:lingering_potion");
metadata.getFlags().setFlag(EntityFlag.LINGERING, isLingering);
}
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -123,7 +123,7 @@ public enum EntityType {
PAINTING(PaintingEntity.class, 83, 0f),
MINECART(MinecartEntity.class, 84, 0.7f, 0.98f, 0.98f, 0.35f),
FIREBALL(ItemedFireballEntity.class, 85, 1.0f),
THROWN_POTION(ThrowableEntity.class, 86, 0.25f, 0.25f, 0.25f, 0f, "minecraft:splash_potion"),
THROWN_POTION(ThrownPotionEntity.class, 86, 0.25f, 0.25f, 0.25f, 0f, "minecraft:splash_potion"),
THROWN_ENDERPEARL(ThrowableEntity.class, 87, 0.25f, 0.25f, 0.25f, 0f, "minecraft:ender_pearl"),
LEASH_KNOT(LeashKnotEntity.class, 88, 0.5f, 0.375f),
WITHER_SKULL(WitherSkullEntity.class, 89, 0.3125f),

View file

@ -132,11 +132,6 @@ public class LoggingPacketHandler implements BedrockPacketHandler {
return defaultHandler(packet);
}
@Override
public boolean handle(EntityFallPacket packet) {
return defaultHandler(packet);
}
@Override
public boolean handle(EntityPickRequestPacket packet) {
return defaultHandler(packet);
@ -826,4 +821,43 @@ public class LoggingPacketHandler implements BedrockPacketHandler {
public boolean handle(PositionTrackingDBServerBroadcastPacket packet) {
return defaultHandler(packet);
}
// 1.16.100 new packets
@Override
public boolean handle(MotionPredictionHintsPacket packet) {
return defaultHandler(packet);
}
@Override
public boolean handle(AnimateEntityPacket packet) {
return defaultHandler(packet);
}
@Override
public boolean handle(CameraShakePacket packet) {
return defaultHandler(packet);
}
@Override
public boolean handle(PlayerFogPacket packet) {
return defaultHandler(packet);
}
@Override
public boolean handle(CorrectPlayerMovePredictionPacket packet) {
return defaultHandler(packet);
}
@Override
public boolean handle(ItemComponentPacket packet) {
return defaultHandler(packet);
}
// 1.16.200 new packet
@Override
public boolean handle(FilterTextPacket packet) {
return defaultHandler(packet);
}
}

View file

@ -39,6 +39,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye
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;
import com.github.steveice10.packetlib.BuiltinFlags;
import com.github.steveice10.packetlib.Client;
import com.github.steveice10.packetlib.event.session.*;
import com.github.steveice10.packetlib.packet.Packet;
@ -250,6 +251,12 @@ public class GeyserSession implements CommandSender {
@Setter
private ScheduledFuture<?> movementSendIfIdle;
/**
* Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless.
*/
@Setter
private boolean daylightCycle = true;
private boolean reducedDebugInfo = false;
@Setter
@ -453,6 +460,10 @@ public class GeyserSession implements CommandSender {
}
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
if (connector.getConfig().getRemote().isUseProxyProtocol()) {
downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
}
// Let Geyser handle sending the keep alive
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
downstream.getSession().addListener(new SessionAdapter() {
@ -642,7 +653,6 @@ public class GeyserSession implements CommandSender {
public void setRenderDistance(int renderDistance) {
renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle
if (renderDistance > 32) renderDistance = 32; // <3 u ViaVersion but I don't like crashing clients x)
this.renderDistance = renderDistance;
ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket();

View file

@ -46,7 +46,6 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator<BlockPic
return;
}
String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0];
InventoryUtils.findOrCreatePickedBlock(session, targetIdentifier);
InventoryUtils.findOrCreateItem(session, BlockTranslator.getPickItem(blockToPick));
}
}

View file

@ -110,6 +110,6 @@ public class BedrockEntityPickRequestTranslator extends PacketTranslator<EntityP
// Verify it is, indeed, an item
if (entry == null) return;
InventoryUtils.findOrCreatePickedBlock(session, fullItemName);
InventoryUtils.findOrCreateItem(session, fullItemName);
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.bedrock;
import com.nukkitx.protocol.bedrock.packet.FilterTextPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
/**
* Used to send strings to the server and filter out unwanted words.
* Java doesn't care, so we don't care, and we approve all strings.
*/
@Translator(packet = FilterTextPacket.class)
public class BedrockFilterTextTranslator extends PacketTranslator<FilterTextPacket> {
@Override
public void translate(FilterTextPacket packet, GeyserSession session) {
packet.setFromServer(true);
session.sendUpstreamPacket(packet);
}
}

View file

@ -38,7 +38,7 @@ public class BedrockTextTranslator extends PacketTranslator<TextPacket> {
@Override
public void translate(TextPacket packet, GeyserSession session) {
String message = packet.getMessage().replaceAll("^\\.", "/").trim();
String message = packet.getMessage();
if (MessageTranslator.isTooLong(message, session)) {
return;

View file

@ -45,8 +45,39 @@ import java.util.stream.Collectors;
@ItemRemapper
public class BannerTranslator extends ItemTranslator {
/**
* Holds what a Java ominous banner pattern looks like.
*
* Translating the patterns over to Bedrock does not work effectively, but Bedrock has a dedicated type for
* ominous banners that we set instead. This variable is used to detect Java ominous banner patterns, and apply
* the correct ominous banner pattern if Bedrock pulls the item from creative.
*/
public static final ListTag OMINOUS_BANNER_PATTERN;
private final List<ItemEntry> appliedItems;
static {
OMINOUS_BANNER_PATTERN = new ListTag("Patterns");
// Construct what an ominous banner is supposed to look like
OMINOUS_BANNER_PATTERN.add(getPatternTag("mr", 9));
OMINOUS_BANNER_PATTERN.add(getPatternTag("bs", 8));
OMINOUS_BANNER_PATTERN.add(getPatternTag("cs", 7));
OMINOUS_BANNER_PATTERN.add(getPatternTag("bo", 8));
OMINOUS_BANNER_PATTERN.add(getPatternTag("ms", 15));
OMINOUS_BANNER_PATTERN.add(getPatternTag("hh", 8));
OMINOUS_BANNER_PATTERN.add(getPatternTag("mc", 8));
OMINOUS_BANNER_PATTERN.add(getPatternTag("bo", 15));
}
private static CompoundTag getPatternTag(String pattern, int color) {
StringTag patternType = new StringTag("Pattern", pattern);
IntTag colorTag = new IntTag("Color", color);
CompoundTag tag = new CompoundTag("");
tag.put(patternType);
tag.put(colorTag);
return tag;
}
public BannerTranslator() {
appliedItems = ItemRegistry.ITEM_ENTRIES.values()
.stream()
@ -62,7 +93,7 @@ public class BannerTranslator extends ItemTranslator {
*/
public static NbtList<NbtMap> convertBannerPattern(ListTag patterns) {
List<NbtMap> tagsList = new ArrayList<>();
for (com.github.steveice10.opennbt.tag.builtin.Tag patternTag : patterns.getValue()) {
for (Tag patternTag : patterns.getValue()) {
NbtMap newPatternTag = getBedrockBannerPattern((CompoundTag) patternTag);
if (newPatternTag != null) {
tagsList.add(newPatternTag);
@ -134,7 +165,13 @@ public class BannerTranslator extends ItemTranslator {
ListTag patterns = blockEntityTag.get("Patterns");
NbtMapBuilder builder = itemData.getTag().toBuilder();
builder.put("Patterns", convertBannerPattern(patterns));
if (patterns.equals(OMINOUS_BANNER_PATTERN)) {
// Remove the current patterns and set the ominous banner type
builder.remove("Patterns");
builder.putInt("Type", 1);
} else {
builder.put("Patterns", convertBannerPattern(patterns));
}
itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build());
}
@ -151,7 +188,14 @@ public class BannerTranslator extends ItemTranslator {
ItemStack itemStack = super.translateToJava(itemData, itemEntry);
NbtMap nbtTag = itemData.getTag();
if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) {
if (nbtTag.containsKey("Type", NbtType.INT) && nbtTag.getInt("Type") == 1) {
// Ominous banner pattern
itemStack.getNbt().remove("Type");
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag");
blockEntityTag.put(OMINOUS_BANNER_PATTERN);
itemStack.getNbt().put(blockEntityTag);
} else if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) {
List<NbtMap> patterns = nbtTag.getList("Patterns", NbtType.COMPOUND);
CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag");

View file

@ -52,9 +52,14 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) {
// Don't send command suggestions if they are disabled
if (!session.getConnector().getConfig().isCommandSuggestions()) {
session.getConnector().getLogger().debug("Not sending command suggestions as they are disabled.");
session.getConnector().getLogger().debug("Not sending translated command suggestions as they are disabled.");
// Send an empty packet so Bedrock doesn't override /help with its own, built-in help command.
AvailableCommandsPacket emptyPacket = new AvailableCommandsPacket();
session.sendUpstreamPacket(emptyPacket);
return;
}
List<CommandData> commandData = new ArrayList<>();
Int2ObjectMap<String> commands = new Int2ObjectOpenHashMap<>();
Int2ObjectMap<List<CommandNode>> commandArgs = new Int2ObjectOpenHashMap<>();
@ -83,14 +88,14 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
}
// The command flags, not sure what these do apart from break things
List<CommandData.Flag> flags = new ArrayList<>();
List<CommandData.Flag> flags = Collections.emptyList();
// Loop through all the found commands
for (int commandID : commands.keySet()) {
String commandName = commands.get(commandID);
// Create a basic alias
CommandEnumData aliases = new CommandEnumData( commandName + "Aliases", new String[] { commandName.toLowerCase() }, false);
CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", new String[] { commandName.toLowerCase() }, false);
// Get and parse all params
CommandParamData[][] params = getParams(packet.getNodes()[commandID], packet.getNodes());
@ -102,9 +107,7 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
// Add our commands to the AvailableCommandsPacket for the bedrock client
AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket();
for (CommandData data : commandData) {
availableCommandsPacket.getCommands().add(data);
}
availableCommandsPacket.getCommands().addAll(commandData);
GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands");

View file

@ -89,6 +89,7 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
case LIVING_DROWN:
case LIVING_HURT:
case LIVING_HURT_SWEET_BERRY_BUSH:
case LIVING_HURT_THORNS:
entityEventPacket.setType(EntityEventType.HURT);
break;
case LIVING_DEATH:

View file

@ -34,8 +34,8 @@ import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
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.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.sound.SoundRegistry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
@Translator(packet = ServerPlayBuiltinSoundPacket.class)
public class JavaPlayBuiltinSoundTranslator extends PacketTranslator<ServerPlayBuiltinSoundPacket> {
@ -82,7 +82,11 @@ public class JavaPlayBuiltinSoundTranslator extends PacketTranslator<ServerPlayB
// Bedrock has a number for each type of note, then proceeds up the scale by adding to that number
soundPacket.setExtraData(soundMapping.getExtraData() + (int)(Math.round((Math.log10(packet.getPitch()) / Math.log10(2)) * 12)) + 12);
} else if (sound == SoundEvent.PLACE && soundMapping.getExtraData() == -1) {
soundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaBlockState(soundMapping.getIdentifier())));
if (!soundMapping.getIdentifier().equals(":")) {
soundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaBlockState(soundMapping.getIdentifier())));
} else {
session.getConnector().getLogger().debug("PLACE sound mapping identifier was invalid! Please report: " + packet.toString());
}
soundPacket.setIdentifier(":");
} else {
soundPacket.setExtraData(soundMapping.getExtraData());

View file

@ -25,49 +25,38 @@
package org.geysermc.connector.network.translators.java.world;
import com.nukkitx.protocol.bedrock.data.GameRuleData;
import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket;
import it.unimi.dsi.fastutil.longs.Long2LongMap;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTimePacket;
import com.nukkitx.protocol.bedrock.packet.SetTimePacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTimePacket;
import com.nukkitx.protocol.bedrock.packet.SetTimePacket;
@Translator(packet = ServerUpdateTimePacket.class)
public class JavaUpdateTimeTranslator extends PacketTranslator<ServerUpdateTimePacket> {
// If negative, the last time is stored so we know it's not some plugin behavior doing weird things.
// Per-player for multi-world support
private static final Long2LongMap LAST_RECORDED_TIMES = new Long2LongOpenHashMap();
@Override
public void translate(ServerUpdateTimePacket packet, GeyserSession session) {
// Bedrock sends a GameRulesChangedPacket if there is no daylight cycle
// Java just sends a negative long if there is no daylight cycle
long lastTime = LAST_RECORDED_TIMES.getOrDefault(session.getPlayerEntity().getEntityId(), 0);
long time = packet.getTime();
if (lastTime != time) {
// https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day
SetTimePacket setTimePacket = new SetTimePacket();
setTimePacket.setTime((int) Math.abs(time) % 24000);
session.sendUpstreamPacket(setTimePacket);
// TODO: Performance efficient to always do this?
LAST_RECORDED_TIMES.put(session.getPlayerEntity().getEntityId(), time);
}
if (lastTime < 0 && time >= 0) {
// https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day
SetTimePacket setTimePacket = new SetTimePacket();
setTimePacket.setTime((int) Math.abs(time) % 24000);
session.sendUpstreamPacket(setTimePacket);
if (!session.isDaylightCycle() && time >= 0) {
// Client thinks there is no daylight cycle but there is
setDoDaylightCycleGamerule(session, true);
} else if (lastTime != time && time < 0) {
} else if (session.isDaylightCycle() && time < 0) {
// Client thinks there is daylight cycle but there isn't
setDoDaylightCycleGamerule(session, false);
}
}
private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
session.sendGameRule("dodaylightcycle", doCycle);
// Save the value so we don't have to constantly send a daylight cycle gamerule update
session.setDaylightCycle(doCycle);
}
}

View file

@ -30,15 +30,18 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.nukkitx.nbt.*;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.*;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntity;
import org.geysermc.connector.utils.FileUtils;
import org.reflections.Reflections;
import java.io.DataInputStream;
import java.io.InputStream;
import java.util.*;
import java.util.Iterator;
import java.util.Map;
public class BlockTranslator {
/**
@ -65,8 +68,6 @@ public class BlockTranslator {
// Bedrock carpet ID, used in LlamaEntity.java for decoration
public static final int CARPET = 171;
private static final Int2ObjectMap<String> JAVA_ID_TO_BLOCK_ENTITY_MAP = new Int2ObjectOpenHashMap<>();
public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap();
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<>();
@ -74,6 +75,8 @@ public class BlockTranslator {
// The index of the collision data in collision.json
public static final Int2IntMap JAVA_RUNTIME_ID_TO_COLLISION_INDEX = new Int2IntOpenHashMap();
private static final Int2ObjectMap<String> JAVA_RUNTIME_ID_TO_PICK_ITEM = new Int2ObjectOpenHashMap<>();
/**
* Java numeric ID to java unique identifier, used for block names in the statistics screen
*/
@ -173,20 +176,13 @@ public class BlockTranslator {
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
String identifier;
String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
for (Class<?> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
identifier = clazz.getAnnotation(BlockEntity.class).regex();
// Endswith, or else the block bedrock gets picked up for bed
if (bedrockIdentifier.endsWith(identifier) && !identifier.equals("")) {
JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaRuntimeId, clazz.getAnnotation(BlockEntity.class).name());
break;
}
JsonNode pickItemNode = entry.getValue().get("pick_item");
if (pickItemNode != null) {
JAVA_RUNTIME_ID_TO_PICK_ITEM.put(javaRuntimeId, pickItemNode.textValue());
}
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
BlockStateValues.storeBlockStateValues(entry, javaRuntimeId);
String cleanJavaIdentifier = entry.getKey().split("\\[")[0];
@ -196,6 +192,8 @@ public class BlockTranslator {
JAVA_ID_TO_JAVA_IDENTIFIER_MAP.put(uniqueJavaId, cleanJavaIdentifier);
}
String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText();
if (!cleanJavaIdentifier.equals(bedrockIdentifier)) {
JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier);
}
@ -352,12 +350,12 @@ public class BlockTranslator {
return BLOCK_STATE_VERSION;
}
/**
* @param javaId the Java string identifier to search for
* @return the Java block state integer, or {@link #JAVA_AIR_ID} if there is no valid entry.
*/
public static int getJavaBlockState(String javaId) {
return JAVA_ID_BLOCK_MAP.get(javaId);
}
public static String getBlockEntityString(int javaId) {
return JAVA_ID_TO_BLOCK_ENTITY_MAP.get(javaId);
return JAVA_ID_BLOCK_MAP.getOrDefault(javaId, JAVA_AIR_ID);
}
public static boolean isWaterlogged(int state) {
@ -371,4 +369,19 @@ public class BlockTranslator {
public static int getJavaWaterloggedState(int bedrockId) {
return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId);
}
/**
* Get the item a Java client would receive when pressing
* the Pick Block key on a specific Java block state.
*
* @param javaId The Java runtime id of the block
* @return The Java identifier of the item
*/
public static String getPickItem(int javaId) {
String itemIdentifier = JAVA_RUNTIME_ID_TO_PICK_ITEM.get(javaId);
if (itemIdentifier == null) {
return JAVA_ID_BLOCK_MAP.inverse().get(javaId).split("\\[")[0];
}
return itemIdentifier;
}
}

View file

@ -47,7 +47,13 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement
if (tag.contains("Patterns")) {
ListTag patterns = tag.get("Patterns");
builder.put("Patterns", BannerTranslator.convertBannerPattern(patterns));
if (patterns.equals(BannerTranslator.OMINOUS_BANNER_PATTERN)) {
// This is an ominous banner; don't try to translate the raw patterns (it doesn't translate correctly)
// and tell the Bedrock client that this is an ominous banner
builder.putInt("Type", 1);
} else {
builder.put("Patterns", BannerTranslator.convertBannerPattern(patterns));
}
}
if (tag.contains("CustomName")) {

View file

@ -81,11 +81,10 @@ public class SkinManager {
// This attempts to find the xuid of the player so profile images show up for xbox accounts
String xuid = "";
for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) {
if (player.getPlayerEntity().getUuid().equals(uuid)) {
xuid = player.getAuthData().getXboxUUID();
break;
}
GeyserSession player = GeyserConnector.getInstance().getPlayerByUuid(uuid);
if (player != null) {
xuid = player.getAuthData().getXboxUUID();
}
PlayerListPacket.Entry entry;
@ -268,11 +267,10 @@ public class SkinManager {
// return default skin with default cape when texture data is invalid
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) {
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) {
if (session.getPlayerEntity().getUuid().equals(profile.getId())) {
skinUrl = session.getClientData().getSkinId();
break;
}
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());
if (session != null) {
skinUrl = session.getClientData().getSkinId();
}
}
return new GameProfileData(skinUrl, SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex);

View file

@ -144,12 +144,10 @@ public class SkinProvider {
String newSkinUrl = skinUrl;
if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) {
// TODO: Don't have a for loop for this? Have a proper map?
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) {
if (session.getPlayerEntity().getUuid().equals(playerId)) {
newSkinUrl = session.getClientData().getSkinId();
break;
}
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(playerId);
if (session != null) {
newSkinUrl = session.getClientData().getSkinId();
}
}

View file

@ -46,6 +46,7 @@ import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import java.util.Collections;
import java.util.Objects;
@ -168,13 +169,17 @@ public class InventoryUtils {
* @param session the Bedrock client's session
* @param itemName the Java identifier of the item to search/select
*/
public static void findOrCreatePickedBlock(GeyserSession session, String itemName) {
public static void findOrCreateItem(GeyserSession session, String itemName) {
// Get the inventory to choose a slot to pick
Inventory inventory = session.getInventoryCache().getOpenInventory();
if (inventory == null) {
inventory = session.getInventory();
}
if (itemName.equals("minecraft:air")) {
return;
}
// Check hotbar for item
for (int i = 36; i < 45; i++) {
if (inventory.getItem(i) == null) {
@ -219,12 +224,17 @@ public class InventoryUtils {
}
}
ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot,
new ItemStack(ItemRegistry.getItemEntry(itemName).getJavaId()));
if ((slot - 36) != session.getInventory().getHeldItemSlot()) {
setHotbarItem(session, slot);
ItemEntry entry = ItemRegistry.getItemEntry(itemName);
if (entry != null) {
ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot,
new ItemStack(entry.getJavaId()));
if ((slot - 36) != session.getInventory().getHeldItemSlot()) {
setHotbarItem(session, slot);
}
session.sendDownstreamPacket(actionPacket);
} else {
session.getConnector().getLogger().debug("Cannot find item for block " + itemName);
}
session.sendDownstreamPacket(actionPacket);
}
}

View file

@ -32,6 +32,11 @@ remote:
port: 25565
# Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate).
auth-type: online
# Whether to enable PROXY protocol or not while connecting to the server.
# This is useful only when:
# 1) Your server supports PROXY protocol (it probably doesn't)
# 2) You run Velocity or BungeeCord with respective option enabled.
use-proxy-protocol: false
# Floodgate uses encryption to ensure use from authorised sources.
# This should point to the public key generated by Floodgate (Bungee or CraftBukkit)

@ -1 +1 @@
Subproject commit 2d14c9dc3d75df7463fc7605a6cff63b5926a03e
Subproject commit 143285afb4bdf4d5ef40ef7a7959477dabf4d34c