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

# Conflicts:
#	connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java
#	connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java
#	connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
#	connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java
#	connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
This commit is contained in:
Tim203 2021-05-01 12:16:25 +02:00
commit 3b9674ac29
No known key found for this signature in database
GPG key ID: 064EE9F5BF7C3EE8
53 changed files with 744 additions and 269 deletions

View file

@ -25,7 +25,6 @@
package org.geysermc.connector.command.defaults; package org.geysermc.connector.command.defaults;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
@ -33,15 +32,12 @@ import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.command.GeyserCommand;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.BlockUtils;
public class OffhandCommand extends GeyserCommand { public class OffhandCommand extends GeyserCommand {
private final GeyserConnector connector;
public OffhandCommand(GeyserConnector connector, String name, String description, String permission) { public OffhandCommand(GeyserConnector connector, String name, String description, String permission) {
super(name, description, permission); super(name, description, permission);
this.connector = connector;
} }
@Override @Override
@ -50,7 +46,7 @@ public class OffhandCommand extends GeyserCommand {
return; return;
} }
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, BlockUtils.POSITION_ZERO,
BlockFace.DOWN); BlockFace.DOWN);
session.sendDownstreamPacket(releaseItemPacket); session.sendDownstreamPacket(releaseItemPacket);
} }

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2019-2021 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.configuration;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
public enum EmoteOffhandWorkaroundOption {
NO_EMOTES,
EMOTES_AND_OFFHAND,
DISABLED;
public static class Deserializer extends JsonDeserializer<EmoteOffhandWorkaroundOption> {
@Override
public EmoteOffhandWorkaroundOption deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
switch (value) {
case "no-emotes":
return NO_EMOTES;
case "emotes-and-offhand":
return EMOTES_AND_OFFHAND;
default:
return DISABLED;
}
}
}
}

View file

@ -77,6 +77,8 @@ public interface GeyserConfiguration {
boolean isShowCoordinates(); boolean isShowCoordinates();
EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround();
String getDefaultLocale(); String getDefaultLocale();
Path getFloodgateKeyPath(); Path getFloodgateKeyPath();
@ -138,6 +140,8 @@ public interface GeyserConfiguration {
boolean isPasswordAuthentication(); boolean isPasswordAuthentication();
boolean isUseProxyProtocol(); boolean isUseProxyProtocol();
boolean isForwardHost();
} }
interface IUserAuthenticationInfo { interface IUserAuthenticationInfo {

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.configuration;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
@ -100,6 +101,10 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("show-coordinates") @JsonProperty("show-coordinates")
private boolean showCoordinates = true; private boolean showCoordinates = true;
@JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class)
@JsonProperty("emote-offhand-workaround")
private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED;
@JsonProperty("allow-third-party-ears") @JsonProperty("allow-third-party-ears")
private boolean allowThirdPartyEars = false; private boolean allowThirdPartyEars = false;
@ -197,6 +202,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("use-proxy-protocol") @JsonProperty("use-proxy-protocol")
private boolean useProxyProtocol = false; private boolean useProxyProtocol = false;
@JsonProperty("forward-hostname")
private boolean forwardHost = false;
} }
@Getter @Getter

View file

@ -27,8 +27,12 @@ package org.geysermc.connector.dump;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import com.google.common.io.Files;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.common.serializer.AsteriskSerializer;
@ -40,6 +44,7 @@ import org.geysermc.connector.utils.FileUtils;
import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.floodgate.util.FloodgateConfigHolder; import org.geysermc.floodgate.util.FloodgateConfigHolder;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -56,6 +61,7 @@ public class DumpInfo {
private Properties gitInfo; private Properties gitInfo;
private final GeyserConfiguration config; private final GeyserConfiguration config;
private final Object floodgateConfig; private final Object floodgateConfig;
private final HashInfo hashInfo;
private final Object2IntMap<DeviceOs> userPlatforms; private final Object2IntMap<DeviceOs> userPlatforms;
private final RamInfo ramInfo; private final RamInfo ramInfo;
private final BootstrapDumpInfo bootstrapInfo; private final BootstrapDumpInfo bootstrapInfo;
@ -71,6 +77,27 @@ public class DumpInfo {
this.config = GeyserConnector.getInstance().getConfig(); this.config = GeyserConnector.getInstance().getConfig();
this.floodgateConfig = FloodgateConfigHolder.getConfig(); this.floodgateConfig = FloodgateConfigHolder.getConfig();
String md5Hash = "unknown";
String sha256Hash = "unknown";
try {
// https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file
// https://stackoverflow.com/questions/304268/getting-a-files-md5-checksum-in-java
File file = new File(DumpInfo.class.getProtectionDomain().getCodeSource().getLocation().toURI());
ByteSource byteSource = Files.asByteSource(file);
// Jenkins uses MD5 for its hash
//noinspection UnstableApiUsage
md5Hash = byteSource.hash(Hashing.md5()).toString();
//noinspection UnstableApiUsage
sha256Hash = byteSource.hash(Hashing.sha256()).toString();
} catch (Exception e) {
if (GeyserConnector.getInstance().getConfig().isDebugMode()) {
e.printStackTrace();
}
}
this.hashInfo = new HashInfo(md5Hash, sha256Hash);
this.ramInfo = new DumpInfo.RamInfo(); this.ramInfo = new DumpInfo.RamInfo();
this.userPlatforms = new Object2IntOpenHashMap<>(); this.userPlatforms = new Object2IntOpenHashMap<>();
@ -108,6 +135,13 @@ public class DumpInfo {
} }
} }
@AllArgsConstructor
@Getter
public static class HashInfo {
private final String md5Hash;
private final String sha256Hash;
}
@Getter @Getter
public static class NetworkInfo { public static class NetworkInfo {
private final boolean dockerCheck; private final boolean dockerCheck;

View file

@ -34,4 +34,14 @@ public class AnimalEntity extends AgeableEntity {
public AnimalEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public AnimalEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
} }
/**
* @param javaIdentifierStripped the stripped Java identifier of the item that is potential breeding food. For example,
* <code>wheat</code>.
* @return true if this is a valid item to breed with for this animal.
*/
public boolean canEat(String javaIdentifierStripped) {
// This is what it defaults to. OK.
return javaIdentifierStripped.equals("wheat");
}
} }

View file

@ -26,6 +26,7 @@
package org.geysermc.connector.entity.living.animal; package org.geysermc.connector.entity.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.google.common.collect.ImmutableSet;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
@ -34,7 +35,15 @@ import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import java.util.Set;
public class BeeEntity extends AnimalEntity { public class BeeEntity extends AnimalEntity {
/**
* A list of all flowers. Used for feeding bees.
*/
private static final Set<String> FLOWERS = ImmutableSet.of("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet",
"red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose",
"sunflower", "lilac", "rose_bush", "peony");
public BeeEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public BeeEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
@ -63,4 +72,9 @@ public class BeeEntity extends AnimalEntity {
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return FLOWERS.contains(javaIdentifierStripped);
}
} }

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2019-2021 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.living.animal;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.entity.type.EntityType;
public class ChickenEntity extends AnimalEntity {
public ChickenEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.contains("seeds");
}
}

View file

@ -52,4 +52,9 @@ public class FoxEntity extends AnimalEntity {
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("sweet_berries");
}
} }

View file

@ -47,4 +47,9 @@ public class HoglinEntity extends AnimalEntity {
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("crimson_fungus");
}
} }

View file

@ -44,4 +44,9 @@ public class OcelotEntity extends AnimalEntity {
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon");
}
} }

View file

