From 2a05c7c01d593fc3ecd01c21552c5ae7698991bb Mon Sep 17 00:00:00 2001 From: ChampionAsh5357 Date: Tue, 14 Jun 2022 18:07:51 -0400 Subject: [PATCH] [1.19.x] Fluid API Overhaul (#8695) --- .../client/player/LocalPlayer.java.patch | 40 +- .../client/renderer/FogRenderer.java.patch | 13 +- .../renderer/ScreenEffectRenderer.java.patch | 3 +- .../block/LiquidBlockRenderer.java.patch | 2 +- .../BoatDispenseItemBehavior.java.patch | 29 + .../minecraft/world/entity/Entity.java.patch | 248 +++- .../world/entity/LivingEntity.java.patch | 100 +- .../net/minecraft/world/entity/Mob.java.patch | 25 + .../world/entity/ai/behavior/Swim.java.patch | 11 + .../world/entity/ai/goal/FloatGoal.java.patch | 11 + .../world/entity/animal/Bee.java.patch | 19 + .../world/entity/animal/Fox.java.patch | 9 + .../entity/item/FallingBlockEntity.java.patch | 16 + .../world/entity/item/ItemEntity.java.patch | 9 + .../world/entity/monster/MagmaCube.java.patch | 25 +- .../projectile/AbstractArrow.java.patch | 9 + .../world/entity/vehicle/Boat.java.patch | 54 + .../world/item/BucketItem.java.patch | 2 +- .../block/ConcretePowderBlock.java.patch | 44 + .../world/level/block/CoralBlock.java.patch | 14 + .../world/level/block/FarmBlock.java.patch | 8 +- .../world/level/block/LiquidBlock.java.patch | 38 +- .../world/level/block/MagmaBlock.java.patch | 11 + .../world/level/block/SpongeBlock.java.patch | 19 + .../level/block/SugarCaneBlock.java.patch | 9 + .../level/material/FlowingFluid.java.patch | 15 +- .../world/level/material/Fluid.java.patch | 21 +- .../pathfinder/BlockPathTypes.java.patch | 17 +- .../pathfinder/WalkNodeEvaluator.java.patch | 32 +- .../client/FluidContainerColorer.java | 2 +- .../client/ForgeHooksClient.java | 31 +- .../client/IFluidTypeRenderProperties.java | 373 ++++++ .../client/RenderProperties.java | 18 + .../client/gui/ForgeIngameGui.java | 3 +- .../client/model/DynamicBucketModel.java | 9 +- .../client/model/FluidModel.java | 15 +- .../net/minecraftforge/common/ForgeHooks.java | 43 +- .../net/minecraftforge/common/ForgeMod.java | 196 ++- .../minecraftforge/common/SoundAction.java | 45 + .../minecraftforge/common/SoundActions.java | 27 + .../java/net/minecraftforge/common/Tags.java | 7 + .../common/extensions/IForgeBlock.java | 56 +- .../common/extensions/IForgeBlockState.java | 60 +- .../common/extensions/IForgeBoat.java | 35 + .../common/extensions/IForgeEntity.java | 208 ++- .../common/extensions/IForgeFluid.java | 202 ++- .../common/extensions/IForgeFluidState.java | 159 ++- .../common/extensions/IForgeLivingEntity.java | 69 + .../fluids/DispenseFluidContainer.java | 2 +- .../fluids/FluidAttributes.java | 446 ------- .../fluids/FluidInteractionRegistry.java | 184 +++ .../net/minecraftforge/fluids/FluidStack.java | 4 +- .../net/minecraftforge/fluids/FluidType.java | 1151 +++++++++++++++++ .../net/minecraftforge/fluids/FluidUtil.java | 17 +- .../fluids/ForgeFlowingFluid.java | 42 +- .../fluids/capability/TileFluidHandler.java | 4 +- .../capability/wrappers/BlockWrapper.java | 12 +- .../wrappers/BucketPickupHandlerWrapper.java | 18 +- .../wrappers/FluidBlockWrapper.java | 4 +- .../wrappers/FluidBucketWrapper.java | 18 +- .../ForgeDeferredRegistriesSetup.java | 1 + .../registries/ForgeRegistries.java | 4 + .../minecraftforge/registries/GameData.java | 6 + src/main/resources/forge.exc | 2 + .../debug/fluid/FluidTypeTest.java | 218 ++++ .../debug/fluid/NewFluidTest.java | 50 +- src/test/resources/META-INF/mods.toml | 2 + 67 files changed, 3856 insertions(+), 740 deletions(-) create mode 100644 patches/minecraft/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch create mode 100644 patches/minecraft/net/minecraft/world/entity/ai/behavior/Swim.java.patch create mode 100644 patches/minecraft/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch create mode 100644 patches/minecraft/net/minecraft/world/entity/item/FallingBlockEntity.java.patch create mode 100644 patches/minecraft/net/minecraft/world/level/block/ConcretePowderBlock.java.patch create mode 100644 patches/minecraft/net/minecraft/world/level/block/CoralBlock.java.patch create mode 100644 patches/minecraft/net/minecraft/world/level/block/MagmaBlock.java.patch create mode 100644 patches/minecraft/net/minecraft/world/level/block/SpongeBlock.java.patch create mode 100644 src/main/java/net/minecraftforge/client/IFluidTypeRenderProperties.java create mode 100644 src/main/java/net/minecraftforge/common/SoundAction.java create mode 100644 src/main/java/net/minecraftforge/common/SoundActions.java create mode 100644 src/main/java/net/minecraftforge/common/extensions/IForgeBoat.java create mode 100644 src/main/java/net/minecraftforge/common/extensions/IForgeLivingEntity.java delete mode 100644 src/main/java/net/minecraftforge/fluids/FluidAttributes.java create mode 100644 src/main/java/net/minecraftforge/fluids/FluidInteractionRegistry.java create mode 100644 src/main/java/net/minecraftforge/fluids/FluidType.java create mode 100644 src/test/java/net/minecraftforge/debug/fluid/FluidTypeTest.java diff --git a/patches/minecraft/net/minecraft/client/player/LocalPlayer.java.patch b/patches/minecraft/net/minecraft/client/player/LocalPlayer.java.patch index 3da16b9616..547df39513 100644 --- a/patches/minecraft/net/minecraft/client/player/LocalPlayer.java.patch +++ b/patches/minecraft/net/minecraft/client/player/LocalPlayer.java.patch @@ -49,7 +49,35 @@ this.f_108619_.m_91301_().m_120586_(this.f_108618_); if (this.m_6117_() && !this.m_20159_()) { this.f_108618_.f_108566_ *= 0.2F; -@@ -772,7 +_,7 @@ +@@ -726,7 +_,7 @@ + } + + boolean flag4 = (float)this.m_36324_().m_38702_() > 6.0F || this.m_150110_().f_35936_; +- if ((this.f_19861_ || this.m_5842_()) && !flag1 && !flag2 && this.m_108733_() && !this.m_20142_() && flag4 && !this.m_6117_() && !this.m_21023_(MobEffects.f_19610_)) { ++ if ((this.f_19861_ || this.m_5842_() || this.canStartSwimming()) && !flag1 && !flag2 && this.m_108733_() && !this.m_20142_() && flag4 && !this.m_6117_() && !this.m_21023_(MobEffects.f_19610_)) { + if (this.f_108583_ <= 0 && !this.f_108619_.f_91066_.f_92091_.m_90857_()) { + this.f_108583_ = 7; + } else { +@@ -734,15 +_,15 @@ + } + } + +- if (!this.m_20142_() && (!this.m_20069_() || this.m_5842_()) && this.m_108733_() && flag4 && !this.m_6117_() && !this.m_21023_(MobEffects.f_19610_) && this.f_108619_.f_91066_.f_92091_.m_90857_()) { ++ if (!this.m_20142_() && (!(this.m_20069_() || this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType))) || (this.m_5842_() || this.canStartSwimming())) && this.m_108733_() && flag4 && !this.m_6117_() && !this.m_21023_(MobEffects.f_19610_) && this.f_108619_.f_91066_.f_92091_.m_90857_()) { + this.m_6858_(true); + } + + if (this.m_20142_()) { + boolean flag5 = !this.f_108618_.m_108577_() || !flag4; +- boolean flag6 = flag5 || this.f_19862_ && !this.f_185931_ || this.m_20069_() && !this.m_5842_(); ++ boolean flag6 = flag5 || this.f_19862_ && !this.f_185931_ || (this.m_20069_() && !this.m_5842_()) || (this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType)) && !this.canStartSwimming()); + if (this.m_6069_()) { +- if (!this.f_19861_ && !this.f_108618_.f_108573_ && flag5 || !this.m_20069_()) { ++ if (!this.f_19861_ && !this.f_108618_.f_108573_ && flag5 || !(this.m_20069_() || this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType)))) { + this.m_6858_(false); + } + } else if (flag6) { +@@ -772,14 +_,15 @@ if (this.f_108618_.f_108572_ && !flag7 && !flag && !this.m_150110_().f_35935_ && !this.m_20159_() && !this.m_6147_()) { ItemStack itemstack = this.m_6844_(EquipmentSlot.CHEST); @@ -58,6 +86,16 @@ this.f_108617_.m_104955_(new ServerboundPlayerCommandPacket(this, ServerboundPlayerCommandPacket.Action.START_FALL_FLYING)); } } + + this.f_108614_ = this.m_21255_(); +- if (this.m_20069_() && this.f_108618_.f_108573_ && this.m_6129_()) { +- this.m_21208_(); ++ net.minecraftforge.fluids.FluidType fluidType = this.getMaxHeightFluidType(); ++ if ((this.m_20069_() || (!fluidType.isAir() && this.canSwimInFluidType(fluidType))) && this.f_108618_.f_108573_ && this.m_6129_()) { ++ this.sinkInFluid(this.m_20069_() ? net.minecraftforge.common.ForgeMod.WATER_TYPE.get() : fluidType); + } + + if (this.m_204029_(FluidTags.f_13131_)) { @@ -890,6 +_,7 @@ public void m_6083_() { diff --git a/patches/minecraft/net/minecraft/client/renderer/FogRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/FogRenderer.java.patch index 2b1b096bba..75f0cddf1e 100644 --- a/patches/minecraft/net/minecraft/client/renderer/FogRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/FogRenderer.java.patch @@ -1,15 +1,14 @@ --- a/net/minecraft/client/renderer/FogRenderer.java +++ b/net/minecraft/client/renderer/FogRenderer.java -@@ -182,6 +_,13 @@ +@@ -182,6 +_,12 @@ f_109012_ = f_109012_ * (1.0F - f7) + f_109012_ * f9 * f7; } -+ net.minecraftforge.client.event.EntityViewRenderEvent.FogColors event = new net.minecraftforge.client.event.EntityViewRenderEvent.FogColors(p_109019_, p_109020_, f_109010_, f_109011_, f_109012_); -+ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(event); ++ Vector3f fogColor = net.minecraftforge.client.ForgeHooksClient.getFogColor(p_109019_, p_109020_, p_109021_, p_109022_, p_109023_, f_109010_, f_109011_, f_109012_); + -+ f_109010_ = event.getRed(); -+ f_109011_ = event.getGreen(); -+ f_109012_ = event.getBlue(); ++ f_109010_ = fogColor.m_122239_(); ++ f_109011_ = fogColor.m_122260_(); ++ f_109012_ = fogColor.m_122269_(); + RenderSystem.m_69424_(f_109010_, f_109011_, f_109012_, 0.0F); } @@ -18,7 +17,7 @@ RenderSystem.m_157445_(fogrenderer$fogdata.f_234200_); RenderSystem.m_157443_(fogrenderer$fogdata.f_234201_); RenderSystem.m_202160_(fogrenderer$fogdata.f_234202_); -+ net.minecraftforge.client.ForgeHooksClient.onFogRender(fogtype, p_234173_, p_234177_, fogrenderer$fogdata.f_234200_, fogrenderer$fogdata.f_234201_, fogrenderer$fogdata.f_234202_); ++ net.minecraftforge.client.ForgeHooksClient.onFogRender(p_234174_, fogtype, p_234173_, p_234177_, p_234175_, fogrenderer$fogdata.f_234200_, fogrenderer$fogdata.f_234201_, fogrenderer$fogdata.f_234202_); } public static void m_109036_() { diff --git a/patches/minecraft/net/minecraft/client/renderer/ScreenEffectRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/ScreenEffectRenderer.java.patch index cab7a34e67..1a20ccee00 100644 --- a/patches/minecraft/net/minecraft/client/renderer/ScreenEffectRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/ScreenEffectRenderer.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/client/renderer/ScreenEffectRenderer.java +++ b/net/minecraft/client/renderer/ScreenEffectRenderer.java -@@ -30,18 +_,21 @@ +@@ -30,18 +_,22 @@ public static void m_110718_(Minecraft p_110719_, PoseStack p_110720_) { Player player = p_110719_.f_91074_; if (!player.f_19794_) { @@ -19,6 +19,7 @@ + if (!net.minecraftforge.client.ForgeHooksClient.renderWaterOverlay(player, p_110720_)) m_110725_(p_110719_, p_110720_); } ++ else if (!player.getEyeInFluidType().isAir()) net.minecraftforge.client.RenderProperties.get(player.getEyeInFluidType()).renderOverlay(p_110719_, p_110720_); if (p_110719_.f_91074_.m_6060_()) { + if (!net.minecraftforge.client.ForgeHooksClient.renderFireOverlay(player, p_110720_)) diff --git a/patches/minecraft/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch b/patches/minecraft/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch index d935e04639..37f9a0f709 100644 --- a/patches/minecraft/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch @@ -7,7 +7,7 @@ - TextureAtlasSprite[] atextureatlassprite = flag ? this.f_110940_ : this.f_110941_; - int i = flag ? 16777215 : BiomeColors.m_108811_(p_234370_, p_234371_); + TextureAtlasSprite[] atextureatlassprite = net.minecraftforge.client.ForgeHooksClient.getFluidSprites(p_234370_, p_234371_, p_234374_); -+ int i = p_234374_.m_76152_().getAttributes().getColor(p_234370_, p_234371_); ++ int i = net.minecraftforge.client.RenderProperties.get(p_234374_).getColorTint(p_234374_, p_234370_, p_234371_); + float alpha = (float)(i >> 24 & 255) / 255.0F; float f = (float)(i >> 16 & 255) / 255.0F; float f1 = (float)(i >> 8 & 255) / 255.0F; diff --git a/patches/minecraft/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/patches/minecraft/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch new file mode 100644 index 0000000000..824ed41f3c --- /dev/null +++ b/patches/minecraft/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +@@ -31,20 +_,21 @@ + double d1 = p_123375_.m_7098_() + (double)((float)direction.m_122430_() * 1.125F); + double d2 = p_123375_.m_7094_() + (double)((float)direction.m_122431_() * 1.125F); + BlockPos blockpos = p_123375_.m_7961_().m_121945_(direction); ++ Boat boat = (Boat)(this.f_235889_ ? new ChestBoat(level, d0, d1, d2) : new Boat(level, d0, d1, d2)); ++ boat.m_38332_(this.f_123369_); ++ boat.m_146922_(direction.m_122435_()); + double d3; +- if (level.m_6425_(blockpos).m_205070_(FluidTags.f_13131_)) { ++ if (boat.canBoatInFluid(level.m_6425_(blockpos))) { + d3 = 1.0D; + } else { +- if (!level.m_8055_(blockpos).m_60795_() || !level.m_6425_(blockpos.m_7495_()).m_205070_(FluidTags.f_13131_)) { ++ if (!level.m_8055_(blockpos).m_60795_() || !boat.canBoatInFluid(level.m_6425_(blockpos.m_7495_()))) { + return this.f_123368_.m_6115_(p_123375_, p_123376_); + } + + d3 = 0.0D; + } + +- Boat boat = (Boat)(this.f_235889_ ? new ChestBoat(level, d0, d1 + d3, d2) : new Boat(level, d0, d1 + d3, d2)); +- boat.m_38332_(this.f_123369_); +- boat.m_146922_(direction.m_122435_()); ++ boat.m_6034_(d0, d1 + d3, d2); + level.m_7967_(boat); + p_123376_.m_41774_(1); + return p_123376_; diff --git a/patches/minecraft/net/minecraft/world/entity/Entity.java.patch b/patches/minecraft/net/minecraft/world/entity/Entity.java.patch index aa63ad6358..bab45c4ad0 100644 --- a/patches/minecraft/net/minecraft/world/entity/Entity.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/Entity.java.patch @@ -17,7 +17,7 @@ private final EntityType f_19847_; private int f_19848_ = f_19843_.incrementAndGet(); public boolean f_19850_; -@@ -177,6 +_,7 @@ +@@ -177,14 +_,17 @@ public double f_19790_; public double f_19791_; public double f_19792_; @@ -25,6 +25,16 @@ public float f_19793_; public boolean f_19794_; protected final RandomSource f_19796_ = RandomSource.m_216327_(); + public int f_19797_; + private int f_19831_ = -this.m_6101_(); + protected boolean f_19798_; ++ @Deprecated // Forge: Use forgeFluidTypeHeight instead + protected Object2DoubleMap> f_19799_ = new Object2DoubleArrayMap<>(2); + protected boolean f_19800_; ++ @Deprecated // Forge: Use forgeFluidTypeOnEyes instead + private final Set> f_19801_ = new HashSet<>(); + public int f_19802_; + protected boolean f_19803_ = true; @@ -231,6 +_,7 @@ private BlockState f_185934_ = null; @@ -54,6 +64,15 @@ } public void m_142036_() { +@@ -455,7 +_,7 @@ + + if (this.m_20077_()) { + this.m_20093_(); +- this.f_19789_ *= 0.5F; ++ this.f_19789_ *= this.getFluidFallDistanceModifier(net.minecraftforge.common.ForgeMod.LAVA_TYPE.get()); + } + + this.m_146871_(); @@ -623,7 +_,7 @@ double d1 = vec3.f_82479_; double d2 = vec3.f_82480_; @@ -90,12 +109,12 @@ - this.f_19853_.m_46473_().m_7238_(); - } -+ if (this.f_146810_ && (this.f_146808_ || this.m_20071_())) { ++ if (this.f_146810_ && (this.f_146808_ || this.m_20071_() || this.isInFluidType((fluidType, height) -> this.canFluidExtinguish(fluidType)))) { + this.m_146873_(); + } + } + -+ if (this.m_6060_() && (this.f_146808_ || this.m_20071_())) { ++ if (this.m_6060_() && (this.f_146808_ || this.m_20071_() || this.isInFluidType((fluidType, height) -> this.canFluidExtinguish(fluidType)))) { + this.m_7311_(-this.m_6101_()); + } + @@ -141,7 +160,61 @@ this.m_5496_(soundtype.m_56776_(), soundtype.m_56773_() * 0.15F, soundtype.m_56774_()); } } -@@ -1168,9 +_,10 @@ +@@ -1068,19 +_,22 @@ + + public void m_5844_() { + if (this.m_6069_()) { +- this.m_20282_(this.m_20142_() && this.m_20069_() && !this.m_20159_()); ++ this.m_20282_(this.m_20142_() && (this.m_20069_() || this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType))) && !this.m_20159_()); + } else { +- this.m_20282_(this.m_20142_() && this.m_5842_() && !this.m_20159_() && this.f_19853_.m_6425_(this.f_19826_).m_205070_(FluidTags.f_13131_)); ++ this.m_20282_(this.m_20142_() && (this.m_5842_() || this.canStartSwimming()) && !this.m_20159_()); + } + + } + + protected boolean m_20073_() { + this.f_19799_.clear(); ++ this.forgeFluidTypeHeight.clear(); + this.m_20074_(); +- double d0 = this.f_19853_.m_6042_().f_63857_() ? 0.007D : 0.0023333333333333335D; +- boolean flag = this.m_204031_(FluidTags.f_13132_, d0); +- return this.m_20069_() || flag; ++ if (!(this.m_20202_() instanceof Boat)) { ++ this.f_19789_ *= this.forgeFluidTypeHeight.object2DoubleEntrySet().stream().filter(e -> !e.getKey().isAir() && !e.getKey().isVanilla()).map(e -> this.getFluidFallDistanceModifier(e.getKey())).min(Float::compare).orElse(1F); ++ if (this.isInFluidType((fluidType, height) -> !fluidType.isAir() && !fluidType.isVanilla() && this.canFluidExtinguish(fluidType))) this.m_20095_(); ++ } ++ return this.isInFluidType(); + } + + void m_20074_() { +@@ -1103,6 +_,7 @@ + private void m_20323_() { + this.f_19800_ = this.m_204029_(FluidTags.f_13131_); + this.f_19801_.clear(); ++ this.forgeFluidTypeOnEyes = net.minecraftforge.common.ForgeMod.EMPTY_TYPE.get(); + double d0 = this.m_20188_() - (double)0.11111111F; + Entity entity = this.m_20202_(); + if (entity instanceof Boat boat) { +@@ -1115,7 +_,7 @@ + FluidState fluidstate = this.f_19853_.m_6425_(blockpos); + double d1 = (double)((float)blockpos.m_123342_() + fluidstate.m_76155_(this.f_19853_, blockpos)); + if (d1 > d0) { +- fluidstate.m_205075_().forEach(this.f_19801_::add); ++ this.forgeFluidTypeOnEyes = fluidstate.getFluidType(); + } + + } +@@ -1159,7 +_,7 @@ + } + + public boolean m_5843_() { +- return this.m_20142_() && !this.m_20069_() && !this.m_5833_() && !this.m_6047_() && !this.m_20077_() && this.m_6084_(); ++ return this.m_20142_() && !this.m_20069_() && !this.m_5833_() && !this.m_6047_() && !this.m_20077_() && this.m_6084_() && !this.isInFluidType(); + } + + protected void m_20076_() { +@@ -1168,19 +_,23 @@ int k = Mth.m_14107_(this.m_20189_()); BlockPos blockpos = new BlockPos(i, j, k); BlockState blockstate = this.f_19853_.m_8055_(blockpos); @@ -153,6 +226,20 @@ } } + ++ @Deprecated // Forge: Use isEyeInFluidType instead + public boolean m_204029_(TagKey p_204030_) { ++ if (p_204030_ == FluidTags.f_13131_) return this.isEyeInFluidType(net.minecraftforge.common.ForgeMod.WATER_TYPE.get()); ++ else if (p_204030_ == FluidTags.f_13132_) return this.isEyeInFluidType(net.minecraftforge.common.ForgeMod.LAVA_TYPE.get()); + return this.f_19801_.contains(p_204030_); + } + + public boolean m_20077_() { +- return !this.f_19803_ && this.f_19799_.getDouble(FluidTags.f_13132_) > 0.0D; ++ return !this.f_19803_ && this.forgeFluidTypeHeight.getDouble(net.minecraftforge.common.ForgeMod.LAVA_TYPE.get()) > 0.0D; + } + + public void m_19920_(float p_19921_, Vec3 p_19922_) { @@ -1494,6 +_,8 @@ p_20241_.m_128379_("HasVisualFire", this.f_146813_); } @@ -220,11 +307,20 @@ return !this.f_19823_.isEmpty(); } -+ /** @deprecated Forge: Use {@link #canBeRiddenInWater(Entity) rider sensitive version} */ ++ /** @deprecated Forge: Use {@link #canBeRiddenUnderFluidType(net.minecraftforge.fluids.FluidType, Entity) rider sensitive version} */ + @Deprecated public boolean m_6146_() { return true; } +@@ -2010,7 +_,7 @@ + } + + public boolean m_20143_() { +- return this.m_6067_() && !this.m_20069_(); ++ return this.m_6067_() && !this.m_20069_() && !this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType)); + } + + public void m_20282_(boolean p_20283_) { @@ -2116,7 +_,7 @@ this.m_20254_(8); } @@ -287,6 +383,14 @@ } } else { return null; +@@ -2408,6 +_,7 @@ + return this.f_19821_; + } + ++ @Deprecated // Forge: Use FluidType sensitive version + public boolean m_6063_() { + return true; + } @@ -2487,8 +_,10 @@ EntityDimensions entitydimensions = this.f_19815_; Pose pose = this.m_20089_(); @@ -310,6 +414,111 @@ }); } +@@ -2772,9 +_,17 @@ + this.f_19859_ = this.m_146908_(); + } + ++ @Deprecated // Forge: Use no parameter version instead, only for vanilla Tags + public boolean m_204031_(TagKey p_204032_, double p_204033_) { ++ this.updateFluidHeightAndDoFluidPushing(); ++ if(p_204032_ == FluidTags.f_13131_) return this.isInFluidType(net.minecraftforge.common.ForgeMod.WATER_TYPE.get()); ++ else if (p_204032_ == FluidTags.f_13132_) return this.isInFluidType(net.minecraftforge.common.ForgeMod.LAVA_TYPE.get()); ++ else return false; ++ } ++ ++ public void updateFluidHeightAndDoFluidPushing() { + if (this.m_146899_()) { +- return false; ++ return; + } else { + AABB aabb = this.m_20191_().m_82406_(0.001D); + int i = Mth.m_14107_(aabb.f_82288_); +@@ -2789,25 +_,28 @@ + Vec3 vec3 = Vec3.f_82478_; + int k1 = 0; + BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos(); ++ it.unimi.dsi.fastutil.objects.Object2ObjectMap> interimCalcs = new it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap<>(net.minecraftforge.fluids.FluidType.SIZE.get() - 1); + + for(int l1 = i; l1 < j; ++l1) { + for(int i2 = k; i2 < l; ++i2) { + for(int j2 = i1; j2 < j1; ++j2) { + blockpos$mutableblockpos.m_122178_(l1, i2, j2); + FluidState fluidstate = this.f_19853_.m_6425_(blockpos$mutableblockpos); +- if (fluidstate.m_205070_(p_204032_)) { ++ net.minecraftforge.fluids.FluidType fluidType = fluidstate.getFluidType(); ++ if (!fluidType.isAir()) { + double d1 = (double)((float)i2 + fluidstate.m_76155_(this.f_19853_, blockpos$mutableblockpos)); + if (d1 >= aabb.f_82289_) { + flag1 = true; +- d0 = Math.max(d1 - aabb.f_82289_, d0); +- if (flag) { ++ org.apache.commons.lang3.tuple.MutableTriple interim = interimCalcs.computeIfAbsent(fluidType, t -> org.apache.commons.lang3.tuple.MutableTriple.of(0.0D, Vec3.f_82478_, 0)); ++ interim.setLeft(Math.max(d1 - aabb.f_82289_, interim.getLeft())); ++ if (this.isPushedByFluid(fluidType)) { + Vec3 vec31 = fluidstate.m_76179_(this.f_19853_, blockpos$mutableblockpos); +- if (d0 < 0.4D) { +- vec31 = vec31.m_82490_(d0); ++ if (interim.getLeft() < 0.4D) { ++ vec31 = vec31.m_82490_(interim.getLeft()); + } + +- vec3 = vec3.m_82549_(vec31); +- ++k1; ++ interim.setMiddle(interim.getMiddle().m_82549_(vec31)); ++ interim.setRight(interim.getRight() + 1); + } + } + } +@@ -2815,27 +_,28 @@ + } + } + +- if (vec3.m_82553_() > 0.0D) { +- if (k1 > 0) { +- vec3 = vec3.m_82490_(1.0D / (double)k1); ++ interimCalcs.forEach((fluidType, interim) -> { ++ if (interim.getMiddle().m_82553_() > 0.0D) { ++ if (interim.getRight() > 0) { ++ interim.setMiddle(interim.getMiddle().m_82490_(1.0D / (double)interim.getRight())); + } + + if (!(this instanceof Player)) { +- vec3 = vec3.m_82541_(); ++ interim.setMiddle(interim.getMiddle().m_82541_()); + } + + Vec3 vec32 = this.m_20184_(); +- vec3 = vec3.m_82490_(p_204033_ * 1.0D); ++ interim.setMiddle(interim.getMiddle().m_82490_(this.getFluidMotionScale(fluidType))); + double d2 = 0.003D; +- if (Math.abs(vec32.f_82479_) < 0.003D && Math.abs(vec32.f_82481_) < 0.003D && vec3.m_82553_() < 0.0045000000000000005D) { +- vec3 = vec3.m_82541_().m_82490_(0.0045000000000000005D); ++ if (Math.abs(vec32.f_82479_) < 0.003D && Math.abs(vec32.f_82481_) < 0.003D && interim.getMiddle().m_82553_() < 0.0045000000000000005D) { ++ interim.setMiddle(interim.getMiddle().m_82541_().m_82490_(0.0045000000000000005D)); + } + +- this.m_20256_(this.m_20184_().m_82549_(vec3)); ++ this.m_20256_(this.m_20184_().m_82549_(interim.getMiddle())); + } + +- this.f_19799_.put(p_204032_, d0); +- return flag1; ++ this.setFluidTypeHeight(fluidType, interim.getLeft()); ++ }); + } + } + +@@ -2848,7 +_,10 @@ + return !this.f_19853_.m_151572_(i, k, j, l); + } + ++ @Deprecated // Forge: Use getFluidTypeHeight instead + public double m_204036_(TagKey p_204037_) { ++ if (p_204037_ == FluidTags.f_13131_) return getFluidTypeHeight(net.minecraftforge.common.ForgeMod.WATER_TYPE.get()); ++ else if (p_204037_ == FluidTags.f_13132_) return getFluidTypeHeight(net.minecraftforge.common.ForgeMod.LAVA_TYPE.get()); + return this.f_19799_.getDouble(p_204037_); + } + @@ -2974,6 +_,7 @@ this.f_146801_.m_142044_(); @@ -318,7 +527,7 @@ } -@@ -3089,6 +_,76 @@ +@@ -3089,6 +_,103 @@ public boolean m_142265_(Level p_146843_, BlockPos p_146844_) { return true; } @@ -390,6 +599,33 @@ + return this.m_6380_(pose, size); + } + ++ protected Object2DoubleMap forgeFluidTypeHeight = new Object2DoubleArrayMap<>(net.minecraftforge.fluids.FluidType.SIZE.get()); ++ private net.minecraftforge.fluids.FluidType forgeFluidTypeOnEyes = net.minecraftforge.common.ForgeMod.EMPTY_TYPE.get(); ++ protected final void setFluidTypeHeight(net.minecraftforge.fluids.FluidType type, double height) { ++ this.forgeFluidTypeHeight.put(type, height); ++ } ++ @Override ++ public final double getFluidTypeHeight(net.minecraftforge.fluids.FluidType type) { ++ return this.forgeFluidTypeHeight.getDouble(type); ++ } ++ @Override ++ public final boolean isInFluidType(java.util.function.BiPredicate predicate, boolean forAllTypes) { ++ return forAllTypes ? this.forgeFluidTypeHeight.object2DoubleEntrySet().stream().allMatch(e -> predicate.test(e.getKey(), e.getDoubleValue())) ++ : this.forgeFluidTypeHeight.object2DoubleEntrySet().stream().anyMatch(e -> predicate.test(e.getKey(), e.getDoubleValue())); ++ } ++ @Override ++ public final boolean isInFluidType() { ++ return this.forgeFluidTypeHeight.size() > 0; ++ } ++ @Override ++ public final net.minecraftforge.fluids.FluidType getEyeInFluidType() { ++ return forgeFluidTypeOnEyes; ++ } ++ @Override ++ public net.minecraftforge.fluids.FluidType getMaxHeightFluidType() { ++ return this.forgeFluidTypeHeight.object2DoubleEntrySet().stream().max(java.util.Comparator.comparingDouble(Object2DoubleMap.Entry::getDoubleValue)).map(Object2DoubleMap.Entry::getKey).orElseGet(net.minecraftforge.common.ForgeMod.EMPTY_TYPE); ++ } ++ + /* ================================== Forge End =====================================*/ + diff --git a/patches/minecraft/net/minecraft/world/entity/LivingEntity.java.patch b/patches/minecraft/net/minecraft/world/entity/LivingEntity.java.patch index b3eb1101c6..0ab70ea7e1 100644 --- a/patches/minecraft/net/minecraft/world/entity/LivingEntity.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/LivingEntity.java.patch @@ -1,6 +1,12 @@ --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java -@@ -123,7 +_,9 @@ +@@ -118,12 +_,14 @@ + import net.minecraft.world.scores.PlayerTeam; + import org.slf4j.Logger; + +-public abstract class LivingEntity extends Entity { ++public abstract class LivingEntity extends Entity implements net.minecraftforge.common.extensions.IForgeLivingEntity { + private static final Logger f_201943_ = LogUtils.getLogger(); private static final UUID f_20929_ = UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D"); private static final UUID f_20959_ = UUID.fromString("87f46a96-686f-4796-b035-22e16ee9e038"); private static final UUID f_147184_ = UUID.fromString("1eaf83ff-7207-4596-b37a-d7a07b3ec4ce"); @@ -19,7 +25,7 @@ } protected void m_7840_(double p_20990_, boolean p_20991_, BlockState p_20992_, BlockPos p_20993_) { -@@ -292,7 +_,8 @@ +@@ -292,13 +_,15 @@ if (!p_20992_.m_60795_()) { double d0 = Math.min((double)(0.2F + f / 15.0F), 2.5D); int i = (int)(150.0D * d0); @@ -29,15 +35,47 @@ } } -@@ -356,7 +_,7 @@ + super.m_7840_(p_20990_, p_20991_, p_20992_, p_20993_); + } + ++ @Deprecated //FORGE: Use canDrownInFluidType instead + public boolean m_6040_() { + return this.m_6336_() == MobType.f_21641_; + } +@@ -337,8 +_,8 @@ + } + } + +- if (this.m_204029_(FluidTags.f_13131_) && !this.f_19853_.m_8055_(new BlockPos(this.m_20185_(), this.m_20188_(), this.m_20189_())).m_60713_(Blocks.f_50628_)) { +- boolean flag1 = !this.m_6040_() && !MobEffectUtil.m_19588_(this) && (!flag || !((Player)this).m_150110_().f_35934_); ++ if (!this.getEyeInFluidType().isAir() && !this.f_19853_.m_8055_(new BlockPos(this.m_20185_(), this.m_20188_(), this.m_20189_())).m_60713_(Blocks.f_50628_)) { ++ boolean flag1 = this.canDrownInFluidType(this.getEyeInFluidType()) && !MobEffectUtil.m_19588_(this) && (!flag || !((Player)this).m_150110_().f_35934_); + if (flag1) { + this.m_20301_(this.m_7302_(this.m_20146_())); + if (this.m_20146_() == -20) { +@@ -356,10 +_,11 @@ } } - if (!this.f_19853_.f_46443_ && this.m_20159_() && this.m_20202_() != null && !this.m_20202_().m_6146_()) { -+ if (!this.f_19853_.f_46443_ && this.m_20159_() && this.m_20202_() != null && !this.m_20202_().canBeRiddenInWater(this)) { ++ if (!this.f_19853_.f_46443_ && this.m_20159_() && this.m_20202_() != null && !this.m_20202_().canBeRiddenUnderFluidType(this.getEyeInFluidType(), this)) { this.m_8127_(); } - } else if (this.m_20146_() < this.m_6062_()) { +- } else if (this.m_20146_() < this.m_6062_()) { ++ } // Forge: Move to if statement since we need to increase the air supply if in bubble column or can't drown ++ if (this.m_20146_() < this.m_6062_() && (!this.canDrownInFluidType(this.getEyeInFluidType()) || this.f_19853_.m_8055_(new BlockPos(this.m_20185_(), this.m_20188_(), this.m_20189_())).m_60713_(Blocks.f_50628_))) { + this.m_20301_(this.m_7305_(this.m_20146_())); + } + +@@ -372,7 +_,7 @@ + } + } + +- if (this.m_6084_() && (this.m_20071_() || this.f_146808_)) { ++ if (this.m_6084_() && (this.m_20071_() || this.f_146808_ || this.isInFluidType((fluidType, height) -> this.canFluidExtinguish(fluidType)))) { + if (!this.f_19853_.f_46443_ && this.f_146810_) { + this.m_146873_(); + } @@ -740,7 +_,7 @@ if (!mobeffectinstance.m_19552_(this, () -> { this.m_141973_(mobeffectinstance, true, (Entity)null); @@ -350,25 +388,27 @@ if (!this.f_20911_ || this.f_20913_ >= this.m_21304_() / 2 || this.f_20913_ < 0) { this.f_20913_ = -1; this.f_20911_ = true; -@@ -1972,14 +_,15 @@ +@@ -1972,14 +_,17 @@ } this.f_19812_ = true; + net.minecraftforge.common.ForgeHooks.onLivingJump(this); } ++ @Deprecated // FORGE: use sinkInFluid instead protected void m_21208_() { - this.m_20256_(this.m_20184_().m_82520_(0.0D, (double)-0.04F, 0.0D)); -+ this.m_20256_(this.m_20184_().m_82520_(0.0D, (double)-0.04F * this.m_21051_(net.minecraftforge.common.ForgeMod.SWIM_SPEED.get()).m_22135_(), 0.0D)); ++ this.sinkInFluid(net.minecraftforge.common.ForgeMod.WATER_TYPE.get()); } ++ @Deprecated // FORGE: use jumpInFluid instead protected void m_203347_(TagKey p_204043_) { - this.m_20256_(this.m_20184_().m_82520_(0.0D, (double)0.04F, 0.0D)); + this.m_20256_(this.m_20184_().m_82520_(0.0D, (double)0.04F * this.m_21051_(net.minecraftforge.common.ForgeMod.SWIM_SPEED.get()).m_22135_(), 0.0D)); } protected float m_6108_() { -@@ -1993,11 +_,15 @@ +@@ -1993,14 +_,19 @@ public void m_7023_(Vec3 p_21280_) { if (this.m_6142_() || this.m_6109_()) { double d0 = 0.08D; @@ -384,7 +424,12 @@ + d0 = gravity.m_22135_(); FluidState fluidstate = this.f_19853_.m_6425_(this.m_20183_()); - if (this.m_20069_() && this.m_6129_() && !this.m_203441_(fluidstate)) { +- if (this.m_20069_() && this.m_6129_() && !this.m_203441_(fluidstate)) { ++ if ((this.m_20069_() || (this.isInFluidType(fluidstate) && fluidstate.getFluidType() != net.minecraftforge.common.ForgeMod.LAVA_TYPE.get())) && this.m_6129_() && !this.m_203441_(fluidstate)) { ++ if (this.m_20069_() || (this.isInFluidType(fluidstate) && !this.moveInFluid(fluidstate, p_21280_, d0))) { + double d9 = this.m_20186_(); + float f4 = this.m_20142_() ? 0.9F : this.m_6108_(); + float f5 = 0.02F; @@ -2022,6 +_,7 @@ f4 = 0.96F; } @@ -393,6 +438,14 @@ this.m_19920_(f5, p_21280_); this.m_6478_(MoverType.SELF, this.m_20184_()); Vec3 vec36 = this.m_20184_(); +@@ -2035,6 +_,7 @@ + if (this.f_19862_ && this.m_20229_(vec32.f_82479_, vec32.f_82480_ + (double)0.6F - this.m_20186_() + d9, vec32.f_82481_)) { + this.m_20334_(vec32.f_82479_, (double)0.3F, vec32.f_82481_); + } ++ } + } else if (this.m_20077_() && this.m_6129_() && !this.m_203441_(fluidstate)) { + double d8 = this.m_20186_(); + this.m_19920_(0.02F, p_21280_); @@ -2100,7 +_,7 @@ } } else { @@ -427,6 +480,35 @@ if (map == null) { map = Maps.newEnumMap(EquipmentSlot.class); } +@@ -2502,6 +_,8 @@ + this.f_19853_.m_46473_().m_6180_("jump"); + if (this.f_20899_ && this.m_6129_()) { + double d7; ++ net.minecraftforge.fluids.FluidType fluidType = this.getMaxHeightFluidType(); ++ if (!fluidType.isAir()) d7 = this.getFluidTypeHeight(fluidType); + if (this.m_20077_()) { + d7 = this.m_204036_(FluidTags.f_13132_); + } else { +@@ -2512,15 +_,17 @@ + double d8 = this.m_20204_(); + if (!flag1 || this.f_19861_ && !(d7 > d8)) { + if (!this.m_20077_() || this.f_19861_ && !(d7 > d8)) { ++ if (fluidType.isAir() || this.f_19861_ && !(d7 > d8)) { + if ((this.f_19861_ || flag1 && d7 <= d8) && this.f_20954_ == 0) { + this.m_6135_(); + this.f_20954_ = 10; + } ++ } else this.jumpInFluid(fluidType); + } else { +- this.m_203347_(FluidTags.f_13132_); ++ this.jumpInFluid(net.minecraftforge.common.ForgeMod.LAVA_TYPE.get()); + } + } else { +- this.m_203347_(FluidTags.f_13131_); ++ this.jumpInFluid(net.minecraftforge.common.ForgeMod.WATER_TYPE.get()); + } + } else { + this.f_20954_ = 0; @@ -2575,6 +_,8 @@ boolean flag = this.m_20291_(7); if (flag && !this.f_19861_ && !this.m_20159_() && !this.m_21023_(MobEffects.f_19620_)) { diff --git a/patches/minecraft/net/minecraft/world/entity/Mob.java.patch b/patches/minecraft/net/minecraft/world/entity/Mob.java.patch index 81f0bba7c8..71fce5b7df 100644 --- a/patches/minecraft/net/minecraft/world/entity/Mob.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/Mob.java.patch @@ -34,3 +34,28 @@ if (entity != null) { double d0 = entity.m_20280_(this); int i = this.m_6095_().m_20674_().m_21611_(); +@@ -1374,13 +_,23 @@ + return false; + } + ++ @Deprecated // FORGE: use jumpInFluid instead + protected void m_203347_(TagKey p_204045_) { ++ this.jumpInLiquidInternal(() -> super.m_203347_(p_204045_)); ++ } ++ ++ private void jumpInLiquidInternal(Runnable onSuper) { + if (this.m_21573_().m_26576_()) { +- super.m_203347_(p_204045_); ++ onSuper.run(); + } else { + this.m_20256_(this.m_20184_().m_82520_(0.0D, 0.3D, 0.0D)); + } + ++ } ++ ++ @Override ++ public void jumpInFluid(net.minecraftforge.fluids.FluidType type) { ++ this.jumpInLiquidInternal(() -> super.jumpInFluid(type)); + } + + public void m_147272_() { diff --git a/patches/minecraft/net/minecraft/world/entity/ai/behavior/Swim.java.patch b/patches/minecraft/net/minecraft/world/entity/ai/behavior/Swim.java.patch new file mode 100644 index 0000000000..5fd5bd89e7 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/entity/ai/behavior/Swim.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/behavior/Swim.java ++++ b/net/minecraft/world/entity/ai/behavior/Swim.java +@@ -14,7 +_,7 @@ + } + + protected boolean m_6114_(ServerLevel p_24388_, Mob p_24389_) { +- return p_24389_.m_20069_() && p_24389_.m_204036_(FluidTags.f_13131_) > p_24389_.m_20204_() || p_24389_.m_20077_(); ++ return p_24389_.m_20069_() && p_24389_.m_204036_(FluidTags.f_13131_) > p_24389_.m_20204_() || p_24389_.m_20077_() || p_24389_.isInFluidType((fluidType, height) -> p_24389_.canSwimInFluidType(fluidType) && height > p_24389_.m_20204_()); + } + + protected boolean m_6737_(ServerLevel p_24391_, Mob p_24392_, long p_24393_) { diff --git a/patches/minecraft/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch b/patches/minecraft/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch new file mode 100644 index 0000000000..422d6d7cfd --- /dev/null +++ b/patches/minecraft/net/minecraft/world/entity/ai/goal/FloatGoal.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/ai/goal/FloatGoal.java ++++ b/net/minecraft/world/entity/ai/goal/FloatGoal.java +@@ -14,7 +_,7 @@ + } + + public boolean m_8036_() { +- return this.f_25228_.m_20069_() && this.f_25228_.m_204036_(FluidTags.f_13131_) > this.f_25228_.m_20204_() || this.f_25228_.m_20077_(); ++ return this.f_25228_.m_20069_() && this.f_25228_.m_204036_(FluidTags.f_13131_) > this.f_25228_.m_20204_() || this.f_25228_.m_20077_() || this.f_25228_.isInFluidType((fluidType, height) -> this.f_25228_.canSwimInFluidType(fluidType) && height > this.f_25228_.m_20204_()); + } + + public boolean m_183429_() { diff --git a/patches/minecraft/net/minecraft/world/entity/animal/Bee.java.patch b/patches/minecraft/net/minecraft/world/entity/animal/Bee.java.patch index 24de028677..d932f5221b 100644 --- a/patches/minecraft/net/minecraft/world/entity/animal/Bee.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/animal/Bee.java.patch @@ -9,3 +9,22 @@ } } +@@ -614,8 +_,18 @@ + return MobType.f_21642_; + } + ++ @Deprecated // FORGE: use jumpInFluid instead + protected void m_203347_(TagKey p_204061_) { ++ this.jumpInLiquidInternal(); ++ } ++ ++ private void jumpInLiquidInternal() { + this.m_20256_(this.m_20184_().m_82520_(0.0D, 0.01D, 0.0D)); ++ } ++ ++ @Override ++ public void jumpInFluid(net.minecraftforge.fluids.FluidType type) { ++ this.jumpInLiquidInternal(); + } + + public Vec3 m_7939_() { diff --git a/patches/minecraft/net/minecraft/world/entity/animal/Fox.java.patch b/patches/minecraft/net/minecraft/world/entity/animal/Fox.java.patch index 8d79817b09..59518a66dd 100644 --- a/patches/minecraft/net/minecraft/world/entity/animal/Fox.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/animal/Fox.java.patch @@ -45,3 +45,12 @@ BlockState blockstate = Fox.this.f_19853_.m_8055_(this.f_25602_); if (blockstate.m_60713_(Blocks.f_50685_)) { this.m_148928_(blockstate); +@@ -930,7 +_,7 @@ + } + + public boolean m_8036_() { +- return Fox.this.m_20069_() && Fox.this.m_204036_(FluidTags.f_13131_) > 0.25D || Fox.this.m_20077_(); ++ return Fox.this.m_20069_() && Fox.this.m_204036_(FluidTags.f_13131_) > 0.25D || Fox.this.m_20077_() || Fox.this.isInFluidType((fluidType, height) -> Fox.this.canSwimInFluidType(fluidType) && height > 0.25D); + } + } + diff --git a/patches/minecraft/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/patches/minecraft/net/minecraft/world/entity/item/FallingBlockEntity.java.patch new file mode 100644 index 0000000000..4abc29c862 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/entity/item/FallingBlockEntity.java.patch @@ -0,0 +1,16 @@ +--- a/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -117,11 +_,11 @@ + if (!this.f_19853_.f_46443_) { + BlockPos blockpos = this.m_20183_(); + boolean flag = this.f_31946_.m_60734_() instanceof ConcretePowderBlock; +- boolean flag1 = flag && this.f_19853_.m_6425_(blockpos).m_205070_(FluidTags.f_13131_); ++ boolean flag1 = flag && this.f_31946_.canBeHydrated(this.f_19853_, blockpos, this.f_19853_.m_6425_(blockpos), blockpos); + double d0 = this.m_20184_().m_82556_(); + if (flag && d0 > 1.0D) { + BlockHitResult blockhitresult = this.f_19853_.m_45547_(new ClipContext(new Vec3(this.f_19854_, this.f_19855_, this.f_19856_), this.m_20182_(), ClipContext.Block.COLLIDER, ClipContext.Fluid.SOURCE_ONLY, this)); +- if (blockhitresult.m_6662_() != HitResult.Type.MISS && this.f_19853_.m_6425_(blockhitresult.m_82425_()).m_205070_(FluidTags.f_13131_)) { ++ if (blockhitresult.m_6662_() != HitResult.Type.MISS && this.f_31946_.canBeHydrated(this.f_19853_, blockpos, this.f_19853_.m_6425_(blockhitresult.m_82425_()), blockhitresult.m_82425_())) { + blockpos = blockhitresult.m_82425_(); + flag1 = true; + } diff --git a/patches/minecraft/net/minecraft/world/entity/item/ItemEntity.java.patch b/patches/minecraft/net/minecraft/world/entity/item/ItemEntity.java.patch index 521b5b34fe..3d5037bdd7 100644 --- a/patches/minecraft/net/minecraft/world/entity/item/ItemEntity.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/item/ItemEntity.java.patch @@ -27,6 +27,15 @@ if (this.m_32055_().m_41619_()) { this.m_146870_(); } else { +@@ -100,6 +_,8 @@ + this.f_19856_ = this.m_20189_(); + Vec3 vec3 = this.m_20184_(); + float f = this.m_20192_() - 0.11111111F; ++ net.minecraftforge.fluids.FluidType fluidType = this.getMaxHeightFluidType(); ++ if (!fluidType.isAir() && !fluidType.isVanilla() && this.getFluidTypeHeight(fluidType) > (double)f) fluidType.setItemMovement(this); + if (this.m_20069_() && this.m_204036_(FluidTags.f_13131_) > (double)f) { + this.m_32067_(); + } else if (this.m_20077_() && this.m_204036_(FluidTags.f_13132_) > (double)f) { @@ -121,7 +_,7 @@ this.m_6478_(MoverType.SELF, this.m_20184_()); float f1 = 0.98F; diff --git a/patches/minecraft/net/minecraft/world/entity/monster/MagmaCube.java.patch b/patches/minecraft/net/minecraft/world/entity/monster/MagmaCube.java.patch index 278cdeb3a0..ac7453dd05 100644 --- a/patches/minecraft/net/minecraft/world/entity/monster/MagmaCube.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/monster/MagmaCube.java.patch @@ -1,10 +1,33 @@ --- a/net/minecraft/world/entity/monster/MagmaCube.java +++ b/net/minecraft/world/entity/monster/MagmaCube.java -@@ -66,6 +_,7 @@ +@@ -66,17 +_,28 @@ Vec3 vec3 = this.m_20184_(); this.m_20334_(vec3.f_82479_, (double)(this.m_6118_() + (float)this.m_33632_() * 0.1F), vec3.f_82481_); this.f_19812_ = true; + net.minecraftforge.common.ForgeHooks.onLivingJump(this); } ++ @Deprecated // FORGE: use jumpInFluid instead protected void m_203347_(TagKey p_204065_) { +- if (p_204065_ == FluidTags.f_13132_) { ++ this.jumpInLiquidInternal(() -> p_204065_ == FluidTags.f_13132_, () -> super.m_203347_(p_204065_)); ++ } ++ ++ private void jumpInLiquidInternal(java.util.function.BooleanSupplier isLava, Runnable onSuper) { ++ if (isLava.getAsBoolean()) { + Vec3 vec3 = this.m_20184_(); + this.m_20334_(vec3.f_82479_, (double)(0.22F + (float)this.m_33632_() * 0.05F), vec3.f_82481_); + this.f_19812_ = true; + } else { +- super.m_203347_(p_204065_); ++ onSuper.run(); + } + ++ } ++ ++ @Override ++ public void jumpInFluid(net.minecraftforge.fluids.FluidType type) { ++ this.jumpInLiquidInternal(() -> type == net.minecraftforge.common.ForgeMod.LAVA_TYPE.get(), () -> super.jumpInFluid(type)); + } + + public boolean m_142535_(float p_149717_, float p_149718_, DamageSource p_149719_) { diff --git a/patches/minecraft/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/patches/minecraft/net/minecraft/world/entity/projectile/AbstractArrow.java.patch index 2ad8b0eacd..8ec30f2350 100644 --- a/patches/minecraft/net/minecraft/world/entity/projectile/AbstractArrow.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/projectile/AbstractArrow.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/world/entity/projectile/AbstractArrow.java +++ b/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -149,7 +_,7 @@ + --this.f_36706_; + } + +- if (this.m_20070_() || blockstate.m_60713_(Blocks.f_152499_)) { ++ if (this.m_20070_() || blockstate.m_60713_(Blocks.f_152499_) || this.isInFluidType((fluidType, height) -> this.canFluidExtinguish(fluidType))) { + this.m_20095_(); + } + @@ -185,7 +_,7 @@ } } diff --git a/patches/minecraft/net/minecraft/world/entity/vehicle/Boat.java.patch b/patches/minecraft/net/minecraft/world/entity/vehicle/Boat.java.patch index 8c0519eb6b..e82d69c7dd 100644 --- a/patches/minecraft/net/minecraft/world/entity/vehicle/Boat.java.patch +++ b/patches/minecraft/net/minecraft/world/entity/vehicle/Boat.java.patch @@ -1,5 +1,23 @@ --- a/net/minecraft/world/entity/vehicle/Boat.java +++ b/net/minecraft/world/entity/vehicle/Boat.java +@@ -48,7 +_,7 @@ + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + +-public class Boat extends Entity { ++public class Boat extends Entity implements net.minecraftforge.common.extensions.IForgeBoat { + private static final EntityDataAccessor f_38282_ = SynchedEntityData.m_135353_(Boat.class, EntityDataSerializers.f_135028_); + private static final EntityDataAccessor f_38283_ = SynchedEntityData.m_135353_(Boat.class, EntityDataSerializers.f_135028_); + private static final EntityDataAccessor f_38284_ = SynchedEntityData.m_135353_(Boat.class, EntityDataSerializers.f_135029_); +@@ -439,7 +_,7 @@ + for(int i2 = i1; i2 < j1; ++i2) { + blockpos$mutableblockpos.m_122178_(l1, k1, i2); + FluidState fluidstate = this.f_19853_.m_6425_(blockpos$mutableblockpos); +- if (fluidstate.m_205070_(FluidTags.f_13131_)) { ++ if (this.canBoatInFluid(fluidstate)) { + f = Math.max(f, fluidstate.m_76155_(this.f_19853_, blockpos$mutableblockpos)); + } + @@ -480,7 +_,7 @@ blockpos$mutableblockpos.m_122178_(l1, k2, i2); BlockState blockstate = this.f_19853_.m_8055_(blockpos$mutableblockpos); @@ -9,6 +27,42 @@ ++k1; } } +@@ -509,7 +_,7 @@ + for(int i2 = i1; i2 < j1; ++i2) { + blockpos$mutableblockpos.m_122178_(k1, l1, i2); + FluidState fluidstate = this.f_19853_.m_6425_(blockpos$mutableblockpos); +- if (fluidstate.m_205070_(FluidTags.f_13131_)) { ++ if (this.canBoatInFluid(fluidstate)) { + float f = (float)l1 + fluidstate.m_76155_(this.f_19853_, blockpos$mutableblockpos); + this.f_38277_ = Math.max((double)f, this.f_38277_); + flag |= aabb.f_82289_ < (double)f; +@@ -539,7 +_,7 @@ + for(int i2 = i1; i2 < j1; ++i2) { + blockpos$mutableblockpos.m_122178_(k1, l1, i2); + FluidState fluidstate = this.f_19853_.m_6425_(blockpos$mutableblockpos); +- if (fluidstate.m_205070_(FluidTags.f_13131_) && d0 < (double)((float)blockpos$mutableblockpos.m_123342_() + fluidstate.m_76155_(this.f_19853_, blockpos$mutableblockpos))) { ++ if (this.canBoatInFluid(fluidstate) && d0 < (double)((float)blockpos$mutableblockpos.m_123342_() + fluidstate.m_76155_(this.f_19853_, blockpos$mutableblockpos))) { + if (!fluidstate.m_76170_()) { + return Boat.Status.UNDER_FLOWING_WATER; + } +@@ -753,7 +_,7 @@ + } + + this.m_183634_(); +- } else if (!this.f_19853_.m_6425_(this.m_20183_().m_7495_()).m_205070_(FluidTags.f_13131_) && p_38307_ < 0.0D) { ++ } else if (!this.canBoatInFluid(this.f_19853_.m_6425_(this.m_20183_().m_7495_())) && p_38307_ < 0.0D) { + this.f_19789_ -= (float)p_38307_; + } + +@@ -809,7 +_,7 @@ + } + + protected boolean m_7310_(Entity p_38390_) { +- return this.m_20197_().size() < this.m_213801_() && !this.m_204029_(FluidTags.f_13131_); ++ return this.m_20197_().size() < this.m_213801_() && !this.canBoatInFluid(this.getEyeInFluidType()); + } + + protected int m_213801_() { @@ -834,6 +_,16 @@ public boolean m_5842_() { diff --git a/patches/minecraft/net/minecraft/world/item/BucketItem.java.patch b/patches/minecraft/net/minecraft/world/item/BucketItem.java.patch index 9634ac5770..bdc897f6b6 100644 --- a/patches/minecraft/net/minecraft/world/item/BucketItem.java.patch +++ b/patches/minecraft/net/minecraft/world/item/BucketItem.java.patch @@ -61,7 +61,7 @@ protected void m_7718_(@Nullable Player p_40696_, LevelAccessor p_40697_, BlockPos p_40698_) { - SoundEvent soundevent = this.f_40687_.m_205067_(FluidTags.f_13132_) ? SoundEvents.f_11780_ : SoundEvents.f_11778_; -+ SoundEvent soundevent = this.f_40687_.getAttributes().getEmptySound(); ++ SoundEvent soundevent = this.f_40687_.getFluidType().getSound(p_40696_, p_40697_, p_40698_, net.minecraftforge.common.SoundActions.BUCKET_EMPTY); + if(soundevent == null) soundevent = this.f_40687_.m_205067_(FluidTags.f_13132_) ? SoundEvents.f_11780_ : SoundEvents.f_11778_; p_40697_.m_5594_(p_40696_, p_40698_, soundevent, SoundSource.BLOCKS, 1.0F, 1.0F); p_40697_.m_142346_(p_40696_, GameEvent.f_157769_, p_40698_); diff --git a/patches/minecraft/net/minecraft/world/level/block/ConcretePowderBlock.java.patch b/patches/minecraft/net/minecraft/world/level/block/ConcretePowderBlock.java.patch new file mode 100644 index 0000000000..5bd78f7ef9 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/level/block/ConcretePowderBlock.java.patch @@ -0,0 +1,44 @@ +--- a/net/minecraft/world/level/block/ConcretePowderBlock.java ++++ b/net/minecraft/world/level/block/ConcretePowderBlock.java +@@ -20,7 +_,7 @@ + } + + public void m_48792_(Level p_52068_, BlockPos p_52069_, BlockState p_52070_, BlockState p_52071_, FallingBlockEntity p_52072_) { +- if (m_52080_(p_52068_, p_52069_, p_52071_)) { ++ if (m_52080_(p_52068_, p_52069_, p_52070_)) { // Forge: Uses block of falling entity instead of block at current position, checked within shouldSolidify + p_52068_.m_7731_(p_52069_, this.f_52058_, 3); + } + +@@ -34,19 +_,19 @@ + } + + private static boolean m_52080_(BlockGetter p_52081_, BlockPos p_52082_, BlockState p_52083_) { +- return m_52088_(p_52083_) || m_52064_(p_52081_, p_52082_); ++ return p_52083_.canBeHydrated(p_52081_, p_52082_, p_52081_.m_6425_(p_52082_), p_52082_) || touchesLiquid(p_52081_, p_52082_, p_52083_); + } + +- private static boolean m_52064_(BlockGetter p_52065_, BlockPos p_52066_) { ++ private static boolean touchesLiquid(BlockGetter p_52065_, BlockPos p_52066_, BlockState state) { + boolean flag = false; + BlockPos.MutableBlockPos blockpos$mutableblockpos = p_52066_.m_122032_(); + + for(Direction direction : Direction.values()) { + BlockState blockstate = p_52065_.m_8055_(blockpos$mutableblockpos); +- if (direction != Direction.DOWN || m_52088_(blockstate)) { ++ if (direction != Direction.DOWN || state.canBeHydrated(p_52065_, p_52066_, blockstate.m_60819_(), blockpos$mutableblockpos)) { + blockpos$mutableblockpos.m_122159_(p_52066_, direction); + blockstate = p_52065_.m_8055_(blockpos$mutableblockpos); +- if (m_52088_(blockstate) && !blockstate.m_60783_(p_52065_, p_52066_, direction.m_122424_())) { ++ if (state.canBeHydrated(p_52065_, p_52066_, blockstate.m_60819_(), blockpos$mutableblockpos) && !blockstate.m_60783_(p_52065_, p_52066_, direction.m_122424_())) { + flag = true; + break; + } +@@ -61,7 +_,7 @@ + } + + public BlockState m_7417_(BlockState p_52074_, Direction p_52075_, BlockState p_52076_, LevelAccessor p_52077_, BlockPos p_52078_, BlockPos p_52079_) { +- return m_52064_(p_52077_, p_52078_) ? this.f_52058_ : super.m_7417_(p_52074_, p_52075_, p_52076_, p_52077_, p_52078_, p_52079_); ++ return touchesLiquid(p_52077_, p_52078_, p_52074_) ? this.f_52058_ : super.m_7417_(p_52074_, p_52075_, p_52076_, p_52077_, p_52078_, p_52079_); + } + + public int m_6248_(BlockState p_52085_, BlockGetter p_52086_, BlockPos p_52087_) { diff --git a/patches/minecraft/net/minecraft/world/level/block/CoralBlock.java.patch b/patches/minecraft/net/minecraft/world/level/block/CoralBlock.java.patch new file mode 100644 index 0000000000..e2b5eb9ed5 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/level/block/CoralBlock.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/CoralBlock.java ++++ b/net/minecraft/world/level/block/CoralBlock.java +@@ -37,9 +_,10 @@ + } + + protected boolean m_52134_(BlockGetter p_52135_, BlockPos p_52136_) { ++ BlockState state = p_52135_.m_8055_(p_52136_); + for(Direction direction : Direction.values()) { + FluidState fluidstate = p_52135_.m_6425_(p_52136_.m_121945_(direction)); +- if (fluidstate.m_205070_(FluidTags.f_13131_)) { ++ if (state.canBeHydrated(p_52135_, p_52136_, fluidstate, p_52136_.m_121945_(direction))) { + return true; + } + } diff --git a/patches/minecraft/net/minecraft/world/level/block/FarmBlock.java.patch b/patches/minecraft/net/minecraft/world/level/block/FarmBlock.java.patch index 71d9a25976..4c0d964541 100644 --- a/patches/minecraft/net/minecraft/world/level/block/FarmBlock.java.patch +++ b/patches/minecraft/net/minecraft/world/level/block/FarmBlock.java.patch @@ -9,7 +9,7 @@ m_53296_(p_153228_, p_153227_, p_153229_); } -@@ -93,8 +_,9 @@ +@@ -93,18 +_,20 @@ } private static boolean m_53250_(BlockGetter p_53251_, BlockPos p_53252_) { @@ -21,7 +21,11 @@ } private static boolean m_53258_(LevelReader p_53259_, BlockPos p_53260_) { -@@ -104,7 +_,7 @@ ++ BlockState state = p_53259_.m_8055_(p_53260_); + for(BlockPos blockpos : BlockPos.m_121940_(p_53260_.m_7918_(-4, 0, -4), p_53260_.m_7918_(4, 1, 4))) { +- if (p_53259_.m_6425_(blockpos).m_205070_(FluidTags.f_13131_)) { ++ if (state.canBeHydrated(p_53259_, p_53260_, p_53259_.m_6425_(blockpos), blockpos)) { + return true; } } diff --git a/patches/minecraft/net/minecraft/world/level/block/LiquidBlock.java.patch b/patches/minecraft/net/minecraft/world/level/block/LiquidBlock.java.patch index b64a788004..6b70fcb189 100644 --- a/patches/minecraft/net/minecraft/world/level/block/LiquidBlock.java.patch +++ b/patches/minecraft/net/minecraft/world/level/block/LiquidBlock.java.patch @@ -43,22 +43,30 @@ return this.f_54691_.get(Math.min(i, 8)); } -@@ -120,13 +_,13 @@ - BlockPos blockpos = p_54698_.m_121945_(direction.m_122424_()); - if (p_54697_.m_6425_(blockpos).m_205070_(FluidTags.f_13131_)) { - Block block = p_54697_.m_6425_(p_54698_).m_76170_() ? Blocks.f_50080_ : Blocks.f_50652_; -- p_54697_.m_46597_(p_54698_, block.m_49966_()); -+ p_54697_.m_46597_(p_54698_, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(p_54697_, p_54698_, p_54698_, block.m_49966_())); - this.m_54700_(p_54697_, p_54698_); - return false; - } +@@ -91,7 +_,7 @@ + } - if (flag && p_54697_.m_8055_(blockpos).m_60713_(Blocks.f_50568_)) { -- p_54697_.m_46597_(p_54698_, Blocks.f_50137_.m_49966_()); -+ p_54697_.m_46597_(p_54698_, net.minecraftforge.event.ForgeEventFactory.fireFluidPlaceBlockEvent(p_54697_, p_54698_, p_54698_, Blocks.f_50137_.m_49966_())); - this.m_54700_(p_54697_, p_54698_); - return false; - } + public void m_6807_(BlockState p_54754_, Level p_54755_, BlockPos p_54756_, BlockState p_54757_, boolean p_54758_) { +- if (this.m_54696_(p_54755_, p_54756_, p_54754_)) { ++ if (!net.minecraftforge.fluids.FluidInteractionRegistry.canInteract(p_54755_, p_54756_)) { + p_54755_.m_186469_(p_54756_, p_54754_.m_60819_().m_76152_(), this.f_54689_.m_6718_(p_54755_)); + } + +@@ -106,12 +_,13 @@ + } + + public void m_6861_(BlockState p_54709_, Level p_54710_, BlockPos p_54711_, Block p_54712_, BlockPos p_54713_, boolean p_54714_) { +- if (this.m_54696_(p_54710_, p_54711_, p_54709_)) { ++ if (!net.minecraftforge.fluids.FluidInteractionRegistry.canInteract(p_54710_, p_54711_)) { + p_54710_.m_186469_(p_54711_, p_54709_.m_60819_().m_76152_(), this.f_54689_.m_6718_(p_54710_)); + } + + } + ++ @Deprecated // FORGE: Use FluidInteractionRegistry#canInteract instead + private boolean m_54696_(Level p_54697_, BlockPos p_54698_, BlockState p_54699_) { + if (this.f_54689_.m_205067_(FluidTags.f_13132_)) { + boolean flag = p_54697_.m_8055_(p_54698_.m_7495_()).m_60713_(Blocks.f_50136_); @@ -150,6 +_,25 @@ return new ItemStack(this.f_54689_.m_6859_()); } else { diff --git a/patches/minecraft/net/minecraft/world/level/block/MagmaBlock.java.patch b/patches/minecraft/net/minecraft/world/level/block/MagmaBlock.java.patch new file mode 100644 index 0000000000..a98781f495 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/level/block/MagmaBlock.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/block/MagmaBlock.java ++++ b/net/minecraft/world/level/block/MagmaBlock.java +@@ -47,7 +_,7 @@ + + public void m_213898_(BlockState p_221420_, ServerLevel p_221421_, BlockPos p_221422_, RandomSource p_221423_) { + BlockPos blockpos = p_221422_.m_7494_(); +- if (p_221421_.m_6425_(p_221422_).m_205070_(FluidTags.f_13131_)) { ++ if (p_221421_.m_6425_(p_221422_).canExtinguish(p_221421_, p_221422_)) { + p_221421_.m_5594_((Player)null, p_221422_, SoundEvents.f_11937_, SoundSource.BLOCKS, 0.5F, 2.6F + (p_221421_.f_46441_.m_188501_() - p_221421_.f_46441_.m_188501_()) * 0.8F); + p_221421_.m_8767_(ParticleTypes.f_123755_, (double)blockpos.m_123341_() + 0.5D, (double)blockpos.m_123342_() + 0.25D, (double)blockpos.m_123343_() + 0.5D, 8, 0.5D, 0.25D, 0.5D, 0.0D); + } diff --git a/patches/minecraft/net/minecraft/world/level/block/SpongeBlock.java.patch b/patches/minecraft/net/minecraft/world/level/block/SpongeBlock.java.patch new file mode 100644 index 0000000000..1a4fdf7d5b --- /dev/null +++ b/patches/minecraft/net/minecraft/world/level/block/SpongeBlock.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/level/block/SpongeBlock.java ++++ b/net/minecraft/world/level/block/SpongeBlock.java +@@ -44,6 +_,7 @@ + Queue> queue = Lists.newLinkedList(); + queue.add(new Tuple<>(p_56809_, 0)); + int i = 0; ++ BlockState state = p_56808_.m_8055_(p_56809_); + + while(!queue.isEmpty()) { + Tuple tuple = queue.poll(); +@@ -55,7 +_,7 @@ + BlockState blockstate = p_56808_.m_8055_(blockpos1); + FluidState fluidstate = p_56808_.m_6425_(blockpos1); + Material material = blockstate.m_60767_(); +- if (fluidstate.m_205070_(FluidTags.f_13131_)) { ++ if (state.canBeHydrated(p_56808_, p_56809_, fluidstate, blockpos1)) { + if (blockstate.m_60734_() instanceof BucketPickup && !((BucketPickup)blockstate.m_60734_()).m_142598_(p_56808_, blockpos1, blockstate).m_41619_()) { + ++i; + if (j < 6) { diff --git a/patches/minecraft/net/minecraft/world/level/block/SugarCaneBlock.java.patch b/patches/minecraft/net/minecraft/world/level/block/SugarCaneBlock.java.patch index 574fee5611..f53079b25f 100644 --- a/patches/minecraft/net/minecraft/world/level/block/SugarCaneBlock.java.patch +++ b/patches/minecraft/net/minecraft/world/level/block/SugarCaneBlock.java.patch @@ -33,6 +33,15 @@ BlockState blockstate = p_57176_.m_8055_(p_57177_.m_7495_()); if (blockstate.m_60713_(this)) { return true; +@@ -77,7 +_,7 @@ + for(Direction direction : Direction.Plane.HORIZONTAL) { + BlockState blockstate1 = p_57176_.m_8055_(blockpos.m_121945_(direction)); + FluidState fluidstate = p_57176_.m_6425_(blockpos.m_121945_(direction)); +- if (fluidstate.m_205070_(FluidTags.f_13131_) || blockstate1.m_60713_(Blocks.f_50449_)) { ++ if (p_57175_.canBeHydrated(p_57176_, p_57177_, fluidstate, blockpos.m_121945_(direction)) || blockstate1.m_60713_(Blocks.f_50449_)) { + return true; + } + } @@ -89,5 +_,15 @@ protected void m_7926_(StateDefinition.Builder p_57186_) { diff --git a/patches/minecraft/net/minecraft/world/level/material/FlowingFluid.java.patch b/patches/minecraft/net/minecraft/world/level/material/FlowingFluid.java.patch index 117f645e97..5daadbdec9 100644 --- a/patches/minecraft/net/minecraft/world/level/material/FlowingFluid.java.patch +++ b/patches/minecraft/net/minecraft/world/level/material/FlowingFluid.java.patch @@ -5,7 +5,7 @@ FluidState fluidstate = blockstate.m_60819_(); if (fluidstate.m_76152_().m_6212_(this) && this.m_76061_(direction, p_76036_, p_76037_, p_76038_, blockpos, blockstate)) { - if (fluidstate.m_76170_()) { -+ if (fluidstate.m_76170_() && net.minecraftforge.event.ForgeEventFactory.canCreateFluidSource(p_76036_, blockpos, blockstate, this.m_6760_())) { ++ if (fluidstate.m_76170_() && net.minecraftforge.event.ForgeEventFactory.canCreateFluidSource(p_76036_, blockpos, blockstate, fluidstate.canConvertToSource(p_76036_, blockpos))) { ++j; } @@ -18,3 +18,16 @@ BlockState blockstate1 = p_76036_.m_8055_(p_76037_.m_7495_()); FluidState fluidstate1 = blockstate1.m_60819_(); if (blockstate1.m_60767_().m_76333_() || this.m_76096_(fluidstate1)) { +@@ -230,6 +_,12 @@ + return this.m_5613_().m_76145_().m_61124_(f_75947_, Boolean.valueOf(p_76069_)); + } + ++ @Override ++ public boolean canConvertToSource(FluidState state, LevelReader reader, BlockPos pos) { ++ return this.m_6760_(); ++ } ++ ++ @Deprecated //FORGE: Use state and level sensitive version instead + protected abstract boolean m_6760_(); + + protected void m_6364_(LevelAccessor p_76005_, BlockPos p_76006_, BlockState p_76007_, Direction p_76008_, FluidState p_76009_) { diff --git a/patches/minecraft/net/minecraft/world/level/material/Fluid.java.patch b/patches/minecraft/net/minecraft/world/level/material/Fluid.java.patch index cbe1d386cc..f39975d1c1 100644 --- a/patches/minecraft/net/minecraft/world/level/material/Fluid.java.patch +++ b/patches/minecraft/net/minecraft/world/level/material/Fluid.java.patch @@ -9,25 +9,16 @@ public static final IdMapper f_76104_ = new IdMapper<>(); protected final StateDefinition f_76105_; private FluidState f_76103_; -@@ -101,6 +_,22 @@ +@@ -101,6 +_,13 @@ } public abstract VoxelShape m_7999_(FluidState p_76137_, BlockGetter p_76138_, BlockPos p_76139_); + -+ /** -+ * Creates the fluid attributes object, which wilAl contain all the extended values for the fluid that aren't part of the vanilla system. -+ * Do not call this from outside. To retrieve the values use {@link Fluid#getAttributes()} -+ */ -+ protected net.minecraftforge.fluids.FluidAttributes createAttributes() -+ { -+ return net.minecraftforge.common.ForgeHooks.createVanillaFluidAttributes(this); -+ } -+ -+ private net.minecraftforge.fluids.FluidAttributes forgeFluidAttributes; -+ public final net.minecraftforge.fluids.FluidAttributes getAttributes() { -+ if (forgeFluidAttributes == null) -+ forgeFluidAttributes = createAttributes(); -+ return forgeFluidAttributes; ++ private net.minecraftforge.fluids.FluidType forgeFluidType; ++ @Override ++ public net.minecraftforge.fluids.FluidType getFluidType() { ++ if (forgeFluidType == null) forgeFluidType = net.minecraftforge.common.ForgeHooks.getVanillaFluidType(this); ++ return forgeFluidType; + } public Optional m_142520_() { diff --git a/patches/minecraft/net/minecraft/world/level/pathfinder/BlockPathTypes.java.patch b/patches/minecraft/net/minecraft/world/level/pathfinder/BlockPathTypes.java.patch index 027d26c79c..684c57c414 100644 --- a/patches/minecraft/net/minecraft/world/level/pathfinder/BlockPathTypes.java.patch +++ b/patches/minecraft/net/minecraft/world/level/pathfinder/BlockPathTypes.java.patch @@ -1,9 +1,18 @@ --- a/net/minecraft/world/level/pathfinder/BlockPathTypes.java +++ b/net/minecraft/world/level/pathfinder/BlockPathTypes.java -@@ -37,4 +_,13 @@ +@@ -1,6 +_,6 @@ + package net.minecraft.world.level.pathfinder; + +-public enum BlockPathTypes { ++public enum BlockPathTypes implements net.minecraftforge.common.IExtensibleEnum { + BLOCKED(-1.0F), + OPEN(0.0F), + WALKABLE(0.0F), +@@ -36,5 +_,18 @@ + public float m_77124_() { return this.f_77117_; - } ++ } + + @org.jetbrains.annotations.Nullable + public BlockPathTypes getDanger() { @@ -13,4 +22,8 @@ + (this == LAVA) ? DAMAGE_FIRE : + null; + } ++ ++ public static BlockPathTypes create(String name, float malus) { ++ throw new IllegalArgumentException("Enum not extended"); + } } diff --git a/patches/minecraft/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch b/patches/minecraft/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch index 0477b7ad55..cc2cc9b94a 100644 --- a/patches/minecraft/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch +++ b/patches/minecraft/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java.patch @@ -9,12 +9,42 @@ } double d0 = this.m_142213_(new BlockPos(p_77641_.f_77271_, p_77641_.f_77272_, p_77641_.f_77273_)); +@@ -471,6 +_,11 @@ + if (l != 0 || j1 != 0) { + p_77609_.m_122178_(i + l, j + i1, k + j1); + BlockState blockstate = p_77608_.m_8055_(p_77609_); ++ BlockPathTypes blockPathType = blockstate.getAdjacentBlockPathType(p_77608_, p_77609_, null, p_77610_); ++ if (blockPathType != null) return blockPathType; ++ FluidState fluidState = blockstate.m_60819_(); ++ BlockPathTypes fluidPathType = fluidState.getAdjacentBlockPathType(p_77608_, p_77609_, null, p_77610_); ++ if (fluidPathType != null) return fluidPathType; + if (blockstate.m_60713_(Blocks.f_50128_)) { + return BlockPathTypes.DANGER_CACTUS; + } @@ -496,6 +_,8 @@ protected static BlockPathTypes m_77643_(BlockGetter p_77644_, BlockPos p_77645_) { BlockState blockstate = p_77644_.m_8055_(p_77645_); -+ BlockPathTypes type = blockstate.getBlockPathType(p_77644_, p_77645_); ++ BlockPathTypes type = blockstate.getBlockPathType(p_77644_, p_77645_, null); + if (type != null) return type; Block block = blockstate.m_60734_(); Material material = blockstate.m_60767_(); if (blockstate.m_60795_()) { +@@ -513,6 +_,8 @@ + return BlockPathTypes.COCOA; + } else { + FluidState fluidstate = p_77644_.m_6425_(p_77645_); ++ BlockPathTypes nonLoggableFluidPathType = fluidstate.getBlockPathType(p_77644_, p_77645_, null, false); ++ if (nonLoggableFluidPathType != null) return nonLoggableFluidPathType; + if (fluidstate.m_205070_(FluidTags.f_13132_)) { + return BlockPathTypes.LAVA; + } else if (m_77622_(blockstate)) { +@@ -531,6 +_,8 @@ + if (!blockstate.m_60647_(p_77644_, p_77645_, PathComputationType.LAND)) { + return BlockPathTypes.BLOCKED; + } else { ++ BlockPathTypes loggableFluidPathType = fluidstate.getBlockPathType(p_77644_, p_77645_, null, true); ++ if (loggableFluidPathType != null) return loggableFluidPathType; + return fluidstate.m_205070_(FluidTags.f_13131_) ? BlockPathTypes.WATER : BlockPathTypes.OPEN; + } + } else { diff --git a/src/main/java/net/minecraftforge/client/FluidContainerColorer.java b/src/main/java/net/minecraftforge/client/FluidContainerColorer.java index e372f44985..65850f0971 100644 --- a/src/main/java/net/minecraftforge/client/FluidContainerColorer.java +++ b/src/main/java/net/minecraftforge/client/FluidContainerColorer.java @@ -17,7 +17,7 @@ public class FluidContainerColorer implements ItemColor { if (tintIndex != 1) return 0xFFFFFFFF; return FluidUtil.getFluidContained(stack) - .map(fstack -> fstack.getFluid().getAttributes().getColor(fstack)) + .map(fstack -> RenderProperties.get(fstack.getFluid()).getColorTint(fstack)) .orElse(0xFFFFFFFF); } } diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index 76f609e61d..744bd119ca 100755 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -383,8 +383,28 @@ public class ForgeHooksClient MinecraftForge.EVENT_BUS.post(new ScreenEvent.DrawScreenEvent.Post(screen, poseStack, mouseX, mouseY, partialTick)); } - public static void onFogRender(FogType type, Camera camera, float partialTick, float nearDistance, float farDistance, FogShape shape) + public static Vector3f getFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount, float fogRed, float fogGreen, float fogBlue) { + // Modify fog color depending on the fluid + FluidState state = level.getFluidState(camera.getBlockPosition()); + Vector3f fluidFogColor = new Vector3f(fogRed, fogGreen, fogBlue); + if (camera.getPosition().y < (double)((float)camera.getBlockPosition().getY() + state.getHeight(level, camera.getBlockPosition()))) + fluidFogColor = RenderProperties.get(state).modifyFogColor(camera, partialTick, level, renderDistance, darkenWorldAmount, fluidFogColor); + + EntityViewRenderEvent.FogColors event = new net.minecraftforge.client.event.EntityViewRenderEvent.FogColors(camera, partialTick, fluidFogColor.x(), fluidFogColor.y(), fluidFogColor.z()); + MinecraftForge.EVENT_BUS.post(event); + + fluidFogColor.set(event.getRed(), event.getGreen(), event.getBlue()); + return fluidFogColor; + } + + public static void onFogRender(FogRenderer.FogMode mode, FogType type, Camera camera, float partialTick, float renderDistance, float nearDistance, float farDistance, FogShape shape) + { + // Modify fog rendering depending on the fluid + FluidState state = camera.getEntity().level.getFluidState(camera.getBlockPosition()); + if (camera.getPosition().y < (double)((float)camera.getBlockPosition().getY() + state.getHeight(camera.getEntity().level, camera.getBlockPosition()))) + RenderProperties.get(state).modifyFogRender(camera, mode, renderDistance, partialTick, nearDistance, farDistance, shape); + EntityViewRenderEvent.RenderFogEvent event = new EntityViewRenderEvent.RenderFogEvent(type, camera, partialTick, nearDistance, farDistance, shape); if (MinecraftForge.EVENT_BUS.post(event)) { @@ -441,10 +461,11 @@ public class ForgeHooksClient @SuppressWarnings("deprecation") public static TextureAtlasSprite[] getFluidSprites(BlockAndTintGetter level, BlockPos pos, FluidState fluidStateIn) { - ResourceLocation overlayTexture = fluidStateIn.getType().getAttributes().getOverlayTexture(); + IFluidTypeRenderProperties props = RenderProperties.get(fluidStateIn); + ResourceLocation overlayTexture = props.getOverlayTexture(fluidStateIn, level, pos); return new TextureAtlasSprite[] { - Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(fluidStateIn.getType().getAttributes().getStillTexture(level, pos)), - Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(fluidStateIn.getType().getAttributes().getFlowingTexture(level, pos)), + Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(props.getStillTexture(fluidStateIn, level, pos)), + Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(props.getFlowingTexture(fluidStateIn, level, pos)), overlayTexture == null ? null : Minecraft.getInstance().getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(overlayTexture), }; } @@ -458,7 +479,7 @@ public class ForgeHooksClient public static Stream getFluidMaterials(Fluid fluid) { - return fluid.getAttributes().getTextures() + return RenderProperties.get(fluid).getTextures() .filter(Objects::nonNull) .map(ForgeHooksClient::getBlockMaterial); } diff --git a/src/main/java/net/minecraftforge/client/IFluidTypeRenderProperties.java b/src/main/java/net/minecraftforge/client/IFluidTypeRenderProperties.java new file mode 100644 index 0000000000..2f1f7994cf --- /dev/null +++ b/src/main/java/net/minecraftforge/client/IFluidTypeRenderProperties.java @@ -0,0 +1,373 @@ +package net.minecraftforge.client; + +import com.mojang.blaze3d.shaders.FogShape; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.BufferUploader; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.math.Matrix4f; +import com.mojang.math.Vector3f; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.FogRenderer; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.ItemBlockRenderTypes; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraftforge.client.model.FluidModel; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidType; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.stream.Stream; + +/** + * An interface which defines properties relating to how a {@link FluidType} + * should render. + * + *

Note: This does not define any data regarding {@link RenderType}s as those + * are specified by the Fluid via {@link ItemBlockRenderTypes#setRenderLayer(Fluid, RenderType)} + * in the {@link FMLClientSetupEvent}. + */ +public interface IFluidTypeRenderProperties +{ + /** + * A dummy instance returned when no render properties are specified. + */ + IFluidTypeRenderProperties DUMMY = new IFluidTypeRenderProperties() + { + }; + + /* Default Accessors */ + + /** + * Returns the tint applied to the fluid's textures. + * + *

The result represents a 32-bit integer where each 8-bits represent + * the alpha, red, green, and blue channel respectively. + * + * @return the tint applied to the fluid's textures in ARGB format + */ + default int getColorTint() + { + return 0xFFFFFFFF; + } + + /** + * Returns the reference of the texture to apply to a source fluid. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_still} will point to + * {@code assets/minecraft/textures/block/water_still.png}). + * + *

Important: This method should only return {@code null} for {@link Fluids#EMPTY}. + * All other implementations must define this property. + * + * @return the reference of the texture to apply to a source fluid + */ + default ResourceLocation getStillTexture() + { + return null; + } + + /** + * Returns the reference of the texture to apply to a flowing fluid. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_flow} will point to + * {@code assets/minecraft/textures/block/water_flow.png}). + * + *

Important: This method should only return {@code null} for {@link Fluids#EMPTY}. + * All other implementations must define this property. + * + * @return the reference of the texture to apply to a flowing fluid + */ + default ResourceLocation getFlowingTexture() + { + return null; + } + + /** + * Returns the reference of the texture to apply to a fluid directly touching + * a non-opaque block other than air. If no reference is specified, either + * {@code #getStillTexture} or {@code #getFlowingTexture} will be applied + * instead. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_overlay} will point to + * {@code assets/minecraft/textures/block/water_overlay.png}). + * + * @return the reference of the texture to apply to a fluid directly touching + * a non-opaque block + */ + @Nullable + default ResourceLocation getOverlayTexture() + { + return null; + } + + /** + * Returns a stream of textures applied to a fluid. + * + *

This is used by the {@link FluidModel} to load in all textures that + * can be applied on reload. + * + * @return a stream of textures applied to a fluid + */ + default Stream getTextures() + { + return Stream.of(this.getStillTexture(), this.getFlowingTexture(), this.getOverlayTexture()) + .filter(Objects::nonNull); + } + + /** + * Returns the location of the texture to apply to the camera when it is + * within the fluid. If no location is specified, no overlay will be applied. + * + *

This should return a location to the texture and not a reference + * (e.g. {@code minecraft:textures/misc/underwater.png} will use the texture + * at {@code assets/minecraft/textures/misc/underwater.png}). + * + * @param mc the client instance + * @return the location of the texture to apply to the camera when it is + * within the fluid + */ + @Nullable + default ResourceLocation getRenderOverlayTexture(Minecraft mc) + { + return null; + } + + /** + * Renders {@code #getRenderOverlayTexture} onto the camera when within + * the fluid. + * + * @param mc the client instance + * @param stack the transformations representing the current rendering position + */ + default void renderOverlay(Minecraft mc, PoseStack stack) + { + ResourceLocation texture = this.getRenderOverlayTexture(mc); + if (texture == null) return; + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.enableTexture(); + RenderSystem.setShaderTexture(0, texture); + BufferBuilder buffer = Tesselator.getInstance().getBuilder(); + BlockPos playerEyePos = new BlockPos(mc.player.getX(), mc.player.getEyeY(), mc.player.getZ()); + float brightness = LightTexture.getBrightness(mc.player.level.dimensionType(), mc.player.level.getMaxLocalRawBrightness(playerEyePos)); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.setShaderColor(brightness, brightness, brightness, 0.1F); + float uOffset = -mc.player.getYRot() / 64.0F; + float vOffset = mc.player.getXRot() / 64.0F; + Matrix4f pose = stack.last().pose(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + buffer.vertex(pose, -1.0F, -1.0F, -0.5F).uv(4.0F + uOffset, 4.0F + vOffset).endVertex(); + buffer.vertex(pose, 1.0F, -1.0F, -0.5F).uv(uOffset, 4.0F + vOffset).endVertex(); + buffer.vertex(pose, 1.0F, 1.0F, -0.5F).uv(uOffset, vOffset).endVertex(); + buffer.vertex(pose, -1.0F, 1.0F, -0.5F).uv(4.0F + uOffset, vOffset).endVertex(); + BufferUploader.drawWithShader(buffer.end()); + RenderSystem.disableBlend(); + } + + /** + * Modifies the color of the fog when the camera is within the fluid. + * + *

The result expects a three float vector representing the red, green, + * and blue channels respectively. Each channel should be between [0,1]. + * + * @param camera the camera instance + * @param partialTick the delta time of where the current frame is within a tick + * @param level the level the camera is located in + * @param renderDistance the render distance of the client + * @param darkenWorldAmount the amount to darken the world by + * @param fluidFogColor the current color of the fog + * @return the color of the fog + */ + @NotNull + default Vector3f modifyFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount, Vector3f fluidFogColor) + { + return fluidFogColor; + } + + /** + * Modifies how the fog is currently being rendered when the camera is + * within a fluid. + * + * @param camera the camera instance + * @param mode the type of fog being rendered + * @param renderDistance the render distance of the client + * @param partialTick the delta time of where the current frame is within a tick + * @param nearDistance the near plane of where the fog starts to render + * @param farDistance the far plane of where the fog ends rendering + * @param shape the shape of the fog being rendered + */ + default void modifyFogRender(Camera camera, FogRenderer.FogMode mode, float renderDistance, float partialTick, float nearDistance, float farDistance, FogShape shape) + { + } + + /* Level-Based Accessors */ + + /** + * Returns the reference of the texture to apply to a source fluid. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_still} will point to + * {@code assets/minecraft/textures/block/water_still.png}). + * + *

Important: This method should only return {@code null} for {@link Fluids#EMPTY}. + * All other implementations must define this property. + * + * @param state the state of the fluid + * @param getter the getter the fluid can be obtained from + * @param pos the position of the fluid + * @return the reference of the texture to apply to a source fluid + */ + default ResourceLocation getStillTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return this.getStillTexture(); + } + + /** + * Returns the reference of the texture to apply to a flowing fluid. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_flow} will point to + * {@code assets/minecraft/textures/block/water_flow.png}). + * + *

Important: This method should only return {@code null} for {@link Fluids#EMPTY}. + * All other implementations must define this property. + * + * @param state the state of the fluid + * @param getter the getter the fluid can be obtained from + * @param pos the position of the fluid + * @return the reference of the texture to apply to a flowing fluid + */ + default ResourceLocation getFlowingTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return this.getFlowingTexture(); + } + + /** + * Returns the reference of the texture to apply to a fluid directly touching + * a non-opaque block other than air. If no reference is specified, either + * {@code #getStillTexture} or {@code #getFlowingTexture} will be applied + * instead. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_overlay} will point to + * {@code assets/minecraft/textures/block/water_overlay.png}). + * + * @param state the state of the fluid + * @param getter the getter the fluid can be obtained from + * @param pos the position of the fluid + * @return the reference of the texture to apply to a fluid directly touching + * a non-opaque block + */ + default ResourceLocation getOverlayTexture(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return this.getOverlayTexture(); + } + + + /** + * Returns the tint applied to the fluid's textures. + * + *

The result represents a 32-bit integer where each 8-bits represent + * the alpha, red, green, and blue channel respectively. + * + * @param state the state of the fluid + * @param getter the getter the fluid can be obtained from + * @param pos the position of the fluid + * @return the tint applied to the fluid's textures in ARGB format + */ + default int getColorTint(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return this.getColorTint(); + } + + /* Stack-Based Accessors */ + + + /** + * Returns the tint applied to the fluid's textures. + * + *

The result represents a 32-bit integer where each 8-bits represent + * the alpha, red, green, and blue channel respectively. + * + * @param stack the stack the fluid is in + * @return the tint applied to the fluid's textures in ARGB format + */ + default int getColorTint(FluidStack stack) + { + return this.getColorTint(); + } + + /** + * Returns the reference of the texture to apply to a source fluid. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_still} will point to + * {@code assets/minecraft/textures/block/water_still.png}). + * + *

Important: This method should only return {@code null} for {@link Fluids#EMPTY}. + * All other implementations must define this property. + * + * @param stack the stack the fluid is in + * @return the reference of the texture to apply to a source fluid + */ + default ResourceLocation getStillTexture(FluidStack stack) + { + return this.getStillTexture(); + } + + /** + * Returns the reference of the texture to apply to a flowing fluid. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_flow} will point to + * {@code assets/minecraft/textures/block/water_flow.png}). + * + *

Important: This method should only return {@code null} for {@link Fluids#EMPTY}. + * All other implementations must define this property. + * + * @param stack the stack the fluid is in + * @return the reference of the texture to apply to a flowing fluid + */ + default ResourceLocation getFlowingTexture(FluidStack stack) + { + return this.getFlowingTexture(); + } + + /** + * Returns the reference of the texture to apply to a fluid directly touching + * a non-opaque block other than air. If no reference is specified, either + * {@code #getStillTexture} or {@code #getFlowingTexture} will be applied + * instead. + * + *

This should return a reference to the texture and not the actual + * texture itself (e.g. {@code minecraft:block/water_overlay} will point to + * {@code assets/minecraft/textures/block/water_overlay.png}). + * + * @param stack the stack the fluid is in + * @return the reference of the texture to apply to a fluid directly touching + * a non-opaque block + */ + default ResourceLocation getOverlayTexture(FluidStack stack) + { + return this.getOverlayTexture(); + } +} diff --git a/src/main/java/net/minecraftforge/client/RenderProperties.java b/src/main/java/net/minecraftforge/client/RenderProperties.java index f5a981c700..05cea4a732 100644 --- a/src/main/java/net/minecraftforge/client/RenderProperties.java +++ b/src/main/java/net/minecraftforge/client/RenderProperties.java @@ -11,6 +11,9 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraftforge.fluids.FluidType; public class RenderProperties { @@ -44,6 +47,21 @@ public class RenderProperties return block.getRenderPropertiesInternal() instanceof IBlockRenderProperties props ? props : IBlockRenderProperties.DUMMY; } + public static IFluidTypeRenderProperties get(FluidState state) + { + return get(state.getFluidType()); + } + + public static IFluidTypeRenderProperties get(Fluid fluid) + { + return get(fluid.getFluidType()); + } + + public static IFluidTypeRenderProperties get(FluidType type) + { + return type.getRenderPropertiesInternal() instanceof IFluidTypeRenderProperties props ? props : IFluidTypeRenderProperties.DUMMY; + } + private RenderProperties() { // Not instantiable. diff --git a/src/main/java/net/minecraftforge/client/gui/ForgeIngameGui.java b/src/main/java/net/minecraftforge/client/gui/ForgeIngameGui.java index 6273348e92..72143a6e1a 100644 --- a/src/main/java/net/minecraftforge/client/gui/ForgeIngameGui.java +++ b/src/main/java/net/minecraftforge/client/gui/ForgeIngameGui.java @@ -41,6 +41,7 @@ import net.minecraft.world.level.GameType; import net.minecraftforge.client.RenderProperties; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType; +import net.minecraftforge.common.ForgeMod; import net.minecraftforge.common.MinecraftForge; import org.apache.logging.log4j.LogManager; @@ -469,7 +470,7 @@ public class ForgeIngameGui extends Gui int top = height - right_height; int air = player.getAirSupply(); - if (player.isEyeInFluid(FluidTags.WATER) || air < 300) + if (player.isEyeInFluidType(ForgeMod.WATER_TYPE.get()) || air < 300) { int full = Mth.ceil((double)(air - 2) * 10.0D / 300.0D); int partial = Mth.ceil((double)air * 10.0D / 300.0D) - full; diff --git a/src/main/java/net/minecraftforge/client/model/DynamicBucketModel.java b/src/main/java/net/minecraftforge/client/model/DynamicBucketModel.java index 8896554b6a..b3faa8ede7 100644 --- a/src/main/java/net/minecraftforge/client/model/DynamicBucketModel.java +++ b/src/main/java/net/minecraftforge/client/model/DynamicBucketModel.java @@ -22,6 +22,7 @@ import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.RenderProperties; import net.minecraftforge.client.model.geometry.IModelGeometry; import net.minecraftforge.fluids.FluidUtil; import net.minecraftforge.registries.ForgeRegistries; @@ -93,7 +94,7 @@ public final class DynamicBucketModel implements IModelGeometry transformMap = @@ -105,7 +106,7 @@ public final class DynamicBucketModel implements IModelGeometry 0), ItemTextureQuadConverter.convertTexture(transform, templateSprite, fluidSprite, NORTH_Z_FLUID, Direction.NORTH, color, 1, luminosity)); builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.convertTexture(transform, templateSprite, fluidSprite, SOUTH_Z_FLUID, Direction.SOUTH, color, 1, luminosity)); } diff --git a/src/main/java/net/minecraftforge/client/model/FluidModel.java b/src/main/java/net/minecraftforge/client/model/FluidModel.java index c62c722dd6..d9435fce77 100644 --- a/src/main/java/net/minecraftforge/client/model/FluidModel.java +++ b/src/main/java/net/minecraftforge/client/model/FluidModel.java @@ -28,12 +28,13 @@ import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.IFluidTypeRenderProperties; +import net.minecraftforge.client.RenderProperties; import net.minecraftforge.client.model.data.IModelData; import net.minecraftforge.client.model.geometry.IModelGeometry; import net.minecraftforge.client.model.pipeline.BakedQuadBuilder; import net.minecraftforge.client.model.pipeline.IVertexConsumer; import net.minecraftforge.client.model.pipeline.TRSRTransformer; -import net.minecraftforge.fluids.FluidAttributes; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -72,16 +73,16 @@ public final class FluidModel implements IModelGeometry @Override public BakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function spriteGetter, ModelState modelTransform, ItemOverrides overrides, ResourceLocation modelLocation) { - FluidAttributes attrs = fluid.getAttributes(); + IFluidTypeRenderProperties props = RenderProperties.get(fluid); return new CachingBakedFluid( modelTransform.getRotation(), PerspectiveMapWrapper.getTransforms(modelTransform), modelLocation, - attrs.getColor(), - spriteGetter.apply(ForgeHooksClient.getBlockMaterial(attrs.getStillTexture())), - spriteGetter.apply(ForgeHooksClient.getBlockMaterial(attrs.getFlowingTexture())), - Optional.ofNullable(attrs.getOverlayTexture()).map(ForgeHooksClient::getBlockMaterial).map(spriteGetter), - attrs.isLighterThanAir(), + props.getColorTint(), + spriteGetter.apply(ForgeHooksClient.getBlockMaterial(props.getStillTexture())), + spriteGetter.apply(ForgeHooksClient.getBlockMaterial(props.getFlowingTexture())), + Optional.ofNullable(props.getOverlayTexture()).map(ForgeHooksClient::getBlockMaterial).map(spriteGetter), + fluid.getFluidType().isLighterThanAir(), null ); } diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index 55cfc41fb9..88a4f8d942 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -51,6 +51,7 @@ import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.material.Fluids; import net.minecraft.world.level.storage.WorldData; import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootTable; @@ -139,7 +140,7 @@ import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.world.BlockEvent; import net.minecraftforge.event.world.NoteBlockEvent; import net.minecraftforge.eventbus.api.Event.Result; -import net.minecraftforge.fluids.FluidAttributes; +import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModLoader; @@ -803,30 +804,24 @@ public class ForgeHooks return ret; } - public static FluidAttributes createVanillaFluidAttributes(Fluid fluid) + /** + * Returns a vanilla fluid type for the given fluid. + * + * @param fluid the fluid looking for its type + * @return the type of the fluid if vanilla + * @throws RuntimeException if the fluid is not a vanilla one + */ + public static FluidType getVanillaFluidType(Fluid fluid) { - if (fluid instanceof EmptyFluid) - return net.minecraftforge.fluids.FluidAttributes.builder(null, null) - .translationKey("block.minecraft.air") - .color(0).density(0).temperature(0).luminosity(0).viscosity(0).build(fluid); - if (fluid instanceof WaterFluid) - return net.minecraftforge.fluids.FluidAttributes.Water.builder( - new ResourceLocation("block/water_still"), - new ResourceLocation("block/water_flow")) - .overlay(new ResourceLocation("block/water_overlay")) - .translationKey("block.minecraft.water") - .color(0xFF3F76E4) - .sound(SoundEvents.BUCKET_FILL, SoundEvents.BUCKET_EMPTY) - .build(fluid); - if (fluid instanceof LavaFluid) - return net.minecraftforge.fluids.FluidAttributes.builder( - new ResourceLocation("block/lava_still"), - new ResourceLocation("block/lava_flow")) - .translationKey("block.minecraft.lava") - .luminosity(15).density(3000).viscosity(6000).temperature(1300) - .sound(SoundEvents.BUCKET_FILL_LAVA, SoundEvents.BUCKET_EMPTY_LAVA) - .build(fluid); - throw new RuntimeException("Mod fluids must override createAttributes."); + if (fluid == Fluids.EMPTY) + return ForgeMod.EMPTY_TYPE.get(); + if (fluid == Fluids.WATER || fluid == Fluids.FLOWING_WATER) + return ForgeMod.WATER_TYPE.get(); + if (fluid == Fluids.LAVA || fluid == Fluids.FLOWING_LAVA) + return ForgeMod.LAVA_TYPE.get(); + if (ForgeMod.MILK.filter(milk -> milk == fluid).isPresent() || ForgeMod.FLOWING_MILK.filter(milk -> milk == fluid).isPresent()) + return ForgeMod.MILK_TYPE.get(); + throw new RuntimeException("Mod fluids must override getFluidType."); } public static String getDefaultWorldPreset() diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index e47947a196..3c42c6668a 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -5,19 +5,30 @@ package net.minecraftforge.common; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BiomeColors; import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.commands.synchronization.ArgumentTypeInfos; import net.minecraft.commands.synchronization.SingletonArgumentInfo; import net.minecraft.core.BlockPos; +import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.Attribute; import net.minecraft.world.entity.ai.attributes.RangedAttribute; +import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.item.Items; import net.minecraft.sounds.SoundEvent; import net.minecraft.core.Registry; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.pathfinder.BlockPathTypes; +import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.IFluidTypeRenderProperties; import net.minecraftforge.client.model.ModelLoaderRegistry; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import net.minecraftforge.common.crafting.PartialNBTIngredient; @@ -35,7 +46,7 @@ import net.minecraftforge.common.world.NoneBiomeModifier; import net.minecraftforge.common.world.NoneStructureModifier; import net.minecraftforge.common.world.StructureModifier; import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.fluids.FluidAttributes; +import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fluids.ForgeFlowingFluid; import net.minecraftforge.fml.*; import net.minecraftforge.fml.config.ModConfig; @@ -82,8 +93,10 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import com.mojang.serialization.Codec; +import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Consumer; @Mod("forge") public class ForgeMod @@ -133,13 +146,153 @@ public class ForgeMod * Noop biome modifier. Can be used in a biome modifier json with "type": "forge:none". */ public static final RegistryObject> NONE_BIOME_MODIFIER_TYPE = BIOME_MODIFIER_SERIALIZERS.register("none", () -> Codec.unit(NoneBiomeModifier.INSTANCE)); + /** * Noop structure modifier. Can be used in a structure modifier json with "type": "forge:none". */ public static final RegistryObject> NONE_STRUCTURE_MODIFIER_TYPE = STRUCTURE_MODIFIER_SERIALIZERS.register("none", () -> Codec.unit(NoneStructureModifier.INSTANCE)); + private static final DeferredRegister VANILLA_FLUID_TYPES = DeferredRegister.create(ForgeRegistries.Keys.FLUID_TYPES, "minecraft"); + + public static final RegistryObject EMPTY_TYPE = VANILLA_FLUID_TYPES.register("empty", () -> + new FluidType(FluidType.Properties.create() + .descriptionId("block.minecraft.air") + .motionScale(1D) + .canPushEntity(false) + .canSwim(false) + .canDrown(false) + .fallDistanceModifier(1F) + .pathType(null) + .adjacentPathType(null) + .density(0) + .temperature(0) + .viscosity(0)) + { + @Override + public void setItemMovement(ItemEntity entity) + { + if (!entity.isNoGravity()) entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.04D, 0.0D)); + } + }); + public static final RegistryObject WATER_TYPE = VANILLA_FLUID_TYPES.register("water", () -> + new FluidType(FluidType.Properties.create() + .descriptionId("block.minecraft.water") + .fallDistanceModifier(0F) + .canExtinguish(true) + .canConvertToSource(true) + .supportsBoating(true) + .sound(SoundActions.BUCKET_FILL, SoundEvents.BUCKET_FILL) + .sound(SoundActions.BUCKET_EMPTY, SoundEvents.BUCKET_EMPTY) + .sound(SoundActions.FLUID_VAPORIZE, SoundEvents.FIRE_EXTINGUISH) + .canHydrate(true)) + { + @Override + public @Nullable BlockPathTypes getBlockPathType(FluidState state, BlockGetter level, BlockPos pos, @Nullable Mob mob, boolean canFluidLog) + { + return canFluidLog ? super.getBlockPathType(state, level, pos, mob, true) : null; + } + + @Override + public void initializeClient(Consumer consumer) + { + consumer.accept(new IFluidTypeRenderProperties() + { + private static final ResourceLocation UNDERWATER_LOCATION = new ResourceLocation("textures/misc/underwater.png"), + WATER_STILL = new ResourceLocation("block/water_still"), + WATER_FLOW = new ResourceLocation("block/water_flow"), + WATER_OVERLAY = new ResourceLocation("block/water_overlay"); + + @Override + public ResourceLocation getStillTexture() + { + return WATER_STILL; + } + + @Override + public ResourceLocation getFlowingTexture() + { + return WATER_FLOW; + } + + @Nullable + @Override + public ResourceLocation getOverlayTexture() + { + return WATER_OVERLAY; + } + + @Override + public ResourceLocation getRenderOverlayTexture(Minecraft mc) + { + return UNDERWATER_LOCATION; + } + + @Override + public int getColorTint() + { + return 0xFF3F76E4; + } + + @Override + public int getColorTint(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return BiomeColors.getAverageWaterColor(getter, pos) | 0xFF000000; + } + }); + } + }); + public static final RegistryObject LAVA_TYPE = VANILLA_FLUID_TYPES.register("lava", () -> + new FluidType(FluidType.Properties.create() + .descriptionId("block.minecraft.lava") + .canSwim(false) + .canDrown(false) + .pathType(BlockPathTypes.LAVA) + .adjacentPathType(null) + .sound(SoundActions.BUCKET_FILL, SoundEvents.BUCKET_FILL_LAVA) + .sound(SoundActions.BUCKET_EMPTY, SoundEvents.BUCKET_EMPTY_LAVA) + .lightLevel(15) + .density(3000) + .viscosity(6000) + .temperature(1300)) + { + @Override + public double motionScale(Entity entity) + { + return entity.level.dimensionType().ultraWarm() ? 0.007D : 0.0023333333333333335D; + } + + @Override + public void setItemMovement(ItemEntity entity) + { + Vec3 vec3 = entity.getDeltaMovement(); + entity.setDeltaMovement(vec3.x * (double)0.95F, vec3.y + (double)(vec3.y < (double)0.06F ? 5.0E-4F : 0.0F), vec3.z * (double)0.95F); + } + + @Override + public void initializeClient(Consumer consumer) + { + consumer.accept(new IFluidTypeRenderProperties() + { + private static final ResourceLocation LAVA_STILL = new ResourceLocation("block/lava_still"), + LAVA_FLOW = new ResourceLocation("block/lava_flow"); + + @Override + public ResourceLocation getStillTexture() + { + return LAVA_STILL; + } + + @Override + public ResourceLocation getFlowingTexture() + { + return LAVA_FLOW; + } + }); + } + }); private static boolean enableMilkFluid = false; + public static final RegistryObject MILK_TYPE = RegistryObject.createOptional(new ResourceLocation("milk"), ForgeRegistries.Keys.FLUID_TYPES.location(), "minecraft"); public static final RegistryObject MILK = RegistryObject.create(new ResourceLocation("milk"), ForgeRegistries.FLUIDS); public static final RegistryObject FLOWING_MILK = RegistryObject.create(new ResourceLocation("flowing_milk"), ForgeRegistries.FLUIDS); @@ -185,6 +338,7 @@ public class ForgeMod COMMAND_ARGUMENT_TYPES.register(modEventBus); BIOME_MODIFIER_SERIALIZERS.register(modEventBus); STRUCTURE_MODIFIER_SERIALIZERS.register(modEventBus); + VANILLA_FLUID_TYPES.register(modEventBus); MinecraftForge.EVENT_BUS.addListener(this::serverStopping); MinecraftForge.EVENT_BUS.addListener(this::missingSoundMapping); ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ForgeConfig.clientSpec); @@ -274,14 +428,42 @@ public class ForgeMod // done in an event instead of deferred to only enable if a mod requests it public void registerFluids(RegisterEvent event) { - if (enableMilkFluid && event.getRegistryKey().equals(ForgeRegistries.Keys.FLUIDS)) + if (enableMilkFluid) { - // set up attributes - FluidAttributes.Builder attributesBuilder = FluidAttributes.builder(new ResourceLocation("forge", "block/milk_still"), new ResourceLocation("forge", "block/milk_flowing")).density(1024).viscosity(1024); - ForgeFlowingFluid.Properties properties = new ForgeFlowingFluid.Properties(MILK, FLOWING_MILK, attributesBuilder).bucket(() -> Items.MILK_BUCKET); + // register fluid type + event.register(ForgeRegistries.Keys.FLUID_TYPES, helper -> helper.register(MILK_TYPE.getId(), new FluidType(FluidType.Properties.create().density(1024).viscosity(1024)) + { + @Override + public void initializeClient(Consumer consumer) + { + consumer.accept(new IFluidTypeRenderProperties() + { + private static final ResourceLocation MILK_STILL = new ResourceLocation("forge", "block/milk_still"), + MILK_FLOW = new ResourceLocation("forge", "block/milk_flowing"); + + @Override + public ResourceLocation getStillTexture() + { + return MILK_STILL; + } + + @Override + public ResourceLocation getFlowingTexture() + { + return MILK_FLOW; + } + }); + } + })); + // register fluids - event.register(ForgeRegistries.Keys.FLUIDS, MILK.getId(), () -> new ForgeFlowingFluid.Source(properties)); - event.register(ForgeRegistries.Keys.FLUIDS, FLOWING_MILK.getId(), () -> new ForgeFlowingFluid.Flowing(properties)); + event.register(ForgeRegistries.Keys.FLUIDS, helper -> { + // set up properties + ForgeFlowingFluid.Properties properties = new ForgeFlowingFluid.Properties(MILK_TYPE, MILK, FLOWING_MILK).bucket(() -> Items.MILK_BUCKET); + + helper.register(MILK.getId(), new ForgeFlowingFluid.Source(properties)); + helper.register(FLOWING_MILK.getId(), new ForgeFlowingFluid.Flowing(properties)); + }); } } diff --git a/src/main/java/net/minecraftforge/common/SoundAction.java b/src/main/java/net/minecraftforge/common/SoundAction.java new file mode 100644 index 0000000000..e6c25b60b8 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/SoundAction.java @@ -0,0 +1,45 @@ +package net.minecraftforge.common; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Defines an action which produces a sound. + */ +@SuppressWarnings("ClassCanBeRecord") +public final class SoundAction +{ + private static final Map ACTIONS = new ConcurrentHashMap<>(); + + /** + * Gets or creates a new {@code SoundAction} for the given name. + * + * @param name the name of the action + * @return the existing {@code SoundAction}, or a new one if not present + */ + public static SoundAction get(String name) + { + return ACTIONS.computeIfAbsent(name, SoundAction::new); + } + + private final String name; + + private SoundAction(final String name) + { + this.name = name; + } + + /** + * Returns the name of the action. + * + * @return the name of the action + */ + public String name() { + return this.name; + } + + @Override + public String toString() { + return "SoundAction[" + this.name + "]"; + } +} diff --git a/src/main/java/net/minecraftforge/common/SoundActions.java b/src/main/java/net/minecraftforge/common/SoundActions.java new file mode 100644 index 0000000000..b7d9b47622 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/SoundActions.java @@ -0,0 +1,27 @@ +package net.minecraftforge.common; + +/** + * A utility holding common sound actions. + */ +public final class SoundActions +{ + private SoundActions() + { + throw new AssertionError("SoundActions should not be instantiated."); + } + + /** + * When a bucket is being filled by a fluid. + */ + public static final SoundAction BUCKET_FILL = SoundAction.get("bucket_fill"); + + /** + * When a bucket is emptying a fluid. + */ + public static final SoundAction BUCKET_EMPTY = SoundAction.get("bucket_empty"); + + /** + * When the fluid is being vaporized. + */ + public static final SoundAction FLUID_VAPORIZE = SoundAction.get("fluid_vaporize"); +} diff --git a/src/main/java/net/minecraftforge/common/Tags.java b/src/main/java/net/minecraftforge/common/Tags.java index 3d24b16bf9..545c2fd256 100644 --- a/src/main/java/net/minecraftforge/common/Tags.java +++ b/src/main/java/net/minecraftforge/common/Tags.java @@ -405,7 +405,14 @@ public class Tags { private static void init() {} + /** + * Holds all fluids related to milk. + */ public static final TagKey MILK = tag("milk"); + /** + * Holds all fluids that are gaseous at room temperature. + */ + public static final TagKey GASEOUS = tag("gaseous"); private static TagKey tag(String name) { diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java b/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java index 616c909843..ee83e1ee7d 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java @@ -28,6 +28,7 @@ import net.minecraft.world.level.pathfinder.BlockPathTypes; import net.minecraft.tags.BlockTags; import net.minecraft.core.Direction; import net.minecraft.core.BlockPos; +import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.server.level.ServerLevel; @@ -480,16 +481,43 @@ public interface IForgeBlock } /** - * Get the {@code PathNodeType} for this block. Return {@code null} for vanilla behavior. + * Gets the path type of this block when an entity is pathfinding. When + * {@code null}, uses vanilla behavior. * - * @return the PathNodeType + * @param state the state of the block + * @param level the level which contains this block + * @param pos the position of the block + * @param mob the mob currently pathfinding, may be {@code null} + * @return the path type of this block */ @Nullable - default BlockPathTypes getAiPathNodeType(BlockState state, BlockGetter level, BlockPos pos, @Nullable Mob entity) + default BlockPathTypes getBlockPathType(BlockState state, BlockGetter level, BlockPos pos, @Nullable Mob mob) { return state.getBlock() == Blocks.LAVA ? BlockPathTypes.LAVA : state.isBurning(level, pos) ? BlockPathTypes.DAMAGE_FIRE : null; } + /** + * Gets the path type of the adjacent block to a pathfinding entity. + * Path types with a negative malus are not traversable for the entity. + * Pathfinding entities will favor paths consisting of a lower malus. + * When {@code null}, uses vanilla behavior. + * + * @param state the state of the block + * @param level the level which contains this block + * @param pos the position of the block + * @param mob the mob currently pathfinding, may be {@code null} + * @param originalType the path type of the source the entity is on + * @return the path type of this block + */ + @Nullable + default BlockPathTypes getAdjacentBlockPathType(BlockState state, BlockGetter level, BlockPos pos, @Nullable Mob mob, BlockPathTypes originalType) + { + if (state.is(Blocks.CACTUS)) return BlockPathTypes.DANGER_CACTUS; + else if (state.is(Blocks.SWEET_BERRY_BUSH)) return BlockPathTypes.DANGER_OTHER; + else if (WalkNodeEvaluator.isBurningBlock(state)) return BlockPathTypes.DANGER_FIRE; + else return null; + } + /** * @param state The state * @return true if the block is sticky block which used for pull or push adjacent blocks (use by piston) @@ -816,4 +844,26 @@ public interface IForgeBlock } return true; } + + /** + * Returns whether the block can be hydrated by a fluid. + * + *

Hydration is an arbitrary word which depends on the block. + *

    + *
  • A farmland has moisture
  • + *
  • A sponge can soak up the liquid
  • + *
  • A coral can live
  • + *
+ * + * @param state the state of the block being hydrated + * @param getter the getter which can get the block + * @param pos the position of the block being hydrated + * @param fluid the state of the fluid + * @param fluidPos the position of the fluid + * @return {@code true} if the block can be hydrated, {@code false} otherwise + */ + default boolean canBeHydrated(BlockState state, BlockGetter getter, BlockPos pos, FluidState fluid, BlockPos fluidPos) + { + return fluid.canHydrate(getter, fluidPos, state, pos); + } } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java index 2fcf6dcf85..8897c0fce6 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java @@ -541,25 +541,36 @@ public interface IForgeBlockState } /** - * Get the {@code BlockPathTypes} for this block. Return {@code null} for vanilla behavior. + * Gets the path type of this block when an entity is pathfinding. When + * {@code null}, uses vanilla behavior. * - * @return the PathNodeType - */ - @Nullable - default BlockPathTypes getBlockPathType(BlockGetter level, BlockPos pos) - { - return getBlockPathType(level, pos, null); - } - - /** - * Get the {@code PathNodeType} for this block. Return {@code null} for vanilla behavior. - * - * @return the PathNodeType + * @param level the level which contains this block + * @param pos the position of the block + * @param mob the mob currently pathfinding, may be {@code null} + * @return the path type of this block */ @Nullable default BlockPathTypes getBlockPathType(BlockGetter level, BlockPos pos, @Nullable Mob mob) { - return self().getBlock().getAiPathNodeType(self(), level, pos, mob); + return self().getBlock().getBlockPathType(self(), level, pos, mob); + } + + /** + * Gets the path type of the adjacent block to a pathfinding entity. + * Path types with a negative malus are not traversable for the entity. + * Pathfinding entities will favor paths consisting of a lower malus. + * When {@code null}, uses vanilla behavior. + * + * @param level the level which contains this block + * @param pos the position of the block + * @param mob the mob currently pathfinding, may be {@code null} + * @param originalType the path type of the source the entity is on + * @return the path type of this block + */ + @Nullable + default BlockPathTypes getAdjacentBlockPathType(BlockGetter level, BlockPos pos, @Nullable Mob mob, BlockPathTypes originalType) + { + return self().getBlock().getAdjacentBlockPathType(self(), level, pos, mob, originalType); } /** @@ -676,4 +687,25 @@ public interface IForgeBlockState { return self().getBlock().supportsExternalFaceHiding(self()); } + + /** + * Returns whether the block can be hydrated by a fluid. + * + *

Hydration is an arbitrary word which depends on the block. + *

    + *
  • A farmland has moisture
  • + *
  • A sponge can soak up the liquid
  • + *
  • A coral can live
  • + *
+ * + * @param getter the getter which can get the block + * @param pos the position of the block being hydrated + * @param fluid the state of the fluid + * @param fluidPos the position of the fluid + * @return {@code true} if the block can be hydrated, {@code false} otherwise + */ + default boolean canBeHydrated(BlockGetter getter, BlockPos pos, FluidState fluid, BlockPos fluidPos) + { + return self().getBlock().canBeHydrated(self(), getter, pos, fluid, fluidPos); + } } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeBoat.java b/src/main/java/net/minecraftforge/common/extensions/IForgeBoat.java new file mode 100644 index 0000000000..87b144a475 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeBoat.java @@ -0,0 +1,35 @@ +package net.minecraftforge.common.extensions; + +import net.minecraft.world.entity.vehicle.Boat; +import net.minecraft.world.level.material.FluidState; +import net.minecraftforge.fluids.FluidType; + +public interface IForgeBoat +{ + private Boat self() + { + return (Boat) this; + } + + /** + * Returns whether the boat can be used on the fluid. + * + * @param state the state of the fluid + * @return {@code true} if the boat can be used, {@code false} otherwise + */ + default boolean canBoatInFluid(FluidState state) + { + return state.supportsBoating(self()); + } + + /** + * Returns whether the boat can be used on the fluid. + * + * @param type the type of the fluid + * @return {@code true} if the boat can be used, {@code false} otherwise + */ + default boolean canBoatInFluid(FluidType type) + { + return type.supportsBoating(self()); + } +} diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeEntity.java b/src/main/java/net/minecraftforge/common/extensions/IForgeEntity.java index 93b1856188..79997142f9 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeEntity.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeEntity.java @@ -6,7 +6,9 @@ package net.minecraftforge.common.extensions; import java.util.Collection; +import java.util.function.BiPredicate; +import net.minecraft.sounds.SoundEvent; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.boss.enderdragon.EnderDragon; @@ -19,11 +21,14 @@ import net.minecraft.world.item.SpawnEggItem; import net.minecraft.world.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.core.BlockPos; +import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.HitResult; import net.minecraftforge.common.ForgeMod; import net.minecraftforge.common.ForgeSpawnEggItem; +import net.minecraftforge.common.SoundAction; import net.minecraftforge.common.capabilities.ICapabilitySerializable; import net.minecraftforge.entity.PartEntity; +import net.minecraftforge.fluids.FluidType; import org.jetbrains.annotations.Nullable; public interface IForgeEntity extends ICapabilitySerializable @@ -101,14 +106,16 @@ public interface IForgeEntity extends ICapabilitySerializable } /** - * Checks if this entity can continue to be ridden while it is underwater. + * Returns whether the entity can ride in this vehicle under the fluid. * - * @param rider The entity that is riding this entity - * @return {@code true} if the entity can continue to ride underwater. {@code false} otherwise. + * @param type the type of the fluid + * @param rider the entity riding the vehicle + * @return {@code true} if the vehicle can be ridden in under this fluid, + * {@code false} otherwise */ - default boolean canBeRiddenInWater(Entity rider) + default boolean canBeRiddenUnderFluidType(FluidType type, Entity rider) { - return self().rideableUnderWater(); + return type.canRideVehicleUnder(self(), rider); } /** @@ -212,4 +219,195 @@ public interface IForgeEntity extends ICapabilitySerializable } return vanillaStep; } + + /** + * Returns the height of the fluid type in relation to the bounding box of + * the entity. If the entity is not in the fluid type, then {@code 0} + * is returned. + * + * @param type the type of the fluid + * @return the height of the fluid compared to the entity + */ + double getFluidTypeHeight(FluidType type); + + /** + * Returns the fluid type which is the highest on the bounding box of + * the entity. + * + * @return the fluid type which is the highest on the bounding box of + * the entity + */ + FluidType getMaxHeightFluidType(); + + /** + * Returns whether the entity is within the fluid type of the state. + * + * @param state the state of the fluid + * @return {@code true} if the entity is within the fluid type of the + * state, {@code false} otherwise + */ + default boolean isInFluidType(FluidState state) + { + return this.isInFluidType(state.getFluidType()); + } + + /** + * Returns whether the entity is within the fluid type. + * + * @param type the type of the fluid + * @return {@code true} if the entity is within the fluid type, + * {@code false} otherwise + */ + default boolean isInFluidType(FluidType type) + { + return this.getFluidTypeHeight(type) > 0.0D; + } + + /** + * Returns whether any fluid type the entity is currently in matches + * the specified condition. + * + * @param predicate a test taking in the fluid type and its height + * @return {@code true} if a fluid type meets the condition, {@code false} + * otherwise + */ + default boolean isInFluidType(BiPredicate predicate) + { + return isInFluidType(predicate, false); + } + + /** + * Returns whether the fluid type the entity is currently in matches + * the specified condition. + * + * @param predicate a test taking in the fluid type and its height + * @param forAllTypes {@code true} if all fluid types should match the + * condition instead of at least one + * @return {@code true} if a fluid type meets the condition, {@code false} + * otherwise + */ + boolean isInFluidType(BiPredicate predicate, boolean forAllTypes); + + /** + * Returns whether the entity is in a fluid. + * + * @return {@code true} if the entity is in a fluid, {@code false} otherwise + */ + boolean isInFluidType(); + + /** + * Returns the fluid that is on the entity's eyes. + * + * @return the fluid that is on the entity's eyes + */ + FluidType getEyeInFluidType(); + + /** + * Returns whether the fluid is on the entity's eyes. + * + * @return {@code true} if the fluid is on the entity's eyes, {@code false} otherwise + */ + default boolean isEyeInFluidType(FluidType type) + { + return type == this.getEyeInFluidType(); + } + + /** + * Returns whether the entity can start swimming in the fluid. + * + * @return {@code true} if the entity can start swimming, {@code false} otherwise + */ + default boolean canStartSwimming() + { + return !this.getEyeInFluidType().isAir() && this.canSwimInFluidType(this.getEyeInFluidType()); + } + + /** + * Returns how much the velocity of the fluid should be scaled by + * when applied to an entity. + * + * @param type the type of the fluid + * @return a scalar to multiply to the fluid velocity + */ + default double getFluidMotionScale(FluidType type) + { + return type.motionScale(self()); + } + + /** + * Returns whether the fluid can push an entity. + * + * @param type the type of the fluid + * @return {@code true} if the entity can be pushed by the fluid, {@code false} otherwise + */ + default boolean isPushedByFluid(FluidType type) + { + return self().isPushedByFluid() && type.canPushEntity(self()); + } + + /** + * Returns whether the entity can swim in the fluid. + * + * @param type the type of the fluid + * @return {@code true} if the entity can swim in the fluid, {@code false} otherwise + */ + default boolean canSwimInFluidType(FluidType type) + { + return type.canSwim(self()); + } + + /** + * Returns whether the entity can be extinguished by this fluid. + * + * @param type the type of the fluid + * @return {@code true} if the entity can be extinguished, {@code false} otherwise + */ + default boolean canFluidExtinguish(FluidType type) + { + return type.canExtinguish(self()); + } + + /** + * Returns how much the fluid should scale the damage done to a falling + * entity when hitting the ground per tick. + * + *

Implementation: If the entity is in many fluids, the smallest modifier + * is applied. + * + * @param type the type of the fluid + * @return a scalar to multiply to the fall damage + */ + default float getFluidFallDistanceModifier(FluidType type) + { + return type.getFallDistanceModifier(self()); + } + + /** + * Returns whether the entity can be hydrated by this fluid. + * + *

Hydration is an arbitrary word which depends on the entity. + * + * @param type the type of the fluid + * @return {@code true} if the entity can be hydrated, {@code false} + * otherwise + */ + default boolean canHydrateInFluidType(FluidType type) + { + return type.canHydrate(self()); + } + + /** + * Returns a sound to play when a certain action is performed by the + * entity in the fluid. If no sound is present, then the sound will be + * {@code null}. + * + * @param type the type of the fluid + * @param action the action being performed + * @return the sound to play when performing the action + */ + @Nullable + default SoundEvent getSoundFromFluidType(FluidType type, SoundAction action) + { + return type.getSound(self(), action); + } } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeFluid.java b/src/main/java/net/minecraftforge/common/extensions/IForgeFluid.java index 897c148d60..d48f7b6618 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeFluid.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeFluid.java @@ -5,74 +5,36 @@ package net.minecraftforge.common.extensions; -import net.minecraft.core.HolderSet; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.entity.Entity; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.vehicle.Boat; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.phys.AABB; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Explosion; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.LevelReader; -import net.minecraftforge.fluids.FluidAttributes; +import net.minecraft.world.level.pathfinder.BlockPathTypes; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.fluids.FluidType; import org.jetbrains.annotations.Nullable; public interface IForgeFluid { - /** - * Called when the entity is inside this block, may be used to determined if the entity can breathing, - * display material overlays, or if the entity can swim inside a block. - * - * @param level that is being tested. - * @param pos position thats being tested. - * @param entity that is being tested. - * @param yToTest, primarily for testingHead, which sends the the eye level of the entity, other wise it sends a y that can be tested vs liquid height. - * @param tag Fluid category - * @param testingHead when true, its testing the entities head for vision, breathing ect... otherwise its testing the body, for swimming and movement adjustment. - */ - default boolean isEntityInside(FluidState state, LevelReader level, BlockPos pos, Entity entity, double yToTest, HolderSet tag, boolean testingHead) + private Fluid self() { - return state.is(tag) && yToTest < (double)(pos.getY() + state.getHeight(level, pos) + 0.11111111F); + return (Fluid) this; } /** - * Called when boats or fishing hooks are inside the block to check if they are inside - * the material requested. + * Returns the explosion resistance of the fluid. * - * @param level level that is being tested. - * @param pos block thats being tested. - * @param boundingBox box to test, generally the bounds of an entity that are besting tested. - * @param materialIn to check for. - * @return null for default behavior, true if the box is within the material, false if it was not. - */ - @Nullable - default Boolean isAABBInsideMaterial(FluidState state, LevelReader level, BlockPos pos, AABB boundingBox, Material materialIn) - { - return null; - } - - /** - * Called when entities are moving to check if they are inside a liquid - * - * @param level level that is being tested. - * @param pos block thats being tested. - * @param boundingBox box to test, generally the bounds of an entity that are besting tested. - * @return null for default behavior, true if the box is within the material, false if it was not. - */ - @Nullable - default Boolean isAABBInsideLiquid(FluidState state, LevelReader level, BlockPos pos, AABB boundingBox) - { - return null; - } - - /** - * Location sensitive version of getExplosionResistance - * - * @param level The current level - * @param pos Block position in level - * @param explosion The explosion - * @return The amount of the explosion absorbed. + * @param state the state of the fluid + * @param level the level the fluid is in + * @param pos the position of the fluid + * @param explosion the explosion the fluid is absorbing + * @return the amount of the explosion the fluid can absorb */ @SuppressWarnings("deprecation") default float getExplosionResistance(FluidState state, BlockGetter level, BlockPos pos, Explosion explosion) @@ -81,17 +43,125 @@ public interface IForgeFluid } /** - * Queries if this fluid should render in a given layer. - * A custom {@link IBakedModel} can use {@link net.minecraftforge.client.MinecraftForgeClient#getRenderType()} to alter the model based on layer. + * Returns the type of this fluid. + * + *

Important: This MUST be overridden on your fluid, otherwise an + * error will be thrown. + * + * @return the type of this fluid */ - /* TODO: reimplement - default boolean canRenderInLayer(IFluidState state, BlockRenderLayer layer) - { - return this.getFluid().getRenderLayer() == layer; - }*/ + FluidType getFluidType(); /** - * Retrieves the non-vanilla fluid attributes, including localized name. + * Performs how an entity moves when within the fluid. If using custom + * movement logic, the method should return {@code true}. Otherwise, the + * movement logic will default to water. + * + * @param state the state of the fluid + * @param entity the entity moving within the fluid + * @param movementVector the velocity of how the entity wants to move + * @param gravity the gravity to apply to the entity + * @return {@code true} if custom movement logic is performed, {@code false} otherwise */ - FluidAttributes getAttributes(); + default boolean move(FluidState state, LivingEntity entity, Vec3 movementVector, double gravity) + { + return getFluidType().move(state, entity, movementVector, gravity); + } + + /** + * Returns whether the fluid can create a source. + * + * @param state the state of the fluid + * @param reader the reader that can get the fluid + * @param pos the location of the fluid + * @return {@code true} if the fluid can create a source, {@code false} otherwise + */ + default boolean canConvertToSource(FluidState state, LevelReader reader, BlockPos pos) + { + return getFluidType().canConvertToSource(state, reader, pos); + } + + /** + * Returns whether the boat can be used on the fluid. + * + * @param state the state of the fluid + * @param boat the boat trying to be used on the fluid + * @return {@code true} if the boat can be used, {@code false} otherwise + */ + default boolean supportsBoating(FluidState state, Boat boat) + { + return getFluidType().supportsBoating(state, boat); + } + + /** + * Gets the path type of this fluid when an entity is pathfinding. When + * {@code null}, uses vanilla behavior. + * + * @param state the state of the fluid + * @param level the level which contains this fluid + * @param pos the position of the fluid + * @param mob the mob currently pathfinding, may be {@code null} + * @param canFluidLog {@code true} if the path is being applied for fluids that can log blocks, + * should be checked against if the fluid can log a block + * @return the path type of this fluid + */ + @Nullable + default BlockPathTypes getBlockPathType(FluidState state, BlockGetter level, BlockPos pos, @org.jetbrains.annotations.Nullable Mob mob, boolean canFluidLog) + { + return getFluidType().getBlockPathType(state, level, pos, mob, canFluidLog); + } + + /** + * Gets the path type of the adjacent fluid to a pathfinding entity. + * Path types with a negative malus are not traversable for the entity. + * Pathfinding entities will favor paths consisting of a lower malus. + * When {@code null}, uses vanilla behavior. + * + * @param state the state of the fluid + * @param level the level which contains this fluid + * @param pos the position of the fluid + * @param mob the mob currently pathfinding, may be {@code null} + * @param originalType the path type of the source the entity is on + * @return the path type of this fluid + */ + @Nullable + default BlockPathTypes getAdjacentBlockPathType(FluidState state, BlockGetter level, BlockPos pos, @org.jetbrains.annotations.Nullable Mob mob, BlockPathTypes originalType) + { + return getFluidType().getAdjacentBlockPathType(state, level, pos, mob, originalType); + } + + /** + * Returns whether the block can be hydrated by a fluid. + * + *

Hydration is an arbitrary word which depends on the block. + *

    + *
  • A farmland has moisture
  • + *
  • A sponge can soak up the liquid
  • + *
  • A coral can live
  • + *
+ * + * @param state the state of the fluid + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @param source the state of the block being hydrated + * @param sourcePos the position of the block being hydrated + * @return {@code true} if the block can be hydrated, {@code false} otherwise + */ + default boolean canHydrate(FluidState state, BlockGetter getter, BlockPos pos, BlockState source, BlockPos sourcePos) + { + return getFluidType().canHydrate(state, getter, pos, source, sourcePos); + } + + /** + * Returns whether the block can be extinguished by this fluid. + * + * @param state the state of the fluid + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @return {@code true} if the block can be extinguished, {@code false} otherwise + */ + default boolean canExtinguish(FluidState state, BlockGetter getter, BlockPos pos) + { + return getFluidType().canExtinguish(state, getter, pos); + } } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeFluidState.java b/src/main/java/net/minecraftforge/common/extensions/IForgeFluidState.java index 7108b69b67..a267d59a12 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeFluidState.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeFluidState.java @@ -5,50 +5,153 @@ package net.minecraftforge.common.extensions; -import net.minecraft.core.HolderSet; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Explosion; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Explosion; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.pathfinder.BlockPathTypes; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.fluids.FluidType; +import org.jetbrains.annotations.Nullable; public interface IForgeFluidState { private FluidState self() { - return (FluidState)this; + return (FluidState) this; } /** - * Called when the entity is inside this block, may be used to determined if the entity can breathing, - * display material overlays, or if the entity can swim inside a block. + * Returns the explosion resistance of the fluid. * - * @param level that is being tested. - * @param pos position thats being tested. - * @param entity that is being tested. - * @param yToTest, primarily for testingHead, which sends the the eye level of the entity, other wise it sends a y that can be tested vs liquid height. - * @param tag to test for. - * @param testingHead when true, its testing the entities head for vision, breathing ect... otherwise its testing the body, for swimming and movement adjustment. - */ - default boolean isEntityInside(LevelReader level, BlockPos pos, Entity entity, double yToTest, HolderSet tag, boolean testingHead) - { -// return ifluidstate.isTagged(p_213290_1_) && d0 < (double)((float)blockpos.getY() + ifluidstate.getActualHeight(this.level, blockpos) + 0.11111111F); - return self().getType().isEntityInside(self(), level, pos, entity, yToTest, tag, testingHead); - } - - /** - * Location sensitive version of getExplosionResistance - * - * @param level The current level - * @param pos Block position in level - * @param explosion The explosion - * @return The amount of the explosion absorbed. + * @param level the level the fluid is in + * @param pos the position of the fluid + * @param explosion the explosion the fluid is absorbing + * @return the amount of the explosion the fluid can absorb */ default float getExplosionResistance(BlockGetter level, BlockPos pos, Explosion explosion) { return self().getType().getExplosionResistance(self(), level, pos, explosion); } + /** + * Returns the type of this fluid. + * + * @return the type of this fluid + */ + default FluidType getFluidType() + { + return self().getType().getFluidType(); + } + + /** + * Performs how an entity moves when within the fluid. If using custom + * movement logic, the method should return {@code true}. Otherwise, the + * movement logic will default to water. + * + * @param entity the entity moving within the fluid + * @param movementVector the velocity of how the entity wants to move + * @param gravity the gravity to apply to the entity + * @return {@code true} if custom movement logic is performed, {@code false} otherwise + */ + default boolean move(LivingEntity entity, Vec3 movementVector, double gravity) + { + return self().getType().move(self(), entity, movementVector, gravity); + } + + /** + * Returns whether the fluid can create a source. + * + * @param reader the reader that can get the fluid + * @param pos the location of the fluid + * @return {@code true} if the fluid can create a source, {@code false} otherwise + */ + default boolean canConvertToSource(LevelReader reader, BlockPos pos) + { + return self().getType().canConvertToSource(self(), reader, pos); + } + + /** + * Returns whether the boat can be used on the fluid. + * + * @param boat the boat trying to be used on the fluid + * @return {@code true} if the boat can be used, {@code false} otherwise + */ + default boolean supportsBoating(Boat boat) + { + return self().getType().supportsBoating(self(), boat); + } + + /** + * Gets the path type of this fluid when an entity is pathfinding. When + * {@code null}, uses vanilla behavior. + * + * @param level the level which contains this fluid + * @param pos the position of the fluid + * @param mob the mob currently pathfinding, may be {@code null} + * @param canFluidLog {@code true} if the path is being applied for fluids that can log blocks, + * should be checked against if the fluid can log a block + * @return the path type of this fluid + */ + @Nullable + default BlockPathTypes getBlockPathType(BlockGetter level, BlockPos pos, @org.jetbrains.annotations.Nullable Mob mob, boolean canFluidLog) + { + return self().getType().getBlockPathType(self(), level, pos, mob, canFluidLog); + } + + /** + * Gets the path type of the adjacent fluid to a pathfinding entity. + * Path types with a negative malus are not traversable for the entity. + * Pathfinding entities will favor paths consisting of a lower malus. + * When {@code null}, uses vanilla behavior. + * + * @param level the level which contains this fluid + * @param pos the position of the fluid + * @param mob the mob currently pathfinding, may be {@code null} + * @param originalType the path type of the source the entity is on + * @return the path type of this fluid + */ + @Nullable + default BlockPathTypes getAdjacentBlockPathType(BlockGetter level, BlockPos pos, @org.jetbrains.annotations.Nullable Mob mob, BlockPathTypes originalType) + { + return self().getType().getAdjacentBlockPathType(self(), level, pos, mob, originalType); + } + + /** + * Returns whether the block can be hydrated by a fluid. + * + *

Hydration is an arbitrary word which depends on the block. + *

    + *
  • A farmland has moisture
  • + *
  • A sponge can soak up the liquid
  • + *
  • A coral can live
  • + *
+ * + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @param source the state of the block being hydrated + * @param sourcePos the position of the block being hydrated + * @return {@code true} if the block can be hydrated, {@code false} otherwise + */ + default boolean canHydrate(BlockGetter getter, BlockPos pos, BlockState source, BlockPos sourcePos) + { + return self().getType().canHydrate(self(), getter, pos, source, sourcePos); + } + + /** + * Returns whether the block can be extinguished by this fluid. + * + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @return {@code true} if the block can be extinguished, {@code false} otherwise + */ + default boolean canExtinguish(BlockGetter getter, BlockPos pos) + { + return self().getType().canExtinguish(self(), getter, pos); + } } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeLivingEntity.java b/src/main/java/net/minecraftforge/common/extensions/IForgeLivingEntity.java new file mode 100644 index 0000000000..3120bd96b6 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeLivingEntity.java @@ -0,0 +1,69 @@ +package net.minecraftforge.common.extensions; + +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.ForgeMod; +import net.minecraftforge.fluids.FluidType; + +public interface IForgeLivingEntity extends IForgeEntity +{ + default LivingEntity self() + { + return (LivingEntity) this; + } + + @Override + default boolean canSwimInFluidType(FluidType type) + { + if (type == ForgeMod.WATER_TYPE.get()) return !self().isSensitiveToWater(); + else return IForgeEntity.super.canSwimInFluidType(type); + } + + /** + * Performs what to do when an entity attempts to go up or "jump" in a fluid. + * + * @param type the type of the fluid + */ + default void jumpInFluid(FluidType type) + { + self().setDeltaMovement(self().getDeltaMovement().add(0.0D, (double)0.04F * self().getAttributeValue(ForgeMod.SWIM_SPEED.get()), 0.0D)); + } + + /** + * Performs what to do when an entity attempts to go down or "sink" in a fluid. + * + * @param type the type of the fluid + */ + default void sinkInFluid(FluidType type) + { + self().setDeltaMovement(self().getDeltaMovement().add(0.0D, (double)-0.04F * self().getAttributeValue(ForgeMod.SWIM_SPEED.get()), 0.0D)); + } + + /** + * Returns whether the entity can drown in the fluid. + * + * @param type the type of the fluid + * @return {@code true} if the entity can drown in the fluid, {@code false} otherwise + */ + default boolean canDrownInFluidType(FluidType type) + { + if (type == ForgeMod.WATER_TYPE.get()) return !self().canBreatheUnderwater(); + return type.canDrownIn(self()); + } + + /** + * Performs how an entity moves when within the fluid. If using custom + * movement logic, the method should return {@code true}. Otherwise, the + * movement logic will default to water. + * + * @param state the state of the fluid + * @param movementVector the velocity of how the entity wants to move + * @param gravity the gravity to apply to the entity + * @return {@code true} if custom movement logic is performed, {@code false} otherwise + */ + default boolean moveInFluid(FluidState state, Vec3 movementVector, double gravity) + { + return state.move(self(), movementVector, gravity); + } +} diff --git a/src/main/java/net/minecraftforge/fluids/DispenseFluidContainer.java b/src/main/java/net/minecraftforge/fluids/DispenseFluidContainer.java index ff6b058b23..477dc42f3a 100644 --- a/src/main/java/net/minecraftforge/fluids/DispenseFluidContainer.java +++ b/src/main/java/net/minecraftforge/fluids/DispenseFluidContainer.java @@ -94,7 +94,7 @@ public class DispenseFluidContainer extends DefaultDispenseItemBehavior return super.execute(source, stack); } - FluidStack fluidStack = fluidHandler.drain(FluidAttributes.BUCKET_VOLUME, IFluidHandler.FluidAction.EXECUTE); + FluidStack fluidStack = fluidHandler.drain(FluidType.BUCKET_VOLUME, IFluidHandler.FluidAction.EXECUTE); Direction dispenserFacing = source.getBlockState().getValue(DispenserBlock.FACING); BlockPos blockpos = source.getPos().relative(dispenserFacing); FluidActionResult result = FluidUtil.tryPlaceFluid(null, source.getLevel(), InteractionHand.MAIN_HAND, blockpos, stack, fluidStack); diff --git a/src/main/java/net/minecraftforge/fluids/FluidAttributes.java b/src/main/java/net/minecraftforge/fluids/FluidAttributes.java deleted file mode 100644 index e8207c1bbb..0000000000 --- a/src/main/java/net/minecraftforge/fluids/FluidAttributes.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.minecraftforge.fluids; - -import net.minecraft.world.item.BucketItem; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.dimension.DimensionType; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.item.ItemStack; -import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.network.chat.Component; -import net.minecraft.world.level.BlockAndTintGetter; - -import java.util.function.BiFunction; -import java.util.stream.Stream; - -import net.minecraft.world.entity.player.Player; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Level; -import net.minecraft.world.item.Rarity; -import net.minecraft.client.renderer.BiomeColors; - -import net.minecraft.Util; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraftforge.registries.ForgeRegistries; -import org.jetbrains.annotations.Nullable; - -/** - * Minecraft Forge Fluid Implementation - * - * This class is a fluid (liquid or gas) equivalent to "Item." It describes the nature of a fluid - * and contains its general properties. - * - * These properties do not have inherent gameplay mechanics - they are provided so that mods may - * choose to take advantage of them. - * - * Fluid implementations are not required to actively use these properties, nor are objects - * interfacing with fluids required to make use of them, but it is encouraged. - * - * The default values can be used as a reference point for mods adding fluids such as oil or heavy - * water. - * - */ -public class FluidAttributes -{ - public static final int BUCKET_VOLUME = 1000; - - private String translationKey; - - private final ResourceLocation stillTexture; - private final ResourceLocation flowingTexture; - - @Nullable - private final ResourceLocation overlayTexture; - - private final SoundEvent fillSound; - private final SoundEvent emptySound; - - /** - * The light level emitted by this fluid. - * - * Default value is 0, as most fluids do not actively emit light. - */ - private final int luminosity; - - /** - * Density of the fluid - completely arbitrary; negative density indicates that the fluid is - * lighter than air. - * - * Default value is approximately the real-life density of water in kg/m^3. - */ - private final int density; - - /** - * Temperature of the fluid - completely arbitrary; higher temperature indicates that the fluid is - * hotter than air. - * - * Default value is approximately the real-life room temperature of water in degrees Kelvin. - */ - private final int temperature; - - /** - * Viscosity ("thickness") of the fluid - completely arbitrary; negative values are not - * permissible. - * - * Default value is approximately the real-life density of water in m/s^2 (x10^-3). - * - * Higher viscosity means that a fluid flows more slowly, like molasses. - * Lower viscosity means that a fluid flows more quickly, like helium. - * - */ - private final int viscosity; - - /** - * This indicates if the fluid is gaseous. - * - * Generally this is associated with negative density fluids. - */ - private final boolean isGaseous; - - /** - * The rarity of the fluid. - * - * Used primarily in tool tips. - */ - private final Rarity rarity; - - /** - * Color used by universal bucket and the ModelFluid baked model. - * Note that this int includes the alpha so converting this to RGB with alpha would be - * float r = ((color >> 16) & 0xFF) / 255f; // red - * float g = ((color >> 8) & 0xFF) / 255f; // green - * float b = ((color >> 0) & 0xFF) / 255f; // blue - * float a = ((color >> 24) & 0xFF) / 255f; // alpha - */ - private final int color; - - protected FluidAttributes(Builder builder, Fluid fluid) - { - this.translationKey = builder.translationKey != null ? builder.translationKey : Util.makeDescriptionId("fluid", ForgeRegistries.FLUIDS.getKey(fluid)); - this.stillTexture = builder.stillTexture; - this.flowingTexture = builder.flowingTexture; - this.overlayTexture = builder.overlayTexture; - this.color = builder.color; - this.fillSound = builder.fillSound; - this.emptySound = builder.emptySound; - this.luminosity = builder.luminosity; - this.temperature = builder.temperature; - this.viscosity = builder.viscosity; - this.density = builder.density; - this.isGaseous = builder.isGaseous; - this.rarity = builder.rarity; - } - - public ItemStack getBucket(FluidStack stack) - { - return new ItemStack(stack.getFluid().getBucket()); - } - - public BlockState getBlock(BlockAndTintGetter reader, BlockPos pos, FluidState state) - { - return state.createLegacyBlock(); - } - - public FluidState getStateForPlacement(BlockAndTintGetter reader, BlockPos pos, FluidStack state) - { - return state.getFluid().defaultFluidState(); - } - - public final boolean canBePlacedInWorld(BlockAndTintGetter reader, BlockPos pos, FluidState state) - { - return !getBlock(reader, pos, state).isAir(); - } - - public final boolean canBePlacedInWorld(BlockAndTintGetter reader, BlockPos pos, FluidStack state) - { - return !getBlock(reader, pos, getStateForPlacement(reader, pos, state)).isAir(); - } - - public final boolean isLighterThanAir() - { - return this.density <= 0; - } - - /** - * Determines if this fluid should vaporize in dimensions where water vaporizes when placed. - * To preserve the intentions of vanilla, fluids that can turn lava into obsidian should vaporize. - * This prevents players from making the nether safe with a single bucket. - * Based on {@link BucketItem#emptyContents(Player, Level, BlockPos, BlockHitResult)} - * - * @param fluidStack The fluidStack is trying to be placed. - * @return true if this fluid should vaporize in dimensions where water vaporizes when placed. - */ - public boolean doesVaporize(BlockAndTintGetter reader, BlockPos pos, FluidStack fluidStack) - { - BlockState blockstate = getBlock(reader, pos, getStateForPlacement(reader, pos, fluidStack)); - if (blockstate == null) - return false; - return blockstate.getMaterial() == net.minecraft.world.level.material.Material.WATER; - } - - /** - * Called instead of placing the fluid block if {@link DimensionType#ultraWarm()} and {@link #doesVaporize(BlockAndTintGetter, BlockPos, FluidStack)} are true. - * Override this to make your explosive liquid blow up instead of the default smoke, etc. - * Based on {@link BucketItem#emptyContents(Player, Level, BlockPos, BlockHitResult)} - * - * @param player Player who tried to place the fluid. May be null for blocks like dispensers. - * @param worldIn World to vaporize the fluid in. - * @param pos The position in the world the fluid block was going to be placed. - * @param fluidStack The fluidStack that was going to be placed. - */ - public void vaporize(@Nullable Player player, Level worldIn, BlockPos pos, FluidStack fluidStack) - { - worldIn.playSound(player, pos, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (worldIn.random.nextFloat() - worldIn.random.nextFloat()) * 0.8F); - - for (int l = 0; l < 8; ++l) - { - worldIn.addAlwaysVisibleParticle(ParticleTypes.LARGE_SMOKE, (double) pos.getX() + Math.random(), (double) pos.getY() + Math.random(), (double) pos.getZ() + Math.random(), 0.0D, 0.0D, 0.0D); - } - } - - /** - * Returns the localized name of this fluid. - */ - public Component getDisplayName(FluidStack stack) - { - return Component.translatable(getTranslationKey(stack)); - } - - /** - * A FluidStack sensitive version of getTranslationKey - */ - public String getTranslationKey(FluidStack stack) - { - return this.getTranslationKey(); - } - - /** - * Returns the translation key of this fluid. - */ - public String getTranslationKey() - { - return this.translationKey; - } - - /* Default Accessors */ - public final int getLuminosity() - { - return this.luminosity; - } - - public final int getDensity() - { - return this.density; - } - - public final int getTemperature() - { - return this.temperature; - } - - public final int getViscosity() - { - return this.viscosity; - } - - public final boolean isGaseous() - { - return this.isGaseous; - } - - public Rarity getRarity() - { - return rarity; - } - - public int getColor() - { - return color; - } - - public ResourceLocation getStillTexture() - { - return stillTexture; - } - - public ResourceLocation getFlowingTexture() - { - return flowingTexture; - } - - @Nullable - public ResourceLocation getOverlayTexture() - { - return overlayTexture; - } - - public SoundEvent getFillSound() - { - return fillSound; - } - - public SoundEvent getEmptySound() - { - return emptySound; - } - - /* Stack-based Accessors */ - public int getLuminosity(FluidStack stack){ return getLuminosity(); } - public int getDensity(FluidStack stack){ return getDensity(); } - public int getTemperature(FluidStack stack){ return getTemperature(); } - public int getViscosity(FluidStack stack){ return getViscosity(); } - public boolean isGaseous(FluidStack stack){ return isGaseous(); } - public Rarity getRarity(FluidStack stack){ return getRarity(); } - public int getColor(FluidStack stack){ return getColor(); } - public ResourceLocation getStillTexture(FluidStack stack) { return getStillTexture(); } - public ResourceLocation getFlowingTexture(FluidStack stack) { return getFlowingTexture(); } - public SoundEvent getFillSound(FluidStack stack) { return getFillSound(); } - public SoundEvent getEmptySound(FluidStack stack) { return getEmptySound(); } - - /* World-based Accessors */ - public int getLuminosity(BlockAndTintGetter level, BlockPos pos){ return getLuminosity(); } - public int getDensity(BlockAndTintGetter level, BlockPos pos){ return getDensity(); } - public int getTemperature(BlockAndTintGetter level, BlockPos pos){ return getTemperature(); } - public int getViscosity(BlockAndTintGetter level, BlockPos pos){ return getViscosity(); } - public boolean isGaseous(BlockAndTintGetter level, BlockPos pos){ return isGaseous(); } - public Rarity getRarity(BlockAndTintGetter level, BlockPos pos){ return getRarity(); } - public int getColor(BlockAndTintGetter level, BlockPos pos){ return getColor(); } - public ResourceLocation getStillTexture(BlockAndTintGetter level, BlockPos pos) { return getStillTexture(); } - public ResourceLocation getFlowingTexture(BlockAndTintGetter level, BlockPos pos) { return getFlowingTexture(); } - public SoundEvent getFillSound(BlockAndTintGetter level, BlockPos pos) { return getFillSound(); } - public SoundEvent getEmptySound(BlockAndTintGetter level, BlockPos pos) { return getEmptySound(); } - - public static Builder builder(ResourceLocation stillTexture, ResourceLocation flowingTexture) { - return new Builder(stillTexture, flowingTexture, FluidAttributes::new); - } - - public Stream getTextures() - { - if (overlayTexture != null) - return Stream.of(stillTexture, flowingTexture, overlayTexture); - return Stream.of(stillTexture, flowingTexture); - } - - public static class Builder - { - private final ResourceLocation stillTexture; - private final ResourceLocation flowingTexture; - private ResourceLocation overlayTexture; - private int color = 0xFFFFFFFF; - private String translationKey; - private SoundEvent fillSound; - private SoundEvent emptySound; - private int luminosity = 0; - private int density = 1000; - private int temperature = 300; - private int viscosity = 1000; - private boolean isGaseous; - private Rarity rarity = Rarity.COMMON; - private BiFunction factory; - - protected Builder(ResourceLocation stillTexture, ResourceLocation flowingTexture, BiFunction factory) { - this.factory = factory; - this.stillTexture = stillTexture; - this.flowingTexture = flowingTexture; - } - - public final Builder translationKey(String translationKey) - { - this.translationKey = translationKey; - return this; - } - - public final Builder color(int color) - { - this.color = color; - return this; - } - - public final Builder overlay(ResourceLocation texture) - { - overlayTexture = texture; - return this; - } - - public final Builder luminosity(int luminosity) - { - this.luminosity = luminosity; - return this; - } - - public final Builder density(int density) - { - this.density = density; - return this; - } - - public final Builder temperature(int temperature) - { - this.temperature = temperature; - return this; - } - - public final Builder viscosity(int viscosity) - { - this.viscosity = viscosity; - return this; - } - - public final Builder gaseous() - { - isGaseous = true; - return this; - } - - public final Builder rarity(Rarity rarity) - { - this.rarity = rarity; - return this; - } - - public final Builder sound(SoundEvent sound) - { - this.fillSound = this.emptySound = sound; - return this; - } - - public final Builder sound(SoundEvent fillSound, SoundEvent emptySound) - { - this.fillSound = fillSound; - this.emptySound = emptySound; - return this; - } - - public FluidAttributes build(Fluid fluid) - { - return factory.apply(this, fluid); - } - } - - public static class Water extends FluidAttributes - { - protected Water(Builder builder, Fluid fluid) - { - super(builder, fluid); - } - - @Override - public int getColor(BlockAndTintGetter level, BlockPos pos) - { - return BiomeColors.getAverageWaterColor(level, pos) | 0xFF000000; - } - - public static Builder builder(ResourceLocation stillTexture, ResourceLocation flowingTexture) { - return new Builder(stillTexture, flowingTexture, Water::new); - } - } -} diff --git a/src/main/java/net/minecraftforge/fluids/FluidInteractionRegistry.java b/src/main/java/net/minecraftforge/fluids/FluidInteractionRegistry.java new file mode 100644 index 0000000000..8df199e8ca --- /dev/null +++ b/src/main/java/net/minecraftforge/fluids/FluidInteractionRegistry.java @@ -0,0 +1,184 @@ +package net.minecraftforge.fluids; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraftforge.common.ForgeMod; +import net.minecraftforge.event.ForgeEventFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * A registry which defines the interactions a source fluid can have with its + * surroundings. Each possible flow direction is checked for all interactions with + * the source. + * + *

