diff --git a/src/client/java/dev/enjarai/trickster/screen/SpellPartWidget.java b/src/client/java/dev/enjarai/trickster/screen/SpellPartWidget.java index a56b15a8..5ef4ba8a 100644 --- a/src/client/java/dev/enjarai/trickster/screen/SpellPartWidget.java +++ b/src/client/java/dev/enjarai/trickster/screen/SpellPartWidget.java @@ -94,7 +94,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { // We're casting the big decimals down to doubles here, which means we're definitely not implementing // infinite scrollability yet. We weren't using doubles in the renderer before though, so having that // bit of extra precision might already make a big difference. If we actually do run into more issues, - // I *can* convert the renderer to big decimals as well, but that would result in an insane amount of + // I *can* convert the renderer to big decimals as well, but that would type in an insane amount of // object allocations every frame, which could very well impact performance significantly. x.doubleValue(), y.doubleValue(), size.doubleValue(), 0, delta, size -> (float) Math.clamp(1 / (size / context.getScaledWindowHeight() * 2) - 0.2, 0, 1), diff --git a/src/main/java/dev/enjarai/trickster/cca/CasterComponent.java b/src/main/java/dev/enjarai/trickster/cca/CasterComponent.java index eaa4635c..c044d6ee 100644 --- a/src/main/java/dev/enjarai/trickster/cca/CasterComponent.java +++ b/src/main/java/dev/enjarai/trickster/cca/CasterComponent.java @@ -4,6 +4,7 @@ import dev.enjarai.trickster.ModSounds; import dev.enjarai.trickster.spell.Fragment; import dev.enjarai.trickster.spell.SpellPart; +import dev.enjarai.trickster.spell.execution.SpellQueueResult; import dev.enjarai.trickster.spell.execution.executor.ErroredSpellExecutor; import dev.enjarai.trickster.spell.execution.executor.SpellExecutor; import dev.enjarai.trickster.spell.execution.source.PlayerSpellSource; @@ -134,14 +135,14 @@ public void writeSyncPacket(RegistryByteBuf buf, ServerPlayerEntity recipient) { buf.write(SPELL_DATA_ENDEC, runningSpellData); } - public boolean queueAndCast(SpellPart spell, List arguments) { + public boolean queueSpell(SpellPart spell, List arguments) { playCastSound(0.8f, 0.1f); return executionManager.queue(spell, arguments); } - public boolean queueAndCast(SpellPart spell, List arguments, ManaPool poolOverride) { + public SpellQueueResult queueSpellAndCast(SpellPart spell, List arguments, ManaPool poolOverride) { playCastSound(0.8f, 0.1f); - return executionManager.queue(spell, arguments, poolOverride); + return executionManager.queueAndCast(spell, arguments, poolOverride); } public void killAll() { diff --git a/src/main/java/dev/enjarai/trickster/item/WandItem.java b/src/main/java/dev/enjarai/trickster/item/WandItem.java index c295c2ce..fe1eae53 100644 --- a/src/main/java/dev/enjarai/trickster/item/WandItem.java +++ b/src/main/java/dev/enjarai/trickster/item/WandItem.java @@ -23,7 +23,7 @@ public TypedActionResult use(World world, PlayerEntity user, Hand han if (!world.isClient()) { var spell = stack.get(ModComponents.SPELL); if (spell != null) { - user.getComponent(ModEntityCumponents.CASTER).queueAndCast(spell.spell(), List.of()); + user.getComponent(ModEntityCumponents.CASTER).queueSpell(spell.spell(), List.of()); } } diff --git a/src/main/java/dev/enjarai/trickster/item/WrittenScrollItem.java b/src/main/java/dev/enjarai/trickster/item/WrittenScrollItem.java index 3a38401e..b46c7a78 100644 --- a/src/main/java/dev/enjarai/trickster/item/WrittenScrollItem.java +++ b/src/main/java/dev/enjarai/trickster/item/WrittenScrollItem.java @@ -3,6 +3,7 @@ import dev.enjarai.trickster.cca.ModEntityCumponents; import dev.enjarai.trickster.item.component.ModComponents; import dev.enjarai.trickster.screen.ScrollAndQuillScreenHandler; +import dev.enjarai.trickster.spell.execution.SpellQueueResult; import dev.enjarai.trickster.spell.mana.SimpleManaPool; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.player.PlayerEntity; @@ -38,7 +39,9 @@ public TypedActionResult use(World world, PlayerEntity user, Hand han if (!world.isClient()) { var spell = stack.get(ModComponents.SPELL); if (spell != null) { - if (ModEntityCumponents.CASTER.get(user).queueAndCast(spell.spell(), List.of(), SimpleManaPool.getSingleUse(meta.mana()))) + var result = ModEntityCumponents.CASTER.get(user).queueSpellAndCast(spell.spell(), List.of(), SimpleManaPool.getSingleUse(meta.mana())); + + if (result.type() != SpellQueueResult.Type.NOT_QUEUED && result.state().hasUsedMana()) stack.decrement(1); return TypedActionResult.success(stack); } diff --git a/src/main/java/dev/enjarai/trickster/screen/ScrollAndQuillScreenHandler.java b/src/main/java/dev/enjarai/trickster/screen/ScrollAndQuillScreenHandler.java index 0f24af19..87aeb2fa 100644 --- a/src/main/java/dev/enjarai/trickster/screen/ScrollAndQuillScreenHandler.java +++ b/src/main/java/dev/enjarai/trickster/screen/ScrollAndQuillScreenHandler.java @@ -117,8 +117,8 @@ public void updateSpell(SpellPart spell) { }); } } else { -// var result = SpellPart.CODEC.encodeStart(JsonOps.INSTANCE, spell).result().get(); -// Trickster.LOGGER.warn(result.toString()); +// var type = SpellPart.CODEC.encodeStart(JsonOps.INSTANCE, spell).type().get(); +// Trickster.LOGGER.warn(type.toString()); sendMessage(new SpellMessage(spell)); } } diff --git a/src/main/java/dev/enjarai/trickster/spell/ItemTriggerProvider.java b/src/main/java/dev/enjarai/trickster/spell/ItemTriggerProvider.java index 1c8568e8..6b08c9f8 100644 --- a/src/main/java/dev/enjarai/trickster/spell/ItemTriggerProvider.java +++ b/src/main/java/dev/enjarai/trickster/spell/ItemTriggerProvider.java @@ -38,7 +38,7 @@ public interface ItemTriggerProvider { var spellComponent = stack.get(ModComponents.SPELL); if (spellComponent != null) { - ModEntityCumponents.CASTER.get(player).queueAndCast(spellComponent.spell(), arguments); + ModEntityCumponents.CASTER.get(player).queueSpell(spellComponent.spell(), arguments); } } } diff --git a/src/main/java/dev/enjarai/trickster/spell/execution/ExecutionState.java b/src/main/java/dev/enjarai/trickster/spell/execution/ExecutionState.java index 83ea951d..4fa7729f 100644 --- a/src/main/java/dev/enjarai/trickster/spell/execution/ExecutionState.java +++ b/src/main/java/dev/enjarai/trickster/spell/execution/ExecutionState.java @@ -22,6 +22,7 @@ public class ExecutionState { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.INT.fieldOf("recursions").forGetter(ExecutionState::getRecursions), Codec.INT.fieldOf("delay").forGetter(ExecutionState::getDelay), + Codec.BOOL.fieldOf("has_used_mana").forGetter(ExecutionState::hasUsedMana), Fragment.CODEC.get().codec().listOf().fieldOf("arguments").forGetter(state -> state.arguments), Codec.INT.listOf().fieldOf("stacktrace").forGetter(state -> state.stacktrace.stream().toList()), ManaLink.CODEC.listOf().fieldOf("mana_links").forGetter(state -> state.manaLinks), @@ -30,14 +31,16 @@ public class ExecutionState { protected int recursions; protected int delay; + private boolean hasUsedMana = false; private final List arguments; private final Deque stacktrace = new ArrayDeque<>(); private final List manaLinks = new ArrayList<>(); private final Optional poolOverride; - private ExecutionState(int recursions, int delay, List arguments, List stacktrace, List manaLinks, Optional poolOverride) { + private ExecutionState(int recursions, int delay, boolean hasUsedMana, List arguments, List stacktrace, List manaLinks, Optional poolOverride) { this.recursions = recursions; this.delay = delay; + this.hasUsedMana = hasUsedMana; this.arguments = arguments; this.stacktrace.addAll(stacktrace); this.manaLinks.addAll(manaLinks); @@ -45,15 +48,15 @@ private ExecutionState(int recursions, int delay, List arguments, List } public ExecutionState(List arguments) { - this(0, 0, arguments, List.of(), List.of(), Optional.empty()); + this(0, 0, false, arguments, List.of(), List.of(), Optional.empty()); } public ExecutionState(List arguments, ManaPool poolOverride) { - this(0, 0, arguments, List.of(), List.of(), Optional.ofNullable(poolOverride)); + this(0, 0, false, arguments, List.of(), List.of(), Optional.ofNullable(poolOverride)); } private ExecutionState(int recursions, List arguments, Optional poolOverride) { - this(recursions, 0, arguments, List.of(), List.of(), poolOverride); + this(recursions, 0, false, arguments, List.of(), List.of(), poolOverride); } public ExecutionState recurseOrThrow(List arguments) throws ExecutionLimitReachedBlunder { @@ -158,7 +161,13 @@ public void addManaLink(Trick source, ManaLink link) throws EntityInvalidBlunder manaLinks.add(link); } + public boolean hasUsedMana() { + return hasUsedMana; + } + public void useMana(Trick trickSource, SpellContext ctx, ManaPool pool, float amount) throws NotEnoughManaBlunder { + hasUsedMana = true; + if (!manaLinks.isEmpty()) { float totalAvailable = 0; float leftOver = 0; diff --git a/src/main/java/dev/enjarai/trickster/spell/execution/SpellExecutionManager.java b/src/main/java/dev/enjarai/trickster/spell/execution/SpellExecutionManager.java index ec7bdcdf..f25f8e05 100644 --- a/src/main/java/dev/enjarai/trickster/spell/execution/SpellExecutionManager.java +++ b/src/main/java/dev/enjarai/trickster/spell/execution/SpellExecutionManager.java @@ -17,6 +17,7 @@ import net.minecraft.text.Text; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; public class SpellExecutionManager { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( @@ -44,8 +45,28 @@ public boolean queue(SpellPart spell, List arguments) { return queue(new DefaultSpellExecutor(spell, arguments)); } - public boolean queue(SpellPart spell, List arguments, ManaPool poolOverride) { - return queue(new DefaultSpellExecutor(spell, new ExecutionState(arguments, poolOverride))); + public SpellQueueResult queueAndCast(SpellPart spell, List arguments, ManaPool poolOverride) { + var executor = new DefaultSpellExecutor(spell, new ExecutionState(arguments, poolOverride)); + boolean queued = queue(executor); + + if (queued) { + for (var iterator = spells.int2ObjectEntrySet().iterator(); iterator.hasNext();) { + var entry = iterator.next(); + + if (entry.getValue() == executor) { + AtomicBoolean isDone = new AtomicBoolean(true); + tryRun(entry, + (index, executor1) -> isDone.set(false), + (index, executor2) -> iterator.remove(), + (index, executor3) -> { }); + return new SpellQueueResult(isDone.get() + ? SpellQueueResult.Type.QUEUED_DONE + : SpellQueueResult.Type.QUEUED_STILL_RUNNING, executor.getCurrentState()); + } + } + } + + return new SpellQueueResult(SpellQueueResult.Type.NOT_QUEUED, executor.getCurrentState()); } public boolean queue(SpellExecutor executor) { @@ -67,36 +88,54 @@ public void tick(ExecutorCallback tickCallback, ExecutorCallback completeCallbac if (source == null) return; - for (var iterator = spells.int2ObjectEntrySet().iterator(); iterator.hasNext(); ) { + for (var iterator = spells.int2ObjectEntrySet().iterator(); iterator.hasNext();) { var entry = iterator.next(); - var spell = entry.getValue(); - - try { - if (spell.run(source).isEmpty()) { - tickCallback.callTheBack(entry.getIntKey(), spell); - } else { - iterator.remove(); - completeCallback.callTheBack(entry.getIntKey(), spell); - } - } catch (BlunderException blunder) { - var message = blunder.createMessage() - .append(" (").append(spell.getCurrentState().formatStackTrace()).append(")"); - - if (blunder instanceof NaNBlunder) - source.getPlayer().ifPresent(ModCriteria.NAN_NUMBER::trigger); - - entry.setValue(new ErroredSpellExecutor(message)); - source.getPlayer().ifPresent(player -> player.sendMessage(message)); - errorCallback.callTheBack(entry.getIntKey(), spell); - } catch (Exception e) { - var message = Text.literal("Uncaught exception in spell: " + e.getMessage()) - .append(" (").append(spell.getCurrentState().formatStackTrace()).append(")"); - - entry.setValue(new ErroredSpellExecutor(message)); - source.getPlayer().ifPresent(player -> player.sendMessage(message)); - errorCallback.callTheBack(entry.getIntKey(), spell); + tryRun(entry, tickCallback, (index, executor) -> { + iterator.remove(); + completeCallback.callTheBack(index, executor); + }, errorCallback); + } + } + + /** + * Attempts to run the given entry's SpellExecutor. + * @param entry + * @param tickCallback + * @param completeCallback + * @param errorCallback + * @return whether the spell has finished running or not. Blunders and normal completion return true, otherwise returns false. + */ + private boolean tryRun(Int2ObjectMap.Entry entry, ExecutorCallback tickCallback, ExecutorCallback completeCallback, ExecutorCallback errorCallback) { + var spell = entry.getValue(); + + try { + if (spell.run(source).isEmpty()) { + tickCallback.callTheBack(entry.getIntKey(), spell); + return false; + } else { + completeCallback.callTheBack(entry.getIntKey(), spell); + return true; } + } catch (BlunderException blunder) { + var message = blunder.createMessage() + .append(" (").append(spell.getCurrentState().formatStackTrace()).append(")"); + + if (blunder instanceof NaNBlunder) + source.getPlayer().ifPresent(ModCriteria.NAN_NUMBER::trigger); + + entry.setValue(new ErroredSpellExecutor(message)); + source.getPlayer().ifPresent(player -> player.sendMessage(message)); + errorCallback.callTheBack(entry.getIntKey(), spell); + } catch (Exception e) { + var message = Text.literal("Uncaught exception in spell: " + e.getMessage()) + .append(" (").append(spell.getCurrentState().formatStackTrace()).append(")"); + + entry.setValue(new ErroredSpellExecutor(message)); + source.getPlayer().ifPresent(player -> player.sendMessage(message)); + errorCallback.callTheBack(entry.getIntKey(), spell); } + + return true; } public void setSource(SpellSource source) { diff --git a/src/main/java/dev/enjarai/trickster/spell/execution/SpellQueueResult.java b/src/main/java/dev/enjarai/trickster/spell/execution/SpellQueueResult.java new file mode 100644 index 00000000..79a0c8e8 --- /dev/null +++ b/src/main/java/dev/enjarai/trickster/spell/execution/SpellQueueResult.java @@ -0,0 +1,9 @@ +package dev.enjarai.trickster.spell.execution; + +public record SpellQueueResult(Type type, ExecutionState state) { + public enum Type { + NOT_QUEUED, + QUEUED_DONE, + QUEUED_STILL_RUNNING + } +} \ No newline at end of file diff --git a/src/main/java/dev/enjarai/trickster/spell/execution/executor/DefaultSpellExecutor.java b/src/main/java/dev/enjarai/trickster/spell/execution/executor/DefaultSpellExecutor.java index 096dc161..1a3146ff 100644 --- a/src/main/java/dev/enjarai/trickster/spell/execution/executor/DefaultSpellExecutor.java +++ b/src/main/java/dev/enjarai/trickster/spell/execution/executor/DefaultSpellExecutor.java @@ -62,19 +62,38 @@ public SpellExecutorType type() { return SpellExecutorType.DEFAULT; } - protected void flattenNode(SpellPart node) { - instructions.push(new ExitScopeInstruction()); - instructions.push(node.glyph); + // made non-recursive by @ArkoSammy12 + protected void flattenNode(SpellPart head) { + Stack headStack = new Stack<>(); + Stack indexStack = new Stack<>(); - for (var subNode : node.subParts.reversed()) { - flattenNode(subNode); - } + headStack.push(head); + indexStack.push(-1); + + while (!headStack.isEmpty()) { + SpellPart currentNode = headStack.peek(); + int currentIndex = indexStack.pop(); + + if (currentIndex == -1) { + instructions.push(new ExitScopeInstruction()); + instructions.push(currentNode.glyph); + } - instructions.push(new EnterScopeInstruction()); + currentIndex++; + + if (currentIndex < currentNode.subParts.size()) { + headStack.push(currentNode.subParts.reversed().get(currentIndex)); + indexStack.push(currentIndex); + indexStack.push(-1); + } else { + headStack.pop(); + instructions.push(new EnterScopeInstruction()); + } + } } /** - * @return the spell's result, or Optional.empty() if the spell is not done executing. + * @return the spell's type, or Optional.empty() if the spell is not done executing. * @throws BlunderException */ @Override @@ -83,7 +102,7 @@ public Optional run(SpellSource source, int executions) throws Blunder } /** - * @return the spell's result, or Optional.empty() if the spell is not done executing. + * @return the spell's type, or Optional.empty() if the spell is not done executing. * @throws BlunderException */ protected Optional run(SpellContext ctx, int executions) throws BlunderException { diff --git a/src/main/java/dev/enjarai/trickster/spell/execution/executor/SpellExecutor.java b/src/main/java/dev/enjarai/trickster/spell/execution/executor/SpellExecutor.java index 8377a626..967bb85a 100644 --- a/src/main/java/dev/enjarai/trickster/spell/execution/executor/SpellExecutor.java +++ b/src/main/java/dev/enjarai/trickster/spell/execution/executor/SpellExecutor.java @@ -22,7 +22,7 @@ public interface SpellExecutor { *

* Before this function throws, it will append the additional spell stacktrace to the stacktrace in the provided context. * - * @return the spell's result. + * @return the spell's type. * @throws BlunderException */ default Fragment singleTickRun(SpellContext context) throws BlunderException { @@ -38,7 +38,7 @@ default Fragment singleTickRun(SpellContext context) throws BlunderException { /** * Attempts to execute the spell within a single tick, throws ExecutionLimitReachedBlunder if single-tick execution is not feasible. * - * @return the spell's result. + * @return the spell's type. * @throws BlunderException */ default Fragment singleTickRun(SpellSource source) throws BlunderException { @@ -46,7 +46,7 @@ default Fragment singleTickRun(SpellSource source) throws BlunderException { } /** - * @return the spell's result, or Optional.empty() if the spell is not done executing. + * @return the spell's type, or Optional.empty() if the spell is not done executing. * @throws BlunderException */ default Optional run(SpellSource source) throws BlunderException { @@ -54,7 +54,7 @@ default Optional run(SpellSource source) throws BlunderException { } /** - * @return the spell's result, or Optional.empty() if the spell is not done executing. + * @return the spell's type, or Optional.empty() if the spell is not done executing. * @throws BlunderException */ Optional run(SpellSource source, int executions) throws BlunderException;