Fix codecs jank

This commit is contained in:
Sollace 2023-09-30 17:48:18 +01:00
parent c3229a3126
commit 69b6e3c92c
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
8 changed files with 94 additions and 56 deletions

View file

@ -7,15 +7,11 @@ import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
import com.google.gson.JsonParseException;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.item.EnchantableItem; import com.minelittlepony.unicopia.item.EnchantableItem;
import com.minelittlepony.unicopia.util.CodecUtils;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec; 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.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.Ingredient;
@ -25,23 +21,11 @@ public class IngredientWithSpell implements Predicate<ItemStack> {
private static final IngredientWithSpell EMPTY = new IngredientWithSpell(Optional.empty(), Optional.empty()); private static final IngredientWithSpell EMPTY = new IngredientWithSpell(Optional.empty(), Optional.empty());
private static final Predicate<Ingredient> INGREDIENT_IS_PRESENT = ((Predicate<Ingredient>)(Ingredient::isEmpty)).negate(); private static final Predicate<Ingredient> INGREDIENT_IS_PRESENT = ((Predicate<Ingredient>)(Ingredient::isEmpty)).negate();
public static final Codec<IngredientWithSpell> CODEC = Codec.of(new Encoder<IngredientWithSpell>() { public static final Codec<IngredientWithSpell> CODEC = CodecUtils.extend(Ingredient.ALLOW_EMPTY_CODEC, SpellType.REGISTRY.getCodec().fieldOf("spell")).xmap(
@Override pair -> new IngredientWithSpell(pair.getFirst(), pair.getSecond()),
public <T> DataResult<T> encode(IngredientWithSpell input, DynamicOps<T> ops, T prefix) { ingredient -> new Pair<>(ingredient.stack, ingredient.spell)
throw new JsonParseException("cannot serialize this type");
}
}, new Decoder<IngredientWithSpell>() {
@Override
public <T> DataResult<Pair<IngredientWithSpell, T>> decode(DynamicOps<T> ops, T input) {
// TODO: Doing codecs properly is an exercise left to the readers
return DataResult.success(new Pair<>(
new IngredientWithSpell(
Ingredient.ALLOW_EMPTY_CODEC.decode(ops, input).map(Pair::getFirst).result(),
ops.getMap(input).flatMap(maplike -> SpellType.REGISTRY.getCodec().parse(ops, maplike.get("spell"))).result()
), input)
); );
}
});
public static final Codec<DefaultedList<IngredientWithSpell>> LIST_CODEC = CODEC.listOf().xmap( public static final Codec<DefaultedList<IngredientWithSpell>> LIST_CODEC = CODEC.listOf().xmap(
list -> DefaultedList.<IngredientWithSpell>copyOf(EMPTY, list.toArray(IngredientWithSpell[]::new)), list -> DefaultedList.<IngredientWithSpell>copyOf(EMPTY, list.toArray(IngredientWithSpell[]::new)),
Function.identity() Function.identity()

View file

@ -10,6 +10,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.container.inventory.SpellbookInventory; import com.minelittlepony.unicopia.container.inventory.SpellbookInventory;
import com.minelittlepony.unicopia.item.EnchantableItem; import com.minelittlepony.unicopia.item.EnchantableItem;
import com.minelittlepony.unicopia.item.URecipes; import com.minelittlepony.unicopia.item.URecipes;
import com.minelittlepony.unicopia.util.CodecUtils;
import com.minelittlepony.unicopia.util.InventoryUtil; import com.minelittlepony.unicopia.util.InventoryUtil;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
@ -45,9 +46,9 @@ public class SpellCraftingRecipe implements SpellbookRecipe {
/** /**
* The resulting item * The resulting item
*/ */
final ItemStackWithSpell output; final ItemStack output;
private SpellCraftingRecipe(IngredientWithSpell material, TraitIngredient requiredTraits, List<IngredientWithSpell> requiredItems, ItemStackWithSpell output) { private SpellCraftingRecipe(IngredientWithSpell material, TraitIngredient requiredTraits, List<IngredientWithSpell> requiredItems, ItemStack output) {
this.material = material; this.material = material;
this.requiredTraits = requiredTraits; this.requiredTraits = requiredTraits;
this.requiredItems = requiredItems; this.requiredItems = requiredItems;
@ -63,7 +64,7 @@ public class SpellCraftingRecipe implements SpellbookRecipe {
requiredTraits.min().ifPresent(min -> { requiredTraits.min().ifPresent(min -> {
min.forEach(e -> builder.input(e.getKey(), e.getValue())); min.forEach(e -> builder.input(e.getKey(), e.getValue()));
}); });
builder.result(output.toItemStack()); builder.result(output);
} }
@Override @Override
@ -115,7 +116,7 @@ public class SpellCraftingRecipe implements SpellbookRecipe {
@Override @Override
public ItemStack getResult(DynamicRegistryManager registries) { public ItemStack getResult(DynamicRegistryManager registries) {
return output.toItemStack(); return output;
} }
@Override @Override
@ -123,29 +124,17 @@ public class SpellCraftingRecipe implements SpellbookRecipe {
return URecipes.TRAIT_REQUIREMENT; return URecipes.TRAIT_REQUIREMENT;
} }
record ItemStackWithSpell(ItemStack stack, Optional<SpellType<?>> spell) {
public static final Codec<ItemStackWithSpell> CODEC = RecordCodecBuilder.create(instance -> instance.group(
RecipeCodecs.CRAFTING_RESULT.fieldOf("stack").forGetter(ItemStackWithSpell::stack),
SpellType.REGISTRY.getCodec().optionalFieldOf("spell").forGetter(ItemStackWithSpell::spell)
).apply(instance, ItemStackWithSpell::new));
public ItemStack toItemStack() {
return spell.filter(s -> s != SpellType.EMPTY_KEY).map(s -> {
return EnchantableItem.enchant(stack.copy(), s);
}).orElse(stack);
}
public ItemStackWithSpell(ItemStack stack) {
this(EnchantableItem.unenchant(stack), Optional.of(EnchantableItem.getSpellKey(stack)));
}
}
public static class Serializer implements RecipeSerializer<SpellCraftingRecipe> { public static class Serializer implements RecipeSerializer<SpellCraftingRecipe> {
private static final Codec<ItemStack> RESULT_CODEC = CodecUtils.extend(RecipeCodecs.CRAFTING_RESULT, SpellType.REGISTRY.getCodec().fieldOf("spell")).xmap(
pair -> pair.getSecond().map(spell -> EnchantableItem.enchant(pair.getFirst().orElse(ItemStack.EMPTY), spell)).orElse(pair.getFirst().orElse(ItemStack.EMPTY)),
stack -> Pair.of(Optional.of(stack), EnchantableItem.getSpellKeyOrEmpty(stack))
);
private static final Codec<SpellCraftingRecipe> CODEC = RecordCodecBuilder.<SpellCraftingRecipe>create(instance -> instance.group( private static final Codec<SpellCraftingRecipe> CODEC = RecordCodecBuilder.<SpellCraftingRecipe>create(instance -> instance.group(
IngredientWithSpell.CODEC.fieldOf("material").forGetter(recipe -> recipe.material), IngredientWithSpell.CODEC.fieldOf("material").forGetter(recipe -> recipe.material),
TraitIngredient.CODEC.fieldOf("traits").forGetter(recipe -> recipe.requiredTraits), TraitIngredient.CODEC.fieldOf("traits").forGetter(recipe -> recipe.requiredTraits),
IngredientWithSpell.CODEC.listOf().fieldOf("ingredients").forGetter(recipe -> recipe.requiredItems), IngredientWithSpell.CODEC.listOf().fieldOf("ingredients").forGetter(recipe -> recipe.requiredItems),
ItemStackWithSpell.CODEC.fieldOf("result").forGetter(recipe -> recipe.output) RESULT_CODEC.fieldOf("result").forGetter(recipe -> recipe.output)
).apply(instance, SpellCraftingRecipe::new)); ).apply(instance, SpellCraftingRecipe::new));
@Override @Override
@ -159,7 +148,7 @@ public class SpellCraftingRecipe implements SpellbookRecipe {
IngredientWithSpell.fromPacket(buf), IngredientWithSpell.fromPacket(buf),
TraitIngredient.fromPacket(buf), TraitIngredient.fromPacket(buf),
buf.readCollection(DefaultedList::ofSize, IngredientWithSpell::fromPacket), buf.readCollection(DefaultedList::ofSize, IngredientWithSpell::fromPacket),
new ItemStackWithSpell(buf.readItemStack()) buf.readItemStack()
); );
} }
@ -168,7 +157,7 @@ public class SpellCraftingRecipe implements SpellbookRecipe {
recipe.material.write(buf); recipe.material.write(buf);
recipe.requiredTraits.write(buf); recipe.requiredTraits.write(buf);
buf.writeCollection(recipe.requiredItems, (b, i) -> i.write(b)); buf.writeCollection(recipe.requiredItems, (b, i) -> i.write(b));
buf.writeItemStack(recipe.output.toItemStack()); buf.writeItemStack(recipe.output);
} }
} }
} }