Fluid interactions mimic the behavior of {@code LiquidBlock#shouldSpreadLiquid}. + * As such, all directions, besides {@link Direction#DOWN} is tested and then replaced. + * Any fluids which cause a change in the down interaction must be handled in + * {@code FlowingFluid#spreadTo} and not by this interaction manager. + */ +public final class FluidInteractionRegistry +{ + private static final Map> INTERACTIONS = new HashMap<>(); + + /** + * Adds an interaction between a source and its surroundings. + * + * @param source the source of the interaction, this will be replaced if the interaction occurs + * @param interaction the interaction data to check and perform + */ + public static synchronized void addInteraction(FluidType source, InteractionInformation interaction) + { + INTERACTIONS.computeIfAbsent(source, s -> new ArrayList<>()).add(interaction); + } + + /** + * Performs all potential fluid interactions at a given position. + * + *

Note: Only the first interaction check that succeeds will occur. + * + * @param level the level the interactions take place in + * @param pos the position of the source fluid + * @return {@code true} if an interaction took place, {@code false} otherwise + */ + public static boolean canInteract(Level level, BlockPos pos) + { + FluidState state = level.getFluidState(pos); + for (Direction direction : LiquidBlock.POSSIBLE_FLOW_DIRECTIONS) + { + BlockPos relativePos = pos.relative(direction.getOpposite()); + List interactions = INTERACTIONS.getOrDefault(state.getFluidType(), Collections.emptyList()); + for (InteractionInformation interaction : interactions) + { + if (interaction.predicate().test(level, pos, relativePos, state)) + { + interaction.interaction().interact(level, pos, relativePos, state); + return true; + } + } + } + + return false; + } + + static + { + // Lava + Water = Obsidian (Source Lava) / Cobblestone (Flowing Lava) + addInteraction(ForgeMod.LAVA_TYPE.get(), new InteractionInformation( + ForgeMod.WATER_TYPE.get(), + fluidState -> fluidState.isSource() ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.COBBLESTONE.defaultBlockState() + )); + + // Lava + Soul Soil (Below) + Blue Ice = Basalt + addInteraction(ForgeMod.LAVA_TYPE.get(), new InteractionInformation( + (level, currentPos, relativePos, currentState) -> level.getBlockState(currentPos.below()).is(Blocks.SOUL_SOIL) && level.getBlockState(relativePos).is(Blocks.BLUE_ICE), + Blocks.BASALT.defaultBlockState() + )); + } + + /** + * Holds the interaction data for a given source type on when to succeed + * and what to perform. + * + * @param predicate a test to see whether an interaction can occur + * @param interaction the interaction to perform + */ + public record InteractionInformation(HasFluidInteraction predicate, FluidInteraction interaction) + { + /** + * Constructor which checks the surroundings fluids for a specific type + * and then transforms the source state into a block. + * + * @param type the type of the fluid that must be surrounding the source + * @param state the state of the block replacing the source + */ + public InteractionInformation(FluidType type, BlockState state) + { + this(type, fluidState -> state); + } + + /** + * Constructor which transforms the source state into a block. + * + * @param predicate a test to see whether an interaction can occur + * @param state the state of the block replacing the source + */ + public InteractionInformation(HasFluidInteraction predicate, BlockState state) + { + this(predicate, fluidState -> state); + } + + /** + * Constructor which checks the surroundings fluids for a specific type + * and then transforms the source state into a block. + * + * @param type the type of the fluid that must be surrounding the source + * @param getState a function to transform the source fluid into a block state + */ + public InteractionInformation(FluidType type, Function getState) + { + this((level, currentPos, relativePos, currentState) -> level.getFluidState(relativePos).getFluidType() == type, getState); + } + + /** + * Constructor which transforms the source state into a block. + * + * @param predicate a test to see whether an interaction can occur + * @param getState a function to transform the source fluid into a block state + */ + public InteractionInformation(HasFluidInteraction predicate, Function getState) + { + this(predicate, (level, currentPos, relativePos, currentState) -> + { + level.setBlockAndUpdate(currentPos, ForgeEventFactory.fireFluidPlaceBlockEvent(level, currentPos, currentPos, getState.apply(currentState))); + level.levelEvent(1501, currentPos, 0); + }); + } + } + + /** + * An interface which tests whether a source fluid can interact with its + * surroundings. + */ + @FunctionalInterface + public interface HasFluidInteraction + { + /** + * Returns whether the interaction can occur. + * + * @param level the level the interaction takes place in + * @param currentPos the position of the source + * @param relativePos a position surrounding the source + * @param currentState the state of the fluid surrounding the source + * @return {@code true} if an interaction can occur, {@code false} otherwise + */ + boolean test(Level level, BlockPos currentPos, BlockPos relativePos, FluidState currentState); + } + + /** + * An interface which performs an interaction for a source. + */ + @FunctionalInterface + public interface FluidInteraction + { + /** + * Performs the interaction between the source and the surrounding data. + * + * @param level the level the interaction takes place in + * @param currentPos the position of the source + * @param relativePos a position surrounding the source + * @param currentState the state of the fluid surrounding the source + */ + void interact(Level level, BlockPos currentPos, BlockPos relativePos, FluidState currentState); + } +} diff --git a/src/main/java/net/minecraftforge/fluids/FluidStack.java b/src/main/java/net/minecraftforge/fluids/FluidStack.java index b72efd5b61..838ba95687 100644 --- a/src/main/java/net/minecraftforge/fluids/FluidStack.java +++ b/src/main/java/net/minecraftforge/fluids/FluidStack.java @@ -234,12 +234,12 @@ public class FluidStack public Component getDisplayName() { - return this.getFluid().getAttributes().getDisplayName(this); + return this.getFluid().getFluidType().getDescription(this); } public String getTranslationKey() { - return this.getFluid().getAttributes().getTranslationKey(this); + return this.getFluid().getFluidType().getDescriptionId(this); } /** diff --git a/src/main/java/net/minecraftforge/fluids/FluidType.java b/src/main/java/net/minecraftforge/fluids/FluidType.java new file mode 100644 index 0000000000..577b011367 --- /dev/null +++ b/src/main/java/net/minecraftforge/fluids/FluidType.java @@ -0,0 +1,1151 @@ +package net.minecraftforge.fluids; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.vehicle.Boat; +import net.minecraft.world.item.BucketItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Rarity; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.pathfinder.BlockPathTypes; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.client.IFluidTypeRenderProperties; +import net.minecraftforge.common.ForgeMod; +import net.minecraftforge.common.SoundAction; +import net.minecraftforge.common.SoundActions; +import net.minecraftforge.common.util.Lazy; +import net.minecraftforge.registries.ForgeRegistries; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +/** + * A definition of common attributes, properties, and methods that is applied + * to a {@link Fluid}. This is used to link a flowing and source fluid together + * without relying on tags. Most accessors do not correlate to in-game features; + * they are provided for mods to take advantage of. + * + *

Accessors are typically implemented in a method call chain. As such, it + * can provide a general implementation while more specific implementations + * can be implemented by overriding methods further in the call chain (on fluids, + * entities, etc.). + */ +public class FluidType +{ + /** + * The number of fluid units that a bucket represents. + */ + public static final int BUCKET_VOLUME = 1000; + + /** + * A lazy value which computes the number of fluid types within the + * registry. + */ + public static final Lazy SIZE = Lazy.of(() -> ForgeRegistries.FLUID_TYPES.get().getKeys().size()); + + private String descriptionId; + private final double motionScale; + private final boolean canPushEntity; + private final boolean canSwim; + private final boolean canDrown; + private final float fallDistanceModifier; + private final boolean canExtinguish; + private final boolean canConvertToSource; + private final boolean supportsBoating; + @Nullable + private final BlockPathTypes pathType, adjacentPathType; + private final boolean canHydrate; + private final int lightLevel; + private final int density; + private final int temperature; + private final int viscosity; + private final Rarity rarity; + + /** + * A map of actions performed to sound that should be played. + */ + protected final Map sounds; + + /** + * Default constructor. + * + * @param properties the general properties of the fluid type + */ + public FluidType(final Properties properties) + { + this.descriptionId = properties.descriptionId; + this.motionScale = properties.motionScale; + this.canPushEntity = properties.canPushEntity; + this.canSwim = properties.canSwim; + this.canDrown = properties.canDrown; + this.fallDistanceModifier = properties.fallDistanceModifier; + this.canExtinguish = properties.canExtinguish; + this.canConvertToSource = properties.canConvertToSource; + this.supportsBoating = properties.supportsBoating; + this.pathType = properties.pathType; + this.adjacentPathType = properties.adjacentPathType; + this.sounds = ImmutableMap.copyOf(properties.sounds); + this.canHydrate = properties.canHydrate; + this.lightLevel = properties.lightLevel; + this.density = properties.density; + this.temperature = properties.temperature; + this.viscosity = properties.viscosity; + this.rarity = properties.rarity; + + this.initClient(); + } + + /* Default Accessors */ + + /** + * Returns the component representing the name of the fluid type. + * + * @return the component representing the name of the fluid type + */ + public Component getDescription() + { + return Component.translatable(this.getDescriptionId()); + } + + /** + * Returns the identifier representing the name of the fluid type. + * If no identifier was specified, then the identifier will be defaulted + * to {@code fluid_type..}. + * + * @return the identifier representing the name of the fluid type + */ + public String getDescriptionId() + { + if (this.descriptionId == null) + this.descriptionId = Util.makeDescriptionId("fluid_type", ForgeRegistries.FLUID_TYPES.get().getKey(this)); + return this.descriptionId; + } + + /** + * Returns the light level emitted by the fluid. + * + *

Note: This should be a value between {@code [0,15]}. If not specified, the + * light level is {@code 0} as most fluids do not emit light. + * + *

Implementation: This is used by the bucket model to determine whether the fluid + * should render full-bright when {@code applyFluidLuminosity} is {@code true}. + * + * @return the light level emitted by the fluid + */ + public int getLightLevel() + { + return this.lightLevel; + } + + /** + * Returns the density of the fluid. + * + *

Note: This is an arbitrary number. Negative or zero values indicate + * that the fluid is lighter than air. If not specified, the density is + * approximately equivalent to the real-life density of water in {@code kg/m^3}. + * + * @return the density of the fluid + */ + public int getDensity() + { + return this.density; + } + + /** + * Returns the temperature of the fluid. + * + *

Note: This is an arbitrary number. Higher temperature values indicate + * that the fluid is hotter. If not specified, the temperature is approximately + * equivalent to the real-life room temperature of water in {@code Kelvin}. + * + * @return the temperature of the fluid + */ + public int getTemperature() + { + return this.temperature; + } + + /** + * Returns the viscosity, or thickness, of the fluid. + * + *

Note: This is an arbitrary number. The value should never be negative. + * Higher viscosity values indicate that the fluid flows more slowly. If not + * specified, the viscosity is approximately equivalent to the real-life + * viscosity of water in {@code m/s^2}. + * + * @return the viscosity of the fluid + */ + public int getViscosity() + { + return this.viscosity; + } + + /** + * Returns the rarity of the fluid. + * + *

Note: If not specified, the rarity of the fluid is {@link Rarity#COMMON}. + * + * @return the rarity of the fluid + */ + public Rarity getRarity() + { + return this.rarity; + } + + /** + * Returns a sound to play when a certain action is performed. If no + * sound is present, then the sound will be {@code null}. + * + * @param action the action being performed + * @return the sound to play when performing the action + */ + @Nullable + public SoundEvent getSound(SoundAction action) + { + return this.sounds.get(action); + } + + /* Entity-Based Accessors */ + + /** + * Returns how much the velocity of the fluid should be scaled by + * when applied to an entity. + * + * @param entity the entity in the fluid + * @return a scalar to multiply to the fluid velocity + */ + public double motionScale(Entity entity) + { + return this.motionScale; + } + + /** + * Returns whether the fluid can push an entity. + * + * @param entity the entity in the fluid + * @return {@code true} if the entity can be pushed by the fluid, {@code false} otherwise + */ + public boolean canPushEntity(Entity entity) + { + return this.canPushEntity; + } + + /** + * Returns whether the entity can swim in the fluid. + * + * @param entity the entity in the fluid + * @return {@code true} if the entity can swim in the fluid, {@code false} otherwise + */ + public boolean canSwim(Entity entity) + { + return this.canSwim; + } + + /** + * Returns how much the fluid should scale the damage done to a falling + * entity when hitting the ground per tick. + * + *

Implementation: If the entity is in many fluids, the smallest modifier + * is applied. + * + * @param entity the entity in the fluid + * @return a scalar to multiply to the fall damage + */ + public float getFallDistanceModifier(Entity entity) + { + return this.fallDistanceModifier; + } + + /** + * Returns whether the entity can be extinguished by this fluid. + * + * @param entity the entity in the fluid + * @return {@code true} if the entity can be extinguished, {@code false} otherwise + */ + public boolean canExtinguish(Entity entity) + { + return this.canExtinguish; + } + + /** + * Performs how an entity moves when within the fluid. If using custom + * movement logic, the method should return {@code true}. Otherwise, the + * movement logic will default to water. + * + * @param state the state of the fluid + * @param entity the entity moving within the fluid + * @param movementVector the velocity of how the entity wants to move + * @param gravity the gravity to apply to the entity + * @return {@code true} if custom movement logic is performed, {@code false} otherwise + */ + public boolean move(FluidState state, LivingEntity entity, Vec3 movementVector, double gravity) + { + return false; + } + + /** + * Returns whether the entity can drown in the fluid. + * + * @param entity the entity in the fluid + * @return {@code true} if the entity can drown in the fluid, {@code false} otherwise + */ + public boolean canDrownIn(LivingEntity entity) + { + return this.canDrown; + } + + /** + * Performs what to do when an item is in a fluid. + * + * @param entity the item in the fluid + */ + public void setItemMovement(ItemEntity entity) + { + Vec3 vec3 = entity.getDeltaMovement(); + entity.setDeltaMovement(vec3.x * (double)0.99F, vec3.y + (double)(vec3.y < (double)0.06F ? 5.0E-4F : 0.0F), vec3.z * (double)0.99F); + } + + /** + * Returns whether the boat can be used on the fluid. + * + * @param boat the boat trying to be used on the fluid + * @return {@code true} if the boat can be used, {@code false} otherwise + */ + public boolean supportsBoating(Boat boat) + { + return this.supportsBoating; + } + + /** + * Returns whether the boat can be used on the fluid. + * + * @param state the state of the fluid + * @param boat the boat trying to be used on the fluid + * @return {@code true} if the boat can be used, {@code false} otherwise + */ + public boolean supportsBoating(FluidState state, Boat boat) + { + return this.supportsBoating(boat); + } + + /** + * Returns whether the entity can ride in this vehicle under the fluid. + * + * @param vehicle the vehicle being ridden in + * @param rider the entity riding the vehicle + * @return {@code true} if the vehicle can be ridden in under this fluid, + * {@code false} otherwise + */ + public boolean canRideVehicleUnder(Entity vehicle, Entity rider) + { + if (this == ForgeMod.WATER_TYPE.get()) return vehicle.rideableUnderWater(); + return true; + } + + /** + * Returns whether the entity can be hydrated by this fluid. + * + *

Hydration is an arbitrary word which depends on the entity. + * + * @param entity the entity in the fluid + * @return {@code true} if the entity can be hydrated, {@code false} + * otherwise + */ + public boolean canHydrate(Entity entity) + { + return this.canHydrate; + } + + /** + * Returns a sound to play when a certain action is performed by the + * entity in the fluid. If no sound is present, then the sound will be + * {@code null}. + * + * @param entity the entity in the fluid + * @param action the action being performed + * @return the sound to play when performing the action + */ + @Nullable + public SoundEvent getSound(Entity entity, SoundAction action) + { + return this.getSound(action); + } + + /* Level-Based Accessors */ + + /** + * Returns whether the block can be extinguished by this fluid. + * + * @param state the state of the fluid + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @return {@code true} if the block can be extinguished, {@code false} otherwise + */ + public boolean canExtinguish(FluidState state, BlockGetter getter, BlockPos pos) + { + return this.canExtinguish; + } + + /** + * Returns whether the fluid can create a source. + * + * @param state the state of the fluid + * @param reader the reader that can get the fluid + * @param pos the location of the fluid + * @return {@code true} if the fluid can create a source, {@code false} otherwise + */ + public boolean canConvertToSource(FluidState state, LevelReader reader, BlockPos pos) + { + return this.canConvertToSource; + } + + /** + * Gets the path type of this fluid when an entity is pathfinding. When + * {@code null}, uses vanilla behavior. + * + * @param state the state of the fluid + * @param level the level which contains this fluid + * @param pos the position of the fluid + * @param mob the mob currently pathfinding, may be {@code null} + * @param canFluidLog {@code true} if the path is being applied for fluids that can log blocks, + * should be checked against if the fluid can log a block + * @return the path type of this fluid + */ + @Nullable + public BlockPathTypes getBlockPathType(FluidState state, BlockGetter level, BlockPos pos, @Nullable Mob mob, boolean canFluidLog) + { + return this.pathType; + } + + /** + * Gets the path type of the adjacent fluid to a pathfinding entity. + * Path types with a negative malus are not traversable for the entity. + * Pathfinding entities will favor paths consisting of a lower malus. + * When {@code null}, uses vanilla behavior. + * + * @param state the state of the fluid + * @param level the level which contains this fluid + * @param pos the position of the fluid + * @param mob the mob currently pathfinding, may be {@code null} + * @param originalType the path type of the source the entity is on + * @return the path type of this fluid + */ + @Nullable + public BlockPathTypes getAdjacentBlockPathType(FluidState state, BlockGetter level, BlockPos pos, @Nullable Mob mob, BlockPathTypes originalType) + { + return this.adjacentPathType; + } + + /** + * Returns a sound to play when a certain action is performed at a + * position. If no sound is present, then the sound will be {@code null}. + * + * @param player the player listening to the sound + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @param action the action being performed + * @return the sound to play when performing the action + */ + @Nullable + public SoundEvent getSound(@Nullable Player player, BlockGetter getter, BlockPos pos, SoundAction action) + { + return this.getSound(action); + } + + /** + * Returns whether the block can be hydrated by a fluid. + * + *

Hydration is an arbitrary word which depends on the block. + *

    + *
  • A farmland has moisture
  • + *
  • A sponge can soak up the liquid
  • + *
  • A coral can live
  • + *
+ * + * @param state the state of the fluid + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @param source the state of the block being hydrated + * @param sourcePos the position of the block being hydrated + * @return {@code true} if the block can be hydrated, {@code false} otherwise + */ + public boolean canHydrate(FluidState state, BlockGetter getter, BlockPos pos, BlockState source, BlockPos sourcePos) + { + return this.canHydrate; + } + + /** + * Returns the light level emitted by the fluid. + * + *

Note: This should be a value between {@code [0,15]}. If not specified, the + * light level is {@code 0} as most fluids do not emit light. + * + * @param state the state of the fluid + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @return the light level emitted by the fluid + */ + public int getLightLevel(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return this.getLightLevel(); + } + + /** + * Returns the density of the fluid. + * + *

Note: This is an arbitrary number. Negative or zero values indicate + * that the fluid is lighter than air. If not specified, the density is + * approximately equivalent to the real-life density of water in {@code kg/m^3}. + * + * @param state the state of the fluid + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @return the density of the fluid + */ + public int getDensity(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return this.getDensity(); + } + + /** + * Returns the temperature of the fluid. + * + *

Note: This is an arbitrary number. Higher temperature values indicate + * that the fluid is hotter. If not specified, the temperature is approximately + * equivalent to the real-life room temperature of water in {@code Kelvin}. + * + * @param state the state of the fluid + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @return the temperature of the fluid + */ + public int getTemperature(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return this.getTemperature(); + } + + /** + * Returns the viscosity, or thickness, of the fluid. + * + *

Note: This is an arbitrary number. The value should never be negative. + * Higher viscosity values indicate that the fluid flows more slowly. If not + * specified, the viscosity is approximately equivalent to the real-life + * viscosity of water in {@code m/s^2}. + * + * @param state the state of the fluid + * @param getter the getter which can get the fluid + * @param pos the position of the fluid + * @return the viscosity of the fluid + */ + public int getViscosity(FluidState state, BlockAndTintGetter getter, BlockPos pos) + { + return this.getViscosity(); + } + + /* Stack-Based Accessors */ + + /** + * Returns whether the fluid can create a source. + * + * @param stack the stack holding the fluid + * @return {@code true} if the fluid can create a source, {@code false} otherwise + */ + public boolean canConvertToSource(FluidStack stack) + { + return this.canConvertToSource; + } + + /** + * Returns a sound to play when a certain action is performed. If no + * sound is present, then the sound will be {@code null}. + * + * @param stack the stack holding the fluid + * @param action the action being performed + * @return the sound to play when performing the action + */ + @Nullable + public SoundEvent getSound(FluidStack stack, SoundAction action) + { + return this.getSound(action); + } + + /** + * Returns the component representing the name of the fluid type. + * + * @param stack the stack holding the fluid + * @return the component representing the name of the fluid type + */ + public Component getDescription(FluidStack stack) + { + return Component.translatable(this.getDescriptionId(stack)); + } + + /** + * Returns the identifier representing the name of the fluid. + * If no identifier was specified, then the identifier will be defaulted + * to {@code fluid_type..}. + * + * @param stack the stack holding the fluid + * @return the identifier representing the name of the fluid + */ + public String getDescriptionId(FluidStack stack) + { + return this.getDescriptionId(); + } + + /** + * Returns whether the fluid can hydrate. + * + *

Hydration is an arbitrary word which depends on the implementation. + * + * @param stack the stack holding the fluid + * @return {@code true} if the fluid can hydrate, {@code false} otherwise + */ + public boolean canHydrate(FluidStack stack) + { + return this.canHydrate; + } + + /** + * Returns the light level emitted by the fluid. + * + *

Note: This should be a value between {@code [0,15]}. If not specified, the + * light level is {@code 0} as most fluids do not emit light. + * + * @param stack the stack holding the fluid + * @return the light level emitted by the fluid + */ + public int getLightLevel(FluidStack stack) + { + return this.getLightLevel(); + } + + /** + * Returns the density of the fluid. + * + *

Note: This is an arbitrary number. Negative or zero values indicate + * that the fluid is lighter than air. If not specified, the density is + * approximately equivalent to the real-life density of water in {@code kg/m^3}. + * + * @param stack the stack holding the fluid + * @return the density of the fluid + */ + public int getDensity(FluidStack stack) + { + return this.getDensity(); + } + + /** + * Returns the temperature of the fluid. + * + *

Note: This is an arbitrary number. Higher temperature values indicate + * that the fluid is hotter. If not specified, the temperature is approximately + * equivalent to the real-life room temperature of water in {@code Kelvin}. + * + * @param stack the stack holding the fluid + * @return the temperature of the fluid + */ + public int getTemperature(FluidStack stack) + { + return this.getTemperature(); + } + + /** + * Returns the viscosity, or thickness, of the fluid. + * + *

Note: This is an arbitrary number. The value should never be negative. + * Higher viscosity values indicate that the fluid flows more slowly. If not + * specified, the viscosity is approximately equivalent to the real-life + * viscosity of water in {@code m/s^2}. + * + * @param stack the stack holding the fluid + * @return the viscosity of the fluid + */ + public int getViscosity(FluidStack stack) + { + return this.getViscosity(); + } + + /** + * Returns the rarity of the fluid. + * + *

Note: If not specified, the rarity of the fluid is {@link Rarity#COMMON}. + * + * @param stack the stack holding the fluid + * @return the rarity of the fluid + */ + public Rarity getRarity(FluidStack stack) + { + return this.getRarity(); + } + + /* Helper Methods */ + + /** + * Returns whether the fluid type represents air. + * + * @return {@code true} if the type represents air, {@code false} otherwise + */ + public final boolean isAir() + { + return this == ForgeMod.EMPTY_TYPE.get(); + } + + /** + * Returns whether the fluid type is from vanilla. + * + * @return {@code true} if the type is from vanilla, {@code false} otherwise + */ + public final boolean isVanilla() + { + return this == ForgeMod.LAVA_TYPE.get() || this == ForgeMod.WATER_TYPE.get(); + } + + /** + * Returns the bucket containing the fluid. + * + * @param stack the stack holding the fluid + * @return the bucket containing the fluid + */ + public ItemStack getBucket(FluidStack stack) + { + return new ItemStack(stack.getFluid().getBucket()); + } + + /** + * Returns the associated {@link BlockState} for a {@link FluidState}. + * + * @param getter the getter which can get the level data + * @param pos the position of where the fluid would be + * @param state the state of the fluid + * @return the {@link BlockState} of a fluid + */ + public BlockState getBlockForFluidState(BlockAndTintGetter getter, BlockPos pos, FluidState state) + { + return state.createLegacyBlock(); + } + + /** + * Returns the {@link FluidState} when a {@link FluidStack} is trying to + * place it. + * + * @param getter the getter which can get the level data + * @param pos the position of where the fluid is being placed + * @param stack the stack holding the fluid + * @return the {@link FluidState} being placed + */ + public FluidState getStateForPlacement(BlockAndTintGetter getter, BlockPos pos, FluidStack stack) + { + return stack.getFluid().defaultFluidState(); + } + + /** + * Returns whether the fluid can be placed in the level. + * + * @param getter the getter which can get the level data + * @param pos the position of where the fluid is being placed + * @param state the state of the fluid being placed + * @return {@code true} if the fluid can be placed, {@code false} otherwise + */ + public final boolean canBePlacedInLevel(BlockAndTintGetter getter, BlockPos pos, FluidState state) + { + return !this.getBlockForFluidState(getter, pos, state).isAir(); + } + + /** + * Returns whether the fluid can be placed in the level. + * + * @param getter the getter which can get the level data + * @param pos the position of where the fluid is being placed + * @param stack the stack holding the fluid + * @return {@code true} if the fluid can be placed, {@code false} otherwise + */ + public final boolean canBePlacedInLevel(BlockAndTintGetter getter, BlockPos pos, FluidStack stack) + { + return this.canBePlacedInLevel(getter, pos, this.getStateForPlacement(getter, pos, stack)); + } + + /** + * Returns whether a fluid is lighter than air. If the fluid's density + * is lower than or equal {@code 0}, the fluid is considered lighter than air. + * + *

Tip: {@code 0} is the "canonical" density of air within Forge. + * + *

Note: Fluids lighter than air will have their bucket model rotated + * upside-down; fluid block models will have their vertices inverted. + * + * @return {@code true} if the fluid is lighter than air, {@code false} otherwise + */ + public final boolean isLighterThanAir() + { + return this.getDensity() <= 0; + } + + /** + * Determines if this fluid should be vaporized when placed into a level. + * + *

Note: Fluids that can turn lava into obsidian should vaporize within + * the nether to preserve the intentions of vanilla. + * + * @param level the level the fluid is being placed in + * @param pos the position to place the fluid at + * @param stack the stack holding the fluid being placed + * @return {@code true} if this fluid should be vaporized on placement, {@code false} otherwise + * + * @see BucketItem#emptyContents(Player, Level, BlockPos, BlockHitResult) + */ + public boolean isVaporizedOnPlacement(Level level, BlockPos pos, FluidStack stack) + { + if (level.dimensionType().ultraWarm()) + { + BlockState state = this.getBlockForFluidState(level, pos, this.getStateForPlacement(level, pos, stack)); + return state != null && state.getMaterial() == Material.WATER; + } + return false; + } + + /** + * Performs an action when a fluid can be vaporized when placed into a level. + * + *

Note: The fluid will already have been drained from the stack. + * + * @param player the player placing the fluid, may be {@code null} for blocks like dispensers + * @param level the level the fluid is vaporized in + * @param pos the position the fluid is vaporized at + * @param stack the stack holding the fluid being vaporized + * + * @see BucketItem#emptyContents(Player, Level, BlockPos, BlockHitResult) + */ + public void onVaporize(@Nullable Player player, Level level, BlockPos pos, FluidStack stack) + { + SoundEvent sound = this.getSound(player, level, pos, SoundActions.FLUID_VAPORIZE); + level.playSound(player, pos, sound != null ? sound : SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (level.random.nextFloat() - level.random.nextFloat()) * 0.8F); + + for (int l = 0; l < 8; ++l) + level.addAlwaysVisibleParticle(ParticleTypes.LARGE_SMOKE, (double) pos.getX() + Math.random(), (double) pos.getY() + Math.random(), (double) pos.getZ() + Math.random(), 0.0D, 0.0D, 0.0D); + } + + @Override + public String toString() { + @Nullable ResourceLocation name = ForgeRegistries.FLUID_TYPES.get().getKey(this); + return name != null ? name.toString() : "Unregistered FluidType"; + } + + private Object renderProperties; + + /* + DO NOT CALL, IT WILL DISAPPEAR IN THE FUTURE + Call RenderProperties.get instead + */ + public Object getRenderPropertiesInternal() + { + return renderProperties; + } + + private void initClient() + { + // Minecraft instance isn't available in datagen, so don't call initializeClient if in datagen + if (net.minecraftforge.fml.loading.FMLEnvironment.dist == net.minecraftforge.api.distmarker.Dist.CLIENT && !net.minecraftforge.fml.loading.FMLLoader.getLaunchHandler().isData()) + { + initializeClient(properties -> + { + if (properties == this) + throw new IllegalStateException("Don't extend IFluidTypeRenderProperties in your fluid type, use an anonymous class instead."); + this.renderProperties = properties; + }); + } + } + + public void initializeClient(Consumer consumer) + { + } + + /** + * The properties of the fluid. The simple forms of each property can + * be specified while more complex logic can be overridden in the {@link FluidType}. + */ + public static final class Properties + { + private String descriptionId; + private double motionScale = 0.014D; + private boolean canPushEntity = true; + private boolean canSwim = true; + private boolean canDrown = true; + private float fallDistanceModifier = 0.5F; + private boolean canExtinguish = false; + private boolean canConvertToSource = false; + private boolean supportsBoating = false; + @Nullable + private BlockPathTypes pathType = BlockPathTypes.WATER, + adjacentPathType = BlockPathTypes.WATER_BORDER; + private final Map sounds = new HashMap<>(); + private boolean canHydrate = false; + private int lightLevel = 0, + density = 1000, + temperature = 300, + viscosity = 1000; + private Rarity rarity = Rarity.COMMON; + + private Properties() {} + + /** + * Creates a new instance of the properties. + * + * @return the property holder instance + */ + public static Properties create() + { + return new Properties(); + } + + /** + * Sets the identifier representing the name of the fluid type. + * + * @param descriptionId the identifier representing the name of the fluid type + * @return the property holder instance + */ + public Properties descriptionId(String descriptionId) + { + this.descriptionId = descriptionId; + return this; + } + + /** + * Sets how much the velocity of the fluid should be scaled by. + * + * @param motionScale a scalar to multiply to the fluid velocity + * @return the property holder instance + */ + public Properties motionScale(double motionScale) + { + this.motionScale = motionScale; + return this; + } + + /** + * Sets whether the fluid can push an entity. + * + * @param canPushEntity if the fluid can push an entity + * @return the property holder instance + */ + public Properties canPushEntity(boolean canPushEntity) + { + this.canPushEntity = canPushEntity; + return this; + } + + /** + * Sets whether the fluid can be swum in. + * + * @param canSwim if the fluid can be swum in + * @return the property holder instance + */ + public Properties canSwim(boolean canSwim) + { + this.canSwim = canSwim; + return this; + } + + /** + * Sets whether the fluid can drown something. + * + * @param canDrown if the fluid can drown something + * @return the property holder instance + */ + public Properties canDrown(boolean canDrown) + { + this.canDrown = canDrown; + return this; + } + + /** + * Sets how much the fluid should scale the damage done when hitting + * the ground per tick. + * + * @param fallDistanceModifier a scalar to multiply to the fall damage + * @return the property holder instance + */ + public Properties fallDistanceModifier(float fallDistanceModifier) + { + this.fallDistanceModifier = fallDistanceModifier; + return this; + } + + /** + * Sets whether the fluid can extinguish. + * + * @param canExtinguish if the fluid can extinguish + * @return the property holder instance + */ + public Properties canExtinguish(boolean canExtinguish) + { + this.canExtinguish = canExtinguish; + return this; + } + + /** + * Sets whether the fluid can create a source. + * + * @param canConvertToSource if the fluid can create a source + * @return the property holder instance + */ + public Properties canConvertToSource(boolean canConvertToSource) + { + this.canConvertToSource = canConvertToSource; + return this; + } + + /** + * Sets whether the fluid supports boating. + * + * @param supportsBoating if the fluid supports boating + * @return the property holder instance + */ + public Properties supportsBoating(boolean supportsBoating) + { + this.supportsBoating = supportsBoating; + return this; + } + + /** + * Sets the path type of this fluid. + * + * @param pathType the path type of this fluid + * @return the property holder instance + */ + public Properties pathType(@Nullable BlockPathTypes pathType) + { + this.pathType = pathType; + return this; + } + + /** + * Sets the path type of the adjacent fluid. Path types with a negative + * malus are not traversable. Pathfinding will favor paths consisting of + * a lower malus. + * + * @param adjacentPathType the path type of this fluid + * @return the property holder instance + */ + public Properties adjacentPathType(@Nullable BlockPathTypes adjacentPathType) + { + this.adjacentPathType = adjacentPathType; + return this; + } + + /** + * Sets a sound to play when a certain action is performed. + * + * @param action the action being performed + * @param sound the sound to play when performing the action + * @return the property holder instance + */ + public Properties sound(SoundAction action, SoundEvent sound) + { + this.sounds.put(action, sound); + return this; + } + + /** + * Sets whether the fluid can hydrate. + * + *

Hydration is an arbitrary word which depends on the implementation. + * + * @param canHydrate if the fluid can hydrate + * @return the property holder instance + */ + public Properties canHydrate(boolean canHydrate) + { + this.canHydrate = canHydrate; + return this; + } + + /** + * Sets the light level emitted by the fluid. + * + * @param lightLevel the light level emitted by the fluid + * @return the property holder instance + * @throws IllegalArgumentException if light level is not between [0,15] + */ + public Properties lightLevel(int lightLevel) + { + if (lightLevel < 0 || lightLevel > 15) + throw new IllegalArgumentException("The light level should be between [0,15]."); + this.lightLevel = lightLevel; + return this; + } + + /** + * Sets the density of the fluid. + * + * @param density the density of the fluid + * @return the property holder instance + */ + public Properties density(int density) + { + this.density = density; + return this; + } + + /** + * Sets the temperature of the fluid. + * + * @param temperature the temperature of the fluid + * @return the property holder instance + */ + public Properties temperature(int temperature) + { + this.temperature = temperature; + return this; + } + + /** + * Sets the viscosity, or thickness, of the fluid. + * + * @param viscosity the viscosity of the fluid + * @return the property holder instance + * @throws IllegalArgumentException if viscosity is negative + */ + public Properties viscosity(int viscosity) + { + if (viscosity < 0) + throw new IllegalArgumentException("The viscosity should never be negative."); + this.viscosity = viscosity; + return this; + } + + /** + * Sets the rarity of the fluid. + * + * @param rarity the rarity of the fluid + * @return the property holder instance + */ + public Properties rarity(Rarity rarity) + { + this.rarity = rarity; + return this; + } + } +} diff --git a/src/main/java/net/minecraftforge/fluids/FluidUtil.java b/src/main/java/net/minecraftforge/fluids/FluidUtil.java index 11492583e4..5f63ae3fd9 100644 --- a/src/main/java/net/minecraftforge/fluids/FluidUtil.java +++ b/src/main/java/net/minecraftforge/fluids/FluidUtil.java @@ -23,6 +23,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.level.Level; +import net.minecraftforge.common.SoundActions; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler; @@ -137,7 +138,7 @@ public class FluidUtil tryFluidTransfer(containerFluidHandler, fluidSource, maxAmount, true); if (player != null) { - SoundEvent soundevent = simulatedTransfer.getFluid().getAttributes().getFillSound(simulatedTransfer); + SoundEvent soundevent = simulatedTransfer.getFluid().getFluidType().getSound(simulatedTransfer, SoundActions.BUCKET_FILL); player.level.playSound(null, player.getX(), player.getY() + 0.5, player.getZ(), soundevent, SoundSource.BLOCKS, 1.0F, 1.0F); } } @@ -181,7 +182,7 @@ public class FluidUtil if (doDrain && player != null) { - SoundEvent soundevent = transfer.getFluid().getAttributes().getEmptySound(transfer); + SoundEvent soundevent = transfer.getFluid().getFluidType().getSound(transfer, SoundActions.BUCKET_EMPTY); player.level.playSound(null, player.getX(), player.getY() + 0.5, player.getZ(), soundevent, SoundSource.BLOCKS, 1.0F, 1.0F); } @@ -546,7 +547,7 @@ public class FluidUtil } Fluid fluid = resource.getFluid(); - if (fluid == Fluids.EMPTY || !fluid.getAttributes().canBePlacedInWorld(level, pos, resource)) + if (fluid == Fluids.EMPTY || !fluid.getFluidType().canBePlacedInLevel(level, pos, resource)) { return false; } @@ -569,12 +570,12 @@ public class FluidUtil return false; // Non-air, solid, unreplacable block. We can't put fluid here. } - if (level.dimensionType().ultraWarm() && fluid.getAttributes().doesVaporize(level, pos, resource)) + if (fluid.getFluidType().isVaporizedOnPlacement(level, pos, resource)) { FluidStack result = fluidSource.drain(resource, IFluidHandler.FluidAction.EXECUTE); if (!result.isEmpty()) { - result.getFluid().getAttributes().vaporize(player, level, pos, result); + result.getFluid().getFluidType().onVaporize(player, level, pos, result); return true; } } @@ -593,7 +594,7 @@ public class FluidUtil FluidStack result = tryFluidTransfer(handler, fluidSource, resource, true); if (!result.isEmpty()) { - SoundEvent soundevent = resource.getFluid().getAttributes().getEmptySound(resource); + SoundEvent soundevent = resource.getFluid().getFluidType().getSound(resource, SoundActions.BUCKET_EMPTY); level.playSound(player, pos, soundevent, SoundSource.BLOCKS, 1.0F, 1.0F); return true; } @@ -609,7 +610,7 @@ public class FluidUtil */ private static IFluidHandler getFluidBlockHandler(Fluid fluid, Level level, BlockPos pos) { - BlockState state = fluid.getAttributes().getBlock(level, pos, fluid.defaultFluidState()); + BlockState state = fluid.getFluidType().getBlockForFluidState(level, pos, fluid.defaultFluidState()); return new BlockWrapper(state, level, pos); } @@ -660,6 +661,6 @@ public class FluidUtil } } - return fluid.getAttributes().getBucket(fluidStack); + return fluid.getFluidType().getBucket(fluidStack); } } diff --git a/src/main/java/net/minecraftforge/fluids/ForgeFlowingFluid.java b/src/main/java/net/minecraftforge/fluids/ForgeFlowingFluid.java index 46922e4d4c..670b1725ba 100644 --- a/src/main/java/net/minecraftforge/fluids/ForgeFlowingFluid.java +++ b/src/main/java/net/minecraftforge/fluids/ForgeFlowingFluid.java @@ -23,6 +23,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelReader; +import net.minecraftforge.common.SoundActions; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,14 +31,13 @@ import java.util.function.Supplier; public abstract class ForgeFlowingFluid extends FlowingFluid { + private final Supplier fluidType; private final Supplier flowing; private final Supplier still; @Nullable private final Supplier bucket; @Nullable private final Supplier block; - private final FluidAttributes.Builder builder; - private final boolean canMultiply; private final int slopeFindDistance; private final int levelDecreasePerBlock; private final float explosionResistance; @@ -45,10 +45,9 @@ public abstract class ForgeFlowingFluid extends FlowingFluid protected ForgeFlowingFluid(Properties properties) { + this.fluidType = properties.fluidType; this.flowing = properties.flowing; this.still = properties.still; - this.builder = properties.attributes; - this.canMultiply = properties.canMultiply; this.bucket = properties.bucket; this.block = properties.block; this.slopeFindDistance = properties.slopeFindDistance; @@ -57,6 +56,12 @@ public abstract class ForgeFlowingFluid extends FlowingFluid this.tickRate = properties.tickRate; } + @Override + public FluidType getFluidType() + { + return this.fluidType.get(); + } + @Override public Fluid getFlowing() { @@ -72,7 +77,13 @@ public abstract class ForgeFlowingFluid extends FlowingFluid @Override protected boolean canConvertToSource() { - return canMultiply; + return false; + } + + @Override + public boolean canConvertToSource(FluidState state, LevelReader reader, BlockPos pos) + { + return this.getFluidType().canConvertToSource(state, reader, pos); } @Override @@ -136,13 +147,7 @@ public abstract class ForgeFlowingFluid extends FlowingFluid @Override public Optional getPickupSound() { - return Optional.ofNullable(getAttributes().getFillSound()); - } - - @Override - protected FluidAttributes createAttributes() - { - return builder.build(this); + return Optional.ofNullable(getFluidType().getSound(SoundActions.BUCKET_FILL)); } public static class Flowing extends ForgeFlowingFluid @@ -185,10 +190,9 @@ public abstract class ForgeFlowingFluid extends FlowingFluid public static class Properties { + private Supplier fluidType; private Supplier still; private Supplier flowing; - private FluidAttributes.Builder attributes; - private boolean canMultiply; private Supplier bucket; private Supplier block; private int slopeFindDistance = 4; @@ -196,17 +200,11 @@ public abstract class ForgeFlowingFluid extends FlowingFluid private float explosionResistance = 1; private int tickRate = 5; - public Properties(Supplier still, Supplier flowing, FluidAttributes.Builder attributes) + public Properties(Supplier fluidType, Supplier still, Supplier flowing) { + this.fluidType = fluidType; this.still = still; this.flowing = flowing; - this.attributes = attributes; - } - - public Properties canMultiply() - { - canMultiply = true; - return this; } public Properties bucket(Supplier bucket) diff --git a/src/main/java/net/minecraftforge/fluids/capability/TileFluidHandler.java b/src/main/java/net/minecraftforge/fluids/capability/TileFluidHandler.java index f57982a56a..3c07c7e4b2 100644 --- a/src/main/java/net/minecraftforge/fluids/capability/TileFluidHandler.java +++ b/src/main/java/net/minecraftforge/fluids/capability/TileFluidHandler.java @@ -13,14 +13,14 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.core.Direction; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.fluids.FluidAttributes; +import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fluids.capability.templates.FluidTank; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class TileFluidHandler extends BlockEntity { - protected FluidTank tank = new FluidTank(FluidAttributes.BUCKET_VOLUME); + protected FluidTank tank = new FluidTank(FluidType.BUCKET_VOLUME); private final LazyOptional holder = LazyOptional.of(() -> tank); diff --git a/src/main/java/net/minecraftforge/fluids/capability/wrappers/BlockWrapper.java b/src/main/java/net/minecraftforge/fluids/capability/wrappers/BlockWrapper.java index d6cdd07dc3..5af80dd486 100644 --- a/src/main/java/net/minecraftforge/fluids/capability/wrappers/BlockWrapper.java +++ b/src/main/java/net/minecraftforge/fluids/capability/wrappers/BlockWrapper.java @@ -10,8 +10,8 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.LiquidBlockContainer; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; -import net.minecraftforge.fluids.FluidAttributes; import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fluids.FluidUtil; import net.minecraftforge.fluids.capability.templates.VoidFluidHandler; @@ -39,7 +39,7 @@ public class BlockWrapper extends VoidFluidHandler public int fill(FluidStack resource, FluidAction action) { // NOTE: "Filling" means placement in this context! - if (resource.getAmount() < FluidAttributes.BUCKET_VOLUME) + if (resource.getAmount() < FluidType.BUCKET_VOLUME) { return 0; } @@ -48,7 +48,7 @@ public class BlockWrapper extends VoidFluidHandler FluidUtil.destroyBlockOnFluidPlacement(world, blockPos); world.setBlock(blockPos, state, Block.UPDATE_ALL_IMMEDIATE); } - return FluidAttributes.BUCKET_VOLUME; + return FluidType.BUCKET_VOLUME; } public static class LiquidContainerBlockWrapper extends VoidFluidHandler @@ -68,15 +68,15 @@ public class BlockWrapper extends VoidFluidHandler public int fill(FluidStack resource, FluidAction action) { // NOTE: "Filling" means placement in this context! - if (resource.getAmount() >= FluidAttributes.BUCKET_VOLUME) + if (resource.getAmount() >= FluidType.BUCKET_VOLUME) { BlockState state = world.getBlockState(blockPos); if (liquidContainer.canPlaceLiquid(world, blockPos, state, resource.getFluid())) { //If we are executing try to actually fill the container, if it failed return that we failed - if (action.simulate() || liquidContainer.placeLiquid(world, blockPos, state, resource.getFluid().getAttributes().getStateForPlacement(world, blockPos, resource))) + if (action.simulate() || liquidContainer.placeLiquid(world, blockPos, state, resource.getFluid().getFluidType().getStateForPlacement(world, blockPos, resource))) { - return FluidAttributes.BUCKET_VOLUME; + return FluidType.BUCKET_VOLUME; } } } diff --git a/src/main/java/net/minecraftforge/fluids/capability/wrappers/BucketPickupHandlerWrapper.java b/src/main/java/net/minecraftforge/fluids/capability/wrappers/BucketPickupHandlerWrapper.java index 3696aea864..7e3eac2386 100644 --- a/src/main/java/net/minecraftforge/fluids/capability/wrappers/BucketPickupHandlerWrapper.java +++ b/src/main/java/net/minecraftforge/fluids/capability/wrappers/BucketPickupHandlerWrapper.java @@ -11,8 +11,8 @@ import net.minecraft.world.level.block.BucketPickup; import net.minecraft.world.level.material.FluidState; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; -import net.minecraftforge.fluids.FluidAttributes; import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.registries.ForgeRegistries; import org.apache.logging.log4j.LogManager; @@ -50,7 +50,7 @@ public class BucketPickupHandlerWrapper implements IFluidHandler FluidState fluidState = world.getFluidState(blockPos); if (!fluidState.isEmpty()) { - return new FluidStack(fluidState.getType(), FluidAttributes.BUCKET_VOLUME); + return new FluidStack(fluidState.getType(), FluidType.BUCKET_VOLUME); } } return FluidStack.EMPTY; @@ -59,7 +59,7 @@ public class BucketPickupHandlerWrapper implements IFluidHandler @Override public int getTankCapacity(int tank) { - return FluidAttributes.BUCKET_VOLUME; + return FluidType.BUCKET_VOLUME; } @Override @@ -78,7 +78,7 @@ public class BucketPickupHandlerWrapper implements IFluidHandler @Override public FluidStack drain(FluidStack resource, FluidAction action) { - if (!resource.isEmpty() && FluidAttributes.BUCKET_VOLUME <= resource.getAmount()) + if (!resource.isEmpty() && FluidType.BUCKET_VOLUME <= resource.getAmount()) { FluidState fluidState = world.getFluidState(blockPos); if (!fluidState.isEmpty() && resource.getFluid() == fluidState.getType()) @@ -88,7 +88,7 @@ public class BucketPickupHandlerWrapper implements IFluidHandler ItemStack itemStack = bucketPickupHandler.pickupBlock(world, blockPos, world.getBlockState(blockPos)); if (itemStack != ItemStack.EMPTY && itemStack.getItem() instanceof BucketItem bucket) { - FluidStack extracted = new FluidStack(bucket.getFluid(), FluidAttributes.BUCKET_VOLUME); + FluidStack extracted = new FluidStack(bucket.getFluid(), FluidType.BUCKET_VOLUME); if (!resource.isFluidEqual(extracted)) { //Be loud if something went wrong @@ -101,7 +101,7 @@ public class BucketPickupHandlerWrapper implements IFluidHandler } else { - FluidStack extracted = new FluidStack(fluidState.getType(), FluidAttributes.BUCKET_VOLUME); + FluidStack extracted = new FluidStack(fluidState.getType(), FluidType.BUCKET_VOLUME); if (resource.isFluidEqual(extracted)) { //Validate NBT matches @@ -117,19 +117,19 @@ public class BucketPickupHandlerWrapper implements IFluidHandler @Override public FluidStack drain(int maxDrain, FluidAction action) { - if (FluidAttributes.BUCKET_VOLUME <= maxDrain) + if (FluidType.BUCKET_VOLUME <= maxDrain) { FluidState fluidState = world.getFluidState(blockPos); if (!fluidState.isEmpty()) { if (action.simulate()) { - return new FluidStack(fluidState.getType(), FluidAttributes.BUCKET_VOLUME); + return new FluidStack(fluidState.getType(), FluidType.BUCKET_VOLUME); } ItemStack itemStack = bucketPickupHandler.pickupBlock(world, blockPos, world.getBlockState(blockPos)); if (itemStack != ItemStack.EMPTY && itemStack.getItem() instanceof BucketItem bucket) { - return new FluidStack(bucket.getFluid(), FluidAttributes.BUCKET_VOLUME); + return new FluidStack(bucket.getFluid(), FluidType.BUCKET_VOLUME); } } } diff --git a/src/main/java/net/minecraftforge/fluids/capability/wrappers/FluidBlockWrapper.java b/src/main/java/net/minecraftforge/fluids/capability/wrappers/FluidBlockWrapper.java index 3c415d2909..75682b63af 100644 --- a/src/main/java/net/minecraftforge/fluids/capability/wrappers/FluidBlockWrapper.java +++ b/src/main/java/net/minecraftforge/fluids/capability/wrappers/FluidBlockWrapper.java @@ -7,8 +7,8 @@ package net.minecraftforge.fluids.capability.wrappers; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; -import net.minecraftforge.fluids.FluidAttributes; import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fluids.IFluidBlock; import net.minecraftforge.fluids.capability.IFluidHandler; @@ -52,7 +52,7 @@ public class FluidBlockWrapper implements IFluidHandler return (int) (stored.getAmount() / filledPercentage); } } - return FluidAttributes.BUCKET_VOLUME; + return FluidType.BUCKET_VOLUME; } @Override diff --git a/src/main/java/net/minecraftforge/fluids/capability/wrappers/FluidBucketWrapper.java b/src/main/java/net/minecraftforge/fluids/capability/wrappers/FluidBucketWrapper.java index 9e7bb442cb..007f272e24 100644 --- a/src/main/java/net/minecraftforge/fluids/capability/wrappers/FluidBucketWrapper.java +++ b/src/main/java/net/minecraftforge/fluids/capability/wrappers/FluidBucketWrapper.java @@ -11,8 +11,8 @@ import net.minecraftforge.common.ForgeMod; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.fluids.FluidAttributes; import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fluids.FluidUtil; import net.minecraftforge.fluids.capability.IFluidHandlerItem; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; @@ -54,7 +54,7 @@ public class FluidBucketWrapper implements IFluidHandlerItem, ICapabilityProvide { return true; } - return !fluid.getFluid().getAttributes().getBucket(fluid).isEmpty(); + return !fluid.getFluid().getFluidType().getBucket(fluid).isEmpty(); } @NotNull @@ -63,11 +63,11 @@ public class FluidBucketWrapper implements IFluidHandlerItem, ICapabilityProvide Item item = container.getItem(); if (item instanceof BucketItem) { - return new FluidStack(((BucketItem)item).getFluid(), FluidAttributes.BUCKET_VOLUME); + return new FluidStack(((BucketItem)item).getFluid(), FluidType.BUCKET_VOLUME); } else if (item instanceof MilkBucketItem && ForgeMod.MILK.isPresent()) { - return new FluidStack(ForgeMod.MILK.get(), FluidAttributes.BUCKET_VOLUME); + return new FluidStack(ForgeMod.MILK.get(), FluidType.BUCKET_VOLUME); } else { @@ -99,7 +99,7 @@ public class FluidBucketWrapper implements IFluidHandlerItem, ICapabilityProvide @Override public int getTankCapacity(int tank) { - return FluidAttributes.BUCKET_VOLUME; + return FluidType.BUCKET_VOLUME; } @Override @@ -111,7 +111,7 @@ public class FluidBucketWrapper implements IFluidHandlerItem, ICapabilityProvide @Override public int fill(FluidStack resource, FluidAction action) { - if (container.getCount() != 1 || resource.getAmount() < FluidAttributes.BUCKET_VOLUME || container.getItem() instanceof MilkBucketItem || !getFluid().isEmpty() || !canFillFluidType(resource)) + if (container.getCount() != 1 || resource.getAmount() < FluidType.BUCKET_VOLUME || container.getItem() instanceof MilkBucketItem || !getFluid().isEmpty() || !canFillFluidType(resource)) { return 0; } @@ -121,14 +121,14 @@ public class FluidBucketWrapper implements IFluidHandlerItem, ICapabilityProvide setFluid(resource); } - return FluidAttributes.BUCKET_VOLUME; + return FluidType.BUCKET_VOLUME; } @NotNull @Override public FluidStack drain(FluidStack resource, FluidAction action) { - if (container.getCount() != 1 || resource.getAmount() < FluidAttributes.BUCKET_VOLUME) + if (container.getCount() != 1 || resource.getAmount() < FluidType.BUCKET_VOLUME) { return FluidStack.EMPTY; } @@ -150,7 +150,7 @@ public class FluidBucketWrapper implements IFluidHandlerItem, ICapabilityProvide @Override public FluidStack drain(int maxDrain, FluidAction action) { - if (container.getCount() != 1 || maxDrain < FluidAttributes.BUCKET_VOLUME) + if (container.getCount() != 1 || maxDrain < FluidType.BUCKET_VOLUME) { return FluidStack.EMPTY; } diff --git a/src/main/java/net/minecraftforge/registries/ForgeDeferredRegistriesSetup.java b/src/main/java/net/minecraftforge/registries/ForgeDeferredRegistriesSetup.java index b0e5e750ae..5552fb294d 100644 --- a/src/main/java/net/minecraftforge/registries/ForgeDeferredRegistriesSetup.java +++ b/src/main/java/net/minecraftforge/registries/ForgeDeferredRegistriesSetup.java @@ -23,6 +23,7 @@ public class ForgeDeferredRegistriesSetup ForgeRegistries.DEFERRED_LOOT_MODIFIER_SERIALIZERS.register(modEventBus); ForgeRegistries.DEFERRED_WORLD_TYPES.register(modEventBus); ForgeRegistries.DEFERRED_BIOME_MODIFIER_SERIALIZERS.register(modEventBus); + ForgeRegistries.DEFERRED_FLUID_TYPES.register(modEventBus); ForgeRegistries.DEFERRED_BIOME_MODIFIERS.register(modEventBus); ForgeRegistries.DEFERRED_STRUCTURE_MODIFIER_SERIALIZERS.register(modEventBus); ForgeRegistries.DEFERRED_STRUCTURE_MODIFIERS.register(modEventBus); diff --git a/src/main/java/net/minecraftforge/registries/ForgeRegistries.java b/src/main/java/net/minecraftforge/registries/ForgeRegistries.java index 1dd4ed9e70..4a33ea65cc 100644 --- a/src/main/java/net/minecraftforge/registries/ForgeRegistries.java +++ b/src/main/java/net/minecraftforge/registries/ForgeRegistries.java @@ -44,6 +44,7 @@ import net.minecraftforge.common.Tags; import net.minecraftforge.common.loot.GlobalLootModifierSerializer; import net.minecraftforge.common.world.BiomeModifier; import net.minecraftforge.common.world.ForgeWorldPreset; +import net.minecraftforge.fluids.FluidType; import java.util.function.Supplier; import net.minecraftforge.common.world.StructureModifier; @@ -105,6 +106,8 @@ public class ForgeRegistries public static final Supplier>> BIOME_MODIFIER_SERIALIZERS = DEFERRED_BIOME_MODIFIER_SERIALIZERS.makeRegistry(() -> new RegistryBuilder>().disableSaving().disableSync()); static final DeferredRegister> DEFERRED_STRUCTURE_MODIFIER_SERIALIZERS = DeferredRegister.create(Keys.STRUCTURE_MODIFIER_SERIALIZERS, Keys.STRUCTURE_MODIFIER_SERIALIZERS.location().getNamespace()); public static final Supplier>> STRUCTURE_MODIFIER_SERIALIZERS = DEFERRED_STRUCTURE_MODIFIER_SERIALIZERS.makeRegistry(() -> new RegistryBuilder>().disableSaving().disableSync()); + static final DeferredRegister DEFERRED_FLUID_TYPES = DeferredRegister.create(Keys.FLUID_TYPES, Keys.FLUID_TYPES.location().getNamespace()); + public static final Supplier> FLUID_TYPES = DEFERRED_FLUID_TYPES.makeRegistry(GameData::getFluidTypeRegistryBuilder); // Custom forge datapack registries static final DeferredRegister DEFERRED_BIOME_MODIFIERS = DeferredRegister.create(Keys.BIOME_MODIFIERS, "forge"); @@ -152,6 +155,7 @@ public class ForgeRegistries public static final ResourceKey> WORLD_TYPES = key("forge:world_types"); public static final ResourceKey>> BIOME_MODIFIER_SERIALIZERS = key("forge:biome_modifier_serializers"); public static final ResourceKey>> STRUCTURE_MODIFIER_SERIALIZERS = key("forge:structure_modifier_serializers"); + public static final ResourceKey> FLUID_TYPES = key("forge:fluid_type"); // Forge Dynamic public static final ResourceKey> BIOME_MODIFIERS = key("forge:biome_modifier"); diff --git a/src/main/java/net/minecraftforge/registries/GameData.java b/src/main/java/net/minecraftforge/registries/GameData.java index dad200d29c..61fc9b8805 100644 --- a/src/main/java/net/minecraftforge/registries/GameData.java +++ b/src/main/java/net/minecraftforge/registries/GameData.java @@ -40,6 +40,7 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.loot.GlobalLootModifierSerializer; import net.minecraftforge.common.util.LogMessageAdapter; import net.minecraftforge.common.world.ForgeWorldPreset; +import net.minecraftforge.fluids.FluidType; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.ModLoadingContext; @@ -153,6 +154,11 @@ public class GameData return makeRegistry(WORLD_TYPES).disableSaving().disableSync(); } + static RegistryBuilder getFluidTypeRegistryBuilder() + { + return makeRegistry(FLUID_TYPES).disableSaving(); + } + private static RegistryBuilder makeRegistry(ResourceKey> key) { return new RegistryBuilder().setName(key.location()).setMaxID(MAX_VARINT).hasWrapper(); diff --git a/src/main/resources/forge.exc b/src/main/resources/forge.exc index 6b8ee4635e..1fe2169358 100644 --- a/src/main/resources/forge.exc +++ b/src/main/resources/forge.exc @@ -41,6 +41,7 @@ net/minecraft/server/level/Ticket.(Lnet/minecraft/server/level/TicketType; net/minecraft/server/packs/metadata/pack/PackMetadataSection.(Lnet/minecraft/network/chat/Component;ILjava/util/Map;)V=|p_10371_,p_10372_,packTypeVersions net/minecraft/server/packs/repository/Pack.(Ljava/lang/String;ZLjava/util/function/Supplier;Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/Component;Lnet/minecraft/server/packs/repository/PackCompatibility;Lnet/minecraft/server/packs/repository/Pack$Position;ZLnet/minecraft/server/packs/repository/PackSource;Z)V=|p_10420_,p_10421_,p_10422_,p_10423_,p_10424_,p_10425_,p_10426_,p_10427_,p_10428_,hidden net/minecraft/world/entity/Entity.changeDimension(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraftforge/common/util/ITeleporter;)Lnet/minecraft/world/entity/Entity;=|p_20118_,teleporter +net/minecraft/world/entity/Entity.updateFluidHeightAndDoFluidPushing()V=| net/minecraft/world/entity/EntityType.(Lnet/minecraft/world/entity/EntityType$EntityFactory;Lnet/minecraft/world/entity/MobCategory;ZZZZLcom/google/common/collect/ImmutableSet;Lnet/minecraft/world/entity/EntityDimensions;IILjava/util/function/Predicate;Ljava/util/function/ToIntFunction;Ljava/util/function/ToIntFunction;Ljava/util/function/BiFunction;)V=|p_20574_,p_20575_,p_20576_,p_20577_,p_20578_,p_20579_,p_20580_,p_20581_,p_20582_,p_20583_,velocityUpdateSupplier,trackingRangeSupplier,updateIntervalSupplier,customClientFactory net/minecraft/world/entity/item/ItemEntity.changeDimension(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraftforge/common/util/ITeleporter;)Lnet/minecraft/world/entity/Entity;=|p_32042_,teleporter net/minecraft/world/entity/npc/AbstractVillager.changeDimension(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraftforge/common/util/ITeleporter;)Lnet/minecraft/world/entity/Entity;=|p_35295_,teleporter @@ -59,6 +60,7 @@ net/minecraft/world/item/crafting/RecipeManager.(Lnet/minecraftforge/commo net/minecraft/world/item/crafting/RecipeManager.fromJson(Lnet/minecraft/resources/ResourceLocation;Lcom/google/gson/JsonObject;Lnet/minecraftforge/common/crafting/conditions/ICondition$IContext;)Lnet/minecraft/world/item/crafting/Recipe;=|p_44046_,p_44047_,context net/minecraft/world/level/Level.markAndNotifyBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/chunk/LevelChunk;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;II)V=|p_46605_,levelchunk,blockstate,p_46606_,p_46607_,p_46608_ net/minecraft/world/level/LevelSettings.(Ljava/lang/String;Lnet/minecraft/world/level/GameType;ZLnet/minecraft/world/Difficulty;ZLnet/minecraft/world/level/GameRules;Lnet/minecraft/world/level/DataPackConfig;Lcom/mojang/serialization/Lifecycle;)V=|p_46910_,p_46911_,p_46912_,p_46913_,p_46914_,p_46915_,p_46916_,lifecycle +net/minecraft/world/level/block/ConcretePowderBlock.touchesLiquid(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)Z=|p_52065_,p_52066_,state net/minecraft/world/level/block/FireBlock.tryCatchFire(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;ILnet/minecraft/util/RandomSource;ILnet/minecraft/core/Direction;)V=|p_53432_,p_53433_,p_53434_,p_53435_,p_53436_,face net/minecraft/world/level/block/FlowerPotBlock.(Ljava/util/function/Supplier;Ljava/util/function/Supplier;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V=|emptyPot,p_53528_,properties net/minecraft/world/level/block/LiquidBlock.(Ljava/util/function/Supplier;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V=|p_54694_,p_54695_ diff --git a/src/test/java/net/minecraftforge/debug/fluid/FluidTypeTest.java b/src/test/java/net/minecraftforge/debug/fluid/FluidTypeTest.java new file mode 100644 index 0000000000..aa02fe9cf2 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/fluid/FluidTypeTest.java @@ -0,0 +1,218 @@ +package net.minecraftforge.debug.fluid; + +import com.mojang.blaze3d.shaders.FogShape; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.math.Vector3f; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.FogRenderer; +import net.minecraft.client.renderer.ItemBlockRenderTypes; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BucketItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.FlowingFluid; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Material; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.IFluidTypeRenderProperties; +import net.minecraftforge.client.RenderProperties; +import net.minecraftforge.client.event.ColorHandlerEvent; +import net.minecraftforge.common.ForgeMod; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fluids.FluidInteractionRegistry; +import net.minecraftforge.fluids.FluidType; +import net.minecraftforge.fluids.ForgeFlowingFluid; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * A test case used to define and test fluid type integration into fluids. + * + *

    + *
  • Checks that each fluid has a fluid type
  • + *
  • Adds a new fluid with its type, source/flowing, block, and bucket
  • + *
  • Sets properties to test out fluid logic
  • + *
  • Overrides fluid rendering methods
  • + *
  • Adds block color and render layer
  • + *
  • Adds fluid interaction definitions
  • + *
