Skip to content

Commit

Permalink
More work on documentation, add custom display fluid ingredient
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxNeedsSnacks authored and Technici4n committed Oct 20, 2024
1 parent b4a4163 commit 7f94384
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 8 deletions.
2 changes: 2 additions & 0 deletions src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
import net.neoforged.neoforge.fluids.CauldronFluidContent;
import net.neoforged.neoforge.fluids.FluidType;
import net.neoforged.neoforge.fluids.crafting.CompoundFluidIngredient;
import net.neoforged.neoforge.fluids.crafting.CustomDisplayFluidIngredient;
import net.neoforged.neoforge.fluids.crafting.DataComponentFluidIngredient;
import net.neoforged.neoforge.fluids.crafting.DifferenceFluidIngredient;
import net.neoforged.neoforge.fluids.crafting.FluidIngredientCodecs;
Expand Down Expand Up @@ -377,6 +378,7 @@ public class NeoForgeMod {
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<DataComponentFluidIngredient>> DATA_COMPONENT_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("components", () -> new FluidIngredientType<>(DataComponentFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<DifferenceFluidIngredient>> DIFFERENCE_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("difference", () -> new FluidIngredientType<>(DifferenceFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<IntersectionFluidIngredient>> INTERSECTION_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("intersection", () -> new FluidIngredientType<>(IntersectionFluidIngredient.CODEC));
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<CustomDisplayFluidIngredient>> CUSTOM_DISPLAY_FLUID_INGREDIENT = FLUID_INGREDIENT_TYPES.register("custom_display", () -> new FluidIngredientType<>(CustomDisplayFluidIngredient.CODEC, CustomDisplayFluidIngredient.STREAM_CODEC));

private static final DeferredRegister<MapCodec<? extends ICondition>> CONDITION_CODECS = DeferredRegister.create(NeoForgeRegistries.Keys.CONDITION_CODECS, NeoForgeVersion.MOD_ID);
public static final DeferredHolder<MapCodec<? extends ICondition>, MapCodec<AndCondition>> AND_CONDITION = CONDITION_CODECS.register("and", () -> AndCondition.CODEC);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.fluids.crafting;

import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.Objects;
import java.util.stream.Stream;
import net.minecraft.core.Holder;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.item.crafting.display.SlotDisplay;
import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.fluids.FluidStack;

/**
* FluidIngredient that wraps another fluid ingredient to override its {@link SlotDisplay}.
*/
public final class CustomDisplayFluidIngredient extends FluidIngredient {
public static final MapCodec<CustomDisplayFluidIngredient> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance
.group(
FluidIngredient.CODEC.fieldOf("base").forGetter(CustomDisplayFluidIngredient::base),
SlotDisplay.CODEC.fieldOf("display").forGetter(CustomDisplayFluidIngredient::display))
.apply(instance, CustomDisplayFluidIngredient::new));

public static final StreamCodec<RegistryFriendlyByteBuf, CustomDisplayFluidIngredient> STREAM_CODEC = StreamCodec.composite(
FluidIngredient.STREAM_CODEC,
CustomDisplayFluidIngredient::base,
SlotDisplay.STREAM_CODEC,
CustomDisplayFluidIngredient::display,
CustomDisplayFluidIngredient::new);

private final FluidIngredient base;
private final SlotDisplay display;

public CustomDisplayFluidIngredient(FluidIngredient base, SlotDisplay display) {
this.base = base;
this.display = display;
}

public static FluidIngredient of(FluidIngredient base, SlotDisplay display) {
return new CustomDisplayFluidIngredient(base, display);
}

@Override
public boolean test(FluidStack stack) {
return base.test(stack);
}

@Override
public Stream<Holder<Fluid>> generateFluids() {
return base.generateFluids();
}

@Override
public boolean isSimple() {
return base.isSimple();
}

@Override
public FluidIngredientType<?> getType() {
return NeoForgeMod.CUSTOM_DISPLAY_FLUID_INGREDIENT.get();
}

public FluidIngredient base() {
return base;
}

@Override
public SlotDisplay display() {
return display;
}

@Override
public boolean equals(Object obj) {
return obj instanceof CustomDisplayFluidIngredient other &&
Objects.equals(this.base, other.base) &&
Objects.equals(this.display, other.display);
}

@Override
public int hashCode() {
return Objects.hash(base, display);
}

@Override
public String toString() {
return "CustomDisplayFluidIngredient[" +
"base=" + base + ", " +
"display=" + display + ']';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,17 @@ public final List<Holder<Fluid>> fluids() {
@ApiStatus.OverrideOnly
protected abstract Stream<Holder<Fluid>> generateFluids();

// TODO(max): docs
/**
* {@return a slot display for this ingredient, used for display on the client-side}
*
* @implNote The default implementation just constructs a list of stacks from {@link #fluids()}.
* This is generally suitable for {@link #isSimple() simple} ingredients.
* Non-simple ingredients can either override this method to provide a more customized display,
* or let data pack writers use {@link CustomDisplayFluidIngredient} to override the display of an ingredient.
*
* @see Ingredient#display()
* @see FluidSlotDisplay
*/
public SlotDisplay display() {
return new SlotDisplay.Composite(fluids()
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.HolderSetCodec;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.item.crafting.display.SlotDisplay;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.fluids.FluidStack;
import net.neoforged.neoforge.fluids.crafting.display.FluidTagSlotDisplay;

/**
* Fluid ingredient that matches the fluids specified by the given {@link HolderSet}.
Expand Down Expand Up @@ -70,6 +72,13 @@ public FluidIngredientType<?> getType() {
return NeoForgeMod.SIMPLE_FLUID_INGREDIENT_TYPE.get();
}

@Override
public SlotDisplay display() {
return values.unwrapKey()
.<SlotDisplay>map(FluidTagSlotDisplay::new)
.orElseGet(super::display);
}

@Override
public int hashCode() {
return this.fluidSet().hashCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
*/
public final class SizedFluidIngredient {
/**
* The "nested" codec for {@link SizedFluidIngredient}.
* The codec for {@link SizedFluidIngredient}.
*
* <p>With this codec, the amount is serialized separately from the ingredient itself, for example:
*
Expand All @@ -39,9 +39,23 @@ public final class SizedFluidIngredient {
* }
* }</pre>
*
* TODO(max): improve documentation
* <p>
* or for custom ingredients:
*
* <pre>{@code
* {
* "ingredient": {
* "neoforge:type": "neoforge:intersection",
* "children": [
* "#example:tag1",
* "#example:tag2"
* ],
* },
* "amount": 4711
* }
* }</pre>
*/
public static final Codec<SizedFluidIngredient> NESTED_CODEC = RecordCodecBuilder.create(instance -> instance.group(
public static final Codec<SizedFluidIngredient> CODEC = RecordCodecBuilder.create(instance -> instance.group(
FluidIngredient.CODEC.fieldOf("ingredient").forGetter(SizedFluidIngredient::ingredient),
NeoForgeExtraCodecs.optionalFieldAlwaysWrite(ExtraCodecs.POSITIVE_INT, "amount", FluidType.BUCKET_VOLUME).forGetter(SizedFluidIngredient::amount))
.apply(instance, SizedFluidIngredient::new));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.NeoForgeMod;

/**
* Slot display for a single fluid holder.
* <p>
* Note that information on amount and data of the displayed fluid stack depends on the provided factory!
*
* @param fluid The fluid to be displayed.
*/
public record FluidSlotDisplay(Holder<Fluid> fluid) implements SlotDisplay {
public static final MapCodec<FluidSlotDisplay> MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(RegistryFixedCodec.create(Registries.FLUID).fieldOf("fluid").forGetter(FluidSlotDisplay::fluid))
.apply(instance, FluidSlotDisplay::new));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
import net.neoforged.neoforge.common.NeoForgeMod;
import net.neoforged.neoforge.fluids.FluidStack;

/**
* Slot display for a given fluid stack, including fluid amount and data components.
*
* @param stack The fluid stack to be displayed.
*/
public record FluidStackSlotDisplay(FluidStack stack) implements SlotDisplay {
public static final MapCodec<FluidStackSlotDisplay> MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(FluidStack.CODEC.fieldOf("fluid").forGetter(FluidStackSlotDisplay::stack))
.apply(instance, FluidStackSlotDisplay::new));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
import net.minecraft.world.level.material.Fluid;
import net.neoforged.neoforge.common.NeoForgeMod;

/**
* Slot display that shows all fluids in a given tag.
*
* Note that information on amount and data of the displayed fluid stacks depends on the provided factory!
*
* @param tag The tag to be displayed.
*/
public record FluidTagSlotDisplay(TagKey<Fluid> tag) implements SlotDisplay {
public static final MapCodec<FluidTagSlotDisplay> MAP_CODEC = RecordCodecBuilder.mapCodec(
p_379704_ -> p_379704_.group(TagKey.codec(Registries.FLUID).fieldOf("tag").forGetter(FluidTagSlotDisplay::tag))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,28 @@
import net.neoforged.neoforge.fluids.FluidType;

public interface ForFluidStacks<T> extends DisplayContentsFactory<T> {
/**
* {@return display data for the given fluid holder}
*
* @param fluid Fluid holder to display.
*/
default T forStack(Holder<Fluid> fluid) {
return this.forStack(new FluidStack(fluid, FluidType.BUCKET_VOLUME));
}

/**
* {@return display data for the given fluid}
*
* @param fluid Fluid to display.
*/
default T forStack(Fluid fluid) {
return this.forStack(fluid.builtInRegistryHolder());
}

/**
* {@return display data for the given fluid stack}
*
* @param fluid Fluid stack to display
*/
T forStack(FluidStack fluid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,16 @@ static void basicFluidIngredientSerialization(ExtendedGameTestHelper helper) {
helper.assertFalse(singleJson.isJsonObject(), "single fluid ingredient should not serialize as nested object!");
helper.assertFalse(tagJson.isJsonObject(), "tag fluid ingredient should not serialize as nested object!");

helper.assertValueEqual(singleJson.toString(), "minecraft:water", "serialized single fluid ingredient to match HolderSet element format!");
helper.assertValueEqual(tagJson.toString(), "#c:water", "serialized tag fluid ingredient to match HolderSet tag format!");
helper.assertValueEqual(singleJson.getAsString(), Fluids.WATER.builtInRegistryHolder().getRegisteredName(), "serialized single fluid ingredient to match HolderSet element format!");
helper.assertValueEqual(tagJson.getAsString(), Tags.Fluids.WATER.location().toString(), "serialized tag fluid ingredient to match HolderSet tag format!");

// tests that deserializing simple ingredients is reproducible and produces the desired ingredients
var singleTwo = FluidIngredient.CODEC.parse(ops, singleJson)
.resultOrPartial(error -> helper.fail("Failed to deserialize single fluid ingredient from JSON: " + error))
.orElseThrow();
helper.assertValueEqual(singleFluid, singleTwo, "single fluid ingredient to be the same after being serialized and deserialized!");

var tagTwo = FluidIngredient.CODEC.parse(JsonOps.INSTANCE, tagJson)
var tagTwo = FluidIngredient.CODEC.parse(ops, tagJson)
.resultOrPartial(error -> helper.fail("Failed to deserialize single fluid ingredient from JSON: " + error))
.orElseThrow();
helper.assertValueEqual(tagFluid, tagTwo, "tag fluid ingredient to be the same after being serialized and deserialized!");
Expand All @@ -77,7 +77,7 @@ static void basicFluidIngredientSerialization(ExtendedGameTestHelper helper) {
static void sizedFluidIngredientSerialization(final GameTestHelper helper) {
var sized = SizedFluidIngredient.of(Fluids.WATER, 1000);

var nestedResult = SizedFluidIngredient.NESTED_CODEC.encodeStart(JsonOps.INSTANCE, sized);
var nestedResult = SizedFluidIngredient.CODEC.encodeStart(JsonOps.INSTANCE, sized);
var nestedJson = nestedResult.resultOrPartial((error) -> helper.fail("Error while encoding SizedFluidIngredient: " + error)).orElseThrow();

helper.assertValueEqual(nestedJson.toString(), "{\"ingredient\":\"minecraft:water\",\"amount\":1000}", "(nested) serialized SizedFluidIngredient");
Expand Down

0 comments on commit 7f94384

Please sign in to comment.