diff --git a/common/src/main/java/juuxel/adorn/compat/jei/BrewerCategory.java b/common/src/main/java/juuxel/adorn/compat/jei/BrewerCategory.java index ddc753206..ad0812168 100644 --- a/common/src/main/java/juuxel/adorn/compat/jei/BrewerCategory.java +++ b/common/src/main/java/juuxel/adorn/compat/jei/BrewerCategory.java @@ -80,8 +80,8 @@ public void setRecipe(IRecipeLayoutBuilder layoutBuilder, BrewingRecipe recipe, var ingredient = r.fluid(); var amount = FluidUnit.convert(ingredient.getAmount(), ingredient.getUnit(), FluidBridge.get().getFluidUnit()); - for (Fluid fluid : ingredient.getFluid().getFluids()) { - tank.addFluidStack(fluid, amount, ingredient.getNbt()); + for (Fluid fluid : ingredient.fluid().getFluids()) { + tank.addFluidStack(fluid, amount, ingredient.nbt()); } } } diff --git a/common/src/main/java/juuxel/adorn/compat/rei/BrewerDisplay.java b/common/src/main/java/juuxel/adorn/compat/rei/BrewerDisplay.java index 6d2447c0f..733d4215d 100644 --- a/common/src/main/java/juuxel/adorn/compat/rei/BrewerDisplay.java +++ b/common/src/main/java/juuxel/adorn/compat/rei/BrewerDisplay.java @@ -48,10 +48,10 @@ public BrewerDisplay(FluidBrewingRecipe recipe) { private static EntryIngredient entryIngredientOf(FluidIngredient fluidIngredient) { var amount = FluidUnit.convert(fluidIngredient.getAmount(), fluidIngredient.getUnit(), FluidBridge.get().getFluidUnit()); - var stacks = fluidIngredient.getFluid() + var stacks = fluidIngredient.fluid() .getFluids() .stream() - .map(fluid -> EntryStacks.of(FluidStack.create(fluid, amount, fluidIngredient.getNbt()))) + .map(fluid -> EntryStacks.of(FluidStack.create(fluid, amount, fluidIngredient.nbt()))) .toList(); return EntryIngredient.of(stacks); } diff --git a/common/src/main/java/juuxel/adorn/fluid/FluidAmountPredicate.java b/common/src/main/java/juuxel/adorn/fluid/FluidAmountPredicate.java new file mode 100644 index 000000000..8ba1cf6df --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/FluidAmountPredicate.java @@ -0,0 +1,41 @@ +package juuxel.adorn.fluid; + +import net.minecraft.fluid.Fluids; + +public interface FluidAmountPredicate { + HasFluidAmount getUpperBound(); + + boolean test(long amount, FluidUnit unit); + + static FluidAmountPredicate exactly(long amount, FluidUnit unit) { + return new FluidAmountPredicate() { + private final FluidVolume upperBound = new FluidVolume(Fluids.EMPTY, amount, null, unit); + + @Override + public HasFluidAmount getUpperBound() { + return upperBound; + } + + @Override + public boolean test(long amount, FluidUnit unit) { + return FluidUnit.compareVolumes(amount, unit, upperBound.getAmount(), upperBound.getUnit()) == 0; + } + }; + } + + static FluidAmountPredicate atMost(long max, FluidUnit unit) { + return new FluidAmountPredicate() { + private final FluidVolume upperBound = new FluidVolume(Fluids.EMPTY, max, null, unit); + + @Override + public HasFluidAmount getUpperBound() { + return upperBound; + } + + @Override + public boolean test(long amount, FluidUnit unit) { + return FluidUnit.compareVolumes(amount, unit, upperBound.getAmount(), upperBound.getUnit()) <= 0; + } + }; + } +} diff --git a/common/src/main/java/juuxel/adorn/fluid/FluidIngredient.java b/common/src/main/java/juuxel/adorn/fluid/FluidIngredient.java new file mode 100644 index 000000000..0fbfd7272 --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/FluidIngredient.java @@ -0,0 +1,53 @@ +package juuxel.adorn.fluid; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +/** + * A fluid ingredient for crafting. + */ +public record FluidIngredient(FluidKey fluid, long amount, @Nullable NbtCompound nbt, FluidUnit unit) implements HasFluidAmount { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + FluidKey.CODEC.fieldOf("fluid").forGetter(FluidIngredient::fluid), + Codec.LONG.fieldOf("amount").forGetter(FluidIngredient::amount), + NbtCompound.CODEC.optionalFieldOf("nbt").forGetter(ingredient -> Optional.ofNullable(ingredient.nbt)), + FluidUnit.CODEC.optionalFieldOf("unit", FluidUnit.LITRE).forGetter(FluidIngredient::unit) + ).apply(instance, FluidIngredient::new)); + + // for DFU + private FluidIngredient(FluidKey fluid, long amount, Optional nbt, FluidUnit unit) { + this(fluid, amount, nbt.orElse(null), unit); + } + + public static FluidIngredient load(PacketByteBuf buf) { + var fluid = FluidKey.load(buf); + var amount = buf.readVarLong(); + var nbt = buf.readNbt(); + var unit = buf.readEnumConstant(FluidUnit.class); + return new FluidIngredient(fluid, amount, nbt, unit); + } + + public void write(PacketByteBuf buf) { + fluid.write(buf); + buf.writeVarLong(amount); + buf.writeNbt(nbt); + buf.writeEnumConstant(unit); + } + + @Override + public long getAmount() { + return amount; + } + + @NotNull + @Override + public FluidUnit getUnit() { + return unit; + } +} diff --git a/common/src/main/java/juuxel/adorn/fluid/FluidKey.java b/common/src/main/java/juuxel/adorn/fluid/FluidKey.java new file mode 100644 index 000000000..d802ef67a --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/FluidKey.java @@ -0,0 +1,67 @@ +package juuxel.adorn.fluid; + +import com.mojang.serialization.Codec; +import net.minecraft.fluid.Fluid; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.Registries; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/// A "key" that identifies a fluid or a group of fluids. +/// +/// Could be a single fluid, a tag or a list of the former. +/// +/// ## JSON format +/// +/// A fluid key is one of: +/// +/// - a string; if prefixed with `#`, a tag, otherwise a fluid ID +/// - an array of strings as described above +/// +/// Examples: `"minecraft:water"`, `"#c:milk"`, `["minecraft:water", "minecraft:lava"]` +public sealed interface FluidKey permits FluidKeyImpl.Simple, FluidKeyImpl.OfArray { + Codec CODEC = FluidKeyImpl.CODEC; + + /** + * Returns the set of all fluids matching this key. + */ + Set getFluids(); + + /** + * Tests whether the fluid matches this key. + */ + boolean matches(Fluid fluid); + + /** + * Writes this key to a packet buffer. + * @see #load + */ + default void write(PacketByteBuf buf) { + var fluids = getFluids(); + buf.writeVarInt(fluids.size()); + + for (Fluid fluid : fluids) { + buf.writeVarInt(Registries.FLUID.getRawId(fluid)); + } + } + + /** + * Reads a key from a packet buffer. + * @see #write + */ + static FluidKey load(PacketByteBuf buf) { + var size = buf.readVarInt(); + + if (size == 1) { + return new FluidKeyImpl.OfFluid(Registries.FLUID.get(buf.readVarInt())); + } else { + List children = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + children.add(new FluidKeyImpl.OfFluid(Registries.FLUID.get(buf.readVarInt()))); + } + return new FluidKeyImpl.OfArray(children); + } + } +} diff --git a/common/src/main/java/juuxel/adorn/fluid/FluidKeyImpl.java b/common/src/main/java/juuxel/adorn/fluid/FluidKeyImpl.java new file mode 100644 index 000000000..67910579d --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/FluidKeyImpl.java @@ -0,0 +1,123 @@ +package juuxel.adorn.fluid; + +import com.mojang.datafixers.util.Either; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import net.minecraft.fluid.Fluid; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import net.minecraft.util.dynamic.Codecs; + +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +final class FluidKeyImpl { + private static final Codec SIMPLE_CODEC = new Codec<>() { + @Override + public DataResult> decode(DynamicOps ops, T input) { + return ops.getStringValue(input) + .map(s -> { + if (s.startsWith("#")) { + return new OfTag(TagKey.of(RegistryKeys.FLUID, new Identifier(s.substring(1)))); + } else { + return new OfFluid(Registries.FLUID.get(new Identifier(s))); + } + }) + .map(key -> Pair.of(key, ops.empty())); + } + + @Override + public DataResult encode(FluidKeyImpl.Simple input, DynamicOps ops, T prefix) { + return ops.mergeToPrimitive(prefix, ops.createString(input.getId())); + } + + @Override + public String toString() { + return "SimpleFluidKey"; + } + }; + + // TODO: Switch to either instead of xor + public static final Codec CODEC = Codecs.xor( + SIMPLE_CODEC, + SIMPLE_CODEC.listOf().xmap(OfArray::new, OfArray::children) + ).xmap( + // TODO (Java 21): Use pattern matching + either -> either.map(Function.identity(), Function.identity()), + key -> { + if (key instanceof Simple simple) { + return Either.left(simple); + } else if (key instanceof OfArray ofArray) { + return Either.right(ofArray); + } else { + throw new IllegalArgumentException(); + } + } + ); + + sealed interface Simple extends FluidKey { + String getId(); + } + + record OfFluid(Fluid fluid) implements Simple { + @Override + public String getId() { + return Registries.FLUID.getId(fluid).toString(); + } + + @Override + public Set getFluids() { + return Set.of(fluid); + } + + @Override + public boolean matches(Fluid fluid) { + return fluid == this.fluid; + } + } + + record OfTag(TagKey tag) implements Simple { + @Override + public String getId() { + return "#" + tag.id(); + } + + @Override + public Set getFluids() { + return Registries.FLUID.getOrCreateEntryList(tag) + .stream() + .map(RegistryEntry::value) + .collect(Collectors.toSet()); + } + + @Override + public boolean matches(Fluid fluid) { + return fluid.isIn(tag); + } + } + + record OfArray(List children) implements FluidKey { + @Override + public Set getFluids() { + return children.stream() + .flatMap(child -> child.getFluids().stream()) + .collect(Collectors.toSet()); + } + + @Override + public boolean matches(Fluid fluid) { + for (var child : children) { + if (child.matches(fluid)) return true; + } + + return false; + } + } +} diff --git a/common/src/main/java/juuxel/adorn/fluid/FluidReference.java b/common/src/main/java/juuxel/adorn/fluid/FluidReference.java new file mode 100644 index 000000000..9aa886fd5 --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/FluidReference.java @@ -0,0 +1,121 @@ +package juuxel.adorn.fluid; + +import juuxel.adorn.config.ConfigManager; +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.Fluids; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.Registries; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * A mutable reference to a fluid volume. + * This can be a {@link FluidVolume} or a block entity's + * internal fluid volume. + */ +public abstract class FluidReference implements HasFluidAmount { + public abstract Fluid getFluid(); + public abstract void setFluid(Fluid fluid); + + public abstract void setAmount(long amount); + + public abstract @Nullable NbtCompound getNbt(); + public abstract void setNbt(@Nullable NbtCompound nbt); + + public boolean isEmpty() { + return getFluid() == Fluids.EMPTY || getAmount() == 0; + } + + public void write(PacketByteBuf buf) { + buf.writeEnumConstant(getUnit()); + + if (isEmpty()) { + buf.writeBoolean(false); + } else { + buf.writeBoolean(true); + buf.writeVarInt(Registries.FLUID.getRawId(getFluid())); + buf.writeVarLong(getAmount()); + buf.writeNbt(getNbt()); + } + } + + protected void readWithoutUnit(PacketByteBuf buf) { + if (buf.readBoolean()) { + setFluid(Registries.FLUID.get(buf.readVarInt())); + setAmount(buf.readVarLong()); + setNbt(buf.readNbt()); + } else { + setFluid(Fluids.EMPTY); + setAmount(0); + setNbt(null); + } + } + + /** + * Creates an independent mutable snapshot of this fluid reference's current contents. + */ + public FluidVolume createSnapshot() { + return new FluidVolume(getFluid(), getAmount(), getNbt(), getUnit()); + } + + public void increment(long amount, FluidUnit unit) { + setAmount(getAmount() + FluidUnit.convert(amount, unit, getUnit())); + } + + public void decrement(long amount, FluidUnit unit) { + increment(-amount, unit); + } + + public boolean matches(FluidIngredient ingredient) { + return ingredient.fluid().matches(getFluid()) + && FluidUnit.compareVolumes(this, ingredient) >= 0 + && Objects.equals(getNbt(), ingredient.nbt()); + } + + public Text getAmountText() { + var displayUnit = getDefaultDisplayUnit(); + return Text.translatable( + "gui.adorn.fluid_volume", + FluidUnit.losslessConvert(getAmount(), getUnit(), displayUnit).resizeFraction(getUnitDenominator(getUnit(), displayUnit)), + displayUnit.getSymbol() + ); + } + + public Text getAmountText(long max, FluidUnit maxUnit) { + var displayUnit = getDefaultDisplayUnit(); + return Text.translatable( + "gui.adorn.fluid_volume.fraction", + FluidUnit.losslessConvert(getAmount(), getUnit(), displayUnit).resizeFraction(getUnitDenominator(getUnit(), displayUnit)), + FluidUnit.losslessConvert(max, maxUnit, displayUnit).resizeFraction(getUnitDenominator(maxUnit, displayUnit)), + displayUnit.getSymbol() + ); + } + + private static FluidUnit getDefaultDisplayUnit() { + return ConfigManager.Companion.config().client.displayedFluidUnit; + } + + private static long getUnitDenominator(FluidUnit from, FluidUnit to) { + if (from.getBucketVolume() == to.getBucketVolume()) return 1; + return Math.max(1, from.getBucketVolume() / to.getBucketVolume()); + } + + @Override + public String toString() { + return "FluidReference(fluid=%s, amount=%d, nbt=%s)" + .formatted(Registries.FLUID.getId(getFluid()), getAmount(), getNbt()); + } + + public static boolean areFluidsEqual(FluidReference a, FluidReference b) { + if (a.isEmpty()) return b.isEmpty(); + return a.getFluid() == b.getFluid() && Objects.equals(a.getNbt(), b.getNbt()); + } + + public static boolean areFluidsAndAmountsEqual(FluidReference a, FluidReference b) { + if (a.isEmpty()) return b.isEmpty(); + return areFluidsEqual(a, b) && FluidUnit.compareVolumes(a, b) == 0; + } +} diff --git a/common/src/main/java/juuxel/adorn/fluid/FluidUnit.java b/common/src/main/java/juuxel/adorn/fluid/FluidUnit.java new file mode 100644 index 000000000..8b7bc50da --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/FluidUnit.java @@ -0,0 +1,114 @@ +package juuxel.adorn.fluid; + +import com.mojang.serialization.Codec; +import juuxel.adorn.util.Displayable; +import juuxel.adorn.util.MixedFraction; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Fluid volume units. This class is used for doing fluid volume + * math in the common module (fluid-based recipes). + * + *

