From 338491d0527ced6c374a9228c4cd6ecf4f61155d Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 13 Nov 2021 00:05:42 +0200 Subject: [PATCH] Expand on spell crafting --- .../spell/crafting/IngredientWithSpell.java | 71 ++++++++++ .../magic/spell/crafting/TraitIngredient.java | 67 +++++++++ .../crafting/TraitRequirementRecipe.java | 24 ++-- .../magic/spell/trait/SpellTraits.java | 39 +++++- .../unicopia/container/SpellbookScreen.java | 1 + .../container/SpellbookScreenHandler.java | 127 ++++++++++++++---- .../unicopia/entity/SpellbookEntity.java | 5 +- .../unicopia/util/InventoryUtil.java | 6 + 8 files changed, 298 insertions(+), 42 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/IngredientWithSpell.java create mode 100644 src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/TraitIngredient.java diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/IngredientWithSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/IngredientWithSpell.java new file mode 100644 index 00000000..7ca3f8b3 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/IngredientWithSpell.java @@ -0,0 +1,71 @@ +package com.minelittlepony.unicopia.ability.magic.spell.crafting; + +import java.util.Optional; +import java.util.function.Predicate; + +import com.google.gson.JsonObject; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.item.GemstoneItem; + +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.recipe.Ingredient; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; + +public class IngredientWithSpell implements Predicate { + + private Optional stack = Optional.empty(); + private Optional> spell = Optional.empty(); + + private IngredientWithSpell() {} + + @Override + public boolean test(ItemStack t) { + boolean stackMatch = stack.map(m -> m.test(t)).orElse(true); + boolean spellMatch = spell.map(m -> GemstoneItem.getSpellKey(t).equals(m)).orElse(true); + return stackMatch && spellMatch; + } + + 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)); + } + + public static IngredientWithSpell fromPacket(PacketByteBuf buf) { + IngredientWithSpell ingredient = new IngredientWithSpell(); + + if (buf.readBoolean()) { + ingredient.stack = Optional.ofNullable(Ingredient.fromPacket(buf)); + } + if (buf.readBoolean()) { + ingredient.spell = Optional.of(SpellType.getKey(buf.readIdentifier())); + } + + 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 = Optional.ofNullable(Identifier.tryParse(JsonHelper.getString(json, "spell"))).map(SpellType::getKey); + } + } else { + ingredient.stack = Optional.ofNullable(Ingredient.fromJson(json)); + } + + return ingredient; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/TraitIngredient.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/TraitIngredient.java new file mode 100644 index 00000000..899622e2 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/TraitIngredient.java @@ -0,0 +1,67 @@ +package com.minelittlepony.unicopia.ability.magic.spell.crafting; + +import java.util.Optional; +import java.util.function.Predicate; + +import com.google.gson.JsonObject; +import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.JsonHelper; + +public class TraitIngredient implements Predicate { + + private Optional min = Optional.empty(); + private Optional max = Optional.empty(); + + private TraitIngredient() {} + + @Override + public boolean test(SpellTraits t) { + boolean minMatch = min.map(m -> t.includes(m)).orElse(true); + boolean maxMatch = max.map(m -> m.includes(t)).orElse(true); + return minMatch && maxMatch; + } + + public void write(PacketByteBuf buf) { + min.ifPresentOrElse(m -> { + buf.writeBoolean(true); + m.write(buf); + }, () -> buf.writeBoolean(false)); + max.ifPresentOrElse(m -> { + buf.writeBoolean(true); + m.write(buf); + }, () -> buf.writeBoolean(false)); + } + + public static TraitIngredient fromPacket(PacketByteBuf buf) { + TraitIngredient ingredient = new TraitIngredient(); + + if (buf.readBoolean()) { + ingredient.min = SpellTraits.fromPacket(buf); + } + if (buf.readBoolean()) { + ingredient.max = SpellTraits.fromPacket(buf); + } + + return ingredient; + } + + public static TraitIngredient fromJson(JsonObject json) { + + TraitIngredient ingredient = new TraitIngredient(); + + if (json.has("min") || json.has("max")) { + if (json.has("min")) { + ingredient.min = SpellTraits.fromJson(JsonHelper.getObject(json, "min")); + } + if (json.has("max")) { + ingredient.max = SpellTraits.fromJson(JsonHelper.getObject(json, "max")); + } + } else { + ingredient.min = SpellTraits.fromJson(json); + } + + return ingredient; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/TraitRequirementRecipe.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/TraitRequirementRecipe.java index 229eff9a..cd0e6ef5 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/TraitRequirementRecipe.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/TraitRequirementRecipe.java @@ -9,7 +9,6 @@ import com.minelittlepony.unicopia.item.URecipes; import net.minecraft.item.ItemStack; import net.minecraft.network.PacketByteBuf; -import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.RecipeSerializer; import net.minecraft.recipe.ShapedRecipe; import net.minecraft.util.Identifier; @@ -17,13 +16,12 @@ import net.minecraft.util.JsonHelper; import net.minecraft.world.World; public class TraitRequirementRecipe implements SpellbookRecipe { - private final Identifier id; - private final Ingredient requirement; - private final SpellTraits requiredTraits; + private final IngredientWithSpell requirement; + private final TraitIngredient requiredTraits; private final ItemStack output; - private TraitRequirementRecipe(Identifier id, Ingredient requirement, SpellTraits requiredTraits, ItemStack output) { + private TraitRequirementRecipe(Identifier id, IngredientWithSpell requirement, TraitIngredient requiredTraits, ItemStack output) { this.id = id; this.requirement = requirement; this.requiredTraits = requiredTraits; @@ -33,14 +31,18 @@ public class TraitRequirementRecipe implements SpellbookRecipe { @Override public boolean matches(SpellbookInventory inventory, World world) { return requirement.test(inventory.getItemToModify()) - && SpellTraits.of(inventory).includes(requiredTraits); + && requiredTraits.test(SpellTraits.union( + SpellTraits.of(inventory.getItemToModify()), + inventory.getTraits(), + SpellTraits.of(output) + )); } @Override public ItemStack craft(SpellbookInventory inventory) { return SpellTraits.union( SpellTraits.of(inventory.getItemToModify()), - SpellTraits.of(inventory), + inventory.getTraits(), SpellTraits.of(output) ).applyTo(output); } @@ -81,16 +83,16 @@ public class TraitRequirementRecipe implements SpellbookRecipe { @Override public TraitRequirementRecipe read(Identifier id, JsonObject json) { return new TraitRequirementRecipe(id, - Ingredient.fromJson(JsonHelper.getObject(json, "material")), - SpellTraits.fromJson(JsonHelper.getObject(json, "traits")).get(), + IngredientWithSpell.fromJson(JsonHelper.getObject(json, "material")), + TraitIngredient.fromJson(JsonHelper.getObject(json, "traits")), outputFromJson(JsonHelper.getObject(json, "result"))); } @Override public TraitRequirementRecipe read(Identifier id, PacketByteBuf buf) { return new TraitRequirementRecipe(id, - Ingredient.fromPacket(buf), - SpellTraits.fromPacket(buf).get(), + IngredientWithSpell.fromPacket(buf), + TraitIngredient.fromPacket(buf), buf.readItemStack() ); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java index c8854841..16730fcb 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java @@ -4,11 +4,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -26,7 +29,7 @@ import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.util.registry.Registry; -public final class SpellTraits { +public final class SpellTraits implements Iterable> { public static final SpellTraits EMPTY = new SpellTraits(Map.of()); private final Map traits; @@ -35,20 +38,47 @@ public final class SpellTraits { this.traits = traits; } + SpellTraits(SpellTraits from) { + this(new EnumMap<>(from.traits)); + } + + public SpellTraits multiply(float factor) { + return factor == 0 ? EMPTY : map(v -> v * factor); + } + + public SpellTraits map(Function function) { + if (isEmpty()) { + return this; + } + + Map newMap = new EnumMap<>(traits); + newMap.entrySet().forEach(entry -> entry.setValue(function.apply(entry.getValue()))); + return fromEntries(newMap.entrySet().stream()).orElse(EMPTY); + } + public boolean isEmpty() { return traits.isEmpty(); } public boolean includes(SpellTraits other) { - return other.entries().stream().allMatch(pair -> { + return other.stream().allMatch(pair -> { return getAmount(pair.getKey()) >= pair.getValue(); }); } + @Override + public Iterator> iterator() { + return entries().iterator(); + } + public Set> entries() { return traits.entrySet(); } + public Stream> stream() { + return entries().stream(); + } + public float getAmount(Trait trait) { return traits.getOrDefault(trait, 0F); } @@ -179,12 +209,15 @@ public final class SpellTraits { static void combine(Map to, Map from) { from.forEach((trait, value) -> { - to.compute(trait, (k, v) -> v == null ? value : (v + value)); + if (value != 0) { + to.compute(trait, (k, v) -> v == null ? value : (v + value)); + } }); } static Map collect(Stream> entries) { return entries.filter(Objects::nonNull) + .filter(e -> e.getValue() != 0) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a + b, () -> new EnumMap<>(Trait.class))); } } diff --git a/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreen.java b/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreen.java index 7b47f5f2..7f4c28b0 100644 --- a/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreen.java @@ -49,6 +49,7 @@ public class SpellbookScreen extends HandledScreen { for (Slot slot : handler.slots) { if (slot.isEnabled() && slot instanceof SpellbookSlot) { drawTexture(matrices, slot.x - 1, slot.y - 1, 74, 223, 18, 18, 512, 256); + // drawStringWithShadow(matrices, this.textRenderer, ((SpellbookSlot)slot).getRing() + "", slot.x, slot.y, 0x000000FF); } } RenderSystem.disableBlend(); diff --git a/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreenHandler.java b/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreenHandler.java index cda31b8b..6cebda98 100644 --- a/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreenHandler.java +++ b/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreenHandler.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Optional; import com.minelittlepony.unicopia.EquinePredicates; +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; @@ -18,14 +19,13 @@ import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.ScreenHandlerContext; import net.minecraft.screen.slot.CraftingResultSlot; import net.minecraft.screen.slot.Slot; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; -import net.minecraft.util.Pair; import net.minecraft.util.collection.DefaultedList; -import net.minecraft.world.World; public class SpellbookScreenHandler extends ScreenHandler { @@ -42,12 +42,19 @@ public class SpellbookScreenHandler extends ScreenHandler { private final PlayerInventory inventory; + private final ScreenHandlerContext context; + protected SpellbookScreenHandler(int syncId, PlayerInventory inv) { + this(syncId, inv, ScreenHandlerContext.EMPTY); + } + + public SpellbookScreenHandler(int syncId, PlayerInventory inv, ScreenHandlerContext context) { super(UScreenHandlers.SPELL_BOOK, syncId); inventory = inv; + this.context = context; - List> grid = new ArrayList<>(); - List> gemPos = new ArrayList<>(); + List grid = new ArrayList<>(); + List gemPos = new ArrayList<>(); createGrid(grid, gemPos); GEM_SLOT_INDEX = MAX_INGREDIENTS = grid.size(); @@ -58,10 +65,10 @@ public class SpellbookScreenHandler extends ScreenHandler { for (int i = 0; i < MAX_INGREDIENTS; i++) { var pos = grid.get(i); - addSlot(new InputSlot(input, i, pos.getLeft(), pos.getRight())); + addSlot(new InputSlot(input, i, pos)); } - addSlot(gemSlot = new OutputSlot(inventory.player, input, result, 0, gemPos.get(0).getLeft(), gemPos.get(0).getRight())); + addSlot(gemSlot = new OutputSlot(inventory.player, input, result, 0, gemPos.get(0))); for (int i = 0; i < 9; ++i) { addSlot(new Slot(inventory, i, 121 + i * 18, 195)); @@ -77,16 +84,16 @@ public class SpellbookScreenHandler extends ScreenHandler { @Override public void onContentChanged(Inventory inventory) { - World world = this.inventory.player.world; - if (!world.isClient && !gemSlot.getStack().isEmpty()) { - world.getServer().getRecipeManager().getFirstMatch(URecipes.SPELLBOOK, input, world) - .filter(recipe -> result.shouldCraftRecipe(world, (ServerPlayerEntity)this.inventory.player, recipe)) - .map(recipe -> { - this.inventory.player.playSound(SoundEvents.BLOCK_END_PORTAL_FRAME_FILL, SoundCategory.MASTER, 1, 0.3F); - return recipe.craft(input); - }) - .ifPresentOrElse(gemSlot::setCrafted, gemSlot::setUncrafted); - } + context.run((world, pos) -> { + if (!world.isClient && !gemSlot.getStack().isEmpty()) { + world.getServer().getRecipeManager().getFirstMatch(URecipes.SPELLBOOK, input, world) + .filter(recipe -> result.shouldCraftRecipe(world, (ServerPlayerEntity)this.inventory.player, recipe)) + .map(recipe -> recipe.craft(input)) + .ifPresentOrElse(gemSlot::setCrafted, gemSlot::setUncrafted); + + ((ServerPlayerEntity)this.inventory.player).networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(syncId, GEM_SLOT_INDEX, gemSlot.getStack())); + } + }); } @Override @@ -190,11 +197,18 @@ public class SpellbookScreenHandler extends ScreenHandler { public void close(PlayerEntity playerEntity) { gemSlot.setUncrafted(); super.close(playerEntity); - dropInventory(playerEntity, input); - dropInventory(playerEntity, result); + context.run((world, pos) -> { + dropInventory(playerEntity, input); + dropInventory(playerEntity, result); + }); } - private static void createGrid(List> grid, List> gemPos) { + /** + * Creates a hexagonal crafting grid. + * @param grid Output for normal slot positions. + * @param gemPos Output for the gem slot position. + */ + private static void createGrid(List grid, List gemPos) { int cols = 4; int spacing = 23; @@ -203,7 +217,25 @@ public class SpellbookScreenHandler extends ScreenHandler { for (int row = 0; row < 7; row++) { for (int i = 0; i < cols; i++) { - (row == 3 && i == 3 ? gemPos : grid).add(new Pair<>(left + (i * spacing), top)); + + int ring = 3; + if (row == 0 || row == 6) { + ring = 1; + } else if ((row == 1 || row == 5) && i > 0 && i < cols - 1) { + ring = 2; + } else { + if (i == 0 || i == cols - 1) { + ring = 1; + } else if (i == 1 || i == cols - 2) { + ring = 2; + } + } + + (row == 3 && i == 3 ? gemPos : grid).add(new int[] { + left + (i * spacing), + top, + row == 3 && i == 3 ? 4 : ring + }); } top += spacing * 0.9; left -= (spacing / 2) * (row > 2 ? -1 : 1); @@ -211,7 +243,9 @@ public class SpellbookScreenHandler extends ScreenHandler { } } - public interface SpellbookSlot {} + public interface SpellbookSlot { + int getRing(); + } public class SpellbookInventory extends CraftingInventory { @@ -220,38 +254,74 @@ public class SpellbookScreenHandler extends ScreenHandler { } public ItemStack getItemToModify() { - return gemSlot.getStack(); + return gemSlot.uncrafted.orElse(gemSlot.getStack()); + } + + public int getRing(int slot) { + Slot s = slots.get(slot); + return s instanceof SpellbookSlot ? ((SpellbookSlot)s).getRing() : 0; + } + + public SpellTraits getTraits() { + return SpellTraits.union(InventoryUtil.slots(this) + .map(slot -> SpellTraits.of(getStack(slot)).multiply(getRingFactor(getRing(slot)))) + .toArray(SpellTraits[]::new) + ); + } + + public static float getRingFactor(int ring) { + switch (ring) { + case 1: return 1; + case 2: return 0.6F; + case 3: return 0.3F; + default: return 0; + } } } public class InputSlot extends Slot implements SpellbookSlot { - public InputSlot(Inventory inventory, int index, int xPosition, int yPosition) { - super(inventory, index, xPosition, yPosition); + private final int ring; + + public InputSlot(Inventory inventory, int index, int[] params) { + super(inventory, index, params[0], params[1]); + ring = params[2]; } @Override public int getMaxItemCount() { return 1; } + + @Override + public int getRing() { + return ring; + } } public static class OutputSlot extends CraftingResultSlot implements SpellbookSlot { private Optional uncrafted = Optional.empty(); + private final PlayerEntity player; private final SpellbookInventory input; - public OutputSlot(PlayerEntity player, SpellbookInventory input, Inventory inventory, int index, int x, int y) { - super(player, input, inventory, index, x, y); + private final int ring; + + public OutputSlot(PlayerEntity player, SpellbookInventory input, Inventory inventory, int index, int[] params) { + super(player, input, inventory, index, params[0], params[1]); + this.player = player; this.input = input; + this.ring = params[2]; } public void setCrafted(ItemStack crafted) { uncrafted = uncrafted.or(() -> Optional.of(getStack())); setStack(crafted); + player.playSound(SoundEvents.BLOCK_END_PORTAL_FRAME_FILL, SoundCategory.MASTER, 1, 0.3F); } public void setUncrafted() { + player.playSound(SoundEvents.BLOCK_END_PORTAL_FRAME_FILL, SoundCategory.MASTER, 0.2F, 0.2F); uncrafted = uncrafted.filter(stack -> { setStack(stack); return false; @@ -268,6 +338,11 @@ public class SpellbookScreenHandler extends ScreenHandler { return 1; } + @Override + public int getRing() { + return ring; + } + @Override public void onTakeItem(PlayerEntity player, ItemStack stack) { if (uncrafted.isPresent()) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/SpellbookEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/SpellbookEntity.java index b92664a8..0dc86c9b 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/SpellbookEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/SpellbookEntity.java @@ -3,7 +3,7 @@ package com.minelittlepony.unicopia.entity; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.EquinePredicates; -import com.minelittlepony.unicopia.container.UScreenHandlers; +import com.minelittlepony.unicopia.container.SpellbookScreenHandler; import com.minelittlepony.unicopia.item.UItems; import net.fabricmc.fabric.api.util.TriState; @@ -18,6 +18,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.particle.ParticleTypes; +import net.minecraft.screen.ScreenHandlerContext; import net.minecraft.screen.SimpleNamedScreenHandlerFactory; import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.SoundCategory; @@ -197,7 +198,7 @@ public class SpellbookEntity extends MobEntity { if (isOpen() && EquinePredicates.PLAYER_UNICORN.test(player)) { setBored(false); - player.openHandledScreen(new SimpleNamedScreenHandlerFactory((syncId, inv, ply) -> UScreenHandlers.SPELL_BOOK.create(syncId, inv), getDisplayName())); + player.openHandledScreen(new SimpleNamedScreenHandlerFactory((syncId, inv, ply) -> new SpellbookScreenHandler(syncId, inv, ScreenHandlerContext.create(world, getBlockPos())), getDisplayName())); player.playSound(SoundEvents.ITEM_BOOK_PAGE_TURN, 2, 1); return ActionResult.SUCCESS; } diff --git a/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java b/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java index 7969f9f5..44deed66 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.util; +import java.util.stream.Stream; + import com.google.common.collect.AbstractIterator; import net.minecraft.inventory.Inventory; @@ -19,4 +21,8 @@ public interface InventoryUtil { } }; } + + static Stream slots(Inventory inventory) { + return Stream.iterate(0, i -> i < inventory.size(), i -> i + 1); + } }