Skip to content

Commit

Permalink
Add loadables for common types
Browse files Browse the repository at this point in the history
Includes:
* ResourceLocation
* Registry loadables for any vanilla registry we might reasonably use, plus helpers to create more
* Tag key loadables for common tag types
* TagCompound for raw NBT fields
* ItemStack and FluidStack (with notably advantages over the recipe helper methods)
* Ingredient and FluidIngredient (with the ability to allow or reject empty)
* ItemOutput

In addition, GenericLoaderRegistry and NamedComponentRegistry both implement loadable so they can be used in other builders
  • Loading branch information
KnightMiner committed Mar 21, 2024
1 parent 82ad93b commit d690781
Show file tree
Hide file tree
Showing 13 changed files with 755 additions and 65 deletions.
67 changes: 67 additions & 0 deletions src/main/java/slimeknights/mantle/data/loadable/Loadables.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package slimeknights.mantle.data.loadable;

import net.minecraft.ResourceLocationException;
import net.minecraft.core.Registry;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.tags.TagKey;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.alchemy.Potion;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.material.Fluid;
import slimeknights.mantle.data.loadable.common.RegistryLoadable;
import slimeknights.mantle.data.loadable.primitive.StringLoadable;

/** Various loadable instances provided by this mod */
@SuppressWarnings({"deprecation", "unused"})
public class Loadables {
private Loadables() {}

/** Loadable for a resource location */
public static final Loadable<ResourceLocation> RESOURCE_LOCATION = StringLoadable.DEFAULT.map((s, e) -> {
try {
return new ResourceLocation(s);
} catch (ResourceLocationException ex) {
throw e.create(ex);
}
}, (r, e) -> r.toString());

/* Registries */
public static final Loadable<SoundEvent> SOUND_EVENT = new RegistryLoadable<>(Registry.SOUND_EVENT);
public static final Loadable<Fluid> FLUID = new RegistryLoadable<>(Registry.FLUID);
public static final Loadable<MobEffect> MOB_EFFECT = new RegistryLoadable<>(Registry.MOB_EFFECT);
public static final Loadable<Block> BLOCK = new RegistryLoadable<>(Registry.BLOCK);
public static final Loadable<Enchantment> ENCHANTMENT = new RegistryLoadable<>(Registry.ENCHANTMENT);
public static final Loadable<EntityType<?>> ENTITY_TYPE = new RegistryLoadable<>(Registry.ENTITY_TYPE);
public static final Loadable<Item> ITEM = new RegistryLoadable<>(Registry.ITEM);
public static final Loadable<Potion> POTION = new RegistryLoadable<>(Registry.POTION);
public static final Loadable<ParticleType<?>> PARTICLE_TYPE = new RegistryLoadable<>(Registry.PARTICLE_TYPE);
public static final Loadable<BlockEntityType<?>> BLOCK_ENTITY_TYPE = new RegistryLoadable<>(Registry.BLOCK_ENTITY_TYPE);
public static final Loadable<Attribute> ATTRIBUTE = new RegistryLoadable<>(Registry.ATTRIBUTE);


/* Tag keys */
public static final Loadable<TagKey<Fluid>> FLUID_TAG = tagKey(Registry.FLUID_REGISTRY);
public static final Loadable<TagKey<MobEffect>> MOB_EFFECT_TAG = tagKey(Registry.MOB_EFFECT_REGISTRY);
public static final Loadable<TagKey<Block>> BLOCK_TAG = tagKey(Registry.BLOCK_REGISTRY);
public static final Loadable<TagKey<Enchantment>> ENCHANTMENT_TAG = tagKey(Registry.ENCHANTMENT_REGISTRY);
public static final Loadable<TagKey<EntityType<?>>> ENTITY_TYPE_TAG = tagKey(Registry.ENTITY_TYPE_REGISTRY);
public static final Loadable<TagKey<Item>> ITEM_TAG = tagKey(Registry.ITEM_REGISTRY);
public static final Loadable<TagKey<Potion>> POTION_TAG = tagKey(Registry.POTION_REGISTRY);
public static final Loadable<TagKey<BlockEntityType<?>>> BLOCK_ENTITY_TYPE_TAG = tagKey(Registry.BLOCK_ENTITY_TYPE_REGISTRY);


/* Helpers */

/** Creates a tag key loadable */
public static <T> Loadable<TagKey<T>> tagKey(ResourceKey<? extends Registry<T>> registry) {
return RESOURCE_LOCATION.flatMap(key -> TagKey.create(registry, key), TagKey::location);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package slimeknights.mantle.data.loadable.common;

import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import net.minecraft.network.FriendlyByteBuf;
import slimeknights.mantle.data.loadable.ErrorFactory;
import slimeknights.mantle.data.loadable.Loadable;

/** Implementation of a loadable using a codec. Note this will be inefficient when reading from and writing to the network */
public record CodecLoadable<T>(Codec<T> codec) implements Loadable<T> {
@Override
public T convert(JsonElement element, String key) throws JsonSyntaxException {
return codec.parse(JsonOps.INSTANCE, element).getOrThrow(false, ErrorFactory.JSON_SYNTAX_ERROR);
}

@Override
public JsonElement serialize(T object) throws RuntimeException {
return codec.encodeStart(JsonOps.INSTANCE, object).getOrThrow(false, ErrorFactory.RUNTIME);
}

@Override
public T fromNetwork(FriendlyByteBuf buffer) throws DecoderException {
return buffer.readWithCodec(codec);
}

@Override
public void toNetwork(T object, FriendlyByteBuf buffer) throws EncoderException {
buffer.writeWithCodec(codec, object);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package slimeknights.mantle.data.loadable.common;

import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import io.netty.handler.codec.EncoderException;
import lombok.RequiredArgsConstructor;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
import net.minecraftforge.fluids.FluidStack;
import slimeknights.mantle.data.loadable.Loadables;
import slimeknights.mantle.data.loadable.record.RecordLoadable;

/** Loadable for a fluid stack */
@RequiredArgsConstructor
public enum FluidStackLoadable implements RecordLoadable<FluidStack> {
/** Loads a non-empty fluid stack, ignoring NBT */
NON_EMPTY(false, true),
/** Loads a non-empty fluid stack, including NBT */
NON_EMPTY_NBT(true, true),
/** Loads a fluid stack that may be empty, ignoring NBT */
EMPTY(false, false),
/** Loads a fluid stack that may be empty, including NBT */
EMPTY_NBT(true, false);

/** If true, we read NBT */
private final boolean readNBT;
/** If true, we disallow reading empty stacks */
private final boolean disallowEmpty;

@Override
public FluidStack convert(JsonElement element, String key) {
if (!disallowEmpty && element.isJsonNull()) {
return FluidStack.EMPTY;
}
return convert(element, key);
}

/** Deserializes this stack from an object */
@Override
public FluidStack deserialize(JsonObject json) {
Fluid fluid = Fluids.EMPTY;
// if we disallow empty, force parsing the fluid so we get a missing field error
// item field is optional if we allow empty
if (json.has("fluid") || disallowEmpty) {
fluid = Loadables.FLUID.getAndDeserialize(json, "fluid");
}
// air may come from the default or the registry, either is disallowed if we disallow empty
if (fluid == Fluids.EMPTY) {
if (disallowEmpty) {
throw new JsonSyntaxException("FluidStack may not be empty");
}
return FluidStack.EMPTY;
}
// we handle empty via item, so amount is not even considered, thus amount of 0 is invalid
int amount = GsonHelper.getAsInt(json, "amount");
if (amount <= 0) {
throw new JsonSyntaxException("FluidStack amount must greater than 0");
}
CompoundTag tag = null;
if (readNBT && json.has("nbt")) {
tag = NBTLoadable.ALLOW_STRING.convert(json.get("nbt"), "nbt");
}
return new FluidStack(fluid, amount, tag);
}

@Override
public FluidStack getAndDeserialize(JsonObject parent, String key) {
if (!disallowEmpty && !parent.has(key)) {
return FluidStack.EMPTY;
}
return RecordLoadable.super.getAndDeserialize(parent, key);
}

@Override
public JsonElement serialize(FluidStack stack) {
if (stack.isEmpty()) {
if (disallowEmpty) {
throw new IllegalArgumentException("FluidStack must not be empty");
}
return JsonNull.INSTANCE;
}
return RecordLoadable.super.serialize(stack);
}

@Override
public void serialize(FluidStack stack, JsonObject json) {
if (stack.isEmpty()) {
if (disallowEmpty) {
throw new IllegalArgumentException("FluidStack must not be empty");
}
return;
}
json.add("fluid", Loadables.FLUID.serialize(stack.getFluid()));
json.addProperty("amount", stack.getAmount());
CompoundTag tag = readNBT ? stack.getTag() : null;
if (tag != null) {
json.add("nbt", NBTLoadable.ALLOW_STRING.serialize(tag));
}
}

@Override
public FluidStack fromNetwork(FriendlyByteBuf buffer) {
return FluidStack.readFromPacket(buffer);
}

@Override
public void toNetwork(FluidStack stack, FriendlyByteBuf buffer) throws EncoderException {
stack.writeToPacket(buffer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package slimeknights.mantle.data.loadable.common;

import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.item.crafting.Ingredient;
import slimeknights.mantle.data.loadable.Loadable;

/** Loadable for ingredients, handling Forge ingredients */
public enum IngredientLoadable implements Loadable<Ingredient> {
ALLOW_EMPTY,
DISALLOW_EMPTY;

@Override
public Ingredient getAndDeserialize(JsonObject parent, String key) {
if (this == ALLOW_EMPTY && !parent.has(key)) {
return Ingredient.EMPTY;
}
return Loadable.super.getAndDeserialize(parent, key);
}

@Override
public Ingredient convert(JsonElement element, String key) throws JsonSyntaxException {
Ingredient ingredient = Ingredient.fromJson(element);
if (ingredient == Ingredient.EMPTY && this == DISALLOW_EMPTY) {
throw new JsonSyntaxException("Ingredient cannot be empty");
}
return ingredient;
}

@Override
public JsonElement serialize(Ingredient object) throws RuntimeException {
if (object == Ingredient.EMPTY) {
if (this == ALLOW_EMPTY) {
return JsonNull.INSTANCE;
}
throw new IllegalArgumentException("Ingredient cannot be empty");
}
return object.toJson();
}

@Override
public Ingredient fromNetwork(FriendlyByteBuf buffer) throws DecoderException {
return Ingredient.fromNetwork(buffer);
}

@Override
public void toNetwork(Ingredient object, FriendlyByteBuf buffer) throws EncoderException {
object.toNetwork(buffer);
}
}

0 comments on commit d690781

Please sign in to comment.