@ -79,6 +79,11 @@ public class PandaEntity extends AnimalEntity {
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("bamboo");
}
/** /**
* Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up * Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up
* when both main and hidden genes match * when both main and hidden genes match

View file

@ -46,6 +46,11 @@ public class PigEntity extends AnimalEntity {
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot");
}
@Override @Override
protected float getDefaultMaxHealth() { protected float getDefaultMaxHealth() {
return 10f; return 10f;

View file

@ -44,4 +44,9 @@ public class PolarBearEntity extends AnimalEntity {
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return false;
}
} }

View file

@ -59,4 +59,9 @@ public class RabbitEntity extends AnimalEntity {
metadata.put(EntityData.VARIANT, variant); metadata.put(EntityData.VARIANT, variant);
} }
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot");
}
} }

View file

@ -85,4 +85,9 @@ public class StriderEntity extends AnimalEntity {
super.updateBedrockMetadata(session); super.updateBedrockMetadata(session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("warped_fungus");
}
} }

View file

@ -46,4 +46,9 @@ public class TurtleEntity extends AnimalEntity {
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("seagrass");
}
} }

View file

@ -26,6 +26,7 @@
package org.geysermc.connector.entity.living.animal.horse; package org.geysermc.connector.entity.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.google.common.collect.ImmutableSet;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
@ -38,7 +39,15 @@ import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry;
import java.util.Set;
public class AbstractHorseEntity extends AnimalEntity { public class AbstractHorseEntity extends AnimalEntity {
/**
* A list of all foods a horse/donkey can eat on Java Edition.
* Used to display interactive tag if needed.
*/
private static final Set<String> DONKEY_AND_HORSE_FOODS = ImmutableSet.of("golden_apple", "enchanted_golden_apple",
"golden_carrot", "sugar", "apple", "wheat", "hay_block");
public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
@ -101,4 +110,9 @@ public class AbstractHorseEntity extends AnimalEntity {
updateBedrockAttributes(session); updateBedrockAttributes(session);
} }
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped);
}
} }

View file

@ -75,4 +75,9 @@ public class LlamaEntity extends ChestedHorseEntity {
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block");
}
} }

View file

@ -83,4 +83,9 @@ public class CatEntity extends TameableEntity {
} }
} }
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon");
}
} }

View file

@ -45,4 +45,9 @@ public class ParrotEntity extends TameableEntity {
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie");
}
} }

View file

