Fix break time while submerged in water (#3110)

* Fix break time while submerged in water

* Review stuff

* LAYERS -> LEVELS
This commit is contained in:
David Choo 2022-07-02 21:17:14 -04:00 committed by GitHub
parent f2f894b1d1
commit dc810f1d39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 45 deletions

View file

@ -99,19 +99,9 @@ public class FishingHookEntity extends ThrowableEntity {
}
}
int waterLevel = BlockStateValues.getWaterLevel(blockID);
if (BlockRegistries.WATERLOGGED.get().contains(blockID)) {
waterLevel = 0;
}
if (waterLevel >= 0) {
double waterMaxY = iter.getY() + 1 - (waterLevel + 1) / 9.0;
// Falling water is a full block
if (waterLevel >= 8) {
waterMaxY = iter.getY() + 1;
}
if (position.getY() <= waterMaxY) {
touchingWater = true;
}
double waterHeight = BlockStateValues.getWaterHeight(blockID);
if (waterHeight != -1 && position.getY() <= (iter.getY() + waterHeight)) {
touchingWater = true;
}
}

View file

@ -77,6 +77,8 @@ public final class BlockStateValues {
public static int JAVA_SPAWNER_ID;
public static int JAVA_WATER_ID;
public static final int NUM_WATER_LEVELS = 9;
/**
* Determines if the block state contains Bedrock block information
*
@ -449,7 +451,6 @@ public final class BlockStateValues {
/**
* Get the level of water from the block state.
* This is used in FishingHookEntity to create splash sounds when the hook hits the water.
*
* @param state BlockState of the block
* @return The water level or -1 if the block isn't water
@ -458,6 +459,30 @@ public final class BlockStateValues {
return WATER_LEVEL.getOrDefault(state, -1);
}
/**
* Get the height of water from the block state
* This is used in FishingHookEntity to create splash sounds when the hook hits the water. In addition,
* CollisionManager uses this to determine if the player's eyes are in water.
*
* @param state BlockState of the block
* @return The water height or -1 if the block does not contain water
*/
public static double getWaterHeight(int state) {
int waterLevel = BlockStateValues.getWaterLevel(state);
if (BlockRegistries.WATERLOGGED.get().contains(state)) {
waterLevel = 0;
}
if (waterLevel >= 0) {
double waterHeight = 1 - (waterLevel + 1) / ((double) NUM_WATER_LEVELS);
// Falling water is a full block
if (waterLevel >= 8) {
waterHeight = 1;
}
return waterHeight;
}
return -1;
}
/**
* Get the slipperiness of a block.
* This is used in ItemEntity to calculate the friction on an item as it slides across the ground

View file

@ -25,6 +25,7 @@
package org.geysermc.geyser.level.physics;
import com.nukkitx.math.GenericMath;
import com.nukkitx.math.vector.Vector3d;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
@ -405,6 +406,18 @@ public class CollisionManager {
return session.getGeyser().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt()) == BlockStateValues.JAVA_WATER_ID;
}
public boolean isWaterInEyes() {
double eyeX = playerBoundingBox.getMiddleX();
double eyeY = playerBoundingBox.getMiddleY() - playerBoundingBox.getSizeY() / 2d + session.getEyeHeight();
double eyeZ = playerBoundingBox.getMiddleZ();
eyeY -= 1 / ((double) BlockStateValues.NUM_WATER_LEVELS); // Subtract the height of one water layer
int blockID = session.getGeyser().getWorldManager().getBlockAt(session, GenericMath.floor(eyeX), GenericMath.floor(eyeY), GenericMath.floor(eyeZ));
double waterHeight = BlockStateValues.getWaterHeight(blockID);
return waterHeight != -1 && eyeY < (Math.floor(eyeY) + waterHeight);
}
/**
* Updates scaffolding entity flags
* Scaffolding needs to be checked per-move since it's a flag in Bedrock but Java does it client-side

View file

@ -100,6 +100,7 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.command.CommandSender;
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
@ -1767,6 +1768,17 @@ public class GeyserSession implements GeyserConnection, CommandSender {
sendUpstreamPacket(packet);
}
public float getEyeHeight() {
return switch (pose) {
case SNEAKING -> 1.27f;
case SWIMMING,
FALL_FLYING, // Elytra
SPIN_ATTACK -> 0.4f; // Trident spin attack
case SLEEPING -> 0.2f;
default -> EntityDefinitions.PLAYER.offset();
};
}
public MinecraftCodecHelper getCodecHelper() {
return (MinecraftCodecHelper) this.downstream.getCodecHelper();
}

View file

@ -201,7 +201,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
// CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch
Vector3f playerPosition = session.getPlayerEntity().getPosition();
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - getEyeHeight(session));
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
boolean creative = session.getGameMode() == GameMode.CREATIVE;
@ -608,7 +608,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
// Use the bounding box's position since we need the player's position seen by the Java server
Vector3d playerPosition = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter();
float xDiff = (float) (target.getX() - playerPosition.getX());
float yDiff = (float) (target.getY() - (playerPosition.getY() + getEyeHeight(session)));
float yDiff = (float) (target.getY() - (playerPosition.getY() + session.getEyeHeight()));
float zDiff = (float) (target.getZ() - playerPosition.getZ());
// First triangle on the XZ plane
@ -637,15 +637,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
}, 150, TimeUnit.MILLISECONDS));
}
}
private float getEyeHeight(GeyserSession session) {
return switch (session.getPose()) {
case SNEAKING -> 1.27f;
case SWIMMING,
FALL_FLYING, // Elytra
SPIN_ATTACK -> 0.4f; // Trident spin attack
case SLEEPING -> 0.2f;
default -> EntityDefinitions.PLAYER.offset();
};
}
}

View file

@ -36,6 +36,8 @@ import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.collision.BlockCollision;
import javax.annotation.Nullable;
public final class BlockUtils {
private static boolean correctTool(GeyserSession session, BlockMapping blockMapping, String itemToolType) {
@ -101,7 +103,7 @@ public final class BlockUtils {
// https://minecraft.gamepedia.com/Breaking
private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, boolean canTierMineBlock,
String toolType, boolean isShearsEffective, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel,
boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround, boolean insideWaterAndNotOnGround) {
boolean insideOfWaterWithoutAquaAffinity, boolean onGround) {
double baseTime = (((correctTool && canTierMineBlock) || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness;
double speed = 1.0 / baseTime;
@ -129,12 +131,11 @@ public final class BlockUtils {
}
if (insideOfWaterWithoutAquaAffinity) speed *= 0.2;
if (outOfWaterButNotOnGround) speed *= 0.2;
if (insideWaterAndNotOnGround) speed *= 0.2;
if (!onGround) speed *= 0.2;
return 1.0 / speed;
}
public static double getBreakTime(GeyserSession session, BlockMapping blockMapping, ItemMapping item, CompoundTag nbtData, boolean isSessionPlayer) {
public static double getBreakTime(GeyserSession session, BlockMapping blockMapping, ItemMapping item, @Nullable CompoundTag nbtData, boolean isSessionPlayer) {
boolean isShearsEffective = session.getTagCache().isShearsEffective(blockMapping); //TODO called twice
boolean canHarvestWithHand = blockMapping.isCanBreakWithHand();
String toolType = "";
@ -154,36 +155,28 @@ public final class BlockUtils {
if (!isSessionPlayer) {
// Another entity is currently mining; we have all the information we know
return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective,
toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false,
false, false);
toolEfficiencyLevel, hasteLevel, miningFatigueLevel, false, true);
}
hasteLevel = Math.max(session.getEffectCache().getHaste(), session.getEffectCache().getConduitPower());
miningFatigueLevel = session.getEffectCache().getMiningFatigue();
boolean isInWater = session.getCollisionManager().isPlayerInWater();
boolean insideOfWaterWithoutAquaAffinity = isInWater &&
boolean waterInEyes = session.getCollisionManager().isWaterInEyes();
boolean insideOfWaterWithoutAquaAffinity = waterInEyes &&
ItemUtils.getEnchantmentLevel(session.getPlayerInventory().getItem(5).getNbt(), "minecraft:aqua_affinity") < 1;
boolean outOfWaterButNotOnGround = (!isInWater) && (!session.getPlayerEntity().isOnGround());
boolean insideWaterNotOnGround = isInWater && !session.getPlayerEntity().isOnGround();
return calculateBreakTime(blockMapping.getHardness(), toolTier, canHarvestWithHand, correctTool, toolCanBreak, toolType, isShearsEffective,
toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity,
outOfWaterButNotOnGround, insideWaterNotOnGround);
toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, session.getPlayerEntity().isOnGround());
}
public static double getSessionBreakTime(GeyserSession session, BlockMapping blockMapping) {
PlayerInventory inventory = session.getPlayerInventory();
GeyserItemStack item = inventory.getItemInHand();
ItemMapping mapping;
CompoundTag nbtData;
ItemMapping mapping = ItemMapping.AIR;
CompoundTag nbtData = null;
if (item != null) {
mapping = item.getMapping(session);
nbtData = item.getNbt();
} else {
mapping = ItemMapping.AIR;
nbtData = new CompoundTag("");
}
return getBreakTime(session, blockMapping, mapping, nbtData, true);
}