mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge remote-tracking branch 'origin/master' into feature/cumulus-1.1
# Conflicts: # ap/pom.xml # api/base/pom.xml # api/geyser/pom.xml # api/pom.xml # bootstrap/bungeecord/pom.xml # bootstrap/pom.xml # bootstrap/spigot/pom.xml # bootstrap/sponge/pom.xml # bootstrap/standalone/pom.xml # bootstrap/velocity/pom.xml # common/pom.xml # core/pom.xml # core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java # core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java # pom.xml
This commit is contained in:
commit
d4ecd2bd72
246 changed files with 7897 additions and 4674 deletions
26
core/pom.xml
26
core/pom.xml
|
@ -6,14 +6,14 @@
|
|||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-parent</artifactId>
|
||||
<version>2.0.1-cumulus-SNAPSHOT</version>
|
||||
<version>2.0.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>core</artifactId>
|
||||
|
||||
<properties>
|
||||
<adventure.version>4.9.3</adventure.version>
|
||||
<fastutil.version>8.5.2</fastutil.version>
|
||||
<jackson.version>2.12.4</jackson.version>
|
||||
<jackson.version>2.13.2</jackson.version>
|
||||
<netty.version>4.1.66.Final</netty.version>
|
||||
</properties>
|
||||
|
||||
|
@ -21,19 +21,19 @@
|
|||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>ap</artifactId>
|
||||
<version>2.0.1-cumulus-SNAPSHOT</version>
|
||||
<version>2.0.3-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-api</artifactId>
|
||||
<version>2.0.1-cumulus-SNAPSHOT</version>
|
||||
<version>2.0.3-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>2.0.1-cumulus-SNAPSHOT</version>
|
||||
<version>2.0.3-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- Jackson JSON and YAML serialization -->
|
||||
|
@ -52,7 +52,7 @@
|
|||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<version>${jackson.version}.1</version> <!-- Extra .1 as databind is a slightly different version -->
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -120,8 +120,8 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.CloudburstMC.Protocol</groupId>
|
||||
<artifactId>bedrock-v475</artifactId>
|
||||
<version>c22aa595</version>
|
||||
<artifactId>bedrock-v503</artifactId>
|
||||
<version>297567d</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
|
@ -147,23 +147,23 @@
|
|||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.RednedEpic</groupId>
|
||||
<groupId>com.github.GeyserMC</groupId>
|
||||
<artifactId>MCAuthLib</artifactId>
|
||||
<version>6c99331</version>
|
||||
<version>d9d773e</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.GeyserMC</groupId>
|
||||
<artifactId>MCProtocolLib</artifactId>
|
||||
<version>6a23a780</version>
|
||||
<version>0771504</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.github.steveice10</groupId>
|
||||
<groupId>com.github.GeyserMC</groupId>
|
||||
<artifactId>packetlib</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.github.steveice10</groupId>
|
||||
<groupId>com.github.GeyserMC</groupId>
|
||||
<artifactId>mcauthlib</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
|
|
|
@ -100,7 +100,7 @@ public class GeyserSession {
|
|||
}
|
||||
|
||||
public void login() {
|
||||
this.handle.login();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void authenticate(String username) {
|
||||
|
@ -120,7 +120,7 @@ public class GeyserSession {
|
|||
}
|
||||
|
||||
public void close() {
|
||||
this.handle.close();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void executeInEventLoop(Runnable runnable) {
|
||||
|
|
|
@ -37,6 +37,8 @@ public final class Constants {
|
|||
|
||||
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/";
|
||||
|
||||
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
|
||||
|
||||
static {
|
||||
URI wsUri = null;
|
||||
try {
|
||||
|
|
|
@ -97,6 +97,13 @@ public interface GeyserBootstrap {
|
|||
*/
|
||||
Path getConfigFolder();
|
||||
|
||||
/**
|
||||
* @return the folder where user tokens are saved. This should always point to the location of the config.
|
||||
*/
|
||||
default Path getSavedUserLoginsFolder() {
|
||||
return getConfigFolder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Information used for the bootstrap section of the debug dump
|
||||
*
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
package org.geysermc.geyser;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.steveice10.packetlib.tcp.TcpSession;
|
||||
|
@ -37,6 +38,7 @@ import io.netty.channel.kqueue.KQueue;
|
|||
import io.netty.util.NettyRuntime;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
@ -59,9 +61,11 @@ import org.geysermc.geyser.registry.BlockRegistries;
|
|||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||
import org.geysermc.geyser.session.SessionManager;
|
||||
import org.geysermc.geyser.session.auth.AuthType;
|
||||
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
||||
|
@ -70,6 +74,9 @@ import org.geysermc.geyser.util.*;
|
|||
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
@ -77,6 +84,7 @@ import java.net.UnknownHostException;
|
|||
import java.security.Key;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -124,6 +132,10 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
private Metrics metrics;
|
||||
|
||||
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private Map<String, String> savedRefreshTokens;
|
||||
|
||||
private static GeyserImpl instance;
|
||||
|
||||
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) {
|
||||
|
@ -195,6 +207,8 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
ScoreboardUpdater.init();
|
||||
|
||||
SkinProvider.registerCacheImageTask(this);
|
||||
|
||||
ResourcePack.loadPacks();
|
||||
|
||||
if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) {
|
||||
|
@ -265,6 +279,8 @@ public class GeyserImpl implements GeyserApi {
|
|||
logger.debug("Not getting git properties for the news handler as we are in a development environment.");
|
||||
}
|
||||
|
||||
pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout());
|
||||
|
||||
this.newsHandler = new NewsHandler(branch, buildNumber);
|
||||
|
||||
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
|
||||
|
@ -317,7 +333,7 @@ public class GeyserImpl implements GeyserApi {
|
|||
metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger(""));
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size));
|
||||
// Prevent unwanted words best we can
|
||||
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().toString().toLowerCase()));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().toString().toLowerCase(Locale.ROOT)));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION));
|
||||
|
@ -401,6 +417,47 @@ public class GeyserImpl implements GeyserApi {
|
|||
metrics = null;
|
||||
}
|
||||
|
||||
if (config.getRemote().getAuthType() == AuthType.ONLINE) {
|
||||
if (config.getUserAuths() != null && !config.getUserAuths().isEmpty()) {
|
||||
getLogger().warning("The 'userAuths' config section is now deprecated, and will be removed in the near future! " +
|
||||
"Please migrate to the new 'saved-user-logins' config option: " +
|
||||
"https://wiki.geysermc.org/geyser/understanding-the-config/");
|
||||
}
|
||||
|
||||
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
|
||||
savedRefreshTokens = new ConcurrentHashMap<>();
|
||||
|
||||
File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
||||
if (tokensFile.exists()) {
|
||||
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
||||
|
||||
Map<String, String> refreshTokenFile = null;
|
||||
try {
|
||||
refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type);
|
||||
} catch (IOException e) {
|
||||
logger.error("Cannot load saved user tokens!", e);
|
||||
}
|
||||
if (refreshTokenFile != null) {
|
||||
List<String> validUsers = config.getSavedUserLogins();
|
||||
boolean doWrite = false;
|
||||
for (Map.Entry<String, String> entry : refreshTokenFile.entrySet()) {
|
||||
String user = entry.getKey();
|
||||
if (!validUsers.contains(user)) {
|
||||
// Perform a write to this file to purge the now-unused name
|
||||
doWrite = true;
|
||||
continue;
|
||||
}
|
||||
savedRefreshTokens.put(user, entry.getValue());
|
||||
}
|
||||
if (doWrite) {
|
||||
scheduleRefreshTokensWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
savedRefreshTokens = null;
|
||||
}
|
||||
|
||||
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
|
||||
}
|
||||
|
||||
|
@ -508,6 +565,39 @@ public class GeyserImpl implements GeyserApi {
|
|||
return bootstrap.getWorldManager();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String refreshTokenFor(@NonNull String bedrockName) {
|
||||
return savedRefreshTokens.get(bedrockName);
|
||||
}
|
||||
|
||||
public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) {
|
||||
if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
|
||||
// Do not save this login
|
||||
return;
|
||||
}
|
||||
|
||||
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
|
||||
// refreshes the token for us
|
||||
if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) {
|
||||
scheduleRefreshTokensWrite();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRefreshTokensWrite() {
|
||||
scheduledThread.execute(() -> {
|
||||
// Ensure all writes are handled on the same thread
|
||||
File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
||||
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
||||
try (FileWriter writer = new FileWriter(savedTokens)) {
|
||||
JSON_MAPPER.writerFor(type)
|
||||
.withDefaultPrettyPrinter()
|
||||
.writeValue(writer, savedRefreshTokens);
|
||||
} catch (IOException e) {
|
||||
getLogger().error("Unable to write saved refresh tokens!", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static GeyserImpl getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
package org.geysermc.geyser;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface GeyserLogger {
|
||||
|
||||
/**
|
||||
|
@ -78,6 +80,15 @@ public interface GeyserLogger {
|
|||
*/
|
||||
void debug(String message);
|
||||
|
||||
/**
|
||||
* Logs an object to console if debug mode is enabled
|
||||
*
|
||||
* @param object the object to log
|
||||
*/
|
||||
default void debug(@Nullable Object object) {
|
||||
debug(String.valueOf(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the logger should print debug messages
|
||||
*
|
||||
|
|
|
@ -86,4 +86,13 @@ public abstract class GeyserCommand {
|
|||
public boolean isBedrockOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for permission defaults on server implementations.
|
||||
*
|
||||
* @return if this command is designated to be used only by server operators.
|
||||
*/
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -145,4 +145,9 @@ public class DumpCommand extends GeyserCommand {
|
|||
public List<String> getSubCommands() {
|
||||
return Arrays.asList("offline", "full", "logs");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,4 +51,9 @@ public class ListCommand extends GeyserCommand {
|
|||
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,4 +54,9 @@ public class ReloadCommand extends GeyserCommand {
|
|||
geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick");
|
||||
geyser.reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,4 +54,9 @@ public class StopCommand extends GeyserCommand {
|
|||
|
||||
geyser.getBootstrap().onDisable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -100,4 +100,9 @@ public class VersionCommand extends GeyserCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuggestedOpOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,9 @@ public interface GeyserConfiguration {
|
|||
|
||||
IRemoteConfiguration getRemote();
|
||||
|
||||
List<String> getSavedUserLogins();
|
||||
|
||||
@Deprecated
|
||||
Map<String, ? extends IUserAuthenticationInfo> getUserAuths();
|
||||
|
||||
boolean isCommandSuggestions();
|
||||
|
@ -78,6 +81,8 @@ public interface GeyserConfiguration {
|
|||
|
||||
boolean isDisableBedrockScaffolding();
|
||||
|
||||
boolean isAlwaysQuickChangeArmor();
|
||||
|
||||
EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround();
|
||||
|
||||
String getDefaultLocale();
|
||||
|
@ -96,8 +101,14 @@ public interface GeyserConfiguration {
|
|||
|
||||
boolean isAllowCustomSkulls();
|
||||
|
||||
int getMaxVisibleCustomSkulls();
|
||||
|
||||
int getCustomSkullRenderDistance();
|
||||
|
||||
IMetricsInfo getMetrics();
|
||||
|
||||
int getPendingAuthenticationTimeout();
|
||||
|
||||
interface IBedrockConfiguration {
|
||||
|
||||
String getAddress();
|
||||
|
|
|
@ -35,9 +35,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.network.CIDRMatcher;
|
||||
import org.geysermc.geyser.session.auth.AuthType;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
import org.geysermc.geyser.network.CIDRMatcher;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -62,6 +62,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
private BedrockConfiguration bedrock = new BedrockConfiguration();
|
||||
private RemoteConfiguration remote = new RemoteConfiguration();
|
||||
|
||||
@JsonProperty("saved-user-logins")
|
||||
private List<String> savedUserLogins = Collections.emptyList();
|
||||
|
||||
@JsonProperty("floodgate-key-file")
|
||||
private String floodgateKeyFile = "key.pem";
|
||||
|
||||
|
@ -108,6 +111,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
@JsonProperty("disable-bedrock-scaffolding")
|
||||
private boolean disableBedrockScaffolding = false;
|
||||
|
||||
@JsonProperty("always-quick-change-armor")
|
||||
private boolean alwaysQuickChangeArmor = false;
|
||||
|
||||
@JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class)
|
||||
@JsonProperty("emote-offhand-workaround")
|
||||
private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED;
|
||||
|
@ -124,6 +130,12 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
@JsonProperty("allow-custom-skulls")
|
||||
private boolean allowCustomSkulls = true;
|
||||
|
||||
@JsonProperty("max-visible-custom-skulls")
|
||||
private int maxVisibleCustomSkulls = 128;
|
||||
|
||||
@JsonProperty("custom-skull-render-distance")
|
||||
private int customSkullRenderDistance = 32;
|
||||
|
||||
@JsonProperty("add-non-bedrock-items")
|
||||
private boolean addNonBedrockItems = true;
|
||||
|
||||
|
@ -138,6 +150,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
|
||||
private MetricsInfo metrics = new MetricsInfo();
|
||||
|
||||
@JsonProperty("pending-authentication-timeout")
|
||||
private int pendingAuthenticationTimeout = 120;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class BedrockConfiguration implements IBedrockConfiguration {
|
||||
|
@ -231,8 +246,21 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
public static class MetricsInfo implements IMetricsInfo {
|
||||
private boolean enabled = true;
|
||||
|
||||
@JsonDeserialize(using = MetricsIdDeserializer.class)
|
||||
@JsonProperty("uuid")
|
||||
private String uniqueId = UUID.randomUUID().toString();
|
||||
|
||||
private static class MetricsIdDeserializer extends JsonDeserializer<String> {
|
||||
@Override
|
||||
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
String uuid = p.getValueAsString();
|
||||
if ("generateduuid".equals(uuid)) {
|
||||
// Compensate for configs not copied from the jar
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty("scoreboard-packet-threshold")
|
||||
|
|
|
@ -54,10 +54,7 @@ import java.net.InetSocketAddress;
|
|||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
|
@ -67,6 +64,8 @@ public class DumpInfo {
|
|||
|
||||
private final DumpInfo.VersionInfo versionInfo;
|
||||
private final int cpuCount;
|
||||
private final Locale systemLocale;
|
||||
private final String systemEncoding;
|
||||
private Properties gitInfo;
|
||||
private final GeyserConfiguration config;
|
||||
private final Floodgate floodgate;
|
||||
|
@ -81,6 +80,8 @@ public class DumpInfo {
|
|||
this.versionInfo = new VersionInfo();
|
||||
|
||||
this.cpuCount = Runtime.getRuntime().availableProcessors();
|
||||
this.systemLocale = Locale.getDefault();
|
||||
this.systemEncoding = System.getProperty("file.encoding");
|
||||
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) {
|
||||
this.gitInfo = new Properties();
|
||||
|
|
|
@ -65,9 +65,9 @@ public final class EntityDefinitions {
|
|||
public static final EntityDefinition<ChickenEntity> CHICKEN;
|
||||
public static final EntityDefinition<AbstractFishEntity> COD;
|
||||
public static final EntityDefinition<CommandBlockMinecartEntity> COMMAND_BLOCK_MINECART;
|
||||
public static final EntityDefinition<AnimalEntity> COW;
|
||||
public static final EntityDefinition<CowEntity> COW;
|
||||
public static final EntityDefinition<CreeperEntity> CREEPER;
|
||||
public static final EntityDefinition<WaterEntity> DOLPHIN;
|
||||
public static final EntityDefinition<DolphinEntity> DOLPHIN;
|
||||
public static final EntityDefinition<ChestedHorseEntity> DONKEY;
|
||||
public static final EntityDefinition<FireballEntity> DRAGON_FIREBALL;
|
||||
public static final EntityDefinition<ZombieEntity> DROWNED;
|
||||
|
@ -132,7 +132,7 @@ public final class EntityDefinitions {
|
|||
public static final EntityDefinition<ThrowableEntity> SHULKER_BULLET;
|
||||
public static final EntityDefinition<MonsterEntity> SILVERFISH;
|
||||
public static final EntityDefinition<SkeletonEntity> SKELETON;
|
||||
public static final EntityDefinition<AbstractHorseEntity> SKELETON_HORSE;
|
||||
public static final EntityDefinition<SkeletonHorseEntity> SKELETON_HORSE;
|
||||
public static final EntityDefinition<SlimeEntity> SLIME;
|
||||
public static final EntityDefinition<FireballEntity> SMALL_FIREBALL;
|
||||
public static final EntityDefinition<ThrowableItemEntity> SNOWBALL;
|
||||
|
@ -160,7 +160,7 @@ public final class EntityDefinitions {
|
|||
public static final EntityDefinition<WolfEntity> WOLF;
|
||||
public static final EntityDefinition<ZoglinEntity> ZOGLIN;
|
||||
public static final EntityDefinition<ZombieEntity> ZOMBIE;
|
||||
public static final EntityDefinition<AbstractHorseEntity> ZOMBIE_HORSE;
|
||||
public static final EntityDefinition<ZombieHorseEntity> ZOMBIE_HORSE;
|
||||
public static final EntityDefinition<ZombieVillagerEntity> ZOMBIE_VILLAGER;
|
||||
public static final EntityDefinition<ZombifiedPiglinEntity> ZOMBIFIED_PIGLIN;
|
||||
|
||||
|
@ -459,7 +459,7 @@ public final class EntityDefinitions {
|
|||
.addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.POWERED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
|
||||
.addTranslator(MetadataType.BOOLEAN, CreeperEntity::setIgnited)
|
||||
.build();
|
||||
DOLPHIN = EntityDefinition.inherited(WaterEntity::new, mobEntityBase)
|
||||
DOLPHIN = EntityDefinition.inherited(DolphinEntity::new, mobEntityBase)
|
||||
.type(EntityType.DOLPHIN)
|
||||
.height(0.6f).width(0.9f)
|
||||
//TODO check
|
||||
|
@ -723,7 +723,7 @@ public final class EntityDefinitions {
|
|||
.type(EntityType.CHICKEN)
|
||||
.height(0.7f).width(0.4f)
|
||||
.build();
|
||||
COW = EntityDefinition.inherited(AnimalEntity::new, ageableEntityBase)
|
||||
COW = EntityDefinition.inherited(CowEntity::new, ageableEntityBase)
|
||||
.type(EntityType.COW)
|
||||
.height(1.4f).width(0.9f)
|
||||
.build();
|
||||
|
@ -745,14 +745,14 @@ public final class EntityDefinitions {
|
|||
.height(1.3f).width(0.9f)
|
||||
.addTranslator(MetadataType.BOOLEAN, GoatEntity::setScreamer)
|
||||
.build();
|
||||
MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase) // TODO remove class
|
||||
MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase)
|
||||
.type(EntityType.MOOSHROOM)
|
||||
.height(1.4f).width(0.9f)
|
||||
.addTranslator(MetadataType.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0))
|
||||
.addTranslator(MetadataType.STRING, MooshroomEntity::setVariant)
|
||||
.build();
|
||||
OCELOT = EntityDefinition.inherited(OcelotEntity::new, ageableEntityBase)
|
||||
.type(EntityType.OCELOT)
|
||||
.height(0.35f).width(0.3f)
|
||||
.height(0.7f).width(0.6f)
|
||||
.addTranslator(MetadataType.BOOLEAN, (ocelotEntity, entityMetadata) -> ocelotEntity.setFlag(EntityFlag.TRUSTING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
|
||||
.build();
|
||||
PANDA = EntityDefinition.inherited(PandaEntity::new, ageableEntityBase)
|
||||
|
@ -783,7 +783,7 @@ public final class EntityDefinitions {
|
|||
.build();
|
||||
SHEEP = EntityDefinition.inherited(SheepEntity::new, ageableEntityBase)
|
||||
.type(EntityType.SHEEP)
|
||||
.heightAndWidth(0.9f)
|
||||
.height(1.3f).width(0.9f)
|
||||
.addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags)
|
||||
.build();
|
||||
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
|
||||
|
@ -832,11 +832,11 @@ public final class EntityDefinitions {
|
|||
.height(1.6f).width(1.3965f)
|
||||
.addTranslator(MetadataType.INT, HorseEntity::setHorseVariant)
|
||||
.build();
|
||||
SKELETON_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase)
|
||||
SKELETON_HORSE = EntityDefinition.inherited(SkeletonHorseEntity::new, abstractHorseEntityBase)
|
||||
.type(EntityType.SKELETON_HORSE)
|
||||
.height(1.6f).width(1.3965f)
|
||||
.build();
|
||||
ZOMBIE_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase)
|
||||
ZOMBIE_HORSE = EntityDefinition.inherited(ZombieHorseEntity::new, abstractHorseEntityBase)
|
||||
.type(EntityType.ZOMBIE_HORSE)
|
||||
.height(1.6f).width(1.3965f)
|
||||
.build();
|
||||
|
|
|
@ -34,7 +34,7 @@ import java.util.Map;
|
|||
/**
|
||||
* A write-only wrapper for temporarily storing entity metadata that will be sent to Bedrock.
|
||||
*/
|
||||
public class GeyserDirtyMetadata {
|
||||
public final class GeyserDirtyMetadata {
|
||||
private final Map<EntityData, Object> metadata = new Object2ObjectLinkedOpenHashMap<>();
|
||||
|
||||
public void put(EntityData entityData, Object value) {
|
||||
|
|
|
@ -1,293 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.living.MobEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.HorseEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity;
|
||||
import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class InteractiveTagManager {
|
||||
/**
|
||||
* All entity types that can be leashed on Java Edition
|
||||
*/
|
||||
private static final Set<EntityType> LEASHABLE_MOB_TYPES = EnumSet.of(EntityType.AXOLOTL, EntityType.BEE, EntityType.CAT, EntityType.CHICKEN,
|
||||
EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.GOAT, EntityType.GLOW_SQUID, EntityType.HOGLIN,
|
||||
EntityType.HORSE, EntityType.SKELETON_HORSE, EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA,
|
||||
EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG,
|
||||
EntityType.POLAR_BEAR, EntityType.RABBIT, EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.SQUID, EntityType.STRIDER,
|
||||
EntityType.WOLF, EntityType.ZOGLIN);
|
||||
|
||||
private static final Set<EntityType> SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE,
|
||||
EntityType.ZOMBIE_HORSE, EntityType.MULE);
|
||||
|
||||
/**
|
||||
* Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride")
|
||||
*
|
||||
* @param session the Bedrock client session
|
||||
* @param interactEntity the entity that the client is currently facing.
|
||||
*/
|
||||
public static void updateTag(GeyserSession session, Entity interactEntity) {
|
||||
ItemMapping mapping = session.getPlayerInventory().getItemInHand().getMapping(session);
|
||||
String javaIdentifierStripped = mapping.getJavaIdentifier().replace("minecraft:", "");
|
||||
EntityType entityType = interactEntity.getDefinition().entityType();
|
||||
if (entityType == null) {
|
||||
// Likely a technical entity; we don't need to worry about this
|
||||
return;
|
||||
}
|
||||
|
||||
InteractiveTag interactiveTag = InteractiveTag.NONE;
|
||||
|
||||
if (interactEntity instanceof MobEntity mobEntity && mobEntity.getLeashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) {
|
||||
// Unleash the entity
|
||||
interactiveTag = InteractiveTag.REMOVE_LEASH;
|
||||
} else if (javaIdentifierStripped.equals("saddle") && !interactEntity.getFlag(EntityFlag.SADDLED) &&
|
||||
((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(entityType) && interactEntity.getFlag(EntityFlag.TAMED) && !session.isSneaking()) ||
|
||||
entityType == EntityType.PIG || entityType == EntityType.STRIDER)) {
|
||||
// Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed)
|
||||
interactiveTag = InteractiveTag.SADDLE;
|
||||
} else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null &&
|
||||
session.getPlayerInventory().getItemInHand().getNbt().contains("display")) {
|
||||
// Holding a named name tag
|
||||
interactiveTag = InteractiveTag.NAME;
|
||||
} else if (interactEntity instanceof MobEntity mobEntity &&javaIdentifierStripped.equals("lead")
|
||||
&& LEASHABLE_MOB_TYPES.contains(entityType) && mobEntity.getLeashHolderBedrockId() == -1L) {
|
||||
// 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)
|
||||
interactiveTag = InteractiveTag.LEASH;
|
||||
} else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(javaIdentifierStripped, mapping)) {
|
||||
// This animal can be fed
|
||||
interactiveTag = InteractiveTag.FEED;
|
||||
} else {
|
||||
switch (entityType) {
|
||||
case BOAT:
|
||||
if (interactEntity.getPassengers().size() < 2) {
|
||||
interactiveTag = InteractiveTag.BOARD_BOAT;
|
||||
}
|
||||
break;
|
||||
case CAT:
|
||||
if (interactEntity.getFlag(EntityFlag.TAMED) &&
|
||||
((CatEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) {
|
||||
// Tamed and owned by player - can sit/stand
|
||||
interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MOOSHROOM:
|
||||
// Shear the mooshroom
|
||||
if (javaIdentifierStripped.equals("shears")) {
|
||||
interactiveTag = InteractiveTag.MOOSHROOM_SHEAR;
|
||||
break;
|
||||
}
|
||||
// Bowls are acceptable here
|
||||
else if (javaIdentifierStripped.equals("bowl")) {
|
||||
interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW;
|
||||
break;
|
||||
}
|
||||
// Fall down to COW as this works on mooshrooms
|
||||
case COW:
|
||||
if (javaIdentifierStripped.equals("bucket")) {
|
||||
// Milk the cow
|
||||
interactiveTag = InteractiveTag.MILK;
|
||||
}
|
||||
break;
|
||||
case CREEPER:
|
||||
if (javaIdentifierStripped.equals("flint_and_steel")) {
|
||||
// Today I learned that you can ignite a creeper with flint and steel! Huh.
|
||||
interactiveTag = InteractiveTag.IGNITE_CREEPER;
|
||||
}
|
||||
break;
|
||||
case DONKEY:
|
||||
case LLAMA:
|
||||
case MULE:
|
||||
if (interactEntity.getFlag(EntityFlag.TAMED) && !interactEntity.getFlag(EntityFlag.CHESTED)
|
||||
&& javaIdentifierStripped.equals("chest")) {
|
||||
// Can attach a chest
|
||||
interactiveTag = InteractiveTag.ATTACH_CHEST;
|
||||
break;
|
||||
}
|
||||
// Intentional fall-through
|
||||
case HORSE:
|
||||
case SKELETON_HORSE:
|
||||
case TRADER_LLAMA:
|
||||
case ZOMBIE_HORSE:
|
||||
boolean tamed = interactEntity.getFlag(EntityFlag.TAMED);
|
||||
if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || interactEntity.getFlag(EntityFlag.CHESTED))) {
|
||||
interactiveTag = InteractiveTag.OPEN_CONTAINER;
|
||||
break;
|
||||
}
|
||||
if (!interactEntity.getFlag(EntityFlag.BABY)) {
|
||||
// Can't ride a baby
|
||||
if (tamed) {
|
||||
interactiveTag = InteractiveTag.RIDE_HORSE;
|
||||
} else if (mapping.getJavaId() == 0) {
|
||||
// Can't hide an untamed entity without having your hand empty
|
||||
interactiveTag = InteractiveTag.MOUNT;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MINECART:
|
||||
if (interactEntity.getPassengers().isEmpty()) {
|
||||
interactiveTag = InteractiveTag.RIDE_MINECART;
|
||||
}
|
||||
break;
|
||||
case CHEST_MINECART:
|
||||
case COMMAND_BLOCK_MINECART:
|
||||
case HOPPER_MINECART:
|
||||
interactiveTag = InteractiveTag.OPEN_CONTAINER;
|
||||
break;
|
||||
case PIG:
|
||||
if (interactEntity.getFlag(EntityFlag.SADDLED)) {
|
||||
interactiveTag = InteractiveTag.MOUNT;
|
||||
}
|
||||
break;
|
||||
case PIGLIN:
|
||||
if (!interactEntity.getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) {
|
||||
interactiveTag = InteractiveTag.BARTER;
|
||||
}
|
||||
break;
|
||||
case SHEEP:
|
||||
if (!interactEntity.getFlag(EntityFlag.SHEARED)) {
|
||||
if (javaIdentifierStripped.equals("shears")) {
|
||||
// Shear the sheep
|
||||
interactiveTag = InteractiveTag.SHEAR;
|
||||
} else if (javaIdentifierStripped.contains("_dye")) {
|
||||
// Dye the sheep
|
||||
interactiveTag = InteractiveTag.DYE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STRIDER:
|
||||
if (interactEntity.getFlag(EntityFlag.SADDLED)) {
|
||||
interactiveTag = InteractiveTag.RIDE_STRIDER;
|
||||
}
|
||||
break;
|
||||
case VILLAGER:
|
||||
VillagerEntity villager = (VillagerEntity) interactEntity;
|
||||
if (villager.isCanTradeWith() && !villager.isBaby()) { // Not a nitwit, has a profession and is not a baby
|
||||
interactiveTag = InteractiveTag.TRADE;
|
||||
}
|
||||
break;
|
||||
case WANDERING_TRADER:
|
||||
interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably.
|
||||
break;
|
||||
case WOLF:
|
||||
if (javaIdentifierStripped.equals("bone") && !interactEntity.getFlag(EntityFlag.TAMED)) {
|
||||
// Bone and untamed - can tame
|
||||
interactiveTag = InteractiveTag.TAME;
|
||||
} else if (interactEntity.getFlag(EntityFlag.TAMED) &&
|
||||
((WolfEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) {
|
||||
// Tamed and owned by player - can sit/stand
|
||||
interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
|
||||
}
|
||||
break;
|
||||
case ZOMBIE_VILLAGER:
|
||||
// We can't guarantee the existence of the weakness effect so we just always show it.
|
||||
if (javaIdentifierStripped.equals("golden_apple")) {
|
||||
interactiveTag = InteractiveTag.CURE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue());
|
||||
session.getPlayerEntity().updateBedrockMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* All interactive tags in enum form. For potential API usage.
|
||||
*/
|
||||
public enum InteractiveTag {
|
||||
NONE((Void) null),
|
||||
IGNITE_CREEPER("creeper"),
|
||||
EDIT,
|
||||
LEAVE_BOAT("exit.boat"),
|
||||
FEED,
|
||||
FISH("fishing"),
|
||||
MILK,
|
||||
MOOSHROOM_SHEAR("mooshear"),
|
||||
MOOSHROOM_MILK_STEW("moostew"),
|
||||
BOARD_BOAT("ride.boat"),
|
||||
RIDE_MINECART("ride.minecart"),
|
||||
RIDE_HORSE("ride.horse"),
|
||||
RIDE_STRIDER("ride.strider"),
|
||||
SHEAR,
|
||||
SIT,
|
||||
STAND,
|
||||
TALK,
|
||||
TAME,
|
||||
DYE,
|
||||
CURE,
|
||||
OPEN_CONTAINER("opencontainer"),
|
||||
CREATE_MAP("createMap"),
|
||||
TAKE_PICTURE("takepicture"),
|
||||
SADDLE,
|
||||
MOUNT,
|
||||
BOOST,
|
||||
WRITE,
|
||||
LEASH,
|
||||
REMOVE_LEASH("unleash"),
|
||||
NAME,
|
||||
ATTACH_CHEST("attachchest"),
|
||||
TRADE,
|
||||
POSE_ARMOR_STAND("armorstand.pose"),
|
||||
EQUIP_ARMOR_STAND("armorstand.equip"),
|
||||
READ,
|
||||
WAKE_VILLAGER("wakevillager"),
|
||||
BARTER;
|
||||
|
||||
/**
|
||||
* The full string that should be passed on to the client.
|
||||
*/
|
||||
@Getter
|
||||
private final String value;
|
||||
|
||||
InteractiveTag(Void isNone) {
|
||||
this.value = "";
|
||||
}
|
||||
|
||||
InteractiveTag(String value) {
|
||||
this.value = "action.interact." + value;
|
||||
}
|
||||
|
||||
InteractiveTag() {
|
||||
this.value = "action.interact." + name().toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -70,8 +70,8 @@ public class AbstractArrowEntity extends Entity {
|
|||
super.setMotion(motion);
|
||||
|
||||
double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ());
|
||||
this.yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()));
|
||||
this.pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed));
|
||||
this.headYaw = yaw;
|
||||
setYaw((float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ())));
|
||||
setPitch((float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed)));
|
||||
setHeadYaw(getYaw());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.entity.type;
|
|||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.packet.AnimatePacket;
|
||||
|
@ -35,6 +36,8 @@ import lombok.Getter;
|
|||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -78,8 +81,8 @@ public class BoatEntity extends Entity {
|
|||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||
// We don't include the rotation (y) as it causes the boat to appear sideways
|
||||
setPosition(position.add(0d, this.definition.offset(), 0d));
|
||||
this.yaw = yaw + 90;
|
||||
this.headYaw = yaw + 90;
|
||||
setYaw(yaw + 90);
|
||||
setHeadYaw(yaw + 90);
|
||||
setOnGround(isOnGround);
|
||||
|
||||
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
|
||||
|
@ -158,6 +161,27 @@ public class BoatEntity extends Entity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InteractiveTag testInteraction(Hand hand) {
|
||||
if (session.isSneaking()) {
|
||||
return InteractiveTag.NONE;
|
||||
} else if (passengers.size() < 2) {
|
||||
return InteractiveTag.BOARD_BOAT;
|
||||
} else {
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
if (session.isSneaking()) {
|
||||
return InteractionResult.PASS;
|
||||
} else {
|
||||
// TODO: the client also checks for "out of control" ticks
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLeftPaddle(GeyserSession session, Entity rower) {
|
||||
if (isPaddlingLeft) {
|
||||
paddleTimeLeft += ROWING_SPEED;
|
||||
|
|
|
@ -25,10 +25,16 @@
|
|||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
|
||||
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -55,4 +61,30 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
|
|||
dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getCommandBlockRuntimeId());
|
||||
dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InteractiveTag testInteraction(Hand hand) {
|
||||
if (session.canUseCommandBlocks()) {
|
||||
return InteractiveTag.OPEN_CONTAINER;
|
||||
} else {
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
if (session.canUseCommandBlocks()) {
|
||||
// Client-side GUI required
|
||||
ContainerOpenPacket openPacket = new ContainerOpenPacket();
|
||||
openPacket.setBlockPosition(Vector3i.ZERO);
|
||||
openPacket.setId((byte) 1);
|
||||
openPacket.setType(ContainerType.COMMAND_BLOCK);
|
||||
openPacket.setUniqueEntityId(geyserId);
|
||||
session.sendUpstreamPacket(openPacket);
|
||||
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,15 +30,14 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
|||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
|
||||
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -48,6 +47,8 @@ import org.geysermc.geyser.entity.GeyserDirtyMetadata;
|
|||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -203,7 +204,7 @@ public class Entity {
|
|||
}
|
||||
|
||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
|
||||
moveRelative(relX, relY, relZ, yaw, pitch, this.headYaw, isOnGround);
|
||||
moveRelative(relX, relY, relZ, yaw, pitch, getHeadYaw(), isOnGround);
|
||||
}
|
||||
|
||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
|
||||
|
@ -224,7 +225,7 @@ public class Entity {
|
|||
}
|
||||
|
||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) {
|
||||
moveAbsolute(position, yaw, pitch, this.headYaw, isOnGround, teleported);
|
||||
moveAbsolute(position, yaw, pitch, getHeadYaw(), isOnGround, teleported);
|
||||
}
|
||||
|
||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||
|
@ -253,7 +254,8 @@ public class Entity {
|
|||
* @param isOnGround Whether the entity is currently on the ground.
|
||||
*/
|
||||
public void teleport(Vector3f position, float yaw, float pitch, boolean isOnGround) {
|
||||
moveAbsolute(position, yaw, pitch, isOnGround, false);
|
||||
// teleport will always set the headYaw to yaw
|
||||
moveAbsolute(position, yaw, pitch, yaw, isOnGround, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,7 +263,7 @@ public class Entity {
|
|||
* @param headYaw The new head rotation of the entity.
|
||||
*/
|
||||
public void updateHeadLookRotation(float headYaw) {
|
||||
moveRelative(0, 0, 0, headYaw, pitch, this.headYaw, onGround);
|
||||
moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -274,7 +276,7 @@ public class Entity {
|
|||
* @param isOnGround Whether the entity is currently on the ground.
|
||||
*/
|
||||
public void updatePositionAndRotation(double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) {
|
||||
moveRelative(moveX, moveY, moveZ, this.yaw, pitch, yaw, isOnGround);
|
||||
moveRelative(moveX, moveY, moveZ, yaw, pitch, getHeadYaw(), isOnGround);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -435,12 +437,12 @@ public class Entity {
|
|||
}
|
||||
|
||||
/**
|
||||
* x = Pitch, y = HeadYaw, z = Yaw
|
||||
* x = Pitch, y = Yaw, z = HeadYaw
|
||||
*
|
||||
* @return the bedrock rotation
|
||||
*/
|
||||
public Vector3f getBedrockRotation() {
|
||||
return Vector3f.from(pitch, headYaw, yaw);
|
||||
return Vector3f.from(getPitch(), getYaw(), getHeadYaw());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -467,12 +469,68 @@ public class Entity {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return this.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride")
|
||||
*/
|
||||
public final void updateInteractiveTag() {
|
||||
InteractiveTag tag = InteractiveTag.NONE;
|
||||
for (Hand hand: EntityUtils.HANDS) {
|
||||
tag = testInteraction(hand);
|
||||
if (tag != InteractiveTag.NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, tag.getValue());
|
||||
session.getPlayerEntity().updateBedrockMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test interacting with the given hand to see if we should send a tag to the Bedrock client.
|
||||
* Should usually mirror {@link #interact(Hand)} without any side effects.
|
||||
*/
|
||||
protected InteractiveTag testInteraction(Hand hand) {
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates interacting with an entity. The code here should mirror Java Edition code to the best of its ability,
|
||||
* to ensure packet parity as well as functionality parity (such as sound effect responses).
|
||||
*/
|
||||
public InteractionResult interact(Hand hand) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates interacting with this entity at a specific click point. As of Java Edition 1.18.1, this is only used for armor stands.
|
||||
*/
|
||||
public InteractionResult interactAt(Hand hand) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an entity event of the specified type to the Bedrock player from this entity.
|
||||
*/
|
||||
public final void playEntityEvent(EntityEventType type) {
|
||||
playEntityEvent(type, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an entity event of the specified type with the specified data to the Bedrock player from this entity.
|
||||
*/
|
||||
public final void playEntityEvent(EntityEventType type, int data) {
|
||||
EntityEventPacket packet = new EntityEventPacket();
|
||||
packet.setRuntimeEntityId(geyserId);
|
||||
packet.setType(type);
|
||||
packet.setData(data);
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <I extends Entity> I as(Class<I> entityClass) {
|
||||
return entityClass.isInstance(this) ? (I) this : null;
|
||||
}
|
||||
|
||||
public <I extends Entity> boolean is(Class<I> entityClass) {
|
||||
return entityClass.isInstance(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,6 @@ public class FireballEntity extends ThrowableEntity {
|
|||
|
||||
@Override
|
||||
public void tick() {
|
||||
moveAbsoluteImmediate(tickMovement(position), yaw, pitch, headYaw, false, false);
|
||||
moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ public class FishingHookEntity extends ThrowableEntity {
|
|||
float gravity = getGravity();
|
||||
motion = motion.down(gravity);
|
||||
|
||||
moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false);
|
||||
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
|
||||
|
||||
float drag = getDrag();
|
||||
motion = motion.mul(drag);
|
||||
|
@ -160,7 +160,7 @@ public class FishingHookEntity extends ThrowableEntity {
|
|||
|
||||
@Override
|
||||
protected float getGravity() {
|
||||
if (!isInWater() && !onGround) {
|
||||
if (!isInWater() && !isOnGround()) {
|
||||
return 0.03f;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -26,11 +26,13 @@
|
|||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -42,6 +44,7 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
|
|||
}
|
||||
|
||||
public void setHasFuel(BooleanEntityMetadata entityMetadata) {
|
||||
// Note: Java ticks this entity and gives it particles if it has fuel
|
||||
hasFuel = entityMetadata.getPrimitiveValue();
|
||||
updateDefaultBlockMetadata();
|
||||
}
|
||||
|
@ -51,4 +54,10 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
|
|||
dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID));
|
||||
dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
// Always works since you can "push" it this way
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,10 +76,10 @@ public class ItemEntity extends ThrowableEntity {
|
|||
if (isInWater()) {
|
||||
return;
|
||||
}
|
||||
if (!onGround || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {
|
||||
if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {
|
||||
float gravity = getGravity();
|
||||
motion = motion.down(gravity);
|
||||
moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false);
|
||||
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
|
||||
float drag = getDrag();
|
||||
motion = motion.mul(drag, 0.98f, drag);
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ public class ItemEntity extends ThrowableEntity {
|
|||
|
||||
@Override
|
||||
protected float getGravity() {
|
||||
if (getFlag(EntityFlag.HAS_GRAVITY) && !onGround && !isInWater()) {
|
||||
if (getFlag(EntityFlag.HAS_GRAVITY) && !isOnGround() && !isInWater()) {
|
||||
// Gravity can change if the item is in water/lava, but
|
||||
// the server calculates the motion & position for us
|
||||
return 0.04f;
|
||||
|
@ -134,7 +134,7 @@ public class ItemEntity extends ThrowableEntity {
|
|||
|
||||
@Override
|
||||
protected float getDrag() {
|
||||
if (onGround) {
|
||||
if (isOnGround()) {
|
||||
Vector3i groundBlockPos = position.toInt().down(1);
|
||||
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos);
|
||||
return BlockStateValues.getSlipperiness(blockState) * 0.98f;
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
|
|||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
|
@ -37,12 +38,13 @@ import com.nukkitx.nbt.NbtMapBuilder;
|
|||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -85,10 +87,8 @@ public class ItemFrameEntity extends Entity {
|
|||
.putInt("version", session.getBlockMappings().getBlockStateVersion());
|
||||
NbtMapBuilder statesBuilder = NbtMap.builder()
|
||||
.putInt("facing_direction", direction.ordinal())
|
||||
.putByte("item_frame_map_bit", (byte) 0);
|
||||
if (session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) {
|
||||
statesBuilder.putByte("item_frame_photo_bit", (byte) 0);
|
||||
}
|
||||
.putByte("item_frame_map_bit", (byte) 0)
|
||||
.putByte("item_frame_photo_bit", (byte) 0);
|
||||
blockBuilder.put("states", statesBuilder.build());
|
||||
|
||||
bedrockRuntimeId = session.getBlockMappings().getItemFrame(blockBuilder.build());
|
||||
|
@ -208,6 +208,11 @@ public class ItemFrameEntity extends Entity {
|
|||
changed = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
return InventoryUtils.isEmpty(heldItem) && session.getPlayerInventory().getItemInHand(hand).isEmpty() ? InteractionResult.PASS : InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the Java entity ID of an item frame from its Bedrock position.
|
||||
* @param position position of item frame in Bedrock.
|
||||
|
|
|
@ -25,9 +25,11 @@
|
|||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -38,4 +40,9 @@ public class LeashKnotEntity extends Entity {
|
|||
super(session, entityId, geyserId, uuid, definition, position.add(0.5f, 0.25f, 0.5f), motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
// Un-leashing the knot
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
|||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.AttributeData;
|
||||
|
@ -48,10 +51,12 @@ import lombok.Getter;
|
|||
import lombok.Setter;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.AttributeUtils;
|
||||
import org.geysermc.geyser.util.ChunkUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -94,13 +99,15 @@ public class LivingEntity extends Entity {
|
|||
public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) {
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
|
||||
// Blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like
|
||||
// you're "mining" with ex. a shield.
|
||||
boolean isUsingItem = (xd & 0x01) == 0x01;
|
||||
boolean isUsingOffhand = (xd & 0x02) == 0x02;
|
||||
|
||||
ItemMapping shield = session.getItemMappings().getStoredItems().shield();
|
||||
boolean isUsingShield = (getHand().getId() == shield.getBedrockId() ||
|
||||
getHand().equals(ItemData.AIR) && getOffHand().getId() == shield.getBedrockId());
|
||||
setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield);
|
||||
setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01);
|
||||
boolean isUsingShield = hasShield(isUsingOffhand, shield);
|
||||
|
||||
setFlag(EntityFlag.USING_ITEM, isUsingItem && !isUsingShield);
|
||||
// Override the blocking
|
||||
setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield);
|
||||
|
||||
// Riptide spin attack
|
||||
setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04);
|
||||
|
@ -137,6 +144,14 @@ public class LivingEntity extends Entity {
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) {
|
||||
if (offhand) {
|
||||
return offHand.getId() == shieldMapping.getBedrockId();
|
||||
} else {
|
||||
return hand.getId() == shieldMapping.getBedrockId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isShaking() {
|
||||
return isMaxFrozenState;
|
||||
|
@ -169,6 +184,36 @@ public class LivingEntity extends Entity {
|
|||
return new AttributeData(GeyserAttributeType.HEALTH.getBedrockIdentifier(), 0f, this.maxHealth, (float) Math.ceil(this.health), this.maxHealth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return this.valid && health > 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand);
|
||||
if (itemStack.getJavaId() == session.getItemMappings().getStoredItems().nameTag()) {
|
||||
InteractionResult result = checkInteractWithNameTag(itemStack);
|
||||
if (result.consumesAction()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return super.interact(hand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a nametag interaction would go through.
|
||||
*/
|
||||
protected final InteractionResult checkInteractWithNameTag(GeyserItemStack itemStack) {
|
||||
CompoundTag nbt = itemStack.getNbt();
|
||||
if (nbt != null && nbt.get("display") instanceof CompoundTag displayTag && displayTag.get("Name") instanceof StringTag) {
|
||||
// The mob shall be named
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
public void updateArmor(GeyserSession session) {
|
||||
if (!valid) return;
|
||||
|
||||
|
@ -255,7 +300,9 @@ public class LivingEntity extends Entity {
|
|||
if (javaAttribute.getType() instanceof AttributeType.Builtin type) {
|
||||
switch (type) {
|
||||
case GENERIC_MAX_HEALTH -> {
|
||||
this.maxHealth = (float) AttributeUtils.calculateValue(javaAttribute);
|
||||
// Since 1.18.0, setting the max health to 0 or below causes the entity to die on Bedrock but not on Java
|
||||
// See https://github.com/GeyserMC/Geyser/issues/2971
|
||||
this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f);
|
||||
newAttributes.add(createHealthAttribute());
|
||||
}
|
||||
case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE));
|
||||
|
|
|
@ -27,10 +27,14 @@ package org.geysermc.geyser.entity.type;
|
|||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -62,6 +66,41 @@ public class MinecartEntity extends Entity {
|
|||
@Override
|
||||
public Vector3f getBedrockRotation() {
|
||||
// Note: minecart rotation on rails does not care about the actual rotation value
|
||||
return Vector3f.from(0, yaw, 0);
|
||||
return Vector3f.from(0, getYaw(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InteractiveTag testInteraction(Hand hand) {
|
||||
if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) {
|
||||
return InteractiveTag.OPEN_CONTAINER;
|
||||
} else {
|
||||
if (session.isSneaking()) {
|
||||
return InteractiveTag.NONE;
|
||||
} else if (!passengers.isEmpty()) {
|
||||
// Can't enter if someone is inside
|
||||
return InteractiveTag.NONE;
|
||||
} else {
|
||||
// Attempt to enter
|
||||
return InteractiveTag.RIDE_MINECART;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) {
|
||||
// Opening the UI of this minecart
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
if (session.isSneaking()) {
|
||||
return InteractionResult.PASS;
|
||||
} else if (!passengers.isEmpty()) {
|
||||
// Can't enter if someone is inside
|
||||
return InteractionResult.PASS;
|
||||
} else {
|
||||
// Attempt to enter
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
*/
|
||||
@Override
|
||||
public void tick() {
|
||||
moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false);
|
||||
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
|
||||
float drag = getDrag();
|
||||
float gravity = getGravity();
|
||||
motion = motion.mul(drag).down(gravity);
|
||||
|
@ -89,20 +89,20 @@ public class ThrowableEntity extends Entity implements Tickable {
|
|||
}
|
||||
setPosition(position);
|
||||
|
||||
if (this.yaw != yaw) {
|
||||
if (getYaw() != yaw) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
|
||||
moveEntityDeltaPacket.setYaw(yaw);
|
||||
this.yaw = yaw;
|
||||
setYaw(yaw);
|
||||
}
|
||||
if (this.pitch != pitch) {
|
||||
if (getPitch() != pitch) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
|
||||
moveEntityDeltaPacket.setPitch(pitch);
|
||||
this.pitch = pitch;
|
||||
setPitch(pitch);
|
||||
}
|
||||
if (this.headYaw != headYaw) {
|
||||
if (getHeadYaw() != headYaw) {
|
||||
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
|
||||
moveEntityDeltaPacket.setHeadYaw(headYaw);
|
||||
this.headYaw = headYaw;
|
||||
setHeadYaw(headYaw);
|
||||
}
|
||||
|
||||
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
|
||||
|
|
|
@ -28,8 +28,12 @@ package org.geysermc.geyser.entity.type.living;
|
|||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AbstractFishEntity extends WaterEntity {
|
||||
|
@ -42,4 +46,14 @@ public class AbstractFishEntity extends WaterEntity {
|
|||
setFlag(EntityFlag.CAN_CLIMB, false);
|
||||
setFlag(EntityFlag.HAS_GRAVITY, false);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (EntityUtils.attemptToBucket(session, itemInHand)) {
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,4 +36,9 @@ public class AmbientEntity extends MobEntity {
|
|||
public AmbientEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ package org.geysermc.geyser.entity.type.living;
|
|||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Rotation;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
|
@ -39,6 +41,8 @@ import org.geysermc.geyser.entity.EntityDefinition;
|
|||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
@ -84,8 +88,6 @@ public class ArmorStandEntity extends LivingEntity {
|
|||
|
||||
@Override
|
||||
public void spawnEntity() {
|
||||
this.pitch = yaw;
|
||||
this.headYaw = yaw;
|
||||
super.spawnEntity();
|
||||
}
|
||||
|
||||
|
@ -136,7 +138,7 @@ public class ArmorStandEntity extends LivingEntity {
|
|||
}
|
||||
|
||||
isSmall = newIsSmall;
|
||||
if (!isMarker) {
|
||||
if (!isMarker && !isInvisible) { // Addition for isInvisible check caused by https://github.com/GeyserMC/Geyser/issues/2780
|
||||
toggleSmallStatus();
|
||||
}
|
||||
}
|
||||
|
@ -202,9 +204,9 @@ public class ArmorStandEntity extends LivingEntity {
|
|||
// Indicate that rotation should be checked
|
||||
setFlag(EntityFlag.BRIBED, true);
|
||||
|
||||
int rotationX = getRotation(rotation.getPitch());
|
||||
int rotationY = getRotation(rotation.getYaw());
|
||||
int rotationZ = getRotation(rotation.getRoll());
|
||||
int rotationX = MathUtils.wrapDegreesToInt(rotation.getPitch());
|
||||
int rotationY = MathUtils.wrapDegreesToInt(rotation.getYaw());
|
||||
int rotationZ = MathUtils.wrapDegreesToInt(rotation.getRoll());
|
||||
// The top bit acts like binary and determines if each rotation goes above 100
|
||||
// We don't do this for the negative values out of concerns of the number being too big
|
||||
int topBit = (Math.abs(rotationX) >= 100 ? 4 : 0) + (Math.abs(rotationY) >= 100 ? 2 : 0) + (Math.abs(rotationZ) >= 100 ? 1 : 0);
|
||||
|
@ -237,6 +239,16 @@ public class ArmorStandEntity extends LivingEntity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interactAt(Hand hand) {
|
||||
if (!isMarker && session.getPlayerInventory().getItemInHand(hand).getJavaId() != session.getItemMappings().getStoredItems().nameTag()) {
|
||||
// Java Edition returns SUCCESS if in spectator mode, but this is overrided with an earlier check on the client
|
||||
return InteractionResult.CONSUME;
|
||||
} else {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelmet(ItemData helmet) {
|
||||
super.setHelmet(helmet);
|
||||
|
@ -306,7 +318,7 @@ public class ArmorStandEntity extends LivingEntity {
|
|||
// Create the second entity. It doesn't need to worry about the items, but it does need to worry about
|
||||
// the metadata as it will hold the name tag.
|
||||
secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null,
|
||||
EntityDefinitions.ARMOR_STAND, position, motion, yaw, pitch, headYaw);
|
||||
EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw());
|
||||
secondEntity.primaryEntity = false;
|
||||
if (!this.positionRequiresOffset) {
|
||||
// Ensure the offset is applied for the 0 scale
|
||||
|
@ -362,17 +374,6 @@ public class ArmorStandEntity extends LivingEntity {
|
|||
}
|
||||
}
|
||||
|
||||
private int getRotation(float rotation) {
|
||||
rotation = rotation % 360f;
|
||||
if (rotation < -180f) {
|
||||
rotation += 360f;
|
||||
} else if (rotation >= 180f) {
|
||||
// 181 -> -179
|
||||
rotation = -(180 - (rotation - 180));
|
||||
}
|
||||
return (int) rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this armor stand is not a marker, set its bounding box size and scale.
|
||||
*/
|
||||
|
@ -426,9 +427,14 @@ public class ArmorStandEntity extends LivingEntity {
|
|||
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
|
||||
moveEntityPacket.setRuntimeEntityId(geyserId);
|
||||
moveEntityPacket.setPosition(position);
|
||||
moveEntityPacket.setRotation(Vector3f.from(yaw, yaw, yaw));
|
||||
moveEntityPacket.setOnGround(onGround);
|
||||
moveEntityPacket.setRotation(getBedrockRotation());
|
||||
moveEntityPacket.setOnGround(isOnGround());
|
||||
moveEntityPacket.setTeleported(false);
|
||||
session.sendUpstreamPacket(moveEntityPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f getBedrockRotation() {
|
||||
return Vector3f.from(getYaw(), getYaw(), getYaw());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DolphinEntity extends WaterEntity {
|
||||
public DolphinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) {
|
||||
return InteractiveTag.FEED;
|
||||
}
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) {
|
||||
// Feed
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
|
@ -29,8 +29,11 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class IronGolemEntity extends GolemEntity {
|
||||
|
@ -42,4 +45,18 @@ public class IronGolemEntity extends GolemEntity {
|
|||
// Required, or else the overlay is black
|
||||
dirtyMetadata.put(EntityData.COLOR_2, (byte) 0);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().ironIngot()) {
|
||||
if (health < maxHealth) {
|
||||
// Healing the iron golem
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,14 +26,21 @@
|
|||
package org.geysermc.geyser.entity.type.living;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.item.StoredItemMappings;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MobEntity extends LivingEntity {
|
||||
|
@ -62,4 +69,95 @@ public class MobEntity extends LivingEntity {
|
|||
this.leashHolderBedrockId = bedrockId;
|
||||
dirtyMetadata.put(EntityData.LEASH_HOLDER_EID, bedrockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final InteractiveTag testInteraction(Hand hand) {
|
||||
if (!isAlive()) {
|
||||
// dead lol
|
||||
return InteractiveTag.NONE;
|
||||
} else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) {
|
||||
return InteractiveTag.REMOVE_LEASH;
|
||||
} else {
|
||||
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand);
|
||||
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
|
||||
if (itemStack.getJavaId() == storedItems.lead() && canBeLeashed()) {
|
||||
// We shall leash
|
||||
return InteractiveTag.LEASH;
|
||||
} else if (itemStack.getJavaId() == storedItems.nameTag()) {
|
||||
InteractionResult result = checkInteractWithNameTag(itemStack);
|
||||
if (result.consumesAction()) {
|
||||
return InteractiveTag.NAME;
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveTag tag = testMobInteraction(itemStack);
|
||||
return tag != InteractiveTag.NONE ? tag : super.testInteraction(hand);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final InteractionResult interact(Hand hand) {
|
||||
if (!isAlive()) {
|
||||
// dead lol
|
||||
return InteractionResult.PASS;
|
||||
} else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) {
|
||||
// TODO looks like the client assumes it will go through and removes the attachment itself?
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(hand);
|
||||
InteractionResult result = checkPriorityInteractions(itemInHand);
|
||||
if (result.consumesAction()) {
|
||||
return result;
|
||||
} else {
|
||||
InteractionResult mobResult = mobInteract(itemInHand);
|
||||
return mobResult.consumesAction() ? mobResult : super.interact(hand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InteractionResult checkPriorityInteractions(GeyserItemStack itemInHand) {
|
||||
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
|
||||
if (itemInHand.getJavaId() == storedItems.lead() && canBeLeashed()) {
|
||||
// We shall leash
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (itemInHand.getJavaId() == storedItems.nameTag()) {
|
||||
InteractionResult result = checkInteractWithNameTag(itemInHand);
|
||||
if (result.consumesAction()) {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
ItemMapping mapping = itemInHand.getMapping(session);
|
||||
if (mapping.getJavaIdentifier().endsWith("_spawn_egg")) {
|
||||
// Using the spawn egg on this entity to create a child
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
}
|
||||
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
protected boolean canBeLeashed() {
|
||||
return isNotLeashed() && !isEnemy();
|
||||
}
|
||||
|
||||
protected final boolean isNotLeashed() {
|
||||
return leashHolderBedrockId == -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the entity is hostile. Used to determine if it can be leashed.
|
||||
*/
|
||||
protected boolean isEnemy() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,4 +42,9 @@ public class SlimeEntity extends MobEntity {
|
|||
public void setScale(IntEntityMetadata entityMetadata) {
|
||||
dirtyMetadata.put(EntityData.SCALE, 0.10f + entityMetadata.getPrimitiveValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,12 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEnti
|
|||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SnowGolemEntity extends GolemEntity {
|
||||
|
@ -44,4 +48,24 @@ public class SnowGolemEntity extends GolemEntity {
|
|||
// Handle the visibility of the pumpkin
|
||||
setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) {
|
||||
// Shearing the snow golem
|
||||
return InteractiveTag.SHEAR;
|
||||
}
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) {
|
||||
// Shearing the snow golem
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,12 @@ public class SquidEntity extends WaterEntity implements Tickable {
|
|||
|
||||
@Override
|
||||
public Vector3f getBedrockRotation() {
|
||||
return Vector3f.from(pitch, yaw, yaw);
|
||||
return Vector3f.from(getPitch(), getYaw(), getYaw());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
|
||||
private void checkInWater() {
|
||||
|
|
|
@ -36,4 +36,9 @@ public class WaterEntity extends CreatureEntity {
|
|||
public WaterEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,17 @@
|
|||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.living.AgeableEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AnimalEntity extends AgeableEntity {
|
||||
|
@ -39,6 +45,12 @@ public class AnimalEntity extends AgeableEntity {
|
|||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
public final boolean canEat(GeyserItemStack itemStack) {
|
||||
ItemMapping mapping = itemStack.getMapping(session);
|
||||
String handIdentifier = mapping.getJavaIdentifier();
|
||||
return canEat(handIdentifier.replace("minecraft:", ""), mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param javaIdentifierStripped the stripped Java identifier of the item that is potential breeding food. For example,
|
||||
* <code>wheat</code>.
|
||||
|
@ -48,4 +60,28 @@ public class AnimalEntity extends AgeableEntity {
|
|||
// This is what it defaults to. OK.
|
||||
return javaIdentifierStripped.equals("wheat");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (canEat(itemInHand)) {
|
||||
return InteractiveTag.FEED;
|
||||
}
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (canEat(itemInHand)) {
|
||||
// FEED
|
||||
if (getFlag(EntityFlag.BABY)) {
|
||||
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
}
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,13 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AxolotlEntity extends AnimalEntity {
|
||||
|
@ -56,11 +60,26 @@ public class AxolotlEntity extends AnimalEntity {
|
|||
|
||||
@Override
|
||||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return javaIdentifierStripped.equals("tropical_fish_bucket");
|
||||
return session.getTagCache().isAxolotlTemptItem(mapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMaxAir() {
|
||||
return 6000;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (EntityUtils.attemptToBucket(session, itemInHand)) {
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CowEntity extends AnimalEntity {
|
||||
public CowEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
|
||||
return InteractiveTag.MILK;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
|
||||
session.playSoundEvent(SoundEvent.MILK, position);
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
|
@ -28,17 +28,20 @@ package org.geysermc.geyser.entity.type.living.animal;
|
|||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import lombok.Getter;
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GoatEntity extends AnimalEntity {
|
||||
private static final float LONG_JUMPING_HEIGHT = 1.3f * 0.7f;
|
||||
private static final float LONG_JUMPING_WIDTH = 0.9f * 0.7f;
|
||||
|
||||
@Getter
|
||||
private boolean isScreamer;
|
||||
|
||||
public GoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
|
@ -59,4 +62,15 @@ public class GoatEntity extends AnimalEntity {
|
|||
super.setDimensions(pose);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!getFlag(EntityFlag.BABY) && itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
|
||||
session.playSoundEvent(isScreamer ? SoundEvent.MILK_SCREAMER : SoundEvent.MILK, position);
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,4 +56,14 @@ public class HoglinEntity extends AnimalEntity {
|
|||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return javaIdentifierStripped.equals("crimson_fungus");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,62 @@
|
|||
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.item.StoredItemMappings;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MooshroomEntity extends AnimalEntity {
|
||||
private boolean isBrown = false;
|
||||
|
||||
public MooshroomEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
public void setVariant(ObjectEntityMetadata<String> entityMetadata) {
|
||||
isBrown = entityMetadata.getValue().equals("brown");
|
||||
dirtyMetadata.put(EntityData.VARIANT, isBrown ? 1 : 0);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
|
||||
if (!isBaby()) {
|
||||
if (itemInHand.getJavaId() == storedItems.bowl()) {
|
||||
// Stew
|
||||
return InteractiveTag.MOOSHROOM_MILK_STEW;
|
||||
} else if (isAlive() && itemInHand.getJavaId() == storedItems.shears()) {
|
||||
// Shear items
|
||||
return InteractiveTag.MOOSHROOM_SHEAR;
|
||||
}
|
||||
}
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
|
||||
boolean isBaby = isBaby();
|
||||
if (!isBaby && itemInHand.getJavaId() == storedItems.bowl()) {
|
||||
// Stew
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (!isBaby && isAlive() && itemInHand.getJavaId() == storedItems.shears()) {
|
||||
// Shear items
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (isBrown && session.getTagCache().isSmallFlower(itemInHand) && itemInHand.getMapping(session).isHasSuspiciousStewEffect()) {
|
||||
// ?
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,15 @@
|
|||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class OcelotEntity extends AnimalEntity {
|
||||
|
@ -42,4 +47,26 @@ public class OcelotEntity extends AnimalEntity {
|
|||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) {
|
||||
// Attempt to feed
|
||||
return InteractiveTag.FEED;
|
||||
} else {
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) {
|
||||
// Attempt to feed
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,14 +33,19 @@ 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.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PandaEntity extends AnimalEntity {
|
||||
private int mainGene;
|
||||
private int hiddenGene;
|
||||
private Gene mainGene = Gene.NORMAL;
|
||||
private Gene hiddenGene = Gene.NORMAL;
|
||||
|
||||
public PandaEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
|
@ -61,12 +66,12 @@ public class PandaEntity extends AnimalEntity {
|
|||
}
|
||||
|
||||
public void setMainGene(ByteEntityMetadata entityMetadata) {
|
||||
mainGene = entityMetadata.getPrimitiveValue();
|
||||
mainGene = Gene.fromId(entityMetadata.getPrimitiveValue());
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
public void setHiddenGene(ByteEntityMetadata entityMetadata) {
|
||||
hiddenGene = entityMetadata.getPrimitiveValue();
|
||||
hiddenGene = Gene.fromId(entityMetadata.getPrimitiveValue());
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
|
@ -86,23 +91,81 @@ public class PandaEntity extends AnimalEntity {
|
|||
return javaIdentifierStripped.equals("bamboo");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (mainGene == Gene.WORRIED && session.isThunder()) {
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (mainGene == Gene.WORRIED && session.isThunder()) {
|
||||
// Huh!
|
||||
return InteractionResult.PASS;
|
||||
} else if (getFlag(EntityFlag.LAYING_DOWN)) {
|
||||
// Stop the panda from laying down
|
||||
// TODO laying up is client-side?
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (canEat(itemInHand)) {
|
||||
if (getFlag(EntityFlag.BABY)) {
|
||||
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private void updateAppearance() {
|
||||
if (mainGene == 4 || mainGene == 5) {
|
||||
// Main gene is a recessive trait
|
||||
if (mainGene.isRecessive) {
|
||||
if (mainGene == hiddenGene) {
|
||||
// Main and hidden genes match; this is what the panda looks like.
|
||||
dirtyMetadata.put(EntityData.VARIANT, mainGene);
|
||||
dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal());
|
||||
} else {
|
||||
// Genes have no effect on appearance
|
||||
dirtyMetadata.put(EntityData.VARIANT, 0);
|
||||
dirtyMetadata.put(EntityData.VARIANT, Gene.NORMAL.ordinal());
|
||||
}
|
||||
} else {
|
||||
// No need to worry about hidden gene
|
||||
dirtyMetadata.put(EntityData.VARIANT, mainGene);
|
||||
dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal());
|
||||
}
|
||||
}
|
||||
|
||||
enum Gene {
|
||||
NORMAL(false),
|
||||
LAZY(false),
|
||||
WORRIED(false),
|
||||
PLAYFUL(false),
|
||||
BROWN(true),
|
||||
WEAK(true),
|
||||
AGGRESSIVE(false);
|
||||
|
||||
private static final Gene[] VALUES = values();
|
||||
|
||||
private final boolean isRecessive;
|
||||
|
||||
Gene(boolean isRecessive) {
|
||||
this.isRecessive = isRecessive;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Gene fromId(int id) {
|
||||
if (id < 0 || id >= VALUES.length) {
|
||||
return NORMAL;
|
||||
}
|
||||
return VALUES[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,16 @@
|
|||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PigEntity extends AnimalEntity {
|
||||
|
@ -42,4 +48,37 @@ public class PigEntity extends AnimalEntity {
|
|||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
|
||||
// Mount
|
||||
return InteractiveTag.MOUNT;
|
||||
} else {
|
||||
InteractiveTag superTag = super.testMobInteraction(itemInHand);
|
||||
if (superTag != InteractiveTag.NONE) {
|
||||
return superTag;
|
||||
} else {
|
||||
return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction()
|
||||
? InteractiveTag.SADDLE : InteractiveTag.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
|
||||
// Mount
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
InteractionResult superResult = super.mobInteract(itemInHand);
|
||||
if (superResult.consumesAction()) {
|
||||
return superResult;
|
||||
} else {
|
||||
return EntityUtils.attemptToSaddle(session, this, itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,19 +30,69 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.geyser.util.ItemUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SheepEntity extends AnimalEntity {
|
||||
private int color;
|
||||
|
||||
public SheepEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
public void setSheepFlags(ByteEntityMetadata entityMetadata) {
|
||||
byte xd = ((ByteEntityMetadata) entityMetadata).getPrimitiveValue();
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10);
|
||||
dirtyMetadata.put(EntityData.COLOR, xd);
|
||||
color = xd & 15;
|
||||
dirtyMetadata.put(EntityData.COLOR, (byte) color);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) {
|
||||
return InteractiveTag.SHEAR;
|
||||
} else {
|
||||
InteractiveTag tag = super.testMobInteraction(itemInHand);
|
||||
if (tag != InteractiveTag.NONE) {
|
||||
return tag;
|
||||
} else {
|
||||
int color = ItemUtils.dyeColorFor(itemInHand.getJavaId());
|
||||
if (canDye(color)) {
|
||||
return InteractiveTag.DYE;
|
||||
}
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) {
|
||||
return InteractionResult.CONSUME;
|
||||
} else {
|
||||
InteractionResult superResult = super.mobInteract(itemInHand);
|
||||
if (superResult.consumesAction()) {
|
||||
return superResult;
|
||||
} else {
|
||||
int color = ItemUtils.dyeColorFor(itemInHand.getJavaId());
|
||||
if (canDye(color)) {
|
||||
// Dyeing the sheep
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canDye(int color) {
|
||||
return color != -1 && color != this.color && !getFlag(EntityFlag.SHEARED);
|
||||
}
|
||||
}
|
|
@ -30,9 +30,14 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class StriderEntity extends AnimalEntity {
|
||||
|
@ -90,4 +95,37 @@ public class StriderEntity extends AnimalEntity {
|
|||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return javaIdentifierStripped.equals("warped_fungus");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
|
||||
// Mount Strider
|
||||
return InteractiveTag.RIDE_STRIDER;
|
||||
} else {
|
||||
InteractiveTag tag = super.testMobInteraction(itemInHand);
|
||||
if (tag != InteractiveTag.NONE) {
|
||||
return tag;
|
||||
} else {
|
||||
return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction()
|
||||
? InteractiveTag.SADDLE : InteractiveTag.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
|
||||
// Mount Strider
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
InteractionResult superResult = super.mobInteract(itemInHand);
|
||||
if (superResult.consumesAction()) {
|
||||
return superResult;
|
||||
} else {
|
||||
return EntityUtils.attemptToSaddle(session, this, itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,4 +52,9 @@ public class TurtleEntity extends AnimalEntity {
|
|||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return javaIdentifierStripped.equals("seagrass");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,13 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
|
|||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -122,4 +126,164 @@ public class AbstractHorseEntity extends AnimalEntity {
|
|||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
return testHorseInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected final InteractiveTag testHorseInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
boolean isBaby = isBaby();
|
||||
if (!isBaby) {
|
||||
if (getFlag(EntityFlag.TAMED) && session.isSneaking()) {
|
||||
return InteractiveTag.OPEN_CONTAINER;
|
||||
}
|
||||
|
||||
if (!passengers.isEmpty()) {
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
}
|
||||
|
||||
if (!itemInHand.isEmpty()) {
|
||||
if (canEat(itemInHand)) {
|
||||
return InteractiveTag.FEED;
|
||||
}
|
||||
|
||||
if (testSaddle(itemInHand)) {
|
||||
return InteractiveTag.SADDLE;
|
||||
}
|
||||
|
||||
if (!getFlag(EntityFlag.TAMED)) {
|
||||
// Horse will become mad
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
|
||||
if (testForChest(itemInHand)) {
|
||||
return InteractiveTag.ATTACH_CHEST;
|
||||
}
|
||||
|
||||
if (additionalTestForInventoryOpen(itemInHand) || !isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle()) {
|
||||
// Will open the inventory to be saddled
|
||||
return InteractiveTag.OPEN_CONTAINER;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBaby) {
|
||||
return super.testMobInteraction(itemInHand);
|
||||
} else {
|
||||
return InteractiveTag.MOUNT;
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
return mobHorseInteract(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected final InteractionResult mobHorseInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
boolean isBaby = isBaby();
|
||||
if (!isBaby) {
|
||||
if (getFlag(EntityFlag.TAMED) && session.isSneaking()) {
|
||||
// Will open the inventory
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
if (!passengers.isEmpty()) {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
|
||||
if (!itemInHand.isEmpty()) {
|
||||
if (canEat(itemInHand)) {
|
||||
if (isBaby) {
|
||||
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
|
||||
}
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
|
||||
if (testSaddle(itemInHand)) {
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
if (!getFlag(EntityFlag.TAMED)) {
|
||||
// Horse will become mad
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
if (testForChest(itemInHand)) {
|
||||
// TODO looks like chest is also handled client side
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
// Note: yes, this code triggers for llamas too. lol (as of Java Edition 1.18.1)
|
||||
if (additionalTestForInventoryOpen(itemInHand) || (!isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle())) {
|
||||
// Will open the inventory to be saddled
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBaby) {
|
||||
return super.mobInteract(itemInHand);
|
||||
} else {
|
||||
// Attempt to mount
|
||||
// TODO client-set flags sitting standing?
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) {
|
||||
return isAlive() && !getFlag(EntityFlag.BABY) && getFlag(EntityFlag.TAMED);
|
||||
}
|
||||
|
||||
protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) {
|
||||
return itemInHand.getMapping(session).getJavaIdentifier().endsWith("_horse_armor");
|
||||
}
|
||||
|
||||
/* Just a place to stuff common code for the undead variants without having duplicate code */
|
||||
|
||||
protected final InteractiveTag testUndeadHorseInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!getFlag(EntityFlag.TAMED)) {
|
||||
return InteractiveTag.NONE;
|
||||
} else if (isBaby()) {
|
||||
return testHorseInteraction(itemInHand);
|
||||
} else if (session.isSneaking()) {
|
||||
return InteractiveTag.OPEN_CONTAINER;
|
||||
} else if (!passengers.isEmpty()) {
|
||||
return testHorseInteraction(itemInHand);
|
||||
} else {
|
||||
if (session.getItemMappings().getStoredItems().saddle() == itemInHand.getJavaId()) {
|
||||
return InteractiveTag.OPEN_CONTAINER;
|
||||
}
|
||||
|
||||
if (testSaddle(itemInHand)) {
|
||||
return InteractiveTag.SADDLE;
|
||||
}
|
||||
|
||||
return InteractiveTag.RIDE_HORSE;
|
||||
}
|
||||
}
|
||||
|
||||
protected final InteractionResult undeadHorseInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (!getFlag(EntityFlag.TAMED)) {
|
||||
return InteractionResult.PASS;
|
||||
} else if (isBaby()) {
|
||||
return mobHorseInteract(itemInHand);
|
||||
} else if (session.isSneaking()) {
|
||||
// Opens inventory
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (!passengers.isEmpty()) {
|
||||
return mobHorseInteract(itemInHand);
|
||||
} else {
|
||||
// The client tests for saddle but it doesn't matter for us at this point.
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,12 @@
|
|||
package org.geysermc.geyser.entity.type.living.animal.horse;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChestedHorseEntity extends AbstractHorseEntity {
|
||||
|
@ -41,4 +44,21 @@ public class ChestedHorseEntity extends AbstractHorseEntity {
|
|||
protected int getContainerBaseSize() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) {
|
||||
// Not checked here
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) {
|
||||
return itemInHand.getJavaId() == session.getItemMappings().getStoredItems().chest() && !getFlag(EntityFlag.CHESTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) {
|
||||
// Armor won't work on these
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
|||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal.horse;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SkeletonHorseEntity extends AbstractHorseEntity {
|
||||
public SkeletonHorseEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
return testUndeadHorseInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
return undeadHorseInteract(itemInHand);
|
||||
}
|
||||
}
|
|
@ -23,29 +23,32 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.java;
|
||||
package org.geysermc.geyser.entity.type.living.animal.horse;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.UnlockRecipesAction;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundRecipePacket;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import java.util.Arrays;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage)
|
||||
*/
|
||||
@Translator(packet = ClientboundRecipePacket.class)
|
||||
public class JavaRecipeTranslator extends PacketTranslator<ClientboundRecipePacket> {
|
||||
public class ZombieHorseEntity extends AbstractHorseEntity {
|
||||
public ZombieHorseEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundRecipePacket packet) {
|
||||
if (packet.getAction() == UnlockRecipesAction.REMOVE) {
|
||||
session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes()));
|
||||
} else {
|
||||
session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes()));
|
||||
}
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
return testUndeadHorseInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
return undeadHorseInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
|
|
@ -32,9 +32,13 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CatEntity extends TameableEntity {
|
||||
|
@ -98,4 +102,28 @@ public class CatEntity extends TameableEntity {
|
|||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
boolean tamed = getFlag(EntityFlag.TAMED);
|
||||
if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
|
||||
// Toggle sitting
|
||||
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
|
||||
} else {
|
||||
return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractiveTag.NONE : InteractiveTag.FEED;
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
boolean tamed = getFlag(EntityFlag.TAMED);
|
||||
if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
// Attempt to feed
|
||||
return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractionResult.PASS : InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,15 @@
|
|||
package org.geysermc.geyser.entity.type.living.animal.tameable;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ParrotEntity extends TameableEntity {
|
||||
|
@ -40,6 +45,46 @@ public class ParrotEntity extends TameableEntity {
|
|||
|
||||
@Override
|
||||
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
|
||||
return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie");
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isTameFood(String javaIdentifierStripped) {
|
||||
return javaIdentifierStripped.contains("seeds");
|
||||
}
|
||||
|
||||
private boolean isPoisonousFood(String javaIdentifierStripped) {
|
||||
return javaIdentifierStripped.equals("cookie");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", "");
|
||||
boolean tame = getFlag(EntityFlag.TAMED);
|
||||
if (!tame && isTameFood(javaIdentifierStripped)) {
|
||||
return InteractiveTag.FEED;
|
||||
} else if (isPoisonousFood(javaIdentifierStripped)) {
|
||||
return InteractiveTag.FEED;
|
||||
} else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
|
||||
// Sitting/standing
|
||||
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
|
||||
}
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", "");
|
||||
boolean tame = getFlag(EntityFlag.TAMED);
|
||||
if (!tame && isTameFood(javaIdentifierStripped)) {
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (isPoisonousFood(javaIdentifierStripped)) {
|
||||
return InteractionResult.SUCCESS;
|
||||
} else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
|
||||
// Sitting/standing
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,17 +61,30 @@ public class TameableEntity extends AnimalEntity {
|
|||
// Note: Must be set for wolf collar color to work
|
||||
if (entityMetadata.getValue().isPresent()) {
|
||||
// Owner UUID of entity
|
||||
Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get());
|
||||
UUID uuid = entityMetadata.getValue().get();
|
||||
Entity entity;
|
||||
if (uuid.equals(session.getPlayerEntity().getUuid())) {
|
||||
entity = session.getPlayerEntity();
|
||||
} else {
|
||||
entity = session.getEntityCache().getPlayerEntity(uuid);
|
||||
}
|
||||
// Used as both a check since the player isn't in the entity cache and a normal fallback
|
||||
if (entity == null) {
|
||||
entity = session.getPlayerEntity();
|
||||
// Set to tame, but indicate that we are not the player that owns this
|
||||
ownerBedrockId = Long.MAX_VALUE;
|
||||
} else {
|
||||
// Translate to entity ID
|
||||
ownerBedrockId = entity.getGeyserId();
|
||||
}
|
||||
// Translate to entity ID
|
||||
ownerBedrockId = entity.getGeyserId();
|
||||
} else {
|
||||
// Reset
|
||||
ownerBedrockId = 0L;
|
||||
}
|
||||
dirtyMetadata.put(EntityData.OWNER_EID, ownerBedrockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,9 +32,14 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
import org.geysermc.geyser.util.ItemUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -90,4 +95,45 @@ public class WolfEntity extends TameableEntity {
|
|||
// Cannot be a baby to eat these foods
|
||||
return WOLF_FOODS.contains(javaIdentifierStripped) && !isBaby();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return !getFlag(EntityFlag.ANGRY) && super.canBeLeashed();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (getFlag(EntityFlag.ANGRY)) {
|
||||
return InteractiveTag.NONE;
|
||||
}
|
||||
if (itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.TAMED)) {
|
||||
// Bone and untamed - can tame
|
||||
return InteractiveTag.TAME;
|
||||
} else {
|
||||
int color = ItemUtils.dyeColorFor(itemInHand.getJavaId());
|
||||
if (color != -1) {
|
||||
// If this fails, as of Java Edition 1.18.1, you cannot toggle sit/stand
|
||||
if (color != this.collarColor) {
|
||||
return InteractiveTag.DYE;
|
||||
}
|
||||
} else if (getFlag(EntityFlag.TAMED) && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
|
||||
// Tamed and owned by player - can sit/stand
|
||||
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
|
||||
}
|
||||
}
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (ownerBedrockId == session.getPlayerEntity().getGeyserId() || getFlag(EntityFlag.TAMED)
|
||||
|| itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.ANGRY)) {
|
||||
// Sitting toggle or feeding; not angry
|
||||
return InteractionResult.CONSUME;
|
||||
} else {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,16 @@
|
|||
package org.geysermc.geyser.entity.type.living.merchant;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.living.AgeableEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AbstractMerchantEntity extends AgeableEntity {
|
||||
|
@ -37,4 +43,37 @@ public class AbstractMerchantEntity extends AgeableEntity {
|
|||
public AbstractMerchantEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier();
|
||||
if (!javaIdentifier.equals("minecraft:villager_spawn_egg")
|
||||
&& (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING) && ((VillagerEntity) this).isCanTradeWith())) {
|
||||
// An additional check we know cannot work
|
||||
if (!isBaby()) {
|
||||
return InteractiveTag.TRADE;
|
||||
}
|
||||
}
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier();
|
||||
if (!javaIdentifier.equals("minecraft:villager_spawn_egg")
|
||||
&& (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING))
|
||||
&& (definition != EntityDefinitions.WANDERING_TRADER || !getFlag(EntityFlag.BABY))) {
|
||||
// Trading time
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,52 +33,49 @@ import com.nukkitx.math.vector.Vector3i;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class VillagerEntity extends AbstractMerchantEntity {
|
||||
|
||||
/**
|
||||
* A map of Java profession IDs to Bedrock IDs
|
||||
*/
|
||||
public static final Int2IntMap VILLAGER_PROFESSIONS = new Int2IntOpenHashMap();
|
||||
private static final int[] VILLAGER_PROFESSIONS = new int[15];
|
||||
/**
|
||||
* A map of all Java region IDs (plains, savanna...) to Bedrock
|
||||
*/
|
||||
public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap();
|
||||
private static final int[] VILLAGER_REGIONS = new int[7];
|
||||
|
||||
static {
|
||||
// Java villager profession IDs -> Bedrock
|
||||
VILLAGER_PROFESSIONS.put(0, 0);
|
||||
VILLAGER_PROFESSIONS.put(1, 8);
|
||||
VILLAGER_PROFESSIONS.put(2, 11);
|
||||
VILLAGER_PROFESSIONS.put(3, 6);
|
||||
VILLAGER_PROFESSIONS.put(4, 7);
|
||||
VILLAGER_PROFESSIONS.put(5, 1);
|
||||
VILLAGER_PROFESSIONS.put(6, 2);
|
||||
VILLAGER_PROFESSIONS.put(7, 4);
|
||||
VILLAGER_PROFESSIONS.put(8, 12);
|
||||
VILLAGER_PROFESSIONS.put(9, 5);
|
||||
VILLAGER_PROFESSIONS.put(10, 13);
|
||||
VILLAGER_PROFESSIONS.put(11, 14);
|
||||
VILLAGER_PROFESSIONS.put(12, 3);
|
||||
VILLAGER_PROFESSIONS.put(13, 10);
|
||||
VILLAGER_PROFESSIONS.put(14, 9);
|
||||
VILLAGER_PROFESSIONS[0] = 0;
|
||||
VILLAGER_PROFESSIONS[1] = 8;
|
||||
VILLAGER_PROFESSIONS[2] = 11;
|
||||
VILLAGER_PROFESSIONS[3] = 6;
|
||||
VILLAGER_PROFESSIONS[4] = 7;
|
||||
VILLAGER_PROFESSIONS[5] = 1;
|
||||
VILLAGER_PROFESSIONS[6] = 2;
|
||||
VILLAGER_PROFESSIONS[7] = 4;
|
||||
VILLAGER_PROFESSIONS[8] = 12;
|
||||
VILLAGER_PROFESSIONS[9] = 5;
|
||||
VILLAGER_PROFESSIONS[10] = 13;
|
||||
VILLAGER_PROFESSIONS[11] = 14;
|
||||
VILLAGER_PROFESSIONS[12] = 3;
|
||||
VILLAGER_PROFESSIONS[13] = 10;
|
||||
VILLAGER_PROFESSIONS[14] = 9;
|
||||
|
||||
VILLAGER_REGIONS.put(0, 1);
|
||||
VILLAGER_REGIONS.put(1, 2);
|
||||
VILLAGER_REGIONS.put(2, 0);
|
||||
VILLAGER_REGIONS.put(3, 3);
|
||||
VILLAGER_REGIONS.put(4, 4);
|
||||
VILLAGER_REGIONS.put(5, 5);
|
||||
VILLAGER_REGIONS.put(6, 6);
|
||||
VILLAGER_REGIONS[0] = 1;
|
||||
VILLAGER_REGIONS[1] = 2;
|
||||
VILLAGER_REGIONS[2] = 0;
|
||||
VILLAGER_REGIONS[3] = 3;
|
||||
VILLAGER_REGIONS[4] = 4;
|
||||
VILLAGER_REGIONS[5] = 5;
|
||||
VILLAGER_REGIONS[6] = 6;
|
||||
}
|
||||
|
||||
private Vector3i bedPosition;
|
||||
|
@ -95,12 +92,12 @@ public class VillagerEntity extends AbstractMerchantEntity {
|
|||
public void setVillagerData(EntityMetadata<VillagerData, ?> entityMetadata) {
|
||||
VillagerData villagerData = entityMetadata.getValue();
|
||||
// Profession
|
||||
int profession = VILLAGER_PROFESSIONS.get(villagerData.getProfession());
|
||||
int profession = getBedrockProfession(villagerData.getProfession());
|
||||
canTradeWith = profession != 14 && profession != 0; // Not a notwit and not professionless
|
||||
dirtyMetadata.put(EntityData.VARIANT, profession);
|
||||
//metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason?
|
||||
// Region
|
||||
dirtyMetadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType()));
|
||||
dirtyMetadata.put(EntityData.MARK_VARIANT, getBedrockRegion(villagerData.getType()));
|
||||
// Trade tier - different indexing in Bedrock
|
||||
dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1);
|
||||
}
|
||||
|
@ -158,4 +155,12 @@ public class VillagerEntity extends AbstractMerchantEntity {
|
|||
moveEntityPacket.setTeleported(false);
|
||||
session.sendUpstreamPacket(moveEntityPacket);
|
||||
}
|
||||
|
||||
public static int getBedrockProfession(int javaProfession) {
|
||||
return javaProfession >= 0 && javaProfession < VILLAGER_PROFESSIONS.length ? VILLAGER_PROFESSIONS[javaProfession] : 0;
|
||||
}
|
||||
|
||||
public static int getBedrockRegion(int javaRegion) {
|
||||
return javaRegion >= 0 && javaRegion < VILLAGER_REGIONS.length ? VILLAGER_REGIONS[javaRegion] : 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,10 +28,15 @@ package org.geysermc.geyser.entity.type.living.monster;
|
|||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CreeperEntity extends MonsterEntity {
|
||||
|
@ -55,4 +60,26 @@ public class CreeperEntity extends MonsterEntity {
|
|||
ignitedByFlintAndSteel = entityMetadata.getPrimitiveValue();
|
||||
setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) {
|
||||
return InteractiveTag.IGNITE_CREEPER;
|
||||
} else {
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) {
|
||||
// Ignite creeper
|
||||
session.playSoundEvent(SoundEvent.IGNITE, position);
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.geysermc.geyser.entity.type.living.MobEntity;
|
|||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.DimensionUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
@ -130,7 +131,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
|||
|
||||
for (int i = 0; i < segmentHistory.length; i++) {
|
||||
segmentHistory[i] = new Segment();
|
||||
segmentHistory[i].yaw = headYaw;
|
||||
segmentHistory[i].yaw = getHeadYaw();
|
||||
segmentHistory[i].y = position.getY();
|
||||
}
|
||||
}
|
||||
|
@ -150,6 +151,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
|||
return super.despawnEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
effectTick();
|
||||
|
@ -163,7 +169,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
|||
* Updates the positions of the Ender Dragon's multiple bounding boxes
|
||||
*/
|
||||
private void updateBoundingBoxes() {
|
||||
Vector3f facingDir = Vector3f.createDirectionDeg(0, headYaw);
|
||||
Vector3f facingDir = Vector3f.createDirectionDeg(0, getHeadYaw());
|
||||
Segment baseSegment = getSegment(5);
|
||||
// Used to angle the head, neck, and tail when the dragon flies up/down
|
||||
float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY()));
|
||||
|
@ -182,7 +188,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
|||
neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck));
|
||||
body.setPosition(facingDir.mul(0.5f, 0f, -0.5f));
|
||||
|
||||
Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - headYaw).mul(4.5f).up(2f);
|
||||
Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - getHeadYaw()).mul(4.5f).up(2f);
|
||||
rightWing.setPosition(wingPos);
|
||||
leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally
|
||||
|
||||
|
@ -191,7 +197,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
|||
float distance = (i + 1) * 2f;
|
||||
// Curls the tail when the dragon turns
|
||||
Segment targetSegment = getSegment(12 + 2 * i);
|
||||
float angle = headYaw + targetSegment.yaw - baseSegment.yaw;
|
||||
float angle = getHeadYaw() + targetSegment.yaw - baseSegment.yaw;
|
||||
|
||||
float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f;
|
||||
tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset));
|
||||
|
@ -257,6 +263,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
|||
spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
|
||||
spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f));
|
||||
spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire");
|
||||
spawnParticleEffectPacket.setMolangVariablesJson(Optional.empty());
|
||||
session.sendUpstreamPacket(spawnParticleEffectPacket);
|
||||
}
|
||||
}
|
||||
|
@ -288,10 +295,6 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
|||
session.sendUpstreamPacket(playSoundPacket);
|
||||
}
|
||||
|
||||
private boolean isAlive() {
|
||||
return health > 0;
|
||||
}
|
||||
|
||||
private boolean isHovering() {
|
||||
return phase == 10;
|
||||
}
|
||||
|
@ -305,7 +308,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
|||
*/
|
||||
private void pushSegment() {
|
||||
latestSegment = (latestSegment + 1) % segmentHistory.length;
|
||||
segmentHistory[latestSegment].yaw = headYaw;
|
||||
segmentHistory[latestSegment].yaw = getHeadYaw();
|
||||
segmentHistory[latestSegment].y = position.getY();
|
||||
}
|
||||
|
||||
|
|
|
@ -44,4 +44,9 @@ public class GhastEntity extends FlyingEntity {
|
|||
// If the ghast is attacking
|
||||
dirtyMetadata.put(EntityData.CHARGE_AMOUNT, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,4 +37,9 @@ public class MonsterEntity extends CreatureEntity {
|
|||
public MonsterEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,4 +48,9 @@ public class PhantomEntity extends FlyingEntity {
|
|||
setBoundingBoxHeight(boundsScale * definition.height());
|
||||
dirtyMetadata.put(EntityData.SCALE, modelScale);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,12 @@ import com.nukkitx.math.vector.Vector3f;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PiglinEntity extends BasePiglinEntity {
|
||||
|
@ -64,4 +68,30 @@ public class PiglinEntity extends BasePiglinEntity {
|
|||
|
||||
super.updateOffHand(session);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
InteractiveTag tag = super.testMobInteraction(itemInHand);
|
||||
if (tag != InteractiveTag.NONE) {
|
||||
return tag;
|
||||
} else {
|
||||
return canGiveGoldTo(itemInHand) ? InteractiveTag.BARTER : InteractiveTag.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
InteractionResult superResult = super.mobInteract(itemInHand);
|
||||
if (superResult.consumesAction()) {
|
||||
return superResult;
|
||||
} else {
|
||||
return canGiveGoldTo(itemInHand) ? InteractionResult.SUCCESS : InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canGiveGoldTo(@Nonnull GeyserItemStack itemInHand) {
|
||||
return !getFlag(EntityFlag.BABY) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldIngot() && !getFlag(EntityFlag.ADMIRING);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,4 +65,9 @@ public class ShulkerEntity extends GolemEntity {
|
|||
dirtyMetadata.put(EntityData.VARIANT, Math.abs(color - 15));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,4 +55,14 @@ public class ZoglinEntity extends MonsterEntity {
|
|||
float scale = getFlag(EntityFlag.BABY) ? 0.55f : 1f;
|
||||
return scale * definition.height();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,33 +33,56 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
|||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InteractiveTag;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ZombieVillagerEntity extends ZombieEntity {
|
||||
private boolean isTransforming;
|
||||
|
||||
public ZombieVillagerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
public void setTransforming(BooleanEntityMetadata entityMetadata) {
|
||||
isTransforming = entityMetadata.getPrimitiveValue();
|
||||
setFlag(EntityFlag.IS_TRANSFORMING, isTransforming);
|
||||
setFlag(EntityFlag.IS_TRANSFORMING, entityMetadata.getPrimitiveValue());
|
||||
setFlag(EntityFlag.SHAKING, isShaking());
|
||||
}
|
||||
|
||||
public void setZombieVillagerData(EntityMetadata<VillagerData, ?> entityMetadata) {
|
||||
VillagerData villagerData = entityMetadata.getValue();
|
||||
dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.VILLAGER_PROFESSIONS.get(villagerData.getProfession())); // Actually works properly with the OptionalPack
|
||||
dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType()));
|
||||
dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.getBedrockProfession(villagerData.getProfession())); // Actually works properly with the OptionalPack
|
||||
dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.getBedrockRegion(villagerData.getType()));
|
||||
// Used with the OptionalPack
|
||||
dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isShaking() {
|
||||
return isTransforming || super.isShaking();
|
||||
return getFlag(EntityFlag.IS_TRANSFORMING) || super.isShaking();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) {
|
||||
return InteractiveTag.CURE;
|
||||
} else {
|
||||
return super.testMobInteraction(itemInHand);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) {
|
||||
// The client doesn't know if the entity has weakness as that's not usually sent over the network
|
||||
return InteractionResult.CONSUME;
|
||||
} else {
|
||||
return super.mobInteract(itemInHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
package org.geysermc.geyser.entity.type.player;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
|
@ -38,6 +37,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
|||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.AttributeData;
|
||||
import com.nukkitx.protocol.bedrock.data.GameType;
|
||||
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
||||
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
|
@ -61,15 +61,21 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
|
|||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter @Setter
|
||||
public class PlayerEntity extends LivingEntity {
|
||||
public static final float SNEAKING_POSE_HEIGHT = 1.5f;
|
||||
|
||||
private GameProfile profile;
|
||||
private String username;
|
||||
private boolean playerList = true; // Player is in the player list
|
||||
private boolean playerList = true; // Player is in the player list
|
||||
|
||||
/**
|
||||
* The textures property from the GameProfile.
|
||||
*/
|
||||
@Nullable
|
||||
private String texturesProperty;
|
||||
|
||||
private Vector3i bedPosition;
|
||||
|
||||
|
@ -82,11 +88,12 @@ public class PlayerEntity extends LivingEntity {
|
|||
*/
|
||||
private ParrotEntity rightParrot;
|
||||
|
||||
public PlayerEntity(GeyserSession session, int entityId, long geyserId, GameProfile gameProfile, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, gameProfile.getId(), EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);
|
||||
public PlayerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, Vector3f position,
|
||||
Vector3f motion, float yaw, float pitch, float headYaw, String username, @Nullable String texturesProperty) {
|
||||
super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);
|
||||
|
||||
profile = gameProfile;
|
||||
username = gameProfile.getName();
|
||||
this.username = username;
|
||||
this.texturesProperty = texturesProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,6 +127,7 @@ public class PlayerEntity extends LivingEntity {
|
|||
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
|
||||
addPlayerPacket.setDeviceId("");
|
||||
addPlayerPacket.setPlatformChatId("");
|
||||
addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO
|
||||
addPlayerPacket.getMetadata().putFlags(flags);
|
||||
dirtyMetadata.apply(addPlayerPacket.getMetadata());
|
||||
|
||||
|
@ -205,7 +213,7 @@ public class PlayerEntity extends LivingEntity {
|
|||
|
||||
@Override
|
||||
public void updateHeadLookRotation(float headYaw) {
|
||||
moveRelative(0, 0, 0, yaw, pitch, headYaw, onGround);
|
||||
moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround());
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(geyserId);
|
||||
movePlayerPacket.setPosition(position);
|
||||
|
@ -225,9 +233,11 @@ public class PlayerEntity extends LivingEntity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRotation(float yaw, float pitch, boolean isOnGround) {
|
||||
super.updateRotation(yaw, pitch, isOnGround);
|
||||
public void updateRotation(float yaw, float pitch, float headYaw, boolean isOnGround) {
|
||||
// the method below is called by super.updateRotation(yaw, pitch, isOnGround).
|
||||
// but we have to be able to set the headYaw, so we call the method below directly.
|
||||
super.moveRelative(0, 0, 0, yaw, pitch, headYaw, isOnGround);
|
||||
|
||||
// Both packets need to be sent or else player head rotation isn't correctly updated
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(geyserId);
|
||||
|
@ -244,6 +254,11 @@ public class PlayerEntity extends LivingEntity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRotation(float yaw, float pitch, boolean isOnGround) {
|
||||
updateRotation(yaw, pitch, getHeadYaw(), isOnGround);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPosition(Vector3f position) {
|
||||
super.setPosition(position.add(0, definition.offset(), 0));
|
||||
|
@ -292,7 +307,7 @@ public class PlayerEntity extends LivingEntity {
|
|||
}
|
||||
// The parrot is a separate entity in Bedrock, but part of the player entity in Java //TODO is a UUID provided in NBT?
|
||||
ParrotEntity parrot = new ParrotEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(),
|
||||
null, EntityDefinitions.PARROT, position, motion, yaw, pitch, headYaw);
|
||||
null, EntityDefinitions.PARROT, position, motion, getYaw(), getPitch(), getHeadYaw());
|
||||
parrot.spawnEntity();
|
||||
parrot.getDirtyMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue());
|
||||
// Different position whether the parrot is left or right
|
||||
|
@ -382,15 +397,26 @@ public class PlayerEntity extends LivingEntity {
|
|||
@Override
|
||||
protected void setDimensions(Pose pose) {
|
||||
float height;
|
||||
float width;
|
||||
switch (pose) {
|
||||
case SNEAKING -> height = SNEAKING_POSE_HEIGHT;
|
||||
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> height = 0.6f;
|
||||
case SNEAKING -> {
|
||||
height = SNEAKING_POSE_HEIGHT;
|
||||
width = definition.width();
|
||||
}
|
||||
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> {
|
||||
height = 0.6f;
|
||||
width = definition.width();
|
||||
}
|
||||
case DYING -> {
|
||||
height = 0.2f;
|
||||
width = 0.2f;
|
||||
}
|
||||
default -> {
|
||||
super.setDimensions(pose);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setBoundingBoxWidth(definition.width());
|
||||
setBoundingBoxWidth(width);
|
||||
setBoundingBoxHeight(height);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
package org.geysermc.geyser.entity.type.player;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||
|
@ -38,6 +37,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
|
|||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.AttributeUtils;
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class SessionPlayerEntity extends PlayerEntity {
|
|||
private int fakeTradeXp;
|
||||
|
||||
public SessionPlayerEntity(GeyserSession session) {
|
||||
super(session, -1, 1, new GameProfile(UUID.randomUUID(), "unknown"), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0);
|
||||
super(session, -1, 1, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "unknown", null);
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
@ -134,6 +134,10 @@ public class SessionPlayerEntity extends PlayerEntity {
|
|||
return maxHealth;
|
||||
}
|
||||
|
||||
public float getHealth() {
|
||||
return this.health;
|
||||
}
|
||||
|
||||
public void setHealth(float health) {
|
||||
this.health = health;
|
||||
}
|
||||
|
@ -167,6 +171,16 @@ public class SessionPlayerEntity extends PlayerEntity {
|
|||
return super.createHealthAttribute();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) {
|
||||
// Must be overridden to point to the player's inventory cache
|
||||
if (offhand) {
|
||||
return session.getPlayerInventory().getOffhand().getJavaId() == shieldMapping.getJavaId();
|
||||
} else {
|
||||
return session.getPlayerInventory().getItemInHand().getJavaId() == shieldMapping.getJavaId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata() {
|
||||
super.updateBedrockMetadata();
|
||||
|
|
|
@ -25,32 +25,29 @@
|
|||
|
||||
package org.geysermc.geyser.entity.type.player;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.GameType;
|
||||
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
||||
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.SkullCache;
|
||||
import org.geysermc.geyser.skin.SkullSkinManager;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no
|
||||
* custom player skulls in Bedrock.
|
||||
*/
|
||||
public class SkullPlayerEntity extends PlayerEntity {
|
||||
/**
|
||||
* Stores the block state that the skull is associated with. Used to determine if the block in the skull's position
|
||||
* has changed
|
||||
*/
|
||||
@Getter
|
||||
private final int blockState;
|
||||
|
||||
public SkullPlayerEntity(GeyserSession session, long geyserId, GameProfile gameProfile, Vector3f position, float rotation, int blockState) {
|
||||
super(session, 0, geyserId, gameProfile, position, Vector3f.ZERO, rotation, 0, rotation);
|
||||
this.blockState = blockState;
|
||||
public SkullPlayerEntity(GeyserSession session, long geyserId) {
|
||||
super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null);
|
||||
setPlayerList(false);
|
||||
}
|
||||
|
||||
|
@ -83,6 +80,7 @@ public class SkullPlayerEntity extends PlayerEntity {
|
|||
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
|
||||
addPlayerPacket.setDeviceId("");
|
||||
addPlayerPacket.setPlatformChatId("");
|
||||
addPlayerPacket.setGameType(GameType.SURVIVAL);
|
||||
addPlayerPacket.getMetadata().putFlags(flags);
|
||||
dirtyMetadata.apply(addPlayerPacket.getMetadata());
|
||||
|
||||
|
@ -92,8 +90,57 @@ public class SkullPlayerEntity extends PlayerEntity {
|
|||
session.sendUpstreamPacket(addPlayerPacket);
|
||||
}
|
||||
|
||||
public void despawnEntity(Vector3i position) {
|
||||
this.despawnEntity();
|
||||
session.getSkullCache().remove(position, this);
|
||||
/**
|
||||
* Hide the player entity so that it can be reused for a different skull.
|
||||
*/
|
||||
public void free() {
|
||||
setFlag(EntityFlag.INVISIBLE, true);
|
||||
updateBedrockMetadata();
|
||||
|
||||
// Move skull entity out of the way
|
||||
moveAbsolute(session.getPlayerEntity().getPosition().up(128), 0, 0, 0, false, true);
|
||||
}
|
||||
|
||||
public void updateSkull(SkullCache.Skull skull) {
|
||||
if (!skull.getTexturesProperty().equals(getTexturesProperty())) {
|
||||
// Make skull invisible as we change skins
|
||||
setFlag(EntityFlag.INVISIBLE, true);
|
||||
updateBedrockMetadata();
|
||||
|
||||
setTexturesProperty(skull.getTexturesProperty());
|
||||
|
||||
SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> {
|
||||
// Delay to minimize split-second "player" pop-in
|
||||
setFlag(EntityFlag.INVISIBLE, false);
|
||||
updateBedrockMetadata();
|
||||
}, 250, TimeUnit.MILLISECONDS)));
|
||||
} else {
|
||||
// Just a rotation/position change
|
||||
setFlag(EntityFlag.INVISIBLE, false);
|
||||
updateBedrockMetadata();
|
||||
}
|
||||
|
||||
float x = skull.getPosition().getX() + .5f;
|
||||
float y = skull.getPosition().getY() - .01f;
|
||||
float z = skull.getPosition().getZ() + .5f;
|
||||
float rotation;
|
||||
|
||||
int blockState = skull.getBlockState();
|
||||
byte floorRotation = BlockStateValues.getSkullRotation(blockState);
|
||||
if (floorRotation == -1) {
|
||||
// Wall skull
|
||||
y += 0.25f;
|
||||
rotation = BlockStateValues.getSkullWallDirections().get(blockState);
|
||||
switch ((int) rotation) {
|
||||
case 180 -> z += 0.24f; // North
|
||||
case 0 -> z -= 0.24f; // South
|
||||
case 90 -> x += 0.24f; // West
|
||||
case 270 -> x -= 0.24f; // East
|
||||
}
|
||||
} else {
|
||||
rotation = (180f + (floorRotation * 22.5f)) % 360;
|
||||
}
|
||||
|
||||
moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,14 @@
|
|||
package org.geysermc.geyser.inventory;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.ItemUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Used to determine if rename packets should be sent and stores
|
||||
|
@ -48,6 +54,7 @@ public class AnvilContainer extends Container {
|
|||
/**
|
||||
* The new name of the item as received from Bedrock
|
||||
*/
|
||||
@Nullable
|
||||
private String newName = null;
|
||||
|
||||
private GeyserItemStack lastInput = GeyserItemStack.EMPTY;
|
||||
|
@ -59,6 +66,36 @@ public class AnvilContainer extends Container {
|
|||
super(title, id, size, containerType, playerInventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name to use instead for renaming.
|
||||
*/
|
||||
public String checkForRename(GeyserSession session, String rename) {
|
||||
String correctRename;
|
||||
newName = rename;
|
||||
|
||||
String originalName = ItemUtils.getCustomName(getInput().getNbt());
|
||||
|
||||
String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.getLocale());
|
||||
String plainNewName = MessageTranslator.convertToPlainText(rename, session.getLocale());
|
||||
if (!plainOriginalName.equals(plainNewName)) {
|
||||
// Strip out formatting since Java Edition does not allow it
|
||||
correctRename = plainNewName;
|
||||
// Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now
|
||||
ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainNewName);
|
||||
session.sendDownstreamPacket(renameItemPacket);
|
||||
} else {
|
||||
// Restore formatting for item since we're not renaming
|
||||
correctRename = MessageTranslator.convertMessageLenient(originalName);
|
||||
// Java Edition sends the original custom name when not renaming,
|
||||
// if there isn't a custom name an empty string is sent
|
||||
ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainOriginalName);
|
||||
session.sendDownstreamPacket(renameItemPacket);
|
||||
}
|
||||
|
||||
useJavaLevelCost = false;
|
||||
return correctRename;
|
||||
}
|
||||
|
||||
public GeyserItemStack getInput() {
|
||||
return getItem(0);
|
||||
}
|
||||
|
|
|
@ -27,11 +27,12 @@ package org.geysermc.geyser.inventory;
|
|||
|
||||
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Combination of {@link Inventory} and {@link PlayerInventory}
|
||||
*/
|
||||
|
@ -66,7 +67,7 @@ public class Container extends Inventory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
|
||||
public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession session) {
|
||||
if (slot < this.size) {
|
||||
super.setItem(slot, newItem, session);
|
||||
} else {
|
||||
|
|
|
@ -31,18 +31,19 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
|||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ToString
|
||||
public abstract class Inventory {
|
||||
|
||||
@Getter
|
||||
protected final int id;
|
||||
|
||||
|
@ -70,8 +71,7 @@ public abstract class Inventory {
|
|||
protected final ContainerType containerType;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
protected String title;
|
||||
protected final String title;
|
||||
|
||||
protected final GeyserItemStack[] items;
|
||||
|
||||
|
@ -113,7 +113,7 @@ public abstract class Inventory {
|
|||
|
||||
public abstract int getOffsetForHotbar(@Range(from = 0, to = 8) int slot);
|
||||
|
||||
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
|
||||
public void setItem(int slot, @Nonnull GeyserItemStack newItem, GeyserSession session) {
|
||||
if (slot > this.size) {
|
||||
session.getGeyser().getLogger().debug("Tried to set an item out of bounds! " + this);
|
||||
return;
|
||||
|
@ -136,7 +136,9 @@ public abstract class Inventory {
|
|||
|
||||
protected void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) {
|
||||
if (!newItem.isEmpty()) {
|
||||
if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) {
|
||||
ItemMapping oldMapping = ItemTranslator.getBedrockItemMapping(session, oldItem);
|
||||
ItemMapping newMapping = ItemTranslator.getBedrockItemMapping(session, newItem);
|
||||
if (oldMapping.getBedrockId() == newMapping.getBedrockId()) {
|
||||
newItem.setNetId(oldItem.getNetId());
|
||||
} else {
|
||||
newItem.setNetId(session.getNextItemNetId());
|
||||
|
|
|
@ -31,15 +31,27 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.Cli
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class MerchantContainer extends Container {
|
||||
@Getter @Setter
|
||||
private Entity villager;
|
||||
@Setter
|
||||
private VillagerTrade[] villagerTrades;
|
||||
@Getter @Setter
|
||||
private ClientboundMerchantOffersPacket pendingOffersPacket;
|
||||
|
||||
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
}
|
||||
|
||||
public void onTradeSelected(GeyserSession session, int slot) {
|
||||
if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) {
|
||||
VillagerTrade trade = villagerTrades[slot];
|
||||
setItem(2, GeyserItemStack.from(trade.getOutput()), session);
|
||||
// TODO this logic doesn't add up
|
||||
session.getPlayerEntity().addFakeTradeExperience(trade.getXp());
|
||||
session.getPlayerEntity().updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.inventory;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
@ -61,6 +62,10 @@ public class PlayerInventory extends Inventory {
|
|||
cursor = newCursor;
|
||||
}
|
||||
|
||||
public GeyserItemStack getItemInHand(@Nonnull Hand hand) {
|
||||
return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand();
|
||||
}
|
||||
|
||||
public GeyserItemStack getItemInHand() {
|
||||
if (36 + heldItemSlot > this.size) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!");
|
||||
|
|
|
@ -40,20 +40,22 @@ import org.geysermc.geyser.inventory.SlotType;
|
|||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.CraftingInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
public class ClickPlan {
|
||||
public final class ClickPlan {
|
||||
private final List<ClickAction> plan = new ArrayList<>();
|
||||
private final Int2ObjectMap<GeyserItemStack> simulatedItems;
|
||||
/**
|
||||
* Used for 1.17.1+ proper packet translation - any non-cursor item that is changed in a single transaction gets sent here.
|
||||
*/
|
||||
private Int2ObjectMap<ItemStack> changedItems;
|
||||
private GeyserItemStack simulatedCursor;
|
||||
private boolean simulating;
|
||||
private boolean finished;
|
||||
|
||||
private final GeyserSession session;
|
||||
private final InventoryTranslator translator;
|
||||
|
@ -66,16 +68,11 @@ public class ClickPlan {
|
|||
this.inventory = inventory;
|
||||
|
||||
this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize());
|
||||
this.changedItems = null;
|
||||
this.simulatedCursor = session.getPlayerInventory().getCursor().copy();
|
||||
this.simulating = true;
|
||||
this.finished = false;
|
||||
|
||||
if (translator instanceof PlayerInventoryTranslator) {
|
||||
gridSize = 4;
|
||||
} else if (translator instanceof CraftingInventoryTranslator) {
|
||||
gridSize = 9;
|
||||
} else {
|
||||
gridSize = -1;
|
||||
}
|
||||
gridSize = translator.getGridSize();
|
||||
}
|
||||
|
||||
private void resetSimulation() {
|
||||
|
@ -88,7 +85,7 @@ public class ClickPlan {
|
|||
}
|
||||
|
||||
public void add(Click click, int slot, boolean force) {
|
||||
if (!simulating)
|
||||
if (finished)
|
||||
throw new UnsupportedOperationException("ClickPlan already executed");
|
||||
|
||||
if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) {
|
||||
|
@ -97,6 +94,8 @@ public class ClickPlan {
|
|||
|
||||
ClickAction action = new ClickAction(click, slot, force);
|
||||
plan.add(action);
|
||||
// RUNNING THE SIMULATION HERE IS IMPORTANT. The contents of the simulation are used in complex, multi-stage tasks
|
||||
// such as autocrafting.
|
||||
simulateAction(action);
|
||||
}
|
||||
|
||||
|
@ -112,33 +111,48 @@ public class ClickPlan {
|
|||
refresh = true;
|
||||
}
|
||||
|
||||
//int stateId = stateIdHack(action);
|
||||
changedItems = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
//simulateAction(action);
|
||||
boolean emulatePost1_16Logic = session.isEmulatePost1_16Logic();
|
||||
|
||||
int stateId;
|
||||
if (emulatePost1_16Logic) {
|
||||
stateId = stateIdHack(action);
|
||||
simulateAction(action);
|
||||
} else {
|
||||
stateId = inventory.getStateId();
|
||||
}
|
||||
|
||||
ItemStack clickedItemStack;
|
||||
if (!planIter.hasNext() && refresh) {
|
||||
clickedItemStack = InventoryUtils.REFRESH_ITEM;
|
||||
} else if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) {
|
||||
clickedItemStack = null;
|
||||
} else {
|
||||
//// The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1)
|
||||
//clickedItemStack = simulatedCursor.getItemStack(); TODO fix - this is the proper behavior but it terribly breaks 1.16.5
|
||||
clickedItemStack = getItem(action.slot).getItemStack();
|
||||
if (emulatePost1_16Logic) {
|
||||
// The action must be simulated first as Java expects the new contents of the cursor (as of 1.18.1)
|
||||
clickedItemStack = simulatedCursor.getItemStack();
|
||||
} else {
|
||||
if (action.click.actionType == ContainerActionType.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) {
|
||||
clickedItemStack = null;
|
||||
} else {
|
||||
clickedItemStack = getItem(action.slot).getItemStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!emulatePost1_16Logic) {
|
||||
simulateAction(action);
|
||||
}
|
||||
|
||||
ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket(
|
||||
inventory.getId(),
|
||||
inventory.getStateId(),
|
||||
stateId,
|
||||
action.slot,
|
||||
action.click.actionType,
|
||||
action.click.action,
|
||||
clickedItemStack,
|
||||
Collections.emptyMap() // Anything else we change, at this time, should have a packet sent to address
|
||||
changedItems
|
||||
);
|
||||
|
||||
simulateAction(action);
|
||||
|
||||
session.sendDownstreamPacket(clickPacket);
|
||||
}
|
||||
|
||||
|
@ -146,19 +160,11 @@ public class ClickPlan {
|
|||
for (Int2ObjectMap.Entry<GeyserItemStack> simulatedSlot : simulatedItems.int2ObjectEntrySet()) {
|
||||
inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session);
|
||||
}
|
||||
simulating = false;
|
||||
finished = true;
|
||||
}
|
||||
|
||||
public GeyserItemStack getItem(int slot) {
|
||||
return getItem(slot, true);
|
||||
}
|
||||
|
||||
public GeyserItemStack getItem(int slot, boolean generate) {
|
||||
if (generate) {
|
||||
return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
|
||||
} else {
|
||||
return simulatedItems.getOrDefault(slot, inventory.getItem(slot));
|
||||
}
|
||||
return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
|
||||
}
|
||||
|
||||
public GeyserItemStack getCursor() {
|
||||
|
@ -166,23 +172,40 @@ public class ClickPlan {
|
|||
}
|
||||
|
||||
private void setItem(int slot, GeyserItemStack item) {
|
||||
if (simulating) {
|
||||
simulatedItems.put(slot, item);
|
||||
} else {
|
||||
inventory.setItem(slot, item, session);
|
||||
}
|
||||
simulatedItems.put(slot, item);
|
||||
onSlotItemChange(slot, item);
|
||||
}
|
||||
|
||||
private void setCursor(GeyserItemStack item) {
|
||||
if (simulating) {
|
||||
simulatedCursor = item;
|
||||
} else {
|
||||
session.getPlayerInventory().setCursor(item, session);
|
||||
simulatedCursor = item;
|
||||
}
|
||||
|
||||
private void add(int slot, GeyserItemStack itemStack, int amount) {
|
||||
itemStack.add(amount);
|
||||
onSlotItemChange(slot, itemStack);
|
||||
}
|
||||
|
||||
private void sub(int slot, GeyserItemStack itemStack, int amount) {
|
||||
itemStack.sub(amount);
|
||||
onSlotItemChange(slot, itemStack);
|
||||
}
|
||||
|
||||
private void setAmount(int slot, GeyserItemStack itemStack, int amount) {
|
||||
itemStack.setAmount(amount);
|
||||
onSlotItemChange(slot, itemStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not need to be called for the cursor
|
||||
*/
|
||||
private void onSlotItemChange(int slot, GeyserItemStack itemStack) {
|
||||
if (changedItems != null) {
|
||||
changedItems.put(slot, itemStack.getItemStack());
|
||||
}
|
||||
}
|
||||
|
||||
private void simulateAction(ClickAction action) {
|
||||
GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor();
|
||||
GeyserItemStack cursor = getCursor();
|
||||
switch (action.click) {
|
||||
case LEFT_OUTSIDE -> {
|
||||
setCursor(GeyserItemStack.EMPTY);
|
||||
|
@ -196,7 +219,7 @@ public class ClickPlan {
|
|||
}
|
||||
}
|
||||
|
||||
GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot);
|
||||
GeyserItemStack clicked = getItem(action.slot);
|
||||
if (translator.getSlotType(action.slot) == SlotType.OUTPUT) {
|
||||
switch (action.click) {
|
||||
case LEFT, RIGHT -> {
|
||||
|
@ -206,6 +229,7 @@ public class ClickPlan {
|
|||
cursor.add(clicked.getAmount());
|
||||
}
|
||||
reduceCraftingGrid(false);
|
||||
setItem(action.slot, GeyserItemStack.EMPTY); // Matches Java Edition 1.18.1
|
||||
}
|
||||
case LEFT_SHIFT -> reduceCraftingGrid(true);
|
||||
}
|
||||
|
@ -217,20 +241,20 @@ public class ClickPlan {
|
|||
setItem(action.slot, cursor);
|
||||
} else {
|
||||
setCursor(GeyserItemStack.EMPTY);
|
||||
clicked.add(cursor.getAmount());
|
||||
add(action.slot, clicked, cursor.getAmount());
|
||||
}
|
||||
break;
|
||||
case RIGHT:
|
||||
if (cursor.isEmpty() && !clicked.isEmpty()) {
|
||||
int half = clicked.getAmount() / 2; //smaller half
|
||||
setCursor(clicked.copy(clicked.getAmount() - half)); //larger half
|
||||
clicked.setAmount(half);
|
||||
setAmount(action.slot, clicked, half);
|
||||
} else if (!cursor.isEmpty() && clicked.isEmpty()) {
|
||||
cursor.sub(1);
|
||||
setItem(action.slot, cursor.copy(1));
|
||||
} else if (InventoryUtils.canStack(cursor, clicked)) {
|
||||
cursor.sub(1);
|
||||
clicked.add(1);
|
||||
add(action.slot, clicked, 1);
|
||||
}
|
||||
break;
|
||||
case SWAP_TO_HOTBAR_1:
|
||||
|
@ -265,7 +289,7 @@ public class ClickPlan {
|
|||
break;
|
||||
case DROP_ONE:
|
||||
if (!clicked.isEmpty()) {
|
||||
clicked.sub(1);
|
||||
sub(action.slot, clicked, 1);
|
||||
}
|
||||
break;
|
||||
case DROP_ALL:
|
||||
|
@ -279,7 +303,7 @@ public class ClickPlan {
|
|||
* Swap between two inventory slots without a cursor. This should only be used with {@link ContainerActionType#MOVE_TO_HOTBAR_SLOT}
|
||||
*/
|
||||
private void swap(int sourceSlot, int destSlot, GeyserItemStack sourceItem) {
|
||||
GeyserItemStack destinationItem = simulating ? getItem(destSlot) : inventory.getItem(destSlot);
|
||||
GeyserItemStack destinationItem = getItem(destSlot);
|
||||
setItem(sourceSlot, destinationItem);
|
||||
setItem(destSlot, sourceItem);
|
||||
}
|
||||
|
@ -292,63 +316,44 @@ public class ClickPlan {
|
|||
stateId = inventory.getStateId();
|
||||
}
|
||||
|
||||
// This is a hack.
|
||||
// Java will never ever send more than one container click packet per set of actions.
|
||||
// Java will never ever send more than one container click packet per set of actions*.
|
||||
// *(exception being Java's "quick craft"/painting feature)
|
||||
// Bedrock might, and this would generally fall into one of two categories:
|
||||
// - Bedrock is sending an item directly from one slot to another, without picking it up, that cannot
|
||||
// be expressed with a shift click
|
||||
// - Bedrock wants to pick up or place an arbitrary amount of items that cannot be expressed from
|
||||
// one left/right click action.
|
||||
// When Bedrock does one of these actions and sends multiple packets, a 1.17.1+ server will
|
||||
// increment the state ID on each confirmation packet it sends back (I.E. set slot). Then when it
|
||||
// reads our next packet, because we kept the same state ID but the server incremented it, it'll be
|
||||
// desynced and send the entire inventory contents back at us.
|
||||
// This hack therefore increments the state ID to what the server will presumably send back to us.
|
||||
// (This won't be perfect, but should get us through most vanilla situations, and if this is wrong the
|
||||
// server will just send a set content packet back at us)
|
||||
// Java typically doesn't increment the state ID if you send a vanilla-accurate container click packet,
|
||||
// but it will increment the state ID with a vanilla client in at least the crafting table
|
||||
if (inventory.getContainerType() == ContainerType.CRAFTING && CraftingInventoryTranslator.isCraftingGrid(action.slot)) {
|
||||
// 1.18.1 sends a second set slot update for any action in the crafting grid
|
||||
// And an additional packet if something is removed (Mojmap: CraftingContainer#removeItem)
|
||||
//TODO this code kind of really sucks; it's potentially possible to see what Bedrock sends us and send a PlaceRecipePacket
|
||||
int stateIdIncrements;
|
||||
GeyserItemStack clicked = getItem(action.slot);
|
||||
if (action.click == Click.LEFT) {
|
||||
if (!clicked.isEmpty() && !InventoryUtils.canStack(simulatedCursor, clicked)) {
|
||||
// An item is removed from the crafting table; yes deletion
|
||||
stateIdIncrements = 3;
|
||||
stateIdIncrements = 2;
|
||||
} else {
|
||||
// We can stack and we add all the items to the crafting slot; no deletion
|
||||
stateIdIncrements = 2;
|
||||
stateIdIncrements = 1;
|
||||
}
|
||||
} else if (action.click == Click.RIGHT) {
|
||||
if (simulatedCursor.isEmpty() && !clicked.isEmpty()) {
|
||||
// Items are taken; yes deletion
|
||||
stateIdIncrements = 3;
|
||||
} else if ((!simulatedCursor.isEmpty() && clicked.isEmpty()) || InventoryUtils.canStack(simulatedCursor, clicked)) {
|
||||
// Adding our cursor item to the slot; no deletion
|
||||
stateIdIncrements = 2;
|
||||
} else {
|
||||
// ?? nothing I guess
|
||||
stateIdIncrements = 2;
|
||||
}
|
||||
stateIdIncrements = 1;
|
||||
} else if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) {
|
||||
stateIdIncrements = 1;
|
||||
} else {
|
||||
if (session.getGeyser().getConfig().isDebugMode()) {
|
||||
session.getGeyser().getLogger().debug("Not sure how to handle state ID hack in crafting table: " + plan);
|
||||
}
|
||||
stateIdIncrements = 2;
|
||||
stateIdIncrements = 1;
|
||||
}
|
||||
inventory.incrementStateId(stateIdIncrements);
|
||||
} else if (action.click.action instanceof MoveToHotbarAction) {
|
||||
// Two slot changes sent
|
||||
inventory.incrementStateId(2);
|
||||
} else {
|
||||
inventory.incrementStateId(1);
|
||||
}
|
||||
|
||||
return stateId;
|
||||
}
|
||||
|
||||
//TODO
|
||||
private void reduceCraftingGrid(boolean makeAll) {
|
||||
if (gridSize == -1)
|
||||
return;
|
||||
|
@ -370,9 +375,12 @@ public class ClickPlan {
|
|||
}
|
||||
|
||||
for (int i = 0; i < gridSize; i++) {
|
||||
GeyserItemStack item = getItem(i + 1);
|
||||
if (!item.isEmpty())
|
||||
item.sub(crafted);
|
||||
final int slot = i + 1;
|
||||
GeyserItemStack item = getItem(slot);
|
||||
if (!item.isEmpty()) {
|
||||
// These changes should be broadcasted to the server
|
||||
sub(slot, item, crafted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -383,8 +391,12 @@ public class ClickPlan {
|
|||
public IntSet getAffectedSlots() {
|
||||
IntSet affectedSlots = new IntOpenHashSet();
|
||||
for (ClickAction action : plan) {
|
||||
if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) {
|
||||
if (translator.getSlotType(action.slot) != SlotType.OUTPUT && action.slot != Click.OUTSIDE_SLOT) {
|
||||
affectedSlots.add(action.slot);
|
||||
if (action.click.actionType == ContainerActionType.MOVE_TO_HOTBAR_SLOT) {
|
||||
//TODO won't work if offhand is added
|
||||
affectedSlots.add(inventory.getOffsetForHotbar(((MoveToHotbarAction) action.click.action).ordinal()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return affectedSlots;
|
||||
|
|
|
@ -41,16 +41,26 @@ public class StoredItemMappings {
|
|||
private final ItemMapping bamboo;
|
||||
private final ItemMapping banner;
|
||||
private final ItemMapping barrier;
|
||||
private final int bowl;
|
||||
private final int chest;
|
||||
private final ItemMapping compass;
|
||||
private final ItemMapping crossbow;
|
||||
private final ItemMapping enchantedBook;
|
||||
private final ItemMapping fishingRod;
|
||||
private final ItemMapping lodestoneCompass;
|
||||
private final int flintAndSteel;
|
||||
private final int goldenApple;
|
||||
private final int goldIngot;
|
||||
private final int ironIngot;
|
||||
private final int lead;
|
||||
private final ItemMapping milkBucket;
|
||||
private final int nameTag;
|
||||
private final ItemMapping powderSnowBucket;
|
||||
private final ItemMapping playerHead;
|
||||
private final ItemMapping egg;
|
||||
private final int saddle;
|
||||
private final int shears;
|
||||
private final ItemMapping shield;
|
||||
private final int waterBucket;
|
||||
private final ItemMapping wheat;
|
||||
private final ItemMapping writableBook;
|
||||
|
||||
|
@ -58,16 +68,26 @@ public class StoredItemMappings {
|
|||
this.bamboo = load(itemMappings, "bamboo");
|
||||
this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID
|
||||
this.barrier = load(itemMappings, "barrier");
|
||||
this.bowl = load(itemMappings, "bowl").getJavaId();
|
||||
this.chest = load(itemMappings, "chest").getJavaId();
|
||||
this.compass = load(itemMappings, "compass");
|
||||
this.crossbow = load(itemMappings, "crossbow");
|
||||
this.enchantedBook = load(itemMappings, "enchanted_book");
|
||||
this.fishingRod = load(itemMappings, "fishing_rod");
|
||||
this.lodestoneCompass = load(itemMappings, "lodestone_compass");
|
||||
this.flintAndSteel = load(itemMappings, "flint_and_steel").getJavaId();
|
||||
this.goldenApple = load(itemMappings, "golden_apple").getJavaId();
|
||||
this.goldIngot = load(itemMappings, "gold_ingot").getJavaId();
|
||||
this.ironIngot = load(itemMappings, "iron_ingot").getJavaId();
|
||||
this.lead = load(itemMappings, "lead").getJavaId();
|
||||
this.milkBucket = load(itemMappings, "milk_bucket");
|
||||
this.nameTag = load(itemMappings, "name_tag").getJavaId();
|
||||
this.powderSnowBucket = load(itemMappings, "powder_snow_bucket");
|
||||
this.playerHead = load(itemMappings, "player_head");
|
||||
this.egg = load(itemMappings, "egg");
|
||||
this.saddle = load(itemMappings, "saddle").getJavaId();
|
||||
this.shears = load(itemMappings, "shears").getJavaId();
|
||||
this.shield = load(itemMappings, "shield");
|
||||
this.waterBucket = load(itemMappings, "water_bucket").getJavaId();
|
||||
this.wheat = load(itemMappings, "wheat");
|
||||
this.writableBook = load(itemMappings, "writable_book");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.inventory.recipe;
|
||||
|
||||
/**
|
||||
* A more compact version of {@link com.github.steveice10.mc.protocol.data.game.recipe.Recipe}.
|
||||
*/
|
||||
public interface GeyserRecipe {
|
||||
/**
|
||||
* Whether the recipe is flexible or not in which items can be placed where.
|
||||
*/
|
||||
boolean isShaped();
|
||||
}
|
|
@ -23,26 +23,21 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.level.block.entity;
|
||||
package org.geysermc.geyser.inventory.recipe;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.packet.BlockEventPacket;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
|
||||
|
||||
/**
|
||||
* Does not implement BlockEntityTranslator because it's only a block entity in Bedrock
|
||||
*/
|
||||
public class NoteblockBlockEntityTranslator {
|
||||
public record GeyserShapedRecipe(int width, int height, Ingredient[] ingredients, ItemStack result) implements GeyserRecipe {
|
||||
|
||||
public static void translate(GeyserSession session, Position position) {
|
||||
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, position);
|
||||
BlockEventPacket blockEventPacket = new BlockEventPacket();
|
||||
blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ()));
|
||||
blockEventPacket.setEventType(0);
|
||||
blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState));
|
||||
session.sendUpstreamPacket(blockEventPacket);
|
||||
public GeyserShapedRecipe(ShapedRecipeData data) {
|
||||
this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShaped() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.inventory.recipe;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
|
||||
|
||||
public record GeyserShapelessRecipe(Ingredient[] ingredients, ItemStack result) implements GeyserRecipe {
|
||||
|
||||
public GeyserShapelessRecipe(ShapelessRecipeData data) {
|
||||
this(data.getIngredients(), data.getResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShaped() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.inventory.recipe;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
|
||||
/**
|
||||
* @param buttonId the button that needs to be pressed for Java Edition to accept this item.
|
||||
* @param output the expected output of this item when cut.
|
||||
*/
|
||||
public record GeyserStonecutterData(int buttonId, ItemStack output) {
|
||||
}
|
|
@ -384,19 +384,19 @@ public class AnvilInventoryUpdater extends InventoryUpdater {
|
|||
if (enchantTag.get("id") instanceof StringTag javaEnchId) {
|
||||
JavaEnchantment enchantment = JavaEnchantment.getByJavaIdentifier(javaEnchId.getValue());
|
||||
if (enchantment == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue());
|
||||
GeyserImpl.getInstance().getLogger().debug("Unknown Java enchantment in anvil: " + javaEnchId.getValue());
|
||||
continue;
|
||||
}
|
||||
|
||||
Tag javaEnchLvl = enchantTag.get("lvl");
|
||||
if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag))
|
||||
if (javaEnchLvl == null || !(javaEnchLvl.getValue() instanceof Number number))
|
||||
continue;
|
||||
|
||||
// Handle duplicate enchantments
|
||||
if (bedrock) {
|
||||
enchantments.putIfAbsent(enchantment, ((Number) javaEnchLvl.getValue()).intValue());
|
||||
enchantments.putIfAbsent(enchantment, number.intValue());
|
||||
} else {
|
||||
enchantments.mergeInt(enchantment, ((Number) javaEnchLvl.getValue()).intValue(), Math::max);
|
||||
enchantments.mergeInt(enchantment, number.intValue(), Math::max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.level;
|
||||
|
||||
/**
|
||||
* A data structure to represent what Bedrock believes are the height requirements for a specific dimension.
|
||||
* As of 1.18.30, biome count is representative of the height of the world, and out-of-bounds chunks can crash
|
||||
* the client.
|
||||
*
|
||||
* @param minY The minimum height Bedrock Edition will accept.
|
||||
* @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest.
|
||||
* @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's.
|
||||
*/
|
||||
public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) {
|
||||
public static BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true);
|
||||
public static BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false);
|
||||
public static BedrockDimension THE_END = new BedrockDimension(0, 256, true);
|
||||
}
|
|
@ -26,8 +26,6 @@
|
|||
package org.geysermc.geyser.level.block;
|
||||
|
||||
import com.nukkitx.network.util.Preconditions;
|
||||
import lombok.Getter;
|
||||
|
||||
|
||||
public class BlockPositionIterator {
|
||||
private final int minX;
|
||||
|
|
|
@ -38,6 +38,8 @@ import org.geysermc.geyser.util.collection.FixedInt2ByteMap;
|
|||
import org.geysermc.geyser.util.collection.FixedInt2IntMap;
|
||||
import org.geysermc.geyser.util.collection.LecternHasBookMap;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Used for block entities if the Java block state contains Bedrock block information.
|
||||
*/
|
||||
|
@ -47,7 +49,9 @@ public final class BlockStateValues {
|
|||
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
|
||||
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
private static final IntSet HORIZONTAL_FACING_JIGSAWS = new IntOpenHashSet();
|
||||
private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap();
|
||||
private static final IntSet NON_WATER_CAULDRONS = new IntOpenHashSet();
|
||||
private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap();
|
||||
private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap();
|
||||
private static final IntSet STICKY_PISTONS = new IntOpenHashSet();
|
||||
|
@ -170,12 +174,27 @@ public final class BlockStateValues {
|
|||
JsonNode shulkerDirection = blockData.get("shulker_direction");
|
||||
if (shulkerDirection != null) {
|
||||
BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue());
|
||||
return;
|
||||
}
|
||||
|
||||
if (javaId.startsWith("minecraft:water")) {
|
||||
if (javaId.startsWith("minecraft:water") && !javaId.contains("cauldron")) {
|
||||
String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1);
|
||||
int level = Integer.parseInt(strLevel);
|
||||
WATER_LEVEL.put(javaBlockState, level);
|
||||
return;
|
||||
}
|
||||
|
||||
if (javaId.startsWith("minecraft:jigsaw[orientation=")) {
|
||||
String blockStateData = javaId.substring(javaId.indexOf("orientation=") + "orientation=".length(), javaId.lastIndexOf('_'));
|
||||
Direction direction = Direction.valueOf(blockStateData.toUpperCase(Locale.ROOT));
|
||||
if (direction.isHorizontal()) {
|
||||
HORIZONTAL_FACING_JIGSAWS.add(javaBlockState);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (javaId.contains("_cauldron") && !javaId.contains("water_")) {
|
||||
NON_WATER_CAULDRONS.add(javaBlockState);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,6 +220,15 @@ public final class BlockStateValues {
|
|||
return BED_COLORS.getOrDefault(state, (byte) -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-water cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues.
|
||||
*
|
||||
* @return if this Java block state is a non-empty non-water cauldron
|
||||
*/
|
||||
public static boolean isCauldron(int state) {
|
||||
return NON_WATER_CAULDRONS.contains(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* The block state in Java and Bedrock both contain the conditional bit, however command block block entity tags
|
||||
* in Bedrock need the conditional information.
|
||||
|
@ -230,6 +258,13 @@ public final class BlockStateValues {
|
|||
return FLOWER_POT_VALUES;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a set of all forward-facing jigsaws, to use as a fallback if NBT is missing.
|
||||
*/
|
||||
public static IntSet getHorizontalFacingJigsaws() {
|
||||
return HORIZONTAL_FACING_JIGSAWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lectern book state map pointing to book present state
|
||||
*/
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.level.chunk;
|
||||
|
||||
import com.nukkitx.network.util.Preconditions;
|
||||
|
||||
public class NibbleArray implements Cloneable {
|
||||
|
||||
private final byte[] data;
|
||||
|
||||
public NibbleArray(int length) {
|
||||
data = new byte[length / 2];
|
||||
}
|
||||
|
||||
public NibbleArray(byte[] array) {
|
||||
data = array;
|
||||
}
|
||||
|
||||
public byte get(int index) {
|
||||
Preconditions.checkElementIndex(index, data.length * 2);
|
||||
byte val = data[index / 2];
|
||||
if ((index & 1) == 0) {
|
||||
return (byte) (val & 0x0f);
|
||||
} else {
|
||||
return (byte) ((val & 0xf0) >>> 4);
|
||||
}
|
||||
}
|
||||
|
||||
public void set(int index, byte value) {
|
||||
Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15.");
|
||||
Preconditions.checkElementIndex(index, data.length * 2);
|
||||
value &= 0xf;
|
||||
int half = index / 2;
|
||||
byte previous = data[half];
|
||||
if ((index & 1) == 0) {
|
||||
data[half] = (byte) (previous & 0xf0 | value);
|
||||
} else {
|
||||
data[half] = (byte) (previous & 0x0f | value << 4);
|
||||
}
|
||||
}
|
||||
|
||||
public void fill(byte value) {
|
||||
Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15.");
|
||||
value &= 0xf;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] = (byte) ((value << 4) | value);
|
||||
}
|
||||
}
|
||||
|
||||
public void copyFrom(byte[] bytes) {
|
||||
Preconditions.checkNotNull(bytes, "bytes");
|
||||
Preconditions.checkArgument(bytes.length == data.length, "length of provided byte array is %s but expected %s", bytes.length,
|
||||
data.length);
|
||||
System.arraycopy(bytes, 0, data, 0, data.length);
|
||||
}
|
||||
|
||||
public void copyFrom(NibbleArray array) {
|
||||
Preconditions.checkNotNull(array, "array");
|
||||
copyFrom(array.data);
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public NibbleArray copy() {
|
||||
return new NibbleArray(getData().clone());
|
||||
}
|
||||
}
|
|
@ -47,6 +47,8 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.List;
|
||||
|
||||
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
||||
private static final boolean PRINT_DEBUG_PINGS = Boolean.parseBoolean(System.getProperty("Geyser.PrintPingsInDebugMode", "true"));
|
||||
|
||||
/*
|
||||
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
|
||||
*/
|
||||
|
@ -88,7 +90,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
|||
|
||||
@Override
|
||||
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
|
||||
if (geyser.getConfig().isDebugMode()) {
|
||||
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
|
||||
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", inetSocketAddress));
|
||||
}
|
||||
|
||||
|
|
|
@ -28,12 +28,12 @@ package org.geysermc.geyser.network;
|
|||
import com.github.steveice10.mc.protocol.codec.MinecraftCodec;
|
||||
import com.github.steveice10.mc.protocol.codec.PacketCodec;
|
||||
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
|
||||
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
|
||||
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||
import com.nukkitx.protocol.bedrock.v475.Bedrock_v475;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import com.nukkitx.protocol.bedrock.v503.Bedrock_v503;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
|
@ -45,7 +45,7 @@ public final class MinecraftProtocol {
|
|||
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
||||
* release of the game that Geyser supports.
|
||||
*/
|
||||
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v475.V475_CODEC;
|
||||
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v503.V503_CODEC;
|
||||
/**
|
||||
* A list of all supported Bedrock versions that can join Geyser
|
||||
*/
|
||||
|
@ -58,9 +58,13 @@ public final class MinecraftProtocol {
|
|||
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
|
||||
|
||||
static {
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v465.V465_CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v475.V475_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v486.V486_CODEC.toBuilder()
|
||||
.minecraftVersion("1.18.10/1.18.12") // 1.18.11 is also supported, but was only on Switch and since that auto-updates it's not needed
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||
.minecraftVersion("1.18.30/1.18.31")
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +96,7 @@ public final class MinecraftProtocol {
|
|||
* @return the supported Minecraft: Java Edition version names
|
||||
*/
|
||||
public static List<String> getJavaVersions() {
|
||||
return Arrays.asList("1.18", "1.18.1");
|
||||
return Collections.singletonList(DEFAULT_JAVA_CODEC.getMinecraftVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,17 +30,18 @@ import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
|
|||
import com.nukkitx.protocol.bedrock.data.ExperimentData;
|
||||
import com.nukkitx.protocol.bedrock.data.ResourcePackType;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.auth.AuthType;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.pack.ResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||
import org.geysermc.geyser.session.auth.AuthType;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.*;
|
||||
import org.geysermc.geyser.util.LoginEncryptionUtils;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
|
@ -73,11 +74,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
String supportedVersions = MinecraftProtocol.getAllSupportedBedrockVersions();
|
||||
if (loginPacket.getProtocolVersion() > MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
||||
// Too early to determine session locale
|
||||
session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions));
|
||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions));
|
||||
return true;
|
||||
} else if (loginPacket.getProtocolVersion() < MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
||||
session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions));
|
||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions));
|
||||
return true;
|
||||
}
|
||||
|
@ -166,11 +165,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true));
|
||||
}
|
||||
|
||||
if (session.getUpstream().getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) {
|
||||
// Allow extended world height in the overworld to work for pre-1.18 clients
|
||||
stackPacket.getExperiments().add(new ExperimentData("caves_and_cliffs", true));
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(stackPacket);
|
||||
break;
|
||||
|
||||
|
@ -189,6 +183,14 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
}
|
||||
|
||||
private boolean couldLoginUserByName(String bedrockUsername) {
|
||||
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
|
||||
String refreshToken = geyser.refreshTokenFor(bedrockUsername);
|
||||
if (refreshToken != null) {
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
|
||||
session.authenticateWithRefreshToken(refreshToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (geyser.getConfig().getUserAuths() != null) {
|
||||
GeyserConfiguration.IUserAuthenticationInfo info = geyser.getConfig().getUserAuths().get(bedrockUsername);
|
||||
|
||||
|
@ -199,6 +201,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(session.getAuthData().xuid());
|
||||
if (task != null) {
|
||||
if (task.getAuthentication().isDone() && session.onMicrosoftLoginComplete(task)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoader;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A mapped registry with an integer as the key. This class is designed to minimize the need for boxing/unboxing keys.
|
||||
*
|
||||
* @param <V> the value
|
||||
*/
|
||||
public class IntMappedRegistry<V> extends AbstractMappedRegistry<Integer, V, Int2ObjectMap<V>> {
|
||||
protected <I> IntMappedRegistry(I input, RegistryLoader<I, Int2ObjectMap<V>> registryLoader) {
|
||||
super(input, registryLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value registered by the given integer.
|
||||
*
|
||||
* @param i the integer
|
||||
* @return the value registered by the given integer.
|
||||
*/
|
||||
public V get(int i) {
|
||||
return this.mappings.get(i);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@Deprecated
|
||||
public V get(Integer key) {
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value registered by the given key or the default value
|
||||
* specified if null.
|
||||
*
|
||||
* @param i the key
|
||||
* @param defaultValue the default value
|
||||
* @return the value registered by the given key or the default value
|
||||
* specified if null.
|
||||
*/
|
||||
public V getOrDefault(int i, V defaultValue) {
|
||||
return this.mappings.getOrDefault(i, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public V getOrDefault(Integer key, V defaultValue) {
|
||||
return super.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new value into this registry with the given key.
|
||||
*
|
||||
* @param i the key
|
||||
* @param value the value
|
||||
* @return a new value into this registry with the given key.
|
||||
*/
|
||||
public V register(int i, V value) {
|
||||
return this.mappings.put(i, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public V register(Integer key, V value) {
|
||||
return super.register(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new integer mapped registry with the given {@link RegistryLoader}. The
|
||||
* input type is not specified here, meaning the loader return type is either
|
||||
* predefined, or the registry is populated at a later point.
|
||||
*
|
||||
* @param registryLoader the registry loader
|
||||
* @param <I> the input
|
||||
* @param <V> the map value
|
||||
* @return a new registry with the given RegistryLoader
|
||||
*/
|
||||
public static <I, V> IntMappedRegistry<V> create(RegistryLoader<I, Int2ObjectMap<V>> registryLoader) {
|
||||
return new IntMappedRegistry<>(null, registryLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new integer mapped registry with the given {@link RegistryLoader} and input.
|
||||
*
|
||||
* @param registryLoader the registry loader
|
||||
* @param <I> the input
|
||||
* @param <V> the map value
|
||||
* @return a new registry with the given RegistryLoader supplier
|
||||
*/
|
||||
public static <I, V> IntMappedRegistry<V> create(I input, Supplier<RegistryLoader<I, Int2ObjectMap<V>>> registryLoader) {
|
||||
return new IntMappedRegistry<>(input, registryLoader.get());
|
||||
}
|
||||
}
|
|
@ -29,9 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
|
|||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
|
||||
import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic;
|
||||
import com.github.steveice10.packetlib.packet.Packet;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.protocol.bedrock.BedrockPacket;
|
||||
|
@ -43,26 +41,26 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
|
||||
import org.geysermc.geyser.translator.sound.SoundTranslator;
|
||||
import org.geysermc.geyser.translator.sound.SoundInteractionTranslator;
|
||||
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.registry.loader.*;
|
||||
import org.geysermc.geyser.registry.populator.ItemRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.RecipeRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.type.EnchantmentData;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.registry.type.ParticleMapping;
|
||||
import org.geysermc.geyser.registry.type.SoundMapping;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
|
||||
import org.geysermc.geyser.translator.sound.SoundInteractionTranslator;
|
||||
import org.geysermc.geyser.translator.sound.SoundTranslator;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* Holds all the common registries in Geyser.
|
||||
|
@ -96,7 +94,7 @@ public final class Registries {
|
|||
/**
|
||||
* A mapped registry containing which holds block IDs to its {@link BlockCollision}.
|
||||
*/
|
||||
public static final SimpleMappedRegistry<Integer, BlockCollision> COLLISIONS = SimpleMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new);
|
||||
public static final IntMappedRegistry<BlockCollision> COLLISIONS = IntMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new);
|
||||
|
||||
/**
|
||||
* A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link CraftingData}.
|
||||
|
@ -141,15 +139,15 @@ public final class Registries {
|
|||
public static final SimpleRegistry<Set<PotionMixData>> POTION_MIXES;
|
||||
|
||||
/**
|
||||
* A versioned registry holding all the recipes, with the net ID being the key, and {@link Recipe} as the value.
|
||||
* A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value.
|
||||
*/
|
||||
public static final VersionedRegistry<Int2ObjectMap<Recipe>> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
public static final VersionedRegistry<Int2ObjectMap<GeyserRecipe>> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A mapped registry holding the available records, with the ID of the record being the key, and the {@link com.nukkitx.protocol.bedrock.data.SoundEvent}
|
||||
* as the value.
|
||||
*/
|
||||
public static final SimpleMappedRegistry<Integer, com.nukkitx.protocol.bedrock.data.SoundEvent> RECORDS = SimpleMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
public static final IntMappedRegistry<com.nukkitx.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}.
|
||||
|
|
|
@ -50,10 +50,10 @@ import java.util.regex.Pattern;
|
|||
/**
|
||||
* Loads collision data from the given resource path.
|
||||
*/
|
||||
public class CollisionRegistryLoader extends MultiResourceRegistryLoader<String, Map<Integer, BlockCollision>> {
|
||||
public class CollisionRegistryLoader extends MultiResourceRegistryLoader<String, Int2ObjectMap<BlockCollision>> {
|
||||
|
||||
@Override
|
||||
public Map<Integer, BlockCollision> load(Pair<String, String> input) {
|
||||
public Int2ObjectMap<BlockCollision> load(Pair<String, String> input) {
|
||||
Int2ObjectMap<BlockCollision> collisions = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
Map<Class<?>, CollisionInfo> annotationMap = new IdentityHashMap<>();
|
||||
|
|
|
@ -28,8 +28,9 @@ package org.geysermc.geyser.registry.populator;
|
|||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.nukkitx.nbt.*;
|
||||
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
|
||||
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||
import com.nukkitx.protocol.bedrock.v475.Bedrock_v475;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import com.nukkitx.protocol.bedrock.v503.Bedrock_v503;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
|
@ -46,7 +47,10 @@ import org.geysermc.geyser.util.BlockUtils;
|
|||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
@ -57,10 +61,51 @@ public class BlockRegistryPopulator {
|
|||
private static final ImmutableMap<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> BLOCK_MAPPERS;
|
||||
private static final BiFunction<String, NbtMapBuilder, String> EMPTY_MAPPER = (bedrockIdentifier, statesBuilder) -> null;
|
||||
|
||||
private static final BiFunction<String, NbtMapBuilder, String> V486_MAPPER = (bedrockIdentifier, statesBuilder) -> {
|
||||
statesBuilder.remove("no_drop_bit"); // Used in skulls
|
||||
if (bedrockIdentifier.equals("minecraft:glow_lichen")) {
|
||||
// Moved around north, south, west
|
||||
int bits = (int) statesBuilder.get("multi_face_direction_bits");
|
||||
boolean north = (bits & (1 << 2)) != 0;
|
||||
boolean south = (bits & (1 << 3)) != 0;
|
||||
boolean west = (bits & (1 << 4)) != 0;
|
||||
if (north) {
|
||||
bits |= 1 << 4;
|
||||
} else {
|
||||
bits &= ~(1 << 4);
|
||||
}
|
||||
if (south) {
|
||||
bits |= 1 << 2;
|
||||
} else {
|
||||
bits &= ~(1 << 2);
|
||||
}
|
||||
if (west) {
|
||||
bits |= 1 << 3;
|
||||
} else {
|
||||
bits &= ~(1 << 3);
|
||||
}
|
||||
statesBuilder.put("multi_face_direction_bits", bits);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
static {
|
||||
ImmutableMap.Builder<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> stateMapperBuilder = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder()
|
||||
.put(ObjectIntPair.of("1_17_30", Bedrock_v465.V465_CODEC.getProtocolVersion()), EMPTY_MAPPER)
|
||||
.put(ObjectIntPair.of("1_17_40", Bedrock_v471.V471_CODEC.getProtocolVersion()), EMPTY_MAPPER);
|
||||
.put(ObjectIntPair.of("1_18_0", Bedrock_v475.V475_CODEC.getProtocolVersion()), EMPTY_MAPPER)
|
||||
.put(ObjectIntPair.of("1_18_10", Bedrock_v486.V486_CODEC.getProtocolVersion()), V486_MAPPER)
|
||||
.put(ObjectIntPair.of("1_18_30", Bedrock_v503.V503_CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> {
|
||||
// Apply these fixes too
|
||||
V486_MAPPER.apply(bedrockIdentifier, statesBuilder);
|
||||
return switch (bedrockIdentifier) {
|
||||
case "minecraft:pistonArmCollision" -> "minecraft:piston_arm_collision";
|
||||
case "minecraft:stickyPistonArmCollision" -> "minecraft:sticky_piston_arm_collision";
|
||||
case "minecraft:movingBlock" -> "minecraft:moving_block";
|
||||
case "minecraft:tripWire" -> "minecraft:trip_wire";
|
||||
case "minecraft:seaLantern" -> "minecraft:sea_lantern";
|
||||
case "minecraft:concretePowder" -> "minecraft:concrete_powder";
|
||||
default -> null;
|
||||
};
|
||||
});
|
||||
|
||||
BLOCK_MAPPERS = stateMapperBuilder.build();
|
||||
}
|
||||
|
@ -87,7 +132,6 @@ public class BlockRegistryPopulator {
|
|||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to get blocks from runtime block states", e);
|
||||
}
|
||||
Map<String, NbtMap> javaIdentifierToBedrockTag = new Object2ObjectOpenHashMap<>(blocksTag.size());
|
||||
// New since 1.16.100 - find the block runtime ID by the order given to us in the block palette,
|
||||
// as we no longer send a block palette
|
||||
Object2IntMap<NbtMap> blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size());
|
||||
|
@ -157,10 +201,6 @@ public class BlockRegistryPopulator {
|
|||
flowerPotBlocks.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId));
|
||||
}
|
||||
|
||||
if (!cleanJavaIdentifier.equals(entry.getValue().get("bedrock_identifier").asText())) {
|
||||
javaIdentifierToBedrockTag.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId));
|
||||
}
|
||||
|
||||
javaToBedrockBlocks[javaRuntimeId] = bedrockRuntimeId;
|
||||
}
|
||||
|
||||
|
@ -195,7 +235,6 @@ public class BlockRegistryPopulator {
|
|||
|
||||
BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion)
|
||||
.javaToBedrockBlocks(javaToBedrockBlocks)
|
||||
.javaIdentifierToBedrockTag(javaIdentifierToBedrockTag)
|
||||
.itemFrames(itemFrames)
|
||||
.flowerPotBlocks(flowerPotBlocks)
|
||||
.jigsawStateIds(jigsawStateIds)
|
||||
|
|
|
@ -35,11 +35,10 @@ import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
|||
import com.nukkitx.protocol.bedrock.data.inventory.ComponentItemData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
||||
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
|
||||
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||
import com.nukkitx.protocol.bedrock.v475.Bedrock_v475;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import com.nukkitx.protocol.bedrock.v503.Bedrock_v503;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
|
@ -49,6 +48,8 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings;
|
|||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.*;
|
||||
import org.geysermc.geyser.util.ItemUtils;
|
||||
import org.geysermc.geyser.util.collection.FixedInt2IntMap;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -59,19 +60,16 @@ import java.util.*;
|
|||
* Populates the item registries.
|
||||
*/
|
||||
public class ItemRegistryPopulator {
|
||||
private static final Map<String, PaletteVersion> PALETTE_VERSIONS;
|
||||
|
||||
static {
|
||||
PALETTE_VERSIONS = new Object2ObjectOpenHashMap<>();
|
||||
PALETTE_VERSIONS.put("1_17_30", new PaletteVersion(Bedrock_v465.V465_CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
PALETTE_VERSIONS.put("1_17_40", new PaletteVersion(Bedrock_v471.V471_CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
PALETTE_VERSIONS.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
}
|
||||
|
||||
private record PaletteVersion(int protocolVersion, Map<String, String> additionalTranslatedItems) {
|
||||
}
|
||||
|
||||
public static void populate() {
|
||||
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
|
||||
paletteVersions.put("1_18_0", new PaletteVersion(Bedrock_v475.V475_CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
paletteVersions.put("1_18_10", new PaletteVersion(Bedrock_v486.V486_CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
paletteVersions.put("1_18_30", new PaletteVersion(Bedrock_v503.V503_CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
TypeReference<Map<String, GeyserMappingItem>> mappingItemsType = new TypeReference<>() { };
|
||||
|
@ -84,8 +82,12 @@ public class ItemRegistryPopulator {
|
|||
throw new AssertionError("Unable to load Java runtime item IDs", e);
|
||||
}
|
||||
|
||||
// We can reduce some operations as Java information is the same across all palette versions
|
||||
boolean firstMappingsPass = true;
|
||||
Int2IntMap dyeColors = new FixedInt2IntMap();
|
||||
|
||||
/* Load item palette */
|
||||
for (Map.Entry<String, PaletteVersion> palette : PALETTE_VERSIONS.entrySet()) {
|
||||
for (Map.Entry<String, PaletteVersion> palette : paletteVersions.entrySet()) {
|
||||
TypeReference<List<PaletteItem>> paletteEntriesType = new TypeReference<>() {};
|
||||
|
||||
// Used to get the Bedrock namespaced ID (in instances where there are small differences)
|
||||
|
@ -125,7 +127,7 @@ public class ItemRegistryPopulator {
|
|||
IntList spawnEggs = new IntArrayList();
|
||||
List<ItemData> carpets = new ObjectArrayList<>();
|
||||
|
||||
Int2ObjectMap<ItemMapping> mappings = new Int2ObjectOpenHashMap<>();
|
||||
List<ItemMapping> mappings = new ObjectArrayList<>();
|
||||
// Temporary mapping to create stored items
|
||||
Map<String, ItemMapping> identifierToMapping = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
|
@ -163,6 +165,9 @@ public class ItemRegistryPopulator {
|
|||
if (identifier.equals("minecraft:debug_stick")) {
|
||||
// Just shows an empty texture; either way it doesn't exist in the creative menu on Java
|
||||
continue;
|
||||
} else if (identifier.equals("minecraft:empty_map") && damage == 2) {
|
||||
// Bedrock-only as its own item
|
||||
continue;
|
||||
}
|
||||
StartGamePacket.ItemEntry entry = entries.get(identifier);
|
||||
int id = -1;
|
||||
|
@ -224,16 +229,27 @@ public class ItemRegistryPopulator {
|
|||
// This items has a mapping specifically for this version of the game
|
||||
mappingItem = entry.getValue();
|
||||
}
|
||||
if (javaIdentifier.equals("minecraft:music_disc_otherside") && palette.getValue().protocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) {
|
||||
mappingItem.setBedrockIdentifier("minecraft:music_disc_pigstep");
|
||||
|
||||
String bedrockIdentifier;
|
||||
if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) {
|
||||
bedrockIdentifier = "minecraft:banner_pattern";
|
||||
} else {
|
||||
bedrockIdentifier = mappingItem.getBedrockIdentifier();
|
||||
if (palette.getValue().protocolVersion() >= Bedrock_v503.V503_CODEC.getProtocolVersion()) {
|
||||
if (bedrockIdentifier.equals("minecraft:sealantern")) {
|
||||
bedrockIdentifier = "minecraft:sea_lantern";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) {
|
||||
javaFurnaceMinecartId = itemIndex;
|
||||
itemIndex++;
|
||||
// Will be added later
|
||||
mappings.add(null);
|
||||
continue;
|
||||
}
|
||||
String bedrockIdentifier = mappingItem.getBedrockIdentifier().intern();
|
||||
|
||||
int bedrockId = bedrockIdentifierToId.getInt(bedrockIdentifier);
|
||||
if (bedrockId == Short.MIN_VALUE) {
|
||||
throw new RuntimeException("Missing Bedrock ID in mappings: " + bedrockIdentifier);
|
||||
|
@ -358,12 +374,13 @@ public class ItemRegistryPopulator {
|
|||
ItemMapping.ItemMappingBuilder mappingBuilder = ItemMapping.builder()
|
||||
.javaIdentifier(javaIdentifier)
|
||||
.javaId(itemIndex)
|
||||
.bedrockIdentifier(bedrockIdentifier)
|
||||
.bedrockIdentifier(bedrockIdentifier.intern())
|
||||
.bedrockId(bedrockId)
|
||||
.bedrockData(mappingItem.getBedrockData())
|
||||
.bedrockBlockId(bedrockBlockId)
|
||||
.stackSize(stackSize)
|
||||
.maxDamage(mappingItem.getMaxDamage());
|
||||
.maxDamage(mappingItem.getMaxDamage())
|
||||
.hasSuspiciousStewEffect(mappingItem.isHasSuspiciousStewEffect());
|
||||
|
||||
if (mappingItem.getRepairMaterials() != null) {
|
||||
mappingBuilder = mappingBuilder.repairMaterials(new ObjectOpenHashSet<>(mappingItem.getRepairMaterials()));
|
||||
|
@ -406,11 +423,15 @@ public class ItemRegistryPopulator {
|
|||
spawnEggs.add(mapping.getBedrockId());
|
||||
}
|
||||
|
||||
mappings.put(itemIndex, mapping);
|
||||
mappings.add(mapping);
|
||||
identifierToMapping.put(javaIdentifier, mapping);
|
||||
|
||||
itemNames.add(javaIdentifier);
|
||||
|
||||
if (firstMappingsPass && mappingItem.getDyeColor() != -1) {
|
||||
dyeColors.put(itemIndex, mappingItem.getDyeColor());
|
||||
}
|
||||
|
||||
itemIndex++;
|
||||
}
|
||||
|
||||
|
@ -423,16 +444,14 @@ public class ItemRegistryPopulator {
|
|||
|
||||
// Add the lodestone compass since it doesn't exist on java but we need it for item conversion
|
||||
ItemMapping lodestoneEntry = ItemMapping.builder()
|
||||
.javaIdentifier("minecraft:lodestone_compass")
|
||||
.javaIdentifier("")
|
||||
.bedrockIdentifier("minecraft:lodestone_compass")
|
||||
.javaId(itemIndex)
|
||||
.javaId(-1)
|
||||
.bedrockId(lodestoneCompassId)
|
||||
.bedrockData(0)
|
||||
.bedrockBlockId(-1)
|
||||
.stackSize(1)
|
||||
.build();
|
||||
mappings.put(itemIndex, lodestoneEntry);
|
||||
identifierToMapping.put(lodestoneEntry.getJavaIdentifier(), lodestoneEntry);
|
||||
|
||||
ComponentItemData furnaceMinecartData = null;
|
||||
if (usingFurnaceMinecart) {
|
||||
|
@ -441,7 +460,7 @@ public class ItemRegistryPopulator {
|
|||
|
||||
entries.put("geysermc:furnace_minecart", new StartGamePacket.ItemEntry("geysermc:furnace_minecart", (short) furnaceMinecartId, true));
|
||||
|
||||
mappings.put(javaFurnaceMinecartId, ItemMapping.builder()
|
||||
mappings.set(javaFurnaceMinecartId, ItemMapping.builder()
|
||||
.javaIdentifier("minecraft:furnace_minecart")
|
||||
.bedrockIdentifier("geysermc:furnace_minecart")
|
||||
.javaId(javaFurnaceMinecartId)
|
||||
|
@ -492,9 +511,9 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
|
||||
ItemMappings itemMappings = ItemMappings.builder()
|
||||
.items(mappings)
|
||||
.items(mappings.toArray(new ItemMapping[0]))
|
||||
.creativeItems(creativeItems.toArray(new ItemData[0]))
|
||||
.itemEntries(new ArrayList<>(entries.values()))
|
||||
.itemEntries(List.copyOf(entries.values()))
|
||||
.itemNames(itemNames.toArray(new String[0]))
|
||||
.storedItems(new StoredItemMappings(identifierToMapping))
|
||||
.javaOnlyItems(javaOnlyItems)
|
||||
|
@ -503,9 +522,14 @@ public class ItemRegistryPopulator {
|
|||
.spawnEggIds(spawnEggs)
|
||||
.carpets(carpets)
|
||||
.furnaceMinecartData(furnaceMinecartData)
|
||||
.lodestoneCompass(lodestoneEntry)
|
||||
.build();
|
||||
|
||||
Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings);
|
||||
|
||||
firstMappingsPass = false;
|
||||
}
|
||||
|
||||
ItemUtils.setDyeColors(dyeColors);
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue