diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java index f95fa84f01..cbc0a5499a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java @@ -4,7 +4,9 @@ import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv; import at.petrak.hexcasting.api.casting.eval.vm.CastingImage; import at.petrak.hexcasting.api.misc.Result; +import at.petrak.hexcasting.api.mod.HexConfig; import at.petrak.hexcasting.api.pigment.FrozenPigment; +import at.petrak.hexcasting.api.utils.ChunkScanning; import at.petrak.hexcasting.api.utils.HexUtils; import com.mojang.datafixers.util.Pair; import net.minecraft.ChatFormatting; @@ -15,8 +17,7 @@ import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.*; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.phys.AABB; @@ -31,42 +32,53 @@ public class CircleExecutionState { public static final String TAG_IMPETUS_POS = "impetus_pos", TAG_IMPETUS_DIR = "impetus_dir", - TAG_KNOWN_POSITIONS = "known_positions", TAG_REACHED_POSITIONS = "reached_positions", TAG_CURRENT_POS = "current_pos", TAG_ENTERED_FROM = "entered_from", TAG_IMAGE = "image", TAG_CASTER = "caster", - TAG_PIGMENT = "pigment"; + TAG_PIGMENT = "pigment", + TAG_REACHED_NUMBER = "reached_slate", + TAG_POSITIVE_POS = "positive_pos", + TAG_NEGATIVE_POS = "negative_pos"; public final BlockPos impetusPos; public final Direction impetusDir; // Does contain the starting impetus - public final Set knownPositions; - public final List reachedPositions; + public final HashSet reachedPositions; public BlockPos currentPos; public Direction enteredFrom; public CastingImage currentImage; public @Nullable UUID caster; public @Nullable FrozenPigment casterPigment; + // This controls the speed of the current slate + public long reachedSlate; + + // Tracks the highest pos, and lowest pos of the AABB + public BlockPos positivePos; + public BlockPos negativePos; public final AABB bounds; - protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set knownPositions, - List reachedPositions, BlockPos currentPos, Direction enteredFrom, - CastingImage currentImage, @Nullable UUID caster, @Nullable FrozenPigment casterPigment) { + protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, + HashSet reachedPositions, BlockPos currentPos, Direction enteredFrom, + CastingImage currentImage, @Nullable UUID caster, @Nullable FrozenPigment casterPigment, @Nullable Long reachedSlate, + BlockPos positivePos, BlockPos negativePos) { this.impetusPos = impetusPos; this.impetusDir = impetusDir; - this.knownPositions = knownPositions; this.reachedPositions = reachedPositions; this.currentPos = currentPos; this.enteredFrom = enteredFrom; this.currentImage = currentImage; this.caster = caster; this.casterPigment = casterPigment; + this.reachedSlate = reachedSlate; + + this.positivePos = positivePos; + this.negativePos = negativePos; - this.bounds = BlockEntityAbstractImpetus.getBounds(new ArrayList<>(this.knownPositions)); + this.bounds = new AABB(positivePos,negativePos); } public @Nullable ServerPlayer getCaster(ServerLevel world) { @@ -83,7 +95,7 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set createNew(BlockEntityAbstractImpetus impetus, - @Nullable ServerPlayer caster) { + @Nullable ServerPlayer caster) { var level = (ServerLevel) impetus.getLevel(); if (level == null) @@ -94,14 +106,22 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set>(); todo.add(Pair.of(impetus.getStartDirection(), impetus.getBlockPos().relative(impetus.getStartDirection()))); var seenGoodPosSet = new HashSet(); - var seenGoodPositions = new ArrayList(); + var positiveBlock = new BlockPos.MutableBlockPos(); + var negativeBlock = new BlockPos.MutableBlockPos(); + var lastBlockPos = new BlockPos.MutableBlockPos(); + var scanning = new ChunkScanning(level); + BlockPos firstBlock = null; while (!todo.isEmpty()) { var pair = todo.pop(); var enterDir = pair.getFirst(); var herePos = pair.getSecond(); + var hereBs = scanning.getBlock(herePos); + + if (hereBs == null){ + continue; + } - var hereBs = level.getBlockState(herePos); if (!(hereBs.getBlock() instanceof ICircleComponent cmp)) { continue; } @@ -110,28 +130,48 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set positiveBlock.getX()) positiveBlock.setX(herePos.getX()); + if (herePos.getX() < negativeBlock.getX()) negativeBlock.setX(herePos.getX()); + + if (herePos.getY() > positiveBlock.getY()) positiveBlock.setY(herePos.getY()); + if (herePos.getY() < negativeBlock.getY()) negativeBlock.setY(herePos.getY()); + + if (herePos.getZ() > positiveBlock.getZ()) positiveBlock.setZ(herePos.getZ()); + if (herePos.getZ() < negativeBlock.getZ()) negativeBlock.setZ(herePos.getZ()); + + // It's new var outs = cmp.possibleExitDirections(herePos, hereBs, level); for (var out : outs) { todo.add(Pair.of(out, herePos.relative(out))); } } + + // Who would leave out the config limit? If this is forgotten, someone could make a Spell Circle the size of a world + if (seenGoodPosSet.size() >= HexConfig.server().maxSpellCircleLength()){ + return new Result.Err<>(null); + } } + // Maybe not required, but still seems like a good idea + scanning.clearCache(); - if (seenGoodPositions.isEmpty()) { + if (firstBlock == null) { return new Result.Err<>(null); } else if (!seenGoodPosSet.contains(impetus.getBlockPos())) { // we can't enter from the side the directrix exits from, so this means we couldn't loop back. // the last item we tried to examine will always be a terminal slate (b/c if it wasn't, // then the *next* slate would be last qed) - return new Result.Err<>(seenGoodPositions.get(seenGoodPositions.size() - 1)); + return new Result.Err<>(lastBlockPos); } - var knownPositions = new HashSet<>(seenGoodPositions); - var reachedPositions = new ArrayList(); + var reachedPositions = new HashSet(); reachedPositions.add(impetus.getBlockPos()); - var start = seenGoodPositions.get(0); FrozenPigment colorizer = null; UUID casterUUID; @@ -142,8 +182,9 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set( - new CircleExecutionState(impetus.getBlockPos(), impetus.getStartDirection(), knownPositions, - reachedPositions, start, impetus.getStartDirection(), new CastingImage(), casterUUID, colorizer)); + new CircleExecutionState(impetus.getBlockPos(), impetus.getStartDirection(), + reachedPositions, firstBlock, impetus.getStartDirection(), new CastingImage(), casterUUID, colorizer, + 0L, positiveBlock.move(1,1,1), negativeBlock)); } public CompoundTag save() { @@ -152,12 +193,6 @@ public CompoundTag save() { out.put(TAG_IMPETUS_POS, NbtUtils.writeBlockPos(this.impetusPos)); out.putByte(TAG_IMPETUS_DIR, (byte) this.impetusDir.ordinal()); - var knownTag = new ListTag(); - for (var bp : this.knownPositions) { - knownTag.add(NbtUtils.writeBlockPos(bp)); - } - out.put(TAG_KNOWN_POSITIONS, knownTag); - var reachedTag = new ListTag(); for (var bp : this.reachedPositions) { reachedTag.add(NbtUtils.writeBlockPos(bp)); @@ -174,6 +209,11 @@ public CompoundTag save() { if (this.casterPigment != null) out.put(TAG_PIGMENT, this.casterPigment.serializeToNBT()); + out.putLong(TAG_REACHED_NUMBER, this.reachedSlate); + + out.put(TAG_POSITIVE_POS,NbtUtils.writeBlockPos(this.positivePos)); + out.put(TAG_NEGATIVE_POS,NbtUtils.writeBlockPos(this.negativePos)); + return out; } @@ -181,12 +221,7 @@ public static CircleExecutionState load(CompoundTag nbt, ServerLevel world) { var startPos = NbtUtils.readBlockPos(nbt.getCompound(TAG_IMPETUS_POS)); var startDir = Direction.values()[nbt.getByte(TAG_IMPETUS_DIR)]; - var knownPositions = new HashSet(); - var knownTag = nbt.getList(TAG_KNOWN_POSITIONS, Tag.TAG_COMPOUND); - for (var tag : knownTag) { - knownPositions.add(NbtUtils.readBlockPos(HexUtils.downcast(tag, CompoundTag.TYPE))); - } - var reachedPositions = new ArrayList(); + var reachedPositions = new HashSet(); var reachedTag = nbt.getList(TAG_REACHED_POSITIONS, Tag.TAG_COMPOUND); for (var tag : reachedTag) { reachedPositions.add(NbtUtils.readBlockPos(HexUtils.downcast(tag, CompoundTag.TYPE))); @@ -204,8 +239,20 @@ public static CircleExecutionState load(CompoundTag nbt, ServerLevel world) { if (nbt.contains(TAG_PIGMENT, Tag.TAG_COMPOUND)) pigment = FrozenPigment.fromNBT(nbt.getCompound(TAG_PIGMENT)); - return new CircleExecutionState(startPos, startDir, knownPositions, reachedPositions, currentPos, - enteredFrom, image, caster, pigment); + long reachedNumber = 0; + if (nbt.contains(TAG_REACHED_NUMBER, Tag.TAG_LONG)) + reachedNumber = nbt.getLong(TAG_REACHED_NUMBER); + + BlockPos.MutableBlockPos positivePos = new BlockPos.MutableBlockPos(); + if (nbt.contains(TAG_POSITIVE_POS)) + positivePos.set(NbtUtils.readBlockPos(nbt.getCompound(TAG_POSITIVE_POS))); + + BlockPos.MutableBlockPos negativePos = new BlockPos.MutableBlockPos(); + if (nbt.contains(TAG_NEGATIVE_POS)) + negativePos.set(NbtUtils.readBlockPos(nbt.getCompound(TAG_NEGATIVE_POS))); + + return new CircleExecutionState(startPos, startDir, reachedPositions, currentPos, + enteredFrom, image, caster, pigment, reachedNumber,positivePos,negativePos); } /** @@ -231,6 +278,7 @@ public boolean tick(BlockEntityAbstractImpetus impetus) { executorBlockState = executor.startEnergized(this.currentPos, executorBlockState, world); this.reachedPositions.add(this.currentPos); + this.reachedSlate +=1; // Do the execution! boolean halt = false; @@ -289,7 +337,7 @@ public boolean tick(BlockEntityAbstractImpetus impetus) { * How many ticks should pass between activations, given the number of blocks encountered so far. */ protected int getTickSpeed() { - return Math.max(2, 10 - (this.reachedPositions.size() - 1) / 3); + return (int) Math.max(2, 10 - (this.reachedSlate - 1) / 3); } public void endExecution(BlockEntityAbstractImpetus impetus) { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java index 66e06b4d0d..f59391da59 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java @@ -11,10 +11,10 @@ import at.petrak.hexcasting.common.lib.HexItems; import at.petrak.hexcasting.common.lib.HexSounds; import com.mojang.datafixers.util.Pair; +import net.minecraft.ChatFormatting; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; @@ -147,10 +147,12 @@ static void sfx(BlockPos pos, BlockState bs, Level world, BlockEntityAbstractImp */ default void fakeThrowMishap(BlockPos pos, BlockState bs, CastingImage image, CircleCastEnv env, Mishap mishap) { Mishap.Context errorCtx = new Mishap.Context(null, - bs.getBlock().getName().append(" (").append(Component.literal(pos.toShortString())).append(")")); + bs.getBlock().getName().withStyle(ChatFormatting.LIGHT_PURPLE)); var sideEffect = new OperatorSideEffect.DoMishap(mishap, errorCtx); var vm = new CastingVM(image, env); sideEffect.performEffect(vm); + if (env.getImpetus() != null) + env.getImpetus().postMishap(mishap.errorMessageWithName(env,errorCtx)); } abstract sealed class ControlFlow { diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixEmptyStack.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixEmptyStack.kt index ae27403f23..b9563285b1 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixEmptyStack.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixEmptyStack.kt @@ -4,6 +4,7 @@ import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.mishaps.Mishap import at.petrak.hexcasting.api.pigment.FrozenPigment +import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component import net.minecraft.world.item.DyeColor @@ -20,5 +21,6 @@ class MishapBoolDirectrixEmptyStack( } override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = - error("circle.bool_directrix.empty_stack", pos.toShortString()) + error("circle.empty_stack", 1, + Component.literal("(").append(pos.toShortString()).append(")").withStyle(ChatFormatting.RED)) } \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt index 7c3d7b1fb5..cc5a1ec897 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/mishaps/circle/MishapBoolDirectrixNotBool.kt @@ -4,6 +4,7 @@ import at.petrak.hexcasting.api.casting.eval.CastingEnvironment import at.petrak.hexcasting.api.casting.iota.Iota import at.petrak.hexcasting.api.casting.mishaps.Mishap import at.petrak.hexcasting.api.pigment.FrozenPigment +import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component import net.minecraft.world.item.DyeColor @@ -21,5 +22,7 @@ class MishapBoolDirectrixNotBool( } override fun errorMessage(ctx: CastingEnvironment, errorCtx: Context): Component = - error("circle.bool_directrix_no_bool", pos.toShortString(), perpetrator.display()) + error("circle.invalid_value", "boolean", 0, + Component.literal("(").append(pos.toShortString()).append(")").withStyle(ChatFormatting.RED), + perpetrator.display()) } \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt new file mode 100644 index 0000000000..c251f917e0 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt @@ -0,0 +1,70 @@ +package at.petrak.hexcasting.api.utils + +import at.petrak.hexcasting.api.HexAPI +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap +import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.level.ChunkPos +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.chunk.ChunkStatus +import net.minecraft.world.level.chunk.ImposterProtoChunk + +/** + * This is a helper class to efficiently scan chunks in ways Minecraft did not intend for. This is for only reading chunks, not writing + * + * TODO: MAKE DOC THIS BETTER + */ +class ChunkScanning(var level: ServerLevel) { + var chunks: Long2ObjectLinkedOpenHashMap = Long2ObjectLinkedOpenHashMap() + + /** + * This attempts to cache a chunk to the local [chunks] + * @param ChunkPos the chunk to try to cache + * @return If the function could cache the chunk or not + */ + fun cacheChunk(chunk: ChunkPos): Boolean { + val chunkLong = chunk.toLong() + // We have the chunk already, so we can skip it + if (chunks.contains(chunkLong)){ + return true + } + val future = level.chunkSource.getChunkFuture(chunk.x,chunk.z, ChunkStatus.EMPTY,true).get() + if (future.left().isPresent){ + chunks.put(chunkLong, future.left().get() as ImposterProtoChunk) + return true + } + HexAPI.LOGGER.warn("Failed to get chunk at {}!",chunk) + return false + } + + fun cacheChunk(chunk: Long): Boolean{ + return cacheChunk(ChunkPos(chunk)) + } + + fun getBlock(blockPos: BlockPos): BlockState? { + val chunkPos = ChunkPos(blockPos).toLong() + if (!cacheChunk(chunkPos)){ + return null + } + return chunks.get(chunkPos).getBlockState(blockPos) + } + + fun getBlockEntity(blockPos: BlockPos): BlockEntity? { + val chunkPos = ChunkPos(blockPos).toLong() + if (!cacheChunk(chunkPos)){ + return null + } + return chunks.get(chunkPos).getBlockEntity(blockPos) + } + + // maybe not required, but still not a bad idea to have a Clear method + fun clearCache(){ + chunks.clear() + } + + // TODO: Might not need this + fun containsChunk(chunk: ChunkPos): Boolean{ + return chunks.contains(chunk.toLong()) + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/impetuses/BlockEntityRedstoneImpetus.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/impetuses/BlockEntityRedstoneImpetus.java index b776d156ce..b668591aab 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/impetuses/BlockEntityRedstoneImpetus.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/impetuses/BlockEntityRedstoneImpetus.java @@ -88,7 +88,6 @@ public ServerPlayer getStoredPlayer() { if (e instanceof ServerPlayer player) { return player; } else { - HexAPI.LOGGER.error("Entity {} stored in a cleric impetus wasn't a player somehow", e); return null; } } diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index 331a158405..9001170f5a 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -1109,10 +1109,10 @@ sapling: "a sapling", replaceable: "somewhere to place a block", }, - - "circle.bool_directrix": { - no_bool: "the iota encountered at %s was %s, not a bool", - empty_stack: "the stack was empty at %s", + + circle: { + empty_stack: "expected %s or more arguments but the stack was empty at %s", + invalid_value: "expected %s at index %s of the stack at %s, but got %s" }, }, // ^ mishap