View file

@ -163,7 +163,7 @@ public enum Trait implements CommandArgumentEnum<Trait> {
@Deprecated @Deprecated
public static Optional<Trait> fromId(Identifier id) { public static Optional<Trait> fromId(Identifier id) {
return Optional.ofNullable(ID_CODEC.byId(id.toString())); return Optional.ofNullable(IDS.get(id));
} }
@Deprecated @Deprecated

View file

@ -3,9 +3,9 @@ package com.minelittlepony.unicopia.advancement;
import net.minecraft.advancement.criterion.Criteria; import net.minecraft.advancement.criterion.Criteria;
public interface UCriteria { public interface UCriteria {
CustomEventCriterion CUSTOM_EVENT = Criteria.register("unicopia:send_dragon_breath", new CustomEventCriterion()); CustomEventCriterion CUSTOM_EVENT = Criteria.register("unicopia:custom", new CustomEventCriterion());
RaceChangeCriterion PLAYER_CHANGE_RACE = Criteria.register("unicopia:player_change_race", new RaceChangeCriterion()); RaceChangeCriterion PLAYER_CHANGE_RACE = Criteria.register("unicopia:player_change_race", new RaceChangeCriterion());
SendViaDragonBreathScrollCriterion SEND_DRAGON_BREATH = Criteria.register("unicopia:custom", new SendViaDragonBreathScrollCriterion()); SendViaDragonBreathScrollCriterion SEND_DRAGON_BREATH = Criteria.register("unicopia:send_dragon_breath", new SendViaDragonBreathScrollCriterion());
CustomEventCriterion.Trigger LOOK_INTO_SUN = CUSTOM_EVENT.createTrigger("look_into_sun"); CustomEventCriterion.Trigger LOOK_INTO_SUN = CUSTOM_EVENT.createTrigger("look_into_sun");
CustomEventCriterion.Trigger WEAR_SHADES = CUSTOM_EVENT.createTrigger("wear_shades"); CustomEventCriterion.Trigger WEAR_SHADES = CUSTOM_EVENT.createTrigger("wear_shades");

View file

@ -1,5 +1,6 @@
package com.minelittlepony.unicopia.item; package com.minelittlepony.unicopia.item;
import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -83,6 +84,10 @@ public interface EnchantableItem extends ItemConvertible {
return stack; return stack;
} }
static Optional<SpellType<?>> getSpellKeyOrEmpty(ItemStack stack) {
return isEnchanted(stack) ? SpellType.REGISTRY.getOrEmpty(new Identifier(stack.getNbt().getString("spell"))) : Optional.empty();
}
static <T extends Spell> SpellType<T> getSpellKey(ItemStack stack) { static <T extends Spell> SpellType<T> getSpellKey(ItemStack stack) {
return SpellType.getKey(isEnchanted(stack) ? new Identifier(stack.getNbt().getString("spell")) : SpellType.EMPTY_ID); return SpellType.getKey(isEnchanted(stack) ? new Identifier(stack.getNbt().getString("spell")) : SpellType.EMPTY_ID);
} }

View file

@ -20,12 +20,11 @@ public class ZapAppleRecipe extends ShapelessRecipe {
public static class Serializer implements RecipeSerializer<ZapAppleRecipe> { public static class Serializer implements RecipeSerializer<ZapAppleRecipe> {
private static final Codec<ZapAppleRecipe> CODEC = RecordCodecBuilder.create(instance -> instance.group( private static final Codec<ZapAppleRecipe> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.optionalFieldOf("group", "").forGetter(ZapAppleRecipe::getGroup), Codec.STRING.optionalFieldOf("group", "").forGetter(ZapAppleRecipe::getGroup),
CraftingRecipeCategory.CODEC.fieldOf("category").forGetter(ZapAppleRecipe::getCategory), CraftingRecipeCategory.CODEC.optionalFieldOf("category", CraftingRecipeCategory.MISC).forGetter(ZapAppleRecipe::getCategory),
Registries.ITEM.getCodec().xmap(item -> { Registries.ITEM.getCodec().xmap(
return UItems.ZAP_APPLE.setAppearance(UItems.ZAP_APPLE.getDefaultStack(), item.getDefaultStack()); item -> UItems.ZAP_APPLE.setAppearance(UItems.ZAP_APPLE.getDefaultStack(), item.getDefaultStack()),
}, stack -> { stack -> UItems.ZAP_APPLE.getAppearance(stack)
return UItems.ZAP_APPLE.getAppearance(stack); ).fieldOf("appearance").forGetter(recipe -> recipe.getResult(null)),
}).fieldOf("appearance").forGetter(recipe -> recipe.getResult(null)),
URecipes.SHAPELESS_RECIPE_INGREDIENTS_CODEC.fieldOf("ingredients").forGetter(ZapAppleRecipe::getIngredients) URecipes.SHAPELESS_RECIPE_INGREDIENTS_CODEC.fieldOf("ingredients").forGetter(ZapAppleRecipe::getIngredients)
).apply(instance, ZapAppleRecipe::new)); ).apply(instance, ZapAppleRecipe::new));

View file

@ -0,0 +1,61 @@
package com.minelittlepony.unicopia.util;
import java.util.Optional;
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 com.mojang.serialization.MapCodec;
public interface CodecUtils {
/**
* Combines the result of two unrelated codecs into a single object.
* <p>
* The first codec serves as the "base" whilst the second codec serves as an additional field to merge into
* that object when serializing. Deserializing produces a pair with the parent value and the extra value.
* <p>
* Recommended usage:
* <code>
* Codec<MyObject> CODEC = CodecUtils.extend(SOME_CODEC, MY_FIELD_CODEC.fieldOf("my_extra_field")).xmap(
* pair -> new MyObject(pair.getLeft(), pair.getRight()),
* myObject -> Pair.of(myObject.parent(), myObject.myExtraField());
* </code>
* <p>
* Json:
* <code>
* {
* "something": "something",
* "something_else": 1,
*
* "my_extra_field": "HAH EAT THAT CODECS"
* }
* </code>
* @param <A> The base type
* @param <B> Type of the field to append
* @param baseCodec Codec for the base type
* @param fieldCodec Codec for the appended field
* @return A codec for serializing objects of the base with the extra field inserted
*/
static <A, B> Codec<Pair<Optional<A>, Optional<B>>> extend(Codec<A> baseCodec, MapCodec<B> fieldCodec) {
return Codec.of(new Encoder<Pair<Optional<A>, Optional<B>>>() {
@Override
public <T> DataResult<T> encode(Pair<Optional<A>, Optional<B>> input, DynamicOps<T> ops, T prefix) {
return baseCodec.encode(input.getFirst().get(), ops, prefix)
.flatMap(leftResult -> input.getSecond()
.map(r -> fieldCodec.encode(r, ops, ops.mapBuilder()).build(prefix))
.orElse(DataResult.success(leftResult)));
}
}, new Decoder<Pair<Optional<A>, Optional<B>>>() {
@Override
public <T> DataResult<Pair<Pair<Optional<A>, Optional<B>>, T>> decode(DynamicOps<T> ops, T input) {
return DataResult.success(new Pair<>(new Pair<>(
baseCodec.decode(ops, input).map(Pair::getFirst).result(),
fieldCodec.decode(ops, ops.getMap(input).result().get()).result()
), input));
}
});
}
}

View file

@ -16,7 +16,7 @@
"block": "unicopia:apple_pie", "block": "unicopia:apple_pie",
"condition": "minecraft:block_state_property", "condition": "minecraft:block_state_property",
"properties": { "properties": {
"stomped": true "stomped": "true"
} }
}, },
{ {