+ */ +@Mod(FluidTypeTest.ID) +public class FluidTypeTest +{ + private static final boolean ENABLE = true; + + protected static final String ID = "fluid_type_test"; + private static Logger logger; + + private static final DeferredRegister FLUID_TYPES = DeferredRegister.create(ForgeRegistries.Keys.FLUID_TYPES, ID); + private static final DeferredRegister FLUIDS = DeferredRegister.create(ForgeRegistries.FLUIDS, ID); + private static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, ID); + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, ID); + + private static ForgeFlowingFluid.Properties fluidProperties() + { + return new ForgeFlowingFluid.Properties(TEST_FLUID_TYPE, TEST_FLUID, TEST_FLUID_FLOWING) + .block(TEST_FLUID_BLOCK) + .bucket(TEST_FLUID_BUCKET); + } + + private static final RegistryObject TEST_FLUID_TYPE = FLUID_TYPES.register("test_fluid", () -> + new FluidType(FluidType.Properties.create().supportsBoating(true).canHydrate(true)) + { + @Override + public void initializeClient(Consumer consumer) + { + consumer.accept(new IFluidTypeRenderProperties() + { + private static final ResourceLocation STILL = new ResourceLocation("block/water_still"), + FLOW = new ResourceLocation("block/water_flow"), + OVERLAY = new ResourceLocation("block/obsidian"), + VIEW_OVERLAY = new ResourceLocation("textures/block/obsidian.png"); + + @Override + public ResourceLocation getStillTexture() + { + return STILL; + } + + @Override + public ResourceLocation getFlowingTexture() + { + return FLOW; + } + + @Override + public ResourceLocation getOverlayTexture() + { + return OVERLAY; + } + + @Override + public ResourceLocation getRenderOverlayTexture(Minecraft mc) + { + return VIEW_OVERLAY; + } + + @Override + public int getColorTint() + { + return 0xAF7FFFD4; + } + + @Override + public @NotNull Vector3f modifyFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount, Vector3f fluidFogColor) + { + int color = this.getColorTint(); + return new Vector3f((color >> 16 & 0xFF) / 255F, (color >> 8 & 0xFF) / 255F, (color & 0xFF) / 255F); + } + + @Override + public void modifyFogRender(Camera camera, FogRenderer.FogMode mode, float renderDistance, float partialTick, float nearDistance, float farDistance, FogShape shape) + { + nearDistance = -8F; + farDistance = 24F; + + if (farDistance > renderDistance) + { + farDistance = renderDistance; + shape = FogShape.CYLINDER; + } + + RenderSystem.setShaderFogStart(nearDistance); + RenderSystem.setShaderFogEnd(farDistance); + RenderSystem.setShaderFogShape(shape); + } + }); + } + }); + private static final RegistryObject TEST_FLUID = FLUIDS.register("test_fluid", () -> + new ForgeFlowingFluid.Source(fluidProperties())); + private static final RegistryObject TEST_FLUID_FLOWING = FLUIDS.register("test_fluid_flowing", () -> + new ForgeFlowingFluid.Flowing(fluidProperties())); + private static final RegistryObject TEST_FLUID_BLOCK = BLOCKS.register("test_fluid_block", () -> + new LiquidBlock(TEST_FLUID, BlockBehaviour.Properties.of(Material.WATER).noCollission().strength(100.0F).noLootTable())); + private static final RegistryObject TEST_FLUID_BUCKET = ITEMS.register("test_fluid_bucket", () -> + new BucketItem(TEST_FLUID, new Item.Properties().craftRemainder(Items.BUCKET).stacksTo(1).tab(CreativeModeTab.TAB_MISC))); + + public FluidTypeTest() + { + if (ENABLE) + { + logger = LogManager.getLogger(); + ForgeMod.enableMilkFluid(); + + var modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + + FLUID_TYPES.register(modEventBus); + FLUIDS.register(modEventBus); + BLOCKS.register(modEventBus); + ITEMS.register(modEventBus); + + modEventBus.addListener(this::commonSetup); + + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> new FluidTypeTestClient(modEventBus)); + } + } + + private void commonSetup(FMLCommonSetupEvent event) + { + // Add Interactions for sources + FluidInteractionRegistry.addInteraction(TEST_FLUID_TYPE.get(), new FluidInteractionRegistry.InteractionInformation(ForgeMod.LAVA_TYPE.get(), Blocks.GOLD_BLOCK.defaultBlockState())); + FluidInteractionRegistry.addInteraction(ForgeMod.WATER_TYPE.get(), new FluidInteractionRegistry.InteractionInformation(TEST_FLUID_TYPE.get(), state -> state.isSource() ? Blocks.DIAMOND_BLOCK.defaultBlockState() : Blocks.IRON_BLOCK.defaultBlockState())); + + // Log Fluid Types for all fluids + event.enqueueWork(() -> + ForgeRegistries.FLUIDS.forEach(fluid -> + logger.info("Fluid {} has FluidType {}", ForgeRegistries.FLUIDS.getKey(fluid), ForgeRegistries.FLUID_TYPES.get().getKey(fluid.getFluidType()))) + ); + } + + private static class FluidTypeTestClient + { + private FluidTypeTestClient(IEventBus modEventBus) + { + modEventBus.addListener(this::clientSetup); + modEventBus.addListener(this::registerBlockColors); + } + + private void clientSetup(FMLClientSetupEvent event) + { + Stream.of(TEST_FLUID, TEST_FLUID_FLOWING).map(RegistryObject::get) + .forEach(fluid -> ItemBlockRenderTypes.setRenderLayer(fluid, RenderType.translucent())); + } + + private void registerBlockColors(ColorHandlerEvent.Block event) + { + event.getBlockColors().register((state, getter, pos, index) -> + { + if (getter != null && pos != null) + { + FluidState fluidState = getter.getFluidState(pos); + return RenderProperties.get(fluidState).getColorTint(fluidState, getter, pos); + } else return 0xAF7FFFD4; + }, TEST_FLUID_BLOCK.get()); + } + } +} diff --git a/src/test/java/net/minecraftforge/debug/fluid/NewFluidTest.java b/src/test/java/net/minecraftforge/debug/fluid/NewFluidTest.java index e97a70a1d4..c88ad78e14 100644 --- a/src/test/java/net/minecraftforge/debug/fluid/NewFluidTest.java +++ b/src/test/java/net/minecraftforge/debug/fluid/NewFluidTest.java @@ -12,7 +12,9 @@ import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.LevelAccessor; +import net.minecraftforge.client.IFluidTypeRenderProperties; import net.minecraftforge.fluids.DispenseFluidContainer; +import net.minecraftforge.fluids.FluidType; import org.apache.commons.lang3.Validate; import net.minecraft.world.level.material.FlowingFluid; @@ -20,7 +22,6 @@ import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.Fluids; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.fluids.FluidAttributes; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.ForgeFlowingFluid; import net.minecraftforge.registries.RegistryObject; @@ -44,6 +45,9 @@ import net.minecraft.world.level.block.DispenserBlock; import net.minecraft.world.level.block.LiquidBlock; import net.minecraft.world.level.block.SimpleWaterloggedBlock; import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; @Mod(NewFluidTest.MODID) public class NewFluidTest @@ -57,15 +61,50 @@ public class NewFluidTest public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); + public static final DeferredRegister FLUID_TYPES = DeferredRegister.create(ForgeRegistries.Keys.FLUID_TYPES, MODID); public static final DeferredRegister FLUIDS = DeferredRegister.create(ForgeRegistries.FLUIDS, MODID); private static ForgeFlowingFluid.Properties makeProperties() { - return new ForgeFlowingFluid.Properties(test_fluid, test_fluid_flowing, - FluidAttributes.builder(FLUID_STILL, FLUID_FLOWING).overlay(FLUID_OVERLAY).color(0x3F1080FF)) + return new ForgeFlowingFluid.Properties(test_fluid_type, test_fluid, test_fluid_flowing) .bucket(test_fluid_bucket).block(test_fluid_block); } + public static RegistryObject test_fluid_type = FLUID_TYPES.register("test_fluid", () -> new FluidType(FluidType.Properties.create()) + { + @Override + public void initializeClient(Consumer consumer) + { + consumer.accept(new IFluidTypeRenderProperties() + { + @Override + public ResourceLocation getStillTexture() + { + return FLUID_STILL; + } + + @Override + public ResourceLocation getFlowingTexture() + { + return FLUID_FLOWING; + } + + @Nullable + @Override + public ResourceLocation getOverlayTexture() + { + return FLUID_OVERLAY; + } + + @Override + public int getColorTint() + { + return 0x3F1080FF; + } + }); + } + }); + public static RegistryObject test_fluid = FLUIDS.register("test_fluid", () -> new ForgeFlowingFluid.Source(makeProperties()) ); @@ -98,6 +137,7 @@ public class NewFluidTest BLOCKS.register(modEventBus); ITEMS.register(modEventBus); + FLUID_TYPES.register(modEventBus); FLUIDS.register(modEventBus); } } @@ -106,9 +146,9 @@ public class NewFluidTest { // some sanity checks BlockState state = Fluids.WATER.defaultFluidState().createLegacyBlock(); - BlockState state2 = Fluids.WATER.getAttributes().getBlock(null,null,Fluids.WATER.defaultFluidState()); + BlockState state2 = Fluids.WATER.getFluidType().getBlockForFluidState(null,null,Fluids.WATER.defaultFluidState()); Validate.isTrue(state.getBlock() == Blocks.WATER && state2 == state); - ItemStack stack = Fluids.WATER.getAttributes().getBucket(new FluidStack(Fluids.WATER, 1)); + ItemStack stack = Fluids.WATER.getFluidType().getBucket(new FluidStack(Fluids.WATER, 1)); Validate.isTrue(stack.getItem() == Fluids.WATER.getBucket()); event.enqueueWork(() -> DispenserBlock.registerBehavior(test_fluid_bucket.get(), DispenseFluidContainer.getInstance())); } diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index ae292bbf3d..ec4fe73d30 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -10,5 +10,7 @@ license="LGPL v2.1" modId="biome_modifiers_test" [[mods]] modId="structure_modifiers_test" +[[mods]] + modId="fluid_type_test" # ADD ABOVE THIS LINE