mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
gg
This commit is contained in:
parent
45f96a03e7
commit
d6e211a086
78 changed files with 2381 additions and 913 deletions
|
|
@ -42,7 +42,7 @@ There are a few things Geyser is unable to support due to various differences be
|
||||||
3. Run `gradlew build` and locate to `bootstrap/build` folder.
|
3. Run `gradlew build` and locate to `bootstrap/build` folder.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Any contributions are appreciated. Please feel free to reach out to us on [Discord](https://discord.gg/geysermc) if
|
Any contributions are appreciated. Please feel free to reach out to us on [Discord](http://discord.geysermc.org/) if
|
||||||
you're interested in helping out with Geyser.
|
you're interested in helping out with Geyser.
|
||||||
|
|
||||||
## Libraries Used:
|
## Libraries Used:
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ dependencies {
|
||||||
shadow(libs.protocol.connection) { isTransitive = false }
|
shadow(libs.protocol.connection) { isTransitive = false }
|
||||||
shadow(libs.protocol.common) { isTransitive = false }
|
shadow(libs.protocol.common) { isTransitive = false }
|
||||||
shadow(libs.protocol.codec) { isTransitive = false }
|
shadow(libs.protocol.codec) { isTransitive = false }
|
||||||
shadow(libs.minecraftauth) { isTransitive = false }
|
shadow(libs.mcauthlib) { isTransitive = false }
|
||||||
shadow(libs.raknet) { isTransitive = false }
|
shadow(libs.raknet) { isTransitive = false }
|
||||||
|
|
||||||
// Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
|
// Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,11 @@ dependencies {
|
||||||
|
|
||||||
api(libs.bundles.protocol)
|
api(libs.bundles.protocol)
|
||||||
|
|
||||||
api(libs.minecraftauth)
|
api(libs.mcauthlib)
|
||||||
api(libs.mcprotocollib) {
|
api(libs.mcprotocollib) {
|
||||||
exclude("io.netty", "netty-all")
|
exclude("io.netty", "netty-all")
|
||||||
exclude("net.raphimc", "MinecraftAuth")
|
exclude("com.github.GeyserMC", "packetlib")
|
||||||
|
exclude("com.github.GeyserMC", "mcauthlib")
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(libs.raknet) {
|
implementation(libs.raknet) {
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,7 @@ public final class Constants {
|
||||||
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
|
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
|
||||||
public static final String UPDATE_PERMISSION = "geyser.update";
|
public static final String UPDATE_PERMISSION = "geyser.update";
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
|
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
|
||||||
static final String SAVED_AUTH_CHAINS_FILE = "saved-auth-chains.json";
|
|
||||||
|
|
||||||
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
|
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.gson.Gson;
|
|
||||||
import io.netty.channel.epoll.Epoll;
|
import io.netty.channel.epoll.Epoll;
|
||||||
import io.netty.util.NettyRuntime;
|
import io.netty.util.NettyRuntime;
|
||||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||||
|
|
@ -39,8 +38,6 @@ import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
|
||||||
import net.raphimc.minecraftauth.step.msa.StepMsaToken;
|
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
@ -58,11 +55,7 @@ import org.geysermc.geyser.api.GeyserApi;
|
||||||
import org.geysermc.geyser.api.command.CommandSource;
|
import org.geysermc.geyser.api.command.CommandSource;
|
||||||
import org.geysermc.geyser.api.event.EventBus;
|
import org.geysermc.geyser.api.event.EventBus;
|
||||||
import org.geysermc.geyser.api.event.EventRegistrar;
|
import org.geysermc.geyser.api.event.EventRegistrar;
|
||||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
|
import org.geysermc.geyser.api.event.lifecycle.*;
|
||||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPostReloadEvent;
|
|
||||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
|
|
||||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPreReloadEvent;
|
|
||||||
import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent;
|
|
||||||
import org.geysermc.geyser.api.network.AuthType;
|
import org.geysermc.geyser.api.network.AuthType;
|
||||||
import org.geysermc.geyser.api.network.BedrockListener;
|
import org.geysermc.geyser.api.network.BedrockListener;
|
||||||
import org.geysermc.geyser.api.network.RemoteServer;
|
import org.geysermc.geyser.api.network.RemoteServer;
|
||||||
|
|
@ -92,14 +85,7 @@ import org.geysermc.geyser.skin.SkinProvider;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
import org.geysermc.geyser.text.MinecraftLocale;
|
import org.geysermc.geyser.text.MinecraftLocale;
|
||||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
import org.geysermc.geyser.util.AssetUtils;
|
import org.geysermc.geyser.util.*;
|
||||||
import org.geysermc.geyser.util.CooldownUtils;
|
|
||||||
import org.geysermc.geyser.util.DimensionUtils;
|
|
||||||
import org.geysermc.geyser.util.Metrics;
|
|
||||||
import org.geysermc.geyser.util.MinecraftAuthLogger;
|
|
||||||
import org.geysermc.geyser.util.NewsHandler;
|
|
||||||
import org.geysermc.geyser.util.VersionCheckUtils;
|
|
||||||
import org.geysermc.geyser.util.WebUtils;
|
|
||||||
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
|
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
@ -111,19 +97,11 @@ import java.net.UnknownHostException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
@ -183,7 +161,7 @@ public class GeyserImpl implements GeyserApi {
|
||||||
|
|
||||||
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
|
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private Map<String, String> savedAuthChains;
|
private Map<String, String> savedRefreshTokens;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private static GeyserImpl instance;
|
private static GeyserImpl instance;
|
||||||
|
|
@ -556,84 +534,37 @@ public class GeyserImpl implements GeyserApi {
|
||||||
|
|
||||||
if (config.getRemote().authType() == AuthType.ONLINE) {
|
if (config.getRemote().authType() == AuthType.ONLINE) {
|
||||||
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
|
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
|
||||||
savedAuthChains = new ConcurrentHashMap<>();
|
savedRefreshTokens = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
// TODO Remove after a while - just a migration help
|
File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
||||||
//noinspection deprecation
|
if (tokensFile.exists()) {
|
||||||
File refreshTokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
|
||||||
if (refreshTokensFile.exists()) {
|
|
||||||
logger.info("Migrating refresh tokens to auth chains...");
|
|
||||||
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
|
||||||
Map<String, String> refreshTokens = null;
|
|
||||||
try {
|
|
||||||
refreshTokens = JSON_MAPPER.readValue(refreshTokensFile, type);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignored - we'll just delete this file :))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refreshTokens != null) {
|
|
||||||
List<String> validUsers = config.getSavedUserLogins();
|
|
||||||
final Gson gson = new Gson();
|
|
||||||
for (Map.Entry<String, String> entry : refreshTokens.entrySet()) {
|
|
||||||
String user = entry.getKey();
|
|
||||||
if (!validUsers.contains(user)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate refresh tokens to auth chains
|
|
||||||
try {
|
|
||||||
StepFullJavaSession javaSession = PendingMicrosoftAuthentication.AUTH_FLOW.apply(false, 10);
|
|
||||||
StepFullJavaSession.FullJavaSession fullJavaSession = javaSession.getFromInput(
|
|
||||||
MinecraftAuthLogger.INSTANCE,
|
|
||||||
PendingMicrosoftAuthentication.AUTH_CLIENT,
|
|
||||||
new StepMsaToken.RefreshToken(entry.getValue())
|
|
||||||
);
|
|
||||||
|
|
||||||
String authChain = gson.toJson(javaSession.toJson(fullJavaSession));
|
|
||||||
savedAuthChains.put(user, authChain);
|
|
||||||
} catch (Exception e) {
|
|
||||||
GeyserImpl.getInstance().getLogger().warning("Could not migrate " + entry.getKey() + " to an auth chain! " +
|
|
||||||
"They will need to sign in the next time they join Geyser.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the new additions are written to the file
|
|
||||||
scheduleAuthChainsWrite();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally: Delete it. Goodbye!
|
|
||||||
refreshTokensFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
File authChainsFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
|
|
||||||
if (authChainsFile.exists()) {
|
|
||||||
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
||||||
|
|
||||||
Map<String, String> authChainFile = null;
|
Map<String, String> refreshTokenFile = null;
|
||||||
try {
|
try {
|
||||||
authChainFile = JSON_MAPPER.readValue(authChainsFile, type);
|
refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Cannot load saved user tokens!", e);
|
logger.error("Cannot load saved user tokens!", e);
|
||||||
}
|
}
|
||||||
if (authChainFile != null) {
|
if (refreshTokenFile != null) {
|
||||||
List<String> validUsers = config.getSavedUserLogins();
|
List<String> validUsers = config.getSavedUserLogins();
|
||||||
boolean doWrite = false;
|
boolean doWrite = false;
|
||||||
for (Map.Entry<String, String> entry : authChainFile.entrySet()) {
|
for (Map.Entry<String, String> entry : refreshTokenFile.entrySet()) {
|
||||||
String user = entry.getKey();
|
String user = entry.getKey();
|
||||||
if (!validUsers.contains(user)) {
|
if (!validUsers.contains(user)) {
|
||||||
// Perform a write to this file to purge the now-unused name
|
// Perform a write to this file to purge the now-unused name
|
||||||
doWrite = true;
|
doWrite = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
savedAuthChains.put(user, entry.getValue());
|
savedRefreshTokens.put(user, entry.getValue());
|
||||||
}
|
}
|
||||||
if (doWrite) {
|
if (doWrite) {
|
||||||
scheduleAuthChainsWrite();
|
scheduleRefreshTokensWrite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
savedAuthChains = null;
|
savedRefreshTokens = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
|
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
|
||||||
|
|
@ -714,11 +645,16 @@ public class GeyserImpl implements GeyserApi {
|
||||||
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done"));
|
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done"));
|
||||||
}
|
}
|
||||||
|
|
||||||
runIfNonNull(scheduledThread, ScheduledExecutorService::shutdown);
|
scheduledThread.shutdown();
|
||||||
runIfNonNull(geyserServer, GeyserServer::shutdown);
|
geyserServer.shutdown();
|
||||||
runIfNonNull(skinUploader, FloodgateSkinUploader::close);
|
if (skinUploader != null) {
|
||||||
runIfNonNull(newsHandler, NewsHandler::shutdown);
|
skinUploader.close();
|
||||||
runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
|
}
|
||||||
|
newsHandler.shutdown();
|
||||||
|
|
||||||
|
if (this.erosionUnixListener != null) {
|
||||||
|
this.erosionUnixListener.close();
|
||||||
|
}
|
||||||
|
|
||||||
Registries.RESOURCE_PACKS.get().clear();
|
Registries.RESOURCE_PACKS.get().clear();
|
||||||
|
|
||||||
|
|
@ -880,11 +816,11 @@ public class GeyserImpl implements GeyserApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String authChainFor(@NonNull String bedrockName) {
|
public String refreshTokenFor(@NonNull String bedrockName) {
|
||||||
return savedAuthChains.get(bedrockName);
|
return savedRefreshTokens.get(bedrockName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveAuthChain(@NonNull String bedrockName, @NonNull String authChain) {
|
public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) {
|
||||||
if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
|
if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
|
||||||
// Do not save this login
|
// Do not save this login
|
||||||
return;
|
return;
|
||||||
|
|
@ -892,26 +828,20 @@ public class GeyserImpl implements GeyserApi {
|
||||||
|
|
||||||
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
|
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
|
||||||
// refreshes the token for us
|
// refreshes the token for us
|
||||||
if (!Objects.equals(authChain, savedAuthChains.put(bedrockName, authChain))) {
|
if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) {
|
||||||
scheduleAuthChainsWrite();
|
scheduleRefreshTokensWrite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void runIfNonNull(T nullable, Consumer<T> consumer) {
|
private void scheduleRefreshTokensWrite() {
|
||||||
if (nullable != null) {
|
|
||||||
consumer.accept(nullable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleAuthChainsWrite() {
|
|
||||||
scheduledThread.execute(() -> {
|
scheduledThread.execute(() -> {
|
||||||
// Ensure all writes are handled on the same thread
|
// Ensure all writes are handled on the same thread
|
||||||
File savedAuthChains = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_AUTH_CHAINS_FILE).toFile();
|
File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
|
||||||
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
TypeReference<Map<String, String>> type = new TypeReference<>() { };
|
||||||
try (FileWriter writer = new FileWriter(savedAuthChains)) {
|
try (FileWriter writer = new FileWriter(savedTokens)) {
|
||||||
JSON_MAPPER.writerFor(type)
|
JSON_MAPPER.writerFor(type)
|
||||||
.withDefaultPrettyPrinter()
|
.withDefaultPrettyPrinter()
|
||||||
.writeValue(writer, this.savedAuthChains);
|
.writeValue(writer, savedRefreshTokens);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
getLogger().error("Unable to write saved refresh tokens!", e);
|
getLogger().error("Unable to write saved refresh tokens!", e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,6 @@ public final class EntityDefinitions {
|
||||||
TEXT_DISPLAY = EntityDefinition.inherited(TextDisplayEntity::new, displayBase)
|
TEXT_DISPLAY = EntityDefinition.inherited(TextDisplayEntity::new, displayBase)
|
||||||
.type(EntityType.TEXT_DISPLAY)
|
.type(EntityType.TEXT_DISPLAY)
|
||||||
.identifier("minecraft:armor_stand")
|
.identifier("minecraft:armor_stand")
|
||||||
.offset(-0.5f)
|
|
||||||
.addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
|
.addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
|
||||||
.addTranslator(null) // Line width
|
.addTranslator(null) // Line width
|
||||||
.addTranslator(null) // Background color
|
.addTranslator(null) // Background color
|
||||||
|
|
@ -888,7 +887,7 @@ public final class EntityDefinitions {
|
||||||
.type(EntityType.PIG)
|
.type(EntityType.PIG)
|
||||||
.heightAndWidth(0.9f)
|
.heightAndWidth(0.9f)
|
||||||
.addTranslator(MetadataType.BOOLEAN, (pigEntity, entityMetadata) -> pigEntity.setFlag(EntityFlag.SADDLED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
|
.addTranslator(MetadataType.BOOLEAN, (pigEntity, entityMetadata) -> pigEntity.setFlag(EntityFlag.SADDLED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
|
||||||
.addTranslator(null) // Boost time
|
.addTranslator(MetadataType.INT, PigEntity::setBoost)
|
||||||
.build();
|
.build();
|
||||||
POLAR_BEAR = EntityDefinition.inherited(PolarBearEntity::new, ageableEntityBase)
|
POLAR_BEAR = EntityDefinition.inherited(PolarBearEntity::new, ageableEntityBase)
|
||||||
.type(EntityType.POLAR_BEAR)
|
.type(EntityType.POLAR_BEAR)
|
||||||
|
|
@ -914,7 +913,7 @@ public final class EntityDefinitions {
|
||||||
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
|
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
|
||||||
.type(EntityType.STRIDER)
|
.type(EntityType.STRIDER)
|
||||||
.height(1.7f).width(0.9f)
|
.height(1.7f).width(0.9f)
|
||||||
.addTranslator(null) // Boost time
|
.addTranslator(MetadataType.INT, StriderEntity::setBoost)
|
||||||
.addTranslator(MetadataType.BOOLEAN, StriderEntity::setCold)
|
.addTranslator(MetadataType.BOOLEAN, StriderEntity::setCold)
|
||||||
.addTranslator(MetadataType.BOOLEAN, StriderEntity::setSaddled)
|
.addTranslator(MetadataType.BOOLEAN, StriderEntity::setSaddled)
|
||||||
.build();
|
.build();
|
||||||
|
|
@ -955,7 +954,7 @@ public final class EntityDefinitions {
|
||||||
.type(EntityType.CAMEL)
|
.type(EntityType.CAMEL)
|
||||||
.height(2.375f).width(1.7f)
|
.height(2.375f).width(1.7f)
|
||||||
.addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing)
|
.addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing)
|
||||||
.addTranslator(null) // Last pose change tick
|
.addTranslator(MetadataType.LONG, CamelEntity::setLastPoseTick)
|
||||||
.build();
|
.build();
|
||||||
HORSE = EntityDefinition.inherited(HorseEntity::new, abstractHorseEntityBase)
|
HORSE = EntityDefinition.inherited(HorseEntity::new, abstractHorseEntityBase)
|
||||||
.type(EntityType.HORSE)
|
.type(EntityType.HORSE)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ public enum GeyserAttributeType {
|
||||||
ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f),
|
ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f),
|
||||||
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
|
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
|
||||||
SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f),
|
SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f),
|
||||||
BLOCK_INTERACTION_RANGE("minecraft:player.block_interaction_range", null, 0.0f, 64f, 4.5f),
|
|
||||||
|
|
||||||
// Bedrock Attributes
|
// Bedrock Attributes
|
||||||
ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),
|
ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||||
import org.geysermc.geyser.item.Items;
|
import org.geysermc.geyser.item.Items;
|
||||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||||
|
|
@ -289,6 +290,36 @@ public class LivingEntity extends Entity {
|
||||||
return super.interact(hand);
|
return super.interact(hand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
|
||||||
|
if (this instanceof ClientVehicle clientVehicle) {
|
||||||
|
if (clientVehicle.isClientControlled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clientVehicle.getVehicleComponent().moveRelative(relX, relY, relZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setBoundingBoxHeight(float height) {
|
||||||
|
if (valid && this instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().setHeight(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.setBoundingBoxHeight(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBoundingBoxWidth(float width) {
|
||||||
|
if (valid && this instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().setWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.setBoundingBoxWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if a nametag interaction would go through.
|
* Checks to see if a nametag interaction would go through.
|
||||||
*/
|
*/
|
||||||
|
|
@ -401,9 +432,25 @@ public class LivingEntity extends Entity {
|
||||||
this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f);
|
this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f);
|
||||||
newAttributes.add(createHealthAttribute());
|
newAttributes.add(createHealthAttribute());
|
||||||
}
|
}
|
||||||
|
case GENERIC_MOVEMENT_SPEED -> {
|
||||||
|
AttributeData attributeData = calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED);
|
||||||
|
newAttributes.add(attributeData);
|
||||||
|
if (this instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().setMoveSpeed(attributeData.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case GENERIC_STEP_HEIGHT -> {
|
||||||
|
if (this instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().setStepHeight((float) AttributeUtils.calculateValue(javaAttribute));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case GENERIC_GRAVITY -> {
|
||||||
|
if (this instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().setGravity(AttributeUtils.calculateValue(javaAttribute));
|
||||||
|
}
|
||||||
|
}
|
||||||
case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE));
|
case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE));
|
||||||
case GENERIC_FLYING_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED));
|
case GENERIC_FLYING_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED));
|
||||||
case GENERIC_MOVEMENT_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED));
|
|
||||||
case GENERIC_FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE));
|
case GENERIC_FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE));
|
||||||
case GENERIC_KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE));
|
case GENERIC_KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE));
|
||||||
case GENERIC_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));
|
case GENERIC_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));
|
||||||
|
|
|
||||||
|
|
@ -38,17 +38,7 @@ import java.util.UUID;
|
||||||
// Note: 1.19.4 requires that the billboard is set to something in order to show, on Java Edition
|
// Note: 1.19.4 requires that the billboard is set to something in order to show, on Java Edition
|
||||||
public class TextDisplayEntity extends DisplayBaseEntity {
|
public class TextDisplayEntity extends DisplayBaseEntity {
|
||||||
public TextDisplayEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
public TextDisplayEntity(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.add(0, definition.offset(), 0), motion, yaw, pitch, headYaw);
|
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
|
|
||||||
super.moveRelative(relX, relY + definition.offset(), relZ, yaw, pitch, isOnGround);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
|
||||||
super.moveAbsolute(position.add(Vector3f.from(0, definition.offset(), 0)), yaw, pitch, headYaw, isOnGround, teleported);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -27,20 +27,30 @@ package org.geysermc.geyser.entity.type.living.animal;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.cloudburstmc.math.vector.Vector2f;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
|
import org.geysermc.geyser.entity.type.Tickable;
|
||||||
|
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
|
||||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||||
|
import org.geysermc.geyser.item.Items;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||||
import org.geysermc.geyser.util.EntityUtils;
|
import org.geysermc.geyser.util.EntityUtils;
|
||||||
import org.geysermc.geyser.util.InteractionResult;
|
import org.geysermc.geyser.util.InteractionResult;
|
||||||
import org.geysermc.geyser.util.InteractiveTag;
|
import org.geysermc.geyser.util.InteractiveTag;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class PigEntity extends AnimalEntity {
|
public class PigEntity extends AnimalEntity implements Tickable, ClientVehicle {
|
||||||
|
private final BoostableVehicleComponent<PigEntity> vehicleComponent = new BoostableVehicleComponent<>(this, 1.0f);
|
||||||
|
|
||||||
public PigEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
public PigEntity(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);
|
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||||
|
|
@ -84,4 +94,55 @@ public class PigEntity extends AnimalEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBoost(IntEntityMetadata entityMetadata) {
|
||||||
|
vehicleComponent.startBoost(entityMetadata.getPrimitiveValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
PlayerEntity player = getPlayerPassenger();
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player == session.getPlayerEntity()) {
|
||||||
|
if (session.getPlayerInventory().isHolding(Items.CARROT_ON_A_STICK)) {
|
||||||
|
vehicleComponent.tickBoost();
|
||||||
|
}
|
||||||
|
} else { // getHand() for session player seems to always return air
|
||||||
|
ItemDefinition itemDefinition = session.getItemMappings().getStoredItems().carrotOnAStick().getBedrockDefinition();
|
||||||
|
if (player.getHand().getDefinition() == itemDefinition || player.getOffhand().getDefinition() == itemDefinition) {
|
||||||
|
vehicleComponent.tickBoost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VehicleComponent<?> getVehicleComponent() {
|
||||||
|
return vehicleComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vector2f getAdjustedInput(Vector2f input) {
|
||||||
|
return Vector2f.UNIT_Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getVehicleSpeed() {
|
||||||
|
return vehicleComponent.getMoveSpeed() * 0.225f * vehicleComponent.getBoostMultiplier();
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable PlayerEntity getPlayerPassenger() {
|
||||||
|
if (getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) instanceof PlayerEntity playerEntity) {
|
||||||
|
return playerEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClientControlled() {
|
||||||
|
return getPlayerPassenger() == session.getPlayerEntity() && session.getPlayerInventory().isHolding(Items.CARROT_ON_A_STICK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,23 +27,33 @@ package org.geysermc.geyser.entity.type.living.animal;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.cloudburstmc.math.vector.Vector2f;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
|
import org.geysermc.geyser.entity.type.Tickable;
|
||||||
|
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.BoostableVehicleComponent;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
|
||||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||||
|
import org.geysermc.geyser.item.Items;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||||
import org.geysermc.geyser.util.EntityUtils;
|
import org.geysermc.geyser.util.EntityUtils;
|
||||||
import org.geysermc.geyser.util.InteractionResult;
|
import org.geysermc.geyser.util.InteractionResult;
|
||||||
import org.geysermc.geyser.util.InteractiveTag;
|
import org.geysermc.geyser.util.InteractiveTag;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class StriderEntity extends AnimalEntity {
|
public class StriderEntity extends AnimalEntity implements Tickable, ClientVehicle {
|
||||||
|
|
||||||
|
private final BoostableVehicleComponent<StriderEntity> vehicleComponent = new BoostableVehicleComponent<>(this, 1.0f);
|
||||||
private boolean isCold = false;
|
private boolean isCold = false;
|
||||||
|
|
||||||
public StriderEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
public StriderEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||||
|
|
@ -131,4 +141,60 @@ public class StriderEntity extends AnimalEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBoost(IntEntityMetadata entityMetadata) {
|
||||||
|
vehicleComponent.startBoost(entityMetadata.getPrimitiveValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
PlayerEntity player = getPlayerPassenger();
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player == session.getPlayerEntity()) {
|
||||||
|
if (session.getPlayerInventory().isHolding(Items.WARPED_FUNGUS_ON_A_STICK)) {
|
||||||
|
vehicleComponent.tickBoost();
|
||||||
|
}
|
||||||
|
} else { // getHand() for session player seems to always return air
|
||||||
|
ItemDefinition itemDefinition = session.getItemMappings().getStoredItems().warpedFungusOnAStick().getBedrockDefinition();
|
||||||
|
if (player.getHand().getDefinition() == itemDefinition || player.getOffhand().getDefinition() == itemDefinition) {
|
||||||
|
vehicleComponent.tickBoost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VehicleComponent<?> getVehicleComponent() {
|
||||||
|
return vehicleComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vector2f getAdjustedInput(Vector2f input) {
|
||||||
|
return Vector2f.UNIT_Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getVehicleSpeed() {
|
||||||
|
return vehicleComponent.getMoveSpeed() * (isCold ? 0.35f : 0.55f) * vehicleComponent.getBoostMultiplier();
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable PlayerEntity getPlayerPassenger() {
|
||||||
|
if (getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) instanceof PlayerEntity playerEntity) {
|
||||||
|
return playerEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClientControlled() {
|
||||||
|
return getPlayerPassenger() == session.getPlayerEntity() && session.getPlayerInventory().isHolding(Items.WARPED_FUNGUS_ON_A_STICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canWalkOnLava() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,26 +25,36 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.entity.type.living.animal.horse;
|
package org.geysermc.geyser.entity.type.living.animal.horse;
|
||||||
|
|
||||||
|
import org.cloudburstmc.math.vector.Vector2f;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
|
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.CamelVehicleComponent;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.LongEntityMetadata;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class CamelEntity extends AbstractHorseEntity {
|
public class CamelEntity extends AbstractHorseEntity implements ClientVehicle {
|
||||||
|
|
||||||
public static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
|
public static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
|
||||||
|
|
||||||
|
private final CamelVehicleComponent vehicleComponent = new CamelVehicleComponent(this);
|
||||||
|
|
||||||
public CamelEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
public CamelEntity(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);
|
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||||
|
|
||||||
|
|
@ -111,5 +121,58 @@ public class CamelEntity extends AbstractHorseEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDashing(BooleanEntityMetadata entityMetadata) {
|
public void setDashing(BooleanEntityMetadata entityMetadata) {
|
||||||
|
// Java sends true to show dash animation and start the dash cooldown,
|
||||||
|
// false ends the dash animation, not the cooldown.
|
||||||
|
// Bedrock shows dash animation if HAS_DASH_COOLDOWN is set and the camel is above ground
|
||||||
|
if (entityMetadata.getPrimitiveValue()) {
|
||||||
|
setFlag(EntityFlag.HAS_DASH_COOLDOWN, true);
|
||||||
|
vehicleComponent.startDashCooldown();
|
||||||
|
} else if (!isClientControlled()) { // Don't remove dash cooldown prematurely if client is controlling
|
||||||
|
setFlag(EntityFlag.HAS_DASH_COOLDOWN, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastPoseTick(LongEntityMetadata entityMetadata) {
|
||||||
|
// Tick is based on world time. If negative, the camel is sitting.
|
||||||
|
// Must be compared to world time to know if the camel is fully standing/sitting or transitioning.
|
||||||
|
vehicleComponent.setLastPoseTick(entityMetadata.getPrimitiveValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) {
|
||||||
|
AttributeData attributeData = super.calculateAttribute(javaAttribute, type);
|
||||||
|
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_JUMP_STRENGTH) {
|
||||||
|
vehicleComponent.setHorseJumpStrength(attributeData.getValue());
|
||||||
|
}
|
||||||
|
return attributeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VehicleComponent<?> getVehicleComponent() {
|
||||||
|
return vehicleComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vector2f getAdjustedInput(Vector2f input) {
|
||||||
|
return input.mul(0.5f, input.getY() < 0 ? 0.25f : 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClientControlled() {
|
||||||
|
return getFlag(EntityFlag.SADDLED) && !passengers.isEmpty() && passengers.get(0) == session.getPlayerEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getVehicleSpeed() {
|
||||||
|
float moveSpeed = vehicleComponent.getMoveSpeed();
|
||||||
|
if (!getFlag(EntityFlag.HAS_DASH_COOLDOWN) && session.getPlayerEntity().getFlag(EntityFlag.SPRINTING)) {
|
||||||
|
return moveSpeed + 0.1f;
|
||||||
|
}
|
||||||
|
return moveSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canClimb() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||||
|
import org.geysermc.geyser.item.Items;
|
||||||
import org.geysermc.geyser.item.type.Item;
|
import org.geysermc.geyser.item.type.Item;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||||
|
|
@ -38,9 +39,13 @@ import org.geysermc.geyser.util.InteractionResult;
|
||||||
import org.geysermc.geyser.util.InteractiveTag;
|
import org.geysermc.geyser.util.InteractiveTag;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ParrotEntity extends TameableEntity {
|
public class ParrotEntity extends TameableEntity {
|
||||||
|
// Note: is the same as chicken. Reuse?
|
||||||
|
private static final Set<Item> TAMING_FOOD = Set.of(Items.WHEAT_SEEDS, Items.MELON_SEEDS, Items.PUMPKIN_SEEDS, Items.BEETROOT_SEEDS);
|
||||||
|
|
||||||
public ParrotEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
public ParrotEntity(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);
|
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,7 @@ import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.ParticleType;
|
import org.cloudburstmc.protocol.bedrock.data.ParticleType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SpawnParticleEffectPacket;
|
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
import org.geysermc.geyser.entity.type.Tickable;
|
import org.geysermc.geyser.entity.type.Tickable;
|
||||||
import org.geysermc.geyser.entity.type.living.MobEntity;
|
import org.geysermc.geyser.entity.type.living.MobEntity;
|
||||||
|
|
@ -264,7 +260,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
||||||
// so we need to manually spawn particles
|
// so we need to manually spawn particles
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket();
|
SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket();
|
||||||
spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session));
|
spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
|
||||||
spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f));
|
spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f));
|
||||||
spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire");
|
spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire");
|
||||||
spawnParticleEffectPacket.setMolangVariablesJson(Optional.empty());
|
spawnParticleEffectPacket.setMolangVariablesJson(Optional.empty());
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.cloudburstmc.math.vector.Vector2f;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||||
|
|
@ -42,6 +43,7 @@ import org.geysermc.geyser.level.BedrockDimension;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.util.AttributeUtils;
|
import org.geysermc.geyser.util.AttributeUtils;
|
||||||
import org.geysermc.geyser.util.DimensionUtils;
|
import org.geysermc.geyser.util.DimensionUtils;
|
||||||
|
import org.geysermc.geyser.util.MathUtils;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.GlobalPos;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.GlobalPos;
|
||||||
|
|
@ -64,16 +66,21 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
protected final Map<GeyserAttributeType, AttributeData> attributes = new Object2ObjectOpenHashMap<>();
|
protected final Map<GeyserAttributeType, AttributeData> attributes = new Object2ObjectOpenHashMap<>();
|
||||||
/**
|
|
||||||
* Java-only attribute
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private double blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue();
|
|
||||||
/**
|
/**
|
||||||
* Used in PlayerInputTranslator for movement checks.
|
* Used in PlayerInputTranslator for movement checks.
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
private boolean isRidingInFront;
|
private boolean isRidingInFront;
|
||||||
|
/**
|
||||||
|
* Used when emulating client-side vehicles
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private Vector2f vehicleInput = Vector2f.ZERO;
|
||||||
|
/**
|
||||||
|
* Used when emulating client-side vehicles
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private int vehicleJumpStrength;
|
||||||
|
|
||||||
private int lastAirSupply = getMaxAir();
|
private int lastAirSupply = getMaxAir();
|
||||||
|
|
||||||
|
|
@ -237,8 +244,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||||
protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) {
|
protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) {
|
||||||
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) {
|
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) {
|
||||||
session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute));
|
session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute));
|
||||||
} else if (javaAttribute.getType() == AttributeType.Builtin.PLAYER_BLOCK_INTERACTION_RANGE) {
|
|
||||||
this.blockInteractionRange = AttributeUtils.calculateValue(javaAttribute);
|
|
||||||
} else {
|
} else {
|
||||||
super.updateAttribute(javaAttribute, newAttributes);
|
super.updateAttribute(javaAttribute, newAttributes);
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +307,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||||
public void resetAttributes() {
|
public void resetAttributes() {
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue();
|
maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue();
|
||||||
blockInteractionRange = GeyserAttributeType.BLOCK_INTERACTION_RANGE.getDefaultValue();
|
|
||||||
|
|
||||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||||
attributesPacket.setRuntimeEntityId(geyserId);
|
attributesPacket.setRuntimeEntityId(geyserId);
|
||||||
|
|
@ -315,6 +319,17 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||||
this.setAirSupply(getMaxAir());
|
this.setAirSupply(getMaxAir());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setVehicleInput(Vector2f vehicleInput) {
|
||||||
|
this.vehicleInput = Vector2f.from(
|
||||||
|
MathUtils.clamp(vehicleInput.getX(), -1.0f, 1.0f),
|
||||||
|
MathUtils.clamp(vehicleInput.getY(), -1.0f, 1.0f)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVehicleJumpStrength(int vehicleJumpStrength) {
|
||||||
|
this.vehicleJumpStrength = MathUtils.constrain(vehicleJumpStrength, 0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isBelowVoidFloor() {
|
private boolean isBelowVoidFloor() {
|
||||||
return position.getY() < voidFloorPosition();
|
return position.getY() < voidFloorPosition();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 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.vehicle;
|
||||||
|
|
||||||
|
import org.cloudburstmc.math.TrigMath;
|
||||||
|
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||||
|
|
||||||
|
public class BoostableVehicleComponent<T extends LivingEntity & ClientVehicle> extends VehicleComponent<T> {
|
||||||
|
private int boostLength;
|
||||||
|
private int boostTicks = 1;
|
||||||
|
|
||||||
|
public BoostableVehicleComponent(T vehicle, float stepHeight) {
|
||||||
|
super(vehicle, stepHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startBoost(int boostLength) {
|
||||||
|
this.boostLength = boostLength;
|
||||||
|
this.boostTicks = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBoostMultiplier() {
|
||||||
|
if (isBoosting()) {
|
||||||
|
return 1.0f + 1.15f * TrigMath.sin((float) boostTicks / (float) boostLength * TrigMath.PI);
|
||||||
|
}
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBoosting() {
|
||||||
|
return boostTicks <= boostLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tickBoost() {
|
||||||
|
if (isBoosting()) {
|
||||||
|
boostTicks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 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.vehicle;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.cloudburstmc.math.vector.Vector2f;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
|
import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity;
|
||||||
|
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
||||||
|
|
||||||
|
public class CamelVehicleComponent extends VehicleComponent<CamelEntity> {
|
||||||
|
private static final int STANDING_TICKS = 52;
|
||||||
|
private static final int DASH_TICKS = 55;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private float horseJumpStrength = 0.42f; // Not sent by vanilla Java server when spawned
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private long lastPoseTick;
|
||||||
|
|
||||||
|
private int dashTick;
|
||||||
|
private int effectJumpBoost;
|
||||||
|
|
||||||
|
public CamelVehicleComponent(CamelEntity vehicle) {
|
||||||
|
super(vehicle, 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startDashCooldown() {
|
||||||
|
// tickVehicle is only called while the vehicle is mounted. Use session ticks to keep
|
||||||
|
// track of time instead of counting down
|
||||||
|
this.dashTick = vehicle.getSession().getTicks() + DASH_TICKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tickVehicle() {
|
||||||
|
if (this.dashTick != 0) {
|
||||||
|
if (vehicle.getSession().getTicks() > this.dashTick) {
|
||||||
|
vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, false);
|
||||||
|
this.dashTick = 0;
|
||||||
|
} else {
|
||||||
|
vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle.setFlag(EntityFlag.CAN_DASH, vehicle.getFlag(EntityFlag.SADDLED) && !isStationary());
|
||||||
|
vehicle.updateBedrockMetadata();
|
||||||
|
super.tickVehicle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismount() {
|
||||||
|
// Prevent camel from getting stuck in dash animation
|
||||||
|
vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, false);
|
||||||
|
vehicle.updateBedrockMetadata();
|
||||||
|
super.onDismount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean travel(VehicleContext ctx, float speed) {
|
||||||
|
if (vehicle.isOnGround() && isStationary()) {
|
||||||
|
vehicle.setMotion(vehicle.getMotion().mul(0, 1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.travel(ctx, speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Vector3f getInputVelocity(VehicleContext ctx, float speed) {
|
||||||
|
if (isStationary()) {
|
||||||
|
return Vector3f.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionPlayerEntity player = vehicle.getSession().getPlayerEntity();
|
||||||
|
Vector3f inputVelocity = super.getInputVelocity(ctx, speed);
|
||||||
|
float jumpStrength = player.getVehicleJumpStrength();
|
||||||
|
|
||||||
|
if (jumpStrength > 0) {
|
||||||
|
player.setVehicleJumpStrength(0);
|
||||||
|
|
||||||
|
if (jumpStrength >= 90) {
|
||||||
|
jumpStrength = 1.0f;
|
||||||
|
} else {
|
||||||
|
jumpStrength = 0.4f + 0.4f * jumpStrength / 90.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputVelocity.add(Vector3f.createDirectionDeg(0, -player.getYaw())
|
||||||
|
.mul(22.2222f * jumpStrength * this.moveSpeed * getVelocityMultiplier(ctx))
|
||||||
|
.up(1.4285f * jumpStrength * (this.horseJumpStrength * getJumpVelocityMultiplier(ctx) + (this.effectJumpBoost * 0.1f))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Vector2f getVehicleRotation() {
|
||||||
|
if (isStationary()) {
|
||||||
|
return Vector2f.from(vehicle.getYaw(), vehicle.getPitch());
|
||||||
|
}
|
||||||
|
return super.getVehicleRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the camel is sitting
|
||||||
|
* or transitioning to standing pose.
|
||||||
|
*/
|
||||||
|
private boolean isStationary() {
|
||||||
|
// Java checks if sitting using lastPoseTick
|
||||||
|
return this.lastPoseTick < 0 || vehicle.getSession().getWorldTicks() < this.lastPoseTick + STANDING_TICKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEffect(Effect effect, int effectAmplifier) {
|
||||||
|
if (effect == Effect.JUMP_BOOST) {
|
||||||
|
effectJumpBoost = effectAmplifier + 1;
|
||||||
|
} else {
|
||||||
|
super.setEffect(effect, effectAmplifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeEffect(Effect effect) {
|
||||||
|
if (effect == Effect.JUMP_BOOST) {
|
||||||
|
effectJumpBoost = 0;
|
||||||
|
} else {
|
||||||
|
super.removeEffect(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
@ -23,14 +23,24 @@
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.geyser.item.type;
|
package org.geysermc.geyser.entity.vehicle;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.cloudburstmc.math.vector.Vector2f;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
|
||||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
|
||||||
|
|
||||||
public interface BedrockRequiresTagItem {
|
public interface ClientVehicle {
|
||||||
|
VehicleComponent<?> getVehicleComponent();
|
||||||
|
|
||||||
void addRequiredNbt(GeyserSession session, @Nullable DataComponents components, BedrockItemBuilder builder);
|
Vector2f getAdjustedInput(Vector2f input);
|
||||||
|
|
||||||
|
float getVehicleSpeed();
|
||||||
|
|
||||||
|
boolean isClientControlled();
|
||||||
|
|
||||||
|
default boolean canWalkOnLava() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean canClimb() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,964 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2023 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.vehicle;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectDoublePair;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.cloudburstmc.math.TrigMath;
|
||||||
|
import org.cloudburstmc.math.vector.Vector2f;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3d;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket;
|
||||||
|
import org.geysermc.erosion.util.BlockPositionIterator;
|
||||||
|
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||||
|
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||||
|
import org.geysermc.geyser.level.block.Blocks;
|
||||||
|
import org.geysermc.geyser.level.block.Fluid;
|
||||||
|
import org.geysermc.geyser.level.block.property.Properties;
|
||||||
|
import org.geysermc.geyser.level.block.type.BedBlock;
|
||||||
|
import org.geysermc.geyser.level.block.type.Block;
|
||||||
|
import org.geysermc.geyser.level.block.type.BlockState;
|
||||||
|
import org.geysermc.geyser.level.block.type.TrapDoorBlock;
|
||||||
|
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||||
|
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||||
|
import org.geysermc.geyser.level.physics.Direction;
|
||||||
|
import org.geysermc.geyser.session.cache.tags.BlockTag;
|
||||||
|
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||||
|
import org.geysermc.geyser.translator.collision.SolidCollision;
|
||||||
|
import org.geysermc.geyser.util.BlockUtils;
|
||||||
|
import org.geysermc.geyser.util.MathUtils;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundMoveVehiclePacket;
|
||||||
|
|
||||||
|
public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
|
||||||
|
private static final ObjectDoublePair<Fluid> EMPTY_FLUID_PAIR = ObjectDoublePair.of(Fluid.EMPTY, 0.0);
|
||||||
|
private static final float MAX_LOGICAL_FLUID_HEIGHT = 8.0f / BlockStateValues.NUM_FLUID_LEVELS;
|
||||||
|
private static final float BASE_SLIPPERINESS_CUBED = 0.6f * 0.6f * 0.6f;
|
||||||
|
private static final float MIN_VELOCITY = 0.003f;
|
||||||
|
|
||||||
|
protected final T vehicle;
|
||||||
|
protected final BoundingBox boundingBox;
|
||||||
|
|
||||||
|
protected float stepHeight;
|
||||||
|
protected float moveSpeed;
|
||||||
|
protected double gravity;
|
||||||
|
protected int effectLevitation;
|
||||||
|
protected boolean effectSlowFalling;
|
||||||
|
protected boolean effectWeaving;
|
||||||
|
|
||||||
|
public VehicleComponent(T vehicle, float stepHeight) {
|
||||||
|
this.vehicle = vehicle;
|
||||||
|
this.stepHeight = stepHeight;
|
||||||
|
this.moveSpeed = (float) AttributeType.Builtin.GENERIC_MOVEMENT_SPEED.getDef();
|
||||||
|
this.gravity = AttributeType.Builtin.GENERIC_GRAVITY.getDef();
|
||||||
|
|
||||||
|
double width = vehicle.getBoundingBoxWidth();
|
||||||
|
double height = vehicle.getBoundingBoxHeight();
|
||||||
|
this.boundingBox = new BoundingBox(
|
||||||
|
vehicle.getPosition().getX(),
|
||||||
|
vehicle.getPosition().getY() + height / 2,
|
||||||
|
vehicle.getPosition().getZ(),
|
||||||
|
width, height, width
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWidth(float width) {
|
||||||
|
boundingBox.setSizeX(width);
|
||||||
|
boundingBox.setSizeZ(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeight(float height) {
|
||||||
|
boundingBox.translate(0, (height - boundingBox.getSizeY()) / 2, 0);
|
||||||
|
boundingBox.setSizeY(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveAbsolute(double x, double y, double z) {
|
||||||
|
boundingBox.setMiddleX(x);
|
||||||
|
boundingBox.setMiddleY(y + boundingBox.getSizeY() / 2);
|
||||||
|
boundingBox.setMiddleZ(z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveRelative(double x, double y, double z) {
|
||||||
|
boundingBox.translate(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveRelative(Vector3d vec) {
|
||||||
|
boundingBox.translate(vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoundingBox getBoundingBox() {
|
||||||
|
return this.boundingBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEffect(Effect effect, int effectAmplifier) {
|
||||||
|
switch (effect) {
|
||||||
|
case LEVITATION -> effectLevitation = effectAmplifier + 1;
|
||||||
|
case SLOW_FALLING -> effectSlowFalling = true;
|
||||||
|
case WEAVING -> effectWeaving = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeEffect(Effect effect) {
|
||||||
|
switch (effect) {
|
||||||
|
case LEVITATION -> effectLevitation = 0;
|
||||||
|
case SLOW_FALLING -> effectSlowFalling = false;
|
||||||
|
case WEAVING -> effectWeaving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMoveSpeed(float moveSpeed) {
|
||||||
|
this.moveSpeed = moveSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMoveSpeed() {
|
||||||
|
return moveSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepHeight(float stepHeight) {
|
||||||
|
this.stepHeight = MathUtils.clamp(stepHeight, 1.0f, 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGravity(double gravity) {
|
||||||
|
this.gravity = MathUtils.constrain(gravity, -1.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3d correctMovement(Vector3d movement) {
|
||||||
|
return vehicle.getSession().getCollisionManager().correctMovement(
|
||||||
|
movement, boundingBox, vehicle.isOnGround(), this.stepHeight, true, vehicle.canWalkOnLava()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMount() {
|
||||||
|
vehicle.getSession().getPlayerEntity().setVehicleInput(Vector2f.ZERO);
|
||||||
|
vehicle.getSession().getPlayerEntity().setVehicleJumpStrength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDismount() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called every session tick while the player is mounted on the vehicle.
|
||||||
|
*/
|
||||||
|
public void tickVehicle() {
|
||||||
|
if (!vehicle.isClientControlled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VehicleContext ctx = new VehicleContext();
|
||||||
|
ctx.loadSurroundingBlocks();
|
||||||
|
|
||||||
|
ObjectDoublePair<Fluid> fluidHeight = updateFluidMovement(ctx);
|
||||||
|
switch (fluidHeight.left()) {
|
||||||
|
case WATER -> waterMovement(ctx);
|
||||||
|
case LAVA -> {
|
||||||
|
if (vehicle.canWalkOnLava() && ctx.centerBlock().is(Blocks.LAVA)) {
|
||||||
|
landMovement(ctx);
|
||||||
|
} else {
|
||||||
|
lavaMovement(ctx, fluidHeight.rightDouble());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case EMPTY -> landMovement(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds velocity of all colliding fluids to the vehicle, and returns the height of the fluid to use for movement.
|
||||||
|
*
|
||||||
|
* @param ctx context
|
||||||
|
* @return type and height of fluid to use for movement
|
||||||
|
*/
|
||||||
|
protected ObjectDoublePair<Fluid> updateFluidMovement(VehicleContext ctx) {
|
||||||
|
BoundingBox box = boundingBox.clone();
|
||||||
|
box.expand(-0.001);
|
||||||
|
|
||||||
|
Vector3d min = box.getMin();
|
||||||
|
Vector3d max = box.getMax();
|
||||||
|
|
||||||
|
BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getFloorX(), min.getFloorY(), min.getFloorZ(), max.getFloorX(), max.getFloorY(), max.getFloorZ());
|
||||||
|
|
||||||
|
double waterHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.WATER, 0.014, min.getY());
|
||||||
|
double lavaHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.LAVA, vehicle.getSession().getDimensionType().ultrawarm() ? 0.007 : 0.007 / 3, min.getY());
|
||||||
|
|
||||||
|
// Apply upward motion if the vehicle is a Strider, and it is submerged in lava
|
||||||
|
if (lavaHeight > 0 && vehicle.getDefinition().entityType() == EntityType.STRIDER) {
|
||||||
|
Vector3i blockPos = ctx.centerPos().toInt();
|
||||||
|
if (!CollisionManager.FLUID_COLLISION.isBelow(blockPos.getY(), boundingBox) || ctx.getBlock(blockPos.up()).is(Blocks.LAVA)) {
|
||||||
|
vehicle.setMotion(vehicle.getMotion().mul(0.5f).add(0, 0.05f, 0));
|
||||||
|
} else {
|
||||||
|
vehicle.setOnGround(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Water movement has priority over lava movement
|
||||||
|
if (waterHeight > 0) {
|
||||||
|
return ObjectDoublePair.of(Fluid.WATER, waterHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lavaHeight > 0) {
|
||||||
|
return ObjectDoublePair.of(Fluid.LAVA, lavaHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EMPTY_FLUID_PAIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates how deep the vehicle is in a fluid, and applies its velocity.
|
||||||
|
*
|
||||||
|
* @param ctx context
|
||||||
|
* @param iter iterator of colliding blocks
|
||||||
|
* @param fluid type of fluid
|
||||||
|
* @param speed multiplier for fluid motion
|
||||||
|
* @param minY minY of the bounding box used to check for fluid collision; not exactly the same as the vehicle's bounding box
|
||||||
|
* @return height of fluid compared to minY
|
||||||
|
*/
|
||||||
|
protected double getFluidHeightAndApplyMovement(VehicleContext ctx, BlockPositionIterator iter, Fluid fluid, double speed, double minY) {
|
||||||
|
Vector3d totalVelocity = Vector3d.ZERO;
|
||||||
|
double maxFluidHeight = 0;
|
||||||
|
int fluidBlocks = 0;
|
||||||
|
|
||||||
|
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||||
|
int blockId = ctx.getBlockId(iter);
|
||||||
|
if (BlockStateValues.getFluid(blockId) != fluid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3i blockPos = Vector3i.from(iter.getX(), iter.getY(), iter.getZ());
|
||||||
|
float worldFluidHeight = getWorldFluidHeight(fluid, blockId);
|
||||||
|
|
||||||
|
double vehicleFluidHeight = blockPos.getY() + worldFluidHeight - minY;
|
||||||
|
if (vehicleFluidHeight < 0) {
|
||||||
|
// Vehicle is not submerged in this fluid block
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// flowBlocked is only used when determining if a falling fluid should drag the vehicle downwards.
|
||||||
|
// If this block is not a falling fluid, set to true to avoid unnecessary checks.
|
||||||
|
boolean flowBlocked = worldFluidHeight != 1;
|
||||||
|
|
||||||
|
Vector3d velocity = Vector3d.ZERO;
|
||||||
|
for (Direction direction : Direction.HORIZONTAL) {
|
||||||
|
Vector3i adjacentBlockPos = blockPos.add(direction.getUnitVector());
|
||||||
|
int adjacentBlockId = ctx.getBlockId(adjacentBlockPos);
|
||||||
|
Fluid adjacentFluid = BlockStateValues.getFluid(adjacentBlockId);
|
||||||
|
|
||||||
|
float fluidHeightDiff = 0;
|
||||||
|
if (adjacentFluid == fluid) {
|
||||||
|
fluidHeightDiff = getLogicalFluidHeight(fluid, blockId) - getLogicalFluidHeight(fluid, adjacentBlockId);
|
||||||
|
} else if (adjacentFluid == Fluid.EMPTY) {
|
||||||
|
// If the adjacent block is not a fluid and does not have collision,
|
||||||
|
// check if there is a fluid under it
|
||||||
|
BlockCollision adjacentBlockCollision = BlockUtils.getCollision(adjacentBlockId);
|
||||||
|
if (adjacentBlockCollision == null) {
|
||||||
|
float adjacentFluidHeight = getLogicalFluidHeight(fluid, ctx.getBlockId(adjacentBlockPos.add(Direction.DOWN.getUnitVector())));
|
||||||
|
if (adjacentFluidHeight != -1) { // Only care about same type of fluid
|
||||||
|
fluidHeightDiff = getLogicalFluidHeight(fluid, blockId) - (adjacentFluidHeight - MAX_LOGICAL_FLUID_HEIGHT);
|
||||||
|
}
|
||||||
|
} else if (!flowBlocked) {
|
||||||
|
// No need to check if flow is already blocked from another direction, or if this isn't a falling fluid.
|
||||||
|
flowBlocked = isFlowBlocked(fluid, adjacentBlockId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fluidHeightDiff != 0) {
|
||||||
|
velocity = velocity.add(direction.getUnitVector().toDouble().mul(fluidHeightDiff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (worldFluidHeight == 1) { // If falling fluid
|
||||||
|
// If flow is not blocked, check if it is blocked for the fluid above
|
||||||
|
if (!flowBlocked) {
|
||||||
|
Vector3i blockPosUp = blockPos.up();
|
||||||
|
for (Direction direction : Direction.HORIZONTAL) {
|
||||||
|
flowBlocked = isFlowBlocked(fluid, ctx.getBlockId(blockPosUp.add(direction.getUnitVector())));
|
||||||
|
if (flowBlocked) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flowBlocked) {
|
||||||
|
velocity = javaNormalize(velocity).add(0.0, -6.0, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
velocity = javaNormalize(velocity);
|
||||||
|
|
||||||
|
maxFluidHeight = Math.max(vehicleFluidHeight, maxFluidHeight);
|
||||||
|
if (maxFluidHeight < 0.4) {
|
||||||
|
velocity = velocity.mul(maxFluidHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalVelocity = totalVelocity.add(velocity);
|
||||||
|
fluidBlocks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!totalVelocity.equals(Vector3d.ZERO)) {
|
||||||
|
Vector3f motion = vehicle.getMotion();
|
||||||
|
|
||||||
|
totalVelocity = javaNormalize(totalVelocity.mul(1.0 / fluidBlocks));
|
||||||
|
totalVelocity = totalVelocity.mul(speed);
|
||||||
|
|
||||||
|
if (totalVelocity.length() < 0.0045 && Math.abs(motion.getX()) < MIN_VELOCITY && Math.abs(motion.getZ()) < MIN_VELOCITY) {
|
||||||
|
totalVelocity = javaNormalize(totalVelocity).mul(0.0045);
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle.setMotion(motion.add(totalVelocity.toFloat()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxFluidHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java edition returns the zero vector if the length of the input vector is less than 0.0001
|
||||||
|
*/
|
||||||
|
protected Vector3d javaNormalize(Vector3d vec) {
|
||||||
|
double len = vec.length();
|
||||||
|
return len < 1.0E-4 ? Vector3d.ZERO : Vector3d.from(vec.getX() / len, vec.getY() / len, vec.getZ() / len);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float getWorldFluidHeight(Fluid fluidType, int blockId) {
|
||||||
|
return (float) switch (fluidType) {
|
||||||
|
case WATER -> BlockStateValues.getWaterHeight(blockId);
|
||||||
|
case LAVA -> BlockStateValues.getLavaHeight(blockId);
|
||||||
|
case EMPTY -> -1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float getLogicalFluidHeight(Fluid fluidType, int blockId) {
|
||||||
|
return Math.min(getWorldFluidHeight(fluidType, blockId), MAX_LOGICAL_FLUID_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isFlowBlocked(Fluid fluid, int adjacentBlockId) {
|
||||||
|
if (BlockState.of(adjacentBlockId).is(Blocks.ICE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BlockStateValues.getFluid(adjacentBlockId) == fluid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: supposed to check if the opposite face of the block touching the fluid is solid, instead of SolidCollision
|
||||||
|
return BlockUtils.getCollision(adjacentBlockId) instanceof SolidCollision;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waterMovement(VehicleContext ctx) {
|
||||||
|
double gravity = getGravity();
|
||||||
|
float drag = vehicle.getFlag(EntityFlag.SPRINTING) ? 0.9f : 0.8f; // 0.8f: getBaseMovementSpeedMultiplier
|
||||||
|
double originalY = ctx.centerPos().getY();
|
||||||
|
boolean falling = vehicle.getMotion().getY() <= 0;
|
||||||
|
|
||||||
|
// NOT IMPLEMENTED: depth strider and dolphins grace
|
||||||
|
|
||||||
|
boolean horizontalCollision = travel(ctx, 0.02f);
|
||||||
|
|
||||||
|
if (horizontalCollision && isClimbing(ctx)) {
|
||||||
|
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.2f, vehicle.getMotion().getZ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle.setMotion(vehicle.getMotion().mul(drag, 0.8f, drag));
|
||||||
|
vehicle.setMotion(getFluidGravity(gravity, falling));
|
||||||
|
|
||||||
|
if (horizontalCollision && shouldApplyFluidJumpBoost(ctx, originalY)) {
|
||||||
|
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.3f, vehicle.getMotion().getZ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void lavaMovement(VehicleContext ctx, double lavaHeight) {
|
||||||
|
double gravity = getGravity();
|
||||||
|
double originalY = ctx.centerPos().getY();
|
||||||
|
boolean falling = vehicle.getMotion().getY() <= 0;
|
||||||
|
|
||||||
|
boolean horizontalCollision = travel(ctx, 0.02f);
|
||||||
|
|
||||||
|
if (lavaHeight <= (boundingBox.getSizeY() * 0.85 < 0.4 ? 0.0 : 0.4)) { // Swim height
|
||||||
|
vehicle.setMotion(vehicle.getMotion().mul(0.5f, 0.8f, 0.5f));
|
||||||
|
vehicle.setMotion(getFluidGravity(gravity, falling));
|
||||||
|
} else {
|
||||||
|
vehicle.setMotion(vehicle.getMotion().mul(0.5f));
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle.setMotion(vehicle.getMotion().down((float) (gravity / 4.0)));
|
||||||
|
|
||||||
|
if (horizontalCollision && shouldApplyFluidJumpBoost(ctx, originalY)) {
|
||||||
|
vehicle.setMotion(Vector3f.from(vehicle.getMotion().getX(), 0.3f, vehicle.getMotion().getZ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void landMovement(VehicleContext ctx) {
|
||||||
|
double gravity = getGravity();
|
||||||
|
float slipperiness = BlockStateValues.getSlipperiness(getVelocityBlock(ctx));
|
||||||
|
float drag = vehicle.isOnGround() ? 0.91f * slipperiness : 0.91f;
|
||||||
|
float speed = vehicle.getVehicleSpeed() * (vehicle.isOnGround() ? BASE_SLIPPERINESS_CUBED / (slipperiness * slipperiness * slipperiness) : 0.1f);
|
||||||
|
|
||||||
|
boolean horizontalCollision = travel(ctx, speed);
|
||||||
|
|
||||||
|
if (isClimbing(ctx)) {
|
||||||
|
Vector3f motion = vehicle.getMotion();
|
||||||
|
vehicle.setMotion(
|
||||||
|
Vector3f.from(
|
||||||
|
MathUtils.clamp(motion.getX(), -0.15f, 0.15f),
|
||||||
|
horizontalCollision ? 0.2f : Math.max(motion.getY(), -0.15f),
|
||||||
|
MathUtils.clamp(motion.getZ(), -0.15f, 0.15f)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// NOT IMPLEMENTED: climbing in powdered snow
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectLevitation > 0) {
|
||||||
|
vehicle.setMotion(vehicle.getMotion().up((0.05f * effectLevitation - vehicle.getMotion().getY()) * 0.2f));
|
||||||
|
} else {
|
||||||
|
vehicle.setMotion(vehicle.getMotion().down((float) gravity));
|
||||||
|
// NOT IMPLEMENTED: slow fall when in unloaded chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle.setMotion(vehicle.getMotion().mul(drag, 0.98f, drag));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean shouldApplyFluidJumpBoost(VehicleContext ctx, double originalY) {
|
||||||
|
BoundingBox box = boundingBox.clone();
|
||||||
|
box.translate(vehicle.getMotion().toDouble().up(0.6f - ctx.centerPos().getY() + originalY));
|
||||||
|
box.expand(-1.0E-7);
|
||||||
|
|
||||||
|
BlockPositionIterator iter = vehicle.getSession().getCollisionManager().collidableBlocksIterator(box);
|
||||||
|
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||||
|
int blockId = ctx.getBlockId(iter);
|
||||||
|
|
||||||
|
// Also check for fluids
|
||||||
|
BlockCollision blockCollision = BlockUtils.getCollision(blockId);
|
||||||
|
if (blockCollision == null && BlockStateValues.getFluid(blockId) != Fluid.EMPTY) {
|
||||||
|
blockCollision = CollisionManager.SOLID_COLLISION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockCollision != null && blockCollision.checkIntersection(iter.getX(), iter.getY(), iter.getZ(), box)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector3f getFluidGravity(double gravity, boolean falling) {
|
||||||
|
Vector3f motion = vehicle.getMotion();
|
||||||
|
if (gravity != 0 && !vehicle.getFlag(EntityFlag.SPRINTING)) {
|
||||||
|
float newY = (float) (motion.getY() - gravity / 16);
|
||||||
|
if (falling && Math.abs(motion.getY() - 0.005f) >= MIN_VELOCITY && Math.abs(newY) < MIN_VELOCITY) {
|
||||||
|
newY = -MIN_VELOCITY;
|
||||||
|
}
|
||||||
|
return Vector3f.from(motion.getX(), newY, motion.getZ());
|
||||||
|
}
|
||||||
|
return motion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any blocks the vehicle is colliding with should multiply movement. (Cobweb, powder snow, berry bush)
|
||||||
|
* <p>
|
||||||
|
* This is different from the speed factor of a block the vehicle is standing on, such as soul sand.
|
||||||
|
*
|
||||||
|
* @param ctx context
|
||||||
|
* @return the multiplier
|
||||||
|
*/
|
||||||
|
protected @Nullable Vector3f getBlockMovementMultiplier(VehicleContext ctx) {
|
||||||
|
BoundingBox box = boundingBox.clone();
|
||||||
|
box.expand(-1.0E-7);
|
||||||
|
|
||||||
|
Vector3i min = box.getMin().toInt();
|
||||||
|
Vector3i max = box.getMax().toInt();
|
||||||
|
|
||||||
|
// Iterate xyz backwards
|
||||||
|
// Minecraft iterates forwards but only the last multiplier affects movement
|
||||||
|
for (int x = max.getX(); x >= min.getX(); x--) {
|
||||||
|
for (int y = max.getY(); y >= min.getY(); y--) {
|
||||||
|
for (int z = max.getZ(); z >= min.getZ(); z--) {
|
||||||
|
Block block = ctx.getBlock(x, y, z).block();
|
||||||
|
Vector3f multiplier = null;
|
||||||
|
|
||||||
|
if (block == Blocks.COBWEB) {
|
||||||
|
if (effectWeaving) {
|
||||||
|
multiplier = Vector3f.from(0.5, 0.25, 0.5);
|
||||||
|
} else {
|
||||||
|
multiplier = Vector3f.from(0.25, 0.05f, 0.25);
|
||||||
|
}
|
||||||
|
} else if (block == Blocks.POWDER_SNOW) {
|
||||||
|
multiplier = Vector3f.from(0.9f, 1.5, 0.9f);
|
||||||
|
} else if (block == Blocks.SWEET_BERRY_BUSH) {
|
||||||
|
multiplier = Vector3f.from(0.8f, 0.75, 0.8f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multiplier != null) {
|
||||||
|
return multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyBlockCollisionEffects(VehicleContext ctx) {
|
||||||
|
BoundingBox box = boundingBox.clone();
|
||||||
|
box.expand(-1.0E-7);
|
||||||
|
|
||||||
|
Vector3i min = box.getMin().toInt();
|
||||||
|
Vector3i max = box.getMax().toInt();
|
||||||
|
|
||||||
|
BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ());
|
||||||
|
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||||
|
BlockState blockState = ctx.getBlock(iter);
|
||||||
|
|
||||||
|
if (blockState.is(Blocks.HONEY_BLOCK)) {
|
||||||
|
onHoneyBlockCollision();
|
||||||
|
} else if (blockState.is(Blocks.BUBBLE_COLUMN)) {
|
||||||
|
onBubbleColumnCollision(blockState.getValue(Properties.DRAG));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onHoneyBlockCollision() {
|
||||||
|
if (vehicle.isOnGround() || vehicle.getMotion().getY() >= -0.08f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT IMPLEMENTED: don't slide if inside the honey block
|
||||||
|
Vector3f motion = vehicle.getMotion();
|
||||||
|
float mul = motion.getY() < -0.13f ? -0.05f / motion.getY() : 1;
|
||||||
|
vehicle.setMotion(Vector3f.from(motion.getX() * mul, -0.05f, motion.getZ() * mul));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onBubbleColumnCollision(boolean drag) {
|
||||||
|
Vector3f motion = vehicle.getMotion();
|
||||||
|
vehicle.setMotion(Vector3f.from(
|
||||||
|
motion.getX(),
|
||||||
|
drag ? Math.max(-0.3f, motion.getY() - 0.03f) : Math.min(0.7f, motion.getY() + 0.06f),
|
||||||
|
motion.getZ()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the next position of the vehicle while checking for collision and adjusting velocity.
|
||||||
|
*
|
||||||
|
* @return true if there was a horizontal collision
|
||||||
|
*/
|
||||||
|
protected boolean travel(VehicleContext ctx, float speed) {
|
||||||
|
Vector3f motion = vehicle.getMotion();
|
||||||
|
|
||||||
|
// Java only does this client side
|
||||||
|
motion = motion.mul(0.98f);
|
||||||
|
|
||||||
|
motion = Vector3f.from(
|
||||||
|
Math.abs(motion.getX()) < MIN_VELOCITY ? 0 : motion.getX(),
|
||||||
|
Math.abs(motion.getY()) < MIN_VELOCITY ? 0 : motion.getY(),
|
||||||
|
Math.abs(motion.getZ()) < MIN_VELOCITY ? 0 : motion.getZ()
|
||||||
|
);
|
||||||
|
|
||||||
|
// !isImmobile
|
||||||
|
if (vehicle.isAlive()) {
|
||||||
|
motion = motion.add(getInputVelocity(ctx, speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f movementMultiplier = getBlockMovementMultiplier(ctx);
|
||||||
|
if (movementMultiplier != null) {
|
||||||
|
motion = motion.mul(movementMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check world border before blocks
|
||||||
|
Vector3d correctedMovement = vehicle.getSession().getWorldBorder().correctMovement(boundingBox, motion.toDouble());
|
||||||
|
correctedMovement = vehicle.getSession().getCollisionManager().correctMovement(
|
||||||
|
correctedMovement, boundingBox, vehicle.isOnGround(), this.stepHeight, true, vehicle.canWalkOnLava()
|
||||||
|
);
|
||||||
|
|
||||||
|
boundingBox.translate(correctedMovement);
|
||||||
|
ctx.loadSurroundingBlocks(); // Context must be reloaded after vehicle is moved
|
||||||
|
|
||||||
|
// Non-zero values indicate a collision on that axis
|
||||||
|
Vector3d moveDiff = motion.toDouble().sub(correctedMovement);
|
||||||
|
|
||||||
|
vehicle.setOnGround(moveDiff.getY() != 0 && motion.getY() < 0);
|
||||||
|
boolean horizontalCollision = moveDiff.getX() != 0 || moveDiff.getZ() != 0;
|
||||||
|
|
||||||
|
boolean bounced = false;
|
||||||
|
if (vehicle.isOnGround()) {
|
||||||
|
Block landingBlock = getLandingBlock(ctx).block();
|
||||||
|
|
||||||
|
if (landingBlock == Blocks.SLIME_BLOCK) {
|
||||||
|
motion = Vector3f.from(motion.getX(), -motion.getY(), motion.getZ());
|
||||||
|
bounced = true;
|
||||||
|
|
||||||
|
// Slow horizontal movement
|
||||||
|
float absY = Math.abs(motion.getY());
|
||||||
|
if (absY < 0.1f) {
|
||||||
|
float mul = 0.4f + absY * 0.2f;
|
||||||
|
motion = motion.mul(mul, 1.0f, mul);
|
||||||
|
}
|
||||||
|
} else if (landingBlock instanceof BedBlock) {
|
||||||
|
motion = Vector3f.from(motion.getX(), -motion.getY() * 0.66f, motion.getZ());
|
||||||
|
bounced = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set motion to 0 if a movement multiplier was used, else set to 0 on each axis with a collision
|
||||||
|
if (movementMultiplier != null) {
|
||||||
|
motion = Vector3f.ZERO;
|
||||||
|
} else {
|
||||||
|
motion = motion.mul(
|
||||||
|
moveDiff.getX() == 0 ? 1 : 0,
|
||||||
|
moveDiff.getY() == 0 || bounced ? 1 : 0,
|
||||||
|
moveDiff.getZ() == 0 ? 1 : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the new position to the bedrock client and java server
|
||||||
|
moveVehicle(ctx.centerPos());
|
||||||
|
vehicle.setMotion(motion);
|
||||||
|
|
||||||
|
applyBlockCollisionEffects(ctx);
|
||||||
|
|
||||||
|
float velocityMultiplier = getVelocityMultiplier(ctx);
|
||||||
|
vehicle.setMotion(vehicle.getMotion().mul(velocityMultiplier, 1.0f, velocityMultiplier));
|
||||||
|
|
||||||
|
return horizontalCollision;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isClimbing(VehicleContext ctx) {
|
||||||
|
if (!vehicle.canClimb()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockState blockState = ctx.centerBlock();
|
||||||
|
if (vehicle.getSession().getTagCache().is(BlockTag.CLIMBABLE, blockState.block())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the vehicle is in an open trapdoor with a ladder of the same direction under it
|
||||||
|
if (blockState.block() instanceof TrapDoorBlock && blockState.getValue(Properties.OPEN)) {
|
||||||
|
BlockState ladderState = ctx.getBlock(ctx.centerPos().toInt().down());
|
||||||
|
return ladderState.is(Blocks.LADDER) &&
|
||||||
|
ladderState.getValue(Properties.HORIZONTAL_FACING) == blockState.getValue(Properties.HORIZONTAL_FACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the player's input into velocity.
|
||||||
|
*
|
||||||
|
* @param ctx context
|
||||||
|
* @param speed multiplier for input
|
||||||
|
* @return velocity
|
||||||
|
*/
|
||||||
|
protected Vector3f getInputVelocity(VehicleContext ctx, float speed) {
|
||||||
|
Vector2f input = vehicle.getSession().getPlayerEntity().getVehicleInput();
|
||||||
|
input = input.mul(0.98f);
|
||||||
|
input = vehicle.getAdjustedInput(input);
|
||||||
|
input = normalizeInput(input);
|
||||||
|
input = input.mul(speed);
|
||||||
|
|
||||||
|
// Match player rotation
|
||||||
|
float yaw = vehicle.getSession().getPlayerEntity().getYaw();
|
||||||
|
float sin = TrigMath.sin(yaw * TrigMath.DEG_TO_RAD);
|
||||||
|
float cos = TrigMath.cos(yaw * TrigMath.DEG_TO_RAD);
|
||||||
|
return Vector3f.from(input.getX() * cos - input.getY() * sin, 0, input.getY() * cos + input.getX() * sin);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2f normalizeInput(Vector2f input) {
|
||||||
|
float lenSquared = input.lengthSquared();
|
||||||
|
if (lenSquared < 1.0E-7) {
|
||||||
|
return Vector2f.ZERO;
|
||||||
|
} else if (lenSquared > 1.0) {
|
||||||
|
return input.normalize();
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the rotation to use for the vehicle. This is based on the player's head rotation.
|
||||||
|
*/
|
||||||
|
protected Vector2f getVehicleRotation() {
|
||||||
|
LivingEntity player = vehicle.getSession().getPlayerEntity();
|
||||||
|
return Vector2f.from(player.getYaw(), player.getPitch() * 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the new position for the vehicle and sends packets to both the java server and bedrock client.
|
||||||
|
* <p>
|
||||||
|
* This also updates the session's last vehicle move timestamp.
|
||||||
|
* @param javaPos the new java position of the vehicle
|
||||||
|
*/
|
||||||
|
protected void moveVehicle(Vector3d javaPos) {
|
||||||
|
Vector3f bedrockPos = javaPos.toFloat();
|
||||||
|
Vector2f rotation = getVehicleRotation();
|
||||||
|
|
||||||
|
MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket();
|
||||||
|
moveEntityDeltaPacket.setRuntimeEntityId(vehicle.getGeyserId());
|
||||||
|
|
||||||
|
if (vehicle.isOnGround()) {
|
||||||
|
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vehicle.getPosition().getX() != bedrockPos.getX()) {
|
||||||
|
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X);
|
||||||
|
moveEntityDeltaPacket.setX(bedrockPos.getX());
|
||||||
|
}
|
||||||
|
if (vehicle.getPosition().getY() != bedrockPos.getY()) {
|
||||||
|
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y);
|
||||||
|
moveEntityDeltaPacket.setY(bedrockPos.getY());
|
||||||
|
}
|
||||||
|
if (vehicle.getPosition().getZ() != bedrockPos.getZ()) {
|
||||||
|
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z);
|
||||||
|
moveEntityDeltaPacket.setZ(bedrockPos.getZ());
|
||||||
|
}
|
||||||
|
vehicle.setPosition(bedrockPos);
|
||||||
|
|
||||||
|
if (vehicle.getYaw() != rotation.getX()) {
|
||||||
|
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
|
||||||
|
moveEntityDeltaPacket.setYaw(rotation.getX());
|
||||||
|
vehicle.setYaw(rotation.getX());
|
||||||
|
}
|
||||||
|
if (vehicle.getPitch() != rotation.getY()) {
|
||||||
|
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
|
||||||
|
moveEntityDeltaPacket.setPitch(rotation.getY());
|
||||||
|
vehicle.setPitch(rotation.getY());
|
||||||
|
}
|
||||||
|
if (vehicle.getHeadYaw() != rotation.getX()) { // Same as yaw
|
||||||
|
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
|
||||||
|
moveEntityDeltaPacket.setHeadYaw(rotation.getX());
|
||||||
|
vehicle.setHeadYaw(rotation.getX());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
|
||||||
|
vehicle.getSession().sendUpstreamPacket(moveEntityDeltaPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos.getX(), javaPos.getY(), javaPos.getZ(), rotation.getX(), rotation.getY());
|
||||||
|
vehicle.getSession().sendDownstreamPacket(moveVehiclePacket);
|
||||||
|
vehicle.getSession().setLastVehicleMoveTimestamp(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getGravity() {
|
||||||
|
if (!vehicle.getFlag(EntityFlag.HAS_GRAVITY)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vehicle.getMotion().getY() <= 0 && effectSlowFalling) {
|
||||||
|
return Math.min(0.01, this.gravity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.gravity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the position of the main block supporting the vehicle.
|
||||||
|
* Used when determining slipperiness, speed, etc.
|
||||||
|
* <p>
|
||||||
|
* Should use {@link VehicleContext#supportingBlockPos()}, instead of calling this directly.
|
||||||
|
*
|
||||||
|
* @param ctx context
|
||||||
|
* @return position of the main block supporting this entity
|
||||||
|
*/
|
||||||
|
private @Nullable Vector3i getSupportingBlockPos(VehicleContext ctx) {
|
||||||
|
Vector3i result = null;
|
||||||
|
|
||||||
|
if (vehicle.isOnGround()) {
|
||||||
|
BoundingBox box = boundingBox.clone();
|
||||||
|
box.extend(0, -1.0E-6, 0); // Extend slightly down
|
||||||
|
|
||||||
|
Vector3i min = box.getMin().toInt();
|
||||||
|
Vector3i max = box.getMax().toInt();
|
||||||
|
|
||||||
|
// Use minY as maxY
|
||||||
|
BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), min.getY(), max.getZ());
|
||||||
|
|
||||||
|
double minDistance = Double.MAX_VALUE;
|
||||||
|
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||||
|
Vector3i blockPos = Vector3i.from(iter.getX(), iter.getY(), iter.getZ());
|
||||||
|
int blockId = ctx.getBlockId(iter);
|
||||||
|
|
||||||
|
BlockCollision blockCollision;
|
||||||
|
if (vehicle.canWalkOnLava()) {
|
||||||
|
blockCollision = vehicle.getSession().getCollisionManager().getCollisionLavaWalking(blockId, blockPos.getY(), boundingBox);
|
||||||
|
} else {
|
||||||
|
blockCollision = BlockUtils.getCollision(blockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockCollision != null && blockCollision.checkIntersection(blockPos, box)) {
|
||||||
|
double distance = ctx.centerPos().distanceSquared(blockPos.toDouble().add(0.5f, 0.5f, 0.5f));
|
||||||
|
if (distance <= minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
result = blockPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the block that is x amount of blocks under the main supporting block.
|
||||||
|
*/
|
||||||
|
protected BlockState getBlockUnderSupport(VehicleContext ctx, float dist) {
|
||||||
|
Vector3i supportingBlockPos = ctx.supportingBlockPos();
|
||||||
|
|
||||||
|
Vector3i blockPos;
|
||||||
|
if (supportingBlockPos != null) {
|
||||||
|
blockPos = Vector3i.from(supportingBlockPos.getX(), Math.floor(ctx.centerPos().getY() - dist), supportingBlockPos.getZ());
|
||||||
|
} else {
|
||||||
|
blockPos = ctx.centerPos().sub(0, dist, 0).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.getBlock(blockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The block to use when determining if the vehicle should bounce after landing. Currently just slime and bed blocks.
|
||||||
|
*/
|
||||||
|
protected BlockState getLandingBlock(VehicleContext ctx) {
|
||||||
|
return getBlockUnderSupport(ctx, 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The block to use when calculating slipperiness and speed. If on a slab, this will be the block under the slab.
|
||||||
|
*/
|
||||||
|
protected BlockState getVelocityBlock(VehicleContext ctx) {
|
||||||
|
return getBlockUnderSupport(ctx, 0.500001f);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float getVelocityMultiplier(VehicleContext ctx) {
|
||||||
|
Block block = ctx.centerBlock().block();
|
||||||
|
if (block == Blocks.WATER || block == Blocks.BUBBLE_COLUMN) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block == Blocks.SOUL_SAND || block == Blocks.HONEY_BLOCK) {
|
||||||
|
return 0.4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
block = getVelocityBlock(ctx).block();
|
||||||
|
if (block == Blocks.SOUL_SAND || block == Blocks.HONEY_BLOCK) {
|
||||||
|
return 0.4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float getJumpVelocityMultiplier(VehicleContext ctx) {
|
||||||
|
Block block = ctx.centerBlock().block();
|
||||||
|
if (block == Blocks.HONEY_BLOCK) {
|
||||||
|
return 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
block = getVelocityBlock(ctx).block();
|
||||||
|
if (block == Blocks.HONEY_BLOCK) {
|
||||||
|
return 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class VehicleContext {
|
||||||
|
private Vector3d centerPos;
|
||||||
|
private Vector3d cachePos;
|
||||||
|
private BlockState centerBlock;
|
||||||
|
private Vector3i supportingBlockPos;
|
||||||
|
private BlockPositionIterator blockIter;
|
||||||
|
private int[] blocks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache frequently used data and blocks used in movement calculations.
|
||||||
|
* <p>
|
||||||
|
* Can be called multiple times, and must be called at least once before using the VehicleContext.
|
||||||
|
*/
|
||||||
|
protected void loadSurroundingBlocks() {
|
||||||
|
this.centerPos = boundingBox.getBottomCenter();
|
||||||
|
|
||||||
|
// Reuse block cache if vehicle moved less than 1 block
|
||||||
|
if (this.cachePos == null || this.cachePos.distanceSquared(this.centerPos) > 1) {
|
||||||
|
BoundingBox box = boundingBox.clone();
|
||||||
|
box.expand(2);
|
||||||
|
|
||||||
|
Vector3i min = box.getMin().toInt();
|
||||||
|
Vector3i max = box.getMax().toInt();
|
||||||
|
this.blockIter = BlockPositionIterator.fromMinMax(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ());
|
||||||
|
this.blocks = vehicle.getSession().getGeyser().getWorldManager().getBlocksAt(vehicle.getSession(), this.blockIter);
|
||||||
|
|
||||||
|
this.cachePos = this.centerPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.centerBlock = getBlock(this.centerPos.toInt());
|
||||||
|
this.supportingBlockPos = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector3d centerPos() {
|
||||||
|
return this.centerPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BlockState centerBlock() {
|
||||||
|
return this.centerBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector3i supportingBlockPos() {
|
||||||
|
if (this.supportingBlockPos == null) {
|
||||||
|
this.supportingBlockPos = getSupportingBlockPos(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.supportingBlockPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getBlockId(int x, int y, int z) {
|
||||||
|
int index = this.blockIter.getIndex(x, y, z);
|
||||||
|
if (index == -1) {
|
||||||
|
vehicle.getSession().getGeyser().getLogger().debug("[client-vehicle] Block cache miss");
|
||||||
|
return vehicle.getSession().getGeyser().getWorldManager().getBlockAt(vehicle.getSession(), x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getBlockId(Vector3i pos) {
|
||||||
|
return getBlockId(pos.getX(), pos.getY(), pos.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getBlockId(BlockPositionIterator iter) {
|
||||||
|
return getBlockId(iter.getX(), iter.getY(), iter.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BlockState getBlock(int x, int y, int z) {
|
||||||
|
return BlockState.of(getBlockId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BlockState getBlock(Vector3i pos) {
|
||||||
|
return BlockState.of(getBlockId(pos.getX(), pos.getY(), pos.getZ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BlockState getBlock(BlockPositionIterator iter) {
|
||||||
|
return BlockState.of(getBlockId(iter.getX(), iter.getY(), iter.getZ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -62,6 +62,16 @@ public class PlayerInventory extends Inventory {
|
||||||
cursor = newCursor;
|
cursor = newCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the player is holding the specified item in either hand
|
||||||
|
*
|
||||||
|
* @param item The item to look for
|
||||||
|
* @return If the player is holding the item in either hand
|
||||||
|
*/
|
||||||
|
public boolean isHolding(@NonNull Item item) {
|
||||||
|
return getItemInHand().asItem() == item || getOffhand().asItem() == item;
|
||||||
|
}
|
||||||
|
|
||||||
public GeyserItemStack getItemInHand(@NonNull Hand hand) {
|
public GeyserItemStack getItemInHand(@NonNull Hand hand) {
|
||||||
return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand();
|
return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ public class StoredItemMappings {
|
||||||
private final ItemMapping banner;
|
private final ItemMapping banner;
|
||||||
private final ItemMapping barrier;
|
private final ItemMapping barrier;
|
||||||
private final ItemMapping bow;
|
private final ItemMapping bow;
|
||||||
|
private final ItemMapping carrotOnAStick;
|
||||||
private final ItemMapping compass;
|
private final ItemMapping compass;
|
||||||
private final ItemMapping crossbow;
|
private final ItemMapping crossbow;
|
||||||
private final ItemMapping egg;
|
private final ItemMapping egg;
|
||||||
|
|
@ -52,6 +53,7 @@ public class StoredItemMappings {
|
||||||
private final ItemMapping shield;
|
private final ItemMapping shield;
|
||||||
private final ItemMapping totem;
|
private final ItemMapping totem;
|
||||||
private final ItemMapping upgradeTemplate;
|
private final ItemMapping upgradeTemplate;
|
||||||
|
private final ItemMapping warpedFungusOnAStick;
|
||||||
private final ItemMapping wheat;
|
private final ItemMapping wheat;
|
||||||
private final ItemMapping writableBook;
|
private final ItemMapping writableBook;
|
||||||
private final ItemMapping writtenBook;
|
private final ItemMapping writtenBook;
|
||||||
|
|
@ -60,6 +62,7 @@ public class StoredItemMappings {
|
||||||
this.banner = load(itemMappings, Items.WHITE_BANNER); // As of 1.17.10, all banners have the same Bedrock ID
|
this.banner = load(itemMappings, Items.WHITE_BANNER); // As of 1.17.10, all banners have the same Bedrock ID
|
||||||
this.barrier = load(itemMappings, Items.BARRIER);
|
this.barrier = load(itemMappings, Items.BARRIER);
|
||||||
this.bow = load(itemMappings, Items.BOW);
|
this.bow = load(itemMappings, Items.BOW);
|
||||||
|
this.carrotOnAStick = load(itemMappings, Items.CARROT_ON_A_STICK);
|
||||||
this.compass = load(itemMappings, Items.COMPASS);
|
this.compass = load(itemMappings, Items.COMPASS);
|
||||||
this.crossbow = load(itemMappings, Items.CROSSBOW);
|
this.crossbow = load(itemMappings, Items.CROSSBOW);
|
||||||
this.egg = load(itemMappings, Items.EGG);
|
this.egg = load(itemMappings, Items.EGG);
|
||||||
|
|
@ -69,6 +72,7 @@ public class StoredItemMappings {
|
||||||
this.shield = load(itemMappings, Items.SHIELD);
|
this.shield = load(itemMappings, Items.SHIELD);
|
||||||
this.totem = load(itemMappings, Items.TOTEM_OF_UNDYING);
|
this.totem = load(itemMappings, Items.TOTEM_OF_UNDYING);
|
||||||
this.upgradeTemplate = load(itemMappings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
|
this.upgradeTemplate = load(itemMappings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
|
||||||
|
this.warpedFungusOnAStick = load(itemMappings, Items.WARPED_FUNGUS_ON_A_STICK);
|
||||||
this.wheat = load(itemMappings, Items.WHEAT);
|
this.wheat = load(itemMappings, Items.WHEAT);
|
||||||
this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);
|
this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);
|
||||||
this.writtenBook = load(itemMappings, Items.WRITTEN_BOOK);
|
this.writtenBook = load(itemMappings, Items.WRITTEN_BOOK);
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,6 @@ package org.geysermc.geyser.item.type;
|
||||||
import org.geysermc.geyser.level.block.type.Block;
|
import org.geysermc.geyser.level.block.type.Block;
|
||||||
|
|
||||||
public class BlockItem extends Item {
|
public class BlockItem extends Item {
|
||||||
// If item is instanceof ItemNameBlockItem
|
|
||||||
private final boolean treatLikeBlock;
|
|
||||||
|
|
||||||
public BlockItem(Builder builder, Block block, Block... otherBlocks) {
|
public BlockItem(Builder builder, Block block, Block... otherBlocks) {
|
||||||
super(block.javaIdentifier().value(), builder);
|
super(block.javaIdentifier().value(), builder);
|
||||||
|
|
||||||
|
|
@ -39,7 +36,6 @@ public class BlockItem extends Item {
|
||||||
for (Block otherBlock : otherBlocks) {
|
for (Block otherBlock : otherBlocks) {
|
||||||
registerBlock(otherBlock, this);
|
registerBlock(otherBlock, this);
|
||||||
}
|
}
|
||||||
treatLikeBlock = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use this constructor if the item name is not the same as its primary block
|
// Use this constructor if the item name is not the same as its primary block
|
||||||
|
|
@ -50,14 +46,5 @@ public class BlockItem extends Item {
|
||||||
for (Block otherBlock : otherBlocks) {
|
for (Block otherBlock : otherBlocks) {
|
||||||
registerBlock(otherBlock, this);
|
registerBlock(otherBlock, this);
|
||||||
}
|
}
|
||||||
treatLikeBlock = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String translationKey() {
|
|
||||||
if (!treatLikeBlock) {
|
|
||||||
return super.translationKey();
|
|
||||||
}
|
|
||||||
return "block." + this.javaIdentifier.namespace() + "." + this.javaIdentifier.value();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,6 @@ package org.geysermc.geyser.item.type;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrays;
|
import it.unimi.dsi.fastutil.ints.IntArrays;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
||||||
import org.cloudburstmc.nbt.NbtList;
|
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
import org.cloudburstmc.nbt.NbtType;
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
|
|
@ -43,7 +41,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.Fireworks;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FireworkRocketItem extends Item implements BedrockRequiresTagItem {
|
public class FireworkRocketItem extends Item {
|
||||||
public FireworkRocketItem(String javaIdentifier, Builder builder) {
|
public FireworkRocketItem(String javaIdentifier, Builder builder) {
|
||||||
super(javaIdentifier, builder);
|
super(javaIdentifier, builder);
|
||||||
}
|
}
|
||||||
|
|
@ -60,16 +58,14 @@ public class FireworkRocketItem extends Item implements BedrockRequiresTagItem {
|
||||||
fireworksNbt.putByte("Flight", (byte) fireworks.getFlightDuration());
|
fireworksNbt.putByte("Flight", (byte) fireworks.getFlightDuration());
|
||||||
|
|
||||||
List<Fireworks.FireworkExplosion> explosions = fireworks.getExplosions();
|
List<Fireworks.FireworkExplosion> explosions = fireworks.getExplosions();
|
||||||
if (!explosions.isEmpty()) {
|
if (explosions.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
List<NbtMap> explosionNbt = new ArrayList<>();
|
List<NbtMap> explosionNbt = new ArrayList<>();
|
||||||
for (Fireworks.FireworkExplosion explosion : explosions) {
|
for (Fireworks.FireworkExplosion explosion : explosions) {
|
||||||
explosionNbt.add(translateExplosionToBedrock(explosion));
|
explosionNbt.add(translateExplosionToBedrock(explosion));
|
||||||
}
|
}
|
||||||
fireworksNbt.putList("Explosions", NbtType.COMPOUND, explosionNbt);
|
fireworksNbt.putList("Explosions", NbtType.COMPOUND, explosionNbt);
|
||||||
} else {
|
|
||||||
// This is the default firework
|
|
||||||
fireworksNbt.put("Explosions", NbtList.EMPTY);
|
|
||||||
}
|
|
||||||
builder.putCompound("Fireworks", fireworksNbt.build());
|
builder.putCompound("Fireworks", fireworksNbt.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,20 +138,4 @@ public class FireworkRocketItem extends Item implements BedrockRequiresTagItem {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addRequiredNbt(GeyserSession session, @Nullable DataComponents components, BedrockItemBuilder builder) {
|
|
||||||
if (components != null) {
|
|
||||||
Fireworks fireworks = components.get(DataComponentType.FIREWORKS);
|
|
||||||
if (fireworks != null) {
|
|
||||||
// Already translated
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NbtMapBuilder fireworksNbt = NbtMap.builder();
|
|
||||||
fireworksNbt.putByte("Flight", (byte) 1);
|
|
||||||
fireworksNbt.put("Explosions", NbtList.EMPTY);
|
|
||||||
builder.putCompound("Fireworks", fireworksNbt.build());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.item.type;
|
package org.geysermc.geyser.item.type;
|
||||||
|
|
||||||
import net.kyori.adventure.key.Key;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
@ -60,7 +59,7 @@ import java.util.Map;
|
||||||
|
|
||||||
public class Item {
|
public class Item {
|
||||||
private static final Map<Block, Item> BLOCK_TO_ITEM = new HashMap<>();
|
private static final Map<Block, Item> BLOCK_TO_ITEM = new HashMap<>();
|
||||||
protected final Key javaIdentifier;
|
private final String javaIdentifier;
|
||||||
private int javaId = -1;
|
private int javaId = -1;
|
||||||
private final int stackSize;
|
private final int stackSize;
|
||||||
private final int attackDamage;
|
private final int attackDamage;
|
||||||
|
|
@ -69,7 +68,7 @@ public class Item {
|
||||||
private final boolean glint;
|
private final boolean glint;
|
||||||
|
|
||||||
public Item(String javaIdentifier, Builder builder) {
|
public Item(String javaIdentifier, Builder builder) {
|
||||||
this.javaIdentifier = MinecraftKey.key(javaIdentifier);
|
this.javaIdentifier = MinecraftKey.key(javaIdentifier).asString().intern();
|
||||||
this.stackSize = builder.stackSize;
|
this.stackSize = builder.stackSize;
|
||||||
this.maxDamage = builder.maxDamage;
|
this.maxDamage = builder.maxDamage;
|
||||||
this.attackDamage = builder.attackDamage;
|
this.attackDamage = builder.attackDamage;
|
||||||
|
|
@ -78,7 +77,7 @@ public class Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String javaIdentifier() {
|
public String javaIdentifier() {
|
||||||
return javaIdentifier.asString();
|
return javaIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int javaId() {
|
public int javaId() {
|
||||||
|
|
@ -109,10 +108,6 @@ public class Item {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String translationKey() {
|
|
||||||
return "item." + javaIdentifier.namespace() + "." + javaIdentifier.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Translation methods to Bedrock and back */
|
/* Translation methods to Bedrock and back */
|
||||||
|
|
||||||
public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) {
|
public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) {
|
||||||
|
|
@ -175,11 +170,6 @@ public class Item {
|
||||||
builder.putInt("RepairCost", repairCost);
|
builder.putInt("RepairCost", repairCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the tag exists, it's unbreakable; the value is just weather to show the tooltip. As of Java 1.21
|
|
||||||
if (components.getDataComponents().containsKey(DataComponentType.UNBREAKABLE)) {
|
|
||||||
builder.putByte("Unbreakable", (byte) 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevents the client from trying to stack items with untranslated components
|
// Prevents the client from trying to stack items with untranslated components
|
||||||
// Relies on correct hash code implementation, and some luck
|
// Relies on correct hash code implementation, and some luck
|
||||||
builder.putInt("GeyserHash", components.hashCode()); // TODO: don't rely on this
|
builder.putInt("GeyserHash", components.hashCode()); // TODO: don't rely on this
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.item.type;
|
package org.geysermc.geyser.item.type;
|
||||||
|
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.geysermc.geyser.level.block.type.Block;
|
import org.geysermc.geyser.level.block.type.Block;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,17 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.level;
|
package org.geysermc.geyser.level;
|
||||||
|
|
||||||
import net.kyori.adventure.key.Key;
|
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
||||||
import org.geysermc.geyser.util.DimensionUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the information we store from the current Java dimension
|
* Represents the information we store from the current Java dimension
|
||||||
* @param piglinSafe Whether piglins and hoglins are safe from conversion in this dimension.
|
* @param piglinSafe Whether piglins and hoglins are safe from conversion in this dimension.
|
||||||
* This controls if they have the shaking effect applied in the dimension.
|
* This controls if they have the shaking effect applied in the dimension.
|
||||||
|
* @param ultrawarm If this dimension is ultrawarm.
|
||||||
|
* Used when calculating movement in lava for client-side vehicles.
|
||||||
*/
|
*/
|
||||||
public record JavaDimension(int minY, int maxY, boolean piglinSafe, double worldCoordinateScale, int bedrockId, boolean isNetherLike) {
|
public record JavaDimension(int minY, int maxY, boolean piglinSafe, boolean ultrawarm, double worldCoordinateScale) {
|
||||||
|
|
||||||
public static JavaDimension read(RegistryEntryContext entry) {
|
public static JavaDimension read(RegistryEntryContext entry) {
|
||||||
NbtMap dimension = entry.data();
|
NbtMap dimension = entry.data();
|
||||||
|
|
@ -45,25 +45,11 @@ public record JavaDimension(int minY, int maxY, boolean piglinSafe, double world
|
||||||
|
|
||||||
// Set if piglins/hoglins should shake
|
// Set if piglins/hoglins should shake
|
||||||
boolean piglinSafe = dimension.getBoolean("piglin_safe");
|
boolean piglinSafe = dimension.getBoolean("piglin_safe");
|
||||||
|
// Entities in lava move faster in ultrawarm dimensions
|
||||||
|
boolean ultrawarm = dimension.getBoolean("ultrawarm");
|
||||||
// Load world coordinate scale for the world border
|
// Load world coordinate scale for the world border
|
||||||
double coordinateScale = dimension.getDouble("coordinate_scale");
|
double coordinateScale = dimension.getDouble("coordinate_scale");
|
||||||
|
|
||||||
boolean isNetherLike;
|
return new JavaDimension(minY, maxY, piglinSafe, ultrawarm, coordinateScale);
|
||||||
// Cache the Bedrock version of this dimension, and base it off the ID - THE ID CAN CHANGE!!!
|
|
||||||
// https://github.com/GeyserMC/Geyser/issues/4837
|
|
||||||
int bedrockId;
|
|
||||||
Key id = entry.id();
|
|
||||||
if ("minecraft".equals(id.namespace())) {
|
|
||||||
String identifier = id.asString();
|
|
||||||
bedrockId = DimensionUtils.javaToBedrock(identifier);
|
|
||||||
isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(identifier);
|
|
||||||
} else {
|
|
||||||
// Effects should give is a clue on how this (custom) dimension is supposed to look like
|
|
||||||
String effects = dimension.getString("effects");
|
|
||||||
bedrockId = DimensionUtils.javaToBedrock(effects);
|
|
||||||
isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(effects);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JavaDimension(minY, maxY, piglinSafe, coordinateScale, bedrockId, isNetherLike);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import org.geysermc.geyser.registry.BlockRegistries;
|
||||||
* Used for block entities if the Java block state contains Bedrock block information.
|
* Used for block entities if the Java block state contains Bedrock block information.
|
||||||
*/
|
*/
|
||||||
public final class BlockStateValues {
|
public final class BlockStateValues {
|
||||||
public static final int NUM_WATER_LEVELS = 9;
|
public static final int NUM_FLUID_LEVELS = 9;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a block sticks to other blocks
|
* Checks if a block sticks to other blocks
|
||||||
|
|
@ -99,6 +99,25 @@ public final class BlockStateValues {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of fluid from the block state, including waterlogged blocks.
|
||||||
|
*
|
||||||
|
* @param state BlockState of the block
|
||||||
|
* @return The type of fluid
|
||||||
|
*/
|
||||||
|
public static Fluid getFluid(int state) {
|
||||||
|
BlockState blockState = BlockState.of(state);
|
||||||
|
if (blockState.is(Blocks.WATER) || BlockRegistries.WATERLOGGED.get().get(state)) {
|
||||||
|
return Fluid.WATER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockState.is(Blocks.LAVA)) {
|
||||||
|
return Fluid.LAVA;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Fluid.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the level of water from the block state.
|
* Get the level of water from the block state.
|
||||||
*
|
*
|
||||||
|
|
@ -127,7 +146,7 @@ public final class BlockStateValues {
|
||||||
waterLevel = 0;
|
waterLevel = 0;
|
||||||
}
|
}
|
||||||
if (waterLevel >= 0) {
|
if (waterLevel >= 0) {
|
||||||
double waterHeight = 1 - (waterLevel + 1) / ((double) NUM_WATER_LEVELS);
|
double waterHeight = 1 - (waterLevel + 1) / ((double) NUM_FLUID_LEVELS);
|
||||||
// Falling water is a full block
|
// Falling water is a full block
|
||||||
if (waterLevel >= 8) {
|
if (waterLevel >= 8) {
|
||||||
waterHeight = 1;
|
waterHeight = 1;
|
||||||
|
|
@ -137,6 +156,39 @@ public final class BlockStateValues {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the level of lava from the block state.
|
||||||
|
*
|
||||||
|
* @param state BlockState of the block
|
||||||
|
* @return The lava level or -1 if the block isn't lava
|
||||||
|
*/
|
||||||
|
public static int getLavaLevel(int state) {
|
||||||
|
BlockState blockState = BlockState.of(state);
|
||||||
|
if (!blockState.is(Blocks.LAVA)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return blockState.getValue(Properties.LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the height of lava from the block state
|
||||||
|
*
|
||||||
|
* @param state BlockState of the block
|
||||||
|
* @return The lava height or -1 if the block does not contain lava
|
||||||
|
*/
|
||||||
|
public static double getLavaHeight(int state) {
|
||||||
|
int lavaLevel = BlockStateValues.getLavaLevel(state);
|
||||||
|
if (lavaLevel >= 0) {
|
||||||
|
double lavaHeight = 1 - (lavaLevel + 1) / ((double) NUM_FLUID_LEVELS);
|
||||||
|
// Falling lava is a full block
|
||||||
|
if (lavaLevel >= 8) {
|
||||||
|
lavaHeight = 1;
|
||||||
|
}
|
||||||
|
return lavaHeight;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the slipperiness of a block.
|
* Get the slipperiness of a block.
|
||||||
* This is used in ItemEntity to calculate the friction on an item as it slides across the ground
|
* This is used in ItemEntity to calculate the friction on an item as it slides across the ground
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
@ -23,27 +23,10 @@
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.geyser.util;
|
package org.geysermc.geyser.level.block;
|
||||||
|
|
||||||
import net.raphimc.minecraftauth.util.logging.ILogger;
|
public enum Fluid {
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
WATER,
|
||||||
|
LAVA,
|
||||||
public class MinecraftAuthLogger implements ILogger {
|
EMPTY
|
||||||
|
|
||||||
public static final MinecraftAuthLogger INSTANCE = new MinecraftAuthLogger();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void info(String message) {
|
|
||||||
GeyserImpl.getInstance().getLogger().debug(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void warn(String message) {
|
|
||||||
GeyserImpl.getInstance().getLogger().warning(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void error(String message) {
|
|
||||||
GeyserImpl.getInstance().getLogger().error(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.level.block.type;
|
package org.geysermc.geyser.level.block.type;
|
||||||
|
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||||
import org.cloudburstmc.math.vector.Vector3i;
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ import org.cloudburstmc.math.vector.Vector3d;
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class BoundingBox implements Cloneable {
|
public class BoundingBox implements Cloneable {
|
||||||
|
private static final double EPSILON = 1.0E-7;
|
||||||
|
|
||||||
private double middleX;
|
private double middleX;
|
||||||
private double middleY;
|
private double middleY;
|
||||||
private double middleZ;
|
private double middleZ;
|
||||||
|
|
@ -57,10 +59,24 @@ public class BoundingBox implements Cloneable {
|
||||||
sizeZ += Math.abs(z);
|
sizeZ += Math.abs(z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void expand(double x, double y, double z) {
|
||||||
|
sizeX += x;
|
||||||
|
sizeY += y;
|
||||||
|
sizeZ += z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void translate(Vector3d translate) {
|
||||||
|
translate(translate.getX(), translate.getY(), translate.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
public void extend(Vector3d extend) {
|
public void extend(Vector3d extend) {
|
||||||
extend(extend.getX(), extend.getY(), extend.getZ());
|
extend(extend.getX(), extend.getY(), extend.getZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void expand(double expand) {
|
||||||
|
expand(expand, expand, expand);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean checkIntersection(double offsetX, double offsetY, double offsetZ, BoundingBox otherBox) {
|
public boolean checkIntersection(double offsetX, double offsetY, double offsetZ, BoundingBox otherBox) {
|
||||||
return (Math.abs((middleX + offsetX) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX())) &&
|
return (Math.abs((middleX + offsetX) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX())) &&
|
||||||
(Math.abs((middleY + offsetY) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY())) &&
|
(Math.abs((middleY + offsetY) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY())) &&
|
||||||
|
|
@ -78,6 +94,14 @@ public class BoundingBox implements Cloneable {
|
||||||
return Vector3d.from(x, y, z);
|
return Vector3d.from(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getMin(Axis axis) {
|
||||||
|
return switch (axis) {
|
||||||
|
case X -> middleX - sizeX / 2;
|
||||||
|
case Y -> middleY - sizeY / 2;
|
||||||
|
case Z -> middleZ - sizeZ / 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public Vector3d getMax() {
|
public Vector3d getMax() {
|
||||||
double x = middleX + sizeX / 2;
|
double x = middleX + sizeX / 2;
|
||||||
double y = middleY + sizeY / 2;
|
double y = middleY + sizeY / 2;
|
||||||
|
|
@ -85,15 +109,23 @@ public class BoundingBox implements Cloneable {
|
||||||
return Vector3d.from(x, y, z);
|
return Vector3d.from(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getMax(Axis axis) {
|
||||||
|
return switch (axis) {
|
||||||
|
case X -> middleX + sizeX / 2;
|
||||||
|
case Y -> middleY + sizeY / 2;
|
||||||
|
case Z -> middleZ + sizeZ / 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public Vector3d getBottomCenter() {
|
public Vector3d getBottomCenter() {
|
||||||
return Vector3d.from(middleX, middleY - sizeY / 2, middleZ);
|
return Vector3d.from(middleX, middleY - sizeY / 2, middleZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkOverlapInAxis(double xOffset, double yOffset, double zOffset, BoundingBox otherBox, Axis axis) {
|
private boolean checkOverlapInAxis(double xOffset, double yOffset, double zOffset, BoundingBox otherBox, Axis axis) {
|
||||||
return switch (axis) {
|
return switch (axis) {
|
||||||
case X -> Math.abs((middleX + xOffset) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX());
|
case X -> (sizeX + otherBox.getSizeX()) - Math.abs((middleX + xOffset) - otherBox.getMiddleX()) * 2 > EPSILON;
|
||||||
case Y -> Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY());
|
case Y -> (sizeY + otherBox.getSizeY()) - Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 > EPSILON;
|
||||||
case Z -> Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 < (sizeZ + otherBox.getSizeZ());
|
case Z -> (sizeZ + otherBox.getSizeZ()) - Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 > EPSILON;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import org.geysermc.erosion.util.BlockPositionIterator;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||||
import org.geysermc.geyser.level.block.Blocks;
|
import org.geysermc.geyser.level.block.Blocks;
|
||||||
import org.geysermc.geyser.level.block.property.Properties;
|
import org.geysermc.geyser.level.block.property.Properties;
|
||||||
|
|
@ -45,7 +46,9 @@ import org.geysermc.geyser.level.block.type.BlockState;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.PistonCache;
|
import org.geysermc.geyser.session.cache.PistonCache;
|
||||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||||
|
import org.geysermc.geyser.translator.collision.OtherCollision;
|
||||||
import org.geysermc.geyser.translator.collision.ScaffoldingCollision;
|
import org.geysermc.geyser.translator.collision.ScaffoldingCollision;
|
||||||
|
import org.geysermc.geyser.translator.collision.SolidCollision;
|
||||||
import org.geysermc.geyser.util.BlockUtils;
|
import org.geysermc.geyser.util.BlockUtils;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
|
@ -53,6 +56,8 @@ import java.text.DecimalFormatSymbols;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class CollisionManager {
|
public class CollisionManager {
|
||||||
|
public static final BlockCollision SOLID_COLLISION = new SolidCollision(null);
|
||||||
|
public static final BlockCollision FLUID_COLLISION = new OtherCollision(new BoundingBox[]{new BoundingBox(0.5, 0.25, 0.5, 1, 0.5, 1)});
|
||||||
|
|
||||||
private final GeyserSession session;
|
private final GeyserSession session;
|
||||||
|
|
||||||
|
|
@ -128,6 +133,21 @@ public class CollisionManager {
|
||||||
playerBoundingBox.setSizeY(playerHeight);
|
playerBoundingBox.setSizeY(playerHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bounding box to use for player movement.
|
||||||
|
* <p>
|
||||||
|
* This will return either the bounding box of a {@link ClientVehicle}, or the player's own bounding box.
|
||||||
|
*
|
||||||
|
* @return the bounding box to use for movement calculations
|
||||||
|
*/
|
||||||
|
public BoundingBox getActiveBoundingBox() {
|
||||||
|
if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||||
|
return clientVehicle.getVehicleComponent().getBoundingBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerBoundingBox;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
|
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
|
||||||
* the two versions. Will also send corrected movement packets back to Bedrock if they collide with pistons.
|
* the two versions. Will also send corrected movement packets back to Bedrock if they collide with pistons.
|
||||||
|
|
@ -150,6 +170,15 @@ public class CollisionManager {
|
||||||
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
|
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
|
||||||
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
|
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
|
||||||
|
|
||||||
|
// Don't correct position if controlling a vehicle
|
||||||
|
if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||||
|
playerBoundingBox.setMiddleX(position.getX());
|
||||||
|
playerBoundingBox.setMiddleY(position.getY() + playerBoundingBox.getSizeY() / 2);
|
||||||
|
playerBoundingBox.setMiddleZ(position.getZ());
|
||||||
|
|
||||||
|
return playerBoundingBox.getBottomCenter();
|
||||||
|
}
|
||||||
|
|
||||||
Vector3d startingPos = playerBoundingBox.getBottomCenter();
|
Vector3d startingPos = playerBoundingBox.getBottomCenter();
|
||||||
Vector3d movement = position.sub(startingPos);
|
Vector3d movement = position.sub(startingPos);
|
||||||
Vector3d adjustedMovement = correctPlayerMovement(movement, false, teleported);
|
Vector3d adjustedMovement = correctPlayerMovement(movement, false, teleported);
|
||||||
|
|
@ -173,7 +202,8 @@ public class CollisionManager {
|
||||||
// Send corrected position to Bedrock if they differ by too much to prevent de-syncs
|
// Send corrected position to Bedrock if they differ by too much to prevent de-syncs
|
||||||
if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) {
|
if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) {
|
||||||
PlayerEntity playerEntity = session.getPlayerEntity();
|
PlayerEntity playerEntity = session.getPlayerEntity();
|
||||||
if (pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) {
|
// Client will dismount if on a vehicle
|
||||||
|
if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) {
|
||||||
playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), newOnGround, true);
|
playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), newOnGround, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -268,13 +298,13 @@ public class CollisionManager {
|
||||||
if (teleported || (!checkWorld && session.getPistonCache().getPistons().isEmpty())) { // There is nothing to check
|
if (teleported || (!checkWorld && session.getPistonCache().getPistons().isEmpty())) { // There is nothing to check
|
||||||
return movement;
|
return movement;
|
||||||
}
|
}
|
||||||
return correctMovement(movement, playerBoundingBox, session.getPlayerEntity().isOnGround(), PLAYER_STEP_UP, checkWorld);
|
return correctMovement(movement, playerBoundingBox, session.getPlayerEntity().isOnGround(), PLAYER_STEP_UP, checkWorld, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3d correctMovement(Vector3d movement, BoundingBox boundingBox, boolean onGround, double stepUp, boolean checkWorld) {
|
public Vector3d correctMovement(Vector3d movement, BoundingBox boundingBox, boolean onGround, double stepUp, boolean checkWorld, boolean walkOnLava) {
|
||||||
Vector3d adjustedMovement = movement;
|
Vector3d adjustedMovement = movement;
|
||||||
if (!movement.equals(Vector3d.ZERO)) {
|
if (!movement.equals(Vector3d.ZERO)) {
|
||||||
adjustedMovement = correctMovementForCollisions(movement, boundingBox, checkWorld);
|
adjustedMovement = correctMovementForCollisions(movement, boundingBox, checkWorld, walkOnLava);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean verticalCollision = adjustedMovement.getY() != movement.getY();
|
boolean verticalCollision = adjustedMovement.getY() != movement.getY();
|
||||||
|
|
@ -283,26 +313,27 @@ public class CollisionManager {
|
||||||
onGround = onGround || (verticalCollision && falling);
|
onGround = onGround || (verticalCollision && falling);
|
||||||
if (onGround && horizontalCollision) {
|
if (onGround && horizontalCollision) {
|
||||||
Vector3d horizontalMovement = Vector3d.from(movement.getX(), 0, movement.getZ());
|
Vector3d horizontalMovement = Vector3d.from(movement.getX(), 0, movement.getZ());
|
||||||
Vector3d stepUpMovement = correctMovementForCollisions(horizontalMovement.up(stepUp), boundingBox, checkWorld);
|
Vector3d stepUpMovement = correctMovementForCollisions(horizontalMovement.up(stepUp), boundingBox, checkWorld, walkOnLava);
|
||||||
|
|
||||||
BoundingBox stretchedBoundingBox = boundingBox.clone();
|
BoundingBox stretchedBoundingBox = boundingBox.clone();
|
||||||
stretchedBoundingBox.extend(horizontalMovement);
|
stretchedBoundingBox.extend(horizontalMovement);
|
||||||
double maxStepUp = correctMovementForCollisions(Vector3d.from(0, stepUp, 0), stretchedBoundingBox, checkWorld).getY();
|
double maxStepUp = correctMovementForCollisions(Vector3d.from(0, stepUp, 0), stretchedBoundingBox, checkWorld, walkOnLava).getY();
|
||||||
if (maxStepUp < stepUp) { // The player collided with a block above them
|
if (maxStepUp < stepUp) { // The player collided with a block above them
|
||||||
boundingBox.translate(0, maxStepUp, 0);
|
BoundingBox stepUpBoundingBox = boundingBox.clone();
|
||||||
Vector3d adjustedStepUpMovement = correctMovementForCollisions(horizontalMovement, boundingBox, checkWorld);
|
stepUpBoundingBox.translate(0, maxStepUp, 0);
|
||||||
boundingBox.translate(0, -maxStepUp, 0);
|
|
||||||
|
|
||||||
|
Vector3d adjustedStepUpMovement = correctMovementForCollisions(horizontalMovement, stepUpBoundingBox, checkWorld, walkOnLava);
|
||||||
if (squaredHorizontalLength(adjustedStepUpMovement) > squaredHorizontalLength(stepUpMovement)) {
|
if (squaredHorizontalLength(adjustedStepUpMovement) > squaredHorizontalLength(stepUpMovement)) {
|
||||||
stepUpMovement = adjustedStepUpMovement.up(maxStepUp);
|
stepUpMovement = adjustedStepUpMovement.up(maxStepUp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (squaredHorizontalLength(stepUpMovement) > squaredHorizontalLength(adjustedMovement)) {
|
if (squaredHorizontalLength(stepUpMovement) > squaredHorizontalLength(adjustedMovement)) {
|
||||||
boundingBox.translate(stepUpMovement.getX(), stepUpMovement.getY(), stepUpMovement.getZ());
|
BoundingBox stepUpBoundingBox = boundingBox.clone();
|
||||||
|
stepUpBoundingBox.translate(stepUpMovement.getX(), stepUpMovement.getY(), stepUpMovement.getZ());
|
||||||
|
|
||||||
// Apply the player's remaining vertical movement
|
// Apply the player's remaining vertical movement
|
||||||
double verticalMovement = correctMovementForCollisions(Vector3d.from(0, movement.getY() - stepUpMovement.getY(), 0), boundingBox, checkWorld).getY();
|
double verticalMovement = correctMovementForCollisions(Vector3d.from(0, movement.getY() - stepUpMovement.getY(), 0), stepUpBoundingBox, checkWorld, walkOnLava).getY();
|
||||||
boundingBox.translate(-stepUpMovement.getX(), -stepUpMovement.getY(), -stepUpMovement.getZ());
|
|
||||||
|
|
||||||
stepUpMovement = stepUpMovement.up(verticalMovement);
|
stepUpMovement = stepUpMovement.up(verticalMovement);
|
||||||
adjustedMovement = stepUpMovement;
|
adjustedMovement = stepUpMovement;
|
||||||
|
|
@ -315,43 +346,53 @@ public class CollisionManager {
|
||||||
return vector.getX() * vector.getX() + vector.getZ() * vector.getZ();
|
return vector.getX() * vector.getX() + vector.getZ() * vector.getZ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld) {
|
private Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld, boolean walkOnLava) {
|
||||||
double movementX = movement.getX();
|
double movementX = movement.getX();
|
||||||
double movementY = movement.getY();
|
double movementY = movement.getY();
|
||||||
double movementZ = movement.getZ();
|
double movementZ = movement.getZ();
|
||||||
|
|
||||||
|
// Position might change slightly due to floating point error
|
||||||
|
double originalX = boundingBox.getMiddleX();
|
||||||
|
double originalY = boundingBox.getMiddleY();
|
||||||
|
double originalZ = boundingBox.getMiddleZ();
|
||||||
|
|
||||||
BoundingBox movementBoundingBox = boundingBox.clone();
|
BoundingBox movementBoundingBox = boundingBox.clone();
|
||||||
movementBoundingBox.extend(movement);
|
movementBoundingBox.extend(movement);
|
||||||
BlockPositionIterator iter = collidableBlocksIterator(movementBoundingBox);
|
BlockPositionIterator iter = collidableBlocksIterator(movementBoundingBox);
|
||||||
if (Math.abs(movementY) > CollisionManager.COLLISION_TOLERANCE) {
|
if (Math.abs(movementY) > CollisionManager.COLLISION_TOLERANCE) {
|
||||||
movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, iter, checkWorld);
|
movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, iter, checkWorld, walkOnLava);
|
||||||
boundingBox.translate(0, movementY, 0);
|
boundingBox.translate(0, movementY, 0);
|
||||||
}
|
}
|
||||||
boolean checkZFirst = Math.abs(movementZ) > Math.abs(movementX);
|
boolean checkZFirst = Math.abs(movementZ) > Math.abs(movementX);
|
||||||
if (checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) {
|
if (checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) {
|
||||||
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld);
|
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld, walkOnLava);
|
||||||
boundingBox.translate(0, 0, movementZ);
|
boundingBox.translate(0, 0, movementZ);
|
||||||
}
|
}
|
||||||
if (Math.abs(movementX) > CollisionManager.COLLISION_TOLERANCE) {
|
if (Math.abs(movementX) > CollisionManager.COLLISION_TOLERANCE) {
|
||||||
movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, iter, checkWorld);
|
movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, iter, checkWorld, walkOnLava);
|
||||||
boundingBox.translate(movementX, 0, 0);
|
boundingBox.translate(movementX, 0, 0);
|
||||||
}
|
}
|
||||||
if (!checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) {
|
if (!checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) {
|
||||||
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld);
|
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld, walkOnLava);
|
||||||
boundingBox.translate(0, 0, movementZ);
|
boundingBox.translate(0, 0, movementZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
boundingBox.translate(-movementX, -movementY, -movementZ);
|
boundingBox.setMiddleX(originalX);
|
||||||
|
boundingBox.setMiddleY(originalY);
|
||||||
|
boundingBox.setMiddleZ(originalZ);
|
||||||
|
|
||||||
return Vector3d.from(movementX, movementY, movementZ);
|
return Vector3d.from(movementX, movementY, movementZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, BlockPositionIterator iter, boolean checkWorld) {
|
private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, BlockPositionIterator iter, boolean checkWorld, boolean walkOnLava) {
|
||||||
for (iter.reset(); iter.hasNext(); iter.next()) {
|
for (iter.reset(); iter.hasNext(); iter.next()) {
|
||||||
int x = iter.getX();
|
int x = iter.getX();
|
||||||
int y = iter.getY();
|
int y = iter.getY();
|
||||||
int z = iter.getZ();
|
int z = iter.getZ();
|
||||||
if (checkWorld) {
|
if (checkWorld) {
|
||||||
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, x, y, z);
|
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, x, y, z);
|
||||||
|
|
||||||
|
BlockCollision blockCollision = walkOnLava ? getCollisionLavaWalking(blockId, y, boundingBox) : BlockUtils.getCollision(blockId);
|
||||||
if (blockCollision != null && !(blockCollision instanceof ScaffoldingCollision)) {
|
if (blockCollision != null && !(blockCollision instanceof ScaffoldingCollision)) {
|
||||||
offset = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, offset);
|
offset = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, offset);
|
||||||
}
|
}
|
||||||
|
|
@ -364,6 +405,16 @@ public class CollisionManager {
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the block collision appropriate for entities that can walk on lava (Strider)
|
||||||
|
*/
|
||||||
|
public BlockCollision getCollisionLavaWalking(int blockId, int blockY, BoundingBox boundingBox) {
|
||||||
|
if (BlockStateValues.getLavaLevel(blockId) == 0 && FLUID_COLLISION.isBelow(blockY, boundingBox)) {
|
||||||
|
return FLUID_COLLISION;
|
||||||
|
}
|
||||||
|
return BlockUtils.getCollision(blockId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the block located at the player's floor position plus 1 would intersect with the player,
|
* @return true if the block located at the player's floor position plus 1 would intersect with the player,
|
||||||
* were they not sneaking
|
* were they not sneaking
|
||||||
|
|
@ -417,7 +468,7 @@ public class CollisionManager {
|
||||||
double eyeY = playerBoundingBox.getMiddleY() - playerBoundingBox.getSizeY() / 2d + session.getEyeHeight();
|
double eyeY = playerBoundingBox.getMiddleY() - playerBoundingBox.getSizeY() / 2d + session.getEyeHeight();
|
||||||
double eyeZ = playerBoundingBox.getMiddleZ();
|
double eyeZ = playerBoundingBox.getMiddleZ();
|
||||||
|
|
||||||
eyeY -= 1 / ((double) BlockStateValues.NUM_WATER_LEVELS); // Subtract the height of one water layer
|
eyeY -= 1 / ((double) BlockStateValues.NUM_FLUID_LEVELS); // Subtract the height of one water layer
|
||||||
int blockID = session.getGeyser().getWorldManager().getBlockAt(session, GenericMath.floor(eyeX), GenericMath.floor(eyeY), GenericMath.floor(eyeZ));
|
int blockID = session.getGeyser().getWorldManager().getBlockAt(session, GenericMath.floor(eyeX), GenericMath.floor(eyeY), GenericMath.floor(eyeZ));
|
||||||
double waterHeight = BlockStateValues.getWaterHeight(blockID);
|
double waterHeight = BlockStateValues.getWaterHeight(blockID);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ public enum Direction {
|
||||||
EAST(4, Vector3i.UNIT_X, Axis.X, org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction.EAST);
|
EAST(4, Vector3i.UNIT_X, Axis.X, org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction.EAST);
|
||||||
|
|
||||||
public static final Direction[] VALUES = values();
|
public static final Direction[] VALUES = values();
|
||||||
|
public static final Direction[] HORIZONTAL = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST};
|
||||||
|
|
||||||
private final int reversedId;
|
private final int reversedId;
|
||||||
@Getter
|
@Getter
|
||||||
|
|
|
||||||
|
|
@ -274,10 +274,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||||
|
|
||||||
private boolean couldLoginUserByName(String bedrockUsername) {
|
private boolean couldLoginUserByName(String bedrockUsername) {
|
||||||
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
|
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
|
||||||
String authChain = geyser.authChainFor(bedrockUsername);
|
String refreshToken = geyser.refreshTokenFor(bedrockUsername);
|
||||||
if (authChain != null) {
|
if (refreshToken != null) {
|
||||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
|
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
|
||||||
session.authenticateWithAuthChain(authChain);
|
session.authenticateWithRefreshToken(refreshToken);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ public class CreativeItemRegistryPopulator {
|
||||||
private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) {
|
private static ItemData.@Nullable Builder createItemData(JsonNode itemNode, BlockMappings blockMappings, Map<String, ItemDefinition> definitions) {
|
||||||
int count = 1;
|
int count = 1;
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
|
int bedrockBlockRuntimeId;
|
||||||
NbtMap tag = null;
|
NbtMap tag = null;
|
||||||
|
|
||||||
String identifier = itemNode.get("id").textValue();
|
String identifier = itemNode.get("id").textValue();
|
||||||
|
|
|
||||||
|
|
@ -199,13 +199,7 @@ public class CustomItemRegistryPopulator {
|
||||||
computeThrowableProperties(componentBuilder);
|
computeThrowableProperties(componentBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardcoded on Java, and should extend to the custom item
|
computeRenderOffsets(false, customItemData, componentBuilder);
|
||||||
boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL)
|
|
||||||
|| javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD)
|
|
||||||
|| javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD)
|
|
||||||
|| javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD)
|
|
||||||
);
|
|
||||||
computeRenderOffsets(isHat, customItemData, componentBuilder);
|
|
||||||
|
|
||||||
componentBuilder.putCompound("item_properties", itemProperties.build());
|
componentBuilder.putCompound("item_properties", itemProperties.build());
|
||||||
builder.putCompound("components", componentBuilder.build());
|
builder.putCompound("components", componentBuilder.build());
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,6 @@ public class ItemRegistryPopulator {
|
||||||
Map<Item, ItemMapping> javaItemToMapping = new Object2ObjectOpenHashMap<>();
|
Map<Item, ItemMapping> javaItemToMapping = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
List<ItemData> creativeItems = new ArrayList<>();
|
List<ItemData> creativeItems = new ArrayList<>();
|
||||||
Set<String> noBlockDefinitions = new ObjectOpenHashSet<>();
|
|
||||||
|
|
||||||
AtomicInteger creativeNetId = new AtomicInteger();
|
AtomicInteger creativeNetId = new AtomicInteger();
|
||||||
CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> {
|
CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> {
|
||||||
|
|
@ -188,9 +187,6 @@ public class ItemRegistryPopulator {
|
||||||
bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition());
|
bedrockBlockIdOverrides.put(identifier, item.getBlockDefinition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Item mappings should also NOT have a block definition for these.
|
|
||||||
noBlockDefinitions.add(item.getDefinition().getIdentifier());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -258,12 +254,7 @@ public class ItemRegistryPopulator {
|
||||||
} else {
|
} else {
|
||||||
// Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining
|
// Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining
|
||||||
int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1);
|
int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1);
|
||||||
if (aValidBedrockBlockId == -1 && customBlockItemOverride == null) {
|
if (aValidBedrockBlockId != -1 || customBlockItemOverride != null) {
|
||||||
// Fallback
|
|
||||||
if (!noBlockDefinitions.contains(entry.getValue().getBedrockIdentifier())) {
|
|
||||||
bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// As of 1.16.220, every item requires a block runtime ID attached to it.
|
// As of 1.16.220, every item requires a block runtime ID attached to it.
|
||||||
// This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls.
|
// This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls.
|
||||||
// However, in order for some visuals and crafting to work, we need to send the first matching block state
|
// However, in order for some visuals and crafting to work, we need to send the first matching block state
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ package org.geysermc.geyser.registry.type;
|
||||||
import it.unimi.dsi.fastutil.Pair;
|
import it.unimi.dsi.fastutil.Pair;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||||
|
|
@ -43,7 +42,6 @@ import java.util.List;
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@ToString
|
|
||||||
public class ItemMapping {
|
public class ItemMapping {
|
||||||
public static final ItemMapping AIR = new ItemMapping(
|
public static final ItemMapping AIR = new ItemMapping(
|
||||||
"minecraft:air",
|
"minecraft:air",
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,9 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.session;
|
package org.geysermc.geyser.session;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||||
import com.google.gson.JsonObject;
|
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||||
|
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.EventLoop;
|
import io.netty.channel.EventLoop;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
|
@ -40,60 +41,22 @@ import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import net.raphimc.minecraftauth.step.java.StepMCProfile;
|
|
||||||
import net.raphimc.minecraftauth.step.java.StepMCToken;
|
|
||||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
|
||||||
import org.checkerframework.checker.index.qual.NonNegative;
|
import org.checkerframework.checker.index.qual.NonNegative;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.checkerframework.common.value.qual.IntRange;
|
import org.checkerframework.common.value.qual.IntRange;
|
||||||
import org.cloudburstmc.math.vector.Vector2f;
|
import org.cloudburstmc.math.vector.*;
|
||||||
import org.cloudburstmc.math.vector.Vector2i;
|
|
||||||
import org.cloudburstmc.math.vector.Vector3d;
|
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
|
||||||
import org.cloudburstmc.math.vector.Vector3i;
|
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||||
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.Ability;
|
import org.cloudburstmc.protocol.bedrock.data.*;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.AuthoritativeMovementMode;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.ChatRestrictionLevel;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.GamePublishSetting;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.GameRuleData;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.GameType;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.SpawnBiomeType;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
|
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
|
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
|
import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.CameraPresetsPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.TransferPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAbilitiesPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAdventureSettingsPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateSoftEnumPacket;
|
|
||||||
import org.cloudburstmc.protocol.common.util.OptionalBoolean;
|
import org.cloudburstmc.protocol.common.util.OptionalBoolean;
|
||||||
import org.geysermc.api.util.BedrockPlatform;
|
import org.geysermc.api.util.BedrockPlatform;
|
||||||
import org.geysermc.api.util.InputMode;
|
import org.geysermc.api.util.InputMode;
|
||||||
|
|
@ -125,6 +88,7 @@ import org.geysermc.geyser.entity.type.Entity;
|
||||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||||
import org.geysermc.geyser.entity.type.Tickable;
|
import org.geysermc.geyser.entity.type.Tickable;
|
||||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.erosion.AbstractGeyserboundPacketHandler;
|
import org.geysermc.geyser.erosion.AbstractGeyserboundPacketHandler;
|
||||||
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
||||||
import org.geysermc.geyser.impl.camera.CameraDefinitions;
|
import org.geysermc.geyser.impl.camera.CameraDefinitions;
|
||||||
|
|
@ -143,22 +107,7 @@ import org.geysermc.geyser.registry.type.BlockMappings;
|
||||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||||
import org.geysermc.geyser.session.auth.AuthData;
|
import org.geysermc.geyser.session.auth.AuthData;
|
||||||
import org.geysermc.geyser.session.auth.BedrockClientData;
|
import org.geysermc.geyser.session.auth.BedrockClientData;
|
||||||
import org.geysermc.geyser.session.cache.AdvancementsCache;
|
import org.geysermc.geyser.session.cache.*;
|
||||||
import org.geysermc.geyser.session.cache.BookEditCache;
|
|
||||||
import org.geysermc.geyser.session.cache.ChunkCache;
|
|
||||||
import org.geysermc.geyser.session.cache.EntityCache;
|
|
||||||
import org.geysermc.geyser.session.cache.EntityEffectCache;
|
|
||||||
import org.geysermc.geyser.session.cache.FormCache;
|
|
||||||
import org.geysermc.geyser.session.cache.LodestoneCache;
|
|
||||||
import org.geysermc.geyser.session.cache.PistonCache;
|
|
||||||
import org.geysermc.geyser.session.cache.PreferencesCache;
|
|
||||||
import org.geysermc.geyser.session.cache.RegistryCache;
|
|
||||||
import org.geysermc.geyser.session.cache.SkullCache;
|
|
||||||
import org.geysermc.geyser.session.cache.StructureBlockCache;
|
|
||||||
import org.geysermc.geyser.session.cache.TagCache;
|
|
||||||
import org.geysermc.geyser.session.cache.TeleportCache;
|
|
||||||
import org.geysermc.geyser.session.cache.WorldBorder;
|
|
||||||
import org.geysermc.geyser.session.cache.WorldCache;
|
|
||||||
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
import org.geysermc.geyser.text.MinecraftLocale;
|
import org.geysermc.geyser.text.MinecraftLocale;
|
||||||
|
|
@ -168,15 +117,9 @@ import org.geysermc.geyser.util.ChunkUtils;
|
||||||
import org.geysermc.geyser.util.DimensionUtils;
|
import org.geysermc.geyser.util.DimensionUtils;
|
||||||
import org.geysermc.geyser.util.EntityUtils;
|
import org.geysermc.geyser.util.EntityUtils;
|
||||||
import org.geysermc.geyser.util.LoginEncryptionUtils;
|
import org.geysermc.geyser.util.LoginEncryptionUtils;
|
||||||
import org.geysermc.geyser.util.MinecraftAuthLogger;
|
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
|
||||||
import org.geysermc.mcprotocollib.network.BuiltinFlags;
|
import org.geysermc.mcprotocollib.network.BuiltinFlags;
|
||||||
import org.geysermc.mcprotocollib.network.Session;
|
import org.geysermc.mcprotocollib.network.Session;
|
||||||
import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent;
|
import org.geysermc.mcprotocollib.network.event.session.*;
|
||||||
import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent;
|
|
||||||
import org.geysermc.mcprotocollib.network.event.session.PacketErrorEvent;
|
|
||||||
import org.geysermc.mcprotocollib.network.event.session.PacketSendingEvent;
|
|
||||||
import org.geysermc.mcprotocollib.network.event.session.SessionAdapter;
|
|
||||||
import org.geysermc.mcprotocollib.network.packet.Packet;
|
import org.geysermc.mcprotocollib.network.packet.Packet;
|
||||||
import org.geysermc.mcprotocollib.network.tcp.TcpClientSession;
|
import org.geysermc.mcprotocollib.network.tcp.TcpClientSession;
|
||||||
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
|
import org.geysermc.mcprotocollib.network.tcp.TcpSession;
|
||||||
|
|
@ -211,16 +154,7 @@ import java.net.ConnectException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
@ -230,8 +164,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@Getter
|
@Getter
|
||||||
public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
|
|
||||||
private static final Gson GSON = new Gson();
|
|
||||||
|
|
||||||
private final GeyserImpl geyser;
|
private final GeyserImpl geyser;
|
||||||
private final UpstreamSession upstream;
|
private final UpstreamSession upstream;
|
||||||
private DownstreamSession downstream;
|
private DownstreamSession downstream;
|
||||||
|
|
@ -381,6 +313,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
* The dimension of the player.
|
* The dimension of the player.
|
||||||
* As all entities are in the same world, this can be safely applied to all other entities.
|
* As all entities are in the same world, this can be safely applied to all other entities.
|
||||||
*/
|
*/
|
||||||
|
@Setter
|
||||||
|
private int dimension = DimensionUtils.OVERWORLD;
|
||||||
@MonotonicNonNull
|
@MonotonicNonNull
|
||||||
@Setter
|
@Setter
|
||||||
private JavaDimension dimensionType = null;
|
private JavaDimension dimensionType = null;
|
||||||
|
|
@ -590,6 +524,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
*/
|
*/
|
||||||
private ScheduledFuture<?> tickThread = null;
|
private ScheduledFuture<?> tickThread = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of ticks that have elapsed since the start of this session
|
||||||
|
*/
|
||||||
|
private int ticks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The world time in ticks according to the server
|
||||||
|
* <p>
|
||||||
|
* Note: The TickingStatePacket is currently ignored.
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private long worldTicks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to return the player to their original rotation after using an item in BedrockInventoryTransactionTranslator
|
* Used to return the player to their original rotation after using an item in BedrockInventoryTransactionTranslator
|
||||||
*/
|
*/
|
||||||
|
|
@ -757,7 +704,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void authenticateWithAuthChain(String authChain) {
|
public void authenticateWithRefreshToken(String refreshToken) {
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name()));
|
geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name()));
|
||||||
return;
|
return;
|
||||||
|
|
@ -766,23 +713,24 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
loggingIn = true;
|
loggingIn = true;
|
||||||
|
|
||||||
CompletableFuture.supplyAsync(() -> {
|
CompletableFuture.supplyAsync(() -> {
|
||||||
StepFullJavaSession step = PendingMicrosoftAuthentication.AUTH_FLOW.apply(true, 30);
|
MsaAuthenticationService service = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
|
||||||
StepFullJavaSession.FullJavaSession response;
|
service.setRefreshToken(refreshToken);
|
||||||
try {
|
try {
|
||||||
response = step.refresh(MinecraftAuthLogger.INSTANCE, PendingMicrosoftAuthentication.AUTH_CLIENT, step.fromJson(GSON.fromJson(authChain, JsonObject.class)));
|
service.login();
|
||||||
} catch (Exception e) {
|
} catch (RequestException e) {
|
||||||
geyser.getLogger().error("Error while attempting to use auth chain for " + bedrockUsername() + "!", e);
|
geyser.getLogger().error("Error while attempting to use refresh token for " + bedrockUsername() + "!", e);
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
StepMCProfile.MCProfile mcProfile = response.getMcProfile();
|
GameProfile profile = service.getSelectedProfile();
|
||||||
StepMCToken.MCToken mcToken = mcProfile.getMcToken();
|
if (profile == null) {
|
||||||
|
// Java account is offline
|
||||||
|
disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protocol = new MinecraftProtocol(
|
protocol = new MinecraftProtocol(profile, service.getAccessToken());
|
||||||
new GameProfile(mcProfile.getId(), mcProfile.getName()),
|
geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken());
|
||||||
mcToken.getAccessToken()
|
|
||||||
);
|
|
||||||
geyser.saveAuthChain(bedrockUsername(), GSON.toJson(step.toJson(response)));
|
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
}).whenComplete((successful, ex) -> {
|
}).whenComplete((successful, ex) -> {
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
|
|
@ -827,15 +775,25 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask(
|
final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask(
|
||||||
getAuthData().xuid()
|
getAuthData().xuid()
|
||||||
);
|
);
|
||||||
if (task.getAuthentication() != null && task.getAuthentication().isDone()) {
|
task.setOnline(true);
|
||||||
|
task.resetTimer();
|
||||||
|
|
||||||
|
if (task.getAuthentication().isDone()) {
|
||||||
onMicrosoftLoginComplete(task);
|
onMicrosoftLoginComplete(task);
|
||||||
} else {
|
} else {
|
||||||
task.resetRunningFlow();
|
task.getCode(offlineAccess).whenComplete((response, ex) -> {
|
||||||
task.performLoginAttempt(offlineAccess, code -> {
|
boolean connected = !closed;
|
||||||
if (!closed) {
|
if (ex != null) {
|
||||||
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, code);
|
if (connected) {
|
||||||
|
geyser.getLogger().error("Failed to get Microsoft auth code", ex);
|
||||||
|
disconnect(ex.toString());
|
||||||
}
|
}
|
||||||
}).handle((r, e) -> onMicrosoftLoginComplete(task));
|
task.cleanup(); // error getting auth code -> clean up immediately
|
||||||
|
} else if (connected) {
|
||||||
|
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
|
||||||
|
task.getAuthentication().whenComplete((r, $) -> onMicrosoftLoginComplete(task));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -847,21 +805,23 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
task.cleanup(); // player is online -> remove pending authentication immediately
|
task.cleanup(); // player is online -> remove pending authentication immediately
|
||||||
return task.getAuthentication().handle((result, ex) -> {
|
Throwable ex = task.getLoginException();
|
||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
||||||
disconnect(ex.toString());
|
disconnect(ex.toString());
|
||||||
return false;
|
} else {
|
||||||
}
|
MsaAuthenticationService service = task.getMsaAuthenticationService();
|
||||||
|
GameProfile selectedProfile = service.getSelectedProfile();
|
||||||
StepMCProfile.MCProfile mcProfile = result.session().getMcProfile();
|
if (selectedProfile == null) {
|
||||||
StepMCToken.MCToken mcToken = mcProfile.getMcToken();
|
disconnect(GeyserLocale.getPlayerLocaleString(
|
||||||
|
"geyser.network.remote.invalid_account",
|
||||||
|
clientData.getLanguageCode()
|
||||||
|
));
|
||||||
|
} else {
|
||||||
this.protocol = new MinecraftProtocol(
|
this.protocol = new MinecraftProtocol(
|
||||||
new GameProfile(mcProfile.getId(), mcProfile.getName()),
|
selectedProfile,
|
||||||
mcToken.getAccessToken()
|
service.getAccessToken()
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
connectDownstream();
|
connectDownstream();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
|
@ -869,10 +829,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save our auth chain for later use
|
// Save our refresh token for later use
|
||||||
geyser.saveAuthChain(bedrockUsername(), GSON.toJson(result.step().toJson(result.session())));
|
geyser.saveRefreshToken(bedrockUsername(), service.getRefreshToken());
|
||||||
return true;
|
return true;
|
||||||
}).getNow(false);
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1155,7 +1117,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
if (authData != null) {
|
if (authData != null) {
|
||||||
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid());
|
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid());
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
task.resetRunningFlow();
|
task.setOnline(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1249,6 +1211,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
isInWorldBorderWarningArea = false;
|
isInWorldBorderWarningArea = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entity vehicle = playerEntity.getVehicle();
|
||||||
|
if (vehicle instanceof ClientVehicle clientVehicle && vehicle.isValid()) {
|
||||||
|
clientVehicle.getVehicleComponent().tickVehicle();
|
||||||
|
}
|
||||||
|
|
||||||
for (Tickable entity : entityCache.getTickableEntities()) {
|
for (Tickable entity : entityCache.getTickableEntities()) {
|
||||||
entity.tick();
|
entity.tick();
|
||||||
|
|
@ -1284,6 +1250,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ticks++;
|
||||||
|
worldTicks++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticationData(AuthData authData) {
|
public void setAuthenticationData(AuthData authData) {
|
||||||
|
|
|
||||||
|
|
@ -25,44 +25,27 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.session;
|
package org.geysermc.geyser.session;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
|
||||||
|
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||||
|
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import net.lenni0451.commons.httpclient.HttpClient;
|
|
||||||
import net.raphimc.minecraftauth.MinecraftAuth;
|
|
||||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
|
||||||
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
|
|
||||||
import net.raphimc.minecraftauth.util.MicrosoftConstants;
|
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.GeyserLogger;
|
import org.geysermc.geyser.GeyserLogger;
|
||||||
import org.geysermc.geyser.util.MinecraftAuthLogger;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.io.Serial;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pending Microsoft authentication task cache.
|
* Pending Microsoft authentication task cache.
|
||||||
* It permits user to exit the server while they authorize Geyser to access their Microsoft account.
|
* It permits user to exit the server while they authorize Geyser to access their Microsoft account.
|
||||||
*/
|
*/
|
||||||
public class PendingMicrosoftAuthentication {
|
public class PendingMicrosoftAuthentication {
|
||||||
public static final HttpClient AUTH_CLIENT = MinecraftAuth.createHttpClient();
|
|
||||||
public static final BiFunction<Boolean, Integer, StepFullJavaSession> AUTH_FLOW = (offlineAccess, timeoutSec) -> MinecraftAuth.builder()
|
|
||||||
.withClientId(GeyserImpl.OAUTH_CLIENT_ID)
|
|
||||||
.withScope(offlineAccess ? "XboxLive.signin XboxLive.offline_access" : "XboxLive.signin")
|
|
||||||
.withTimeout(timeoutSec)
|
|
||||||
.deviceCode()
|
|
||||||
.withoutDeviceToken()
|
|
||||||
.regularAuthentication(MicrosoftConstants.JAVA_XSTS_RELYING_PARTY)
|
|
||||||
.buildMinecraftJavaProfileStep(false);
|
|
||||||
/**
|
/**
|
||||||
* For GeyserConnect usage.
|
* For GeyserConnect usage.
|
||||||
*/
|
*/
|
||||||
|
|
@ -74,8 +57,8 @@ public class PendingMicrosoftAuthentication {
|
||||||
.build(new CacheLoader<>() {
|
.build(new CacheLoader<>() {
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationTask load(@NonNull String userKey) {
|
public AuthenticationTask load(@NonNull String userKey) {
|
||||||
return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds)
|
return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds * 1000L)
|
||||||
: new AuthenticationTask(userKey, timeoutSeconds);
|
: new AuthenticationTask(userKey, timeoutSeconds * 1000L);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -97,23 +80,37 @@ public class PendingMicrosoftAuthentication {
|
||||||
public class AuthenticationTask {
|
public class AuthenticationTask {
|
||||||
private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS);
|
private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
private final String userKey;
|
|
||||||
private final int timeoutSec;
|
|
||||||
@Getter
|
@Getter
|
||||||
private CompletableFuture<StepChainResult> authentication;
|
private final MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
|
||||||
|
private final String userKey;
|
||||||
|
private final long timeoutMs;
|
||||||
|
|
||||||
private AuthenticationTask(String userKey, int timeoutSec) {
|
private long remainingTimeMs;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private boolean online = true;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final CompletableFuture<MsaAuthenticationService> authentication;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private volatile Throwable loginException;
|
||||||
|
|
||||||
|
private AuthenticationTask(String userKey, long timeoutMs) {
|
||||||
this.userKey = userKey;
|
this.userKey = userKey;
|
||||||
this.timeoutSec = timeoutSec;
|
this.timeoutMs = timeoutMs;
|
||||||
|
this.remainingTimeMs = timeoutMs;
|
||||||
|
|
||||||
|
this.authentication = new CompletableFuture<>();
|
||||||
|
this.authentication.whenComplete((r, ex) -> {
|
||||||
|
this.loginException = ex;
|
||||||
|
// avoid memory leak, in case player doesn't connect again
|
||||||
|
CompletableFuture.delayedExecutor(timeoutMs, TimeUnit.MILLISECONDS).execute(this::cleanup);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetRunningFlow() {
|
public void resetTimer() {
|
||||||
if (authentication == null) {
|
this.remainingTimeMs = this.timeoutMs;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt the current flow
|
|
||||||
this.authentication.cancel(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
|
|
@ -124,18 +121,52 @@ public class PendingMicrosoftAuthentication {
|
||||||
authentications.invalidate(userKey);
|
authentications.invalidate(userKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<StepChainResult> performLoginAttempt(boolean offlineAccess, Consumer<StepMsaDeviceCode.MsaDeviceCode> deviceCodeConsumer) {
|
public CompletableFuture<MsaAuthenticationService.MsCodeResponse> getCode(boolean offlineAccess) {
|
||||||
return authentication = CompletableFuture.supplyAsync(() -> {
|
// Request the code
|
||||||
|
CompletableFuture<MsaAuthenticationService.MsCodeResponse> code = CompletableFuture.supplyAsync(
|
||||||
|
() -> tryGetCode(offlineAccess));
|
||||||
|
// Once the code is received, continuously try to request the access token, profile, etc
|
||||||
|
code.thenRun(() -> performLoginAttempt(System.currentTimeMillis()));
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param offlineAccess whether we want a refresh token for later use.
|
||||||
|
*/
|
||||||
|
private MsaAuthenticationService.MsCodeResponse tryGetCode(boolean offlineAccess) throws CompletionException {
|
||||||
try {
|
try {
|
||||||
StepFullJavaSession step = AUTH_FLOW.apply(offlineAccess, timeoutSec);
|
return msaAuthenticationService.getAuthCode(offlineAccess);
|
||||||
return new StepChainResult(step, step.getFromInput(MinecraftAuthLogger.INSTANCE, AUTH_CLIENT, new StepMsaDeviceCode.MsaDeviceCodeCallback(deviceCodeConsumer)));
|
} catch (RequestException e) {
|
||||||
} catch (Exception e) {
|
|
||||||
throw new CompletionException(e);
|
throw new CompletionException(e);
|
||||||
}
|
}
|
||||||
}, DELAYED_BY_ONE_SECOND).whenComplete((r, ex) -> {
|
}
|
||||||
// avoid memory leak, in case player doesn't connect again
|
|
||||||
CompletableFuture.delayedExecutor(timeoutSec, TimeUnit.SECONDS).execute(this::cleanup);
|
private void performLoginAttempt(long lastAttempt) {
|
||||||
});
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
msaAuthenticationService.login();
|
||||||
|
} catch (AuthPendingException e) {
|
||||||
|
long currentAttempt = System.currentTimeMillis();
|
||||||
|
if (!online) {
|
||||||
|
// decrement timer only when player's offline
|
||||||
|
remainingTimeMs -= currentAttempt - lastAttempt;
|
||||||
|
if (remainingTimeMs <= 0L) {
|
||||||
|
// time's up
|
||||||
|
authentication.completeExceptionally(new TaskTimeoutException());
|
||||||
|
cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try again in 1 second
|
||||||
|
performLoginAttempt(currentAttempt);
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
authentication.completeExceptionally(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// login successful
|
||||||
|
authentication.complete(msaAuthenticationService);
|
||||||
|
}, DELAYED_BY_ONE_SECOND);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -150,11 +181,22 @@ public class PendingMicrosoftAuthentication {
|
||||||
private String server;
|
private String server;
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
private ProxyAuthenticationTask(String userKey, int timeoutSec) {
|
private ProxyAuthenticationTask(String userKey, long timeoutMs) {
|
||||||
super(userKey, timeoutSec);
|
super(userKey, timeoutMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record StepChainResult(StepFullJavaSession step, StepFullJavaSession.FullJavaSession session) {
|
/**
|
||||||
|
* @see PendingMicrosoftAuthentication
|
||||||
|
*/
|
||||||
|
public static class TaskTimeoutException extends Exception {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
TaskTimeoutException() {
|
||||||
|
super("It took too long to authorize Geyser to access your Microsoft account. " +
|
||||||
|
"Please request new code and try again.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.level.physics.Axis;
|
import org.geysermc.geyser.level.physics.Axis;
|
||||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
@ -119,6 +121,12 @@ public class PistonCache {
|
||||||
private void sendPlayerMovement() {
|
private void sendPlayerMovement() {
|
||||||
if (!playerDisplacement.equals(Vector3d.ZERO) && playerMotion.equals(Vector3f.ZERO)) {
|
if (!playerDisplacement.equals(Vector3d.ZERO) && playerMotion.equals(Vector3f.ZERO)) {
|
||||||
SessionPlayerEntity playerEntity = session.getPlayerEntity();
|
SessionPlayerEntity playerEntity = session.getPlayerEntity();
|
||||||
|
|
||||||
|
Entity vehicle = playerEntity.getVehicle();
|
||||||
|
if (vehicle instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
boolean isOnGround = playerDisplacement.getY() > 0 || playerEntity.isOnGround();
|
boolean isOnGround = playerDisplacement.getY() > 0 || playerEntity.isOnGround();
|
||||||
Vector3d position = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter();
|
Vector3d position = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter();
|
||||||
playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), isOnGround, true);
|
playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), isOnGround, true);
|
||||||
|
|
@ -128,6 +136,13 @@ public class PistonCache {
|
||||||
private void sendPlayerMotion() {
|
private void sendPlayerMotion() {
|
||||||
if (!playerMotion.equals(Vector3f.ZERO)) {
|
if (!playerMotion.equals(Vector3f.ZERO)) {
|
||||||
SessionPlayerEntity playerEntity = session.getPlayerEntity();
|
SessionPlayerEntity playerEntity = session.getPlayerEntity();
|
||||||
|
|
||||||
|
Entity vehicle = playerEntity.getVehicle();
|
||||||
|
if (vehicle instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||||
|
vehicle.setMotion(playerMotion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
playerEntity.setMotion(playerMotion);
|
playerEntity.setMotion(playerMotion);
|
||||||
|
|
||||||
SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket();
|
SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket();
|
||||||
|
|
@ -149,10 +164,15 @@ public class PistonCache {
|
||||||
totalDisplacement = totalDisplacement.max(-0.51d, -0.51d, -0.51d).min(0.51d, 0.51d, 0.51d);
|
totalDisplacement = totalDisplacement.max(-0.51d, -0.51d, -0.51d).min(0.51d, 0.51d, 0.51d);
|
||||||
|
|
||||||
Vector3d delta = totalDisplacement.sub(playerDisplacement);
|
Vector3d delta = totalDisplacement.sub(playerDisplacement);
|
||||||
// Check if the piston is pushing a player into collision
|
|
||||||
delta = session.getCollisionManager().correctPlayerMovement(delta, true, false);
|
|
||||||
|
|
||||||
|
// Check if the piston is pushing a player into collision
|
||||||
|
if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||||
|
delta = clientVehicle.getVehicleComponent().correctMovement(delta);
|
||||||
|
clientVehicle.getVehicleComponent().moveRelative(delta);
|
||||||
|
} else {
|
||||||
|
delta = session.getCollisionManager().correctPlayerMovement(delta, true, false);
|
||||||
session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ());
|
session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
playerDisplacement = totalDisplacement;
|
playerDisplacement = totalDisplacement;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.session.cache;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.cloudburstmc.math.GenericMath;
|
import org.cloudburstmc.math.GenericMath;
|
||||||
import org.cloudburstmc.math.vector.Vector2d;
|
import org.cloudburstmc.math.vector.Vector2d;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3d;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.LevelEventType;
|
import org.cloudburstmc.protocol.bedrock.data.LevelEventType;
|
||||||
|
|
@ -36,8 +37,12 @@ import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||||
|
import org.geysermc.geyser.level.physics.Axis;
|
||||||
|
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
||||||
|
import static org.geysermc.geyser.level.physics.CollisionManager.COLLISION_TOLERANCE;
|
||||||
|
|
||||||
public class WorldBorder {
|
public class WorldBorder {
|
||||||
private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D;
|
private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D;
|
||||||
|
|
||||||
|
|
@ -190,6 +195,53 @@ public class WorldBorder {
|
||||||
return entityPosition.getX() > warningMinX && entityPosition.getX() < warningMaxX && entityPosition.getZ() > warningMinZ && entityPosition.getZ() < warningMaxZ;
|
return entityPosition.getX() > warningMinX && entityPosition.getX() < warningMaxX && entityPosition.getZ() > warningMinZ && entityPosition.getZ() < warningMaxZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the movement of an entity so that it does not cross the world border.
|
||||||
|
*
|
||||||
|
* @param boundingBox bounding box of the entity
|
||||||
|
* @param movement movement of the entity
|
||||||
|
* @return the corrected movement
|
||||||
|
*/
|
||||||
|
public Vector3d correctMovement(BoundingBox boundingBox, Vector3d movement) {
|
||||||
|
double correctedX;
|
||||||
|
if (movement.getX() < 0) {
|
||||||
|
correctedX = -limitMovement(-movement.getX(), boundingBox.getMin(Axis.X) - GenericMath.floor(minX));
|
||||||
|
} else {
|
||||||
|
correctedX = limitMovement(movement.getX(), GenericMath.ceil(maxX) - boundingBox.getMax(Axis.X));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outside of border, don't adjust movement
|
||||||
|
if (Double.isNaN(correctedX)) {
|
||||||
|
return movement;
|
||||||
|
}
|
||||||
|
|
||||||
|
double correctedZ;
|
||||||
|
if (movement.getZ() < 0) {
|
||||||
|
correctedZ = -limitMovement(-movement.getZ(), boundingBox.getMin(Axis.Z) - GenericMath.floor(minZ));
|
||||||
|
} else {
|
||||||
|
correctedZ = limitMovement(movement.getZ(), GenericMath.ceil(maxZ) - boundingBox.getMax(Axis.Z));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Double.isNaN(correctedZ)) {
|
||||||
|
return movement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Vector3d.from(correctedX, movement.getY(), correctedZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double limitMovement(double movement, double limit) {
|
||||||
|
if (limit < 0) {
|
||||||
|
// Return NaN to indicate outside of border
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit < COLLISION_TOLERANCE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(movement, limit);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the world border's minimum and maximum properties
|
* Updates the world border's minimum and maximum properties
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,11 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.skin;
|
package org.geysermc.geyser.skin;
|
||||||
|
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile.Texture;
|
import com.github.steveice10.mc.auth.data.GameProfile.Texture;
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile.TextureModel;
|
import com.github.steveice10.mc.auth.data.GameProfile.TextureModel;
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
|
import com.github.steveice10.mc.auth.data.GameProfile.TextureType;
|
||||||
|
import com.github.steveice10.mc.auth.exception.property.PropertyException;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
@ -112,7 +113,12 @@ public class FakeHeadProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<TextureType, Texture> textures = profile.getTextures(false);
|
Map<TextureType, Texture> textures = null;
|
||||||
|
try {
|
||||||
|
textures = profile.getTextures(false);
|
||||||
|
} catch (PropertyException e) {
|
||||||
|
session.getGeyser().getLogger().debug("Failed to get textures from GameProfile: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
if (textures == null || textures.isEmpty()) {
|
if (textures == null || textures.isEmpty()) {
|
||||||
loadHead(session, entity, profile.getName());
|
loadHead(session, entity, profile.getName());
|
||||||
|
|
|
||||||
|
|
@ -166,4 +166,22 @@ public class BlockCollision {
|
||||||
}
|
}
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this block collision is below the given bounding box.
|
||||||
|
*
|
||||||
|
* @param blockY the y position of the block in the world
|
||||||
|
* @param boundingBox the bounding box to compare
|
||||||
|
* @return true if this block collision is below the bounding box
|
||||||
|
*/
|
||||||
|
public boolean isBelow(int blockY, BoundingBox boundingBox) {
|
||||||
|
double minY = boundingBox.getMiddleY() - boundingBox.getSizeY() / 2;
|
||||||
|
for (BoundingBox b : boundingBoxes) {
|
||||||
|
double offset = blockY + b.getMiddleY() + b.getSizeY() / 2 - minY;
|
||||||
|
if (offset > CollisionManager.COLLISION_TOLERANCE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,9 +25,10 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.item;
|
package org.geysermc.geyser.translator.item;
|
||||||
|
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile.Texture;
|
import com.github.steveice10.mc.auth.data.GameProfile.Texture;
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
|
import com.github.steveice10.mc.auth.data.GameProfile.TextureType;
|
||||||
|
import com.github.steveice10.mc.auth.exception.property.PropertyException;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
@ -44,7 +45,6 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||||
import org.geysermc.geyser.item.Items;
|
import org.geysermc.geyser.item.Items;
|
||||||
import org.geysermc.geyser.item.components.Rarity;
|
import org.geysermc.geyser.item.components.Rarity;
|
||||||
import org.geysermc.geyser.item.type.Item;
|
import org.geysermc.geyser.item.type.Item;
|
||||||
import org.geysermc.geyser.item.type.BedrockRequiresTagItem;
|
|
||||||
import org.geysermc.geyser.level.block.type.Block;
|
import org.geysermc.geyser.level.block.type.Block;
|
||||||
import org.geysermc.geyser.registry.BlockRegistries;
|
import org.geysermc.geyser.registry.BlockRegistries;
|
||||||
import org.geysermc.geyser.registry.Registries;
|
import org.geysermc.geyser.registry.Registries;
|
||||||
|
|
@ -148,12 +148,6 @@ public final class ItemTranslator {
|
||||||
if (components.get(DataComponentType.HIDE_TOOLTIP) != null) hideTooltips = true;
|
if (components.get(DataComponentType.HIDE_TOOLTIP) != null) hideTooltips = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixes fireworks crafting recipe: they always contain a tag
|
|
||||||
// TODO remove once all items have their default components
|
|
||||||
if (javaItem instanceof BedrockRequiresTagItem requiresTagItem) {
|
|
||||||
requiresTagItem.addRequiredNbt(session, components, nbtBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rarity rarity = javaItem.rarity();
|
Rarity rarity = javaItem.rarity();
|
||||||
boolean enchantmentGlint = javaItem.glint();
|
boolean enchantmentGlint = javaItem.glint();
|
||||||
if (components != null) {
|
if (components != null) {
|
||||||
|
|
@ -486,7 +480,12 @@ public final class ItemTranslator {
|
||||||
|
|
||||||
GameProfile profile = components.get(DataComponentType.PROFILE);
|
GameProfile profile = components.get(DataComponentType.PROFILE);
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
Map<TextureType, Texture> textures = profile.getTextures(false);
|
Map<TextureType, Texture> textures = null;
|
||||||
|
try {
|
||||||
|
textures = profile.getTextures(false);
|
||||||
|
} catch (PropertyException e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().debug("Failed to get textures from GameProfile: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
if (textures == null || textures.isEmpty()) {
|
if (textures == null || textures.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,10 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
|
||||||
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
|
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
|
||||||
List<NbtMap> items = javaNbt.getList("Items", NbtType.COMPOUND);
|
List<NbtMap> items = javaNbt.getList("Items", NbtType.COMPOUND);
|
||||||
if (items != null) {
|
if (items != null) {
|
||||||
|
int i = 1;
|
||||||
for (NbtMap itemTag : items) {
|
for (NbtMap itemTag : items) {
|
||||||
int slot = itemTag.getByte("Slot") + 1;
|
bedrockNbt.put("Item" + i, getItem(session, itemTag));
|
||||||
bedrockNbt.put("Item" + slot, getItem(session, itemTag));
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +55,8 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
|
||||||
if (mapping == null) {
|
if (mapping == null) {
|
||||||
mapping = ItemMapping.AIR;
|
mapping = ItemMapping.AIR;
|
||||||
}
|
}
|
||||||
NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getInt("count"), mapping.getBedrockData());
|
NbtMapBuilder tagBuilder = BedrockItemBuilder.createItemNbt(mapping, tag.getByte("Count"), mapping.getBedrockData());
|
||||||
|
tagBuilder.put("tag", NbtMap.builder().build()); // I don't think this is necessary... - Camo, 1.20.5/1.20.80
|
||||||
return tagBuilder.build();
|
return tagBuilder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
||||||
import org.geysermc.geyser.api.util.PlatformType;
|
import org.geysermc.geyser.api.util.PlatformType;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||||
import org.geysermc.geyser.level.block.Blocks;
|
import org.geysermc.geyser.level.block.Blocks;
|
||||||
import org.geysermc.geyser.level.block.property.Properties;
|
import org.geysermc.geyser.level.block.property.Properties;
|
||||||
|
|
@ -348,18 +349,31 @@ public class PistonBlockEntity {
|
||||||
blockMovement = 1f - lastProgress;
|
blockMovement = 1f - lastProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox();
|
boolean onGround;
|
||||||
|
BoundingBox playerBoundingBox;
|
||||||
|
if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) {
|
||||||
|
onGround = session.getPlayerEntity().getVehicle().isOnGround();
|
||||||
|
playerBoundingBox = clientVehicle.getVehicleComponent().getBoundingBox();
|
||||||
|
} else {
|
||||||
|
onGround = session.getPlayerEntity().isOnGround();
|
||||||
|
playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox();
|
||||||
|
}
|
||||||
|
|
||||||
// Shrink the collision in the other axes slightly, to avoid false positives when pressed up against the side of blocks
|
// Shrink the collision in the other axes slightly, to avoid false positives when pressed up against the side of blocks
|
||||||
Vector3d shrink = Vector3i.ONE.sub(direction.abs()).toDouble().mul(CollisionManager.COLLISION_TOLERANCE * 2);
|
Vector3d shrink = Vector3i.ONE.sub(direction.abs()).toDouble().mul(CollisionManager.COLLISION_TOLERANCE * 2);
|
||||||
playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() - shrink.getX());
|
double sizeX = playerBoundingBox.getSizeX();
|
||||||
playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() - shrink.getY());
|
double sizeY = playerBoundingBox.getSizeY();
|
||||||
playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() - shrink.getZ());
|
double sizeZ = playerBoundingBox.getSizeZ();
|
||||||
|
|
||||||
|
playerBoundingBox.setSizeX(sizeX - shrink.getX());
|
||||||
|
playerBoundingBox.setSizeY(sizeY - shrink.getY());
|
||||||
|
playerBoundingBox.setSizeZ(sizeZ - shrink.getZ());
|
||||||
|
|
||||||
// Resolve collision with the piston head
|
// Resolve collision with the piston head
|
||||||
BlockState pistonHeadId = Blocks.PISTON_HEAD.defaultBlockState()
|
BlockState pistonHeadId = Blocks.PISTON_HEAD.defaultBlockState()
|
||||||
.withValue(Properties.SHORT, false)
|
.withValue(Properties.SHORT, false)
|
||||||
.withValue(Properties.FACING, orientation);
|
.withValue(Properties.FACING, orientation);
|
||||||
pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox);
|
pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox, onGround);
|
||||||
|
|
||||||
// Resolve collision with any attached moving blocks, but skip slime blocks
|
// Resolve collision with any attached moving blocks, but skip slime blocks
|
||||||
// This prevents players from being launched by slime blocks covered by other blocks
|
// This prevents players from being launched by slime blocks covered by other blocks
|
||||||
|
|
@ -367,7 +381,7 @@ public class PistonBlockEntity {
|
||||||
BlockState state = entry.getValue();
|
BlockState state = entry.getValue();
|
||||||
if (!state.is(Blocks.SLIME_BLOCK)) {
|
if (!state.is(Blocks.SLIME_BLOCK)) {
|
||||||
Vector3d blockPos = entry.getKey().toDouble();
|
Vector3d blockPos = entry.getKey().toDouble();
|
||||||
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox);
|
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox, onGround);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Resolve collision with slime blocks
|
// Resolve collision with slime blocks
|
||||||
|
|
@ -375,14 +389,14 @@ public class PistonBlockEntity {
|
||||||
BlockState state = entry.getValue();
|
BlockState state = entry.getValue();
|
||||||
if (state.is(Blocks.SLIME_BLOCK)) {
|
if (state.is(Blocks.SLIME_BLOCK)) {
|
||||||
Vector3d blockPos = entry.getKey().toDouble();
|
Vector3d blockPos = entry.getKey().toDouble();
|
||||||
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox);
|
pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox, onGround);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undo shrink
|
// Undo shrink
|
||||||
playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() + shrink.getX());
|
playerBoundingBox.setSizeX(sizeX);
|
||||||
playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() + shrink.getY());
|
playerBoundingBox.setSizeY(sizeY);
|
||||||
playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() + shrink.getZ());
|
playerBoundingBox.setSizeZ(sizeZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -392,20 +406,22 @@ public class PistonBlockEntity {
|
||||||
* @param playerBoundingBox The player's bounding box
|
* @param playerBoundingBox The player's bounding box
|
||||||
* @return True if the player attached, otherwise false
|
* @return True if the player attached, otherwise false
|
||||||
*/
|
*/
|
||||||
private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox) {
|
private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox, boolean onGround) {
|
||||||
if (orientation.isVertical()) {
|
if (orientation.isVertical()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return session.getPlayerEntity().isOnGround() && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox);
|
return onGround && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches a player if the player is on the pushing side of the slime block
|
* Launches a player if the player is on the pushing side of the slime block
|
||||||
*
|
*
|
||||||
* @param blockPos The position of the slime block
|
* @param blockPos The position of the slime block
|
||||||
* @param playerPos The player's position
|
* @param playerBoundingBox The player's bounding box
|
||||||
*/
|
*/
|
||||||
private void applySlimeBlockMotion(Vector3d blockPos, Vector3d playerPos) {
|
private void applySlimeBlockMotion(Vector3d blockPos, BoundingBox playerBoundingBox) {
|
||||||
|
Vector3d playerPos = Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ());
|
||||||
|
|
||||||
Direction movementDirection = orientation;
|
Direction movementDirection = orientation;
|
||||||
// Invert direction when pulling
|
// Invert direction when pulling
|
||||||
if (action == PistonValueType.PULLING) {
|
if (action == PistonValueType.PULLING) {
|
||||||
|
|
@ -471,7 +487,7 @@ public class PistonBlockEntity {
|
||||||
return maxIntersection;
|
return maxIntersection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox) {
|
private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox, boolean onGround) {
|
||||||
PistonCache pistonCache = session.getPistonCache();
|
PistonCache pistonCache = session.getPistonCache();
|
||||||
Vector3d movement = getMovement().toDouble();
|
Vector3d movement = getMovement().toDouble();
|
||||||
// Check if the player collides with the movingBlock block entity
|
// Check if the player collides with the movingBlock block entity
|
||||||
|
|
@ -481,12 +497,12 @@ public class PistonBlockEntity {
|
||||||
|
|
||||||
if (state.is(Blocks.SLIME_BLOCK)) {
|
if (state.is(Blocks.SLIME_BLOCK)) {
|
||||||
pistonCache.setPlayerSlimeCollision(true);
|
pistonCache.setPlayerSlimeCollision(true);
|
||||||
applySlimeBlockMotion(finalBlockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ()));
|
applySlimeBlockMotion(finalBlockPos, playerBoundingBox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3d blockPos = startingPos.add(movement.mul(blockMovement));
|
Vector3d blockPos = startingPos.add(movement.mul(blockMovement));
|
||||||
if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox)) {
|
if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox, onGround)) {
|
||||||
pistonCache.setPlayerCollided(true);
|
pistonCache.setPlayerCollided(true);
|
||||||
pistonCache.setPlayerAttachedToHoney(true);
|
pistonCache.setPlayerAttachedToHoney(true);
|
||||||
|
|
||||||
|
|
@ -509,7 +525,7 @@ public class PistonBlockEntity {
|
||||||
|
|
||||||
if (state.is(Blocks.SLIME_BLOCK)) {
|
if (state.is(Blocks.SLIME_BLOCK)) {
|
||||||
pistonCache.setPlayerSlimeCollision(true);
|
pistonCache.setPlayerSlimeCollision(true);
|
||||||
applySlimeBlockMotion(blockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ()));
|
applySlimeBlockMotion(blockPos, playerBoundingBox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -585,7 +601,7 @@ public class PistonBlockEntity {
|
||||||
movingBlockMap.put(getPistonHeadPos(), this);
|
movingBlockMap.put(getPistonHeadPos(), this);
|
||||||
|
|
||||||
Vector3i movement = getMovement();
|
Vector3i movement = getMovement();
|
||||||
BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox().clone();
|
BoundingBox playerBoundingBox = session.getCollisionManager().getActiveBoundingBox().clone();
|
||||||
if (orientation == Direction.UP) {
|
if (orientation == Direction.UP) {
|
||||||
// Extend the bounding box down, to catch collisions when the player is falling down
|
// Extend the bounding box down, to catch collisions when the player is falling down
|
||||||
playerBoundingBox.extend(0, -256, 0);
|
playerBoundingBox.extend(0, -256, 0);
|
||||||
|
|
@ -629,17 +645,19 @@ public class PistonBlockEntity {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
placedFinalBlocks = true;
|
placedFinalBlocks = true;
|
||||||
|
|
||||||
Vector3i movement = getMovement();
|
Vector3i movement = getMovement();
|
||||||
|
BoundingBox playerBoundingBox = session.getCollisionManager().getActiveBoundingBox().clone();
|
||||||
attachedBlocks.forEach((blockPos, state) -> {
|
attachedBlocks.forEach((blockPos, state) -> {
|
||||||
blockPos = blockPos.add(movement);
|
blockPos = blockPos.add(movement);
|
||||||
// Don't place blocks that collide with the player
|
// Don't place blocks that collide with the player
|
||||||
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
|
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), playerBoundingBox)) {
|
||||||
ChunkUtils.updateBlock(session, state, blockPos);
|
ChunkUtils.updateBlock(session, state, blockPos);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (action == PistonValueType.PUSHING) {
|
if (action == PistonValueType.PUSHING) {
|
||||||
Vector3i pistonHeadPos = getPistonHeadPos().add(movement);
|
Vector3i pistonHeadPos = getPistonHeadPos().add(movement);
|
||||||
if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
|
if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), playerBoundingBox)) {
|
||||||
ChunkUtils.updateBlock(session, Blocks.PISTON_HEAD.defaultBlockState()
|
ChunkUtils.updateBlock(session, Blocks.PISTON_HEAD.defaultBlockState()
|
||||||
.withValue(Properties.SHORT, false)
|
.withValue(Properties.SHORT, false)
|
||||||
.withValue(Properties.FACING, orientation), pistonHeadPos);
|
.withValue(Properties.FACING, orientation), pistonHeadPos);
|
||||||
|
|
|
||||||
|
|
@ -70,11 +70,7 @@ import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.geyser.util.BlockUtils;
|
import org.geysermc.geyser.util.*;
|
||||||
import org.geysermc.geyser.util.CooldownUtils;
|
|
||||||
import org.geysermc.geyser.util.EntityUtils;
|
|
||||||
import org.geysermc.geyser.util.InteractionResult;
|
|
||||||
import org.geysermc.geyser.util.InventoryUtils;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||||
|
|
@ -82,11 +78,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractActio
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.*;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
@ -98,6 +90,11 @@ import java.util.concurrent.TimeUnit;
|
||||||
@Translator(packet = InventoryTransactionPacket.class)
|
@Translator(packet = InventoryTransactionPacket.class)
|
||||||
public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> {
|
public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> {
|
||||||
|
|
||||||
|
private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f;
|
||||||
|
private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49;
|
||||||
|
private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36;
|
||||||
|
private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, InventoryTransactionPacket packet) {
|
public void translate(GeyserSession session, InventoryTransactionPacket packet) {
|
||||||
if (packet.getTransactionType() == InventoryTransactionType.NORMAL && packet.getActions().size() == 3) {
|
if (packet.getTransactionType() == InventoryTransactionType.NORMAL && packet.getActions().size() == 3) {
|
||||||
|
|
@ -246,11 +243,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// As of 1.21, Paper does not have any additional range checks that would inconvenience normal players.
|
// CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch
|
||||||
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
||||||
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
|
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
|
||||||
|
|
||||||
if (!canInteractWithBlock(session, playerPosition, packetBlockPosition)) {
|
boolean creative = session.getGameMode() == GameMode.CREATIVE;
|
||||||
|
|
||||||
|
float diffX = playerPosition.getX() - packetBlockPosition.getX();
|
||||||
|
float diffY = playerPosition.getY() - packetBlockPosition.getY();
|
||||||
|
float diffZ = playerPosition.getZ() - packetBlockPosition.getZ();
|
||||||
|
if (((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) >
|
||||||
|
(creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
|
||||||
restoreCorrectBlock(session, blockPos, packet);
|
restoreCorrectBlock(session, blockPos, packet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -259,8 +262,26 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||||
double clickPositionFullY = (double) packetBlockPosition.getY() + (double) packet.getClickPosition().getY();
|
double clickPositionFullY = (double) packetBlockPosition.getY() + (double) packet.getClickPosition().getY();
|
||||||
double clickPositionFullZ = (double) packetBlockPosition.getZ() + (double) packet.getClickPosition().getZ();
|
double clickPositionFullZ = (double) packetBlockPosition.getZ() + (double) packet.getClickPosition().getZ();
|
||||||
|
|
||||||
Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f);
|
// More recent Paper check - https://github.com/PaperMC/Paper/blob/87e11bf7fdf48ecdf3e1cae383c368b9b61d7df9/patches/server/0470-Move-range-check-for-block-placing-up.patch
|
||||||
|
double clickDiffX = playerPosition.getX() - clickPositionFullX;
|
||||||
|
double clickDiffY = playerPosition.getY() - clickPositionFullY;
|
||||||
|
double clickDiffZ = playerPosition.getZ() - clickPositionFullZ;
|
||||||
|
if (((clickDiffX * clickDiffX) + (clickDiffY * clickDiffY) + (clickDiffZ * clickDiffZ)) >
|
||||||
|
(creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
|
||||||
|
restoreCorrectBlock(session, blockPos, packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f blockCenter = Vector3f.from(packetBlockPosition.getX() + 0.5f, packetBlockPosition.getY() + 0.5f, packetBlockPosition.getZ() + 0.5f);
|
||||||
|
// Vanilla check
|
||||||
|
if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0)
|
||||||
|
.distanceSquared(blockCenter) < MAXIMUM_BLOCK_PLACING_DISTANCE)) {
|
||||||
|
// The client thinks that its blocks have been successfully placed. Restore the server's blocks instead.
|
||||||
|
restoreCorrectBlock(session, blockPos, packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// More recent vanilla check (as of 1.18.2)
|
||||||
double clickDistanceX = clickPositionFullX - blockCenter.getX();
|
double clickDistanceX = clickPositionFullX - blockCenter.getX();
|
||||||
double clickDistanceY = clickPositionFullY - blockCenter.getY();
|
double clickDistanceY = clickPositionFullY - blockCenter.getY();
|
||||||
double clickDistanceZ = clickPositionFullZ - blockCenter.getZ();
|
double clickDistanceZ = clickPositionFullZ - blockCenter.getZ();
|
||||||
|
|
@ -282,16 +303,13 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Storing the block position allows inconsistencies in block place checking from post-1.19 - pre-1.20.5 to be resolved.
|
|
||||||
int sequence = session.getWorldCache().nextPredictionSequence();
|
|
||||||
session.getWorldCache().markPositionInSequence(blockPos);
|
|
||||||
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
|
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
|
||||||
packet.getBlockPosition(),
|
packet.getBlockPosition(),
|
||||||
Direction.VALUES[packet.getBlockFace()],
|
Direction.VALUES[packet.getBlockFace()],
|
||||||
Hand.MAIN_HAND,
|
Hand.MAIN_HAND,
|
||||||
packet.getClickPosition().getX(), packet.getClickPosition().getY(), packet.getClickPosition().getZ(),
|
packet.getClickPosition().getX(), packet.getClickPosition().getY(), packet.getClickPosition().getZ(),
|
||||||
false,
|
false,
|
||||||
sequence);
|
session.getWorldCache().nextPredictionSequence());
|
||||||
session.sendDownstreamGamePacket(blockPacket);
|
session.sendDownstreamGamePacket(blockPacket);
|
||||||
|
|
||||||
Item item = session.getPlayerInventory().getItemInHand().asItem();
|
Item item = session.getPlayerInventory().getItemInHand().asItem();
|
||||||
|
|
@ -415,10 +433,14 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is working out the distance using 3d Pythagoras and the extra value added to the Y is the sneaking height of a java player.
|
||||||
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
||||||
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
|
Vector3f floatBlockPosition = packet.getBlockPosition().toFloat();
|
||||||
|
float diffX = playerPosition.getX() - (floatBlockPosition.getX() + 0.5f);
|
||||||
if (!canInteractWithBlock(session, playerPosition, packet.getBlockPosition())) {
|
float diffY = (playerPosition.getY() - EntityDefinitions.PLAYER.offset()) - (floatBlockPosition.getY() + 0.5f) + 1.5f;
|
||||||
|
float diffZ = playerPosition.getZ() - (floatBlockPosition.getZ() + 0.5f);
|
||||||
|
float distanceSquared = diffX * diffX + diffY * diffY + diffZ * diffZ;
|
||||||
|
if (distanceSquared > MAXIMUM_BLOCK_DESTROYING_DISTANCE) {
|
||||||
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
|
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -528,28 +550,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canInteractWithBlock(GeyserSession session, Vector3f playerPosition, Vector3i packetBlockPosition) {
|
|
||||||
// ViaVersion sends this 1.20.5+ attribute also, so older servers will have correct range checks.
|
|
||||||
double blockInteractionRange = session.getPlayerEntity().getBlockInteractionRange();
|
|
||||||
|
|
||||||
// Mojmap Player#canInteractWithBlock
|
|
||||||
double additionalRangeCheck = blockInteractionRange + 1.0d;
|
|
||||||
|
|
||||||
// AABB.<init>(BlockPos)
|
|
||||||
float minX = packetBlockPosition.getX();
|
|
||||||
float minY = packetBlockPosition.getY();
|
|
||||||
float minZ = packetBlockPosition.getZ();
|
|
||||||
float maxX = packetBlockPosition.getX() + 1;
|
|
||||||
float maxY = packetBlockPosition.getY() + 1;
|
|
||||||
float maxZ = packetBlockPosition.getZ() + 1;
|
|
||||||
|
|
||||||
// AABB#distanceToSqr
|
|
||||||
float diffX = Math.max(Math.max(minX - playerPosition.getX(), playerPosition.getX() - maxX), 0);
|
|
||||||
float diffY = Math.max(Math.max(minY - playerPosition.getY(), playerPosition.getY() - maxY), 0);
|
|
||||||
float diffZ = Math.max(Math.max(minZ - playerPosition.getZ(), playerPosition.getZ() - maxZ), 0);
|
|
||||||
return ((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) < (additionalRangeCheck * additionalRangeCheck);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore the correct block state from the server without updating the chunk cache.
|
* Restore the correct block state from the server without updating the chunk cache.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ public class BedrockPlayerInputTranslator extends PacketTranslator<PlayerInputPa
|
||||||
|
|
||||||
session.sendDownstreamGamePacket(playerInputPacket);
|
session.sendDownstreamGamePacket(playerInputPacket);
|
||||||
|
|
||||||
|
session.getPlayerEntity().setVehicleInput(packet.getInputMotion());
|
||||||
|
|
||||||
// Bedrock only sends movement vehicle packets while moving
|
// Bedrock only sends movement vehicle packets while moving
|
||||||
// This allows horses to take damage while standing on magma
|
// This allows horses to take damage while standing on magma
|
||||||
Entity vehicle = session.getPlayerEntity().getVehicle();
|
Entity vehicle = session.getPlayerEntity().getVehicle();
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,12 @@ package org.geysermc.geyser.translator.protocol.bedrock;
|
||||||
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ServerSettingsRequestPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.ServerSettingsRequestPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ServerSettingsResponsePacket;
|
import org.cloudburstmc.protocol.bedrock.packet.ServerSettingsResponsePacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket;
|
|
||||||
import org.geysermc.cumulus.form.CustomForm;
|
import org.geysermc.cumulus.form.CustomForm;
|
||||||
import org.geysermc.cumulus.form.impl.FormDefinitions;
|
import org.geysermc.cumulus.form.impl.FormDefinitions;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.geyser.util.SettingsUtils;
|
import org.geysermc.geyser.util.SettingsUtils;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
@ -49,14 +47,6 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator<Ser
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peaceful difficulty allows always eating food - hence, we just do not send it to Bedrock.
|
|
||||||
// However, in order for server settings to show it properly, let's revert while we are in the menu!
|
|
||||||
if (session.getWorldCache().getDifficulty() == Difficulty.PEACEFUL) {
|
|
||||||
SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket();
|
|
||||||
setDifficultyPacket.setDifficulty(Difficulty.PEACEFUL.ordinal());
|
|
||||||
session.sendUpstreamPacket(setDifficultyPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomForm form = SettingsUtils.buildForm(session);
|
CustomForm form = SettingsUtils.buildForm(session);
|
||||||
int formId = session.getFormCache().addForm(form);
|
int formId = session.getFormCache().addForm(form);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,10 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
|
||||||
if (session.remoteServer().authType() == AuthType.ONLINE) {
|
if (session.remoteServer().authType() == AuthType.ONLINE) {
|
||||||
if (!session.isLoggedIn()) {
|
if (!session.isLoggedIn()) {
|
||||||
if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.bedrockUsername())) {
|
if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.bedrockUsername())) {
|
||||||
if (session.getGeyser().authChainFor(session.bedrockUsername()) == null) {
|
if (session.getGeyser().refreshTokenFor(session.bedrockUsername()) == null) {
|
||||||
LoginEncryptionUtils.buildAndShowConsentWindow(session);
|
LoginEncryptionUtils.buildAndShowConsentWindow(session);
|
||||||
} else {
|
} else {
|
||||||
// If the auth chain is not null and we're here, then it expired
|
// If the refresh token is not null and we're here, then the refresh token expired
|
||||||
// and the expiration form has been cached
|
// and the expiration form has been cached
|
||||||
session.getFormCache().resendAllForms();
|
session.getFormCache().resendAllForms();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,7 @@ import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
|
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
|
||||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||||
|
|
@ -57,17 +52,8 @@ import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.geyser.util.BlockUtils;
|
import org.geysermc.geyser.util.BlockUtils;
|
||||||
import org.geysermc.geyser.util.CooldownUtils;
|
import org.geysermc.geyser.util.CooldownUtils;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.*;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.*;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractAction;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerState;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
|
|
||||||
|
|
||||||
@Translator(packet = PlayerActionPacket.class)
|
@Translator(packet = PlayerActionPacket.class)
|
||||||
public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket> {
|
public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket> {
|
||||||
|
|
@ -84,7 +70,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
Vector3i vector = packet.getBlockPosition();
|
Vector3i vector = packet.getBlockPosition();
|
||||||
|
|
||||||
switch (packet.getAction()) {
|
switch (packet.getAction()) {
|
||||||
case RESPAWN -> {
|
case RESPAWN:
|
||||||
// Respawn process is finished and the server and client are both OK with respawning.
|
// Respawn process is finished and the server and client are both OK with respawning.
|
||||||
EntityEventPacket eventPacket = new EntityEventPacket();
|
EntityEventPacket eventPacket = new EntityEventPacket();
|
||||||
eventPacket.setRuntimeEntityId(entity.getGeyserId());
|
eventPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||||
|
|
@ -102,16 +88,16 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
|
|
||||||
// Needed here since 1.19.81 for dimension switching
|
// Needed here since 1.19.81 for dimension switching
|
||||||
session.getEntityCache().updateBossBars();
|
session.getEntityCache().updateBossBars();
|
||||||
}
|
break;
|
||||||
case START_SWIMMING -> {
|
case START_SWIMMING:
|
||||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||||
ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
|
ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
|
||||||
session.sendDownstreamGamePacket(startSwimPacket);
|
session.sendDownstreamGamePacket(startSwimPacket);
|
||||||
|
|
||||||
session.setSwimming(true);
|
session.setSwimming(true);
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
case STOP_SWIMMING -> {
|
case STOP_SWIMMING:
|
||||||
// Prevent packet spam when Bedrock players are crawling near the edge of a block
|
// Prevent packet spam when Bedrock players are crawling near the edge of a block
|
||||||
if (!session.getCollisionManager().mustPlayerCrawlHere()) {
|
if (!session.getCollisionManager().mustPlayerCrawlHere()) {
|
||||||
ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
|
ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
|
||||||
|
|
@ -119,50 +105,51 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
|
|
||||||
session.setSwimming(false);
|
session.setSwimming(false);
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
case START_GLIDE -> {
|
case START_GLIDE:
|
||||||
// Otherwise gliding will not work in creative
|
// Otherwise gliding will not work in creative
|
||||||
ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false);
|
ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false);
|
||||||
session.sendDownstreamGamePacket(playerAbilitiesPacket);
|
session.sendDownstreamGamePacket(playerAbilitiesPacket);
|
||||||
sendPlayerGlideToggle(session, entity);
|
case STOP_GLIDE:
|
||||||
}
|
ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
|
||||||
case STOP_GLIDE -> sendPlayerGlideToggle(session, entity);
|
session.sendDownstreamGamePacket(glidePacket);
|
||||||
case START_SNEAK -> {
|
break;
|
||||||
|
case START_SNEAK:
|
||||||
ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING);
|
ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING);
|
||||||
session.sendDownstreamGamePacket(startSneakPacket);
|
session.sendDownstreamGamePacket(startSneakPacket);
|
||||||
|
|
||||||
session.startSneaking();
|
session.startSneaking();
|
||||||
}
|
break;
|
||||||
case STOP_SNEAK -> {
|
case STOP_SNEAK:
|
||||||
ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING);
|
ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING);
|
||||||
session.sendDownstreamGamePacket(stopSneakPacket);
|
session.sendDownstreamGamePacket(stopSneakPacket);
|
||||||
|
|
||||||
session.stopSneaking();
|
session.stopSneaking();
|
||||||
}
|
break;
|
||||||
case START_SPRINT -> {
|
case START_SPRINT:
|
||||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||||
ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
|
ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
|
||||||
session.sendDownstreamGamePacket(startSprintPacket);
|
session.sendDownstreamGamePacket(startSprintPacket);
|
||||||
session.setSprinting(true);
|
session.setSprinting(true);
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
case STOP_SPRINT -> {
|
case STOP_SPRINT:
|
||||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||||
ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
|
ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
|
||||||
session.sendDownstreamGamePacket(stopSprintPacket);
|
session.sendDownstreamGamePacket(stopSprintPacket);
|
||||||
}
|
}
|
||||||
session.setSprinting(false);
|
session.setSprinting(false);
|
||||||
}
|
break;
|
||||||
case DROP_ITEM -> {
|
case DROP_ITEM:
|
||||||
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
|
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
|
||||||
vector, Direction.VALUES[packet.getFace()], 0);
|
vector, Direction.VALUES[packet.getFace()], 0);
|
||||||
session.sendDownstreamGamePacket(dropItemPacket);
|
session.sendDownstreamGamePacket(dropItemPacket);
|
||||||
}
|
break;
|
||||||
case STOP_SLEEP -> {
|
case STOP_SLEEP:
|
||||||
ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED);
|
ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED);
|
||||||
session.sendDownstreamGamePacket(stopSleepingPacket);
|
session.sendDownstreamGamePacket(stopSleepingPacket);
|
||||||
}
|
break;
|
||||||
case START_BREAK -> {
|
case START_BREAK: {
|
||||||
// Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021
|
// Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021
|
||||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -193,20 +180,18 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
// Account for fire - the client likes to hit the block behind.
|
// Account for fire - the client likes to hit the block behind.
|
||||||
Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, packet.getFace());
|
Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, packet.getFace());
|
||||||
Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block();
|
Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block();
|
||||||
Direction direction = Direction.VALUES[packet.getFace()];
|
|
||||||
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
|
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
|
||||||
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
|
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
|
||||||
direction, session.getWorldCache().nextPredictionSequence());
|
Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence());
|
||||||
session.sendDownstreamGamePacket(startBreakingPacket);
|
session.sendDownstreamGamePacket(startBreakingPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING,
|
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING,
|
||||||
vector, direction, session.getWorldCache().nextPredictionSequence());
|
vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence());
|
||||||
session.sendDownstreamGamePacket(startBreakingPacket);
|
session.sendDownstreamGamePacket(startBreakingPacket);
|
||||||
|
break;
|
||||||
spawnBlockBreakParticles(session, direction, vector, BlockState.of(blockState));
|
|
||||||
}
|
}
|
||||||
case CONTINUE_BREAK -> {
|
case CONTINUE_BREAK:
|
||||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -216,12 +201,19 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3f vectorFloat = vector.toFloat();
|
Vector3f vectorFloat = vector.toFloat();
|
||||||
|
LevelEventPacket continueBreakPacket = new LevelEventPacket();
|
||||||
|
continueBreakPacket.setType(LevelEvent.PARTICLE_CRACK_BLOCK);
|
||||||
|
continueBreakPacket.setData((session.getBlockMappings().getBedrockBlockId(breakingBlock)) | (packet.getFace() << 24));
|
||||||
|
continueBreakPacket.setPosition(vectorFloat);
|
||||||
|
session.sendUpstreamPacket(continueBreakPacket);
|
||||||
|
|
||||||
|
// Update the break time in the event that player conditions changed (jumping, effects applied)
|
||||||
|
LevelEventPacket updateBreak = new LevelEventPacket();
|
||||||
|
updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK);
|
||||||
|
updateBreak.setPosition(vectorFloat);
|
||||||
|
double breakTime = BlockUtils.getSessionBreakTime(session, BlockState.of(breakingBlock).block()) * 20;
|
||||||
|
|
||||||
BlockState breakingBlockState = BlockState.of(breakingBlock);
|
|
||||||
Direction direction = Direction.VALUES[packet.getFace()];
|
|
||||||
spawnBlockBreakParticles(session, direction, vector, breakingBlockState);
|
|
||||||
|
|
||||||
double breakTime = BlockUtils.getSessionBreakTime(session, breakingBlockState.block()) * 20;
|
|
||||||
// If the block is custom, we must keep track of when it should break ourselves
|
// If the block is custom, we must keep track of when it should break ourselves
|
||||||
long blockBreakStartTime = session.getBlockBreakStartTime();
|
long blockBreakStartTime = session.getBlockBreakStartTime();
|
||||||
if (blockBreakStartTime != 0) {
|
if (blockBreakStartTime != 0) {
|
||||||
|
|
@ -237,20 +229,17 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
|
|
||||||
// Break the block
|
// Break the block
|
||||||
ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING,
|
ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING,
|
||||||
vector, direction, session.getWorldCache().nextPredictionSequence());
|
vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence());
|
||||||
session.sendDownstreamGamePacket(finishBreakingPacket);
|
session.sendDownstreamGamePacket(finishBreakingPacket);
|
||||||
session.setBlockBreakStartTime(0);
|
session.setBlockBreakStartTime(0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update the break time in the event that player conditions changed (jumping, effects applied)
|
|
||||||
LevelEventPacket updateBreak = new LevelEventPacket();
|
|
||||||
updateBreak.setType(LevelEvent.BLOCK_UPDATE_BREAK);
|
|
||||||
updateBreak.setPosition(vectorFloat);
|
|
||||||
updateBreak.setData((int) (65535 / breakTime));
|
updateBreak.setData((int) (65535 / breakTime));
|
||||||
session.sendUpstreamPacket(updateBreak);
|
session.sendUpstreamPacket(updateBreak);
|
||||||
}
|
break;
|
||||||
case ABORT_BREAK -> {
|
case ABORT_BREAK:
|
||||||
if (session.getGameMode() != GameMode.CREATIVE) {
|
if (session.getGameMode() != GameMode.CREATIVE) {
|
||||||
// As of 1.16.210: item frame items are taken out here.
|
// As of 1.16.210: item frame items are taken out here.
|
||||||
// Survival also sends START_BREAK, but by attaching our process here adventure mode also works
|
// Survival also sends START_BREAK, but by attaching our process here adventure mode also works
|
||||||
|
|
@ -271,23 +260,25 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
stopBreak.setData(0);
|
stopBreak.setData(0);
|
||||||
session.setBreakingBlock(-1);
|
session.setBreakingBlock(-1);
|
||||||
session.sendUpstreamPacket(stopBreak);
|
session.sendUpstreamPacket(stopBreak);
|
||||||
}
|
break;
|
||||||
|
case STOP_BREAK:
|
||||||
// Handled in BedrockInventoryTransactionTranslator
|
// Handled in BedrockInventoryTransactionTranslator
|
||||||
case STOP_BREAK -> {
|
break;
|
||||||
}
|
case DIMENSION_CHANGE_SUCCESS:
|
||||||
case DIMENSION_CHANGE_SUCCESS -> {
|
|
||||||
//sometimes the client doesn't feel like loading
|
//sometimes the client doesn't feel like loading
|
||||||
PlayStatusPacket spawnPacket = new PlayStatusPacket();
|
PlayStatusPacket spawnPacket = new PlayStatusPacket();
|
||||||
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
|
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
|
||||||
session.sendUpstreamPacket(spawnPacket);
|
session.sendUpstreamPacket(spawnPacket);
|
||||||
|
|
||||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
attributesPacket = new UpdateAttributesPacket();
|
||||||
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
|
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||||
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
|
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
|
||||||
session.sendUpstreamPacket(attributesPacket);
|
session.sendUpstreamPacket(attributesPacket);
|
||||||
}
|
break;
|
||||||
case JUMP -> entity.setOnGround(false); // Increase block break time while jumping
|
case JUMP:
|
||||||
case MISSED_SWING -> {
|
entity.setOnGround(false); // Increase block break time while jumping
|
||||||
|
break;
|
||||||
|
case MISSED_SWING:
|
||||||
// Java edition sends a cooldown when hitting air.
|
// Java edition sends a cooldown when hitting air.
|
||||||
// Normally handled by BedrockLevelSoundEventTranslator, but there is no sound on Java for this.
|
// Normally handled by BedrockLevelSoundEventTranslator, but there is no sound on Java for this.
|
||||||
CooldownUtils.sendCooldown(session);
|
CooldownUtils.sendCooldown(session);
|
||||||
|
|
@ -303,8 +294,8 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
|
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
|
||||||
session.sendUpstreamPacket(animatePacket);
|
session.sendUpstreamPacket(animatePacket);
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
case START_FLYING -> { // Since 1.20.30
|
case START_FLYING: // Since 1.20.30
|
||||||
if (session.isCanFly()) {
|
if (session.isCanFly()) {
|
||||||
if (session.getGameMode() == GameMode.SPECTATOR) {
|
if (session.getGameMode() == GameMode.SPECTATOR) {
|
||||||
// should already be flying
|
// should already be flying
|
||||||
|
|
@ -333,12 +324,12 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
stopFlyingPacket.setFace(0);
|
stopFlyingPacket.setFace(0);
|
||||||
session.sendUpstreamPacket(stopFlyingPacket);
|
session.sendUpstreamPacket(stopFlyingPacket);
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
case STOP_FLYING -> {
|
case STOP_FLYING:
|
||||||
session.setFlying(false);
|
session.setFlying(false);
|
||||||
session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false));
|
session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false));
|
||||||
}
|
break;
|
||||||
case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK -> { // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70
|
case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK: // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70
|
||||||
BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector);
|
BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector);
|
||||||
|
|
||||||
if (state.getValue(Properties.HAS_BOOK, false)) {
|
if (state.getValue(Properties.HAS_BOOK, false)) {
|
||||||
|
|
@ -361,27 +352,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||||
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
|
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
|
||||||
session.sendDownstreamGamePacket(interactPacket);
|
session.sendDownstreamGamePacket(interactPacket);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnBlockBreakParticles(GeyserSession session, Direction direction, Vector3i position, BlockState blockState) {
|
|
||||||
LevelEventPacket levelEventPacket = new LevelEventPacket();
|
|
||||||
switch (direction) {
|
|
||||||
case UP -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_UP);
|
|
||||||
case DOWN -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_DOWN);
|
|
||||||
case NORTH -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_NORTH);
|
|
||||||
case EAST -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_EAST);
|
|
||||||
case SOUTH -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_SOUTH);
|
|
||||||
case WEST -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_WEST);
|
|
||||||
}
|
|
||||||
levelEventPacket.setPosition(position.toFloat());
|
|
||||||
levelEventPacket.setData(session.getBlockMappings().getBedrockBlock(blockState).getRuntimeId());
|
|
||||||
session.sendUpstreamPacket(levelEventPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendPlayerGlideToggle(GeyserSession session, Entity entity) {
|
|
||||||
ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
|
|
||||||
session.sendDownstreamGamePacket(glidePacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.text.ChatColor;
|
import org.geysermc.geyser.text.ChatColor;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
|
|
@ -84,7 +85,9 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||||
|
|
||||||
session.sendDownstreamGamePacket(playerRotationPacket);
|
session.sendDownstreamGamePacket(playerRotationPacket);
|
||||||
} else {
|
} else {
|
||||||
if (session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
|
// World border collision will be handled by client vehicle
|
||||||
|
if (!(entity.getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled())
|
||||||
|
&& session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
public class BedrockRiderJumpTranslator extends PacketTranslator<RiderJumpPacket> {
|
public class BedrockRiderJumpTranslator extends PacketTranslator<RiderJumpPacket> {
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, RiderJumpPacket packet) {
|
public void translate(GeyserSession session, RiderJumpPacket packet) {
|
||||||
|
session.getPlayerEntity().setVehicleJumpStrength(packet.getJumpStrength());
|
||||||
|
|
||||||
Entity vehicle = session.getPlayerEntity().getVehicle();
|
Entity vehicle = session.getPlayerEntity().getVehicle();
|
||||||
if (vehicle instanceof AbstractHorseEntity) {
|
if (vehicle instanceof AbstractHorseEntity) {
|
||||||
ServerboundPlayerCommandPacket playerCommandPacket = new ServerboundPlayerCommandPacket(vehicle.getEntityId(), PlayerState.START_HORSE_JUMP, packet.getJumpStrength());
|
ServerboundPlayerCommandPacket playerCommandPacket = new ServerboundPlayerCommandPacket(vehicle.getEntityId(), PlayerState.START_HORSE_JUMP, packet.getJumpStrength());
|
||||||
|
|
|
||||||
|
|
@ -25,29 +25,21 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java;
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundChangeDifficultyPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundChangeDifficultyPacket;
|
|
||||||
|
|
||||||
@Translator(packet = ClientboundChangeDifficultyPacket.class)
|
@Translator(packet = ClientboundChangeDifficultyPacket.class)
|
||||||
public class JavaChangeDifficultyTranslator extends PacketTranslator<ClientboundChangeDifficultyPacket> {
|
public class JavaChangeDifficultyTranslator extends PacketTranslator<ClientboundChangeDifficultyPacket> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundChangeDifficultyPacket packet) {
|
public void translate(GeyserSession session, ClientboundChangeDifficultyPacket packet) {
|
||||||
Difficulty difficulty = packet.getDifficulty();
|
|
||||||
session.getWorldCache().setDifficulty(difficulty);
|
|
||||||
|
|
||||||
// Peaceful difficulty allows always eating food - hence, we just do not send it to Bedrock.
|
|
||||||
if (difficulty == Difficulty.PEACEFUL) {
|
|
||||||
difficulty = Difficulty.EASY;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket();
|
SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket();
|
||||||
setDifficultyPacket.setDifficulty(difficulty.ordinal());
|
setDifficultyPacket.setDifficulty(packet.getDifficulty().ordinal());
|
||||||
session.sendUpstreamPacket(setDifficultyPacket);
|
session.sendUpstreamPacket(setDifficultyPacket);
|
||||||
|
|
||||||
|
session.getWorldCache().setDifficulty(packet.getDifficulty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java;
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import org.geysermc.geyser.api.network.AuthType;
|
import org.geysermc.geyser.api.network.AuthType;
|
||||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||||
|
|
|
||||||
|
|
@ -26,25 +26,27 @@
|
||||||
package org.geysermc.geyser.translator.protocol.java;
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.GameRuleData;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
|
||||||
import org.geysermc.erosion.Constants;
|
import org.geysermc.erosion.Constants;
|
||||||
|
import org.geysermc.geyser.util.MinecraftKey;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerSpawnInfo;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
|
||||||
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.GameRuleData;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
||||||
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
||||||
import org.geysermc.geyser.api.network.AuthType;
|
import org.geysermc.geyser.api.network.AuthType;
|
||||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||||
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
||||||
import org.geysermc.geyser.level.JavaDimension;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.geyser.util.ChunkUtils;
|
import org.geysermc.geyser.util.ChunkUtils;
|
||||||
import org.geysermc.geyser.util.DimensionUtils;
|
import org.geysermc.geyser.util.DimensionUtils;
|
||||||
import org.geysermc.geyser.util.EntityUtils;
|
import org.geysermc.geyser.util.EntityUtils;
|
||||||
import org.geysermc.geyser.util.MinecraftKey;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerSpawnInfo;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -63,15 +65,12 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerSpawnInfo spawnInfo = packet.getCommonPlayerSpawnInfo();
|
PlayerSpawnInfo spawnInfo = packet.getCommonPlayerSpawnInfo();
|
||||||
JavaDimension newDimension = session.getRegistryCache().dimensions().byId(spawnInfo.getDimension());
|
|
||||||
boolean forceDimSwitch = false;
|
|
||||||
|
|
||||||
// If the player is already initialized and a join game packet is sent, they
|
// If the player is already initialized and a join game packet is sent, they
|
||||||
// are swapping servers
|
// are swapping servers
|
||||||
if (session.isSpawned()) {
|
if (session.isSpawned()) {
|
||||||
int fakeDim = DimensionUtils.getTemporaryDimension(session.getDimensionType().bedrockId(), newDimension.bedrockId());
|
int fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), spawnInfo.getDimension());
|
||||||
DimensionUtils.fastSwitchDimension(session, fakeDim);
|
DimensionUtils.switchDimension(session, fakeDim);
|
||||||
forceDimSwitch = true;
|
|
||||||
|
|
||||||
session.getWorldCache().removeScoreboard();
|
session.getWorldCache().removeScoreboard();
|
||||||
|
|
||||||
|
|
@ -80,24 +79,18 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
||||||
// Remove extra hearts, hunger, etc.
|
// Remove extra hearts, hunger, etc.
|
||||||
entity.resetAttributes();
|
entity.resetAttributes();
|
||||||
entity.resetMetadata();
|
entity.resetMetadata();
|
||||||
} else if (session.getUpstream().isInitialized()) {
|
|
||||||
if (newDimension.bedrockId() == 0) {
|
|
||||||
// A dimension switch will not happen, so make sure we initialized the dimension choice.
|
|
||||||
// Otherwise, the dimension switch will fill these values in.
|
|
||||||
session.setDimensionType(newDimension);
|
|
||||||
DimensionUtils.setBedrockDimension(session, newDimension.bedrockId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
session.setWorldName(spawnInfo.getWorldName());
|
session.setWorldName(spawnInfo.getWorldName());
|
||||||
session.setLevels(Arrays.stream(packet.getWorldNames()).map(Key::asString).toArray(String[]::new));
|
session.setLevels(Arrays.stream(packet.getWorldNames()).map(Key::asString).toArray(String[]::new));
|
||||||
session.setGameMode(spawnInfo.getGameMode());
|
session.setGameMode(spawnInfo.getGameMode());
|
||||||
|
int newDimension = spawnInfo.getDimension();
|
||||||
|
|
||||||
boolean needsSpawnPacket = !session.isSentSpawnPacket();
|
boolean needsSpawnPacket = !session.isSentSpawnPacket();
|
||||||
if (needsSpawnPacket) {
|
if (needsSpawnPacket) {
|
||||||
// The player has yet to spawn so let's do that using some of the information in this Java packet
|
// The player has yet to spawn so let's do that using some of the information in this Java packet
|
||||||
session.setDimensionType(newDimension);
|
session.setDimension(newDimension);
|
||||||
DimensionUtils.setBedrockDimension(session, newDimension.bedrockId());
|
DimensionUtils.setBedrockDimension(session, newDimension);
|
||||||
session.connect();
|
session.connect();
|
||||||
|
|
||||||
// It is now safe to send these packets
|
// It is now safe to send these packets
|
||||||
|
|
@ -131,9 +124,9 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
||||||
}
|
}
|
||||||
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, Constants.PLUGIN_MESSAGE.getBytes(StandardCharsets.UTF_8)));
|
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, Constants.PLUGIN_MESSAGE.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
|
||||||
if (newDimension != session.getDimensionType() || forceDimSwitch) {
|
if (newDimension != session.getDimension()) {
|
||||||
DimensionUtils.switchDimension(session, newDimension);
|
DimensionUtils.switchDimension(session, newDimension);
|
||||||
} else if (DimensionUtils.isCustomBedrockNetherId() && newDimension.isNetherLike()) {
|
} else if (DimensionUtils.isCustomBedrockNetherId() && newDimension == DimensionUtils.NETHER) {
|
||||||
// If the player is spawning into the "fake" nether, send them some fog
|
// If the player is spawning into the "fake" nether, send them some fog
|
||||||
session.camera().sendFog(DimensionUtils.BEDROCK_FOG_HELL);
|
session.camera().sendFog(DimensionUtils.BEDROCK_FOG_HELL);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
||||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||||
import org.geysermc.geyser.level.JavaDimension;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
|
|
@ -93,14 +92,15 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
|
||||||
session.setThunder(false);
|
session.setThunder(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
JavaDimension newDimension = session.getRegistryCache().dimensions().byId(spawnInfo.getDimension());
|
int newDimension = spawnInfo.getDimension();
|
||||||
if (session.getDimensionType() != newDimension || !spawnInfo.getWorldName().equals(session.getWorldName())) {
|
if (session.getDimension() != newDimension || !spawnInfo.getWorldName().equals(session.getWorldName())) {
|
||||||
// Switching to a new world (based off the world name change or new dimension); send a fake dimension change
|
// Switching to a new world (based off the world name change or new dimension); send a fake dimension change
|
||||||
if (session.getDimensionType().bedrockId() == newDimension.bedrockId()) {
|
if (DimensionUtils.javaToBedrock(session.getDimension()) == DimensionUtils.javaToBedrock(newDimension)) {
|
||||||
int fakeDim = DimensionUtils.getTemporaryDimension(session.getDimensionType().bedrockId(), newDimension.bedrockId());
|
int fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), newDimension);
|
||||||
DimensionUtils.fastSwitchDimension(session, fakeDim);
|
DimensionUtils.switchDimension(session, fakeDim);
|
||||||
}
|
}
|
||||||
session.setWorldName(spawnInfo.getWorldName());
|
session.setWorldName(spawnInfo.getWorldName());
|
||||||
|
session.setWorldTicks(0);
|
||||||
DimensionUtils.switchDimension(session, newDimension);
|
DimensionUtils.switchDimension(session, newDimension);
|
||||||
|
|
||||||
ChunkUtils.loadDimension(session);
|
ChunkUtils.loadDimension(session);
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java;
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||||
|
|
@ -44,11 +40,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescri
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.TrimDataPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.TrimDataPacket;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
import org.geysermc.geyser.inventory.recipe.*;
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
|
|
||||||
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
|
|
||||||
import org.geysermc.geyser.inventory.recipe.TrimRecipe;
|
|
||||||
import org.geysermc.geyser.registry.Registries;
|
import org.geysermc.geyser.registry.Registries;
|
||||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
@ -66,17 +58,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.recipe.data.SmithingTransfo
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.data.StoneCuttingRecipeData;
|
import org.geysermc.mcprotocollib.protocol.data.game.recipe.data.StoneCuttingRecipeData;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID;
|
import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID;
|
||||||
|
|
@ -209,9 +191,6 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||||
case CRAFTING_SPECIAL_MAPCLONING -> {
|
case CRAFTING_SPECIAL_MAPCLONING -> {
|
||||||
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), context.getAndIncrementNetId()));
|
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), context.getAndIncrementNetId()));
|
||||||
}
|
}
|
||||||
case CRAFTING_SPECIAL_FIREWORK_ROCKET -> {
|
|
||||||
craftingDataPacket.getCraftingData().add(MultiRecipeData.of(UUID.fromString("00000000-0000-0000-0000-000000000002"), context.getAndIncrementNetId()));
|
|
||||||
}
|
|
||||||
default -> {
|
default -> {
|
||||||
List<GeyserRecipe> recipes = Registries.RECIPES.get(recipe.getType());
|
List<GeyserRecipe> recipes = Registries.RECIPES.get(recipe.getType());
|
||||||
if (recipes != null) {
|
if (recipes != null) {
|
||||||
|
|
@ -253,8 +232,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||||
// We can get the correct order for button pressing
|
// We can get the correct order for button pressing
|
||||||
data.getValue().sort(Comparator.comparing((stoneCuttingRecipeData ->
|
data.getValue().sort(Comparator.comparing((stoneCuttingRecipeData ->
|
||||||
Registries.JAVA_ITEMS.get().get(stoneCuttingRecipeData.getResult().getId())
|
Registries.JAVA_ITEMS.get().get(stoneCuttingRecipeData.getResult().getId())
|
||||||
// See RecipeManager#getRecipesFor as of 1.21
|
.javaIdentifier())));
|
||||||
.translationKey())));
|
|
||||||
|
|
||||||
// Now that it's sorted, let's translate these recipes
|
// Now that it's sorted, let's translate these recipes
|
||||||
int buttonId = 0;
|
int buttonId = 0;
|
||||||
|
|
@ -449,7 +427,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||||
// output = output.toBuilder().tag(null).build(); // TODO confirm???
|
output = output.toBuilder().tag(null).build();
|
||||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||||
if (inputCombinations == null) {
|
if (inputCombinations == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -473,7 +451,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Strip NBT - tools won't appear in the recipe book otherwise
|
// Strip NBT - tools won't appear in the recipe book otherwise
|
||||||
//output = output.toBuilder().tag(null).build(); // TODO confirm this is still true???
|
output = output.toBuilder().tag(null).build();
|
||||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||||
if (inputCombinations == null) {
|
if (inputCombinations == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -497,7 +475,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// See above
|
// See above
|
||||||
//output = output.toBuilder().tag(null).build();
|
output = output.toBuilder().tag(null).build();
|
||||||
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
ItemDescriptorWithCount[][] inputCombinations = combinations(session, recipe.ingredients());
|
||||||
if (inputCombinations == null) {
|
if (inputCombinations == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ public class JavaAnimateTranslator extends PacketTranslator<ClientboundAnimatePa
|
||||||
// Spawn custom particle
|
// Spawn custom particle
|
||||||
SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket();
|
SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket();
|
||||||
stringPacket.setIdentifier("geyseropt:enchanted_hit_multiple");
|
stringPacket.setIdentifier("geyseropt:enchanted_hit_multiple");
|
||||||
stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session));
|
stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
|
||||||
stringPacket.setPosition(Vector3f.ZERO);
|
stringPacket.setPosition(Vector3f.ZERO);
|
||||||
stringPacket.setUniqueEntityId(entity.getGeyserId());
|
stringPacket.setUniqueEntityId(entity.getGeyserId());
|
||||||
stringPacket.setMolangVariablesJson(Optional.empty());
|
stringPacket.setMolangVariablesJson(Optional.empty());
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundMoveVehiclePacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundMoveVehiclePacket;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
|
@ -40,6 +41,10 @@ public class JavaMoveVehicleTranslator extends PacketTranslator<ClientboundMoveV
|
||||||
Entity entity = session.getPlayerEntity().getVehicle();
|
Entity entity = session.getPlayerEntity().getVehicle();
|
||||||
if (entity == null) return;
|
if (entity == null) return;
|
||||||
|
|
||||||
|
if (entity instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().moveAbsolute(packet.getX(), packet.getY(), packet.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
entity.moveAbsolute(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, true);
|
entity.moveAbsolute(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundRemoveMobEffectPacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundRemoveMobEffectPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
|
@ -39,11 +40,15 @@ public class JavaRemoveMobEffectTranslator extends PacketTranslator<ClientboundR
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundRemoveMobEffectPacket packet) {
|
public void translate(GeyserSession session, ClientboundRemoveMobEffectPacket packet) {
|
||||||
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
||||||
|
if (entity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (entity == session.getPlayerEntity()) {
|
if (entity == session.getPlayerEntity()) {
|
||||||
session.getEffectCache().removeEffect(packet.getEffect());
|
session.getEffectCache().removeEffect(packet.getEffect());
|
||||||
|
} else if (entity instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().removeEffect(packet.getEffect());
|
||||||
}
|
}
|
||||||
if (entity == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
MobEffectPacket mobEffectPacket = new MobEffectPacket();
|
MobEffectPacket mobEffectPacket = new MobEffectPacket();
|
||||||
mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE);
|
mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE);
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
|
@ -55,6 +56,10 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
|
||||||
session.getPlayerEntity().setVehicle(entity);
|
session.getPlayerEntity().setVehicle(entity);
|
||||||
// We need to confirm teleports before entering a vehicle, or else we will likely exit right out
|
// We need to confirm teleports before entering a vehicle, or else we will likely exit right out
|
||||||
session.confirmTeleport(passenger.getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0).toDouble());
|
session.confirmTeleport(passenger.getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0).toDouble());
|
||||||
|
|
||||||
|
if (entity instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().onMount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (passenger == null) {
|
if (passenger == null) {
|
||||||
// Can occur if the passenger is outside the client's tracking range
|
// Can occur if the passenger is outside the client's tracking range
|
||||||
|
|
@ -100,6 +105,10 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
|
||||||
// as of Java 1.19.3, but the scheduled future checks for the vehicle being null anyway.
|
// as of Java 1.19.3, but the scheduled future checks for the vehicle being null anyway.
|
||||||
session.getMountVehicleScheduledFuture().cancel(false);
|
session.getMountVehicleScheduledFuture().cancel(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().onDismount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundTeleportEntityPacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundTeleportEntityPacket;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
|
@ -40,6 +41,10 @@ public class JavaTeleportEntityTranslator extends PacketTranslator<ClientboundTe
|
||||||
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
||||||
if (entity == null) return;
|
if (entity == null) return;
|
||||||
|
|
||||||
|
if (entity instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().moveAbsolute(packet.getX(), packet.getY(), packet.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
entity.teleport(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround());
|
entity.teleport(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundUpdateMobEffectPacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundUpdateMobEffectPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
|
@ -39,13 +40,16 @@ public class JavaUpdateMobEffectTranslator extends PacketTranslator<ClientboundU
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundUpdateMobEffectPacket packet) {
|
public void translate(GeyserSession session, ClientboundUpdateMobEffectPacket packet) {
|
||||||
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
|
||||||
if (entity == session.getPlayerEntity()) {
|
|
||||||
session.getEffectCache().setEffect(packet.getEffect(), packet.getAmplifier());
|
|
||||||
}
|
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity == session.getPlayerEntity()) {
|
||||||
|
session.getEffectCache().setEffect(packet.getEffect(), packet.getAmplifier());
|
||||||
|
} else if (entity instanceof ClientVehicle clientVehicle) {
|
||||||
|
clientVehicle.getVehicleComponent().setEffect(packet.getEffect(), packet.getAmplifier());
|
||||||
|
}
|
||||||
|
|
||||||
int duration = packet.getDuration();
|
int duration = packet.getDuration();
|
||||||
if (duration < 0) {
|
if (duration < 0) {
|
||||||
// java edition uses -1 for infinite, but bedrock doesn't have infinite
|
// java edition uses -1 for infinite, but bedrock doesn't have infinite
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java.entity.player;
|
package org.geysermc.geyser.translator.protocol.java.entity.player;
|
||||||
|
|
||||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,7 @@ import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
|
||||||
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
|
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.BellValue;
|
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.*;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.BlockValue;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.ChestValue;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.DecoratedPotValue;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.EndGatewayValue;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.MobSpawnerValue;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.NoteBlockValue;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValue;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundBlockEventPacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundBlockEventPacket;
|
||||||
|
|
||||||
@Translator(packet = ClientboundBlockEventPacket.class)
|
@Translator(packet = ClientboundBlockEventPacket.class)
|
||||||
|
|
@ -88,7 +80,7 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
|
||||||
// See https://github.com/PaperMC/Paper/blob/6fa1983e9ce177a4a412d5b950fd978620174777/patches/server/0304-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch
|
// See https://github.com/PaperMC/Paper/blob/6fa1983e9ce177a4a412d5b950fd978620174777/patches/server/0304-Fire-BlockPistonRetractEvent-for-all-empty-pistons.patch
|
||||||
if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) {
|
if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) {
|
||||||
BlockState pistonBlock = session.getGeyser().getWorldManager().blockAt(session, position);
|
BlockState pistonBlock = session.getGeyser().getWorldManager().blockAt(session, position);
|
||||||
if (!isSticky(pistonBlock)) {
|
if (!pistonBlock.is(Blocks.STICKY_PISTON)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (action != PistonValueType.CANCELLED_MID_PUSH) {
|
if (action != PistonValueType.CANCELLED_MID_PUSH) {
|
||||||
|
|
@ -107,7 +99,7 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
|
||||||
} else {
|
} else {
|
||||||
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> {
|
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos -> {
|
||||||
BlockState state = session.getGeyser().getWorldManager().blockAt(session, position);
|
BlockState state = session.getGeyser().getWorldManager().blockAt(session, position);
|
||||||
boolean sticky = isSticky(state);
|
boolean sticky = state.is(Blocks.STICKY_PISTON);
|
||||||
boolean extended = action != PistonValueType.PUSHING;
|
boolean extended = action != PistonValueType.PUSHING;
|
||||||
return new PistonBlockEntity(session, pos, direction, sticky, extended);
|
return new PistonBlockEntity(session, pos, direction, sticky, extended);
|
||||||
});
|
});
|
||||||
|
|
@ -157,8 +149,4 @@ public class JavaBlockEventTranslator extends PacketTranslator<ClientboundBlockE
|
||||||
session.getGeyser().getLogger().debug("Unhandled block event packet: " + packet);
|
session.getGeyser().getLogger().debug("Unhandled block event packet: " + packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSticky(BlockState state) {
|
|
||||||
return state.is(Blocks.STICKY_PISTON) || (state.is(Blocks.MOVING_PISTON) && "sticky".equals(state.getValue(Properties.PISTON_TYPE)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.level;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.math.vector.Vector3i;
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.ParticleType;
|
import org.cloudburstmc.protocol.bedrock.data.ParticleType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket;
|
||||||
|
|
@ -480,7 +481,7 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void spawnOminousTrialSpawnerParticles(GeyserSession session, Vector3f pos) {
|
private static void spawnOminousTrialSpawnerParticles(GeyserSession session, Vector3f pos) {
|
||||||
int dimensionId = DimensionUtils.javaToBedrock(session);
|
int dimensionId = DimensionUtils.javaToBedrock(session.getDimension());
|
||||||
SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket();
|
SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket();
|
||||||
stringPacket.setIdentifier("minecraft:trial_spawner_detection_ominous");
|
stringPacket.setIdentifier("minecraft:trial_spawner_detection_ominous");
|
||||||
stringPacket.setDimensionId(dimensionId);
|
stringPacket.setDimensionId(dimensionId);
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ public class JavaLevelParticlesTranslator extends PacketTranslator<ClientboundLe
|
||||||
return packet;
|
return packet;
|
||||||
};
|
};
|
||||||
} else if (particleMapping.identifier() != null) {
|
} else if (particleMapping.identifier() != null) {
|
||||||
int dimensionId = DimensionUtils.javaToBedrock(session);
|
int dimensionId = DimensionUtils.javaToBedrock(session.getDimension());
|
||||||
return (position) -> {
|
return (position) -> {
|
||||||
SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket();
|
SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket();
|
||||||
stringPacket.setIdentifier(particleMapping.identifier());
|
stringPacket.setIdentifier(particleMapping.identifier());
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ public class JavaMapItemDataTranslator extends PacketTranslator<ClientboundMapIt
|
||||||
org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket mapItemDataPacket = new org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket();
|
org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket mapItemDataPacket = new org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket();
|
||||||
|
|
||||||
mapItemDataPacket.setUniqueMapId(packet.getMapId());
|
mapItemDataPacket.setUniqueMapId(packet.getMapId());
|
||||||
mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session));
|
mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
|
||||||
mapItemDataPacket.setLocked(packet.isLocked());
|
mapItemDataPacket.setLocked(packet.isLocked());
|
||||||
mapItemDataPacket.setOrigin(Vector3i.ZERO); // Required since 1.19.20
|
mapItemDataPacket.setOrigin(Vector3i.ZERO); // Required since 1.19.20
|
||||||
mapItemDataPacket.setScale(packet.getScale());
|
mapItemDataPacket.setScale(packet.getScale());
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public class JavaSetDefaultSpawnPositionTranslator extends PacketTranslator<Clie
|
||||||
public void translate(GeyserSession session, ClientboundSetDefaultSpawnPositionPacket packet) {
|
public void translate(GeyserSession session, ClientboundSetDefaultSpawnPositionPacket packet) {
|
||||||
SetSpawnPositionPacket spawnPositionPacket = new SetSpawnPositionPacket();
|
SetSpawnPositionPacket spawnPositionPacket = new SetSpawnPositionPacket();
|
||||||
spawnPositionPacket.setBlockPosition(packet.getPosition());
|
spawnPositionPacket.setBlockPosition(packet.getPosition());
|
||||||
spawnPositionPacket.setDimensionId(DimensionUtils.javaToBedrock(session));
|
spawnPositionPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
|
||||||
spawnPositionPacket.setSpawnType(SetSpawnPositionPacket.Type.WORLD_SPAWN);
|
spawnPositionPacket.setSpawnType(SetSpawnPositionPacket.Type.WORLD_SPAWN);
|
||||||
session.sendUpstreamPacket(spawnPositionPacket);
|
session.sendUpstreamPacket(spawnPositionPacket);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ public class JavaSetTimeTranslator extends PacketTranslator<ClientboundSetTimePa
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundSetTimePacket packet) {
|
public void translate(GeyserSession session, ClientboundSetTimePacket packet) {
|
||||||
|
session.setWorldTicks(packet.getWorldAge());
|
||||||
|
|
||||||
// Bedrock sends a GameRulesChangedPacket if there is no daylight cycle
|
// Bedrock sends a GameRulesChangedPacket if there is no daylight cycle
|
||||||
// Java just sends a negative long if there is no daylight cycle
|
// Java just sends a negative long if there is no daylight cycle
|
||||||
long time = packet.getTime();
|
long time = packet.getTime();
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,8 @@ public class ChunkUtils {
|
||||||
* This must be done after the player has switched dimensions so we know what their dimension is
|
* This must be done after the player has switched dimensions so we know what their dimension is
|
||||||
*/
|
*/
|
||||||
public static void loadDimension(GeyserSession session) {
|
public static void loadDimension(GeyserSession session) {
|
||||||
JavaDimension dimension = session.getDimensionType();
|
JavaDimension dimension = session.getRegistryCache().dimensions().byId(session.getDimension());
|
||||||
|
session.setDimensionType(dimension);
|
||||||
int minY = dimension.minY();
|
int minY = dimension.minY();
|
||||||
int maxY = dimension.maxY();
|
int maxY = dimension.maxY();
|
||||||
|
|
||||||
|
|
@ -222,7 +223,7 @@ public class ChunkUtils {
|
||||||
session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.translator.chunk.out_of_bounds",
|
session.getGeyser().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.network.translator.chunk.out_of_bounds",
|
||||||
String.valueOf(bedrockDimension.minY()),
|
String.valueOf(bedrockDimension.minY()),
|
||||||
String.valueOf(bedrockDimension.height()),
|
String.valueOf(bedrockDimension.height()),
|
||||||
session.getRegistryCache().dimensions().byValue(session.getDimensionType())));
|
session.getDimension()));
|
||||||
}
|
}
|
||||||
|
|
||||||
session.getChunkCache().setMinY(minY);
|
session.getChunkCache().setMinY(minY);
|
||||||
|
|
|
||||||
|
|
@ -25,20 +25,13 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.util;
|
package org.geysermc.geyser.util;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.math.vector.Vector3i;
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
|
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ChangeDimensionPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.StopSoundPacket;
|
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
import org.geysermc.geyser.level.BedrockDimension;
|
import org.geysermc.geyser.level.BedrockDimension;
|
||||||
import org.geysermc.geyser.level.JavaDimension;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
||||||
|
|
||||||
|
|
@ -51,18 +44,22 @@ public class DimensionUtils {
|
||||||
|
|
||||||
public static final String BEDROCK_FOG_HELL = "minecraft:fog_hell";
|
public static final String BEDROCK_FOG_HELL = "minecraft:fog_hell";
|
||||||
|
|
||||||
public static final String NETHER_IDENTIFIER = "minecraft:the_nether";
|
/**
|
||||||
|
* String reference to vanilla Java overworld dimension identifier
|
||||||
|
*/
|
||||||
|
public static final int OVERWORLD = 0;
|
||||||
|
/**
|
||||||
|
* String reference to vanilla Java nether dimension identifier
|
||||||
|
*/
|
||||||
|
public static final int NETHER = 3;
|
||||||
|
/**
|
||||||
|
* String reference to vanilla Java end dimension identifier
|
||||||
|
*/
|
||||||
|
public static final int THE_END = 2;
|
||||||
|
|
||||||
private static final int BEDROCK_OVERWORLD_ID = 0;
|
public static void switchDimension(GeyserSession session, int javaDimension) {
|
||||||
private static final int BEDROCK_DEFAULT_NETHER_ID = 1;
|
int bedrockDimension = javaToBedrock(javaDimension); // new bedrock dimension
|
||||||
private static final int BEDROCK_END_ID = 2;
|
int previousDimension = session.getDimension(); // previous java dimension
|
||||||
|
|
||||||
public static void switchDimension(GeyserSession session, JavaDimension javaDimension) {
|
|
||||||
switchDimension(session, javaDimension, javaDimension.bedrockId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void switchDimension(GeyserSession session, JavaDimension javaDimension, int bedrockDimension) {
|
|
||||||
@Nullable JavaDimension previousDimension = session.getDimensionType(); // previous java dimension; can be null if an online player with no saved auth token logs in.
|
|
||||||
|
|
||||||
Entity player = session.getPlayerEntity();
|
Entity player = session.getPlayerEntity();
|
||||||
|
|
||||||
|
|
@ -73,9 +70,35 @@ public class DimensionUtils {
|
||||||
session.getPistonCache().clear();
|
session.getPistonCache().clear();
|
||||||
session.getSkullCache().clear();
|
session.getSkullCache().clear();
|
||||||
|
|
||||||
changeDimension(session, bedrockDimension);
|
if (session.getServerRenderDistance() > 32 && !session.isEmulatePost1_13Logic()) {
|
||||||
|
// The server-sided view distance wasn't a thing until Minecraft Java 1.14
|
||||||
|
// So ViaVersion compensates by sending a "view distance" of 64
|
||||||
|
// That's fine, except when the actual view distance sent from the server is five chunks
|
||||||
|
// The client locks up when switching dimensions, expecting more chunks than it's getting
|
||||||
|
// To solve this, we cap at 32 unless we know that the render distance actually exceeds 32
|
||||||
|
// Also, as of 1.19: PS4 crashes with a ChunkRadiusUpdatedPacket too large
|
||||||
|
session.getGeyser().getLogger().debug("Applying dimension switching workaround for Bedrock render distance of "
|
||||||
|
+ session.getServerRenderDistance());
|
||||||
|
ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket();
|
||||||
|
chunkRadiusUpdatedPacket.setRadius(32);
|
||||||
|
session.sendUpstreamPacket(chunkRadiusUpdatedPacket);
|
||||||
|
// Will be re-adjusted on spawn
|
||||||
|
}
|
||||||
|
|
||||||
session.setDimensionType(javaDimension);
|
Vector3f pos = Vector3f.from(0, Short.MAX_VALUE, 0);
|
||||||
|
|
||||||
|
ChangeDimensionPacket changeDimensionPacket = new ChangeDimensionPacket();
|
||||||
|
changeDimensionPacket.setDimension(bedrockDimension);
|
||||||
|
changeDimensionPacket.setRespawn(true);
|
||||||
|
changeDimensionPacket.setPosition(pos);
|
||||||
|
session.sendUpstreamPacket(changeDimensionPacket);
|
||||||
|
|
||||||
|
session.setDimension(javaDimension);
|
||||||
|
setBedrockDimension(session, javaDimension);
|
||||||
|
|
||||||
|
player.setPosition(pos);
|
||||||
|
session.setSpawned(false);
|
||||||
|
session.setLastChunkPosition(null);
|
||||||
|
|
||||||
Set<Effect> entityEffects = session.getEffectCache().getEntityEffects();
|
Set<Effect> entityEffects = session.getEffectCache().getEntityEffects();
|
||||||
for (Effect effect : entityEffects) {
|
for (Effect effect : entityEffects) {
|
||||||
|
|
@ -102,60 +125,6 @@ public class DimensionUtils {
|
||||||
session.sendUpstreamPacket(stopThunderPacket);
|
session.sendUpstreamPacket(stopThunderPacket);
|
||||||
session.setThunder(false);
|
session.setThunder(false);
|
||||||
|
|
||||||
finalizeDimensionSwitch(session, player);
|
|
||||||
|
|
||||||
// If the bedrock nether height workaround is enabled, meaning the client is told it's in the end dimension,
|
|
||||||
// we check if the player is entering the nether and apply the nether fog to fake the fact that the client
|
|
||||||
// thinks they are in the end dimension.
|
|
||||||
if (isCustomBedrockNetherId()) {
|
|
||||||
if (javaDimension.isNetherLike()) {
|
|
||||||
session.camera().sendFog(BEDROCK_FOG_HELL);
|
|
||||||
} else if (previousDimension != null && previousDimension.isNetherLike()) {
|
|
||||||
session.camera().removeFog(BEDROCK_FOG_HELL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch dimensions without clearing internal logic.
|
|
||||||
*/
|
|
||||||
public static void fastSwitchDimension(GeyserSession session, int bedrockDimension) {
|
|
||||||
changeDimension(session, bedrockDimension);
|
|
||||||
finalizeDimensionSwitch(session, session.getPlayerEntity());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void changeDimension(GeyserSession session, int bedrockDimension) {
|
|
||||||
if (session.getServerRenderDistance() > 32 && !session.isEmulatePost1_13Logic()) {
|
|
||||||
// The server-sided view distance wasn't a thing until Minecraft Java 1.14
|
|
||||||
// So ViaVersion compensates by sending a "view distance" of 64
|
|
||||||
// That's fine, except when the actual view distance sent from the server is five chunks
|
|
||||||
// The client locks up when switching dimensions, expecting more chunks than it's getting
|
|
||||||
// To solve this, we cap at 32 unless we know that the render distance actually exceeds 32
|
|
||||||
// Also, as of 1.19: PS4 crashes with a ChunkRadiusUpdatedPacket too large
|
|
||||||
session.getGeyser().getLogger().debug("Applying dimension switching workaround for Bedrock render distance of "
|
|
||||||
+ session.getServerRenderDistance());
|
|
||||||
ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket();
|
|
||||||
chunkRadiusUpdatedPacket.setRadius(32);
|
|
||||||
session.sendUpstreamPacket(chunkRadiusUpdatedPacket);
|
|
||||||
// Will be re-adjusted on spawn
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3f pos = Vector3f.from(0, Short.MAX_VALUE, 0);
|
|
||||||
|
|
||||||
ChangeDimensionPacket changeDimensionPacket = new ChangeDimensionPacket();
|
|
||||||
changeDimensionPacket.setDimension(bedrockDimension);
|
|
||||||
changeDimensionPacket.setRespawn(true);
|
|
||||||
changeDimensionPacket.setPosition(pos);
|
|
||||||
session.sendUpstreamPacket(changeDimensionPacket);
|
|
||||||
|
|
||||||
setBedrockDimension(session, bedrockDimension);
|
|
||||||
|
|
||||||
session.getPlayerEntity().setPosition(pos);
|
|
||||||
session.setSpawned(false);
|
|
||||||
session.setLastChunkPosition(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void finalizeDimensionSwitch(GeyserSession session, Entity player) {
|
|
||||||
//let java server handle portal travel sound
|
//let java server handle portal travel sound
|
||||||
StopSoundPacket stopSoundPacket = new StopSoundPacket();
|
StopSoundPacket stopSoundPacket = new StopSoundPacket();
|
||||||
stopSoundPacket.setStoppingAllSound(true);
|
stopSoundPacket.setStoppingAllSound(true);
|
||||||
|
|
@ -176,12 +145,23 @@ public class DimensionUtils {
|
||||||
// TODO - fix this hack of a fix by sending the final dimension switching logic after sections have been sent.
|
// TODO - fix this hack of a fix by sending the final dimension switching logic after sections have been sent.
|
||||||
// The client wants sections sent to it before it can successfully respawn.
|
// The client wants sections sent to it before it can successfully respawn.
|
||||||
ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
|
ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
|
||||||
|
|
||||||
|
// If the bedrock nether height workaround is enabled, meaning the client is told it's in the end dimension,
|
||||||
|
// we check if the player is entering the nether and apply the nether fog to fake the fact that the client
|
||||||
|
// thinks they are in the end dimension.
|
||||||
|
if (isCustomBedrockNetherId()) {
|
||||||
|
if (NETHER == javaDimension) {
|
||||||
|
session.camera().sendFog(BEDROCK_FOG_HELL);
|
||||||
|
} else if (NETHER == previousDimension) {
|
||||||
|
session.camera().removeFog(BEDROCK_FOG_HELL);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setBedrockDimension(GeyserSession session, int bedrockDimension) {
|
public static void setBedrockDimension(GeyserSession session, int javaDimension) {
|
||||||
session.getChunkCache().setBedrockDimension(switch (bedrockDimension) {
|
session.getChunkCache().setBedrockDimension(switch (javaDimension) {
|
||||||
case BEDROCK_END_ID -> BedrockDimension.THE_END;
|
case DimensionUtils.THE_END -> BedrockDimension.THE_END;
|
||||||
case BEDROCK_DEFAULT_NETHER_ID -> BedrockDimension.THE_NETHER; // JavaDimension *should* be set to BEDROCK_END_ID if the Nether workaround is enabled.
|
case DimensionUtils.NETHER -> DimensionUtils.isCustomBedrockNetherId() ? BedrockDimension.THE_END : BedrockDimension.THE_NETHER;
|
||||||
default -> BedrockDimension.OVERWORLD;
|
default -> BedrockDimension.OVERWORLD;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -190,12 +170,26 @@ public class DimensionUtils {
|
||||||
if (dimension == BedrockDimension.THE_NETHER) {
|
if (dimension == BedrockDimension.THE_NETHER) {
|
||||||
return BEDROCK_NETHER_ID;
|
return BEDROCK_NETHER_ID;
|
||||||
} else if (dimension == BedrockDimension.THE_END) {
|
} else if (dimension == BedrockDimension.THE_END) {
|
||||||
return BEDROCK_END_ID;
|
return 2;
|
||||||
} else {
|
} else {
|
||||||
return BEDROCK_OVERWORLD_ID;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the Java edition dimension IDs to Bedrock edition
|
||||||
|
*
|
||||||
|
* @param javaDimension Dimension ID to convert
|
||||||
|
* @return Converted Bedrock edition dimension ID
|
||||||
|
*/
|
||||||
|
public static int javaToBedrock(int javaDimension) {
|
||||||
|
return switch (javaDimension) {
|
||||||
|
case NETHER -> BEDROCK_NETHER_ID;
|
||||||
|
case THE_END -> 2;
|
||||||
|
default -> 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map the Java edition dimension IDs to Bedrock edition
|
* Map the Java edition dimension IDs to Bedrock edition
|
||||||
*
|
*
|
||||||
|
|
@ -204,23 +198,12 @@ public class DimensionUtils {
|
||||||
*/
|
*/
|
||||||
public static int javaToBedrock(String javaDimension) {
|
public static int javaToBedrock(String javaDimension) {
|
||||||
return switch (javaDimension) {
|
return switch (javaDimension) {
|
||||||
case NETHER_IDENTIFIER -> BEDROCK_NETHER_ID;
|
case "minecraft:the_nether" -> BEDROCK_NETHER_ID;
|
||||||
case "minecraft:the_end" -> 2;
|
case "minecraft:the_end" -> 2;
|
||||||
default -> 0;
|
default -> 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the Bedrock dimension ID, with a safety check if a packet is created before the player is logged/spawned in.
|
|
||||||
*/
|
|
||||||
public static int javaToBedrock(GeyserSession session) {
|
|
||||||
JavaDimension dimension = session.getDimensionType();
|
|
||||||
if (dimension == null) {
|
|
||||||
return BEDROCK_OVERWORLD_ID;
|
|
||||||
}
|
|
||||||
return dimension.bedrockId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Nether dimension in Bedrock does not permit building above Y128 - the Bedrock above the dimension.
|
* The Nether dimension in Bedrock does not permit building above Y128 - the Bedrock above the dimension.
|
||||||
* This workaround sets the Nether as the End dimension to ignore this limit.
|
* This workaround sets the Nether as the End dimension to ignore this limit.
|
||||||
|
|
@ -229,28 +212,28 @@ public class DimensionUtils {
|
||||||
*/
|
*/
|
||||||
public static void changeBedrockNetherId(boolean isAboveNetherBedrockBuilding) {
|
public static void changeBedrockNetherId(boolean isAboveNetherBedrockBuilding) {
|
||||||
// Change dimension ID to the End to allow for building above Bedrock
|
// Change dimension ID to the End to allow for building above Bedrock
|
||||||
BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? BEDROCK_END_ID : BEDROCK_DEFAULT_NETHER_ID;
|
BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the fake, temporary dimension we send clients to so we aren't switching to the same dimension without an additional
|
* Gets the fake, temporary dimension we send clients to so we aren't switching to the same dimension without an additional
|
||||||
* dimension switch.
|
* dimension switch.
|
||||||
*
|
*
|
||||||
* @param currentBedrockDimension the current dimension of the player
|
* @param currentDimension the current dimension of the player
|
||||||
* @param newBedrockDimension the new dimension that the player will be transferred to
|
* @param newDimension the new dimension that the player will be transferred to
|
||||||
* @return the Bedrock fake dimension to transfer to
|
* @return the fake dimension to transfer to
|
||||||
*/
|
*/
|
||||||
public static int getTemporaryDimension(int currentBedrockDimension, int newBedrockDimension) {
|
public static int getTemporaryDimension(int currentDimension, int newDimension) {
|
||||||
if (isCustomBedrockNetherId()) {
|
if (isCustomBedrockNetherId()) {
|
||||||
// Prevents rare instances of Bedrock locking up
|
// Prevents rare instances of Bedrock locking up
|
||||||
return newBedrockDimension == BEDROCK_END_ID ? BEDROCK_OVERWORLD_ID : BEDROCK_END_ID;
|
return javaToBedrock(newDimension) == 2 ? OVERWORLD : NETHER;
|
||||||
}
|
}
|
||||||
// Check current Bedrock dimension and not just the Java dimension.
|
// Check current Bedrock dimension and not just the Java dimension.
|
||||||
// Fixes rare instances like https://github.com/GeyserMC/Geyser/issues/3161
|
// Fixes rare instances like https://github.com/GeyserMC/Geyser/issues/3161
|
||||||
return currentBedrockDimension == BEDROCK_OVERWORLD_ID ? BEDROCK_DEFAULT_NETHER_ID : BEDROCK_OVERWORLD_ID;
|
return javaToBedrock(currentDimension) == 0 ? NETHER : OVERWORLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isCustomBedrockNetherId() {
|
public static boolean isCustomBedrockNetherId() {
|
||||||
return BEDROCK_NETHER_ID == BEDROCK_END_ID;
|
return BEDROCK_NETHER_ID == 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ package org.geysermc.geyser.util;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
|
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult;
|
import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult;
|
||||||
|
|
@ -203,7 +203,7 @@ public class LoginEncryptionUtils {
|
||||||
/**
|
/**
|
||||||
* Shows the code that a user must input into their browser
|
* Shows the code that a user must input into their browser
|
||||||
*/
|
*/
|
||||||
public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, StepMsaDeviceCode.MsaDeviceCode msCode) {
|
public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) {
|
||||||
String locale = session.locale();
|
String locale = session.locale();
|
||||||
|
|
||||||
StringBuilder message = new StringBuilder("%xbox.signin.website\n")
|
StringBuilder message = new StringBuilder("%xbox.signin.website\n")
|
||||||
|
|
@ -212,7 +212,7 @@ public class LoginEncryptionUtils {
|
||||||
.append(ChatColor.RESET)
|
.append(ChatColor.RESET)
|
||||||
.append("\n%xbox.signin.enterCode\n")
|
.append("\n%xbox.signin.enterCode\n")
|
||||||
.append(ChatColor.GREEN)
|
.append(ChatColor.GREEN)
|
||||||
.append(msCode.getUserCode());
|
.append(msCode.user_code);
|
||||||
int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout();
|
int timeout = session.getGeyser().getConfig().getPendingAuthenticationTimeout();
|
||||||
if (timeout != 0) {
|
if (timeout != 0) {
|
||||||
message.append("\n\n")
|
message.append("\n\n")
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.util;
|
package org.geysermc.geyser.util;
|
||||||
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket;
|
|
||||||
import org.geysermc.cumulus.component.DropdownComponent;
|
import org.geysermc.cumulus.component.DropdownComponent;
|
||||||
import org.geysermc.cumulus.form.CustomForm;
|
import org.geysermc.cumulus.form.CustomForm;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
|
@ -34,7 +33,6 @@ import org.geysermc.geyser.level.WorldManager;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
import org.geysermc.geyser.text.MinecraftLocale;
|
import org.geysermc.geyser.text.MinecraftLocale;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty;
|
|
||||||
|
|
||||||
public class SettingsUtils {
|
public class SettingsUtils {
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,7 +96,6 @@ public class SettingsUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.validResultHandler((response) -> {
|
builder.validResultHandler((response) -> {
|
||||||
applyDifficultyFix(session);
|
|
||||||
if (showClientSettings) {
|
if (showClientSettings) {
|
||||||
// Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
|
// Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config.
|
||||||
if (showCoordinates) {
|
if (showCoordinates) {
|
||||||
|
|
@ -137,21 +134,9 @@ public class SettingsUtils {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.closedOrInvalidResultHandler($ -> applyDifficultyFix(session));
|
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyDifficultyFix(GeyserSession session) {
|
|
||||||
// Peaceful difficulty allows always eating food - hence, we just do not send it to Bedrock.
|
|
||||||
// Since we sent the real difficulty before opening the server settings form, let's restore it to our workaround here
|
|
||||||
if (session.getWorldCache().getDifficulty() == Difficulty.PEACEFUL) {
|
|
||||||
SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket();
|
|
||||||
setDifficultyPacket.setDifficulty(Difficulty.EASY.ordinal());
|
|
||||||
session.sendUpstreamPacket(setDifficultyPacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String translateEntry(String key, String locale) {
|
private static String translateEntry(String key, String locale) {
|
||||||
if (key.startsWith("%")) {
|
if (key.startsWith("%")) {
|
||||||
// Bedrock will translate
|
// Bedrock will translate
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ public class StatisticsUtils {
|
||||||
|
|
||||||
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
||||||
if (entry.getKey() instanceof BreakItemStatistic statistic) {
|
if (entry.getKey() instanceof BreakItemStatistic statistic) {
|
||||||
Item item = itemRegistry.get(statistic.getId());
|
String item = itemRegistry.get(statistic.getId()).javaIdentifier();
|
||||||
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ public class StatisticsUtils {
|
||||||
|
|
||||||
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
||||||
if (entry.getKey() instanceof CraftItemStatistic statistic) {
|
if (entry.getKey() instanceof CraftItemStatistic statistic) {
|
||||||
Item item = itemRegistry.get(statistic.getId());
|
String item = itemRegistry.get(statistic.getId()).javaIdentifier();
|
||||||
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +127,7 @@ public class StatisticsUtils {
|
||||||
|
|
||||||
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
||||||
if (entry.getKey() instanceof UseItemStatistic statistic) {
|
if (entry.getKey() instanceof UseItemStatistic statistic) {
|
||||||
Item item = itemRegistry.get(statistic.getId());
|
String item = itemRegistry.get(statistic.getId()).javaIdentifier();
|
||||||
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +137,7 @@ public class StatisticsUtils {
|
||||||
|
|
||||||
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
||||||
if (entry.getKey() instanceof PickupItemStatistic statistic) {
|
if (entry.getKey() instanceof PickupItemStatistic statistic) {
|
||||||
Item item = itemRegistry.get(statistic.getId());
|
String item = itemRegistry.get(statistic.getId()).javaIdentifier();
|
||||||
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +147,7 @@ public class StatisticsUtils {
|
||||||
|
|
||||||
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
for (Object2IntMap.Entry<Statistic> entry : session.getStatistics().object2IntEntrySet()) {
|
||||||
if (entry.getKey() instanceof DropItemStatistic statistic) {
|
if (entry.getKey() instanceof DropItemStatistic statistic) {
|
||||||
Item item = itemRegistry.get(statistic.getId());
|
String item = itemRegistry.get(statistic.getId()).javaIdentifier();
|
||||||
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
content.add(getItemTranslateKey(item, language) + ": " + entry.getIntValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -208,8 +208,14 @@ public class StatisticsUtils {
|
||||||
* @param language the language to search in
|
* @param language the language to search in
|
||||||
* @return the full name of the item
|
* @return the full name of the item
|
||||||
*/
|
*/
|
||||||
private static String getItemTranslateKey(Item item, String language) {
|
private static String getItemTranslateKey(String item, String language) {
|
||||||
return MinecraftLocale.getLocaleString(item.translationKey(), language);
|
item = item.replace("minecraft:", "item.minecraft.");
|
||||||
|
String translatedItem = MinecraftLocale.getLocaleString(item, language);
|
||||||
|
if (translatedItem.equals(item)) {
|
||||||
|
// Didn't translate; must be a block
|
||||||
|
translatedItem = MinecraftLocale.getLocaleString(item.replace("item.", "block."), language);
|
||||||
|
}
|
||||||
|
return translatedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String translate(String keys, String locale) {
|
private static String translate(String keys, String locale) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[versions]
|
[versions]
|
||||||
base-api = "1.0.0-SNAPSHOT"
|
base-api = "1.0.0-SNAPSHOT"
|
||||||
cumulus = "1.1.2"
|
cumulus = "1.1.2"
|
||||||
erosion = "1.1-20240515.191456-1"
|
erosion = "1.1-20240521.000109-3"
|
||||||
events = "1.1-SNAPSHOT"
|
events = "1.1-SNAPSHOT"
|
||||||
jackson = "2.17.0"
|
jackson = "2.17.0"
|
||||||
fastutil = "8.5.2"
|
fastutil = "8.5.2"
|
||||||
|
|
@ -12,8 +12,8 @@ gson = "2.3.1" # Provided by Spigot 1.8.8
|
||||||
websocket = "1.5.1"
|
websocket = "1.5.1"
|
||||||
protocol = "3.0.0.Beta2-20240704.153116-14"
|
protocol = "3.0.0.Beta2-20240704.153116-14"
|
||||||
raknet = "1.0.0.CR3-20240416.144209-1"
|
raknet = "1.0.0.CR3-20240416.144209-1"
|
||||||
minecraftauth = "4.1.0"
|
mcauthlib = "e5b0bcc"
|
||||||
mcprotocollib = "1.21-20240725.013034-16"
|
mcprotocollib = "1.21-20240616.154144-5"
|
||||||
adventure = "4.14.0"
|
adventure = "4.14.0"
|
||||||
adventure-platform = "4.3.0"
|
adventure-platform = "4.3.0"
|
||||||
junit = "5.9.2"
|
junit = "5.9.2"
|
||||||
|
|
@ -107,7 +107,7 @@ commodore = { group = "me.lucko", name = "commodore", version.ref = "commodore"
|
||||||
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
|
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
|
||||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||||
junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
|
junit = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
|
||||||
minecraftauth = { group = "net.raphimc", name = "MinecraftAuth", version.ref = "minecraftauth" }
|
mcauthlib = { group = "com.github.GeyserMC", name = "MCAuthLib", version.ref = "mcauthlib" }
|
||||||
mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" }
|
mcprotocollib = { group = "org.geysermc.mcprotocollib", name = "protocol", version.ref = "mcprotocollib" }
|
||||||
raknet = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version.ref = "raknet" }
|
raknet = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version.ref = "raknet" }
|
||||||
terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" }
|
terminalconsoleappender = { group = "net.minecrell", name = "terminalconsoleappender", version.ref = "terminalconsoleappender" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue