diff --git a/src/client/java/dev/enjarai/trickster/screen/ScrollAndQuillScreen.java b/src/client/java/dev/enjarai/trickster/screen/ScrollAndQuillScreen.java index 4f5a2bac..24e978a0 100644 --- a/src/client/java/dev/enjarai/trickster/screen/ScrollAndQuillScreen.java +++ b/src/client/java/dev/enjarai/trickster/screen/ScrollAndQuillScreen.java @@ -21,7 +21,8 @@ protected void init() { super.init(); partWidget = new SpellPartWidget( - handler.spell.get(), width / 2d, height / 2d, 64, handler::updateSpell, + handler.spell.get(), width / 2d, height / 2d, 64, + handler::updateSpell, handler::updateOtherHandSpell, handler.otherHandSpell::get, handler::executeOffhand ); handler.replacerCallback = frag -> partWidget.replaceCallback(frag); diff --git a/src/client/java/dev/enjarai/trickster/screen/SpellPartWidget.java b/src/client/java/dev/enjarai/trickster/screen/SpellPartWidget.java index 28104392..d78c416b 100644 --- a/src/client/java/dev/enjarai/trickster/screen/SpellPartWidget.java +++ b/src/client/java/dev/enjarai/trickster/screen/SpellPartWidget.java @@ -4,6 +4,8 @@ import dev.enjarai.trickster.Trickster; import dev.enjarai.trickster.render.SpellCircleRenderer; import dev.enjarai.trickster.spell.*; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.*; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; @@ -12,6 +14,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -30,6 +33,8 @@ public class SpellPartWidget extends AbstractParentElement implements Drawable, public static final Pattern COPY_OFFHAND_LITERAL = Pattern.of(4, 0, 1, 4, 2, 1); public static final Pattern COPY_OFFHAND_LITERAL_INNER = Pattern.of(1, 2, 4, 1, 0, 4, 7); public static final Pattern COPY_OFFHAND_EXECUTE = Pattern.of(4, 3, 0, 4, 5, 2, 4, 1); + public static final Pattern WRITE_OFFHAND_ADDRESS = Pattern.of(1, 0, 4, 8, 7, 6, 4, 2, 1, 4); + private SpellPart spellPart; // private List partWidgets; @@ -42,6 +47,7 @@ public class SpellPartWidget extends AbstractParentElement implements Drawable, private boolean isMutable = true; private Consumer updateListener; + private Consumer otherHandSpellUpdateListener; private Supplier otherHandSpellSupplier; @Nullable private SpellPart toBeReplaced; @@ -53,12 +59,13 @@ public class SpellPartWidget extends AbstractParentElement implements Drawable, private final SpellCircleRenderer renderer; - public SpellPartWidget(SpellPart spellPart, double x, double y, double size, Consumer updateListener, Supplier otherHandSpellSupplier, Runnable initializeReplace) { + public SpellPartWidget(SpellPart spellPart, double x, double y, double size, Consumer spellUpdateListener, Consumer otherHandSpellUpdateListener, Supplier otherHandSpellSupplier, Runnable initializeReplace) { this.spellPart = spellPart; this.x = x; this.y = y; this.size = size; - this.updateListener = updateListener; + this.updateListener = spellUpdateListener; + this.otherHandSpellUpdateListener = otherHandSpellUpdateListener; this.otherHandSpellSupplier = otherHandSpellSupplier; this.initializeReplace = initializeReplace; this.renderer = new SpellCircleRenderer(() -> this.drawingPart, () -> this.drawingPattern); @@ -264,7 +271,7 @@ protected void stopDrawing() { if (drawingPart == spellPart) { spellPart = newPart; } else { - setSubPartInTree(drawingPart, Optional.of(newPart), spellPart, false); + drawingPart.setSubPartInTree(Optional.of(newPart), spellPart, false); } } else if (compiled.equals(CREATE_PARENT_GLYPH_GLYPH)) { var newPart = new SpellPart(); @@ -272,14 +279,14 @@ protected void stopDrawing() { if (drawingPart == spellPart) { spellPart = newPart; } else { - setSubPartInTree(drawingPart, Optional.of(newPart), spellPart, false); + drawingPart.setSubPartInTree(Optional.of(newPart), spellPart, false); } } else if (compiled.equals(EXPAND_TO_OUTER_CIRCLE_GLYPH)) { if (drawingPart != spellPart) { if (spellPart.glyph == drawingPart) { spellPart = drawingPart; } else { - setSubPartInTree(drawingPart, Optional.of(drawingPart), spellPart, true); + drawingPart.setSubPartInTree(Optional.of(drawingPart), spellPart, true); } } } else if (compiled.equals(DELETE_CIRCLE_GLYPH)) { @@ -287,25 +294,31 @@ protected void stopDrawing() { if (drawingPart == spellPart) { spellPart = firstSubpart.orElse(new SpellPart()); } else { - setSubPartInTree(drawingPart, firstSubpart, spellPart, false); + drawingPart.setSubPartInTree(firstSubpart, spellPart, false); } } else if (compiled.equals(DELETE_BRANCH_GLYPH)) { if (drawingPart == spellPart) { spellPart = new SpellPart(); } else { - setSubPartInTree(drawingPart, Optional.empty(), spellPart, false); + drawingPart.setSubPartInTree(Optional.empty(), spellPart, false); } } else if (compiled.equals(COPY_OFFHAND_LITERAL)) { if (drawingPart == spellPart) { spellPart = otherHandSpellSupplier.get().deepClone(); } else { - setSubPartInTree(drawingPart, Optional.of(otherHandSpellSupplier.get().deepClone()), spellPart, false); + drawingPart.setSubPartInTree(Optional.of(otherHandSpellSupplier.get().deepClone()), spellPart, false); } } else if (compiled.equals(COPY_OFFHAND_LITERAL_INNER)) { drawingPart.glyph = otherHandSpellSupplier.get().deepClone(); } else if (compiled.equals(COPY_OFFHAND_EXECUTE)) { toBeReplaced = drawingPart; initializeReplace.run(); + } else if (compiled.equals(WRITE_OFFHAND_ADDRESS)) { + var address = getAddress(spellPart, drawingPart); + if (address.isPresent()) { + var addressFragment = new ListFragment(address.get().stream().map(num -> (Fragment) new NumberFragment(num)).toList()); + otherHandSpellUpdateListener.accept(new SpellPart(addressFragment, List.of())); + } } else { drawingPart.glyph = new PatternGlyph(compiled, drawingPattern); tryReset = false; @@ -338,39 +351,39 @@ public boolean isDrawing() { return drawingPart != null; } - protected boolean setSubPartInTree(SpellPart target, Optional replacement, SpellPart current, boolean targetIsInner) { - if (current.glyph instanceof SpellPart part) { - if (targetIsInner ? part.glyph == target : part == target) { - if (replacement.isPresent()) { - current.glyph = replacement.get(); - } else { - current.glyph = new PatternGlyph(); - } - return true; - } - - if (setSubPartInTree(target, replacement, part, targetIsInner)) { - return true; - } + protected Optional> getAddress(SpellPart node, SpellPart target) { + var address = new LinkedList(); + var found = getAddress(node, target, address, new LinkedList<>()); + if (found) { + return Optional.of(address); + } else { + return Optional.empty(); } + } - int i = 0; - for (var part : current.subParts) { - if (part.isPresent()) { - if (targetIsInner ? part.get().glyph == target : part.get() == target) { - if (replacement.isPresent()) { - current.subParts.set(i, replacement); - } else { - current.subParts.remove(i); - } - return true; - } + protected boolean getAddress(SpellPart node, SpellPart target, List address, List glyphSpells) { + if (node == target) { + return true; + } + if (node.glyph instanceof SpellPart glyph) { + glyphSpells.add(glyph); + } - if (setSubPartInTree(target, replacement, part.get(), targetIsInner)) { - return true; + var subParts = node.subParts; + if (subParts.stream().map(Optional::isPresent).findAny().isEmpty() && !address.isEmpty()) { + address.removeLast(); + } else { + for (int i = 0; i < subParts.size(); i++) { + if (subParts.get(i).isPresent()) { + address.add(i); + var found = getAddress(subParts.get(i).get(), target, address, glyphSpells); + if (found) return true; } } - i++; + for (var glyph : glyphSpells) { + var found = getAddress(glyph, target, address, new LinkedList<>()); + if (found) return true; + } } return false; diff --git a/src/main/java/dev/enjarai/trickster/screen/ScrollAndQuillScreenHandler.java b/src/main/java/dev/enjarai/trickster/screen/ScrollAndQuillScreenHandler.java index 8e1db10c..2872de1e 100644 --- a/src/main/java/dev/enjarai/trickster/screen/ScrollAndQuillScreenHandler.java +++ b/src/main/java/dev/enjarai/trickster/screen/ScrollAndQuillScreenHandler.java @@ -23,6 +23,8 @@ public class ScrollAndQuillScreenHandler extends ScreenHandler { private final ItemStack scrollStack; + private final ItemStack otherHandStack; + public final SyncedProperty spell = createProperty(SpellPart.class, SpellPart.ENDEC, new SpellPart()); public final SyncedProperty otherHandSpell = createProperty(SpellPart.class, SpellPart.ENDEC, new SpellPart()); @@ -41,6 +43,8 @@ public ScrollAndQuillScreenHandler(int syncId, PlayerInventory playerInventory, super(ModScreenHandlers.SCROLL_AND_QUILL, syncId); this.scrollStack = scrollStack; + this.otherHandStack = otherHandStack; + this.slot = slot; this.greedyEvaluation = greedyEvaluation; @@ -61,6 +65,8 @@ public ScrollAndQuillScreenHandler(int syncId, PlayerInventory playerInventory, this.isMutable.set(isMutable); addServerboundMessage(SpellMessage.class, SpellMessage.ENDEC, msg -> updateSpell(msg.spell())); + addServerboundMessage(OtherHandSpellMessage.class, OtherHandSpellMessage.ENDEC, msg -> updateOtherHandSpell(msg.spell())); + addServerboundMessage(ExecuteOffhand.class, msg -> executeOffhand()); addClientboundMessage(Replace.class, Replace.ENDEC, msg -> { if (replacerCallback != null) { @@ -102,6 +108,24 @@ public void updateSpell(SpellPart spell) { } } + public void updateOtherHandSpell(SpellPart spell) { + if (isMutable.get()) { + if (otherHandStack != null) { + if (!otherHandStack.isEmpty()) { + var server = player().getServer(); + if (server != null) { + server.execute(() -> { + otherHandStack.set(ModComponents.SPELL, new SpellComponent(spell)); + otherHandSpell.set(spell); + }); + } + } + } else { + sendMessage(new OtherHandSpellMessage(spell)); + } + } + } + public void executeOffhand() { var server = player().getServer(); if (server != null) { @@ -134,6 +158,10 @@ public record SpellMessage(SpellPart spell) { public static final Endec ENDEC = SpellPart.ENDEC.xmap(SpellMessage::new, SpellMessage::spell); } + public record OtherHandSpellMessage(SpellPart spell) { + public static final Endec ENDEC = SpellPart.ENDEC.xmap(OtherHandSpellMessage::new, OtherHandSpellMessage::spell); + } + public record ExecuteOffhand() { } diff --git a/src/main/java/dev/enjarai/trickster/spell/SpellPart.java b/src/main/java/dev/enjarai/trickster/spell/SpellPart.java index d75bb753..bc8a135b 100644 --- a/src/main/java/dev/enjarai/trickster/spell/SpellPart.java +++ b/src/main/java/dev/enjarai/trickster/spell/SpellPart.java @@ -119,6 +119,44 @@ public void buildClosure(Map replacements) { } } + public boolean setSubPartInTree(Optional replacement, SpellPart current, boolean targetIsInner) { + if (current.glyph instanceof SpellPart part) { + if (targetIsInner ? part.glyph == this : part == this) { + if (replacement.isPresent()) { + current.glyph = replacement.get(); + } else { + current.glyph = new PatternGlyph(); + } + return true; + } + + if (setSubPartInTree(replacement, part, targetIsInner)) { + return true; + } + } + + int i = 0; + for (var part : current.subParts) { + if (part.isPresent()) { + if (targetIsInner ? part.get().glyph == this : part.get() == this) { + if (replacement.isPresent()) { + current.subParts.set(i, replacement); + } else { + current.subParts.remove(i); + } + return true; + } + + if (setSubPartInTree(replacement, part.get(), targetIsInner)) { + return true; + } + } + i++; + } + + return false; + } + public Fragment getGlyph() { return glyph; } diff --git a/src/main/java/dev/enjarai/trickster/spell/fragment/ListFragment.java b/src/main/java/dev/enjarai/trickster/spell/fragment/ListFragment.java index 30a17ded..091ec523 100644 --- a/src/main/java/dev/enjarai/trickster/spell/fragment/ListFragment.java +++ b/src/main/java/dev/enjarai/trickster/spell/fragment/ListFragment.java @@ -2,12 +2,16 @@ import com.google.common.collect.ImmutableList; import com.mojang.serialization.MapCodec; +import dev.enjarai.trickster.Trickster; import dev.enjarai.trickster.spell.Fragment; +import dev.enjarai.trickster.spell.tricks.Trick; import dev.enjarai.trickster.spell.tricks.Tricks; import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; import dev.enjarai.trickster.spell.tricks.blunder.IncompatibleTypesBlunder; +import dev.enjarai.trickster.spell.tricks.blunder.IncorrectFragmentBlunder; import net.minecraft.text.Text; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -43,4 +47,22 @@ public BooleanFragment asBoolean() { public ListFragment addRange(ListFragment other) throws BlunderException { return new ListFragment(ImmutableList.builder().addAll(fragments).addAll(other.fragments).build()); } + + public List sanitizeAddress(Trick source) { + var sanitizedAddress = new ArrayList(); + + for (Fragment fragment : this.fragments()) { + if (fragment instanceof NumberFragment index && index.isInteger()) { + sanitizedAddress.add((int) index.number()); + } else { + throw new IncorrectFragmentBlunder( + source, + 1, + Text.translatable(Trickster.MOD_ID + ".fragment." + Trickster.MOD_ID + "." + "integer_list"), + this); + } + } + + return sanitizedAddress; + } } diff --git a/src/main/java/dev/enjarai/trickster/spell/fragment/NumberFragment.java b/src/main/java/dev/enjarai/trickster/spell/fragment/NumberFragment.java index 54fa4938..47a71076 100644 --- a/src/main/java/dev/enjarai/trickster/spell/fragment/NumberFragment.java +++ b/src/main/java/dev/enjarai/trickster/spell/fragment/NumberFragment.java @@ -87,4 +87,8 @@ public boolean equals(Object obj) { } return false; } + + public boolean isInteger() { + return number - Math.floor(number) == 0; + } } diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/Tricks.java b/src/main/java/dev/enjarai/trickster/spell/tricks/Tricks.java index d58af55b..1b8f0910 100644 --- a/src/main/java/dev/enjarai/trickster/spell/tricks/Tricks.java +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/Tricks.java @@ -16,6 +16,7 @@ import dev.enjarai.trickster.spell.tricks.func.SupplierTrick; import dev.enjarai.trickster.spell.tricks.list.*; import dev.enjarai.trickster.spell.tricks.math.*; +import dev.enjarai.trickster.spell.tricks.tree.*; import dev.enjarai.trickster.spell.tricks.vector.*; import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; @@ -116,6 +117,17 @@ public RegistryEntry.Reference add(RegistryKey key, Trick value, R public static final ListRemoveElementTrick LIST_REMOVE_ELEMENT = register("list_remove_element", new ListRemoveElementTrick()); public static final ListRemoveTrick LIST_REMOVE = register("list_remove", new ListRemoveTrick()); + // Tree + public static final LocateGlyphTrick LOCATE_GLYPH = register("locate_glyph", new LocateGlyphTrick()); + public static final LocateGlyphsTrick LOCATE_GLYPHS = register("locate_glyphs", new LocateGlyphsTrick()); + public static final RetrieveGlyphTrick RETRIEVE_GLYPH = register("retrieve_glyph", new RetrieveGlyphTrick()); + public static final SetGlyphTrick SET_GLYPH = register("set_glyph", new SetGlyphTrick()); + public static final RetrieveSubtreeTrick RETRIEVE_SUBTREE= register("retrieve_subtree", new RetrieveSubtreeTrick()); + public static final SetSubtreeTrick SET_SUBTREE = register("set_subtree", new SetSubtreeTrick()); + public static final AddSubtreeTrick ADD_LEAF = register("add_subtree", new AddSubtreeTrick()); + public static final RemoveSubtreeTrick REMOVE_SUBTREE = register("remove_subtree", new RemoveSubtreeTrick()); + + // Events public static final CreateSpellCircleTrick CREATE_SPELL_CIRCLE = register("create_spell_circle", new CreateSpellCircleTrick()); public static final DeleteSpellCircleTrick DELETE_SPELL_CIRCLE = register("delete_spell_circle", new DeleteSpellCircleTrick()); diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/blunder/AddressNotInTreeBlunder.java b/src/main/java/dev/enjarai/trickster/spell/tricks/blunder/AddressNotInTreeBlunder.java new file mode 100644 index 00000000..5391e9fb --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/blunder/AddressNotInTreeBlunder.java @@ -0,0 +1,20 @@ +package dev.enjarai.trickster.spell.tricks.blunder; + +import dev.enjarai.trickster.spell.tricks.Trick; +import net.minecraft.text.MutableText; + +import java.util.List; + +public class AddressNotInTreeBlunder extends TrickBlunderException { + public final List address; + + public AddressNotInTreeBlunder(Trick source, List index) { + super(source); + this.address = index; + } + + @Override + public MutableText createMessage() { + return super.createMessage().append("Spell does not contain a circle at this address: ").append(formatAddress(address)); + } +} diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/blunder/BlunderException.java b/src/main/java/dev/enjarai/trickster/spell/tricks/blunder/BlunderException.java index 5250af2f..caac0976 100644 --- a/src/main/java/dev/enjarai/trickster/spell/tricks/blunder/BlunderException.java +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/blunder/BlunderException.java @@ -4,10 +4,25 @@ import net.minecraft.text.MutableText; import net.minecraft.text.Text; +import java.util.Arrays; +import java.util.List; + public abstract class BlunderException extends RuntimeException { public abstract MutableText createMessage(); protected Text formatInt(int number) { return Text.literal("" + number).withColor(FragmentType.NUMBER.color().getAsInt()); } + + protected Text formatAddress(List address) { + var out = Text.literal("["); + var first = true; + for (int integer : address) { + if (!first) out.append(Text.literal(", ")); + out.append(formatInt(integer)); + first = false; + } + out.append("]"); + return out; + } } diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/tree/AddSubtreeTrick.java b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/AddSubtreeTrick.java new file mode 100644 index 00000000..c81378a3 --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/AddSubtreeTrick.java @@ -0,0 +1,44 @@ +package dev.enjarai.trickster.spell.tricks.tree; + +import dev.enjarai.trickster.Trickster; +import dev.enjarai.trickster.spell.*; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; +import dev.enjarai.trickster.spell.tricks.Trick; +import dev.enjarai.trickster.spell.tricks.blunder.AddressNotInTreeBlunder; +import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; +import dev.enjarai.trickster.spell.tricks.blunder.IncorrectFragmentBlunder; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class AddSubtreeTrick extends Trick { + public AddSubtreeTrick() { + super(Pattern.of(2, 1, 0, 4, 8, 7, 6, 4, 2, 5, 8)); + } + + @Override + public Fragment activate(SpellContext ctx, List fragments) throws BlunderException { + var spell = expectInput(fragments, SpellPart.class, 0); + var addressFragment = expectInput(fragments, ListFragment.class, 1); + var subtree = expectInput(fragments, SpellPart.class, 2); + + var address = addressFragment.sanitizeAddress(this); + var newSpell = spell.deepClone(); + + var node = newSpell; + for (int index : address) { + var subParts = node.subParts; + if (subParts.size() > index && subParts.get(index).isPresent()) { + node = subParts.get(index).get(); + } else { + throw new AddressNotInTreeBlunder(this, address); + } + } + node.subParts.add(Optional.of(subtree)); + + return newSpell; + } +} diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/tree/LocateGlyphTrick.java b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/LocateGlyphTrick.java new file mode 100644 index 00000000..146e5d2a --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/LocateGlyphTrick.java @@ -0,0 +1,60 @@ +package dev.enjarai.trickster.spell.tricks.tree; + +import dev.enjarai.trickster.spell.Fragment; +import dev.enjarai.trickster.spell.Pattern; +import dev.enjarai.trickster.spell.SpellContext; +import dev.enjarai.trickster.spell.SpellPart; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; +import dev.enjarai.trickster.spell.fragment.VoidFragment; +import dev.enjarai.trickster.spell.tricks.Trick; +import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; +import oshi.util.tuples.Pair; + +import java.util.*; + +public class LocateGlyphTrick extends Trick { + public LocateGlyphTrick() { + super(Pattern.of(6, 7, 8, 2, 1, 0, 4, 8, 5)); + } + + @Override + public Fragment activate(SpellContext ctx, List fragments) throws BlunderException { + var spell = expectInput(fragments, SpellPart.class, 0); + var glyph = expectInput(fragments, Fragment.class, 1); + + var address = search(spell, glyph); + + + if (address == null) + return VoidFragment.INSTANCE; + else + return new ListFragment(address.stream().map(num -> (Fragment) new NumberFragment(num)).toList()); + + } + + //todo: improve memory efficiency + private List search(SpellPart spell, Fragment target) { + Queue> queue = new LinkedList<>(); + + queue.add(new Pair<>(new Integer[]{}, spell)); + while (!queue.isEmpty()) { + + var temp = queue.poll(); + + if(temp.getB().glyph.equals(target)) + return Arrays.asList(temp.getA()); + + var subParts = temp.getB().subParts; + for (int i = 0; i < subParts.size(); i++) { + if(subParts.get(i).isPresent()) { + var newAddress = Arrays.copyOfRange(temp.getA(), 0, temp.getA().length + 1); + newAddress[temp.getA().length] = i; + + queue.add(new Pair<>(newAddress, subParts.get(i).get())); + } + } + } + return null; + } +} diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/tree/LocateGlyphsTrick.java b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/LocateGlyphsTrick.java new file mode 100644 index 00000000..891bb3ec --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/LocateGlyphsTrick.java @@ -0,0 +1,58 @@ +package dev.enjarai.trickster.spell.tricks.tree; + +import dev.enjarai.trickster.spell.Fragment; +import dev.enjarai.trickster.spell.Pattern; +import dev.enjarai.trickster.spell.SpellContext; +import dev.enjarai.trickster.spell.SpellPart; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; +import dev.enjarai.trickster.spell.fragment.VoidFragment; +import dev.enjarai.trickster.spell.tricks.Trick; +import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; +import oshi.util.tuples.Pair; + +import java.util.*; + +public class LocateGlyphsTrick extends Trick { + public LocateGlyphsTrick() { + super(Pattern.of(6, 7, 8, 2, 1, 0, 4, 8, 5, 2)); + } + + @Override + public Fragment activate(SpellContext ctx, List fragments) throws BlunderException { + var spell = expectInput(fragments, SpellPart.class, 0); + var glyph = expectInput(fragments, Fragment.class, 1); + + var addresses = new ArrayList>(); + search(spell, glyph, addresses); + + + return new ListFragment(addresses.stream().map( + address -> (Fragment) new ListFragment(address.stream().map(num -> (Fragment) new NumberFragment(num) + ).toList())).toList()); + + } + + private void search(SpellPart spell, Fragment target, List> addresses) { + Queue> queue = new LinkedList<>(); + + queue.add(new Pair<>(new Integer[]{}, spell)); + while (!queue.isEmpty()) { + + var temp = queue.poll(); + + if (temp.getB().glyph.equals(target)) + addresses.add(Arrays.asList(temp.getA())); + + var subParts = temp.getB().subParts; + for (int i = 0; i < subParts.size(); i++) { + if (subParts.get(i).isPresent()) { + var newAddress = Arrays.copyOfRange(temp.getA(), 0, temp.getA().length + 1); + newAddress[temp.getA().length] = i; + + queue.add(new Pair<>(newAddress, subParts.get(i).get())); + } + } + } + } +} diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RemoveSubtreeTrick.java b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RemoveSubtreeTrick.java new file mode 100644 index 00000000..48cce4a4 --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RemoveSubtreeTrick.java @@ -0,0 +1,50 @@ +package dev.enjarai.trickster.spell.tricks.tree; + +import dev.enjarai.trickster.Trickster; +import dev.enjarai.trickster.spell.*; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; +import dev.enjarai.trickster.spell.fragment.VoidFragment; +import dev.enjarai.trickster.spell.tricks.Trick; +import dev.enjarai.trickster.spell.tricks.blunder.AddressNotInTreeBlunder; +import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; +import dev.enjarai.trickster.spell.tricks.blunder.IncorrectFragmentBlunder; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class RemoveSubtreeTrick extends Trick { + public RemoveSubtreeTrick() { + super(Pattern.of(6, 3, 0, 4, 8, 5, 2, 4, 6, 7, 8)); + } + + @Override + public Fragment activate(SpellContext ctx, List fragments) throws BlunderException { + var spell = expectInput(fragments, SpellPart.class, 0); + var addressFragment = expectInput(fragments, ListFragment.class, 1); + + var address = addressFragment.sanitizeAddress(this); + var newSpell = spell.deepClone(); + + SpellPart prev = null; + var node = newSpell; + for (int index : address) { + var subParts = node.subParts; + if (subParts.size() > index && subParts.get(index).isPresent()) { + var newNode = subParts.get(index).get(); + prev = node; + node = newNode; + } else { + throw new AddressNotInTreeBlunder(this, address); + } + } + if (prev == null) { + return VoidFragment.INSTANCE; + } else { + prev.subParts.remove(address.getLast().intValue()); + return newSpell; + } + } +} diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RetrieveGlyphTrick.java b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RetrieveGlyphTrick.java new file mode 100644 index 00000000..b5f202d3 --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RetrieveGlyphTrick.java @@ -0,0 +1,46 @@ +package dev.enjarai.trickster.spell.tricks.tree; + +import dev.enjarai.trickster.Trickster; +import dev.enjarai.trickster.spell.Fragment; +import dev.enjarai.trickster.spell.Pattern; +import dev.enjarai.trickster.spell.SpellContext; +import dev.enjarai.trickster.spell.SpellPart; +import dev.enjarai.trickster.spell.fragment.FragmentType; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; +import dev.enjarai.trickster.spell.fragment.VoidFragment; +import dev.enjarai.trickster.spell.tricks.Trick; +import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; +import dev.enjarai.trickster.spell.tricks.blunder.IncorrectFragmentBlunder; +import net.minecraft.text.Text; +import oshi.util.tuples.Pair; + +import java.util.*; + +public class RetrieveGlyphTrick extends Trick { + public RetrieveGlyphTrick() { + super(Pattern.of(2, 1, 0, 4, 6, 7, 8)); + } + + @Override + public Fragment activate(SpellContext ctx, List fragments) throws BlunderException { + var spell = expectInput(fragments, SpellPart.class, 0); + var addressFragment = expectInput(fragments, ListFragment.class, 1); + + var address = addressFragment.sanitizeAddress(this); + + var node = spell; + for (int index : address) { + var subParts = node.subParts; + if (subParts.size() > index && subParts.get(index).isPresent()) { + node = subParts.get(index).get(); + } else { + // return void if the spell does not contain a glyph at the address + return VoidFragment.INSTANCE; + } + } + + return node.glyph; + } + +} diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RetrieveSubtreeTrick.java b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RetrieveSubtreeTrick.java new file mode 100644 index 00000000..9525244a --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/RetrieveSubtreeTrick.java @@ -0,0 +1,44 @@ +package dev.enjarai.trickster.spell.tricks.tree; + +import dev.enjarai.trickster.Trickster; +import dev.enjarai.trickster.spell.Fragment; +import dev.enjarai.trickster.spell.Pattern; +import dev.enjarai.trickster.spell.SpellContext; +import dev.enjarai.trickster.spell.SpellPart; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; +import dev.enjarai.trickster.spell.fragment.VoidFragment; +import dev.enjarai.trickster.spell.tricks.Trick; +import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; +import dev.enjarai.trickster.spell.tricks.blunder.IncorrectFragmentBlunder; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; + +public class RetrieveSubtreeTrick extends Trick { + public RetrieveSubtreeTrick() { + super(Pattern.of(0, 3, 6, 4, 2, 5, 8, 4, 0, 1, 2)); + } + + @Override + public Fragment activate(SpellContext ctx, List fragments) throws BlunderException { + var spell = expectInput(fragments, SpellPart.class, 0); + var addressFragment = expectInput(fragments, ListFragment.class, 1); + + var address = addressFragment.sanitizeAddress(this); + + var node = spell; + for (int index : address) { + var subParts = node.subParts; + if (subParts.size() > index && subParts.get(index).isPresent()) { + node = subParts.get(index).get(); + } else { + // return void if the spell does not contain a glyph at the address + return VoidFragment.INSTANCE; + } + } + + return node; + } +} diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/tree/SetGlyphTrick.java b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/SetGlyphTrick.java new file mode 100644 index 00000000..4052c6f2 --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/SetGlyphTrick.java @@ -0,0 +1,47 @@ +package dev.enjarai.trickster.spell.tricks.tree; + +import dev.enjarai.trickster.Trickster; +import dev.enjarai.trickster.spell.Fragment; +import dev.enjarai.trickster.spell.Pattern; +import dev.enjarai.trickster.spell.SpellContext; +import dev.enjarai.trickster.spell.SpellPart; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; +import dev.enjarai.trickster.spell.fragment.VoidFragment; +import dev.enjarai.trickster.spell.tricks.Trick; +import dev.enjarai.trickster.spell.tricks.blunder.AddressNotInTreeBlunder; +import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; +import dev.enjarai.trickster.spell.tricks.blunder.IncorrectFragmentBlunder; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; + +public class SetGlyphTrick extends Trick { + public SetGlyphTrick() { + super(Pattern.of(0, 1, 2, 4, 8, 7, 6)); + } + + @Override + public Fragment activate(SpellContext ctx, List fragments) throws BlunderException { + var spell = expectInput(fragments, SpellPart.class, 0); + var addressFragment = expectInput(fragments, ListFragment.class, 1); + var glyph = expectInput(fragments, Fragment.class, 2); + + var address = addressFragment.sanitizeAddress(this); + var newSpell = spell.deepClone(); + + var node = newSpell; + for (int index : address) { + var subParts = node.subParts; + if (subParts.size() > index && subParts.get(index).isPresent()) { + node = subParts.get(index).get(); + } else { + throw new AddressNotInTreeBlunder(this, address); + } + } + node.glyph = glyph; + + return newSpell; + } +} diff --git a/src/main/java/dev/enjarai/trickster/spell/tricks/tree/SetSubtreeTrick.java b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/SetSubtreeTrick.java new file mode 100644 index 00000000..dda7a7d9 --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/tricks/tree/SetSubtreeTrick.java @@ -0,0 +1,48 @@ +package dev.enjarai.trickster.spell.tricks.tree; + +import dev.enjarai.trickster.Trickster; +import dev.enjarai.trickster.spell.Fragment; +import dev.enjarai.trickster.spell.Pattern; +import dev.enjarai.trickster.spell.SpellContext; +import dev.enjarai.trickster.spell.SpellPart; +import dev.enjarai.trickster.spell.fragment.ListFragment; +import dev.enjarai.trickster.spell.fragment.NumberFragment; +import dev.enjarai.trickster.spell.tricks.Trick; +import dev.enjarai.trickster.spell.tricks.blunder.AddressNotInTreeBlunder; +import dev.enjarai.trickster.spell.tricks.blunder.BlunderException; +import dev.enjarai.trickster.spell.tricks.blunder.IncorrectFragmentBlunder; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; + +public class SetSubtreeTrick extends Trick { + public SetSubtreeTrick() { + super(Pattern.of(0, 1, 2, 4, 6, 7, 8, 4, 0, 3, 6)); + } + + @Override + public Fragment activate(SpellContext ctx, List fragments) throws BlunderException { + var spell = expectInput(fragments, SpellPart.class, 0); + var addressFragment = expectInput(fragments, ListFragment.class, 1); + var subTree = expectInput(fragments, SpellPart.class, 2); + + var address = addressFragment.sanitizeAddress(this); + var newSpell = spell.deepClone(); + + var node = newSpell; + for (int index : address) { + var subParts = node.subParts; + if (subParts.size() > index && subParts.get(index).isPresent()) { + node = subParts.get(index).get(); + } else { + throw new AddressNotInTreeBlunder(this, address); + } + } + node.glyph = subTree.glyph; + node.subParts = subTree.subParts; + + return newSpell; + } + +} diff --git a/src/main/resources/assets/trickster/lang/en_us.yml b/src/main/resources/assets/trickster/lang/en_us.yml index af5a9869..4e1b39a6 100644 --- a/src/main/resources/assets/trickster/lang/en_us.yml +++ b/src/main/resources/assets/trickster/lang/en_us.yml @@ -82,6 +82,15 @@ trickster: list_remove: Expulsion Stratagem list_remove_element: Eviction Stratagem + locate_glyph: Pinpoint Distortion + locate_glyphs: Discovering Distortion + retrieve_glyph: Retrieval Distortion + set_glyph: Replacement Distortion + retrieve_subtree: Felling Distortion + set_subtree: Grafting Distortion + add_subtree: Branching Distortion + remove_subtree: Pruning Distortion + swap_block: Ploy of Exchange conjure_flower: Floral Ploy conjure_water: Aquatic Ploy @@ -100,6 +109,7 @@ trickster: zalgo: §kZalgo item_type: Item block_type: Block + integer_list: List of Integers blunder: incorrect_fragment: Incorrect argument at index %d. Expected %s, got %s diff --git a/src/main/resources/assets/trickster/lavender/entries/tome_of_tomfoolery/tree.md b/src/main/resources/assets/trickster/lavender/entries/tome_of_tomfoolery/tree.md new file mode 100644 index 00000000..29e1751c --- /dev/null +++ b/src/main/resources/assets/trickster/lavender/entries/tome_of_tomfoolery/tree.md @@ -0,0 +1,112 @@ +```json +{ + "title": "Spell Manipulation", + "icon": "minecraft:oak_sapling", + "category": "trickster:tricks" +} +``` + +Although [Scribing Patterns](^trickster:editing) allow for spells to be edited before they are cast, +the following patterns allow for a spell to modify other spells *during* the cast. + +;;;;; + +<|page-title@lavender:book_components|title=Note: Addresses|>Just as elements of a list are accessed by their index, +parts of a spell are accessed by their address. +An address is a list of integers that forms a path to a specific circle in a spell. + +;;;;; + +To find the address of a circle, start at the central circle in the spell. Next, find the sub-circle attached to the central circle that is in the +path to the circle you are finding the address of. Take the index of that circle, which is the number of circles that come before it, counterclockwise. +Repeat this process, adding each index to the list until you reach the circle you are finding the address of. The list you constructed is the address to +that circle. + +;;;;; + +<|pattern@trickster:templates|pattern=1\,0\,4\,8\,7\,6\,4\,2\,1\,4,title=Address Revision|> + +--- + +When this scribing pattern is drawn, the address of the circle this pattern was drawn in is written to the item in your other hand. + +;;;;; + +<|glyph@trickster:templates|trick-id=trickster:locate_glyph,title=Pinpoint Distortion|> + +spell, any -> number[] | void + +--- + +Returns the address of the first circle in the given spell with a glyph matching the given fragment. +The spell is searched using [BFS](https://en.wikipedia.org/wiki/Breadth-first_search). + +;;;;; + +<|glyph@trickster:templates|trick-id=trickster:locate_glyphs,title=Discovering Distortion|> + +spell, any -> number[][] + +--- + +Returns a list of all the addresses of circles in the given spell with a glyph matching the given fragment. + +;;;;; + +<|glyph@trickster:templates|trick-id=trickster:retrieve_glyph,title=Retrieval Distortion|> + +spell, number[] -> any + +--- + +Returns the glyph of the circle at the given address. + +;;;;; + +<|glyph@trickster:templates|trick-id=trickster:set_glyph,title=Replacement Distortion|> + +spell, number[], any -> spell + +--- + +Replaces the glyph of circle at the given address with the given fragment. + +;;;;; + +<|glyph@trickster:templates|trick-id=trickster:retrieve_subtree,title=Felling Distortion|> + +spell, number[] -> spell + +--- + +Returns the circle (and its branches) at the given address. + +;;;;; + +<|glyph@trickster:templates|trick-id=trickster:set_subtree,title=Grafting Distortion|> + +spell, number[], spell -> spell + +--- + +Grafts the latter spell into the former, replacing the circle at the given address. + +;;;;; + +<|glyph@trickster:templates|trick-id=trickster:add_subtree,title=Branching Distortion|> + +spell, number[], spell -> spell + +--- + +Attaches the latter spell to the circle at the given address as a new branch. + +;;;;; + +<|glyph@trickster:templates|trick-id=trickster:remove_subtree,title=Pruning Distortion|> + +spell, number[] -> spell | void + +--- + +Removes the circle at the given address. Returns void if the root node is removed. \ No newline at end of file