@ -26,13 +26,23 @@
package org.geysermc.connector.entity.living.animal.tameable; package org.geysermc.connector.entity.living.animal.tameable;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.google.common.collect.ImmutableSet;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import java.util.Set;
public class WolfEntity extends TameableEntity { public class WolfEntity extends TameableEntity {
/**
* A list of all foods a wolf can eat on Java Edition.
* Used to display interactive tag or particles if needed.
*/
private static final Set<String> WOLF_FOODS = ImmutableSet.of("pufferfish", "tropical_fish", "chicken", "cooked_chicken",
"porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton",
"cooked_rabbit");
private byte collarColor; private byte collarColor;
@ -75,4 +85,10 @@ public class WolfEntity extends TameableEntity {
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
@Override
public boolean canEat(String javaIdentifierStripped) {
// Cannot be a baby to eat these foods
return WOLF_FOODS.contains(javaIdentifierStripped) && !metadata.getFlags().getFlag(EntityFlag.BABY);
}
} }

View file

@ -42,7 +42,7 @@ public class AbstractSkeletonEntity extends MonsterEntity {
if (entityMetadata.getId() == 14) { if (entityMetadata.getId() == 14) {
byte xd = (byte) entityMetadata.getValue(); byte xd = (byte) entityMetadata.getValue();
// A bit of a loophole so the hands get raised - set the target ID to its own ID // A bit of a loophole so the hands get raised - set the target ID to its own ID
metadata.put(EntityData.TARGET_EID, (xd == 4) ? geyserId : 0); metadata.put(EntityData.TARGET_EID, ((xd & 4) == 4) ? geyserId : 0);
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }

View file

@ -25,13 +25,43 @@
package org.geysermc.connector.entity.living.monster.raid; package org.geysermc.connector.entity.living.monster.raid;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; 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.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class SpellcasterIllagerEntity extends AbstractIllagerEntity { public class SpellcasterIllagerEntity extends AbstractIllagerEntity {
private static final int SUMMON_VEX_PARTICLE_COLOR = (179 << 16) | (179 << 8) | 204;
private static final int ATTACK_PARTICLE_COLOR = (102 << 16) | (77 << 8) | 89;
private static final int WOLOLO_PARTICLE_COLOR = (179 << 16) | (128 << 8) | 51;
public SpellcasterIllagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public SpellcasterIllagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
} }
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 16) {
int spellType = (int) (byte) entityMetadata.getValue();
// Summon vex, attack, or wololo
metadata.getFlags().setFlag(EntityFlag.CASTING, spellType == 1 || spellType == 2 || spellType == 3);
int rgbData = 0;
// Set the spell color based on Java values
switch (spellType) {
case 1:
rgbData = SUMMON_VEX_PARTICLE_COLOR;
break;
case 2:
rgbData = ATTACK_PARTICLE_COLOR;
break;
case 3:
rgbData = WOLOLO_PARTICLE_COLOR;
break;
}
metadata.put(EntityData.EVOKER_SPELL_COLOR, rgbData);
}
super.updateBedrockMetadata(entityMetadata, session);
}
} }

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2019-2021 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.living.monster.raid;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class VindicatorEntity extends AbstractIllagerEntity {
public VindicatorEntity(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) {
// Allow the axe to be shown if necessary
if (entityMetadata.getId() == 14) {
byte xd = (byte) entityMetadata.getValue();
metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 4) == 4);
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -36,10 +36,7 @@ import org.geysermc.connector.entity.living.animal.tameable.WolfEntity;
import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity;
import org.geysermc.connector.entity.living.merchant.VillagerEntity; import org.geysermc.connector.entity.living.merchant.VillagerEntity;
import org.geysermc.connector.entity.living.monster.*; import org.geysermc.connector.entity.living.monster.*;
import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; import org.geysermc.connector.entity.living.monster.raid.*;
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; import org.geysermc.connector.entity.player.PlayerEntity;
import java.util.ArrayList; import java.util.ArrayList;
@ -48,7 +45,7 @@ import java.util.List;
@Getter @Getter
public enum EntityType { public enum EntityType {
CHICKEN(AnimalEntity.class, 10, 0.7f, 0.4f), CHICKEN(ChickenEntity.class, 10, 0.7f, 0.4f),
COW(AnimalEntity.class, 11, 1.4f, 0.9f), COW(AnimalEntity.class, 11, 1.4f, 0.9f),
PIG(PigEntity.class, 12, 0.9f), PIG(PigEntity.class, 12, 0.9f),
SHEEP(SheepEntity.class, 13, 1.3f, 0.9f), SHEEP(SheepEntity.class, 13, 1.3f, 0.9f),
@ -97,7 +94,7 @@ public enum EntityType {
SHULKER(ShulkerEntity.class, 54, 1f, 1f), SHULKER(ShulkerEntity.class, 54, 1f, 1f),
ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f),
AGENT(Entity.class, 56, 0f), AGENT(Entity.class, 56, 0f),
VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f), VINDICATOR(VindicatorEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f),
PILLAGER(PillagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f), PILLAGER(PillagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f),
WANDERING_TRADER(AbstractMerchantEntity.class, 118, 1.8f, 0.6f, 0.6f, 1.62f), WANDERING_TRADER(AbstractMerchantEntity.class, 118, 1.8f, 0.6f, 0.6f, 1.62f),
PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f), PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f),
@ -188,7 +185,7 @@ public enum EntityType {
static { static {
List<String> allJavaIdentifiers = new ArrayList<>(); List<String> allJavaIdentifiers = new ArrayList<>();
for (EntityType type : values()) { for (EntityType type : VALUES) {
if (type == AGENT || type == BALLOON || type == CHALKBOARD || type == NPC || type == TRIPOD_CAMERA || type == ENDER_DRAGON_PART) { if (type == AGENT || type == BALLOON || type == CHALKBOARD || type == NPC || type == TRIPOD_CAMERA || type == ENDER_DRAGON_PART) {
continue; continue;
} }

View file

@ -77,6 +77,7 @@ import lombok.Setter;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.common.AuthType; import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.configuration.EmoteOffhandWorkaroundOption;
import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.entity.attribute.Attribute; import org.geysermc.connector.entity.attribute.Attribute;
@ -427,9 +428,7 @@ public class GeyserSession implements CommandSender {
@Setter @Setter
private boolean waitingForStatistics = false; private boolean waitingForStatistics = false;
@Setter private final Set<UUID> emotes;
private List<UUID> selectedEmotes = new ArrayList<>();
private final Set<UUID> emotes = new HashSet<>();
/** /**
* The thread that will run every 50 milliseconds - one Minecraft tick. * The thread that will run every 50 milliseconds - one Minecraft tick.
@ -464,9 +463,14 @@ public class GeyserSession implements CommandSender {
this.spawned = false; this.spawned = false;
this.loggedIn = false; this.loggedIn = false;
if (connector.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) {
this.emotes = new HashSet<>();
// Make a copy to prevent ConcurrentModificationException // Make a copy to prevent ConcurrentModificationException
final List<GeyserSession> tmpPlayers = new ArrayList<>(connector.getPlayers()); final List<GeyserSession> tmpPlayers = new ArrayList<>(connector.getPlayers());
tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes())); tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes()));
} else {
this.emotes = null;
}
bedrockServerSession.addDisconnectHandler(disconnectReason -> { bedrockServerSession.addDisconnectHandler(disconnectReason -> {
InetAddress address = bedrockServerSession.getRealAddress().getAddress(); InetAddress address = bedrockServerSession.getRealAddress().getAddress();
@ -668,7 +672,9 @@ public class GeyserSession implements CommandSender {
@Override @Override
public void packetSending(PacketSendingEvent event) { public void packetSending(PacketSendingEvent event) {
//todo move this somewhere else //todo move this somewhere else
if (event.getPacket() instanceof HandshakePacket && floodgate) { if (event.getPacket() instanceof HandshakePacket) {
String addressSuffix;
if (floodgate) {
byte[] encryptedData; byte[] encryptedData;
try { try {
@ -693,12 +699,23 @@ public class GeyserSession implements CommandSender {
return; return;
} }
String finalDataString = new String(encryptedData, StandardCharsets.UTF_8); addressSuffix = '\0' + new String(encryptedData, StandardCharsets.UTF_8);
} else {
addressSuffix = "";
}
HandshakePacket handshakePacket = event.getPacket(); HandshakePacket handshakePacket = event.getPacket();
String address;
if (connector.getConfig().getRemote().isForwardHost()) {
address = clientData.getServerAddress().split(":")[0];
} else {
address = handshakePacket.getHostname();
}
event.setPacket(new HandshakePacket( event.setPacket(new HandshakePacket(
handshakePacket.getProtocolVersion(), handshakePacket.getProtocolVersion(),
handshakePacket.getHostname() + '\0' + finalDataString, address + addressSuffix,
handshakePacket.getPort(), handshakePacket.getPort(),
handshakePacket.getIntent() handshakePacket.getIntent()
)); ));
@ -825,7 +842,7 @@ public class GeyserSession implements CommandSender {
/** /**
* Called every 50 milliseconds - one Minecraft tick. * Called every 50 milliseconds - one Minecraft tick.
*/ */
public void tick() { protected void tick() {
// Check to see if the player's position needs updating - a position update should be sent once every 3 seconds // Check to see if the player's position needs updating - a position update should be sent once every 3 seconds
if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) { if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) {
// Recalculate in case something else changed position // Recalculate in case something else changed position
@ -997,7 +1014,6 @@ public class GeyserSession implements CommandSender {
startGamePacket.setLightningLevel(0); startGamePacket.setLightningLevel(0);
startGamePacket.setMultiplayerGame(true); startGamePacket.setMultiplayerGame(true);
startGamePacket.setBroadcastingToLan(true); startGamePacket.setBroadcastingToLan(true);
startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", connector.getConfig().isShowCoordinates()));
startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setCommandsEnabled(!connector.getConfig().isXboxAchievementsEnabled()); startGamePacket.setCommandsEnabled(!connector.getConfig().isXboxAchievementsEnabled());
@ -1189,13 +1205,14 @@ public class GeyserSession implements CommandSender {
/** /**
* Update the cached value for the reduced debug info gamerule. * Update the cached value for the reduced debug info gamerule.
* This also toggles the coordinates display * If enabled, also hides the player's coordinates.
* *
* @param value The new value for reducedDebugInfo * @param value The new value for reducedDebugInfo
*/ */
public void setReducedDebugInfo(boolean value) { public void setReducedDebugInfo(boolean value) {
worldCache.setShowCoordinates(!value);
reducedDebugInfo = value; reducedDebugInfo = value;
// Set the showCoordinates data. This is done because updateShowCoordinates() uses this gamerule as a variable.
getWorldCache().updateShowCoordinates();
} }
/** /**
@ -1281,7 +1298,6 @@ public class GeyserSession implements CommandSender {
} }
public void refreshEmotes(List<UUID> emotes) { public void refreshEmotes(List<UUID> emotes) {
this.selectedEmotes = emotes;
this.emotes.addAll(emotes); this.emotes.addAll(emotes);
for (GeyserSession player : connector.getPlayers()) { for (GeyserSession player : connector.getPlayers()) {
List<UUID> pieces = new ArrayList<>(); List<UUID> pieces = new ArrayList<>();

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.network.session.cache;
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Objective;
import org.geysermc.connector.scoreboard.Scoreboard; import org.geysermc.connector.scoreboard.Scoreboard;
@ -38,7 +39,13 @@ public class WorldCache {
private final GeyserSession session; private final GeyserSession session;
@Setter @Setter
private Difficulty difficulty = Difficulty.EASY; private Difficulty difficulty = Difficulty.EASY;
private boolean showCoordinates = true;
/**
* True if the client prefers being shown their coordinates, regardless if they're being shown or not.
* This will be true everytime the client joins the server because neither the client nor server store the preference permanently.
*/
@Setter
private boolean prefersShowCoordinates = true;
private Scoreboard scoreboard; private Scoreboard scoreboard;
private final ScoreboardUpdater scoreboardUpdater; private final ScoreboardUpdater scoreboardUpdater;
@ -66,12 +73,15 @@ public class WorldCache {
} }
/** /**
* Tell the client to hide or show the coordinates * Tell the client to hide or show the coordinates.
* *
* @param value True to show, false to hide * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply: <br>
* <br>
* {@link GeyserSession#reducedDebugInfo} is enabled
* {@link GeyserConfiguration#isShowCoordinates()} is disabled
*/ */
public void setShowCoordinates(boolean value) { public void updateShowCoordinates() {
showCoordinates = value; boolean allowShowCoordinates = !session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates();
session.sendGameRule("showcoordinates", value); session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates);
} }
} }

View file

@ -26,6 +26,7 @@
package org.geysermc.connector.network.translators.bedrock; package org.geysermc.connector.network.translators.bedrock;
import com.nukkitx.protocol.bedrock.packet.EmoteListPacket; import com.nukkitx.protocol.bedrock.packet.EmoteListPacket;
import org.geysermc.connector.configuration.EmoteOffhandWorkaroundOption;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.Translator;
@ -35,6 +36,10 @@ public class BedrockEmoteListTranslator extends PacketTranslator<EmoteListPacket
@Override @Override
public void translate(EmoteListPacket packet, GeyserSession session) { public void translate(EmoteListPacket packet, GeyserSession session) {
if (session.getConnector().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) {
return;
}
session.refreshEmotes(packet.getPieceIds()); session.refreshEmotes(packet.getPieceIds());
} }
} }

View file

@ -321,7 +321,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.sendUpstreamPacket(openPacket); session.sendUpstreamPacket(openPacket);
break; break;
} }
Vector3f vector = packet.getClickPosition(); Vector3f vector = packet.getClickPosition().sub(entity.getPosition());
ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking()); InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking());
ClientPlayerInteractEntityPacket interactAtPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), ClientPlayerInteractEntityPacket interactAtPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
@ -329,7 +329,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.sendDownstreamPacket(interactPacket); session.sendDownstreamPacket(interactPacket);
session.sendDownstreamPacket(interactAtPacket); session.sendDownstreamPacket(interactAtPacket);
EntitySoundInteractionHandler.handleEntityInteraction(session, vector, entity); EntitySoundInteractionHandler.handleEntityInteraction(session, packet.getClickPosition(), entity);
break; break;
case 1: //Attack case 1: //Attack
if (entity.getEntityType() == EntityType.ENDER_DRAGON) { if (entity.getEntityType() == EntityType.ENDER_DRAGON) {

View file

@ -54,7 +54,7 @@ public class BedrockLecternUpdateTranslator extends PacketTranslator<LecternUpda
// Emulate an interact packet // Emulate an interact packet
ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket( ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
BlockFace.values()[0], BlockFace.DOWN,
Hand.MAIN_HAND, Hand.MAIN_HAND,
0, 0, 0, // Java doesn't care about these when dealing with a lectern 0, 0, 0, // Java doesn't care about these when dealing with a lectern
false); false);

View file

@ -25,25 +25,43 @@
package org.geysermc.connector.network.translators.bedrock.entity.player; package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
import com.nukkitx.protocol.bedrock.packet.EmotePacket; import com.nukkitx.protocol.bedrock.packet.EmotePacket;
import org.geysermc.connector.configuration.EmoteOffhandWorkaroundOption;
import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.BlockUtils;
@Translator(packet = EmotePacket.class) @Translator(packet = EmotePacket.class)
public class BedrockEmoteTranslator extends PacketTranslator<EmotePacket> { public class BedrockEmoteTranslator extends PacketTranslator<EmotePacket> {
@Override @Override
public void translate(EmotePacket packet, GeyserSession session) { public void translate(EmotePacket packet, GeyserSession session) {
if (session.getConnector().getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.DISABLED) {
// Activate the workaround - we should trigger the offhand now
ClientPlayerActionPacket swapHandsPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, BlockUtils.POSITION_ZERO,
BlockFace.DOWN);
session.sendDownstreamPacket(swapHandsPacket);
if (session.getConnector().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) {
return;
}
}
long javaId = session.getPlayerEntity().getEntityId(); long javaId = session.getPlayerEntity().getEntityId();
for (GeyserSession otherSession : session.getConnector().getPlayers()) { for (GeyserSession otherSession : session.getConnector().getPlayers()) {
if (otherSession != session) { if (otherSession != session) {
if (otherSession.isClosed()) continue; if (otherSession.isClosed()) continue;
Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId); Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId);
if (otherEntity == null) continue; if (otherEntity == null) continue;
packet.setRuntimeEntityId(otherEntity.getGeyserId()); EmotePacket otherEmotePacket = new EmotePacket();
otherSession.sendUpstreamPacket(packet); otherEmotePacket.setEmoteId(packet.getEmoteId());
otherEmotePacket.setRuntimeEntityId(otherEntity.getGeyserId());
otherSession.sendUpstreamPacket(otherEmotePacket);
} }
} }
} }