The platform-specific unit is available via + * {@link juuxel.adorn.platform.FluidBridge#getFluidUnit}. + */ +public enum FluidUnit implements Displayable { + /** Litres. Defined as one thousandth of a cubic metre ({@link #getBucketVolume()} = 1000). */ + LITRE("litres", 1000), + + /** Droplets. Defined as 1/81 000 of a cubic metre ({@link #getBucketVolume()} = 81 000). */ + DROPLET("droplets", 81_000); + + public static final Codec CODEC = Codec.STRING.xmap(FluidUnit::byId, FluidUnit::getId); + private static final Map BY_ID = Arrays.stream(values()) + .collect(Collectors.toMap(FluidUnit::getId, Function.identity())); + + private final String id; + private final long bucketVolume; + private final Text displayName; + private final Text symbol; + + FluidUnit(String id, long bucketVolume) { + this.id = id; + this.bucketVolume = bucketVolume; + this.displayName = Text.translatable("gui.adorn.fluid_unit.%s.name".formatted(id)); + this.symbol = Text.translatable("gui.adorn.fluid_unit.%s.symbol".formatted(id)); + } + + public String getId() { + return id; + } + + public long getBucketVolume() { + return bucketVolume; + } + + @Override + public Text getDisplayName() { + return displayName; + } + + public Text getSymbol() { + return symbol; + } + + /** + * Returns the fluid unit with the specified {@linkplain #getId() ID}, + * or null if not found. + */ + public static @Nullable FluidUnit byId(String id) { + return BY_ID.get(id.toLowerCase(Locale.ROOT)); + } + + /** + * Converts a volume between two fluid units. Potentially lossy, use with caution! + */ + public static long convert(long volume, FluidUnit from, FluidUnit to) { + if (from == to) return volume; + return volume * to.getBucketVolume() / from.getBucketVolume(); + } + + /** + * Converts a volume between two fluid units losslessly, returning a mixed fraction. + */ + public static MixedFraction losslessConvert(long volume, FluidUnit from, FluidUnit to) { + if (from == to) return MixedFraction.whole(volume); + return MixedFraction.of(volume * to.getBucketVolume(), from.getBucketVolume()); + } + + /** + * Converts a volume between two fluid units. + * This variant is meant to be used for rendering. + */ + public static double convertAsDouble(double volume, FluidUnit from, FluidUnit to) { + if (from == to) return volume; + return volume * (double) to.getBucketVolume() / (double) from.getBucketVolume(); + } + + /** + * Compares two fluid volumes with specified units. + */ + public static int compareVolumes(long volume1, FluidUnit unit1, long volume2, FluidUnit unit2) { + if (unit1 == unit2) { + return Long.compare(volume1, volume2); + } else if (unit1.getBucketVolume() > unit2.getBucketVolume()) { + return Long.compare(volume1, volume2 * unit1.getBucketVolume() / unit2.getBucketVolume()); + } else { + return Long.compare(volume1 * unit2.getBucketVolume() / unit1.getBucketVolume(), volume2); + } + } + + /** + * Compares the amounts of two fluid references. + */ + public static int compareVolumes(HasFluidAmount volume1, HasFluidAmount volume2) { + return compareVolumes(volume1.getAmount(), volume1.getUnit(), volume2.getAmount(), volume2.getUnit()); + } +} diff --git a/common/src/main/java/juuxel/adorn/fluid/FluidVolume.java b/common/src/main/java/juuxel/adorn/fluid/FluidVolume.java new file mode 100644 index 000000000..8e95e1e59 --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/FluidVolume.java @@ -0,0 +1,71 @@ +package juuxel.adorn.fluid; + +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.Fluids; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; +import org.jetbrains.annotations.Nullable; + +public final class FluidVolume extends FluidReference { + private Fluid fluid; + private long amount; + private @Nullable NbtCompound nbt; + private final FluidUnit unit; + + public FluidVolume(Fluid fluid, long amount, @Nullable NbtCompound nbt, FluidUnit unit) { + this.fluid = fluid; + this.amount = amount; + this.nbt = nbt; + this.unit = unit; + } + + public static FluidVolume empty(FluidUnit unit) { + return new FluidVolume(Fluids.EMPTY, 0, null, unit); + } + + public static FluidVolume load(PacketByteBuf buf) { + var volume = empty(buf.readEnumConstant(FluidUnit.class)); + volume.readWithoutUnit(buf); + return volume; + } + + @Override + public Fluid getFluid() { + return fluid; + } + + @Override + public void setFluid(Fluid fluid) { + this.fluid = fluid; + } + + @Override + public long getAmount() { + return amount; + } + + @Override + public void setAmount(long amount) { + this.amount = amount; + } + + @Override + public @Nullable NbtCompound getNbt() { + return nbt; + } + + @Override + public void setNbt(@Nullable NbtCompound nbt) { + this.nbt = nbt; + } + + @Override + public FluidUnit getUnit() { + return unit; + } + + @Override + public String toString() { + return "FluidVolume(fluid=%s, amount=%d, nbt=%s)".formatted(fluid, amount, nbt); + } +} diff --git a/common/src/main/java/juuxel/adorn/fluid/HasFluidAmount.java b/common/src/main/java/juuxel/adorn/fluid/HasFluidAmount.java new file mode 100644 index 000000000..a0d8b4879 --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/HasFluidAmount.java @@ -0,0 +1,9 @@ +package juuxel.adorn.fluid; + +/** + * A fluid volume-like object that has a fluid amount and a unit. + */ +public interface HasFluidAmount { + long getAmount(); + FluidUnit getUnit(); +} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/README.md b/common/src/main/java/juuxel/adorn/fluid/README.md similarity index 100% rename from common/src/main/kotlin/juuxel/adorn/fluid/README.md rename to common/src/main/java/juuxel/adorn/fluid/README.md diff --git a/common/src/main/java/juuxel/adorn/fluid/StepMaximum.java b/common/src/main/java/juuxel/adorn/fluid/StepMaximum.java new file mode 100644 index 000000000..9f55c5469 --- /dev/null +++ b/common/src/main/java/juuxel/adorn/fluid/StepMaximum.java @@ -0,0 +1,66 @@ +package juuxel.adorn.fluid; + +public final class StepMaximum implements FluidAmountPredicate { + private final long min; + private final long max; + private final long step; + private final FluidUnit unit; + private final HasFluidAmount upperBound; + + public StepMaximum(long min, long max, long step, FluidUnit unit) { + if (min >= max) { + throw new IllegalArgumentException("min must be less than max"); + } else if ((max - min) % step != 0L) { + throw new IllegalArgumentException("max - min must be divisible by step"); + } + + this.min = min; + this.max = max; + this.step = step; + this.unit = unit; + this.upperBound = new HasFluidAmount() { + @Override + public long getAmount() { + return StepMaximum.this.max; + } + + @Override + public FluidUnit getUnit() { + return StepMaximum.this.unit; + } + }; + } + + public long getMin() { + return min; + } + + public long getMax() { + return max; + } + + public long getStep() { + return step; + } + + public FluidUnit getUnit() { + return unit; + } + + @Override + public HasFluidAmount getUpperBound() { + return upperBound; + } + + @Override + public boolean test(long amount, FluidUnit unit) { + long toCompare = FluidUnit.convert(amount, unit, this.unit); + + if (toCompare < min || toCompare > max) { + return false; + } + + long zeroed = toCompare - min; + return zeroed % step == 0L; + } +} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/FluidAmountPredicate.kt b/common/src/main/kotlin/juuxel/adorn/fluid/FluidAmountPredicate.kt deleted file mode 100644 index 14f2ff0df..000000000 --- a/common/src/main/kotlin/juuxel/adorn/fluid/FluidAmountPredicate.kt +++ /dev/null @@ -1,29 +0,0 @@ -package juuxel.adorn.fluid - -import net.minecraft.fluid.Fluids - -interface FluidAmountPredicate { - val upperBound: HasFluidAmount - - fun test(amount: Long, unit: FluidUnit): Boolean - - companion object { - fun exactly(amount: Long, unit: FluidUnit): FluidAmountPredicate { - return object : FluidAmountPredicate { - override val upperBound = FluidVolume(Fluids.EMPTY, amount, null, unit) - - override fun test(amount: Long, unit: FluidUnit): Boolean = - FluidUnit.compareVolumes(amount, unit, upperBound.amount, upperBound.unit) == 0 - } - } - - fun atMost(max: Long, unit: FluidUnit): FluidAmountPredicate { - return object : FluidAmountPredicate { - override val upperBound = FluidVolume(Fluids.EMPTY, max, null, unit) - - override fun test(amount: Long, unit: FluidUnit): Boolean = - FluidUnit.compareVolumes(amount, unit, upperBound.amount, upperBound.unit) == 0 - } - } - } -} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/FluidIngredient.kt b/common/src/main/kotlin/juuxel/adorn/fluid/FluidIngredient.kt deleted file mode 100644 index cf72dfd20..000000000 --- a/common/src/main/kotlin/juuxel/adorn/fluid/FluidIngredient.kt +++ /dev/null @@ -1,49 +0,0 @@ -package juuxel.adorn.fluid - -import com.mojang.serialization.Codec -import com.mojang.serialization.codecs.RecordCodecBuilder -import net.minecraft.nbt.NbtCompound -import net.minecraft.network.PacketByteBuf -import java.util.Optional - -/** - * A fluid ingredient for crafting. - */ -data class FluidIngredient( - val fluid: FluidKey, - override val amount: Long, - val nbt: NbtCompound?, - override val unit: FluidUnit -) : HasFluidAmount { - // for DFU - private constructor(fluid: FluidKey, amount: Long, nbt: Optional, unit: FluidUnit) : - this(fluid, amount, nbt.orElse(null), unit) - - fun write(buf: PacketByteBuf) { - fluid.write(buf) - buf.writeVarLong(amount) - buf.writeNbt(nbt) - buf.writeEnumConstant(unit) - } - - companion object { - @JvmField - val CODEC: Codec = RecordCodecBuilder.create { instance -> - instance.group( - FluidKey.CODEC.fieldOf("fluid").forGetter { it.fluid }, - Codec.LONG.fieldOf("amount").forGetter { it.amount }, - NbtCompound.CODEC.optionalFieldOf("nbt").forGetter { Optional.ofNullable(it.nbt) }, - FluidUnit.CODEC.optionalFieldOf("unit", FluidUnit.LITRE).forGetter { it.unit }, - ).apply(instance, ::FluidIngredient) - } - - @JvmStatic - fun load(buf: PacketByteBuf): FluidIngredient { - val fluid = FluidKey.load(buf) - val amount = buf.readVarLong() - val nbt = buf.readNbt() - val unit = buf.readEnumConstant(FluidUnit::class.java) - return FluidIngredient(fluid, amount, nbt, unit) - } - } -} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/FluidKey.kt b/common/src/main/kotlin/juuxel/adorn/fluid/FluidKey.kt deleted file mode 100644 index 2ee18ee83..000000000 --- a/common/src/main/kotlin/juuxel/adorn/fluid/FluidKey.kt +++ /dev/null @@ -1,139 +0,0 @@ -package juuxel.adorn.fluid - -import com.mojang.datafixers.util.Either -import com.mojang.datafixers.util.Pair -import com.mojang.serialization.Codec -import com.mojang.serialization.DataResult -import com.mojang.serialization.Decoder -import com.mojang.serialization.DynamicOps -import com.mojang.serialization.Encoder -import net.minecraft.fluid.Fluid -import net.minecraft.network.PacketByteBuf -import net.minecraft.registry.Registries -import net.minecraft.registry.RegistryKeys -import net.minecraft.registry.tag.TagKey -import net.minecraft.util.Identifier -import net.minecraft.util.dynamic.Codecs - -/** - * A "key" that identifies a fluid or a group of fluids. - * - * Could be a single fluid, a tag or a list of the former. - * - * ## JSON format - * - * A fluid key is one of: - * - * - a string; if prefixed with `#`, a tag, otherwise a fluid ID - * - an array of strings as described above - * - * Examples: `"minecraft:water"`, `"#c:milk"`, `["minecraft:water", "minecraft:lava"]` - */ -sealed class FluidKey { - /** - * Returns the set of all fluids matching this key. - */ - abstract fun getFluids(): Set - - /** - * Tests whether the [fluid] matches this key. - */ - abstract fun matches(fluid: Fluid): Boolean - - /** - * Writes this key to a packet buffer. - * @see load - */ - fun write(buf: PacketByteBuf) { - val fluids = getFluids() - buf.writeVarInt(fluids.size) - - for (fluid in fluids) { - buf.writeVarInt(Registries.FLUID.getRawId(fluid)) - } - } - - companion object { - private val SIMPLE_ENCODER = object : Encoder { - override fun encode(input: Simple, ops: DynamicOps, prefix: T): DataResult = - ops.mergeToPrimitive(prefix, ops.createString(input.id)) - } - - private val SIMPLE_DECODER = object : Decoder { - override fun decode(ops: DynamicOps, input: T): DataResult> = - ops.getStringValue(input) - .map { - if (it.startsWith('#')) { - OfTag(TagKey.of(RegistryKeys.FLUID, Identifier(it.substring(1)))) - } else { - OfFluid(Registries.FLUID[Identifier(it)]) - } - } - .map { Pair.of(it, ops.empty()) } - } - - private val SIMPLE_CODEC: Codec = Codec.of(SIMPLE_ENCODER, SIMPLE_DECODER, "SimpleFluidKey") - - val CODEC: Codec = Codecs.xor( - SIMPLE_CODEC, - SIMPLE_CODEC.listOf().xmap({ OfArray(it) }, { it.children }) - ).xmap( - { it.map({ x -> x }, { x -> x }) }, - { - when (it) { - is Simple -> Either.left(it) - is OfArray -> Either.right(it) - } - } - ) - - /** - * Reads a key from a packet buffer. - * @see write - */ - fun load(buf: PacketByteBuf): FluidKey { - val size = buf.readVarInt() - fun readFluid(): Fluid = Registries.FLUID[buf.readVarInt()] - - return if (size == 1) { - OfFluid(readFluid()) - } else { - OfArray( - (1..size).map { - OfFluid(readFluid()) - } - ) - } - } - } - - private sealed class Simple : FluidKey() { - abstract val id: String - } - - private data class OfFluid(val fluid: Fluid) : Simple() { - override val id: String by lazy { Registries.FLUID.getId(fluid).toString() } - override fun getFluids() = setOf(fluid) - - override fun matches(fluid: Fluid): Boolean = - fluid === this.fluid - } - - private data class OfTag(val tag: TagKey) : Simple() { - override val id = "#${tag.id}" - - override fun getFluids(): Set = - Registries.FLUID.getOrCreateEntryList(tag).mapTo(HashSet()) { it.value() } - - override fun matches(fluid: Fluid): Boolean = - fluid.isIn(tag) - } - - private data class OfArray(val children: List) : FluidKey() { - override fun getFluids(): Set = - children.flatMapTo(HashSet()) { it.getFluids() } - - override fun matches(fluid: Fluid): Boolean = - children.any { it.matches(fluid) } - } -} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/FluidReference.kt b/common/src/main/kotlin/juuxel/adorn/fluid/FluidReference.kt deleted file mode 100644 index 782d143d5..000000000 --- a/common/src/main/kotlin/juuxel/adorn/fluid/FluidReference.kt +++ /dev/null @@ -1,102 +0,0 @@ -package juuxel.adorn.fluid - -import juuxel.adorn.config.ConfigManager -import net.minecraft.fluid.Fluid -import net.minecraft.fluid.Fluids -import net.minecraft.nbt.NbtCompound -import net.minecraft.network.PacketByteBuf -import net.minecraft.registry.Registries -import net.minecraft.text.Text -import kotlin.math.max - -/** - * A mutable reference to a fluid volume. - * This can be a [FluidVolume] or a block entity's - * internal fluid volume. - */ -abstract class FluidReference : HasFluidAmount { - abstract var fluid: Fluid - abstract override var amount: Long - abstract var nbt: NbtCompound? - val isEmpty: Boolean get() = fluid == Fluids.EMPTY || amount == 0L - - fun write(buf: PacketByteBuf) { - buf.writeEnumConstant(unit) - - if (isEmpty) { - buf.writeBoolean(false) - } else { - buf.writeBoolean(true) - buf.writeVarInt(Registries.FLUID.getRawId(fluid)) - buf.writeVarLong(amount) - buf.writeNbt(nbt) - } - } - - protected fun readWithoutUnit(buf: PacketByteBuf) { - if (buf.readBoolean()) { - fluid = Registries.FLUID[buf.readVarInt()] - amount = buf.readVarLong() - nbt = buf.readNbt() - } else { - fluid = Fluids.EMPTY - amount = 0 - nbt = null - } - } - - /** - * Creates an independent mutable snapshot of this fluid reference's current contents. - * - * For fluid volumes, this is the same as creating a standard copy with [FluidVolume.copy]. - */ - fun createSnapshot(): FluidVolume = FluidVolume(fluid, amount, nbt, unit) - - fun increment(amount: Long, unit: FluidUnit) { - this.amount += FluidUnit.convert(amount, this.unit, unit) - } - - fun decrement(amount: Long, unit: FluidUnit) = - increment(-amount, unit) - - fun matches(ingredient: FluidIngredient): Boolean = - ingredient.fluid.matches(fluid) && FluidUnit.compareVolumes(this, ingredient) >= 0 && nbt == ingredient.nbt - - fun getAmountText(displayUnit: FluidUnit = getDefaultDisplayUnit()): Text = - Text.translatable( - "gui.adorn.fluid_volume", - FluidUnit.losslessConvert(amount, unit, displayUnit).resizeFraction(getUnitDenominator(unit, displayUnit)), - displayUnit.symbol - ) - - fun getAmountText(max: Long, maxUnit: FluidUnit, displayUnit: FluidUnit = getDefaultDisplayUnit()): Text = - Text.translatable( - "gui.adorn.fluid_volume.fraction", - FluidUnit.losslessConvert(amount, unit, displayUnit).resizeFraction(getUnitDenominator(unit, displayUnit)), - FluidUnit.losslessConvert(max, maxUnit, displayUnit).resizeFraction(getUnitDenominator(maxUnit, displayUnit)), - displayUnit.symbol - ) - - override fun toString() = - "FluidReference(fluid=${Registries.FLUID.getId(fluid)}, amount=$amount, nbt=$nbt)" - - companion object { - private fun getUnitDenominator(from: FluidUnit, to: FluidUnit): Long { - if (from.bucketVolume == to.bucketVolume) return 1 - return max(1, from.bucketVolume / to.bucketVolume) - } - - private fun getDefaultDisplayUnit() = - ConfigManager.config().client.displayedFluidUnit - - fun areFluidsEqual(a: FluidReference, b: FluidReference): Boolean { - if (a.isEmpty) return b.isEmpty - return a.fluid == b.fluid && a.nbt == b.nbt - } - - fun areFluidsAndAmountsEqual(a: FluidReference, b: FluidReference): Boolean { - if (a.isEmpty) return b.isEmpty - return areFluidsEqual(a, b) && FluidUnit.compareVolumes(a, b) == 0 - } - } -} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/FluidUnit.kt b/common/src/main/kotlin/juuxel/adorn/fluid/FluidUnit.kt deleted file mode 100644 index 281fea15e..000000000 --- a/common/src/main/kotlin/juuxel/adorn/fluid/FluidUnit.kt +++ /dev/null @@ -1,79 +0,0 @@ -package juuxel.adorn.fluid - -import com.mojang.serialization.Codec -import juuxel.adorn.util.Displayable -import juuxel.adorn.util.MixedFraction -import net.minecraft.text.Text - -/** - * Fluid volume units. This class is used for doing fluid volume - * math in the common module (fluid-based recipes). - * - * The platform-specific unit is available via - * [FluidBridge.fluidUnit][juuxel.adorn.platform.FluidBridge.fluidUnit]. - */ -enum class FluidUnit(val id: String, val bucketVolume: Long) : Displayable { - /** Litres. Defined as one thousandth of a cubic metre ([bucketVolume] = 1000). */ - LITRE("litres", 1000), - - /** Droplets. Defined as 1/81 000 of a cubic metre ([bucketVolume] = 81 000). */ - DROPLET("droplets", 81_000); - - override val displayName: Text = Text.translatable("gui.adorn.fluid_unit.$id.name") - val symbol: Text = Text.translatable("gui.adorn.fluid_unit.$id.symbol") - - companion object { - private val BY_ID: Map = values().associateBy { it.id } - val CODEC: Codec = Codec.STRING.xmap(this::byId, FluidUnit::id) - - /** - * Returns the fluid unit with the specified [ID][FluidUnit.id], - * or null if not found. - */ - fun byId(id: String): FluidUnit? = BY_ID[id.lowercase()] - - /** - * Converts a volume between two fluid units. Potentially lossy, use with caution! - */ - @JvmStatic - fun convert(volume: Long, from: FluidUnit, to: FluidUnit): Long { - if (from == to) return volume - return volume * to.bucketVolume / from.bucketVolume - } - - /** - * Converts a volume between two fluid units losslessly, returning a mixed fraction. - */ - fun losslessConvert(volume: Long, from: FluidUnit, to: FluidUnit): MixedFraction { - if (from == to) return MixedFraction.whole(volume) - return MixedFraction.of(volume * to.bucketVolume, from.bucketVolume) - } - - /** - * Converts a volume between two fluid units. - * This variant is meant to be used for rendering. - */ - fun convertAsDouble(volume: Double, from: FluidUnit, to: FluidUnit): Double { - if (from == to) return volume - return volume * to.bucketVolume.toDouble() / from.bucketVolume.toDouble() - } - - /** - * Compares two fluid volumes with specified units. - */ - fun compareVolumes(volume1: Long, unit1: FluidUnit, volume2: Long, unit2: FluidUnit): Int = - if (unit1 == unit2) { - volume1.compareTo(volume2) - } else if (unit1.bucketVolume > unit2.bucketVolume) { - volume1.compareTo(volume2 * unit1.bucketVolume / unit2.bucketVolume) - } else { - (volume1 * unit2.bucketVolume / unit1.bucketVolume).compareTo(volume2) - } - - /** - * Compares the amounts of two [FluidReference]s. - */ - fun compareVolumes(volume1: HasFluidAmount, volume2: HasFluidAmount): Int = - compareVolumes(volume1.amount, volume1.unit, volume2.amount, volume2.unit) - } -} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/FluidVolume.kt b/common/src/main/kotlin/juuxel/adorn/fluid/FluidVolume.kt deleted file mode 100644 index 8c90921fe..000000000 --- a/common/src/main/kotlin/juuxel/adorn/fluid/FluidVolume.kt +++ /dev/null @@ -1,25 +0,0 @@ -package juuxel.adorn.fluid - -import net.minecraft.fluid.Fluid -import net.minecraft.fluid.Fluids -import net.minecraft.nbt.NbtCompound -import net.minecraft.network.PacketByteBuf -import net.minecraft.registry.Registries - -data class FluidVolume( - override var fluid: Fluid, - override var amount: Long, - override var nbt: NbtCompound?, - override val unit: FluidUnit -) : FluidReference() { - override fun toString() = - "FluidVolume(fluid=${Registries.FLUID.getId(fluid)}, amount=$amount, nbt=$nbt)" - - companion object { - fun empty(unit: FluidUnit): FluidVolume = - FluidVolume(Fluids.EMPTY, 0L, null, unit) - - fun load(buf: PacketByteBuf): FluidVolume = - empty(buf.readEnumConstant(FluidUnit::class.java)).apply { readWithoutUnit(buf) } - } -} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/HasFluidAmount.kt b/common/src/main/kotlin/juuxel/adorn/fluid/HasFluidAmount.kt deleted file mode 100644 index 34030f402..000000000 --- a/common/src/main/kotlin/juuxel/adorn/fluid/HasFluidAmount.kt +++ /dev/null @@ -1,9 +0,0 @@ -package juuxel.adorn.fluid - -/** - * A fluid volume-like object that has a fluid amount and unit. - */ -interface HasFluidAmount { - val amount: Long - val unit: FluidUnit -} diff --git a/common/src/main/kotlin/juuxel/adorn/fluid/StepMaximum.kt b/common/src/main/kotlin/juuxel/adorn/fluid/StepMaximum.kt deleted file mode 100644 index 2d8185027..000000000 --- a/common/src/main/kotlin/juuxel/adorn/fluid/StepMaximum.kt +++ /dev/null @@ -1,29 +0,0 @@ -package juuxel.adorn.fluid - -class StepMaximum( - val min: Long, - val max: Long, - val step: Long, - val unit: FluidUnit -) : FluidAmountPredicate { - override val upperBound = object : HasFluidAmount { - override val amount = max - override val unit = this@StepMaximum.unit - } - - init { - require(min < max) { "min must be less than max" } - require((max - min) % step == 0L) { "max - min must be divisible by step" } - } - - override fun test(amount: Long, unit: FluidUnit): Boolean { - val toCompare = FluidUnit.convert(amount, from = unit, to = this.unit) - - if (toCompare < min || toCompare > max) { - return false - } - - val zeroed = toCompare - min - return zeroed % step == 0L - } -} diff --git a/common/src/main/kotlin/juuxel/adorn/item/WateringCanItem.kt b/common/src/main/kotlin/juuxel/adorn/item/WateringCanItem.kt index 9e7ddae2b..2ccb6d7e7 100644 --- a/common/src/main/kotlin/juuxel/adorn/item/WateringCanItem.kt +++ b/common/src/main/kotlin/juuxel/adorn/item/WateringCanItem.kt @@ -66,7 +66,7 @@ class WateringCanItem(settings: Settings) : ItemWithDescription(settings) { val drained = FluidBridge.get().drain(world, pos, null, hitResult.side.opposite, Fluids.WATER, FLUID_DRAIN_PREDICATE) if (drained != null) { - val amount = FluidUnit.convert(drained.amount, from = drained.unit, to = FluidUnit.LITRE) + val amount = FluidUnit.convert(drained.amount, drained.unit, FluidUnit.LITRE) val levels = (amount / FLUID_DRAIN_PREDICATE.step).toInt() waterLevel = min(waterLevel + levels, MAX_WATER_LEVEL) nbt.putInt(NBT_WATER_LEVEL, waterLevel) @@ -191,7 +191,7 @@ class WateringCanItem(settings: Settings) : ItemWithDescription(settings) { private const val WATER_LEVEL_DIVISOR = 1f / MAX_WATER_LEVEL private const val WATER_LEVELS_PER_BUCKET = 10 - private val FLUID_DRAIN_PREDICATE = StepMaximum(min = 0L, max = 1000L, step = 1000L / WATER_LEVELS_PER_BUCKET, unit = FluidUnit.LITRE) + private val FLUID_DRAIN_PREDICATE = StepMaximum(0L, 1000L, 1000L / WATER_LEVELS_PER_BUCKET, FluidUnit.LITRE) private fun spawnParticlesAt(world: ServerWorld, pos: BlockPos, y: Double) { val px = pos.x.toDouble() + 0.3 + world.random.nextDouble() * 0.4 diff --git a/fabric/src/main/java/juuxel/adorn/util/FluidStorageReference.java b/fabric/src/main/java/juuxel/adorn/util/FluidStorageReference.java new file mode 100644 index 000000000..2bc84f9e4 --- /dev/null +++ b/fabric/src/main/java/juuxel/adorn/util/FluidStorageReference.java @@ -0,0 +1,71 @@ +package juuxel.adorn.util; + +import juuxel.adorn.fluid.FluidReference; +import juuxel.adorn.fluid.FluidUnit; +import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant; +import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantStorage; +import net.minecraft.fluid.Fluid; +import net.minecraft.nbt.NbtCompound; +import org.jetbrains.annotations.Nullable; + +/** + * A {@linkplain FluidReference fluid reference} to a {@link SingleVariantStorage}. + */ +public final class FluidStorageReference extends FluidReference { + private final SingleVariantStorage storage; + + public FluidStorageReference(SingleVariantStorage storage) { + this.storage = storage; + } + + public FluidVariant getVariant() { + return storage.variant; + } + + @Override + public Fluid getFluid() { + return storage.variant.getFluid(); + } + + @Override + public void setFluid(Fluid fluid) { + storage.variant = FluidVariant.of(fluid, storage.variant.getNbt()); + } + + @Override + public long getAmount() { + return storage.amount; + } + + @Override + public void setAmount(long amount) { + storage.amount = amount; + } + + @Override + public @Nullable NbtCompound getNbt() { + return storage.variant.getNbt(); + } + + @Override + public void setNbt(@Nullable NbtCompound nbt) { + storage.variant = FluidVariant.of(storage.variant.getFluid(), nbt); + } + + @Override + public FluidUnit getUnit() { + return FluidUnit.DROPLET; + } + + /** + * Converts this fluid reference to a {@link FluidVariant}. + * This is faster than a manual conversion for a {@code FluidStorageReference}. + */ + public static FluidVariant toFluidVariant(FluidReference reference) { + if (reference instanceof FluidStorageReference fsr) { + return fsr.getVariant(); + } else { + return FluidVariant.of(reference.getFluid(), reference.getNbt()); + } + } +} diff --git a/fabric/src/main/kotlin/juuxel/adorn/block/entity/KitchenSinkBlockEntityFabric.kt b/fabric/src/main/kotlin/juuxel/adorn/block/entity/KitchenSinkBlockEntityFabric.kt index 788ddacce..3e5fae82a 100644 --- a/fabric/src/main/kotlin/juuxel/adorn/block/entity/KitchenSinkBlockEntityFabric.kt +++ b/fabric/src/main/kotlin/juuxel/adorn/block/entity/KitchenSinkBlockEntityFabric.kt @@ -5,7 +5,6 @@ package juuxel.adorn.block.entity import com.google.common.base.Predicates import juuxel.adorn.fluid.FluidReference import juuxel.adorn.util.FluidStorageReference -import juuxel.adorn.util.toFluidVariant import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext import net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants @@ -91,10 +90,10 @@ class KitchenSinkBlockEntityFabric(pos: BlockPos, state: BlockState) : KitchenSi } override fun getFillSound(fluid: FluidReference, stack: ItemStack): FluidItemSound = - super.getFillSound(fluid, stack).orElse(FluidVariantAttributes.getFillSound(fluid.toFluidVariant())) + super.getFillSound(fluid, stack).orElse(FluidVariantAttributes.getFillSound(FluidStorageReference.toFluidVariant(fluid))) override fun getEmptySound(fluid: FluidReference, stack: ItemStack): FluidItemSound = - super.getEmptySound(fluid, stack).orElse(FluidVariantAttributes.getEmptySound(fluid.toFluidVariant())) + super.getEmptySound(fluid, stack).orElse(FluidVariantAttributes.getEmptySound(FluidStorageReference.toFluidVariant(fluid))) override fun readNbt(nbt: NbtCompound) { super.readNbt(nbt) diff --git a/fabric/src/main/kotlin/juuxel/adorn/client/FluidRenderingBridgeFabric.kt b/fabric/src/main/kotlin/juuxel/adorn/client/FluidRenderingBridgeFabric.kt index f38063055..9bcf71b42 100644 --- a/fabric/src/main/kotlin/juuxel/adorn/client/FluidRenderingBridgeFabric.kt +++ b/fabric/src/main/kotlin/juuxel/adorn/client/FluidRenderingBridgeFabric.kt @@ -2,7 +2,7 @@ package juuxel.adorn.client import juuxel.adorn.fluid.FluidReference import juuxel.adorn.fluid.FluidUnit -import juuxel.adorn.util.toFluidVariant +import juuxel.adorn.util.FluidStorageReference import net.fabricmc.api.EnvType import net.fabricmc.api.Environment import net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRendering @@ -16,19 +16,19 @@ import net.minecraft.world.BlockRenderView object FluidRenderingBridgeFabric : FluidRenderingBridge { @Environment(EnvType.CLIENT) override fun getStillSprite(volume: FluidReference): Sprite? = - FluidVariantRendering.getSprite(volume.toFluidVariant()) + FluidVariantRendering.getSprite(FluidStorageReference.toFluidVariant(volume)) @Environment(EnvType.CLIENT) override fun getColor(volume: FluidReference, world: BlockRenderView?, pos: BlockPos?) = - FluidVariantRendering.getColor(volume.toFluidVariant(), world, pos) + FluidVariantRendering.getColor(FluidStorageReference.toFluidVariant(volume), world, pos) @Environment(EnvType.CLIENT) override fun fillsFromTop(volume: FluidReference): Boolean = - FluidVariantAttributes.isLighterThanAir(volume.toFluidVariant()) + FluidVariantAttributes.isLighterThanAir(FluidStorageReference.toFluidVariant(volume)) @Environment(EnvType.CLIENT) override fun getTooltip(volume: FluidReference, context: TooltipContext, maxAmountInLitres: Int?): List { - val result = FluidVariantRendering.getTooltip(volume.toFluidVariant(), context).toMutableList() + val result = FluidVariantRendering.getTooltip(FluidStorageReference.toFluidVariant(volume), context).toMutableList() if (maxAmountInLitres != null) { result.add(1, volume.getAmountText(maxAmountInLitres.toLong(), FluidUnit.LITRE)) diff --git a/fabric/src/main/kotlin/juuxel/adorn/client/renderer/KitchenSinkRendererFabric.kt b/fabric/src/main/kotlin/juuxel/adorn/client/renderer/KitchenSinkRendererFabric.kt index f902849f7..162a38049 100644 --- a/fabric/src/main/kotlin/juuxel/adorn/client/renderer/KitchenSinkRendererFabric.kt +++ b/fabric/src/main/kotlin/juuxel/adorn/client/renderer/KitchenSinkRendererFabric.kt @@ -6,7 +6,7 @@ import net.minecraft.client.render.block.entity.BlockEntityRendererFactory class KitchenSinkRendererFabric(context: BlockEntityRendererFactory.Context) : KitchenSinkRenderer(context) { override fun getFluidLevel(entity: KitchenSinkBlockEntityFabric): Double = - FluidUnit.convertAsDouble(entity.storage.amount.toDouble(), from = FluidUnit.DROPLET, to = FluidUnit.LITRE) + FluidUnit.convertAsDouble(entity.storage.amount.toDouble(), FluidUnit.DROPLET, FluidUnit.LITRE) override fun isEmpty(entity: KitchenSinkBlockEntityFabric): Boolean = entity.storage.amount == 0L diff --git a/fabric/src/main/kotlin/juuxel/adorn/platform/fabric/FluidBridgeFabric.kt b/fabric/src/main/kotlin/juuxel/adorn/platform/fabric/FluidBridgeFabric.kt index d827a5a29..26a8acab2 100644 --- a/fabric/src/main/kotlin/juuxel/adorn/platform/fabric/FluidBridgeFabric.kt +++ b/fabric/src/main/kotlin/juuxel/adorn/platform/fabric/FluidBridgeFabric.kt @@ -21,7 +21,7 @@ class FluidBridgeFabric : FluidBridge { if (storage != null) { val upperBound = amountPredicate.upperBound - val maxAmount = FluidUnit.convert(upperBound.amount, from = upperBound.unit, to = FluidUnit.DROPLET) + val maxAmount = FluidUnit.convert(upperBound.amount, upperBound.unit, FluidUnit.DROPLET) Transaction.openOuter().use { transaction -> val extracted = storage.extract(FluidVariant.of(fluid), maxAmount, transaction) diff --git a/fabric/src/main/kotlin/juuxel/adorn/util/FluidReferences.kt b/fabric/src/main/kotlin/juuxel/adorn/util/FluidReferences.kt deleted file mode 100644 index 71e1d1785..000000000 --- a/fabric/src/main/kotlin/juuxel/adorn/util/FluidReferences.kt +++ /dev/null @@ -1,46 +0,0 @@ -package juuxel.adorn.util - -import juuxel.adorn.fluid.FluidReference -import juuxel.adorn.fluid.FluidUnit -import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant -import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantStorage -import net.minecraft.fluid.Fluid -import net.minecraft.nbt.NbtCompound - -/** - * A [fluid reference][FluidReference] to a [`SingleVariantStorage`][SingleVariantStorage]. - */ -class FluidStorageReference(private val storage: SingleVariantStorage) : FluidReference() { - val variant: FluidVariant get() = storage.variant - - override var fluid: Fluid - get() = storage.variant.fluid - set(value) { - storage.variant = FluidVariant.of(value, storage.variant.nbt) - } - - override var amount: Long - get() = storage.amount - set(value) { - storage.amount = value - } - - override var nbt: NbtCompound? - get() = storage.variant.nbt - set(value) { - storage.variant = FluidVariant.of(storage.variant.fluid, value) - } - - override val unit = FluidUnit.DROPLET -} - -/** - * Converts this fluid reference to a [FluidVariant]. - * This is faster than a manual conversion for a [FluidStorageReference]. - */ -fun FluidReference.toFluidVariant(): FluidVariant = - if (this is FluidStorageReference) { - variant - } else { - FluidVariant.of(fluid, nbt) - } diff --git a/forge/src/main/java/juuxel/adorn/platform/forge/util/FluidTankReference.java b/forge/src/main/java/juuxel/adorn/platform/forge/util/FluidTankReference.java new file mode 100644 index 000000000..d5000fbaa --- /dev/null +++ b/forge/src/main/java/juuxel/adorn/platform/forge/util/FluidTankReference.java @@ -0,0 +1,73 @@ +package juuxel.adorn.platform.forge.util; + +import juuxel.adorn.fluid.FluidReference; +import juuxel.adorn.fluid.FluidUnit; +import juuxel.adorn.fluid.FluidVolume; +import net.minecraft.fluid.Fluid; +import net.minecraft.nbt.NbtCompound; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.templates.FluidTank; +import org.jetbrains.annotations.Nullable; + +public final class FluidTankReference extends FluidReference { + private final FluidTank tank; + + public FluidTankReference(FluidTank tank) { + this.tank = tank; + } + + public FluidTank getTank() { + return tank; + } + + @Override + public Fluid getFluid() { + return tank.getFluid().getFluid(); + } + + @Override + public void setFluid(Fluid fluid) { + tank.setFluid(new FluidStack(fluid, tank.getFluid().getAmount(), tank.getFluid().getTag())); + } + + @Override + public long getAmount() { + return tank.getFluid().getAmount(); + } + + @Override + public void setAmount(long amount) { + tank.getFluid().setAmount((int) amount); + } + + @Override + public @Nullable NbtCompound getNbt() { + return tank.getFluid().getTag(); + } + + @Override + public void setNbt(@Nullable NbtCompound nbt) { + tank.getFluid().setTag(nbt); + } + + @Override + public FluidUnit getUnit() { + return FluidUnit.LITRE; + } + + /** + * Converts this fluid reference to a {@link FluidStack}. + * This is faster than a manual conversion for a {@code FluidTankReference}. + */ + public static FluidStack toFluidStack(FluidReference reference) { + if (reference instanceof FluidTankReference ftr) { + return ftr.tank.getFluid(); + } else { + return new FluidStack(reference.getFluid(), (int) reference.getAmount(), reference.getNbt()); + } + } + + public static FluidVolume toFluidVolume(FluidStack stack) { + return new FluidVolume(stack.getFluid(), stack.getAmount(), stack.getTag(), FluidUnit.LITRE); + } +} diff --git a/forge/src/main/kotlin/juuxel/adorn/platform/forge/FluidBridgeForge.kt b/forge/src/main/kotlin/juuxel/adorn/platform/forge/FluidBridgeForge.kt index 7cf45822d..bf28c238a 100644 --- a/forge/src/main/kotlin/juuxel/adorn/platform/forge/FluidBridgeForge.kt +++ b/forge/src/main/kotlin/juuxel/adorn/platform/forge/FluidBridgeForge.kt @@ -4,7 +4,7 @@ import juuxel.adorn.fluid.FluidAmountPredicate import juuxel.adorn.fluid.FluidUnit import juuxel.adorn.fluid.FluidVolume import juuxel.adorn.platform.FluidBridge -import juuxel.adorn.platform.forge.util.toFluidVolume +import juuxel.adorn.platform.forge.util.FluidTankReference import net.minecraft.block.BlockState import net.minecraft.fluid.Fluid import net.minecraft.util.math.BlockPos @@ -23,13 +23,13 @@ class FluidBridgeForge : FluidBridge { if (fluidHandler != null) { val upperBound = amountPredicate.upperBound - val maxAmount = FluidUnit.convert(upperBound.amount, from = upperBound.unit, to = FluidUnit.LITRE).toInt() + val maxAmount = FluidUnit.convert(upperBound.amount, upperBound.unit, FluidUnit.LITRE).toInt() val max = FluidStack(fluid, maxAmount) val extracted = fluidHandler.drain(max, IFluidHandler.FluidAction.SIMULATE) if (!extracted.isEmpty && amountPredicate.test(extracted.amount.toLong(), FluidUnit.LITRE)) { fluidHandler.drain(extracted, IFluidHandler.FluidAction.EXECUTE) - return extracted.toFluidVolume() + return FluidTankReference.toFluidVolume(extracted) } } diff --git a/forge/src/main/kotlin/juuxel/adorn/platform/forge/block/entity/KitchenSinkBlockEntityForge.kt b/forge/src/main/kotlin/juuxel/adorn/platform/forge/block/entity/KitchenSinkBlockEntityForge.kt index bc253a8e3..f7f27bd47 100644 --- a/forge/src/main/kotlin/juuxel/adorn/platform/forge/block/entity/KitchenSinkBlockEntityForge.kt +++ b/forge/src/main/kotlin/juuxel/adorn/platform/forge/block/entity/KitchenSinkBlockEntityForge.kt @@ -3,7 +3,6 @@ package juuxel.adorn.platform.forge.block.entity import juuxel.adorn.block.entity.KitchenSinkBlockEntity import juuxel.adorn.fluid.FluidReference import juuxel.adorn.platform.forge.util.FluidTankReference -import juuxel.adorn.platform.forge.util.toFluidStack import net.minecraft.block.BlockState import net.minecraft.entity.player.PlayerEntity import net.minecraft.fluid.Fluids @@ -112,10 +111,10 @@ class KitchenSinkBlockEntityForge(pos: BlockPos, state: BlockState) : KitchenSin } override fun getFillSound(fluid: FluidReference, stack: ItemStack): FluidItemSound = - super.getFillSound(fluid, stack).orElse(fluid.fluid.fluidType.getSound(fluid.toFluidStack(), SoundActions.BUCKET_FILL)) + super.getFillSound(fluid, stack).orElse(fluid.fluid.fluidType.getSound(FluidTankReference.toFluidStack(fluid), SoundActions.BUCKET_FILL)) override fun getEmptySound(fluid: FluidReference, stack: ItemStack): FluidItemSound = - super.getEmptySound(fluid, stack).orElse(fluid.fluid.fluidType.getSound(fluid.toFluidStack(), SoundActions.BUCKET_EMPTY)) + super.getEmptySound(fluid, stack).orElse(fluid.fluid.fluidType.getSound(FluidTankReference.toFluidStack(fluid), SoundActions.BUCKET_EMPTY)) override fun readNbt(nbt: NbtCompound) { super.readNbt(nbt) diff --git a/forge/src/main/kotlin/juuxel/adorn/platform/forge/client/FluidRenderingBridgeForge.kt b/forge/src/main/kotlin/juuxel/adorn/platform/forge/client/FluidRenderingBridgeForge.kt index f89d07c8f..317b6b88f 100644 --- a/forge/src/main/kotlin/juuxel/adorn/platform/forge/client/FluidRenderingBridgeForge.kt +++ b/forge/src/main/kotlin/juuxel/adorn/platform/forge/client/FluidRenderingBridgeForge.kt @@ -3,7 +3,7 @@ package juuxel.adorn.platform.forge.client import juuxel.adorn.client.FluidRenderingBridge import juuxel.adorn.fluid.FluidReference import juuxel.adorn.fluid.FluidUnit -import juuxel.adorn.platform.forge.util.toFluidStack +import juuxel.adorn.platform.forge.util.FluidTankReference import net.minecraft.client.MinecraftClient import net.minecraft.client.item.TooltipContext import net.minecraft.client.texture.Sprite @@ -23,7 +23,7 @@ object FluidRenderingBridgeForge : FluidRenderingBridge { override fun getStillSprite(volume: FluidReference): Sprite? { val fluid: Fluid = volume.fluid val atlas = MinecraftClient.getInstance().getSpriteAtlas(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE) - return atlas.apply(IClientFluidTypeExtensions.of(fluid).getStillTexture(volume.toFluidStack())) + return atlas.apply(IClientFluidTypeExtensions.of(fluid).getStillTexture(FluidTankReference.toFluidStack(volume))) } @OnlyIn(Dist.CLIENT) @@ -32,7 +32,7 @@ object FluidRenderingBridgeForge : FluidRenderingBridge { return if (world != null && pos != null) { IClientFluidTypeExtensions.of(fluid).getTintColor(fluid.defaultState, world, pos) } else { - IClientFluidTypeExtensions.of(fluid).getTintColor(volume.toFluidStack()) + IClientFluidTypeExtensions.of(fluid).getTintColor(FluidTankReference.toFluidStack(volume)) } } @@ -45,7 +45,7 @@ object FluidRenderingBridgeForge : FluidRenderingBridge { @OnlyIn(Dist.CLIENT) override fun getTooltip(volume: FluidReference, context: TooltipContext, maxAmountInLitres: Int?): List = buildList { val fluid: Fluid = volume.fluid - val stack = volume.toFluidStack() + val stack = FluidTankReference.toFluidStack(volume) val name = stack.displayName add(Text.empty().append(name).styled(fluid.fluidType.getRarity(stack).styleModifier)) diff --git a/forge/src/main/kotlin/juuxel/adorn/platform/forge/util/FluidReferences.kt b/forge/src/main/kotlin/juuxel/adorn/platform/forge/util/FluidReferences.kt deleted file mode 100644 index a2bafa1ed..000000000 --- a/forge/src/main/kotlin/juuxel/adorn/platform/forge/util/FluidReferences.kt +++ /dev/null @@ -1,48 +0,0 @@ -package juuxel.adorn.platform.forge.util - -import juuxel.adorn.fluid.FluidReference -import juuxel.adorn.fluid.FluidUnit -import juuxel.adorn.fluid.FluidVolume -import net.minecraft.fluid.Fluid -import net.minecraft.nbt.NbtCompound -import net.neoforged.neoforge.fluids.FluidStack -import net.neoforged.neoforge.fluids.capability.templates.FluidTank - -/** - * A [fluid reference][FluidReference] to a [FluidTank]. - */ -class FluidTankReference(val tank: FluidTank) : FluidReference() { - override var fluid: Fluid - get() = tank.fluid.fluid - set(value) { - tank.fluid = FluidStack(value, tank.fluid.amount, tank.fluid.tag) - } - - override var amount: Long - get() = tank.fluid.amount.toLong() - set(value) { - tank.fluid.amount = value.toInt() - } - - override var nbt: NbtCompound? - get() = tank.fluid.tag - set(value) { - tank.fluid.tag = value - } - - override val unit = FluidUnit.LITRE -} - -/** - * Converts this fluid reference to a [FluidStack]. - * This is faster than a manual conversion for a [FluidTankReference]. - */ -fun FluidReference.toFluidStack(): FluidStack = - if (this is FluidTankReference) { - tank.fluid - } else { - FluidStack(fluid, amount.toInt(), nbt) - } - -fun FluidStack.toFluidVolume(): FluidVolume = - FluidVolume(fluid, amount.toLong(), tag, FluidUnit.LITRE)