Implement botched gems: Crafting a combination that doesn't result in a working spell will now produce a "botched gem" that's only useful for collecting traits.

This commit is contained in:
Sollace 2022-08-28 17:12:33 +02:00
parent 60acaf64dd
commit 48f5943a5b
31 changed files with 248 additions and 118 deletions

View file

@ -47,10 +47,6 @@ public class Unicopia implements ModInitializer {
return new Identifier(DEFAULT_NAMESPACE, name);
}
public Unicopia() {
getConfig();
}
@Override
public void onInitialize() {
Channel.bootstrap();

View file

@ -6,7 +6,8 @@ import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.item.GemstoneItem;
@ -15,8 +16,11 @@ import net.minecraft.network.PacketByteBuf;
import net.minecraft.recipe.Ingredient;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.collection.DefaultedList;
public class IngredientWithSpell implements Predicate<ItemStack> {
private static final Predicate<Ingredient> INGREDIENT_IS_PRESENT = ((Predicate<Ingredient>)(Ingredient::isEmpty)).negate();
private Optional<Ingredient> stack = Optional.empty();
private Optional<SpellType<?>> spell = Optional.empty();
@ -43,45 +47,39 @@ public class IngredientWithSpell implements Predicate<ItemStack> {
return stacks;
}
public boolean isEmpty() {
return stack.filter(INGREDIENT_IS_PRESENT).isEmpty() && spell.isEmpty();
}
public void write(PacketByteBuf buf) {
stack.ifPresentOrElse(i -> {
buf.writeBoolean(true);
i.write(buf);
}, () -> buf.writeBoolean(false));
spell.ifPresentOrElse(i -> {
buf.writeBoolean(true);
buf.writeIdentifier(i.getId());
}, () -> buf.writeBoolean(false));
buf.writeOptional(stack, (b, i) -> i.write(b));
buf.writeOptional(spell.map(SpellType::getId), PacketByteBuf::writeIdentifier);
}
public static IngredientWithSpell fromPacket(PacketByteBuf buf) {
IngredientWithSpell ingredient = new IngredientWithSpell();
ingredient.stack = buf.readOptional(Ingredient::fromPacket);
ingredient.spell = buf.readOptional(PacketByteBuf::readIdentifier).flatMap(SpellType.REGISTRY::getOrEmpty);
return ingredient;
}
if (buf.readBoolean()) {
ingredient.stack = Optional.ofNullable(Ingredient.fromPacket(buf));
}
if (buf.readBoolean()) {
ingredient.spell = SpellType.REGISTRY.getOrEmpty(buf.readIdentifier());
public static IngredientWithSpell fromJson(JsonElement json) {
IngredientWithSpell ingredient = new IngredientWithSpell();
ingredient.stack = Optional.ofNullable(Ingredient.fromJson(json));
if (json.isJsonObject() && json.getAsJsonObject().has("spell")) {
ingredient.spell = SpellType.REGISTRY.getOrEmpty(Identifier.tryParse(JsonHelper.getString(json.getAsJsonObject(), "spell")));
}
return ingredient;
}
public static IngredientWithSpell fromJson(JsonObject json) {
IngredientWithSpell ingredient = new IngredientWithSpell();
if (json.has("item") || json.has("spell")) {
if (json.has("item")) {
ingredient.stack = Optional.ofNullable(Ingredient.fromJson(JsonHelper.getObject(json, "item")));
}
if (json.has("spell")) {
ingredient.spell = SpellType.REGISTRY.getOrEmpty(Identifier.tryParse(JsonHelper.getString(json, "spell")));
}
} else {
ingredient.stack = Optional.ofNullable(Ingredient.fromJson(json));
public static DefaultedList<IngredientWithSpell> fromJson(JsonArray json) {
DefaultedList<IngredientWithSpell> ingredients = DefaultedList.of();
for (int i = 0; i < json.size(); i++) {
IngredientWithSpell ingredient = fromJson(json.get(i));
if (ingredient.isEmpty()) continue;
ingredients.add(ingredient);
}
return ingredient;
return ingredients;
}
}

View file

@ -1,5 +1,7 @@
package com.minelittlepony.unicopia.ability.magic.spell.crafting;
import java.util.List;
import com.google.gson.JsonObject;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
@ -13,36 +15,63 @@ import net.minecraft.recipe.RecipeSerializer;
import net.minecraft.recipe.ShapedRecipe;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.world.World;
/**
* Recipe that requires an item and a certain number of traits to produce a result.
* A recipe for creating a new spell from input traits and items.
*/
public class TraitRequirementRecipe implements SpellbookRecipe {
public class SpellCraftingRecipe implements SpellbookRecipe {
private final Identifier id;
private final IngredientWithSpell requirement;
/**
* The ingredient to modify
*/
private final IngredientWithSpell material;
/**
* The required traits
*/
private final TraitIngredient requiredTraits;
/**
* Items required for crafting.
*/
private final List<IngredientWithSpell> requiredItems;
/**
* The resulting item
*/
private final ItemStack output;
private TraitRequirementRecipe(Identifier id, IngredientWithSpell requirement, TraitIngredient requiredTraits, ItemStack output) {
private SpellCraftingRecipe(Identifier id, IngredientWithSpell material, TraitIngredient requiredTraits, List<IngredientWithSpell> requiredItems, ItemStack output) {
this.id = id;
this.requirement = requirement;
this.material = material;
this.requiredTraits = requiredTraits;
this.requiredItems = requiredItems;
this.output = output;
}
@Override
public void buildCraftingTree(CraftingTreeBuilder builder) {
builder.input(requirement.getMatchingStacks());
builder.input(material.getMatchingStacks());
for (var ingredient : requiredItems) {
builder.input(ingredient.getMatchingStacks());
}
requiredTraits.min().ifPresent(min -> {
min.forEach(e -> builder.input(e.getKey(), e.getValue()));
});
builder.result(output);
}
@Override
public int getPriority() {
return 0;
}
@Override
public boolean matches(SpellbookInventory inventory, World world) {
return requirement.test(inventory.getItemToModify()) && requiredTraits.test(inventory.getTraits());
return material.test(inventory.getItemToModify()) && requiredTraits.test(inventory.getTraits());
}
@Override
@ -86,28 +115,31 @@ public class TraitRequirementRecipe implements SpellbookRecipe {
return stack;
}
public static class Serializer implements RecipeSerializer<TraitRequirementRecipe> {
public static class Serializer implements RecipeSerializer<SpellCraftingRecipe> {
@Override
public TraitRequirementRecipe read(Identifier id, JsonObject json) {
return new TraitRequirementRecipe(id,
IngredientWithSpell.fromJson(JsonHelper.getObject(json, "material")),
public SpellCraftingRecipe read(Identifier id, JsonObject json) {
return new SpellCraftingRecipe(id,
IngredientWithSpell.fromJson(json.get("material")),
TraitIngredient.fromJson(JsonHelper.getObject(json, "traits")),
IngredientWithSpell.fromJson(JsonHelper.asArray(json.get("ingredients"), "ingredients")),
outputFromJson(JsonHelper.getObject(json, "result")));
}
@Override
public TraitRequirementRecipe read(Identifier id, PacketByteBuf buf) {
return new TraitRequirementRecipe(id,
public SpellCraftingRecipe read(Identifier id, PacketByteBuf buf) {
return new SpellCraftingRecipe(id,
IngredientWithSpell.fromPacket(buf),
TraitIngredient.fromPacket(buf),
buf.readCollection(DefaultedList::ofSize, IngredientWithSpell::fromPacket),
buf.readItemStack()
);
}
@Override
public void write(PacketByteBuf buf, TraitRequirementRecipe recipe) {
recipe.requirement.write(buf);
public void write(PacketByteBuf buf, SpellCraftingRecipe recipe) {
recipe.material.write(buf);
recipe.requiredTraits.write(buf);
buf.writeCollection(recipe.requiredItems, (b, i) -> i.write(b));
buf.writeItemStack(recipe.output);
}
}

View file

@ -0,0 +1,86 @@
package com.minelittlepony.unicopia.ability.magic.spell.crafting;
import com.google.gson.JsonObject;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.container.SpellbookScreenHandler.SpellbookInventory;
import com.minelittlepony.unicopia.item.*;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.recipe.RecipeSerializer;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
/**
* Recipe for adding traits to an existing spell.
*/
public class SpellEnhancingRecipe implements SpellbookRecipe {
private final Identifier id;
private final IngredientWithSpell material;
private SpellEnhancingRecipe(Identifier id, IngredientWithSpell material) {
this.id = id;
this.material = material;
}
@Override
public void buildCraftingTree(CraftingTreeBuilder builder) {
}
@Override
public int getPriority() {
return 1;
}
@Override
public boolean matches(SpellbookInventory inventory, World world) {
ItemStack stack = inventory.getItemToModify();
return !stack.isEmpty() && stack.getItem() == UItems.GEMSTONE && GemstoneItem.isEnchanted(stack);
}
@Override
public ItemStack craft(SpellbookInventory inventory) {
return SpellTraits.of(inventory.getItemToModify())
.add(inventory.getTraits())
.applyTo(inventory.getItemToModify());
}
@Override
public boolean fits(int width, int height) {
return (width * height) > 0;
}
@Override
public ItemStack getOutput() {
return UItems.GEMSTONE.getDefaultStack();
}
@Override
public Identifier getId() {
return id;
}
@Override
public RecipeSerializer<?> getSerializer() {
return URecipes.TRAIT_COMBINING;
}
public static class Serializer implements RecipeSerializer<SpellEnhancingRecipe> {
@Override
public SpellEnhancingRecipe read(Identifier id, JsonObject json) {
return new SpellEnhancingRecipe(id, IngredientWithSpell.fromJson(json.get("material")));
}
@Override
public SpellEnhancingRecipe read(Identifier id, PacketByteBuf buf) {
return new SpellEnhancingRecipe(id, IngredientWithSpell.fromPacket(buf));
}
@Override
public void write(PacketByteBuf buf, SpellEnhancingRecipe recipe) {
recipe.material.write(buf);
}
}
}

View file

@ -27,6 +27,8 @@ public interface SpellbookRecipe extends Recipe<SpellbookInventory> {
void buildCraftingTree(CraftingTreeBuilder builder);
int getPriority();
interface CraftingTreeBuilder {
void input(ItemStack...stack);

View file

@ -12,7 +12,7 @@ import net.minecraft.util.JsonHelper;
public record TraitIngredient (
Optional<SpellTraits> min,
Optional<SpellTraits> max
) implements Predicate<SpellTraits> {
) implements Predicate<SpellTraits> {
@Override
public boolean test(SpellTraits t) {

View file

@ -56,6 +56,10 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
return amount == 0 ? this : map(v -> v + amount);
}
public SpellTraits add(SpellTraits traits) {
return union(this, traits);
}
public SpellTraits map(Function<Float, Float> function) {
return map((k, v) -> function.apply(v));
}

View file

@ -1,12 +1,13 @@
package com.minelittlepony.unicopia.container;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.URecipes;
import com.minelittlepony.unicopia.util.InventoryUtil;
import com.mojang.datafixers.util.Pair;
@ -147,10 +148,12 @@ public class SpellbookScreenHandler extends ScreenHandler {
if (!world.isClient && !gemSlot.getStack().isEmpty()) {
outputSlot.setStack(
world.getServer().getRecipeManager()
.getFirstMatch(URecipes.SPELLBOOK, input, world)
.getAllMatches(URecipes.SPELLBOOK, input, world)
.stream().sorted(Comparator.comparing(SpellbookRecipe::getPriority))
.findFirst()
.filter(recipe -> result.shouldCraftRecipe(world, (ServerPlayerEntity)this.inventory.player, recipe))
.map(recipe -> recipe.craft(input))
.orElse(ItemStack.EMPTY));
.orElse(UItems.BOTCHED_GEM.getDefaultStack()));
((ServerPlayerEntity)this.inventory.player).networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(syncId, nextRevision(), outputSlot.id, outputSlot.getStack()));
}
@ -431,7 +434,7 @@ public class SpellbookScreenHandler extends ScreenHandler {
public void setStack(ItemStack stack) {
super.setStack(stack);
if (!stack.isEmpty()) {
player.playSound(USounds.GUI_SPELL_CRAFT_SUCCESS, SoundCategory.MASTER, 1, 0.3F);
player.playSound(stack.getItem() == UItems.BOTCHED_GEM ? USounds.GUI_ABILITY_FAIL : USounds.GUI_SPELL_CRAFT_SUCCESS, SoundCategory.MASTER, 1, 0.3F);
}
}

View file

@ -57,6 +57,7 @@ public interface UItems {
Item CRYSTAL_SHARD = register("crystal_shard", new Item(new Item.Settings().group(ItemGroup.MATERIALS)));
Item GEMSTONE = register("gemstone", new GemstoneItem(new Item.Settings().group(ItemGroup.MATERIALS)));
Item BOTCHED_GEM = register("botched_gem", new Item(new Item.Settings().group(ItemGroup.MATERIALS)));
Item PEGASUS_FEATHER = register("pegasus_feather", new Item(new Item.Settings().group(ItemGroup.MATERIALS)));
Item GRYPHON_FEATHER = register("gryphon_feather", new Item(new Item.Settings().group(ItemGroup.MATERIALS)));

View file

@ -3,8 +3,7 @@ package com.minelittlepony.unicopia.item;
import java.util.List;
import com.google.gson.JsonArray;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.TraitRequirementRecipe;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.*;
import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
import net.minecraft.loot.LootTable;
@ -22,7 +21,8 @@ public interface URecipes {
RecipeSerializer<ShapelessRecipe> ZAP_APPLE_SERIALIZER = RecipeSerializer.register("unicopia:crafting_zap_apple", new ZapAppleRecipe.Serializer());
RecipeSerializer<GlowingRecipe> GLOWING_SERIALIZER = RecipeSerializer.register("unicopia:crafting_glowing", new SpecialRecipeSerializer<>(GlowingRecipe::new));
RecipeSerializer<JarInsertRecipe> JAR_INSERT_SERIALIZER = RecipeSerializer.register("unicopia:jar_insert", new SpecialRecipeSerializer<>(JarInsertRecipe::new));
RecipeSerializer<TraitRequirementRecipe> TRAIT_REQUIREMENT = RecipeSerializer.register("unicopia:spellbook/crafting", new TraitRequirementRecipe.Serializer());
RecipeSerializer<SpellCraftingRecipe> TRAIT_REQUIREMENT = RecipeSerializer.register("unicopia:spellbook/crafting", new SpellCraftingRecipe.Serializer());
RecipeSerializer<SpellEnhancingRecipe> TRAIT_COMBINING = RecipeSerializer.register("unicopia:spellbook/combining", new SpellEnhancingRecipe.Serializer());
static DefaultedList<Ingredient> getIngredients(JsonArray json) {
DefaultedList<Ingredient> defaultedList = DefaultedList.of();

View file

@ -39,6 +39,7 @@
"item.unicopia.gemstone": "Gemstone",
"item.unicopia.gemstone.enchanted": "%s Gem",
"item.unicopia.gemstone.obfuscated": "Mysterious Gem",
"item.unicopia.botched_gem": "Botched Gem",
"item.unicopia.pegasus_feather": "Pegasus Feather",
"item.unicopia.gryphon_feather": "Gryphon Feather",

View file

@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "unicopia:item/botched_gem"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

View file

@ -1,12 +1,13 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"item": "unicopia:gemstone",
"spell": "unicopia:dark_vortex"
},
"traits": {
"darkness": 30, "power": 30, "blood": 30
},
"ingredients": [],
"result": {
"item": "unicopia:alicorn_amulet"
}

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:flame"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"focus": 9, "air": 9
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:flame" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:catapult"

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:vortex"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"strength": 10, "knowledge": 8, "darkness": 9, "chaos": 8
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:vortex" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:dark_vortex"

View file

@ -1,13 +1,13 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:shield"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"knowledge": 20, "life": 10, "chaos": 4,
"generosity": 10
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:shield" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:feather_fall"

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:flame"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"focus": 9, "air": 9, "fire": 30
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:flame" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:fire_bolt"

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:scorch"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"fire": 20
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:scorch" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:flame"

View file

@ -1,12 +1,10 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:none"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"ice": 1
},
"ingredients": [],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:frost"

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:flame"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"fire": 50, "dark": 10
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:flame" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:infernal"

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:fire_bolt"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"ice": 30, "life": 30, "focus": 10
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:fire_bolt" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:light"

View file

@ -1,13 +1,13 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:siphoning"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"strength": 10, "knowledge": 8, "darkness": 19, "chaos": 8,
"blood": 10, "poison": 9
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:siphoning" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:necromancy"

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:shield"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"knowledge": 18, "life": 1, "order": 4
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:shield" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:reveal"

View file

@ -1,12 +1,10 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:none"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"fire": 10
},
"ingredients": [],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:scorch"

View file

@ -1,12 +1,10 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:none"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"strength": 10, "focus": 6, "power": 10
},
"ingredients": [],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:shield"

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:infernal"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"blood": 8, "poison": 10
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:infernal" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:siphoning"

View file

@ -1,12 +1,10 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:none"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"knowledge": 18, "life": 10, "chaos": 4
},
"ingredients": [],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:transformation"

View file

@ -1,12 +1,12 @@
{
"type": "unicopia:spellbook/crafting",
"material": {
"item": { "item": "unicopia:gemstone" },
"spell": "unicopia:shield"
},
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"strength": 10, "knowledge": 8
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:shield" }
],
"result": {
"item": "unicopia:gemstone",
"spell": "unicopia:vortex"

View file

@ -0,0 +1,4 @@
{
"type": "unicopia:spellbook/combining",
"material": { "item": "unicopia:botched_gem" }
}

View file

@ -0,0 +1,4 @@
{
"type": "unicopia:spellbook/combining",
"material": { "item": "unicopia:gemstone" }
}