View file

@ -72,7 +72,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
// The Bedrock index might need changed, so let's look it up and see. // The Bedrock index might need changed, so let's look it up and see.
int bedrockIndex = value; int bedrockIndex = value;
if (bedrockIndex != -1) { if (bedrockIndex != -1) {
Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + JavaEnchantment.values()[bedrockIndex].name().toLowerCase()); Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + Enchantment.JavaEnchantment.of(bedrockIndex).name().toLowerCase());
if (enchantment != null) { if (enchantment != null) {
// Convert the Java enchantment index to Bedrock's // Convert the Java enchantment index to Bedrock's
bedrockIndex = enchantment.ordinal(); bedrockIndex = enchantment.ordinal();
@ -170,48 +170,4 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new EnchantingContainer(name, windowId, this.size, windowType, playerInventory); return new EnchantingContainer(name, windowId, this.size, windowType, playerInventory);
} }
/**
* Enchantments classified by their Java index
*/
public enum JavaEnchantment {
PROTECTION,
FIRE_PROTECTION,
FEATHER_FALLING,
BLAST_PROTECTION,
PROJECTILE_PROTECTION,
RESPIRATION,
AQUA_AFFINITY,
THORNS,
DEPTH_STRIDER,
FROST_WALKER,
BINDING_CURSE,
SOUL_SPEED,
SHARPNESS,
SMITE,
BANE_OF_ARTHROPODS,
KNOCKBACK,
FIRE_ASPECT,
LOOTING,
SWEEPING,
EFFICIENCY,
SILK_TOUCH,
UNBREAKING,
FORTUNE,
POWER,
PUNCH,
FLAME,
INFINITY,
LUCK_OF_THE_SEA,
LURE,
LOYALTY,
IMPALING,
RIPTIDE,
CHANNELING,
MULTISHOT,
QUICK_CHARGE,
PIERCING,
MENDING,
VANISHING_CURSE
}
} }

View file

@ -69,17 +69,7 @@ public enum Enchantment {
QUICK_CHARGE, QUICK_CHARGE,
SOUL_SPEED; SOUL_SPEED;
/** private static final Enchantment[] VALUES = values();
* A list of all enchantment Java identifiers for use with command suggestions.
*/
public static final String[] ALL_JAVA_IDENTIFIERS;
static {
ALL_JAVA_IDENTIFIERS = new String[values().length];
for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) {
ALL_JAVA_IDENTIFIERS[i] = values()[i].javaIdentifier;
}
}
private final String javaIdentifier; private final String javaIdentifier;
@ -88,7 +78,7 @@ public enum Enchantment {
} }
public static Enchantment getByJavaIdentifier(String javaIdentifier) { public static Enchantment getByJavaIdentifier(String javaIdentifier) {
for (Enchantment enchantment : Enchantment.values()) { for (Enchantment enchantment : VALUES) {
if (enchantment.javaIdentifier.equals(javaIdentifier) || enchantment.name().toLowerCase(Locale.ENGLISH).equalsIgnoreCase(javaIdentifier)) { if (enchantment.javaIdentifier.equals(javaIdentifier) || enchantment.name().toLowerCase(Locale.ENGLISH).equalsIgnoreCase(javaIdentifier)) {
return enchantment; return enchantment;
} }
@ -97,9 +87,71 @@ public enum Enchantment {
} }
public static Enchantment getByBedrockId(int bedrockId) { public static Enchantment getByBedrockId(int bedrockId) {
if (bedrockId >= 0 && bedrockId < Enchantment.values().length) { if (bedrockId >= 0 && bedrockId < VALUES.length) {
return Enchantment.values()[bedrockId]; return VALUES[bedrockId];
} }
return null; return null;
} }
/**
* Enchantments classified by their Java index
*/
public enum JavaEnchantment {
PROTECTION,
FIRE_PROTECTION,
FEATHER_FALLING,
BLAST_PROTECTION,
PROJECTILE_PROTECTION,
RESPIRATION,
AQUA_AFFINITY,
THORNS,
DEPTH_STRIDER,
FROST_WALKER,
BINDING_CURSE,
SOUL_SPEED,
SHARPNESS,
SMITE,
BANE_OF_ARTHROPODS,
KNOCKBACK,
FIRE_ASPECT,
LOOTING,
SWEEPING,
EFFICIENCY,
SILK_TOUCH,
UNBREAKING,
FORTUNE,
POWER,
PUNCH,
FLAME,
INFINITY,
LUCK_OF_THE_SEA,
LURE,
LOYALTY,
IMPALING,
RIPTIDE,
CHANNELING,
MULTISHOT,
QUICK_CHARGE,
PIERCING,
MENDING,
VANISHING_CURSE;
private static final JavaEnchantment[] VALUES = JavaEnchantment.values();
public static JavaEnchantment of(int index) {
return VALUES[index];
}
/**
* A list of all enchantment Java identifiers for use with command suggestions.
*/
public static final String[] ALL_JAVA_IDENTIFIERS;
static {
ALL_JAVA_IDENTIFIERS = new String[VALUES.length];
for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) {
ALL_JAVA_IDENTIFIERS[i] = "minecraft:" + VALUES[i].name().toLowerCase(Locale.ENGLISH);
}
}
}
} }

