From a23273422c13af7431a181fcb338803e9d723183 Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Mon, 30 Dec 2024 17:45:40 +0800 Subject: [PATCH] feat: implement liquid motion this commit also has some bug fixes, see changelog for details --- CHANGELOG.md | 12 ++ .../component/BlockLiquidBaseComponent.java | 4 +- .../org/allaymc/api/block/data/BlockFace.java | 11 +- .../entity/component/EntityBaseComponent.java | 9 ++ .../component/EntityDamageComponent.java | 7 + .../java/org/allaymc/api/math/MathUtils.java | 79 ++++++++++-- .../BlockLiquidBaseComponentImpl.java | 89 ++++++++++++- .../server/block/impl/BlockBehaviorImpl.java | 2 +- .../block/impl/BlockLiquidBehaviorImpl.java | 2 +- .../component/EntityBaseComponentImpl.java | 9 +- .../component/EntityDamageComponentImpl.java | 9 ++ .../EntityPlayerNetworkComponentImpl.java | 3 +- .../entity/type/EntityTypeInitializer.java | 5 + .../service/AllayEntityPhysicsService.java | 122 +++++++++++++++--- 14 files changed, 317 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 436904a41..55e828c9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ Unless otherwise specified, any version comparison below is the comparison of se - (API) Added an extra argument to `Dimension#breakBlock` method to control if the block breaking particle should be played. - (API) Added `LiquidHardenEvent#setHardenedBlockState` method to allow changing the hardened block state. +- (API) Introduced `MathUtils#normalizeIfNotZero` method to normalize a vector only if it is not zero, this method + prevents NaN caused by `Vector3fc#normalize` method. +- (API) Introduced `EntityBaseComponent#computeLiquidMotion` method to control whether an entity has liquid motion. +- (API) Introduced `EntityDamageComponent#hasDrowningDamage` method to control whether an entity has drowning damage. +- Added liquid motion for water and lava. Now entity will be moved by liquid flow if it is in the liquid. ### Changed @@ -29,11 +34,18 @@ Unless otherwise specified, any version comparison below is the comparison of se - (API) Corrected the return type of `Dimension#breakBlock(Vector3ic, ItemStack, Entity)` from `void` to `boolean`. Some overloads for this method are also added. +- (API) Fixed incorrect bit operations in `BlockLiquidBaseComponent#getLiquidBlockState` and + `BlockLiquidBaseComponent#getLiquidBlockState#getDepth`, + although it seems that they do not cause any issues. - Block breaking particle won't be sent if block is broken by flowing liquid. - Water placed in nether dimension will disappear immediately now. - `Pos`, `Motion` and `Rotation` in entity nbt are now saved as list tag instead of compound tag to match vanilla. This also fixed the bug that entities being spawned in incorrect position when placing structure using `/structure` command. Please note that this change is not backward compatible and will break the old world and player data. +- Fixed several NaNs caused by `Vector3fc#normalize` methods in the physics engine, and now setting the motion of an + entity to a vector which contains NaN will result in an error. +- EntityItem now won't have drowning damage when it is in water, this bug causes entity item died after a period of time + in water. ## 0.1.1 (API 0.2.0) diff --git a/api/src/main/java/org/allaymc/api/block/component/BlockLiquidBaseComponent.java b/api/src/main/java/org/allaymc/api/block/component/BlockLiquidBaseComponent.java index e9079e4a2..27a61a487 100644 --- a/api/src/main/java/org/allaymc/api/block/component/BlockLiquidBaseComponent.java +++ b/api/src/main/java/org/allaymc/api/block/component/BlockLiquidBaseComponent.java @@ -38,7 +38,7 @@ static int getDepth(BlockState blockState) { if (isFalling(blockState) || isSource(blockState)) { return 8; } - return 8 - blockState.getPropertyValue(BlockPropertyTypes.LIQUID_DEPTH) & 0b0111; + return 8 - (blockState.getPropertyValue(BlockPropertyTypes.LIQUID_DEPTH) & 0b0111); } /** @@ -61,7 +61,7 @@ static boolean isSource(BlockState blockState) { * @return the block state of the liquid block with given depth and falling state. */ default BlockState getLiquidBlockState(int depth, boolean falling) { - return getBlockType().ofState(BlockPropertyTypes.LIQUID_DEPTH.createValue(falling ? 0b1000 | 8 - depth : 8 - depth)); + return getBlockType().ofState(BlockPropertyTypes.LIQUID_DEPTH.createValue(falling ? 0b1000 | (8 - depth) : 8 - depth)); } /** diff --git a/api/src/main/java/org/allaymc/api/block/data/BlockFace.java b/api/src/main/java/org/allaymc/api/block/data/BlockFace.java index b46aada9f..9daab02e7 100644 --- a/api/src/main/java/org/allaymc/api/block/data/BlockFace.java +++ b/api/src/main/java/org/allaymc/api/block/data/BlockFace.java @@ -30,10 +30,9 @@ public enum BlockFace { WEST(1, new Vector3i(-1, 0, 0)), EAST(3, new Vector3i(1, 0, 0)); - private static final BlockFace[] STAIR_DIRECTION_VALUE_TO_BLOCK_FACE = new BlockFace[]{ - BlockFace.EAST, BlockFace.WEST, - BlockFace.SOUTH, BlockFace.NORTH - }; + private static final BlockFace[] HORIZONTAL_BLOCK_FACES = {NORTH, EAST, SOUTH, WEST}; + private static final BlockFace[] VERTICAL_BLOCK_FACES = {UP, DOWN}; + private static final BlockFace[] STAIR_DIRECTION_VALUE_TO_BLOCK_FACE = {EAST, WEST, SOUTH, NORTH}; private final int horizontalIndex; private final Vector3ic offset; @@ -63,7 +62,7 @@ public static BlockFace fromId(int value) { * @return the horizontal block faces. */ public static BlockFace[] getHorizontalBlockFaces() { - return new BlockFace[]{NORTH, EAST, SOUTH, WEST}; + return HORIZONTAL_BLOCK_FACES; } /** @@ -72,7 +71,7 @@ public static BlockFace[] getHorizontalBlockFaces() { * @return the vertical block faces. */ public static BlockFace[] getVerticalBlockFaces() { - return new BlockFace[]{UP, DOWN}; + return VERTICAL_BLOCK_FACES; } public static BlockFace getBlockFaceByStairDirectionValue(int value) { diff --git a/api/src/main/java/org/allaymc/api/entity/component/EntityBaseComponent.java b/api/src/main/java/org/allaymc/api/entity/component/EntityBaseComponent.java index d81729382..d1f4deb14 100644 --- a/api/src/main/java/org/allaymc/api/entity/component/EntityBaseComponent.java +++ b/api/src/main/java/org/allaymc/api/entity/component/EntityBaseComponent.java @@ -339,6 +339,15 @@ default boolean computeBlockCollisionMotion() { return false; } + /** + * Check if the entity has liquid motion. + * + * @return {@code true} if the entity has liquid motion. + */ + default boolean computeLiquidMotion() { + return true; + } + /** * Called when the entity collides with another entity. * diff --git a/api/src/main/java/org/allaymc/api/entity/component/EntityDamageComponent.java b/api/src/main/java/org/allaymc/api/entity/component/EntityDamageComponent.java index 1d0de32be..22dcad836 100644 --- a/api/src/main/java/org/allaymc/api/entity/component/EntityDamageComponent.java +++ b/api/src/main/java/org/allaymc/api/entity/component/EntityDamageComponent.java @@ -84,6 +84,13 @@ default boolean attack(Entity attacker, float damage) { */ boolean hasFireDamage(); + /** + * Check if the entity has drowning damage. + * + * @return {@code true} if the entity has drowning damage, {@code false} otherwise. + */ + boolean hasDrowningDamage(); + /** * Check if this entity can against fire damage even if * it does not have fire resistance effect. diff --git a/api/src/main/java/org/allaymc/api/math/MathUtils.java b/api/src/main/java/org/allaymc/api/math/MathUtils.java index 041b006b3..54624522b 100644 --- a/api/src/main/java/org/allaymc/api/math/MathUtils.java +++ b/api/src/main/java/org/allaymc/api/math/MathUtils.java @@ -171,19 +171,82 @@ public static double getPitchFromVector(Vector3fc vector) { return StrictMath.abs(pitch) < 1E-10 ? 0 : pitch; } - public static float fastSin(float p) { - return SIN_LOOK_UP_TABLE[((int) (p * 10430.378F) & 0xFFFF)]; + /** + * Calculate sin value quickly by looking up a pre-calculated table. + * + * @param radian the radian. + * + * @return the sin value. + */ + public static float fastSin(float radian) { + return SIN_LOOK_UP_TABLE[((int) (radian * 10430.378F) & 0xFFFF)]; + } + + /** + * Calculate sin value quickly by looking up a pre-calculated table. + * + * @param radian the radian. + * + * @return the sin value. + */ + public static float fastSin(double radian) { + return SIN_LOOK_UP_TABLE[((int) (radian * 10430.378F) & 0xFFFF)]; + } + + /** + * Calculate cos value quickly by looking up a pre-calculated table. + * + * @param radian the radian. + * + * @return the cos value. + */ + public static float fastCos(float radian) { + return SIN_LOOK_UP_TABLE[((int) (radian * 10430.378F + 16384.0F) & 0xFFFF)]; + } + + /** + * Calculate cos value quickly by looking up a pre-calculated table. + * + * @param radian the radian. + * + * @return the cos value. + */ + public static float fastCos(double radian) { + return SIN_LOOK_UP_TABLE[((int) (radian * 10430.378F + 16384.0F) & 0xFFFF)]; } - public static float fastSin(double p) { - return SIN_LOOK_UP_TABLE[((int) (p * 10430.378F) & 0xFFFF)]; + /** + * Check if the vector contains NaN. + * + * @param v the vector to check. + * + * @return {@code true} if the vector contains NaN, {@code false} otherwise. + */ + public static boolean hasNaN(Vector3fc v) { + return Float.isNaN(v.x()) || Float.isNaN(v.y()) || Float.isNaN(v.z()); } - public static float fastCos(float p) { - return SIN_LOOK_UP_TABLE[((int) (p * 10430.378F + 16384.0F) & 0xFFFF)]; + /** + * Check if the vector contains NaN. + * + * @param v the vector to check. + * + * @return {@code true} if the vector contains NaN, {@code false} otherwise. + */ + public static boolean hasNaN(Vector3dc v) { + return Double.isNaN(v.x()) || Double.isNaN(v.y()) || Double.isNaN(v.z()); } - public static float fastCos(double p) { - return SIN_LOOK_UP_TABLE[((int) (p * 10430.378F + 16384.0F) & 0xFFFF)]; + /** + * Normalize the vector if it is not zero. + *
+ * If the vector is zero, it can't be normalized, otherwise a vector with three NaN values will be produced.
+ *
+ * @param v the vector.
+ *
+ * @return the normalized vector.
+ */
+ public static Vector3f normalizeIfNotZero(Vector3f v) {
+ return v.lengthSquared() > 0 ? v.normalize(v) : v;
}
}
diff --git a/server/src/main/java/org/allaymc/server/block/component/BlockLiquidBaseComponentImpl.java b/server/src/main/java/org/allaymc/server/block/component/BlockLiquidBaseComponentImpl.java
index 366cc7ea7..9fa2c7e91 100644
--- a/server/src/main/java/org/allaymc/server/block/component/BlockLiquidBaseComponentImpl.java
+++ b/server/src/main/java/org/allaymc/server/block/component/BlockLiquidBaseComponentImpl.java
@@ -12,11 +12,12 @@
import org.allaymc.api.block.type.BlockState;
import org.allaymc.api.block.type.BlockType;
import org.allaymc.api.block.type.BlockTypes;
-import org.allaymc.api.entity.Entity;
import org.allaymc.api.eventbus.event.block.LiquidDecayEvent;
import org.allaymc.api.eventbus.event.block.LiquidFlowEvent;
+import org.allaymc.api.math.MathUtils;
import org.allaymc.api.math.position.Position3i;
import org.allaymc.api.world.Dimension;
+import org.joml.Vector3f;
import org.joml.Vector3i;
import org.joml.Vector3ic;
@@ -94,9 +95,74 @@ public void onScheduledUpdate(BlockStateWithPos blockStateWithPos) {
updateLiquid(dimension, pos, liquid, blockStateWithPos.layer());
}
- @Override
- public void onCollideWithEntity(BlockStateWithPos blockStateWithPos, Entity entity) {
- // TODO
+ /**
+ * This method is used in {@link org.allaymc.server.world.service.AllayEntityPhysicsService}
+ */
+ public Vector3f calculateFlowVector(Dimension dimension, int x, int y, int z, BlockState current) {
+ // TODO: cache the flow vector for better performance
+ var vx = 0;
+ var vy = 0;
+ var vz = 0;
+ var decay = getEffectiveFlowDecay(current);
+
+ for (var face : BlockFace.getHorizontalBlockFaces()) {
+ var offset = face.getOffset();
+
+ var sideX = x + offset.x();
+ var sideY = y + offset.y();
+ var sideZ = z + offset.z();
+ var sideBlock = dimension.getBlockState(sideX, sideY, sideZ);
+ var blockDecay = getEffectiveFlowDecay(sideBlock);
+
+ if (blockDecay < 0) {
+ if (!sideBlock.getBlockStateData().liquidReactionOnTouch().canLiquidFlowInto()) {
+ continue;
+ }
+
+ blockDecay = getEffectiveFlowDecay(dimension.getBlockState(sideX, sideY - 1, sideZ));
+
+ if (blockDecay >= 0) {
+ var realDecay = blockDecay - (decay - 8);
+ vx += offset.x() * realDecay;
+ vy += offset.y() * realDecay;
+ vz += offset.z() * realDecay;
+ }
+
+ continue;
+ }
+
+ var realDecay = blockDecay - decay;
+ vx += offset.x() * realDecay;
+ vy += offset.y() * realDecay;
+ vz += offset.z() * realDecay;
+ }
+
+ var vector = new Vector3f(vx, vy, vz);
+
+ if (isFalling(current)) {
+ for (var face : BlockFace.getHorizontalBlockFaces()) {
+ var offset = face.getOffset();
+ if (!canFlowInto(dimension, x + offset.x(), y + offset.y(), z + offset.z(), true) &&
+ !canFlowInto(dimension, x + offset.x(), y + offset.y() + 1, z + offset.z(), true)) {
+ // normalize() should only be called when the vector is not zero,
+ // otherwise it will produce a vector with three NaN values.
+ MathUtils.normalizeIfNotZero(vector);
+ vector.add(0, -6, 0);
+ break;
+ }
+ }
+ }
+
+ // Same to above
+ return MathUtils.normalizeIfNotZero(vector);
+ }
+
+ protected int getEffectiveFlowDecay(BlockState liquid) {
+ if (!isSameLiquidType(liquid.getBlockType())) {
+ return -1;
+ }
+
+ return isFalling(liquid) ? 0 : 8 - getDepth(liquid);
}
/**
@@ -390,17 +456,26 @@ protected boolean spreadNeighbor(Dimension dimension, Vector3ic source, LiquidNo
return false;
}
+ /**
+ * @see #canFlowInto(Dimension, int, int, int, boolean)
+ */
+ protected boolean canFlowInto(Dimension dimension, Vector3ic pos, boolean sideways) {
+ return canFlowInto(dimension, pos.x(), pos.y(), pos.z(), sideways);
+ }
+
/**
* Checks if a liquid can flow into the block present in the world at a specific block position.
*
* @param dimension The dimension the block is in.
- * @param pos The position of the block to flow into.
+ * @param x The x coordinate of the block.
+ * @param y The y coordinate of the block.
+ * @param z The z coordinate of the block.
* @param sideways Whether the flow is sideways or downwards.
*
* @return Whether the liquid can flow into the block.
*/
- protected boolean canFlowInto(Dimension dimension, Vector3ic pos, boolean sideways) {
- var existing = dimension.getBlockState(pos);
+ protected boolean canFlowInto(Dimension dimension, int x, int y, int z, boolean sideways) {
+ var existing = dimension.getBlockState(x, y, z);
if (existing.getBlockType() == BlockTypes.AIR ||
existing.getBlockStateData().liquidReactionOnTouch().removedOnTouch()) {
return true;
diff --git a/server/src/main/java/org/allaymc/server/block/impl/BlockBehaviorImpl.java b/server/src/main/java/org/allaymc/server/block/impl/BlockBehaviorImpl.java
index d3014af27..211bd150b 100644
--- a/server/src/main/java/org/allaymc/server/block/impl/BlockBehaviorImpl.java
+++ b/server/src/main/java/org/allaymc/server/block/impl/BlockBehaviorImpl.java
@@ -26,7 +26,7 @@ public BlockBehaviorImpl(List