Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<BlockPos> knownPositions;
public final List<BlockPos> reachedPositions;
public final HashSet<BlockPos> 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<BlockPos> knownPositions,
List<BlockPos> reachedPositions, BlockPos currentPos, Direction enteredFrom,
CastingImage currentImage, @Nullable UUID caster, @Nullable FrozenPigment casterPigment) {
protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir,
HashSet<BlockPos> 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) {
Expand All @@ -83,7 +95,7 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set<Bl

// Return OK if it succeeded; returns Err if it didn't close and the location
public static Result<CircleExecutionState, @Nullable BlockPos> createNew(BlockEntityAbstractImpetus impetus,
@Nullable ServerPlayer caster) {
@Nullable ServerPlayer caster) {
var level = (ServerLevel) impetus.getLevel();

if (level == null)
Expand All @@ -94,14 +106,22 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set<Bl
var todo = new Stack<Pair<Direction, BlockPos>>();
todo.add(Pair.of(impetus.getStartDirection(), impetus.getBlockPos().relative(impetus.getStartDirection())));
var seenGoodPosSet = new HashSet<BlockPos>();
var seenGoodPositions = new ArrayList<BlockPos>();
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;
}
Expand All @@ -110,28 +130,48 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set<Bl
}

if (seenGoodPosSet.add(herePos)) {
// it's new
seenGoodPositions.add(herePos);
if (firstBlock == null) {
firstBlock = herePos;
negativeBlock.set(firstBlock);
positiveBlock.set(firstBlock);
}
lastBlockPos.set(herePos);
// Checks to see if it should update the most positive/negative block pos
if (herePos.getX() > 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<BlockPos>();
var reachedPositions = new HashSet<BlockPos>();
reachedPositions.add(impetus.getBlockPos());
var start = seenGoodPositions.get(0);

FrozenPigment colorizer = null;
UUID casterUUID;
Expand All @@ -142,8 +182,9 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set<Bl
casterUUID = caster.getUUID();
}
return new Result.Ok<>(
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() {
Expand All @@ -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));
Expand All @@ -174,19 +209,19 @@ 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;
}

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<BlockPos>();
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<BlockPos>();
var reachedPositions = new HashSet<BlockPos>();
var reachedTag = nbt.getList(TAG_REACHED_POSITIONS, Tag.TAG_COMPOUND);
for (var tag : reachedTag) {
reachedPositions.add(NbtUtils.readBlockPos(HexUtils.downcast(tag, CompoundTag.TYPE)));
Expand All @@ -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);
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
}
Original file line number Diff line number Diff line change
@@ -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<ImposterProtoChunk> = 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())
}
}
Loading