View file

@ -408,6 +408,13 @@ public class ItemRegistry {
"", bedrockBlockId, "", bedrockBlockId,
stackSize); stackSize);
} }
} else if (entry.getKey().equals("minecraft:spectral_arrow") || entry.getKey().equals("minecraft:knowledge_book")) {
// These items don't exist on Java, so set up a container that indicates they should have custom names
itemEntry = new TranslatableItemEntry(
entry.getKey(), bedrockIdentifier, itemIndex, bedrockId,
entry.getValue().get("bedrock_data").intValue(),
bedrockBlockId,
stackSize);
} else { } else {
itemEntry = new ItemEntry( itemEntry = new ItemEntry(
entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getKey(), bedrockIdentifier, itemIndex, bedrockId,
@ -471,7 +478,6 @@ public class ItemRegistry {
} }
itemNames.add("minecraft:furnace_minecart"); itemNames.add("minecraft:furnace_minecart");
itemNames.add("minecraft:spectral_arrow");
if (lodestoneCompassId == 0) { if (lodestoneCompassId == 0) {
throw new RuntimeException("Lodestone compass not found in item palette!"); throw new RuntimeException("Lodestone compass not found in item palette!");

View file

@ -40,6 +40,7 @@ import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
import org.reflections.Reflections; import org.reflections.Reflections;
import java.util.*; import java.util.*;
@ -137,8 +138,6 @@ public abstract class ItemTranslator {
nbt.put(new IntTag("map", 0)); nbt.put(new IntTag("map", 0));
} }
ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt);
if (nbt != null) { if (nbt != null) {
for (NbtItemStackTranslator translator : NBT_TRANSLATORS) { for (NbtItemStackTranslator translator : NBT_TRANSLATORS) {
if (translator.acceptItem(bedrockItem)) { if (translator.acceptItem(bedrockItem)) {
@ -147,7 +146,9 @@ public abstract class ItemTranslator {
} }
} }
translateDisplayProperties(session, nbt); nbt = translateDisplayProperties(session, nbt, bedrockItem);
ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt);
ItemData.Builder builder; ItemData.Builder builder;
ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId()); ItemTranslator itemStackTranslator = ITEM_STACK_TRANSLATORS.get(bedrockItem.getJavaId());
@ -393,8 +394,20 @@ public abstract class ItemTranslator {
* Translates the display name of the item * Translates the display name of the item
* @param session the Bedrock client's session * @param session the Bedrock client's session
* @param tag the tag to translate * @param tag the tag to translate
* @param itemEntry the item entry, in case it requires translation
*
* @return the new tag to use, should the current one be null
*/ */
public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) { public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemEntry itemEntry) {
return translateDisplayProperties(session, tag, itemEntry, 'f');
}
/**
* @param translationColor if this item is not available on Java, the color that the new name should be.
* Normally, this should just be white, but for shulker boxes this should be gray.
*/
public static CompoundTag translateDisplayProperties(GeyserSession session, CompoundTag tag, ItemEntry itemEntry, char translationColor) {
boolean hasCustomName = false;
if (tag != null) { if (tag != null) {
CompoundTag display = tag.get("display"); CompoundTag display = tag.get("display");
if (display != null && display.contains("Name")) { if (display != null && display.contains("Name")) {
@ -405,11 +418,32 @@ public abstract class ItemTranslator {
// Add the new name tag // Add the new name tag
display.put(new StringTag("Name", name)); display.put(new StringTag("Name", name));
// Indicate that a custom name is present
hasCustomName = true;
// Add to the new root tag // Add to the new root tag
tag.put(display); tag.put(display);
} }
} }
if (!hasCustomName && itemEntry instanceof TranslatableItemEntry) {
// No custom name, but we need to localize the item's name
if (tag == null) {
tag = new CompoundTag("");
}
CompoundTag display = tag.get("display");
if (display == null) {
display = new CompoundTag("display");
// Add to the new root tag
tag.put(display);
}
String translationKey = ((TranslatableItemEntry) itemEntry).getTranslationString();
// Reset formatting since Bedrock defaults to italics
display.put(new StringTag("Name", "§r§" + translationColor + LocaleUtils.getLocaleString(translationKey, session.getLocale())));
}
return tag;
} }
/** /**

View file

@ -74,6 +74,8 @@ public enum Potion {
SLOW_FALLING(40), SLOW_FALLING(40),
LONG_SLOW_FALLING(41); LONG_SLOW_FALLING(41);
public static final Potion[] VALUES = values();
private final String javaIdentifier; private final String javaIdentifier;
private final short bedrockId; private final short bedrockId;
@ -83,7 +85,7 @@ public enum Potion {
} }
public static Potion getByJavaIdentifier(String javaIdentifier) { public static Potion getByJavaIdentifier(String javaIdentifier) {
for (Potion potion : Potion.values()) { for (Potion potion : VALUES) {
if (potion.javaIdentifier.equals(javaIdentifier)) { if (potion.javaIdentifier.equals(javaIdentifier)) {
return potion; return potion;
} }
@ -92,7 +94,7 @@ public enum Potion {
} }
public static Potion getByBedrockId(int bedrockId) { public static Potion getByBedrockId(int bedrockId) {
for (Potion potion : Potion.values()) { for (Potion potion : VALUES) {
if (potion.bedrockId == bedrockId) { if (potion.bedrockId == bedrockId) {
return potion; return potion;
} }

View file

@ -80,7 +80,7 @@ public class PotionMixRegistry {
// Add all types of potions as inputs // Add all types of potions as inputs
ItemEntry fillerIngredient = ingredients.get(0); ItemEntry fillerIngredient = ingredients.get(0);
for (ItemEntry input : inputs) { for (ItemEntry input : inputs) {
for (Potion potion : Potion.values()) { for (Potion potion : Potion.VALUES) {
potionMixes.add(new PotionMixData( potionMixes.add(new PotionMixData(
input.getBedrockId(), potion.getBedrockId(), input.getBedrockId(), potion.getBedrockId(),
fillerIngredient.getBedrockId(), fillerIngredient.getBedrockData(), fillerIngredient.getBedrockId(), fillerIngredient.getBedrockData(),

View file

@ -77,6 +77,8 @@ public enum TippedArrowPotion {
SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING), SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING),
LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING); LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING);
private static final TippedArrowPotion[] VALUES = values();
private final String javaIdentifier; private final String javaIdentifier;
private final short bedrockId; private final short bedrockId;
/** /**
@ -92,7 +94,7 @@ public enum TippedArrowPotion {
} }
public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) { public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) {
for (TippedArrowPotion potion : TippedArrowPotion.values()) { for (TippedArrowPotion potion : VALUES) {
if (potion.javaIdentifier.equals(javaIdentifier)) { if (potion.javaIdentifier.equals(javaIdentifier)) {
return potion; return potion;
} }
@ -101,7 +103,7 @@ public enum TippedArrowPotion {
} }
public static TippedArrowPotion getByBedrockId(int bedrockId) { public static TippedArrowPotion getByBedrockId(int bedrockId) {
for (TippedArrowPotion potion : TippedArrowPotion.values()) { for (TippedArrowPotion potion : VALUES) {
if (potion.bedrockId == bedrockId) { if (potion.bedrockId == bedrockId) {
return potion; return potion;
} }
@ -114,7 +116,7 @@ public enum TippedArrowPotion {
* @return the tipped arrow potion that most closely resembles that color. * @return the tipped arrow potion that most closely resembles that color.
*/ */
public static TippedArrowPotion getByJavaColor(int color) { public static TippedArrowPotion getByJavaColor(int color) {
for (TippedArrowPotion potion : TippedArrowPotion.values()) { for (TippedArrowPotion potion : VALUES) {
if (potion.javaColor == color) { if (potion.javaColor == color) {
return potion; return potion;
} }

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.item;
import lombok.Getter;
/**
* Used when an item should have a custom name applied, if there already isn't one.
*/
public class TranslatableItemEntry extends ItemEntry {
@Getter
private final String translationString;
public TranslatableItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, int bedrockBlockId, int stackSize) {
super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, bedrockBlockId, stackSize);
this.translationString = "item." + javaIdentifier.replace(":", ".");
}
}

View file

@ -28,10 +28,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.*; import com.github.steveice10.opennbt.tag.builtin.*;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.*;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
@ItemRemapper @ItemRemapper
public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { public class ShulkerBoxItemTranslator extends NbtItemStackTranslator {
@ -54,11 +51,13 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator {
boxItemTag.put(new StringTag("Name", boxItemEntry.getBedrockIdentifier())); boxItemTag.put(new StringTag("Name", boxItemEntry.getBedrockIdentifier()));
boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData()));
boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue())); boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue()));
if (itemData.contains("tag")) {
// Only the display name is what we have interest in, so just translate that if relevant // Only the display name is what we have interest in, so just translate that if relevant
CompoundTag displayTag = itemData.get("tag"); CompoundTag displayTag = itemData.get("tag");
ItemTranslator.translateDisplayProperties(session, displayTag); if (displayTag == null && boxItemEntry instanceof TranslatableItemEntry) {
boxItemTag.put(displayTag); displayTag = new CompoundTag("tag");
}
if (displayTag != null) {
boxItemTag.put(ItemTranslator.translateDisplayProperties(session, displayTag, boxItemEntry, '7'));
} }
itemsList.add(boxItemTag); itemsList.add(boxItemTag);

View file

@ -249,7 +249,7 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator<ServerDeclar
return ItemRegistry.ITEM_NAMES; return ItemRegistry.ITEM_NAMES;
case ITEM_ENCHANTMENT: case ITEM_ENCHANTMENT:
return Enchantment.ALL_JAVA_IDENTIFIERS; //TODO: inventory branch use Java enums return Enchantment.JavaEnchantment.ALL_JAVA_IDENTIFIERS;
case ENTITY_SUMMON: case ENTITY_SUMMON:
return EntityType.ALL_JAVA_IDENTIFIERS; return EntityType.ALL_JAVA_IDENTIFIERS;

View file

@ -47,6 +47,8 @@ import java.util.List;
@Translator(packet = ServerJoinGamePacket.class) @Translator(packet = ServerJoinGamePacket.class)
public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacket> { public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacket> {
private static final List<SkinPart> SKIN_PART_VALUES = Arrays.asList(SkinPart.values());
@Override @Override
public void translate(ServerJoinGamePacket packet, GeyserSession session) { public void translate(ServerJoinGamePacket packet, GeyserSession session) {
PlayerEntity entity = session.getPlayerEntity(); PlayerEntity entity = session.getPlayerEntity();
@ -86,12 +88,13 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
gamerulePacket.getGameRules().add(new GameRuleData<>("doimmediaterespawn", !packet.isEnableRespawnScreen())); gamerulePacket.getGameRules().add(new GameRuleData<>("doimmediaterespawn", !packet.isEnableRespawnScreen()));
session.sendUpstreamPacket(gamerulePacket); session.sendUpstreamPacket(gamerulePacket);
session.setReducedDebugInfo(packet.isReducedDebugInfo());
session.setRenderDistance(packet.getViewDistance()); session.setRenderDistance(packet.getViewDistance());
// We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc // We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc
String locale = session.getLocale(); String locale = session.getLocale();
List<SkinPart> skinParts = Arrays.asList(SkinPart.values()); ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, SKIN_PART_VALUES, HandPreference.RIGHT_HAND);
ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND);
session.sendDownstreamPacket(clientSettingsPacket); session.sendDownstreamPacket(clientSettingsPacket);
session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData())); session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));

