Merge branch 'master' into fix-fabric-world-manager-performance

This commit is contained in:
chris 2024-08-20 11:12:05 +02:00 committed by GitHub
commit f18a163eaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 15631 additions and 219 deletions

2
.gitignore vendored
View file

@ -249,6 +249,8 @@ locales/
/packs/ /packs/
/dump.json /dump.json
/saved-refresh-tokens.json /saved-refresh-tokens.json
/saved-auth-chains.json
/custom_mappings/ /custom_mappings/
/languages/ /languages/
/custom-skulls.yml /custom-skulls.yml
/permissions.yml

View file

@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
## Supported Versions ## Supported Versions
Geyser is currently supporting Minecraft Bedrock 1.20.80 - 1.21.3 and Minecraft Java Server 1.21. For more info please see [here](https://geysermc.org/wiki/geyser/supported-versions/). Geyser is currently supporting Minecraft Bedrock 1.20.80 - 1.21.20 and Minecraft Java Server 1.21/1.21.1. For more info please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
## Setting Up ## Setting Up
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser. Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.

View file

@ -60,6 +60,16 @@ public interface GeyserConnection extends Connection, CommandSource {
*/ */
@NonNull EntityData entities(); @NonNull EntityData entities();
/**
* Returns the current ping of the connection.
*/
int ping();
/**
* Closes the currently open form on the client.
*/
void closeForm();
/** /**
* @param javaId the Java entity ID to look up. * @param javaId the Java entity ID to look up.
* @return a {@link GeyserEntity} if present in this connection's entity tracker. * @return a {@link GeyserEntity} if present in this connection's entity tracker.
@ -132,9 +142,4 @@ public interface GeyserConnection extends Connection, CommandSource {
@Deprecated @Deprecated
@NonNull @NonNull
Set<String> fogEffects(); Set<String> fogEffects();
/**
* Returns the current ping of the connection.
*/
int ping();
} }

View file

@ -71,9 +71,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
private IGeyserPingPassthrough geyserBungeePingPassthrough; private IGeyserPingPassthrough geyserBungeePingPassthrough;
private GeyserImpl geyser; private GeyserImpl geyser;
// We can't disable the plugin; hence we need to keep track of it manually
private boolean disabled;
@Override @Override
public void onLoad() { public void onLoad() {
onGeyserInitialize(); onGeyserInitialize();
@ -98,7 +95,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
} }
if (!this.loadConfig()) { if (!this.loadConfig()) {
disabled = true;
return; return;
} }
this.geyserLogger.setDebug(geyserConfig.isDebugMode()); this.geyserLogger.setDebug(geyserConfig.isDebugMode());
@ -112,7 +108,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
@Override @Override
public void onEnable() { public void onEnable() {
if (disabled) { if (geyser == null) {
return; // Config did not load properly! return; // Config did not load properly!
} }
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating

View file

@ -89,6 +89,11 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
} }
public void onGeyserEnable() { public void onGeyserEnable() {
// "Disabling" a mod isn't possible; so if we fail to initialize we need to manually stop here
if (geyser == null) {
return;
}
if (GeyserImpl.getInstance().isReloading()) { if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) { if (!loadConfig()) {
return; return;

View file

@ -81,5 +81,7 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
modrinth { modrinth {
uploadFile.set(tasks.getByPath("shadowJar")) uploadFile.set(tasks.getByPath("shadowJar"))
gameVersions.addAll("1.16.5", "1.17", "1.17.1", "1.18", "1.18.1", "1.18.2", "1.19",
"1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6")
loaders.addAll("spigot", "paper") loaders.addAll("spigot", "paper")
} }

View file

@ -117,7 +117,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2")); geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
geyserLogger.error(""); geyserLogger.error("");
geyserLogger.error("*********************************************"); geyserLogger.error("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
@ -131,7 +130,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper")); geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
geyserLogger.error(""); geyserLogger.error("");
geyserLogger.error("*********************************************"); geyserLogger.error("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
} }
@ -144,10 +142,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserLogger.error("This version of Spigot is using an outdated version of netty. Please use Paper instead!"); geyserLogger.error("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
geyserLogger.error(""); geyserLogger.error("");
geyserLogger.error("*********************************************"); geyserLogger.error("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
try {
// Check spigot config for BungeeCord mode
if (Bukkit.getServer().spigot().getConfig().getBoolean("settings.bungeecord")) {
warnInvalidProxySetups("BungeeCord");
return;
}
// Now: Check for velocity mode - deliberately after checking bungeecord because this is a paper only option
if (Bukkit.getServer().spigot().getPaperConfig().getBoolean("proxies.velocity.enabled")) {
warnInvalidProxySetups("Velocity");
return;
}
} catch (NoSuchMethodError e) {
// no-op
}
if (!loadConfig()) { if (!loadConfig()) {
return; return;
} }
@ -162,6 +175,12 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
@Override @Override
public void onEnable() { public void onEnable() {
// Disabling the plugin in onLoad() is not supported; we need to manually stop here and disable ourselves
if (geyser == null) {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
// Create command manager early so we can add Geyser extension commands // Create command manager early so we can add Geyser extension commands
var sourceConverter = new CommandSourceConverter<>( var sourceConverter = new CommandSourceConverter<>(
CommandSender.class, CommandSender.class,
@ -458,4 +477,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
return true; return true;
} }
private void warnInvalidProxySetups(String platform) {
geyserLogger.error("*********************************************");
geyserLogger.error("");
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy_backend", platform));
geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.setup_guide", "https://geysermc.org/wiki/geyser/setup/"));
geyserLogger.error("");
geyserLogger.error("*********************************************");
}
} }

View file

@ -113,6 +113,10 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
@Override @Override
public void onGeyserEnable() { public void onGeyserEnable() {
// If e.g. the config failed to load, GeyserImpl was not loaded and we cannot start
if (geyser == null) {
return;
}
if (GeyserImpl.getInstance().isReloading()) { if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) { if (!loadConfig()) {
return; return;

View file

@ -132,6 +132,10 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
@Override @Override
public void onGeyserEnable() { public void onGeyserEnable() {
// If e.g. the config failed to load, GeyserImpl was not loaded and we cannot start
if (geyser == null) {
return;
}
boolean reloading = geyser.isReloading(); boolean reloading = geyser.isReloading();
if (reloading) { if (reloading) {
if (!this.loadConfig()) { if (!this.loadConfig()) {
@ -155,6 +159,9 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
// Only initialize the ping passthrough if the protocol version is above beta 1.7.3, as that's when the status protocol was added // Only initialize the ping passthrough if the protocol version is above beta 1.7.3, as that's when the status protocol was added
this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser); this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser);
} }
if (this.config.getRemote().authType() == AuthType.FLOODGATE) {
ViaProxy.getConfig().setPassthroughBungeecordPlayerInfo(true);
}
} }
@Override @Override

View file

@ -2,4 +2,4 @@ name: "${name}-ViaProxy"
version: "${version}" version: "${version}"
author: "${author}" author: "${author}"
main: "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyPlugin" main: "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyPlugin"
min-version: "3.2.1" min-version: "3.3.2"

View file

@ -0,0 +1,45 @@
repositories {
// mavenLocal()
mavenCentral()
// Floodgate, Cumulus etc.
maven("https://repo.opencollab.dev/main")
// Paper, Velocity
maven("https://repo.papermc.io/repository/maven-public")
// Spigot
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") {
mavenContent { snapshotsOnly() }
}
// BungeeCord
maven("https://oss.sonatype.org/content/repositories/snapshots") {
mavenContent { snapshotsOnly() }
}
// NeoForge
maven("https://maven.neoforged.net/releases") {
mavenContent { releasesOnly() }
}
// Minecraft
maven("https://libraries.minecraft.net") {
name = "minecraft"
mavenContent { releasesOnly() }
}
// ViaVersion
maven("https://repo.viaversion.com") {
name = "viaversion"
}
// Jitpack for e.g. MCPL
maven("https://jitpack.io") {
content { includeGroupByRegex("com\\.github\\..*") }
}
// For Adventure snapshots
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}

View file

@ -5,6 +5,7 @@ import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.maven import org.gradle.kotlin.dsl.maven
plugins { plugins {
id("geyser.build-logic")
id("geyser.publish-conventions") id("geyser.publish-conventions")
id("architectury-plugin") id("architectury-plugin")
id("dev.architectury.loom") id("dev.architectury.loom")
@ -116,12 +117,3 @@ dependencies {
minecraft(libs.minecraft) minecraft(libs.minecraft)
mappings(loom.officialMojangMappings()) mappings(loom.officialMojangMappings())
} }
repositories {
// mavenLocal()
maven("https://repo.opencollab.dev/main")
maven("https://jitpack.io")
maven("https://oss.sonatype.org/content/repositories/snapshots/")
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
maven("https://maven.neoforged.net/releases")
}

View file

@ -11,7 +11,7 @@ modrinth {
versionNumber.set(project.version as String + "-" + System.getenv("BUILD_NUMBER")) versionNumber.set(project.version as String + "-" + System.getenv("BUILD_NUMBER"))
versionType.set("beta") versionType.set("beta")
changelog.set(System.getenv("CHANGELOG") ?: "") changelog.set(System.getenv("CHANGELOG") ?: "")
gameVersions.add(libs.minecraft.get().version as String) gameVersions.addAll("1.21", libs.minecraft.get().version as String)
failSilently.set(true) failSilently.set(true)
syncBodyFrom.set(rootProject.file("README.md").readText()) syncBodyFrom.set(rootProject.file("README.md").readText())

View file

@ -156,12 +156,6 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
private final SessionManager sessionManager = new SessionManager(); private final SessionManager sessionManager = new SessionManager();
/**
* This is used in GeyserConnect to stop the bedrock server binding to a port
*/
@Setter
private static boolean shouldStartListener = true;
private FloodgateCipher cipher; private FloodgateCipher cipher;
private FloodgateSkinUploader skinUploader; private FloodgateSkinUploader skinUploader;
private NewsHandler newsHandler; private NewsHandler newsHandler;
@ -435,24 +429,27 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); bedrockThreadCount = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
} }
if (shouldStartListener) {
this.geyserServer = new GeyserServer(this, bedrockThreadCount); this.geyserServer = new GeyserServer(this, bedrockThreadCount);
this.geyserServer.bind(new InetSocketAddress(config.getBedrock().address(), config.getBedrock().port())) this.geyserServer.bind(new InetSocketAddress(config.getBedrock().address(), config.getBedrock().port()))
.whenComplete((avoid, throwable) -> { .whenComplete((avoid, throwable) -> {
if (throwable == null) {
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", config.getBedrock().address(),
String.valueOf(config.getBedrock().port())));
} else {
String address = config.getBedrock().address(); String address = config.getBedrock().address();
int port = config.getBedrock().port(); String port = String.valueOf(config.getBedrock().port()); // otherwise we get commas
logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, String.valueOf(port)));
if (throwable == null) {
if ("0.0.0.0".equals(address)) {
// basically just hide it in the log because some people get confused and try to change it
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start.ip_suppressed", port));
} else {
logger.info(GeyserLocale.getLocaleStringLog("geyser.core.start", address, port));
}
} else {
logger.severe(GeyserLocale.getLocaleStringLog("geyser.core.fail", address, port));
if (!"0.0.0.0".equals(address)) { if (!"0.0.0.0".equals(address)) {
logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN)); logger.info(Component.text("Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0", NamedTextColor.GREEN));
logger.info(Component.text("Then, restart this server.", NamedTextColor.GREEN)); logger.info(Component.text("Then, restart this server.", NamedTextColor.GREEN));
} }
} }
}).join(); }).join();
}
if (config.getRemote().authType() == AuthType.FLOODGATE) { if (config.getRemote().authType() == AuthType.FLOODGATE) {
try { try {

View file

@ -888,7 +888,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 +914,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 +955,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)

View file

@ -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;
@ -74,6 +75,7 @@ public class LivingEntity extends Entity {
protected ItemData chestplate = ItemData.AIR; protected ItemData chestplate = ItemData.AIR;
protected ItemData leggings = ItemData.AIR; protected ItemData leggings = ItemData.AIR;
protected ItemData boots = ItemData.AIR; protected ItemData boots = ItemData.AIR;
protected ItemData body = ItemData.AIR;
protected ItemData hand = ItemData.AIR; protected ItemData hand = ItemData.AIR;
protected ItemData offhand = ItemData.AIR; protected ItemData offhand = ItemData.AIR;
@ -112,6 +114,10 @@ public class LivingEntity extends Entity {
this.chestplate = ItemTranslator.translateToBedrock(session, stack); this.chestplate = ItemTranslator.translateToBedrock(session, stack);
} }
public void setBody(ItemStack stack) {
this.body = ItemTranslator.translateToBedrock(session, stack);
}
public void setLeggings(ItemStack stack) { public void setLeggings(ItemStack stack) {
this.leggings = ItemTranslator.translateToBedrock(session, stack); this.leggings = ItemTranslator.translateToBedrock(session, stack);
} }
@ -289,6 +295,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.
*/ */
@ -323,6 +359,7 @@ public class LivingEntity extends Entity {
armorEquipmentPacket.setChestplate(chestplate); armorEquipmentPacket.setChestplate(chestplate);
armorEquipmentPacket.setLeggings(leggings); armorEquipmentPacket.setLeggings(leggings);
armorEquipmentPacket.setBoots(boots); armorEquipmentPacket.setBoots(boots);
armorEquipmentPacket.setBody(body);
session.sendUpstreamPacket(armorEquipmentPacket); session.sendUpstreamPacket(armorEquipmentPacket);
} }
@ -401,9 +438,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));

View file

@ -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);
}
} }

View file

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

View file

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

View file

@ -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;
@ -74,6 +76,16 @@ public class SessionPlayerEntity extends PlayerEntity {
*/ */
@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();
@ -315,6 +327,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();
} }

View file

@ -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++;
}
}
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.vector.Vector2f;
public interface ClientVehicle {
VehicleComponent<?> getVehicleComponent();
Vector2f getAdjustedInput(Vector2f input);
float getVehicleSpeed();
boolean isClientControlled();
default boolean canWalkOnLava() {
return false;
}
default boolean canClimb() {
return true;
}
}

View file

@ -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()));
}
}
}

View file

@ -43,13 +43,13 @@ public class CameraDefinitions {
static { static {
CAMERA_PRESETS = List.of( CAMERA_PRESETS = List.of(
new CameraPreset(CameraPerspective.FIRST_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()), new CameraPreset(CameraPerspective.FIRST_PERSON.id(), "", null, null, null, null, null, null, OptionalBoolean.empty()),
new CameraPreset(CameraPerspective.FREE.id(), "", null, null, null, null, OptionalBoolean.empty()), new CameraPreset(CameraPerspective.FREE.id(), "", null, null, null, null, null, null, OptionalBoolean.empty()),
new CameraPreset(CameraPerspective.THIRD_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()), new CameraPreset(CameraPerspective.THIRD_PERSON.id(), "", null, null, null, null, null, null, OptionalBoolean.empty()),
new CameraPreset(CameraPerspective.THIRD_PERSON_FRONT.id(), "", null, null, null, null, OptionalBoolean.empty()), new CameraPreset(CameraPerspective.THIRD_PERSON_FRONT.id(), "", null, null, null, null, null, null, OptionalBoolean.empty()),
new CameraPreset("geyser:free_audio", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(false)), new CameraPreset("geyser:free_audio", "minecraft:free", null, null, null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(false)),
new CameraPreset("geyser:free_effects", "minecraft:free", null, null, null, CameraAudioListener.CAMERA, OptionalBoolean.of(true)), new CameraPreset("geyser:free_effects", "minecraft:free", null, null, null, null, null, CameraAudioListener.CAMERA, OptionalBoolean.of(true)),
new CameraPreset("geyser:free_audio_effects", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(true))); new CameraPreset("geyser:free_audio_effects", "minecraft:free", null, null, null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(true)));
SimpleDefinitionRegistry.Builder<NamedDefinition> builder = SimpleDefinitionRegistry.builder(); SimpleDefinitionRegistry.Builder<NamedDefinition> builder = SimpleDefinitionRegistry.builder();
for (int i = 0; i < CAMERA_PRESETS.size(); i++) { for (int i = 0; i < CAMERA_PRESETS.size(); i++) {

View file

@ -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();
} }

View file

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

View file

@ -34,11 +34,13 @@ 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.
* @param bedrockId the Bedrock dimension ID of this dimension. * @param bedrockId the Bedrock dimension ID of this dimension.
* As a Java dimension can be null in some login cases (e.g. GeyserConnect), make sure the player * As a Java dimension can be null in some login cases (e.g. GeyserConnect), make sure the player
* is logged in before utilizing this field. * is logged in before utilizing this field.
*/ */
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, int bedrockId, boolean isNetherLike) {
public static JavaDimension read(RegistryEntryContext entry) { public static JavaDimension read(RegistryEntryContext entry) {
NbtMap dimension = entry.data(); NbtMap dimension = entry.data();
@ -48,6 +50,8 @@ 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.getNumber("coordinate_scale").doubleValue(); // FIXME see if we can change this in the NBT library itself. double coordinateScale = dimension.getNumber("coordinate_scale").doubleValue(); // FIXME see if we can change this in the NBT library itself.
@ -67,6 +71,6 @@ public record JavaDimension(int minY, int maxY, boolean piglinSafe, double world
isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(effects); isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(effects);
} }
return new JavaDimension(minY, maxY, piglinSafe, coordinateScale, bedrockId, isNetherLike); return new JavaDimension(minY, maxY, piglinSafe, ultrawarm, coordinateScale, bedrockId, isNetherLike);
} }
} }

View file

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

View file

