Skip to content

Commit

Permalink
Fix break time while submerged in water (GeyserMC#3110)
Browse files Browse the repository at this point in the history
* Fix break time while submerged in water

* Review stuff

* LAYERS -> LEVELS
  • Loading branch information
davchoo authored Jul 3, 2022
1 parent f2f894b commit dc810f1
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,9 @@ protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch,
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -449,7 +451,6 @@ public static byte getShulkerBoxDirection(int state) {

/**
* 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
Expand All @@ -458,6 +459,30 @@ public static int getWaterLevel(int state) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -405,6 +406,18 @@ public boolean isPlayerInWater() {
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
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
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;
Expand Down Expand Up @@ -1767,6 +1768,17 @@ public void playSoundEvent(SoundEvent sound, Vector3f position) {
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet)

// 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;

Expand Down Expand Up @@ -608,7 +608,7 @@ private void lookAt(GeyserSession session, Vector3f target) {
// 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
Expand Down Expand Up @@ -637,15 +637,4 @@ private void lookAt(GeyserSession session, Vector3f target) {
}, 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();
};
}
}
29 changes: 11 additions & 18 deletions core/src/main/java/org/geysermc/geyser/util/BlockUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
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) {
Expand Down Expand Up @@ -101,7 +103,7 @@ private static boolean canToolTierBreakBlock(GeyserSession session, BlockMapping
// 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;

Expand Down Expand Up @@ -129,12 +131,11 @@ private static double calculateBreakTime(double blockHardness, String toolTier,
}

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 = "";
Expand All @@ -154,36 +155,28 @@ public static double getBreakTime(GeyserSession session, BlockMapping blockMappi
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);
}
Expand Down

0 comments on commit dc810f1

Please sign in to comment.