View file

@ -30,12 +30,14 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.LivingEntity;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
@ -47,9 +49,11 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
@Override @Override
public void translate(ServerEntityStatusPacket packet, GeyserSession session) { public void translate(ServerEntityStatusPacket packet, GeyserSession session) {
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); Entity entity;
if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) {
entity = session.getPlayerEntity(); entity = session.getPlayerEntity();
} else {
entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
} }
if (entity == null) if (entity == null)
return; return;
@ -196,6 +200,19 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
equipmentBreakPacket.setIdentifier(""); equipmentBreakPacket.setIdentifier("");
session.sendUpstreamPacket(equipmentBreakPacket); session.sendUpstreamPacket(equipmentBreakPacket);
return; return;
case PLAYER_SWAP_SAME_ITEM: // Not just used for players
if (entity instanceof LivingEntity) {
LivingEntity livingEntity = (LivingEntity) entity;
ItemData newMainHand = livingEntity.getOffHand();
livingEntity.setOffHand(livingEntity.getHand());
livingEntity.setHand(newMainHand);
livingEntity.updateMainHand(session);
livingEntity.updateOffHand(session);
} else {
session.getConnector().getLogger().debug("Got status message to swap hands for a non-living entity.");
}
return;
} }
session.sendUpstreamPacket(entityEventPacket); session.sendUpstreamPacket(entityEventPacket);

View file