@ -0,0 +1,32 @@
/*
* 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.level.block;
public enum Fluid {
WATER,
LAVA,
EMPTY
}

View file

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

View file

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

View file

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

View file

@ -40,6 +40,9 @@ import org.cloudburstmc.protocol.bedrock.codec.v407.serializer.InventorySlotSeri
import org.cloudburstmc.protocol.bedrock.codec.v486.serializer.BossEventSerializer_v486; import org.cloudburstmc.protocol.bedrock.codec.v486.serializer.BossEventSerializer_v486;
import org.cloudburstmc.protocol.bedrock.codec.v557.serializer.SetEntityDataSerializer_v557; import org.cloudburstmc.protocol.bedrock.codec.v557.serializer.SetEntityDataSerializer_v557;
import org.cloudburstmc.protocol.bedrock.codec.v662.serializer.SetEntityMotionSerializer_v662; import org.cloudburstmc.protocol.bedrock.codec.v662.serializer.SetEntityMotionSerializer_v662;
import org.cloudburstmc.protocol.bedrock.codec.v712.serializer.InventoryContentSerializer_v712;
import org.cloudburstmc.protocol.bedrock.codec.v712.serializer.InventorySlotSerializer_v712;
import org.cloudburstmc.protocol.bedrock.codec.v712.serializer.MobArmorEquipmentSerializer_v712;
import org.cloudburstmc.protocol.bedrock.packet.AnvilDamagePacket; import org.cloudburstmc.protocol.bedrock.packet.AnvilDamagePacket;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.BossEventPacket; import org.cloudburstmc.protocol.bedrock.packet.BossEventPacket;
@ -119,7 +122,17 @@ class CodecProcessor {
/** /**
* Serializer that throws an exception when trying to deserialize InventoryContentPacket since server-auth inventory is used. * Serializer that throws an exception when trying to deserialize InventoryContentPacket since server-auth inventory is used.
*/ */
private static final BedrockPacketSerializer<InventoryContentPacket> INVENTORY_CONTENT_SERIALIZER = new InventoryContentSerializer_v407() { private static final BedrockPacketSerializer<InventoryContentPacket> INVENTORY_CONTENT_SERIALIZER_V407 = new InventoryContentSerializer_v407() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventoryContentPacket packet) {
throw new IllegalArgumentException("Client cannot send InventoryContentPacket in server-auth inventory environment!");
}
};
/**
* Serializer that throws an exception when trying to deserialize InventoryContentPacket since server-auth inventory is used.
*/
private static final BedrockPacketSerializer<InventoryContentPacket> INVENTORY_CONTENT_SERIALIZER_V712 = new InventoryContentSerializer_v712() {
@Override @Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventoryContentPacket packet) { public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventoryContentPacket packet) {
throw new IllegalArgumentException("Client cannot send InventoryContentPacket in server-auth inventory environment!"); throw new IllegalArgumentException("Client cannot send InventoryContentPacket in server-auth inventory environment!");
@ -129,7 +142,17 @@ class CodecProcessor {
/** /**
* Serializer that throws an exception when trying to deserialize InventorySlotPacket since server-auth inventory is used. * Serializer that throws an exception when trying to deserialize InventorySlotPacket since server-auth inventory is used.
*/ */
private static final BedrockPacketSerializer<InventorySlotPacket> INVENTORY_SLOT_SERIALIZER = new InventorySlotSerializer_v407() { private static final BedrockPacketSerializer<InventorySlotPacket> INVENTORY_SLOT_SERIALIZER_V407 = new InventorySlotSerializer_v407() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlotPacket packet) {
throw new IllegalArgumentException("Client cannot send InventorySlotPacket in server-auth inventory environment!");
}
};
/*
* Serializer that throws an exception when trying to deserialize InventorySlotPacket since server-auth inventory is used.
*/
private static final BedrockPacketSerializer<InventorySlotPacket> INVENTORY_SLOT_SERIALIZER_V712 = new InventorySlotSerializer_v712() {
@Override @Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlotPacket packet) { public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, InventorySlotPacket packet) {
throw new IllegalArgumentException("Client cannot send InventorySlotPacket in server-auth inventory environment!"); throw new IllegalArgumentException("Client cannot send InventorySlotPacket in server-auth inventory environment!");
@ -148,7 +171,16 @@ class CodecProcessor {
/** /**
* Serializer that does nothing when trying to deserialize MobArmorEquipmentPacket since it is not used from the client. * Serializer that does nothing when trying to deserialize MobArmorEquipmentPacket since it is not used from the client.
*/ */
private static final BedrockPacketSerializer<MobArmorEquipmentPacket> MOB_ARMOR_EQUIPMENT_SERIALIZER = new MobArmorEquipmentSerializer_v291() { private static final BedrockPacketSerializer<MobArmorEquipmentPacket> MOB_ARMOR_EQUIPMENT_SERIALIZER_V291 = new MobArmorEquipmentSerializer_v291() {
@Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, MobArmorEquipmentPacket packet) {
}
};
/**
* Serializer that does nothing when trying to deserialize MobArmorEquipmentPacket since it is not used from the client.
*/
private static final BedrockPacketSerializer<MobArmorEquipmentPacket> MOB_ARMOR_EQUIPMENT_SERIALIZER_V712 = new MobArmorEquipmentSerializer_v712() {
@Override @Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, MobArmorEquipmentPacket packet) { public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, MobArmorEquipmentPacket packet) {
} }
@ -193,7 +225,7 @@ class CodecProcessor {
/** /**
* Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v662. * Serializer that does nothing when trying to deserialize SetEntityMotionPacket since it is not used from the client for codec v662.
*/ */
private static final BedrockPacketSerializer<SetEntityMotionPacket> SET_ENTITY_MOTION_SERIALIZER_V662 = new SetEntityMotionSerializer_v662() { private static final BedrockPacketSerializer<SetEntityMotionPacket> SET_ENTITY_MOTION_SERIALIZER = new SetEntityMotionSerializer_v662() {
@Override @Override
public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityMotionPacket packet) { public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, SetEntityMotionPacket packet) {
} }
@ -224,6 +256,8 @@ class CodecProcessor {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static BedrockCodec processCodec(BedrockCodec codec) { static BedrockCodec processCodec(BedrockCodec codec) {
boolean isPre712 = codec.getProtocolVersion() < 712;
BedrockCodec.Builder codecBuilder = codec.toBuilder() BedrockCodec.Builder codecBuilder = codec.toBuilder()
// Illegal unused serverbound EDU packets // Illegal unused serverbound EDU packets
.updateSerializer(PhotoTransferPacket.class, ILLEGAL_SERIALIZER) .updateSerializer(PhotoTransferPacket.class, ILLEGAL_SERIALIZER)
@ -252,15 +286,15 @@ class CodecProcessor {
.updateSerializer(AnvilDamagePacket.class, IGNORED_SERIALIZER) .updateSerializer(AnvilDamagePacket.class, IGNORED_SERIALIZER)
.updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER) .updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER)
// Illegal when serverbound due to Geyser specific setup // Illegal when serverbound due to Geyser specific setup
.updateSerializer(InventoryContentPacket.class, INVENTORY_CONTENT_SERIALIZER) .updateSerializer(InventoryContentPacket.class, isPre712 ? INVENTORY_CONTENT_SERIALIZER_V407 : INVENTORY_CONTENT_SERIALIZER_V712)
.updateSerializer(InventorySlotPacket.class, INVENTORY_SLOT_SERIALIZER) .updateSerializer(InventorySlotPacket.class, isPre712 ? INVENTORY_SLOT_SERIALIZER_V407 : INVENTORY_SLOT_SERIALIZER_V712)
// Ignored only when serverbound // Ignored only when serverbound
.updateSerializer(BossEventPacket.class, BOSS_EVENT_SERIALIZER) .updateSerializer(BossEventPacket.class, BOSS_EVENT_SERIALIZER)
.updateSerializer(MobArmorEquipmentPacket.class, MOB_ARMOR_EQUIPMENT_SERIALIZER) .updateSerializer(MobArmorEquipmentPacket.class, isPre712 ? MOB_ARMOR_EQUIPMENT_SERIALIZER_V291 : MOB_ARMOR_EQUIPMENT_SERIALIZER_V712)
.updateSerializer(PlayerHotbarPacket.class, PLAYER_HOTBAR_SERIALIZER) .updateSerializer(PlayerHotbarPacket.class, PLAYER_HOTBAR_SERIALIZER)
.updateSerializer(PlayerSkinPacket.class, PLAYER_SKIN_SERIALIZER) .updateSerializer(PlayerSkinPacket.class, PLAYER_SKIN_SERIALIZER)
.updateSerializer(SetEntityDataPacket.class, SET_ENTITY_DATA_SERIALIZER) .updateSerializer(SetEntityDataPacket.class, SET_ENTITY_DATA_SERIALIZER)
.updateSerializer(SetEntityMotionPacket.class, SET_ENTITY_MOTION_SERIALIZER_V662) .updateSerializer(SetEntityMotionPacket.class, SET_ENTITY_MOTION_SERIALIZER)
.updateSerializer(SetEntityLinkPacket.class, SET_ENTITY_LINK_SERIALIZER) .updateSerializer(SetEntityLinkPacket.class, SET_ENTITY_LINK_SERIALIZER)
// Valid serverbound packets where reading of some fields can be skipped // Valid serverbound packets where reading of some fields can be skipped
.updateSerializer(MobEquipmentPacket.class, MOB_EQUIPMENT_SERIALIZER) .updateSerializer(MobEquipmentPacket.class, MOB_EQUIPMENT_SERIALIZER)

View file

@ -29,6 +29,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671; import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671;
import org.cloudburstmc.protocol.bedrock.codec.v685.Bedrock_v685; import org.cloudburstmc.protocol.bedrock.codec.v685.Bedrock_v685;
import org.cloudburstmc.protocol.bedrock.codec.v686.Bedrock_v686;
import org.cloudburstmc.protocol.bedrock.codec.v712.Bedrock_v712;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec; import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec; import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec;
@ -43,17 +45,13 @@ import java.util.StringJoiner;
*/ */
public final class GameProtocol { public final class GameProtocol {
// Surprise protocol bump WOW
private static final BedrockCodec BEDROCK_V686 = Bedrock_v685.CODEC.toBuilder()
.protocolVersion(686)
.minecraftVersion("1.21.2")
.build();
/** /**
* Default Bedrock codec that should act as a fallback. Should represent the latest available * Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports. * release of the game that Geyser supports.
*/ */
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(BEDROCK_V686); public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v712.CODEC.toBuilder()
.minecraftVersion("1.21.20")
.build());
/** /**
* A list of all supported Bedrock versions that can join Geyser * A list of all supported Bedrock versions that can join Geyser
@ -73,9 +71,10 @@ public final class GameProtocol {
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder() SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v685.CODEC.toBuilder()
.minecraftVersion("1.21.0/1.21.1") .minecraftVersion("1.21.0/1.21.1")
.build())); .build()));
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v686.CODEC.toBuilder()
.minecraftVersion("1.21.2/1.21.3") .minecraftVersion("1.21.2/1.21.3")
.build()); .build()));
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
} }
/** /**
@ -98,6 +97,10 @@ public final class GameProtocol {
return session.getUpstream().getProtocolVersion() < Bedrock_v685.CODEC.getProtocolVersion(); return session.getUpstream().getProtocolVersion() < Bedrock_v685.CODEC.getProtocolVersion();
} }
public static boolean isPre1_21_2(GeyserSession session) {
return session.getUpstream().getProtocolVersion() < Bedrock_v686.CODEC.getProtocolVersion();
}
/** /**
* Gets the {@link PacketCodec} for Minecraft: Java Edition. * Gets the {@link PacketCodec} for Minecraft: Java Edition.
* *

View file

@ -209,7 +209,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
ResourcePackManifest.Header header = pack.manifest().header(); ResourcePackManifest.Header header = pack.manifest().header();
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry( resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(
header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(), header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(),
"", header.uuid().toString(), false, false)); "", header.uuid().toString(), false, false, false));
} }
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks()); resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
session.sendUpstreamPacket(resourcePacksInfo); session.sendUpstreamPacket(resourcePacksInfo);

View file

@ -38,6 +38,7 @@ import it.unimi.dsi.fastutil.objects.*;
import org.cloudburstmc.nbt.*; import org.cloudburstmc.nbt.*;
import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671; import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671;
import org.cloudburstmc.protocol.bedrock.codec.v685.Bedrock_v685; import org.cloudburstmc.protocol.bedrock.codec.v685.Bedrock_v685;
import org.cloudburstmc.protocol.bedrock.codec.v712.Bedrock_v712;
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData; import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
@ -108,7 +109,8 @@ public final class BlockRegistryPopulator {
private static void registerBedrockBlocks() { private static void registerBedrockBlocks() {
var blockMappers = ImmutableMap.<ObjectIntPair<String>, Remapper>builder() var blockMappers = ImmutableMap.<ObjectIntPair<String>, Remapper>builder()
.put(ObjectIntPair.of("1_20_80", Bedrock_v671.CODEC.getProtocolVersion()), Conversion685_671::remapBlock) .put(ObjectIntPair.of("1_20_80", Bedrock_v671.CODEC.getProtocolVersion()), Conversion685_671::remapBlock)
.put(ObjectIntPair.of("1_21_0", Bedrock_v685.CODEC.getProtocolVersion()), tag -> tag) .put(ObjectIntPair.of("1_21_0", Bedrock_v685.CODEC.getProtocolVersion()), Conversion712_685::remapBlock)
.put(ObjectIntPair.of("1_21_20", Bedrock_v712.CODEC.getProtocolVersion()), tag -> tag)
.build(); .build();
// We can keep this strong as nothing should be garbage collected // We can keep this strong as nothing should be garbage collected

View file

@ -45,6 +45,8 @@ public class Conversion685_671 {
private static final List<Item> NEW_MUSIC_DISCS = List.of(Items.MUSIC_DISC_CREATOR, Items.MUSIC_DISC_CREATOR_MUSIC_BOX, Items.MUSIC_DISC_PRECIPICE); private static final List<Item> NEW_MUSIC_DISCS = List.of(Items.MUSIC_DISC_CREATOR, Items.MUSIC_DISC_CREATOR_MUSIC_BOX, Items.MUSIC_DISC_PRECIPICE);
static GeyserMappingItem remapItem(Item item, GeyserMappingItem mapping) { static GeyserMappingItem remapItem(Item item, GeyserMappingItem mapping) {
mapping = Conversion712_685.remapItem(item, mapping);
String identifer = mapping.getBedrockIdentifier(); String identifer = mapping.getBedrockIdentifier();
if (NEW_MUSIC_DISCS.contains(item)) { if (NEW_MUSIC_DISCS.contains(item)) {
@ -111,6 +113,8 @@ public class Conversion685_671 {
} }
static NbtMap remapBlock(NbtMap tag) { static NbtMap remapBlock(NbtMap tag) {
tag = Conversion712_685.remapBlock(tag);
final String name = tag.getString("name"); final String name = tag.getString("name");
if (!MODIFIED_BLOCKS.contains(name)) { if (!MODIFIED_BLOCKS.contains(name)) {
@ -130,7 +134,7 @@ public class Conversion685_671 {
String coralColor; String coralColor;
boolean deadBit = name.startsWith("minecraft:dead_"); boolean deadBit = name.startsWith("minecraft:dead_");
switch(name) { switch (name) {
case "minecraft:tube_coral_block", "minecraft:dead_tube_coral_block" -> coralColor = "blue"; case "minecraft:tube_coral_block", "minecraft:dead_tube_coral_block" -> coralColor = "blue";
case "minecraft:brain_coral_block", "minecraft:dead_brain_coral_block" -> coralColor = "pink"; case "minecraft:brain_coral_block", "minecraft:dead_brain_coral_block" -> coralColor = "pink";
case "minecraft:bubble_coral_block", "minecraft:dead_bubble_coral_block" -> coralColor = "purple"; case "minecraft:bubble_coral_block", "minecraft:dead_bubble_coral_block" -> coralColor = "purple";
@ -152,7 +156,7 @@ public class Conversion685_671 {
replacement = "minecraft:double_plant"; replacement = "minecraft:double_plant";
String doublePlantType; String doublePlantType;
switch(name) { switch (name) {
case "minecraft:sunflower" -> doublePlantType = "sunflower"; case "minecraft:sunflower" -> doublePlantType = "sunflower";
case "minecraft:lilac" -> doublePlantType = "syringa"; case "minecraft:lilac" -> doublePlantType = "syringa";
case "minecraft:tall_grass" -> doublePlantType = "grass"; case "minecraft:tall_grass" -> doublePlantType = "grass";
@ -174,7 +178,7 @@ public class Conversion685_671 {
replacement = "minecraft:stone_block_slab"; replacement = "minecraft:stone_block_slab";
String stoneSlabType; String stoneSlabType;
switch(name) { switch (name) {
case "minecraft:smooth_stone_slab" -> stoneSlabType = "smooth_stone"; case "minecraft:smooth_stone_slab" -> stoneSlabType = "smooth_stone";
case "minecraft:sandstone_slab" -> stoneSlabType = "sandstone"; case "minecraft:sandstone_slab" -> stoneSlabType = "sandstone";
case "minecraft:petrified_oak_slab" -> stoneSlabType = "wood"; case "minecraft:petrified_oak_slab" -> stoneSlabType = "wood";
@ -198,7 +202,7 @@ public class Conversion685_671 {
replacement = "minecraft:tallgrass"; replacement = "minecraft:tallgrass";
String tallGrassType; String tallGrassType;
switch(name) { switch (name) {
case "minecraft:short_grass" -> tallGrassType = "tall"; case "minecraft:short_grass" -> tallGrassType = "tall";
case "minecraft:fern" -> tallGrassType = "fern"; case "minecraft:fern" -> tallGrassType = "fern";
default -> throw new IllegalStateException("Unexpected value: " + name); default -> throw new IllegalStateException("Unexpected value: " + name);

View file

@ -0,0 +1,436 @@
package org.geysermc.geyser.registry.populator;
import org.cloudburstmc.nbt.NbtMap;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.type.GeyserMappingItem;
import java.util.List;
import java.util.stream.Stream;
public class Conversion712_685 {
private static final List<String> NEW_STONE_BLOCK_SLABS_2 = List.of("minecraft:prismarine_slab", "minecraft:dark_prismarine_slab", "minecraft:smooth_sandstone_slab", "minecraft:purpur_slab", "minecraft:red_nether_brick_slab", "minecraft:prismarine_brick_slab", "minecraft:mossy_cobblestone_slab", "minecraft:red_sandstone_slab");
private static final List<String> NEW_STONE_BLOCK_SLABS_3 = List.of("minecraft:smooth_red_sandstone_slab", "minecraft:polished_granite_slab", "minecraft:granite_slab", "minecraft:polished_diorite_slab", "minecraft:andesite_slab", "minecraft:polished_andesite_slab", "minecraft:diorite_slab", "minecraft:end_stone_brick_slab");
private static final List<String> NEW_STONE_BLOCK_SLABS_4 = List.of("minecraft:smooth_quartz_slab", "minecraft:cut_sandstone_slab", "minecraft:cut_red_sandstone_slab", "minecraft:normal_stone_slab", "minecraft:mossy_stone_brick_slab");
private static final List<String> NEW_DOUBLE_STONE_BLOCK_SLABS = List.of("minecraft:quartz_double_slab", "minecraft:petrified_oak_double_slab", "minecraft:stone_brick_double_slab", "minecraft:brick_double_slab", "minecraft:sandstone_double_slab", "minecraft:nether_brick_double_slab", "minecraft:cobblestone_double_slab", "minecraft:smooth_stone_double_slab");
private static final List<String> NEW_DOUBLE_STONE_BLOCK_SLABS_2 = List.of("minecraft:prismarine_double_slab", "minecraft:dark_prismarine_double_slab", "minecraft:smooth_sandstone_double_slab", "minecraft:purpur_double_slab", "minecraft:red_nether_brick_double_slab", "minecraft:prismarine_brick_double_slab", "minecraft:mossy_cobblestone_double_slab", "minecraft:red_sandstone_double_slab");
private static final List<String> NEW_DOUBLE_STONE_BLOCK_SLABS_3 = List.of("minecraft:smooth_red_sandstone_double_slab", "minecraft:polished_granite_double_slab", "minecraft:granite_double_slab", "minecraft:polished_diorite_double_slab", "minecraft:andesite_double_slab", "minecraft:polished_andesite_double_slab", "minecraft:diorite_double_slab", "minecraft:end_stone_brick_double_slab");
private static final List<String> NEW_DOUBLE_STONE_BLOCK_SLABS_4 = List.of("minecraft:smooth_quartz_double_slab", "minecraft:cut_sandstone_double_slab", "minecraft:cut_red_sandstone_double_slab", "minecraft:normal_stone_double_slab", "minecraft:mossy_stone_brick_double_slab");
private static final List<String> NEW_PRISMARINE_BLOCKS = List.of("minecraft:prismarine_bricks", "minecraft:dark_prismarine", "minecraft:prismarine");
private static final List<String> NEW_CORAL_FAN_HANGS = List.of("minecraft:tube_coral_wall_fan", "minecraft:brain_coral_wall_fan", "minecraft:dead_tube_coral_wall_fan", "minecraft:dead_brain_coral_wall_fan");
private static final List<String> NEW_CORAL_FAN_HANGS_2 = List.of("minecraft:bubble_coral_wall_fan", "minecraft:fire_coral_wall_fan", "minecraft:dead_bubble_coral_wall_fan", "minecraft:dead_fire_coral_wall_fan");
private static final List<String> NEW_CORAL_FAN_HANGS_3 = List.of("minecraft:horn_coral_wall_fan", "minecraft:dead_horn_coral_wall_fan");
private static final List<String> NEW_MONSTER_EGGS = List.of("minecraft:infested_cobblestone", "minecraft:infested_stone_bricks", "minecraft:infested_mossy_stone_bricks", "minecraft:infested_cracked_stone_bricks", "minecraft:infested_chiseled_stone_bricks", "minecraft:infested_stone");
private static final List<String> NEW_STONEBRICK_BLOCKS = List.of("minecraft:mossy_stone_bricks", "minecraft:cracked_stone_bricks", "minecraft:chiseled_stone_bricks", "minecraft:smooth_stone_bricks", "minecraft:stone_bricks");
private static final List<String> NEW_LIGHT_BLOCKS = List.of("minecraft:light_block_0", "minecraft:light_block_1", "minecraft:light_block_2", "minecraft:light_block_3", "minecraft:light_block_4", "minecraft:light_block_5", "minecraft:light_block_6", "minecraft:light_block_7", "minecraft:light_block_8", "minecraft:light_block_9", "minecraft:light_block_10", "minecraft:light_block_11", "minecraft:light_block_12", "minecraft:light_block_13", "minecraft:light_block_14", "minecraft:light_block_15");
private static final List<String> NEW_SANDSTONE_BLOCKS = List.of("minecraft:cut_sandstone", "minecraft:chiseled_sandstone", "minecraft:smooth_sandstone", "minecraft:sandstone");
private static final List<String> NEW_QUARTZ_BLOCKS = List.of("minecraft:chiseled_quartz_block", "minecraft:quartz_pillar", "minecraft:smooth_quartz", "minecraft:quartz_block");
private static final List<String> NEW_RED_SANDSTONE_BLOCKS = List.of("minecraft:cut_red_sandstone", "minecraft:chiseled_red_sandstone", "minecraft:smooth_red_sandstone", "minecraft:red_sandstone");
private static final List<String> NEW_SAND_BLOCKS = List.of("minecraft:red_sand", "minecraft:sand");
private static final List<String> NEW_DIRT_BLOCKS = List.of("minecraft:coarse_dirt", "minecraft:dirt");
private static final List<String> NEW_ANVILS = List.of("minecraft:damaged_anvil", "minecraft:chipped_anvil", "minecraft:deprecated_anvil", "minecraft:anvil");
private static final List<String> NEW_YELLOW_FLOWERS = List.of("minecraft:dandelion");
private static final List<String> NEW_BLOCKS = Stream.of(NEW_STONE_BLOCK_SLABS_2, NEW_STONE_BLOCK_SLABS_3, NEW_STONE_BLOCK_SLABS_4, NEW_DOUBLE_STONE_BLOCK_SLABS, NEW_DOUBLE_STONE_BLOCK_SLABS_2, NEW_DOUBLE_STONE_BLOCK_SLABS_3, NEW_DOUBLE_STONE_BLOCK_SLABS_4, NEW_PRISMARINE_BLOCKS, NEW_CORAL_FAN_HANGS, NEW_CORAL_FAN_HANGS_2, NEW_CORAL_FAN_HANGS_3, NEW_MONSTER_EGGS, NEW_STONEBRICK_BLOCKS, NEW_LIGHT_BLOCKS, NEW_SANDSTONE_BLOCKS, NEW_QUARTZ_BLOCKS, NEW_RED_SANDSTONE_BLOCKS, NEW_SAND_BLOCKS, NEW_DIRT_BLOCKS, NEW_ANVILS, NEW_YELLOW_FLOWERS).flatMap(List::stream).toList();
static GeyserMappingItem remapItem(Item item, GeyserMappingItem mapping) {
String identifer = mapping.getBedrockIdentifier();
if (!NEW_BLOCKS.contains(identifer)) {
return mapping;
}
if (identifer.equals("minecraft:coarse_dirt")) {
return mapping.withBedrockIdentifier("minecraft:dirt").withBedrockData(1);
}
if (identifer.equals("minecraft:dandelion")) {
return mapping.withBedrockIdentifier("minecraft:yellow_flower").withBedrockData(0);
}
if (identifer.equals("minecraft:red_sand")) {
return mapping.withBedrockIdentifier("minecraft:sand").withBedrockData(1);
}
if (NEW_PRISMARINE_BLOCKS.contains(identifer)) {
switch (identifer) {
case "minecraft:prismarine" -> { return mapping.withBedrockIdentifier("minecraft:prismarine").withBedrockData(0); }
case "minecraft:dark_prismarine" -> { return mapping.withBedrockIdentifier("minecraft:prismarine").withBedrockData(1); }
case "minecraft:prismarine_bricks" -> { return mapping.withBedrockIdentifier("minecraft:prismarine").withBedrockData(2); }
}
}
if (NEW_SANDSTONE_BLOCKS.contains(identifer)) {
switch (identifer) {
case "minecraft:sandstone" -> { return mapping.withBedrockIdentifier("minecraft:sandstone").withBedrockData(0); }
case "minecraft:chiseled_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:sandstone").withBedrockData(1); }
case "minecraft:cut_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:sandstone").withBedrockData(2); }
case "minecraft:smooth_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:sandstone").withBedrockData(3); }
}
}
if (NEW_RED_SANDSTONE_BLOCKS.contains(identifer)) {
switch (identifer) {
case "minecraft:red_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:red_sandstone").withBedrockData(0); }
case "minecraft:chiseled_red_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:red_sandstone").withBedrockData(1); }
case "minecraft:cut_red_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:red_sandstone").withBedrockData(2); }
case "minecraft:smooth_red_sandstone" -> { return mapping.withBedrockIdentifier("minecraft:red_sandstone").withBedrockData(3); }
}
}
if (NEW_QUARTZ_BLOCKS.contains(identifer)) {
switch (identifer) {
case "minecraft:quartz_block" -> { return mapping.withBedrockIdentifier("minecraft:quartz_block").withBedrockData(0); }
case "minecraft:chiseled_quartz_block" -> { return mapping.withBedrockIdentifier("minecraft:quartz_block").withBedrockData(1); }
case "minecraft:quartz_pillar" -> { return mapping.withBedrockIdentifier("minecraft:quartz_block").withBedrockData(2); }
case "minecraft:smooth_quartz" -> { return mapping.withBedrockIdentifier("minecraft:quartz_block").withBedrockData(3); }
}
}
if (NEW_STONE_BLOCK_SLABS_2.contains(identifer)) {
switch (identifer) {
case "minecraft:red_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(0); }
case "minecraft:purpur_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(1); }
case "minecraft:prismarine_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(2); }
case "minecraft:dark_prismarine_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(3); }
case "minecraft:prismarine_brick_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(4); }
case "minecraft:mossy_cobblestone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(5); }
case "minecraft:smooth_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(6); }
case "minecraft:red_nether_brick_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab2").withBedrockData(7); }
}
}
if (NEW_STONE_BLOCK_SLABS_3.contains(identifer)) {
switch (identifer) {
case "minecraft:end_stone_brick_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(0); }
case "minecraft:smooth_red_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(1); }
case "minecraft:polished_andesite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(2); }
case "minecraft:andesite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(3); }
case "minecraft:diorite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(4); }
case "minecraft:polished_diorite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(5); }
case "minecraft:granite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(6); }
case "minecraft:polished_granite_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab3").withBedrockData(7); }
}
}
if (NEW_STONE_BLOCK_SLABS_4.contains(identifer)) {
switch (identifer) {
case "minecraft:mossy_stone_brick_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(0); }
case "minecraft:smooth_quartz_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(1); }
case "minecraft:normal_stone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(2); }
case "minecraft:cut_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(3); }
case "minecraft:cut_red_sandstone_slab" -> { return mapping.withBedrockIdentifier("minecraft:stone_block_slab4").withBedrockData(4); }
}
}
if (NEW_MONSTER_EGGS.contains(identifer)) {
switch (identifer) {
case "minecraft:infested_stone" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(0); }
case "minecraft:infested_cobblestone" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(1); }
case "minecraft:infested_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(2); }
case "minecraft:infested_mossy_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(3); }
case "minecraft:infested_cracked_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(4); }
case "minecraft:infested_chiseled_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:monster_egg").withBedrockData(5); }
}
}
if (NEW_STONEBRICK_BLOCKS.contains(identifer)) {
switch (identifer) {
case "minecraft:stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:stonebrick").withBedrockData(0); }
case "minecraft:mossy_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:stonebrick").withBedrockData(1); }
case "minecraft:cracked_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:stonebrick").withBedrockData(2); }
case "minecraft:chiseled_stone_bricks" -> { return mapping.withBedrockIdentifier("minecraft:stonebrick").withBedrockData(3); }
}
}
if (NEW_ANVILS.contains(identifer)) {
switch (identifer) {
case "minecraft:anvil" -> { return mapping.withBedrockIdentifier("minecraft:anvil").withBedrockData(0); }
case "minecraft:chipped_anvil" -> { return mapping.withBedrockIdentifier("minecraft:anvil").withBedrockData(4); }
case "minecraft:damaged_anvil" -> { return mapping.withBedrockIdentifier("minecraft:anvil").withBedrockData(8); }
}
}
return mapping;
}
static NbtMap remapBlock(NbtMap tag) {
final String name = tag.getString("name");
if (!NEW_BLOCKS.contains(name)) {
return tag;
}
String replacement;
if (NEW_DOUBLE_STONE_BLOCK_SLABS.contains(name)) {
replacement = "minecraft:double_stone_block_slab";
String stoneSlabType;
switch (name) {
case "minecraft:quartz_double_slab" -> stoneSlabType = "quartz";
case "minecraft:petrified_oak_double_slab" -> stoneSlabType = "wood";
case "minecraft:stone_brick_double_slab" -> stoneSlabType = "stone_brick";
case "minecraft:brick_double_slab" -> stoneSlabType = "brick";
case "minecraft:sandstone_double_slab" -> stoneSlabType = "sandstone";
case "minecraft:nether_brick_double_slab" -> stoneSlabType = "nether_brick";
case "minecraft:cobblestone_double_slab" -> stoneSlabType = "cobblestone";
case "minecraft:smooth_stone_double_slab" -> stoneSlabType = "smooth_stone";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("stone_slab_type", stoneSlabType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_STONE_BLOCK_SLABS_2.contains(name) || NEW_DOUBLE_STONE_BLOCK_SLABS_2.contains(name)) {
replacement = NEW_STONE_BLOCK_SLABS_2.contains(name) ? "minecraft:stone_block_slab2" : "minecraft:double_stone_block_slab2";
String stoneSlabType2;
switch (name) {
case "minecraft:prismarine_slab", "minecraft:prismarine_double_slab" -> stoneSlabType2 = "prismarine_rough";
case "minecraft:dark_prismarine_slab", "minecraft:dark_prismarine_double_slab" -> stoneSlabType2 = "prismarine_dark";
case "minecraft:smooth_sandstone_slab", "minecraft:smooth_sandstone_double_slab" -> stoneSlabType2 = "smooth_sandstone";
case "minecraft:purpur_slab", "minecraft:purpur_double_slab" -> stoneSlabType2 = "purpur";
case "minecraft:red_nether_brick_slab", "minecraft:red_nether_brick_double_slab" -> stoneSlabType2 = "red_nether_brick";
case "minecraft:prismarine_brick_slab", "minecraft:prismarine_brick_double_slab" -> stoneSlabType2 = "prismarine_brick";
case "minecraft:mossy_cobblestone_slab", "minecraft:mossy_cobblestone_double_slab" -> stoneSlabType2 = "mossy_cobblestone";
case "minecraft:red_sandstone_slab", "minecraft:red_sandstone_double_slab" -> stoneSlabType2 = "red_sandstone";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("stone_slab_type_2", stoneSlabType2)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_STONE_BLOCK_SLABS_3.contains(name) || NEW_DOUBLE_STONE_BLOCK_SLABS_3.contains(name)) {
replacement = NEW_STONE_BLOCK_SLABS_3.contains(name) ? "minecraft:stone_block_slab3" : "minecraft:double_stone_block_slab3";
String stoneSlabType3;
switch (name) {
case "minecraft:smooth_red_sandstone_slab", "minecraft:smooth_red_sandstone_double_slab" -> stoneSlabType3 = "smooth_red_sandstone";
case "minecraft:polished_granite_slab", "minecraft:polished_granite_double_slab" -> stoneSlabType3 = "polished_granite";
case "minecraft:granite_slab", "minecraft:granite_double_slab" -> stoneSlabType3 = "granite";
case "minecraft:polished_diorite_slab", "minecraft:polished_diorite_double_slab" -> stoneSlabType3 = "polished_diorite";
case "minecraft:andesite_slab", "minecraft:andesite_double_slab" -> stoneSlabType3 = "andesite";
case "minecraft:polished_andesite_slab", "minecraft:polished_andesite_double_slab" -> stoneSlabType3 = "polished_andesite";
case "minecraft:diorite_slab", "minecraft:diorite_double_slab" -> stoneSlabType3 = "diorite";
case "minecraft:end_stone_brick_slab", "minecraft:end_stone_brick_double_slab" -> stoneSlabType3 = "end_stone_brick";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("stone_slab_type_3", stoneSlabType3)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_STONE_BLOCK_SLABS_4.contains(name) || NEW_DOUBLE_STONE_BLOCK_SLABS_4.contains(name)) {
replacement = NEW_STONE_BLOCK_SLABS_4.contains(name) ? "minecraft:stone_block_slab4" : "minecraft:double_stone_block_slab4";
String stoneSlabType4;
switch (name) {
case "minecraft:smooth_quartz_slab", "minecraft:smooth_quartz_double_slab" -> stoneSlabType4 = "smooth_quartz";
case "minecraft:cut_sandstone_slab", "minecraft:cut_sandstone_double_slab" -> stoneSlabType4 = "cut_sandstone";
case "minecraft:cut_red_sandstone_slab", "minecraft:cut_red_sandstone_double_slab" -> stoneSlabType4 = "cut_red_sandstone";
case "minecraft:normal_stone_slab", "minecraft:normal_stone_double_slab" -> stoneSlabType4 = "stone";
case "minecraft:mossy_stone_brick_slab", "minecraft:mossy_stone_brick_double_slab" -> stoneSlabType4 = "mossy_stone_brick";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("stone_slab_type_4", stoneSlabType4)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_PRISMARINE_BLOCKS.contains(name)) {
replacement = "minecraft:prismarine";
String prismarineBlockType;
switch (name) {
case "minecraft:prismarine_bricks" -> prismarineBlockType = "bricks";
case "minecraft:dark_prismarine" -> prismarineBlockType = "dark";
case "minecraft:prismarine" -> prismarineBlockType = "default";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("prismarine_block_type", prismarineBlockType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_CORAL_FAN_HANGS.contains(name) || NEW_CORAL_FAN_HANGS_2.contains(name) || NEW_CORAL_FAN_HANGS_3.contains(name)) {
replacement = NEW_CORAL_FAN_HANGS.contains(name) ? "minecraft:coral_fan_hang" : NEW_CORAL_FAN_HANGS_2.contains(name) ? "minecraft:coral_fan_hang2" : "minecraft:coral_fan_hang3";
boolean deadBit = name.startsWith("minecraft:dead_");
boolean coralHangTypeBit = name.contains("brain") || name.contains("fire");
NbtMap states = tag.getCompound("states")
.toBuilder()
.putBoolean("coral_hang_type_bit", coralHangTypeBit)
.putBoolean("dead_bit", deadBit)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_MONSTER_EGGS.contains(name)) {
replacement = "minecraft:monster_egg";
String monsterEggStoneType;
switch (name) {
case "minecraft:infested_cobblestone" -> monsterEggStoneType = "cobblestone";
case "minecraft:infested_stone_bricks" -> monsterEggStoneType = "stone_brick";
case "minecraft:infested_mossy_stone_bricks" -> monsterEggStoneType = "mossy_stone_brick";
case "minecraft:infested_cracked_stone_bricks" -> monsterEggStoneType = "cracked_stone_brick";
case "minecraft:infested_chiseled_stone_bricks" -> monsterEggStoneType = "chiseled_stone_brick";
case "minecraft:infested_stone" -> monsterEggStoneType = "stone";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("monster_egg_stone_type", monsterEggStoneType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_STONEBRICK_BLOCKS.contains(name)) {
replacement = "minecraft:stonebrick";
String stoneBrickType;
switch (name) {
case "minecraft:mossy_stone_bricks" -> stoneBrickType = "mossy";
case "minecraft:cracked_stone_bricks" -> stoneBrickType = "cracked";
case "minecraft:chiseled_stone_bricks" -> stoneBrickType = "chiseled";
case "minecraft:smooth_stone_bricks" -> stoneBrickType = "smooth";
case "minecraft:stone_bricks" -> stoneBrickType = "default";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("stone_brick_type", stoneBrickType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_LIGHT_BLOCKS.contains(name)) {
replacement = "minecraft:light_block";
int blockLightLevel = Integer.parseInt(name.split("_")[2]);
NbtMap states = tag.getCompound("states")
.toBuilder()
.putInt("block_light_level", blockLightLevel)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_SANDSTONE_BLOCKS.contains(name) || NEW_RED_SANDSTONE_BLOCKS.contains(name)) {
replacement = NEW_SANDSTONE_BLOCKS.contains(name) ? "minecraft:sandstone" : "minecraft:red_sandstone";
String sandStoneType;
switch (name) {
case "minecraft:cut_sandstone", "minecraft:cut_red_sandstone" -> sandStoneType = "cut";
case "minecraft:chiseled_sandstone", "minecraft:chiseled_red_sandstone" -> sandStoneType = "heiroglyphs";
case "minecraft:smooth_sandstone", "minecraft:smooth_red_sandstone" -> sandStoneType = "smooth";
case "minecraft:sandstone", "minecraft:red_sandstone" -> sandStoneType = "default";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("sand_stone_type", sandStoneType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_QUARTZ_BLOCKS.contains(name)) {
replacement = "minecraft:quartz_block";
String chiselType;
switch (name) {
case "minecraft:chiseled_quartz_block" -> chiselType = "chiseled";
case "minecraft:quartz_pillar" -> chiselType = "lines";
case "minecraft:smooth_quartz" -> chiselType = "smooth";
case "minecraft:quartz_block" -> chiselType = "default";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("chisel_type", chiselType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_SAND_BLOCKS.contains(name)) {
replacement = "minecraft:sand";
String sandType = name.equals("minecraft:red_sand") ? "red" : "normal";
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("sand_type", sandType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_DIRT_BLOCKS.contains(name)) {
replacement = "minecraft:dirt";
String dirtType = name.equals("minecraft:coarse_dirt") ? "coarse" : "normal";
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("dirt_type", dirtType)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_ANVILS.contains(name)) {
replacement = "minecraft:anvil";
String damage;
switch (name) {
case "minecraft:damaged_anvil" -> damage = "broken";
case "minecraft:chipped_anvil" -> damage = "slightly_damaged";
case "minecraft:deprecated_anvil" -> damage = "very_damaged";
case "minecraft:anvil" -> damage = "undamaged";
default -> throw new IllegalStateException("Unexpected value: " + name);
}
NbtMap states = tag.getCompound("states")
.toBuilder()
.putString("damage", damage)
.build();
return tag.toBuilder().putString("name", replacement).putCompound("states", states).build();
}
if (NEW_YELLOW_FLOWERS.contains(name)) {
replacement = "minecraft:yellow_flower";
return tag.toBuilder().putString("name", replacement).build();
}
return tag;
}
}

View file

@ -41,6 +41,7 @@ import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.nbt.NbtUtils; import org.cloudburstmc.nbt.NbtUtils;
import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671; import org.cloudburstmc.protocol.bedrock.codec.v671.Bedrock_v671;
import org.cloudburstmc.protocol.bedrock.codec.v685.Bedrock_v685; import org.cloudburstmc.protocol.bedrock.codec.v685.Bedrock_v685;
import org.cloudburstmc.protocol.bedrock.codec.v712.Bedrock_v712;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
@ -90,7 +91,8 @@ public class ItemRegistryPopulator {
public static void populate() { public static void populate() {
List<PaletteVersion> paletteVersions = new ArrayList<>(3); List<PaletteVersion> paletteVersions = new ArrayList<>(3);
paletteVersions.add(new PaletteVersion("1_20_80", Bedrock_v671.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion685_671::remapItem)); paletteVersions.add(new PaletteVersion("1_20_80", Bedrock_v671.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion685_671::remapItem));
paletteVersions.add(new PaletteVersion("1_21_0", Bedrock_v685.CODEC.getProtocolVersion())); paletteVersions.add(new PaletteVersion("1_21_0", Bedrock_v685.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion712_685::remapItem));
paletteVersions.add(new PaletteVersion("1_21_20", Bedrock_v712.CODEC.getProtocolVersion()));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();

View file

@ -79,6 +79,7 @@ import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket; import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket;
import org.cloudburstmc.protocol.bedrock.packet.CameraPresetsPacket; import org.cloudburstmc.protocol.bedrock.packet.CameraPresetsPacket;
import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket; import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
import org.cloudburstmc.protocol.bedrock.packet.ClientboundCloseFormPacket;
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket; import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket; import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket;
import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket; import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket;
@ -127,6 +128,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;
@ -140,6 +142,7 @@ import org.geysermc.geyser.item.type.BlockItem;
import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.level.physics.CollisionManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.network.netty.LocalSession;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.BlockMappings;
@ -600,6 +603,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
*/ */
@ -1259,6 +1275,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();
@ -1294,6 +1314,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) {
@ -2114,6 +2137,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
return (int) Math.floor(rakSessionCodec.getPing()); return (int) Math.floor(rakSessionCodec.getPing());
} }
@Override
public void closeForm() {
if (!GameProtocol.isPre1_21_2(this)) {
sendUpstreamPacket(new ClientboundCloseFormPacket());
}
}
public void addCommandEnum(String name, String enums) { public void addCommandEnum(String name, String enums) {
softEnumPacket(name, SoftEnumUpdateType.ADD, enums); softEnumPacket(name, SoftEnumUpdateType.ADD, enums);
} }

View file

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

View file

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

View file

@ -112,7 +112,13 @@ public class FakeHeadProvider {
return; return;
} }
Map<TextureType, Texture> textures = profile.getTextures(false); Map<TextureType, Texture> textures;
try {
textures = profile.getTextures(false);
} catch (IllegalStateException e) {
GeyserImpl.getInstance().getLogger().debug("Could not decode player head from profile %s, got: %s".formatted(profile, e.getMessage()));
textures = null;
}
if (textures == null || textures.isEmpty()) { if (textures == null || textures.isEmpty()) {
loadHead(session, entity, profile.getName()); loadHead(session, entity, profile.getName());

View file

@ -150,7 +150,7 @@ public class GeyserLocale {
} else { } else {
if (!validLocalLanguage) { if (!validLocalLanguage) {
// Don't warn on missing locales if a local file has been found // Don't warn on missing locales if a local file has been found
bootstrap.getGeyserLogger().warning("Missing locale: " + locale); bootstrap.getGeyserLogger().debug("Missing locale: " + locale);
} }
} }

View file

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

View file

@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.ints.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.*; import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.*;
@ -894,11 +895,11 @@ public abstract class InventoryTranslator {
List<ItemStackResponseContainer> containerEntries = new ArrayList<>(); List<ItemStackResponseContainer> containerEntries = new ArrayList<>();
for (Map.Entry<ContainerSlotType, List<ItemStackResponseSlot>> entry : containerMap.entrySet()) { for (Map.Entry<ContainerSlotType, List<ItemStackResponseSlot>> entry : containerMap.entrySet()) {
containerEntries.add(new ItemStackResponseContainer(entry.getKey(), entry.getValue())); containerEntries.add(new ItemStackResponseContainer(entry.getKey(), entry.getValue(), new FullContainerName(entry.getKey(), 0)));
} }
ItemStackResponseSlot cursorEntry = makeItemEntry(0, session.getPlayerInventory().getCursor()); ItemStackResponseSlot cursorEntry = makeItemEntry(0, session.getPlayerInventory().getCursor());
containerEntries.add(new ItemStackResponseContainer(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry))); containerEntries.add(new ItemStackResponseContainer(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry), new FullContainerName(ContainerSlotType.CURSOR, 0)));
return containerEntries; return containerEntries;
} }

View file

@ -25,9 +25,6 @@
package org.geysermc.geyser.translator.item; package org.geysermc.geyser.translator.item;
import org.geysermc.mcprotocollib.auth.GameProfile;
import org.geysermc.mcprotocollib.auth.GameProfile.Texture;
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
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;
@ -43,8 +40,8 @@ import org.geysermc.geyser.api.block.custom.CustomBlockData;
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.item.components.Rarity; import org.geysermc.geyser.item.components.Rarity;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.item.type.BedrockRequiresTagItem; import org.geysermc.geyser.item.type.BedrockRequiresTagItem;
import org.geysermc.geyser.item.type.Item;
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;
@ -55,13 +52,24 @@ import org.geysermc.geyser.text.ChatColor;
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.InventoryUtils; import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.mcprotocollib.auth.GameProfile;
import org.geysermc.mcprotocollib.auth.GameProfile.Texture;
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
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.attribute.ModifierOperation; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.ModifierOperation;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.*; import org.geysermc.mcprotocollib.protocol.data.game.item.component.AdventureModePredicate;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemAttributeModifiers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.*; import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class ItemTranslator { public final class ItemTranslator {
@ -486,7 +494,13 @@ 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;
try {
textures = profile.getTextures(false);
} catch (IllegalStateException e) {
GeyserImpl.getInstance().getLogger().debug("Could not decode player head from profile %s, got: %s".formatted(profile, e.getMessage()));
return null;
}
if (textures == null || textures.isEmpty()) { if (textures == null || textures.isEmpty()) {
return null; return null;

View file

@ -37,6 +37,7 @@ 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;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
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;
@ -347,18 +348,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
@ -366,7 +380,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
@ -374,14 +388,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);
} }
/** /**
@ -391,20 +405,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) {
@ -470,7 +486,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
@ -480,12 +496,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);
@ -508,7 +524,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);
} }
} }
} }
@ -584,7 +600,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);
@ -628,17 +644,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);

View file

@ -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();

View file

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

View file

@ -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());

View file

@ -76,6 +76,9 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
@Override @Override
public int hashCode(BedrockCommandInfo o) { public int hashCode(BedrockCommandInfo o) {
int paramHash = Arrays.deepHashCode(o.paramData()); int paramHash = Arrays.deepHashCode(o.paramData());
if ("help".equals(o.name())) {
paramHash = 31 * paramHash + 1;
}
return 31 * paramHash + o.description().hashCode(); return 31 * paramHash + o.description().hashCode();
} }
@ -83,6 +86,11 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
public boolean equals(BedrockCommandInfo a, BedrockCommandInfo b) { public boolean equals(BedrockCommandInfo a, BedrockCommandInfo b) {
if (a == b) return true; if (a == b) return true;
if (a == null || b == null) return false; if (a == null || b == null) return false;
if ("help".equals(a.name()) && !"help".equals(b.name())) {
// Merging this causes Bedrock to fallback to its own help command
// https://github.com/GeyserMC/Geyser/issues/2573
return false;
}
if (!a.description().equals(b.description())) return false; if (!a.description().equals(b.description())) return false;
if (a.paramData().length != b.paramData().length) return false; if (a.paramData().length != b.paramData().length) return false;
for (int i = 0; i < a.paramData().length; i++) { for (int i = 0; i < a.paramData().length; i++) {

View file

@ -101,6 +101,7 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
DimensionUtils.fastSwitchDimension(session, fakeDim); DimensionUtils.fastSwitchDimension(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);

View file

@ -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);
} }
} }

View file

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

View file

@ -29,6 +29,7 @@ import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.FakeHeadProvider; import org.geysermc.geyser.skin.FakeHeadProvider;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -72,11 +73,19 @@ public class JavaSetEquipmentTranslator extends PacketTranslator<ClientboundSetE
livingEntity.setHelmet(stack); livingEntity.setHelmet(stack);
armorUpdated = true; armorUpdated = true;
} }
case CHESTPLATE, BODY -> { case CHESTPLATE -> {
// BODY is sent for llamas with a carpet equipped, as of 1.20.5
livingEntity.setChestplate(stack); livingEntity.setChestplate(stack);
armorUpdated = true; armorUpdated = true;
} }
case BODY -> {
// BODY is sent for llamas with a carpet equipped, as of 1.20.5
if (GameProtocol.isPre1_21_2(session)) {
livingEntity.setChestplate(stack);
} else {
livingEntity.setBody(stack);
}
armorUpdated = true;
}
case LEGGINGS -> { case LEGGINGS -> {
livingEntity.setLeggings(stack); livingEntity.setLeggings(stack);
armorUpdated = true; armorUpdated = true;

View file

@ -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();
}
} }
} }
} }

View file

@ -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());
} }
} }

View file

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

View file

@ -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();

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -54,9 +54,6 @@ remote:
# For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured. # For plugin versions, it's recommended to keep the `address` field to "auto" so Floodgate support is automatically configured.
# If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used. # If Floodgate is installed and `address:` is set to "auto", then "auth-type: floodgate" will automatically be used.
auth-type: online auth-type: online
# Allow for password-based authentication methods through Geyser. Only useful in online mode.
# If this is false, users must authenticate to Microsoft using a code provided by Geyser on their desktop.
allow-password-authentication: true
# Whether to enable PROXY protocol or not while connecting to the server. # Whether to enable PROXY protocol or not while connecting to the server.
# This is useful only when: # This is useful only when:
# 1) Your server supports PROXY protocol (it probably doesn't) # 1) Your server supports PROXY protocol (it probably doesn't)

@ -1 +1 @@
Subproject commit 60b20023a92f084aba895ab0336e70fa7fb311fb Subproject commit 7499daf712ad6de70a07fba471b51b4ad92315c5

@ -1 +1 @@
Subproject commit aaf53d6953c927e5ac1b87fd6627ffbfd4aa7cf5 Subproject commit 698fd2b108a9e53f1e47b8cfdc122651b70d6059

View file

@ -7,5 +7,5 @@ org.gradle.vfs.watch=false
group=org.geysermc group=org.geysermc
id=geyser id=geyser
version=2.4.1-SNAPSHOT version=2.4.2-SNAPSHOT
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers. description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.

View file

@ -1,7 +1,7 @@
[versions] [versions]
base-api = "1.0.1" base-api = "1.0.1"
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"
@ -10,9 +10,9 @@ netty-io-uring = "0.0.25.Final-SNAPSHOT"
guava = "29.0-jre" guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.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.Beta3-20240814.133201-7"
raknet = "1.0.0.CR3-20240416.144209-1" raknet = "1.0.0.CR3-20240416.144209-1"
minecraftauth = "4.1.0" minecraftauth = "4.1.1-20240806.235051-7"
mcprotocollib = "1.21-20240725.013034-16" mcprotocollib = "1.21-20240725.013034-16"
adventure = "4.14.0" adventure = "4.14.0"
adventure-platform = "4.3.0" adventure-platform = "4.3.0"
@ -30,13 +30,13 @@ cloud-minecraft-modded = "2.0.0-beta.7"
commodore = "2.2" commodore = "2.2"
bungeecord = "a7c6ede" bungeecord = "a7c6ede"
velocity = "3.3.0-SNAPSHOT" velocity = "3.3.0-SNAPSHOT"
viaproxy = "3.2.1" viaproxy = "3.3.2-SNAPSHOT"
fabric-loader = "0.15.11" fabric-loader = "0.15.11"
fabric-api = "0.100.1+1.21" fabric-api = "0.100.1+1.21"
neoforge-minecraft = "21.0.0-beta" neoforge-minecraft = "21.1.1"
mixin = "0.8.5" mixin = "0.8.5"
mixinextras = "0.3.5" mixinextras = "0.3.5"
minecraft = "1.21" minecraft = "1.21.1"
# plugin versions # plugin versions
indra = "3.1.3" indra = "3.1.3"

View file

@ -2,52 +2,6 @@
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement {
repositories {
// mavenLocal()
// Floodgate, Cumulus etc.
maven("https://repo.opencollab.dev/main")
// Paper, Velocity
maven("https://repo.papermc.io/repository/maven-public")
// Spigot
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") {
mavenContent { snapshotsOnly() }
}
// BungeeCord
maven("https://oss.sonatype.org/content/repositories/snapshots") {
mavenContent { snapshotsOnly() }
}
// NeoForge
maven("https://maven.neoforged.net/releases") {
mavenContent { releasesOnly() }
}
// Minecraft
maven("https://libraries.minecraft.net") {
name = "minecraft"
mavenContent { releasesOnly() }
}
mavenCentral()
// ViaVersion
maven("https://repo.viaversion.com") {
name = "viaversion"
}
maven("https://jitpack.io") {
content { includeGroupByRegex("com\\.github\\..*") }
}
// For Adventure snapshots
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}
}
pluginManagement { pluginManagement {
repositories { repositories {
gradlePluginPortal() gradlePluginPortal()