Skip to content

Commit

Permalink
Made single-use scrolls more sensible (also DefaultSpellExecutor#flat…
Browse files Browse the repository at this point in the history
…tenNode has now been flattened, thanks @ArkoSammy12)
  • Loading branch information
StellarWitch7 committed Jul 30, 2024
1 parent 115a6c5 commit 0ed11cd
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/dev/enjarai/trickster/cca/CasterComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -134,14 +135,14 @@ public void writeSyncPacket(RegistryByteBuf buf, ServerPlayerEntity recipient) {
buf.write(SPELL_DATA_ENDEC, runningSpellData);
}

public boolean queueAndCast(SpellPart spell, List<Fragment> arguments) {
public boolean queueSpell(SpellPart spell, List<Fragment> arguments) {
playCastSound(0.8f, 0.1f);
return executionManager.queue(spell, arguments);
}

public boolean queueAndCast(SpellPart spell, List<Fragment> arguments, ManaPool poolOverride) {
public SpellQueueResult queueSpellAndCast(SpellPart spell, List<Fragment> arguments, ManaPool poolOverride) {
playCastSound(0.8f, 0.1f);
return executionManager.queue(spell, arguments, poolOverride);
return executionManager.queueAndCast(spell, arguments, poolOverride);
}

public void killAll() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/dev/enjarai/trickster/item/WandItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public TypedActionResult<ItemStack> 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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -38,7 +39,9 @@ public TypedActionResult<ItemStack> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class ExecutionState {
public static final Codec<ExecutionState> 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),
Expand All @@ -30,30 +31,32 @@ public class ExecutionState {

protected int recursions;
protected int delay;
private boolean hasUsedMana = false;
private final List<Fragment> arguments;
private final Deque<Integer> stacktrace = new ArrayDeque<>();
private final List<ManaLink> manaLinks = new ArrayList<>();
private final Optional<ManaPool> poolOverride;

private ExecutionState(int recursions, int delay, List<Fragment> arguments, List<Integer> stacktrace, List<ManaLink> manaLinks, Optional<ManaPool> poolOverride) {
private ExecutionState(int recursions, int delay, boolean hasUsedMana, List<Fragment> arguments, List<Integer> stacktrace, List<ManaLink> manaLinks, Optional<ManaPool> poolOverride) {
this.recursions = recursions;
this.delay = delay;
this.hasUsedMana = hasUsedMana;
this.arguments = arguments;
this.stacktrace.addAll(stacktrace);
this.manaLinks.addAll(manaLinks);
this.poolOverride = poolOverride;
}

public ExecutionState(List<Fragment> 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<Fragment> 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<Fragment> arguments, Optional<ManaPool> poolOverride) {
this(recursions, 0, arguments, List.of(), List.of(), poolOverride);
this(recursions, 0, false, arguments, List.of(), List.of(), poolOverride);
}

public ExecutionState recurseOrThrow(List<Fragment> arguments) throws ExecutionLimitReachedBlunder {
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SpellExecutionManager> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Expand Down Expand Up @@ -44,8 +45,28 @@ public boolean queue(SpellPart spell, List<Fragment> arguments) {
return queue(new DefaultSpellExecutor(spell, arguments));
}

public boolean queue(SpellPart spell, List<Fragment> arguments, ManaPool poolOverride) {
return queue(new DefaultSpellExecutor(spell, new ExecutionState(arguments, poolOverride)));
public SpellQueueResult queueAndCast(SpellPart spell, List<Fragment> 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) {
Expand All @@ -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<SpellExecutor> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<SpellPart> headStack = new Stack<>();
Stack<Integer> 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
Expand All @@ -83,7 +102,7 @@ public Optional<Fragment> 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<Fragment> run(SpellContext ctx, int executions) throws BlunderException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface SpellExecutor {
* <p>
* 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 {
Expand All @@ -38,23 +38,23 @@ 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 {
return run(source).orElseThrow(ExecutionLimitReachedBlunder::new);
}

/**
* @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<Fragment> run(SpellSource source) throws BlunderException {
return run(source, 0);
}

/**
* @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<Fragment> run(SpellSource source, int executions) throws BlunderException;
Expand Down

0 comments on commit 0ed11cd

Please sign in to comment.