@ -25,11 +25,9 @@
package org.geysermc.connector.network.translators.sound; package org.geysermc.connector.network.translators.sound;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import java.util.Map; import java.util.Map;
@ -47,6 +45,9 @@ public interface BlockSoundInteractionHandler extends SoundInteractionHandler<St
* @param identifier the identifier of the block * @param identifier the identifier of the block
*/ */
static void handleBlockInteraction(GeyserSession session, Vector3f position, String identifier) { static void handleBlockInteraction(GeyserSession session, Vector3f position, String identifier) {
// If we need to get the hand identifier, only get it once and save it to a variable
String handIdentifier = null;
for (Map.Entry<SoundHandler, SoundInteractionHandler<?>> interactionEntry : SoundHandlerRegistry.INTERACTION_HANDLERS.entrySet()) { for (Map.Entry<SoundHandler, SoundInteractionHandler<?>> interactionEntry : SoundHandlerRegistry.INTERACTION_HANDLERS.entrySet()) {
if (!(interactionEntry.getValue() instanceof BlockSoundInteractionHandler)) { if (!(interactionEntry.getValue() instanceof BlockSoundInteractionHandler)) {
continue; continue;
@ -66,7 +67,9 @@ public interface BlockSoundInteractionHandler extends SoundInteractionHandler<St
if (itemInHand.isEmpty()) { if (itemInHand.isEmpty()) {
continue; continue;
} }
String handIdentifier = itemInHand.getItemEntry().getJavaIdentifier(); if (handIdentifier == null) {
handIdentifier = itemInHand.getItemEntry().getJavaIdentifier();
}
boolean contains = false; boolean contains = false;
for (String itemIdentifier : interactionEntry.getKey().items()) { for (String itemIdentifier : interactionEntry.getKey().items()) {
if (handIdentifier.contains(itemIdentifier)) { if (handIdentifier.contains(itemIdentifier)) {

View file

@ -25,12 +25,10 @@
package org.geysermc.connector.network.translators.sound; package org.geysermc.connector.network.translators.sound;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import java.util.Map; import java.util.Map;
@ -48,6 +46,9 @@ public interface EntitySoundInteractionHandler extends SoundInteractionHandler<E
* @param entity the entity interacted with * @param entity the entity interacted with
*/ */
static void handleEntityInteraction(GeyserSession session, Vector3f position, Entity entity) { static void handleEntityInteraction(GeyserSession session, Vector3f position, Entity entity) {
// If we need to get the hand identifier, only get it once and save it to a variable
String handIdentifier = null;
for (Map.Entry<SoundHandler, SoundInteractionHandler<?>> interactionEntry : SoundHandlerRegistry.INTERACTION_HANDLERS.entrySet()) { for (Map.Entry<SoundHandler, SoundInteractionHandler<?>> interactionEntry : SoundHandlerRegistry.INTERACTION_HANDLERS.entrySet()) {
if (!(interactionEntry.getValue() instanceof EntitySoundInteractionHandler)) { if (!(interactionEntry.getValue() instanceof EntitySoundInteractionHandler)) {
continue; continue;
@ -67,7 +68,10 @@ public interface EntitySoundInteractionHandler extends SoundInteractionHandler<E
if (itemInHand.isEmpty()) { if (itemInHand.isEmpty()) {
continue; continue;
} }
String handIdentifier = itemInHand.getItemEntry().getJavaIdentifier(); if (handIdentifier == null) {
// Don't get the identifier unless we need it
handIdentifier = itemInHand.getItemEntry().getJavaIdentifier();
}
boolean contains = false; boolean contains = false;
for (String itemIdentifier : interactionEntry.getKey().items()) { for (String itemIdentifier : interactionEntry.getKey().items()) {
if (handIdentifier.contains(itemIdentifier)) { if (handIdentifier.contains(itemIdentifier)) {

View file

@ -59,7 +59,7 @@ public @interface SoundHandler {
* Leave empty to ignore. * Leave empty to ignore.
* *
* Only applies to interaction handlers that are an * Only applies to interaction handlers that are an
* instance of {@link BlockSoundInteractionHandler}. * instance of {@link EntitySoundInteractionHandler}.
* *
* @return the value the item in the player's hand must contain * @return the value the item in the player's hand must contain
*/ */

View file

@ -33,7 +33,7 @@ import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHan
import org.geysermc.connector.network.translators.sound.SoundHandler; import org.geysermc.connector.network.translators.sound.SoundHandler;
@SoundHandler(blocks = "comparator") @SoundHandler(blocks = "comparator")
public class ComparatorSoundInteractHandler implements BlockSoundInteractionHandler { public class ComparatorSoundInteractionHandler implements BlockSoundInteractionHandler {
@Override @Override
public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { public void handleInteraction(GeyserSession session, Vector3f position, String identifier) {

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2019-2021 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.sound.entity;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.living.animal.AnimalEntity;
import org.geysermc.connector.entity.living.animal.OcelotEntity;
import org.geysermc.connector.entity.living.animal.tameable.CatEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler;
import org.geysermc.connector.network.translators.sound.SoundHandler;
@SoundHandler
public class FeedBabySoundInteractionHandler implements EntitySoundInteractionHandler {
@Override
public void handleInteraction(GeyserSession session, Vector3f position, Entity entity) {
if (entity instanceof AnimalEntity && !(entity instanceof CatEntity || entity instanceof OcelotEntity)) {
String handIdentifier = session.getPlayerInventory().getItemInHand().getItemEntry().getJavaIdentifier();
boolean isBaby = entity.getMetadata().getFlags().getFlag(EntityFlag.BABY);
if (isBaby && ((AnimalEntity) entity).canEat(handIdentifier.replace("minecraft:", ""))) {
// Play the "feed child" effect
EntityEventPacket feedEvent = new EntityEventPacket();
feedEvent.setRuntimeEntityId(entity.getGeyserId());
feedEvent.setType(EntityEventType.BABY_ANIMAL_FEED);
session.sendUpstreamPacket(feedEvent);
}
}
}
}

View file

@ -62,10 +62,6 @@ public class BlockStorage {
return (version.getId() << 1) | (runtime ? 1 : 0); return (version.getId() << 1) | (runtime ? 1 : 0);
} }
private static BitArrayVersion getVersionFromHeader(byte header) {
return BitArrayVersion.get(header >> 1, true);
}
public int getFullBlock(int index) { public int getFullBlock(int index) {
return this.palette.getInt(this.bitArray.get(index)); return this.palette.getInt(this.bitArray.get(index));
} }

View file

@ -25,12 +25,12 @@
package org.geysermc.connector.utils; package org.geysermc.connector.utils;
import com.google.common.collect.ImmutableSet;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import lombok.Getter; import lombok.Getter;
import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.living.animal.AnimalEntity;
import org.geysermc.connector.entity.living.animal.horse.HorseEntity; import org.geysermc.connector.entity.living.animal.horse.HorseEntity;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
@ -40,20 +40,6 @@ import java.util.EnumSet;
import java.util.Set; import java.util.Set;
public class InteractiveTagManager { public class InteractiveTagManager {
/**
* A list of all foods a horse/donkey can eat on Java Edition.
* Used to display interactive tag if needed.
*/
private static final Set<String> DONKEY_AND_HORSE_FOODS = ImmutableSet.of("golden_apple", "enchanted_golden_apple",
"golden_carrot", "sugar", "apple", "wheat", "hay_block");
/**
* A list of all flowers. Used for feeding bees.
*/
private static final Set<String> FLOWERS = ImmutableSet.of("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet",
"red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose",
"sunflower", "lilac", "rose_bush", "peony");
/** /**
* All entity types that can be leashed on Java Edition * All entity types that can be leashed on Java Edition
*/ */
@ -66,14 +52,6 @@ public class InteractiveTagManager {
private static final Set<EntityType> SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE, private static final Set<EntityType> SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE,
EntityType.ZOMBIE_HORSE, EntityType.MULE); EntityType.ZOMBIE_HORSE, EntityType.MULE);
/**
* A list of all foods a wolf can eat on Java Edition.
* Used to display interactive tag if needed.
*/
private static final Set<String> WOLF_FOODS = ImmutableSet.of("pufferfish", "tropical_fish", "chicken", "cooked_chicken",
"porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton",
"cooked_rabbit");
/** /**
* Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride") * Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride")
* *
@ -85,9 +63,8 @@ public class InteractiveTagManager {
ItemEntry itemEntry = session.getPlayerInventory().getItemInHand().getItemEntry(); ItemEntry itemEntry = session.getPlayerInventory().getItemInHand().getItemEntry();
String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", "");
// TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen
// TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food
InteractiveTag interactiveTag = InteractiveTag.NONE; InteractiveTag interactiveTag = InteractiveTag.NONE;
if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) { if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) {
// Unleash the entity // Unleash the entity
interactiveTag = InteractiveTag.REMOVE_LEASH; interactiveTag = InteractiveTag.REMOVE_LEASH;
@ -105,31 +82,24 @@ public class InteractiveTagManager {
// Holding a leash and the mob is leashable for sure // Holding a leash and the mob is leashable for sure
// (Plugins can change this behavior so that's something to look into in the far far future) // (Plugins can change this behavior so that's something to look into in the far far future)
interactiveTag = InteractiveTag.LEASH; interactiveTag = InteractiveTag.LEASH;
} else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(javaIdentifierStripped)) {
// This animal can be fed
interactiveTag = InteractiveTag.FEED;
} else { } else {
switch (interactEntity.getEntityType()) { switch (interactEntity.getEntityType()) {
case BEE: case BOAT:
if (FLOWERS.contains(javaIdentifierStripped)) { if (interactEntity.getPassengers().size() < 2) {
interactiveTag = InteractiveTag.FEED; interactiveTag = InteractiveTag.BOARD_BOAT;
} }
break; break;
case BOAT:
interactiveTag = InteractiveTag.BOARD_BOAT;
break;
case CAT: case CAT:
if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) &&
interactiveTag = InteractiveTag.FEED;
} else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) &&
entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand // Tamed and owned by player - can sit/stand
interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
break; break;
} }
break; break;
case CHICKEN:
if (javaIdentifierStripped.contains("seeds")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case MOOSHROOM: case MOOSHROOM:
// Shear the mooshroom // Shear the mooshroom
if (javaIdentifierStripped.equals("shears")) { if (javaIdentifierStripped.equals("shears")) {
@ -143,9 +113,7 @@ public class InteractiveTagManager {
} }
// Fall down to COW as this works on mooshrooms // Fall down to COW as this works on mooshrooms
case COW: case COW:
if (javaIdentifierStripped.equals("wheat")) { if (javaIdentifierStripped.equals("bucket")) {
interactiveTag = InteractiveTag.FEED;
} else if (javaIdentifierStripped.equals("bucket")) {
// Milk the cow // Milk the cow
interactiveTag = InteractiveTag.MILK; interactiveTag = InteractiveTag.MILK;
} }
@ -175,69 +143,28 @@ public class InteractiveTagManager {
interactiveTag = InteractiveTag.OPEN_CONTAINER; interactiveTag = InteractiveTag.OPEN_CONTAINER;
break; break;
} }
// have another switch statement as, while these share mount attributes they don't share food
switch (interactEntity.getEntityType()) {
case LLAMA:
case TRADER_LLAMA:
if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) {
interactiveTag = InteractiveTag.FEED;
break;
}
case DONKEY:
case HORSE:
// Undead can't eat
if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) {
interactiveTag = InteractiveTag.FEED;
break;
}
}
if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) {
// Can't ride a baby // Can't ride a baby
if (tamed) { if (tamed) {
interactiveTag = InteractiveTag.RIDE_HORSE; interactiveTag = InteractiveTag.RIDE_HORSE;
} else if (itemEntry.equals(ItemEntry.AIR)) { } else if (itemEntry.getJavaId() == 0) {
// Can't hide an untamed entity without having your hand empty // Can't hide an untamed entity without having your hand empty
interactiveTag = InteractiveTag.MOUNT; interactiveTag = InteractiveTag.MOUNT;
} }
} }
break; break;
case FOX:
if (javaIdentifierStripped.equals("sweet_berries")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case HOGLIN:
if (javaIdentifierStripped.equals("crimson_fungus")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case MINECART: case MINECART:
if (interactEntity.getPassengers().isEmpty()) {
interactiveTag = InteractiveTag.RIDE_MINECART; interactiveTag = InteractiveTag.RIDE_MINECART;
}
break; break;
case MINECART_CHEST: case MINECART_CHEST:
case MINECART_COMMAND_BLOCK: case MINECART_COMMAND_BLOCK:
case MINECART_HOPPER: case MINECART_HOPPER:
interactiveTag = InteractiveTag.OPEN_CONTAINER; interactiveTag = InteractiveTag.OPEN_CONTAINER;
break; break;
case OCELOT:
if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case PANDA:
if (javaIdentifierStripped.equals("bamboo")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case PARROT:
if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case PIG: case PIG:
if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) { if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.FEED;
} else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.MOUNT; interactiveTag = InteractiveTag.MOUNT;
} }
break; break;
@ -246,15 +173,8 @@ public class InteractiveTagManager {
interactiveTag = InteractiveTag.BARTER; interactiveTag = InteractiveTag.BARTER;
} }
break; break;
case RABBIT:
if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case SHEEP: case SHEEP:
if (javaIdentifierStripped.equals("wheat")) { if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) {
interactiveTag = InteractiveTag.FEED;
} else if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) {
if (javaIdentifierStripped.equals("shears")) { if (javaIdentifierStripped.equals("shears")) {
// Shear the sheep // Shear the sheep
interactiveTag = InteractiveTag.SHEAR; interactiveTag = InteractiveTag.SHEAR;
@ -265,17 +185,10 @@ public class InteractiveTagManager {
} }
break; break;
case STRIDER: case STRIDER:
if (javaIdentifierStripped.equals("warped_fungus")) { if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.FEED;
} else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.RIDE_STRIDER; interactiveTag = InteractiveTag.RIDE_STRIDER;
} }
break; break;
case TURTLE:
if (javaIdentifierStripped.equals("seagrass")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case VILLAGER: case VILLAGER:
if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0
&& entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby
@ -289,10 +202,6 @@ public class InteractiveTagManager {
if (javaIdentifierStripped.equals("bone") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { if (javaIdentifierStripped.equals("bone") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) {
// Bone and untamed - can tame // Bone and untamed - can tame
interactiveTag = InteractiveTag.TAME; interactiveTag = InteractiveTag.TAME;
} else if (WOLF_FOODS.contains(javaIdentifierStripped)) {
// Compatible food in hand - feed
// Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this
interactiveTag = InteractiveTag.FEED;
} else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) &&
entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand // Tamed and owned by player - can sit/stand

View file

@ -35,7 +35,6 @@ import org.geysermc.cumulus.component.DropdownComponent;
import org.geysermc.cumulus.response.CustomFormResponse; import org.geysermc.cumulus.response.CustomFormResponse;
public class SettingsUtils { public class SettingsUtils {
/** /**
* Build a settings form for the given session and store it for later * Build a settings form for the given session and store it for later
* *
@ -48,9 +47,13 @@ public class SettingsUtils {
CustomForm.Builder builder = CustomForm.builder() CustomForm.Builder builder = CustomForm.builder()
.translator(LanguageUtils::getPlayerLocaleString, language) .translator(LanguageUtils::getPlayerLocaleString, language)
.title("geyser.settings.title.main") .title("geyser.settings.title.main")
.iconPath("textures/ui/settings_glyph_color_2x.png") .iconPath("textures/ui/settings_glyph_color_2x.png");
.label("geyser.settings.title.client")
.toggle("geyser.settings.option.coordinates"); // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
if (!session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates()) {
builder.label("geyser.settings.title.client")
.toggle("geyser.settings.option.coordinates", session.getWorldCache().isPrefersShowCoordinates());
}
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
@ -94,7 +97,13 @@ public class SettingsUtils {
return; return;
} }
session.getWorldCache().setShowCoordinates(response.next()); // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
if (!session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates()) {
response.skip(); // Client settings title
session.getWorldCache().setPrefersShowCoordinates(response.next());
session.getWorldCache().updateShowCoordinates();
}
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
GameMode gameMode = GameMode.values()[(int) response.next()]; GameMode gameMode = GameMode.values()[(int) response.next()];

View file

@ -53,6 +53,9 @@ remote:
# 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config. # 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config.
# IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT! # IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!
use-proxy-protocol: false use-proxy-protocol: false
# Forward the hostname that the Bedrock client used to connect over to the Java server
# This is designed to be used for forced hosts on proxies
forward-hostname: false
# Floodgate uses encryption to ensure use from authorised sources. # Floodgate uses encryption to ensure use from authorised sources.
# This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity) # This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity)
@ -121,6 +124,13 @@ show-cooldown: title
# Controls if coordinates are shown to players. # Controls if coordinates are shown to players.
show-coordinates: true show-coordinates: true
# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind
# There are three options this can be set to:
# disabled - the default/fallback, which doesn't apply this workaround
# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen.
# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped
emote-offhand-workaround: "disabled"
# The default locale if we dont have the one the client requested. Uncomment to not use the default system language. # The default locale if we dont have the one the client requested. Uncomment to not use the default system language.
# default-locale: en_us # default-locale: en_us