Skip to content

Commit

Permalink
24w09a - Transfer API (FabricMC#3626)
Browse files Browse the repository at this point in the history
* First pass on transfer API

* More fixes

* Another fix

* Small fixes

* Move transfer API tests to junit

* Fix client run

* Small fixes

* Copy stack when component changes

* Small improvement

* More tests and docs fixes

* Mutate existing stack
  • Loading branch information
modmuss50 authored Mar 1, 2024
1 parent 2f977a4 commit 9d6d003
Show file tree
Hide file tree
Showing 34 changed files with 585 additions and 384 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@

import org.jetbrains.annotations.Nullable;

import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.PotionContentsComponent;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BucketItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.PotionUtil;
import net.minecraft.potion.Potions;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
Expand Down Expand Up @@ -161,8 +162,8 @@ private FluidStorage() {
combinedItemApiProvider(Items.GLASS_BOTTLE).register(context -> {
return new EmptyItemFluidStorage(context, emptyBottle -> {
ItemStack newStack = emptyBottle.toStack();
PotionUtil.setPotion(newStack, Potions.WATER);
return ItemVariant.of(Items.POTION, newStack.getNbt());
newStack.set(DataComponentTypes.POTION_CONTENTS, new PotionContentsComponent(Potions.WATER));
return ItemVariant.of(Items.POTION, newStack.getComponentChanges());
}, Fluids.WATER, FluidConstants.BOTTLE);
});
// Register water potion storage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,35 @@

package net.fabricmc.fabric.api.transfer.v1.fluid;

import com.mojang.serialization.Codec;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

import net.minecraft.component.ComponentChanges;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.registry.entry.RegistryEntry;

import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
import net.fabricmc.fabric.impl.transfer.VariantCodecs;
import net.fabricmc.fabric.impl.transfer.fluid.FluidVariantImpl;

/**
* An immutable association of a still fluid and an optional NBT tag.
* An immutable association of a still fluid and data components.
*
* <p>Do not extend this class. Use {@link #of(Fluid)} and {@link #of(Fluid, NbtCompound)} to create instances.
* <p>Do not extend this class. Use {@link #of(Fluid)} and {@link #of(Fluid, ComponentChanges)} to create instances.
*
* <p>{@link net.fabricmc.fabric.api.transfer.v1.client.fluid.FluidVariantRendering} can be used for client-side rendering of fluid variants.
*
* <p><b>Fluid variants must always be compared with {@code equals}, never by reference!</b>
* {@code hashCode} is guaranteed to be correct and constant time independently of the size of the NBT.
* {@code hashCode} is guaranteed to be correct and constant time independently of the size of the components.
*/
@ApiStatus.NonExtendable
public interface FluidVariant extends TransferVariant<Fluid> {
Codec<FluidVariant> CODEC = VariantCodecs.FLUID_CODEC;
PacketCodec<RegistryByteBuf, FluidVariant> PACKET_CODEC = VariantCodecs.FLUID_PACKET_CODEC;

/**
* Retrieve a blank FluidVariant.
*/
Expand All @@ -54,18 +60,18 @@ static FluidVariant blank() {
* {@code FluidVariant.of(Fluids.FLOWING_WATER).getFluid() == Fluids.WATER}.
*/
static FluidVariant of(Fluid fluid) {
return of(fluid, null);
return of(fluid, ComponentChanges.EMPTY);
}

/**
* Retrieve a FluidVariant with a fluid, and an optional tag.
*
* <p>The flowing and still variations of {@linkplain net.minecraft.fluid.FlowableFluid flowable fluids}
* are normalized to always refer to the still fluid. For example,
* {@code FluidVariant.of(Fluids.FLOWING_WATER, nbt).getFluid() == Fluids.WATER}.
* {@code FluidVariant.of(Fluids.FLOWING_WATER, ComponentChanges.EMPTY).getFluid() == Fluids.WATER}.
*/
static FluidVariant of(Fluid fluid, @Nullable NbtCompound nbt) {
return FluidVariantImpl.of(fluid, nbt);
static FluidVariant of(Fluid fluid, ComponentChanges components) {
return FluidVariantImpl.of(fluid, components);
}

/**
Expand All @@ -75,19 +81,7 @@ default Fluid getFluid() {
return getObject();
}

/**
* Deserialize a variant from an NBT compound tag, assuming it was serialized using {@link #toNbt}.
*
* <p>If an error occurs during deserialization, it will be logged with the DEBUG level, and a blank variant will be returned.
*/
static FluidVariant fromNbt(NbtCompound nbt) {
return FluidVariantImpl.fromNbt(nbt);
}

/**
* Read a variant from a packet byte buffer, assuming it was serialized using {@link #toPacket}.
*/
static FluidVariant fromPacket(PacketByteBuf buf) {
return FluidVariantImpl.fromPacket(buf);
default RegistryEntry<Fluid> getRegistryEntry() {
return getFluid().getRegistryEntry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public final class EmptyItemFluidStorage implements InsertionOnlyStorage<FluidVa
* @param insertableAmount The amount of fluid that can be inserted.
*/
public EmptyItemFluidStorage(ContainerItemContext context, Item fullItem, Fluid insertableFluid, long insertableAmount) {
this(context, emptyVariant -> ItemVariant.of(fullItem, emptyVariant.getNbt()), insertableFluid, insertableAmount);
this(context, emptyVariant -> ItemVariant.of(fullItem, emptyVariant.getComponents()), insertableFluid, insertableAmount);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public final class FullItemFluidStorage implements ExtractionOnlyStorage<FluidVa
* @param containedAmount How much of {@code containedFluid} is contained.
*/
public FullItemFluidStorage(ContainerItemContext context, Item emptyItem, FluidVariant containedFluid, long containedAmount) {
this(context, fullVariant -> ItemVariant.of(emptyItem, fullVariant.getNbt()), containedFluid, containedAmount);
this(context, fullVariant -> ItemVariant.of(emptyItem, fullVariant.getComponents()), containedFluid, containedAmount);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Objects;

import net.minecraft.nbt.NbtCompound;
import net.minecraft.registry.RegistryWrapper;

import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
Expand Down Expand Up @@ -66,8 +67,14 @@ protected final FluidVariant getBlankVariant() {
* Simple implementation of reading from NBT, to match what is written by {@link #writeNbt}.
* Other formats are allowed, this is just a suggestion.
*/
public void readNbt(NbtCompound nbt) {
variant = FluidVariant.fromNbt(nbt.getCompound("variant"));
amount = nbt.getLong("amount");
public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
SingleVariantStorage.readNbt(this, FluidVariant.CODEC, FluidVariant::blank, nbt, wrapperLookup);
}

/**
* Simple implementation of writing to NBT. Other formats are allowed, this is just a convenient suggestion.
*/
public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
SingleVariantStorage.writeNbt(this, FluidVariant.CODEC, nbt, wrapperLookup);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,34 @@

package net.fabricmc.fabric.api.transfer.v1.item;

import java.util.Objects;

import com.mojang.serialization.Codec;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

import net.minecraft.component.ComponentChanges;
import net.minecraft.item.Item;
import net.minecraft.item.ItemConvertible;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.registry.entry.RegistryEntry;

import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
import net.fabricmc.fabric.impl.transfer.VariantCodecs;
import net.fabricmc.fabric.impl.transfer.item.ItemVariantImpl;

/**
* An immutable count-less ItemStack, i.e. an immutable association of an item and an optional NBT compound tag.
* An immutable count-less ItemStack, i.e. an immutable association of an item and its data components.
*
* <p>Do not implement, use the static {@code of(...)} functions instead.
*/
@ApiStatus.NonExtendable
public interface ItemVariant extends TransferVariant<Item> {
Codec<ItemVariant> CODEC = VariantCodecs.ITEM_CODEC;
PacketCodec<RegistryByteBuf, ItemVariant> PACKET_CODEC = VariantCodecs.ITEM_PACKET_CODEC;

/**
* Retrieve a blank ItemVariant.
*/
Expand All @@ -47,28 +55,28 @@ static ItemVariant blank() {
* Retrieve an ItemVariant with the item and tag of a stack.
*/
static ItemVariant of(ItemStack stack) {
return of(stack.getItem(), stack.getNbt());
return of(stack.getItem(), stack.getComponentChanges());
}

/**
* Retrieve an ItemVariant with an item and without a tag.
*/
static ItemVariant of(ItemConvertible item) {
return of(item, null);
return of(item, ComponentChanges.EMPTY);
}

/**
* Retrieve an ItemVariant with an item and an optional tag.
*/
static ItemVariant of(ItemConvertible item, @Nullable NbtCompound tag) {
return ItemVariantImpl.of(item.asItem(), tag);
static ItemVariant of(ItemConvertible item, ComponentChanges components) {
return ItemVariantImpl.of(item.asItem(), components);
}

/**
* Return true if the item and tag of this variant match those of the passed stack, and false otherwise.
*/
default boolean matches(ItemStack stack) {
return isOf(stack.getItem()) && nbtMatches(stack.getNbt());
return isOf(stack.getItem()) && Objects.equals(stack.getComponentChanges(), getComponents());
}

/**
Expand All @@ -78,6 +86,10 @@ default Item getItem() {
return getObject();
}

default RegistryEntry<Item> getRegistryEntry() {
return getItem().getRegistryEntry();
}

/**
* Create a new item stack with count 1 from this variant.
*/
Expand All @@ -92,25 +104,6 @@ default ItemStack toStack() {
*/
default ItemStack toStack(int count) {
if (isBlank()) return ItemStack.EMPTY;
ItemStack stack = new ItemStack(getItem(), count);
stack.setNbt(copyNbt());
return stack;
}

/**
* Deserialize a variant from an NBT compound tag, assuming it was serialized using
* {@link #toNbt}. If an error occurs during deserialization, it will be logged
* with the DEBUG level, and a blank variant will be returned.
*/
static ItemVariant fromNbt(NbtCompound nbt) {
return ItemVariantImpl.fromNbt(nbt);
}

/**
* Write a variant from a packet byte buffer, assuming it was serialized using
* {@link #toPacket}.
*/
static ItemVariant fromPacket(PacketByteBuf buf) {
return ItemVariantImpl.fromPacket(buf);
return new ItemStack(getRegistryEntry(), count, getComponents());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package net.fabricmc.fabric.api.transfer.v1.item.base;

import net.minecraft.nbt.NbtCompound;
import net.minecraft.registry.RegistryWrapper;

import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.TransferVariant;
Expand All @@ -40,8 +41,14 @@ protected final ItemVariant getBlankVariant() {
* Simple implementation of reading from NBT, to match what is written by {@link #writeNbt}.
* Other formats are allowed, this is just a suggestion.
*/
public void readNbt(NbtCompound nbt) {
variant = ItemVariant.fromNbt(nbt.getCompound("variant"));
amount = nbt.getLong("amount");
public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
SingleVariantStorage.readNbt(this, ItemVariant.CODEC, ItemVariant::blank, nbt, wrapperLookup);
}

/**
* Simple implementation of writing to NBT. Other formats are allowed, this is just a convenient suggestion.
*/
public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapperLookup) {
SingleVariantStorage.writeNbt(this, ItemVariant.CODEC, nbt, wrapperLookup);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,10 @@

import java.util.Objects;

import org.jetbrains.annotations.Nullable;

import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.component.ComponentChanges;

/**
* An immutable association of an immutable object instance (for example {@code Item} or {@code Fluid}) and an optional NBT tag.
* An immutable association of an immutable object instance (for example {@code Item} or {@code Fluid}) and data components.
*
* <p>This is exposed for convenience for code that needs to be generic across multiple transfer variants,
* but note that a {@link Storage} is not necessarily bound to {@code TransferVariant}. Its generic parameter can be any immutable object.
Expand All @@ -46,27 +43,24 @@ public interface TransferVariant<O> {
O getObject();

/**
* Return the underlying tag.
*
* <p><b>NEVER MUTATE THIS NBT TAG</b>, if you need to mutate it you can use {@link #copyNbt()} to retrieve a copy instead.
* @return The {@link ComponentChanges} of this variant.
*/
@Nullable
NbtCompound getNbt();
ComponentChanges getComponents();

/**
* Return true if this variant has a tag, false otherwise.
* Return true if this variant has a component changes.
*/
default boolean hasNbt() {
return getNbt() != null;
default boolean hasComponents() {
return !getComponents().isEmpty();
}

/**
* Return true if the tag of this variant matches the passed tag, and false otherwise.
*
* <p>Note: True is returned if both tags are {@code null}.
*/
default boolean nbtMatches(@Nullable NbtCompound other) {
return Objects.equals(getNbt(), other);
default boolean componentsMatches(ComponentChanges other) {
return Objects.equals(getComponents(), other);
}

/**
Expand All @@ -75,37 +69,4 @@ default boolean nbtMatches(@Nullable NbtCompound other) {
default boolean isOf(O object) {
return getObject() == object;
}

/**
* Return a copy of the tag of this variant, or {@code null} if this variant doesn't have a tag.
*
* <p>Note: Use {@link #nbtMatches} if you only need to check for custom tag equality, or {@link #getNbt()} if you don't need to mutate the tag.
*/
@Nullable
default NbtCompound copyNbt() {
NbtCompound nbt = getNbt();
return nbt == null ? null : nbt.copy();
}

/**
* Return a copy of the tag of this variant, or a new compound if this variant doesn't have a tag.
*/
default NbtCompound copyOrCreateNbt() {
NbtCompound nbt = getNbt();
return nbt == null ? new NbtCompound() : nbt.copy();
}

/**
* Save this variant into an NBT compound tag. Subinterfaces should have a matching static {@code fromNbt}.
*
* <p>Note: This is safe to use for persisting data as objects are saved using their full Identifier.
*/
NbtCompound toNbt();

/**
* Write this variant into a packet byte buffer. Subinterfaces should have a matching static {@code fromPacket}.
*
* <p>Implementation note: Objects are saved using their raw registry integer id.
*/
void toPacket(PacketByteBuf buf);
}
Loading

0 comments on commit 9d6d003

Please sign in to comment.