Merge pull request #451 from Talia-12/casting-context

Wowza!
This commit is contained in:
petrak@ 2023-04-24 11:18:14 -04:00 committed by GitHub
commit e07c4465ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
98 changed files with 1721 additions and 1090 deletions

View file

@ -15,7 +15,7 @@ public interface ADHexHolder {
@Nullable
List<Iota> getHex(ServerLevel level);
void writeHex(List<Iota> patterns, int media);
void writeHex(List<Iota> patterns, long media);
void clearHex();
}

View file

@ -7,28 +7,28 @@ public interface ADMediaHolder {
/**
* Use {@code withdrawMedia(-1, true)}
*
* @see ADMediaHolder#withdrawMedia(int, boolean)
* @see ADMediaHolder#withdrawMedia(long, boolean)
*/
@ApiStatus.OverrideOnly
int getMedia();
long getMedia();
/**
* Use {@code withdrawMedia(-1, true) + insertMedia(-1, true)} where possible
*
* @see ADMediaHolder#insertMedia(int, boolean)
* @see ADMediaHolder#withdrawMedia(int, boolean)
* @see ADMediaHolder#insertMedia(long, boolean)
* @see ADMediaHolder#withdrawMedia(long, boolean)
*/
@ApiStatus.OverrideOnly
int getMaxMedia();
long getMaxMedia();
/**
* Use {@code insertMedia(media - withdrawMedia(-1, true), false)} where possible
*
* @see ADMediaHolder#insertMedia(int, boolean)
* @see ADMediaHolder#withdrawMedia(int, boolean)
* @see ADMediaHolder#insertMedia(long, boolean)
* @see ADMediaHolder#withdrawMedia(long, boolean)
*/
@ApiStatus.OverrideOnly
void setMedia(int media);
void setMedia(long media);
/**
* Whether this media holder can have media inserted into it.
@ -63,7 +63,7 @@ public interface ADMediaHolder {
* <p>
* Withdrawing a negative amount will act as though you attempted to withdraw as much media as the holder contains.
*/
default int withdrawMedia(int cost, boolean simulate) {
default long withdrawMedia(long cost, boolean simulate) {
var mediaHere = getMedia();
if (cost < 0) {
cost = mediaHere;
@ -83,9 +83,9 @@ public interface ADMediaHolder {
* Inserting a negative amount will act as though you attempted to insert exactly as much media as the holder was
* missing.
*/
default int insertMedia(int amount, boolean simulate) {
default long insertMedia(long amount, boolean simulate) {
var mediaHere = getMedia();
int emptySpace = getMaxMedia() - mediaHere;
long emptySpace = getMaxMedia() - mediaHere;
if (emptySpace <= 0) {
return 0;
}
@ -93,7 +93,7 @@ public interface ADMediaHolder {
amount = emptySpace;
}
int inserting = Math.min(amount, emptySpace);
long inserting = Math.min(amount, emptySpace);
if (!simulate) {
var newMedia = mediaHere + inserting;

View file

@ -1,6 +1,8 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
@ -15,9 +17,9 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import java.util.List;
// Facing dir is the direction it starts searching for slates in to start
public abstract class BlockAbstractImpetus extends BlockCircleComponent implements EntityBlock {
@ -30,21 +32,20 @@ public abstract class BlockAbstractImpetus extends BlockCircleComponent implemen
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level world) {
public ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
return new ControlFlow.Stop();
}
@Override
public boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
return enterDir != bs.getValue(FACING);
}
@Override
public EnumSet<Direction> exitDirections(BlockPos pos, BlockState bs, Level world) {
public EnumSet<Direction> possibleExitDirections(BlockPos pos, BlockState bs, Level world) {
return EnumSet.of(bs.getValue(FACING));
}
@Override
public @Nullable HexPattern getPattern(BlockPos pos, BlockState bs, Level world) {
return null;
}
@Override
public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft) {
return normalDirOfOther(pos.relative(bs.getValue(FACING)), world, recursionLeft);
@ -58,7 +59,7 @@ public abstract class BlockAbstractImpetus extends BlockCircleComponent implemen
@Override
public void tick(BlockState pState, ServerLevel pLevel, BlockPos pPos, RandomSource pRandom) {
if (pLevel.getBlockEntity(pPos) instanceof BlockEntityAbstractImpetus tile && pState.getValue(ENERGIZED)) {
tile.stepCircle();
tile.tickExecution();
}
}
@ -66,7 +67,8 @@ public abstract class BlockAbstractImpetus extends BlockCircleComponent implemen
public void onRemove(BlockState pState, Level pLevel, BlockPos pPos, BlockState pNewState, boolean pIsMoving) {
if (!pNewState.is(pState.getBlock())
&& pLevel.getBlockEntity(pPos) instanceof BlockEntityAbstractImpetus impetus) {
impetus.stopCasting();
impetus.endExecution(); // TODO: Determine if this was important
// TODO: Fix this, it should make all the glowy circle components stop glowing.
}
super.onRemove(pState, pLevel, pPos, pNewState, pIsMoving);
}

View file

@ -1,38 +1,47 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.api.casting.circles.ICircleComponent;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
public abstract class BlockCircleComponent extends Block {
public abstract class BlockCircleComponent extends Block implements ICircleComponent {
public static final BooleanProperty ENERGIZED = BooleanProperty.create("energized");
public BlockCircleComponent(Properties p_49795_) {
super(p_49795_);
}
/**
* Can this component get transferred to from a block coming in from that direction, with the given normal?
*/
abstract public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos,
BlockState bs, Level world);
@Override
public BlockState startEnergized(BlockPos pos, BlockState bs, Level world) {
var newState = bs.setValue(ENERGIZED, true);
world.setBlockAndUpdate(pos, newState);
abstract public EnumSet<Direction> exitDirections(BlockPos pos, BlockState bs, Level world);
return newState;
}
@Nullable
abstract public HexPattern getPattern(BlockPos pos, BlockState bs, Level world);
@Override
public boolean isEnergized(BlockPos pos, BlockState bs, Level world) {
return bs.getValue(ENERGIZED);
}
@Override
public BlockState endEnergized(BlockPos pos, BlockState bs, Level world) {
var newState = bs.setValue(ENERGIZED, false);
world.setBlockAndUpdate(pos, newState);
return newState;
}
/**
* Which direction points "up" or "out" for this block?
* This is used for {@link BlockCircleComponent#canEnterFromDirection(Direction, Direction, BlockPos, BlockState, Level)}
* This is used for {@link ICircleComponent#canEnterFromDirection(Direction, BlockPos, BlockState, ServerLevel)}
* as well as particles.
*/
public Direction normalDir(BlockPos pos, BlockState bs, Level world) {

View file

@ -1,578 +0,0 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.block.HexBlockEntity;
import at.petrak.hexcasting.api.casting.ParticleSpray;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.SpellCircleContext;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.PatternIota;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.misc.MediaConstants;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.api.utils.MediaHelper;
import at.petrak.hexcasting.common.items.magic.ItemCreativeUnlocker;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
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.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.WorldlyContainer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat;
import java.util.*;
public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implements WorldlyContainer {
public static final String
TAG_ACTIVATOR = "activator",
TAG_COLORIZER = "colorizer",
TAG_NEXT_BLOCK = "next_block",
TAG_TRACKED_BLOCKS = "tracked_blocks",
TAG_FOUND_ALL = "found_all",
TAG_MEDIA = "media",
TAG_LAST_MISHAP = "last_mishap";
private static final DecimalFormat DUST_AMOUNT = new DecimalFormat("###,###.##");
@Nullable
private UUID activator = null;
@Nullable
private FrozenColorizer colorizer = null;
@Nullable
private BlockPos nextBlock = null;
@Nullable
private List<BlockPos> trackedBlocks = null;
private transient Set<BlockPos> knownBlocks = null;
private boolean foundAll = false;
@Nullable
private Component lastMishap = null;
private static final int MAX_CAPACITY = 2_000_000_000;
private int media = 0;
public BlockEntityAbstractImpetus(BlockEntityType<?> pType, BlockPos pWorldPosition, BlockState pBlockState) {
super(pType, pWorldPosition, pBlockState);
}
abstract public boolean activatorAlwaysInRange();
public int getMedia() {
return this.media;
}
public void setMedia(int media) {
this.media = media;
}
@Nullable
public Component getLastMishap() {
return lastMishap;
}
public void setLastMishap(@Nullable Component lastMishap) {
this.lastMishap = lastMishap;
}
public void activateSpellCircle(ServerPlayer activator) {
if (this.nextBlock != null) {
return;
}
this.level.scheduleTick(this.getBlockPos(), this.getBlockState().getBlock(), this.getTickSpeed());
this.activator = activator.getUUID();
this.nextBlock = this.getBlockPos();
this.trackedBlocks = new ArrayList<>();
this.knownBlocks = new HashSet<>();
this.colorizer = IXplatAbstractions.INSTANCE.getColorizer(activator);
this.level.setBlockAndUpdate(this.getBlockPos(),
this.getBlockState().setValue(BlockAbstractImpetus.ENERGIZED, true));
this.stepCircle();
}
public void applyScryingLensOverlay(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos,
Player observer, Level world,
Direction hitFace) {
if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) {
if (beai.getMedia() < 0) {
lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), ItemCreativeUnlocker.infiniteMedia(world)));
} else {
var dustCount = (float) beai.getMedia() / (float) MediaConstants.DUST_UNIT;
var dustCmp = Component.translatable("hexcasting.tooltip.media",
DUST_AMOUNT.format(dustCount));
lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), dustCmp));
}
var mishap = this.getLastMishap();
if (mishap != null) {
lines.add(new Pair<>(new ItemStack(Items.MUSIC_DISC_11), mishap));
}
}
}
@Override
protected void saveModData(CompoundTag tag) {
if (this.activator != null && this.colorizer != null && this.nextBlock != null && this.trackedBlocks != null) {
tag.putUUID(TAG_ACTIVATOR, this.activator);
tag.put(TAG_NEXT_BLOCK, NbtUtils.writeBlockPos(this.nextBlock));
tag.put(TAG_COLORIZER, this.colorizer.serializeToNBT());
tag.putBoolean(TAG_FOUND_ALL, this.foundAll);
var trackeds = new ListTag();
for (var tracked : this.trackedBlocks) {
trackeds.add(NbtUtils.writeBlockPos(tracked));
}
tag.put(TAG_TRACKED_BLOCKS, trackeds);
}
tag.putInt(TAG_MEDIA, this.media);
if (this.lastMishap != null) {
tag.putString(TAG_LAST_MISHAP, Component.Serializer.toJson(this.lastMishap));
}
}
@Override
protected void loadModData(CompoundTag tag) {
if (tag.contains(TAG_ACTIVATOR, Tag.TAG_INT_ARRAY) &&
tag.contains(TAG_COLORIZER, Tag.TAG_COMPOUND) &&
tag.contains(TAG_NEXT_BLOCK, Tag.TAG_COMPOUND) &&
tag.contains(TAG_TRACKED_BLOCKS, Tag.TAG_LIST)) {
this.activator = tag.getUUID(TAG_ACTIVATOR);
this.colorizer = FrozenColorizer.fromNBT(tag.getCompound(TAG_COLORIZER));
this.nextBlock = NbtUtils.readBlockPos(tag.getCompound(TAG_NEXT_BLOCK));
this.foundAll = tag.getBoolean(TAG_FOUND_ALL);
var trackeds = tag.getList(TAG_TRACKED_BLOCKS, Tag.TAG_COMPOUND);
this.trackedBlocks = new ArrayList<>(trackeds.size());
this.knownBlocks = new HashSet<>();
for (var tracked : trackeds) {
var pos = NbtUtils.readBlockPos((CompoundTag) tracked);
this.trackedBlocks.add(pos);
this.knownBlocks.add(pos);
}
} else {
this.activator = null;
this.colorizer = null;
this.nextBlock = null;
this.foundAll = false;
this.trackedBlocks = new ArrayList<>();
this.knownBlocks = new HashSet<>();
}
this.media = tag.getInt(TAG_MEDIA);
if (tag.contains(TAG_LAST_MISHAP, Tag.TAG_STRING)) {
this.lastMishap = Component.Serializer.fromJson(tag.getString(TAG_LAST_MISHAP));
} else {
this.lastMishap = null;
}
}
void stepCircle() {
this.setChanged();
// haha which silly idiot would have done something like this
if (this.activator == null || this.colorizer == null || this.nextBlock == null || this.trackedBlocks == null) {
return;
}
var possibleErrorPos = this.checkEverythingOk();
if (possibleErrorPos != null) {
this.sfx(possibleErrorPos, false);
this.stopCasting();
return;
}
if (this.foundAll) {
this.clearEnergized();
this.castSpell();
this.stopCasting();
return;
}
// This should only fail if we remove blocks halfway through casting
var bsHere = this.level.getBlockState(this.nextBlock);
if (!this.trackedBlocks.isEmpty() && bsHere.getBlock() instanceof BlockAbstractImpetus) {
// no two impetuses!
this.sfx(this.nextBlock, false);
this.stopCasting();
return;
}
var blockHere = bsHere.getBlock();
if (!(blockHere instanceof BlockCircleComponent cc)) {
this.sfx(this.nextBlock, false);
this.stopCasting();
return;
}
// Awesome we know this block is OK
var thisNormal = cc.normalDir(this.nextBlock, bsHere, this.level);
var possibleExits = cc.exitDirections(this.nextBlock, bsHere, this.level);
BlockPos foundPos = null;
for (var exit : possibleExits) {
var neighborPos = this.nextBlock.relative(exit);
var blockThere = this.level.getBlockState(neighborPos);
// at this point, we haven't actually added nextBlock to trackedBlocks
// so, in the smallest circle case (a 2x2), this will have a size of 3 (with this block being the 4th).
var closedLoop = (this.trackedBlocks.size() >= 3 && this.trackedBlocks.get(0).equals(neighborPos));
var mightBeOkThere = closedLoop
|| this.trackedBlocks.isEmpty()
|| !this.trackedBlocks.get(this.trackedBlocks.size() - 1).equals(neighborPos);
if (mightBeOkThere
&& blockThere.getBlock() instanceof BlockCircleComponent cc2
&& cc2.canEnterFromDirection(exit.getOpposite(), thisNormal, neighborPos, blockThere, this.level)
// another good use for the implies operator 😩
&& (!blockThere.getValue(BlockCircleComponent.ENERGIZED) || this.knownBlocks.contains(neighborPos))) {
if (foundPos == null) {
foundPos = neighborPos;
this.foundAll |= closedLoop;
} else {
// uh oh, fork in the road
this.sfx(this.nextBlock, false);
this.stopCasting();
return;
}
}
}
if (foundPos != null) {
// pog
this.trackedBlocks.add(this.nextBlock);
this.knownBlocks.add(this.nextBlock);
this.nextBlock = foundPos;
} else {
// end of the line
this.sfx(this.nextBlock, false);
this.stopCasting();
return;
}
var lastPos = this.trackedBlocks.get(this.trackedBlocks.size() - 1);
var justTrackedBlock = this.level.getBlockState(lastPos);
this.level.setBlockAndUpdate(lastPos, justTrackedBlock.setValue(BlockCircleComponent.ENERGIZED, true));
this.sfx(lastPos, true);
this.level.scheduleTick(this.getBlockPos(), this.getBlockState().getBlock(), this.getTickSpeed());
}
private void castSpell() {
var player = this.getPlayer();
if (player instanceof ServerPlayer splayer) {
var bounds = getBounds(this.trackedBlocks);
var ctx = new CastingEnvironment(splayer, InteractionHand.MAIN_HAND,
new SpellCircleContext(this.getBlockPos(), bounds, this.activatorAlwaysInRange()));
var harness = new CastingVM(ctx);
BlockPos erroredPos = null;
for (var tracked : this.trackedBlocks) {
var bs = this.level.getBlockState(tracked);
if (bs.getBlock() instanceof BlockCircleComponent cc) {
var newPattern = cc.getPattern(tracked, bs, this.level);
if (newPattern != null) {
var info = harness.queueAndExecuteIota(new PatternIota(newPattern), splayer.getLevel());
if (!info.getResolutionType().getSuccess()) {
erroredPos = tracked;
break;
}
}
}
}
if (erroredPos != null) {
this.sfx(erroredPos, false);
} else {
this.setLastMishap(null);
}
this.setChanged();
}
}
@Contract(pure = true)
private static AABB getBounds(List<BlockPos> poses) {
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int minZ = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
int maxZ = Integer.MIN_VALUE;
for (var pos : poses) {
if (pos.getX() < minX) {
minX = pos.getX();
}
if (pos.getY() < minY) {
minY = pos.getY();
}
if (pos.getZ() < minZ) {
minZ = pos.getZ();
}
if (pos.getX() > maxX) {
maxX = pos.getX();
}
if (pos.getY() > maxY) {
maxY = pos.getY();
}
if (pos.getZ() > maxZ) {
maxZ = pos.getZ();
}
}
return new AABB(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1);
}
@Nullable
private BlockPos checkEverythingOk() {
// if they logged out or changed dimensions or something
if (this.getPlayer() == null) {
return this.getBlockPos();
}
for (var pos : this.trackedBlocks) {
if (!(this.level.getBlockState(pos).getBlock() instanceof BlockCircleComponent)) {
return pos;
}
}
if (this.trackedBlocks.size() > HexConfig.server().maxSpellCircleLength()) {
return this.trackedBlocks.get(this.trackedBlocks.size() - 1);
}
return null;
}
private void sfx(BlockPos pos, boolean success) {
Vec3 vpos;
Vec3 vecOutDir;
var bs = this.level.getBlockState(pos);
if (bs.getBlock() instanceof BlockCircleComponent bcc) {
var outDir = bcc.normalDir(pos, bs, this.level);
var height = bcc.particleHeight(pos, bs, this.level);
vecOutDir = new Vec3(outDir.step());
vpos = Vec3.atCenterOf(pos).add(vecOutDir.scale(height));
} else {
// we probably are doing this because it's an error and we removed a block
vpos = Vec3.atCenterOf(pos);
vecOutDir = new Vec3(0, 0, 0);
}
if (this.level instanceof ServerLevel serverLevel) {
var spray = new ParticleSpray(vpos, vecOutDir.scale(success ? 1.0 : 1.5), success ? 0.1 : 0.5,
Mth.PI / (success ? 4 : 2), success ? 30 : 100);
spray.sprayParticles(serverLevel,
success ? this.colorizer : new FrozenColorizer(new ItemStack(HexItems.DYE_COLORIZERS.get(DyeColor.RED)),
this.activator));
}
var pitch = 1f;
var sound = HexSounds.SPELL_CIRCLE_FAIL;
if (success) {
sound = HexSounds.SPELL_CIRCLE_FIND_BLOCK;
// This is a good use of my time
var note = this.trackedBlocks.size() - 1;
var semitone = this.semitoneFromScale(note);
pitch = (float) Math.pow(2.0, (semitone - 8) / 12d);
}
level.playSound(null, vpos.x, vpos.y, vpos.z, sound, SoundSource.BLOCKS, 1f, pitch);
}
protected void clearEnergized() {
if (this.trackedBlocks != null) {
for (var tracked : this.trackedBlocks) {
var bs = this.level.getBlockState(tracked);
if (bs.getBlock() instanceof BlockCircleComponent) {
this.level.setBlockAndUpdate(tracked, bs.setValue(BlockCircleComponent.ENERGIZED, false));
}
}
}
}
protected void stopCasting() {
clearEnergized();
this.activator = null;
this.nextBlock = null;
this.trackedBlocks = null;
this.foundAll = false;
// without this check, breaking the block will just immediately replace it with
// the new unenergized state
if (this.level.getBlockState(this.getBlockPos()).getBlock() instanceof BlockAbstractImpetus) {
this.level.setBlockAndUpdate(this.getBlockPos(),
this.getBlockState().setValue(BlockCircleComponent.ENERGIZED, false));
}
}
@Nullable
protected Player getPlayer() {
return this.level.getPlayerByUUID(this.activator);
}
protected int getTickSpeed() {
if (this.trackedBlocks == null) {
return 10;
} else {
return Math.max(2, 10 - trackedBlocks.size() / 3);
}
}
protected int semitoneFromScale(int note) {
var blockBelow = this.level.getBlockState(this.getBlockPos().below());
var scale = MAJOR_SCALE;
if (blockBelow.is(Blocks.CRYING_OBSIDIAN)) {
scale = MINOR_SCALE;
} else if (blockBelow.is(BlockTags.DOORS) || blockBelow.is(BlockTags.TRAPDOORS)) {
scale = DORIAN_SCALE;
} else if (blockBelow.is(Blocks.PISTON) || blockBelow.is(Blocks.STICKY_PISTON)) {
scale = MIXOLYDIAN_SCALE;
} else if (blockBelow.is(Blocks.BLUE_WOOL)
|| blockBelow.is(Blocks.BLUE_CONCRETE) || blockBelow.is(Blocks.BLUE_CONCRETE_POWDER)
|| blockBelow.is(Blocks.BLUE_TERRACOTTA) || blockBelow.is(Blocks.BLUE_GLAZED_TERRACOTTA)
|| blockBelow.is(Blocks.BLUE_STAINED_GLASS) || blockBelow.is(Blocks.BLUE_STAINED_GLASS_PANE)) {
scale = BLUES_SCALE;
} else if (blockBelow.is(Blocks.BONE_BLOCK)) {
scale = BAD_TIME;
} else if (blockBelow.is(Blocks.COMPOSTER)) {
scale = SUSSY_BAKA;
}
note = Mth.clamp(note, 0, scale.length - 1);
return scale[note];
}
// this is a good use of my time
private static final int[] MAJOR_SCALE = {0, 2, 4, 5, 7, 9, 11, 12};
private static final int[] MINOR_SCALE = {0, 2, 3, 5, 7, 8, 11, 12};
private static final int[] DORIAN_SCALE = {0, 2, 3, 5, 7, 9, 10, 12};
private static final int[] MIXOLYDIAN_SCALE = {0, 2, 4, 5, 7, 9, 10, 12};
private static final int[] BLUES_SCALE = {0, 3, 5, 6, 7, 10, 12};
private static final int[] BAD_TIME = {0, 0, 12, 7, 6, 5, 3, 0, 3, 5};
private static final int[] SUSSY_BAKA = {5, 8, 10, 11, 10, 8, 5, 3, 7, 5};
private static final int[] SLOTS = {0};
@Override
public int[] getSlotsForFace(Direction var1) {
return SLOTS;
}
@Override
public boolean canPlaceItemThroughFace(int index, ItemStack stack, @Nullable Direction dir) {
return this.canPlaceItem(index, stack);
}
@Override
public boolean canTakeItemThroughFace(int var1, ItemStack var2, Direction var3) {
return false;
}
@Override
public int getContainerSize() {
return 1;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public ItemStack getItem(int index) {
return ItemStack.EMPTY.copy();
}
@Override
public ItemStack removeItem(int index, int count) {
return ItemStack.EMPTY.copy();
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return ItemStack.EMPTY.copy();
}
@Override
public void setItem(int index, ItemStack stack) {
insertMedia(stack);
}
@Override
public boolean stillValid(Player player) {
return false;
}
@Override
public void clearContent() {
// NO-OP
}
@Override
public boolean canPlaceItem(int index, ItemStack stack) {
if (remainingMediaCapacity() == 0) {
return false;
}
if (stack.is(HexItems.CREATIVE_UNLOCKER)) {
return true;
}
var mediamount = extractMediaFromItem(stack, true);
return mediamount > 0;
}
public int extractMediaFromItem(ItemStack stack, boolean simulate) {
if (this.media < 0) {
return 0;
}
return MediaHelper.extractMedia(stack, remainingMediaCapacity(), true, simulate);
}
public void insertMedia(ItemStack stack) {
if (getMedia() >= 0 && !stack.isEmpty() && stack.getItem() == HexItems.CREATIVE_UNLOCKER) {
setInfiniteMedia();
stack.shrink(1);
} else {
var mediamount = extractMediaFromItem(stack, false);
if (mediamount > 0) {
this.media = Math.min(mediamount + media, MAX_CAPACITY);
this.sync();
}
}
}
public void setInfiniteMedia() {
this.media = -1;
this.sync();
}
public int remainingMediaCapacity() {
if (this.media < 0) {
return 0;
}
return Math.max(0, MAX_CAPACITY - this.media);
}
}

View file

@ -232,7 +232,7 @@ fun List<Iota>.getIntBetween(idx: Int, min: Int, max: Int, argc: Int = 0): Int {
return rounded
}
}
throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "double.between", min, max)
throw MishapInvalidIota.of(x, if (argc == 0) idx else argc - (idx + 1), "int.between", min, max)
}
fun List<Iota>.getBlockPos(idx: Int, argc: Int = 0): BlockPos {

View file

@ -0,0 +1,401 @@
package at.petrak.hexcasting.api.casting.circles;
import at.petrak.hexcasting.api.block.HexBlockEntity;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.misc.MediaConstants;
import at.petrak.hexcasting.api.utils.MediaHelper;
import at.petrak.hexcasting.common.items.magic.ItemCreativeUnlocker;
import at.petrak.hexcasting.common.lib.HexItems;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
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.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.WorldlyContainer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat;
import java.util.List;
/**
* Default impl for an impetus, not tecnically necessary but I'm exposing it for ease of use
* <p>
* This does assume a great deal so you might have to re-implement a lot of this yourself if you
* wanna do something wild and new
*/
public abstract class BlockEntityAbstractImpetus extends HexBlockEntity implements WorldlyContainer {
private static final DecimalFormat DUST_AMOUNT = new DecimalFormat("###,###.##");
private static final long MAX_CAPACITY = 9_000_000_000_000_000_000L;
public static final String
TAG_EXECUTION_STATE = "executor",
TAG_MEDIA = "media",
TAG_ERROR_MSG = "errorMsg",
TAG_ERROR_DISPLAY = "errorDisplay";
// We might try to load the executor in loadModData when the level doesn't exist yet,
// so save the tag and load it lazy
@Nullable CompoundTag lazyExecutionState;
@Nullable
protected CircleExecutionState executionState;
protected long media = 0;
// these are null together
@Nullable
protected Component errorMsg = null;
@Nullable
protected ItemStack errorDisplay = null;
public BlockEntityAbstractImpetus(BlockEntityType<?> pType, BlockPos pWorldPosition, BlockState pBlockState) {
super(pType, pWorldPosition, pBlockState);
}
public Direction getStartDirection() {
return this.getBlockState().getValue(BlockStateProperties.FACING);
}
@Nullable
public Component getErrorMsg() {
return errorMsg;
}
public void clearError() {
this.errorMsg = null;
this.errorDisplay = null;
}
public void postError(Component error, ItemStack display) {
this.errorMsg = error;
this.errorDisplay = display;
}
public void postMishap(Component mishapDisplay) {
this.errorMsg = mishapDisplay;
this.errorDisplay = new ItemStack(Items.MUSIC_DISC_11);
}
//region execution
public void tickExecution() {
if (this.level == null)
return;
this.setChanged();
var state = this.getExecutionState();
if (state == null) {
return;
}
var shouldContinue = state.tick(this);
if (!shouldContinue) {
this.endExecution();
this.executionState = null;
}
else
this.level.scheduleTick(this.getBlockPos(), this.getBlockState().getBlock(), state.getTickSpeed());
}
public void endExecution() {
if (this.executionState == null)
return;
this.executionState.endExecution(this);
}
/**
* ONLY CALL THIS WHEN YOU KNOW THE WORLD EXISTS AND ON THE SERVER, lazy-loads it
*/
public @Nullable CircleExecutionState getExecutionState() {
if (this.level == null) {
throw new IllegalStateException("didn't you read the doc comment, don't call this if the level is null");
}
if (this.executionState != null)
return this.executionState;
if (this.lazyExecutionState != null)
this.executionState = CircleExecutionState.load(this.lazyExecutionState, (ServerLevel) this.level);
return this.executionState;
}
public void startExecution(@Nullable ServerPlayer player) {
if (this.level == null)
return; // TODO: error here?
if (this.level.isClientSide)
return; // TODO: error here?
if (this.executionState != null) {
return;
}
this.executionState = CircleExecutionState.createNew(this, player);
if (this.executionState == null)
return;
var serverLevel = (ServerLevel) this.level;
serverLevel.scheduleTick(this.getBlockPos(), this.getBlockState().getBlock(), this.executionState.getTickSpeed());
serverLevel.setBlockAndUpdate(this.getBlockPos(), this.getBlockState().setValue(BlockCircleComponent.ENERGIZED, true));
}
@Contract(pure = true)
protected static AABB getBounds(List<BlockPos> poses) {
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int minZ = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
int maxZ = Integer.MIN_VALUE;
for (var pos : poses) {
if (pos.getX() < minX) {
minX = pos.getX();
}
if (pos.getY() < minY) {
minY = pos.getY();
}
if (pos.getZ() < minZ) {
minZ = pos.getZ();
}
if (pos.getX() > maxX) {
maxX = pos.getX();
}
if (pos.getY() > maxY) {
maxY = pos.getY();
}
if (pos.getZ() > maxZ) {
maxZ = pos.getZ();
}
}
return new AABB(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1);
}
//endregion
//region media handling
public long getMedia() {
return this.media;
}
public void setMedia(long media) {
this.media = media;
}
public long extractMediaFromInsertedItem(ItemStack stack, boolean simulate) {
if (this.media < 0) {
return 0;
}
return MediaHelper.extractMedia(stack, remainingMediaCapacity(), true, simulate);
}
public void insertMedia(ItemStack stack) {
if (getMedia() >= 0 && !stack.isEmpty() && stack.getItem() == HexItems.CREATIVE_UNLOCKER) {
setInfiniteMedia();
stack.shrink(1);
} else {
var mediamount = extractMediaFromInsertedItem(stack, false);
if (mediamount > 0) {
this.media = Math.min(mediamount + media, MAX_CAPACITY);
this.sync();
}
}
}
public void setInfiniteMedia() {
this.media = -1;
this.sync();
}
public long remainingMediaCapacity() {
if (this.media < 0) {
return 0;
}
return Math.max(0, MAX_CAPACITY - this.media);
}
//endregion
@Override
protected void saveModData(CompoundTag tag) {
if (this.executionState != null) {
tag.put(TAG_EXECUTION_STATE, this.executionState.save());
}
tag.putLong(TAG_MEDIA, this.media);
if (this.errorMsg != null && this.errorDisplay != null) {
tag.putString(TAG_ERROR_MSG, Component.Serializer.toJson(this.errorMsg));
var itemTag = new CompoundTag();
this.errorDisplay.save(itemTag);
tag.put(TAG_ERROR_DISPLAY, itemTag);
}
}
@Override
protected void loadModData(CompoundTag tag) {
this.executionState = null;
if (tag.contains(TAG_EXECUTION_STATE, Tag.TAG_COMPOUND)) {
this.lazyExecutionState = tag.getCompound(TAG_EXECUTION_STATE);
} else {
this.lazyExecutionState = null;
}
if (tag.contains(TAG_MEDIA, Tag.TAG_INT)) {
this.media = tag.getInt(TAG_MEDIA);
} else if (tag.contains(TAG_MEDIA, Tag.TAG_LONG)) {
this.media = tag.getLong(TAG_MEDIA);
}
}
public void applyScryingLensOverlay(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos, Player observer, Level world, Direction hitFace) {
if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) {
if (beai.getMedia() < 0) {
lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), ItemCreativeUnlocker.infiniteMedia(world)));
} else {
var dustCount = (float) beai.getMedia() / (float) MediaConstants.DUST_UNIT;
var dustCmp = Component.translatable("hexcasting.tooltip.media",
DUST_AMOUNT.format(dustCount));
lines.add(new Pair<>(new ItemStack(HexItems.AMETHYST_DUST), dustCmp));
}
if (this.errorMsg != null && this.errorDisplay != null) {
lines.add(new Pair<>(this.errorDisplay, this.errorMsg));
}
}
}
//region music
protected int semitoneFromScale(int note) {
var blockBelow = this.level.getBlockState(this.getBlockPos().below());
var scale = MAJOR_SCALE;
if (blockBelow.is(Blocks.CRYING_OBSIDIAN)) {
scale = MINOR_SCALE;
} else if (blockBelow.is(BlockTags.DOORS) || blockBelow.is(BlockTags.TRAPDOORS)) {
scale = DORIAN_SCALE;
} else if (blockBelow.is(Blocks.PISTON) || blockBelow.is(Blocks.STICKY_PISTON)) {
scale = MIXOLYDIAN_SCALE;
} else if (blockBelow.is(Blocks.BLUE_WOOL)
|| blockBelow.is(Blocks.BLUE_CONCRETE) || blockBelow.is(Blocks.BLUE_CONCRETE_POWDER)
|| blockBelow.is(Blocks.BLUE_TERRACOTTA) || blockBelow.is(Blocks.BLUE_GLAZED_TERRACOTTA)
|| blockBelow.is(Blocks.BLUE_STAINED_GLASS) || blockBelow.is(Blocks.BLUE_STAINED_GLASS_PANE)) {
scale = BLUES_SCALE;
} else if (blockBelow.is(Blocks.BONE_BLOCK)) {
scale = BAD_TIME;
} else if (blockBelow.is(Blocks.COMPOSTER)) {
scale = SUSSY_BAKA;
}
note = Mth.clamp(note, 0, scale.length - 1);
return scale[note];
}
// this is a good use of my time
private static final int[] MAJOR_SCALE = {0, 2, 4, 5, 7, 9, 11, 12};
private static final int[] MINOR_SCALE = {0, 2, 3, 5, 7, 8, 11, 12};
private static final int[] DORIAN_SCALE = {0, 2, 3, 5, 7, 9, 10, 12};
private static final int[] MIXOLYDIAN_SCALE = {0, 2, 4, 5, 7, 9, 10, 12};
private static final int[] BLUES_SCALE = {0, 3, 5, 6, 7, 10, 12};
private static final int[] BAD_TIME = {0, 0, 12, 7, 6, 5, 3, 0, 3, 5};
private static final int[] SUSSY_BAKA = {5, 8, 10, 11, 10, 8, 5, 3, 7, 5};
//endregion
//region item handler contract stuff
private static final int[] SLOTS = {0};
@Override
public int[] getSlotsForFace(Direction var1) {
return SLOTS;
}
@Override
public boolean canPlaceItemThroughFace(int index, ItemStack stack, @Nullable Direction dir) {
return this.canPlaceItem(index, stack);
}
@Override
public boolean canTakeItemThroughFace(int var1, ItemStack var2, Direction var3) {
return false;
}
@Override
public int getContainerSize() {
return 1;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public ItemStack getItem(int index) {
return ItemStack.EMPTY.copy();
}
@Override
public ItemStack removeItem(int index, int count) {
return ItemStack.EMPTY.copy();
}
@Override
public ItemStack removeItemNoUpdate(int index) {
return ItemStack.EMPTY.copy();
}
@Override
public void setItem(int index, ItemStack stack) {
insertMedia(stack);
}
@Override
public boolean stillValid(Player player) {
return false;
}
@Override
public void clearContent() {
// NO-OP
}
@Override
public boolean canPlaceItem(int index, ItemStack stack) {
if (remainingMediaCapacity() == 0) {
return false;
}
if (stack.is(HexItems.CREATIVE_UNLOCKER)) {
return true;
}
var mediamount = extractMediaFromInsertedItem(stack, true);
return mediamount > 0;
}
//endregion
}

View file

@ -0,0 +1,271 @@
package at.petrak.hexcasting.api.casting.circles;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.lib.HexItems;
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.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
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.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* See {@link BlockEntityAbstractImpetus}, this is what's stored in it
*/
public class CircleExecutionState {
public static final String
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_COLORIZER = "colorizer";
// Does contain the starting impetus
public final Set<BlockPos> knownPositions;
public final List<BlockPos> reachedPositions;
public BlockPos currentPos;
public Direction enteredFrom;
public CastingImage currentImage;
public @Nullable UUID caster;
public FrozenColorizer colorizer;
public final AABB bounds;
protected CircleExecutionState(Set<BlockPos> knownPositions, List<BlockPos> reachedPositions,
BlockPos currentPos, Direction enteredFrom,
CastingImage currentImage, @Nullable UUID caster, FrozenColorizer colorizer) {
this.knownPositions = knownPositions;
this.reachedPositions = reachedPositions;
this.currentPos = currentPos;
this.enteredFrom = enteredFrom;
this.currentImage = currentImage;
this.caster = caster;
this.colorizer = colorizer;
this.bounds = BlockEntityAbstractImpetus.getBounds(new ArrayList<>(this.knownPositions));
}
// Return null if the circle does not close.
public static @Nullable CircleExecutionState createNew(BlockEntityAbstractImpetus impetus, @Nullable ServerPlayer caster) {
var level = (ServerLevel) impetus.getLevel();
if (level == null)
return null;
// Flood fill! Just like VCC all over again.
// this contains tentative positions and directions entered from
var todo = new Stack<Pair<Direction, BlockPos>>();
todo.add(Pair.of(impetus.getStartDirection(), impetus.getBlockPos().relative(impetus.getStartDirection())));
var seenPositions = new HashSet<BlockPos>();
var seenGoodPositions = new ArrayList<BlockPos>();
while (!todo.isEmpty()) {
var pair = todo.pop();
var enterDir = pair.getFirst();
var herePos = pair.getSecond();
if (seenPositions.add(herePos)) {
// it's new
var hereBs = level.getBlockState(herePos);
if (!(hereBs.getBlock() instanceof ICircleComponent cmp)) {
continue;
}
if (!cmp.canEnterFromDirection(enterDir, herePos, hereBs, level)) {
continue;
}
seenGoodPositions.add(herePos);
var outs = cmp.possibleExitDirections(herePos, hereBs, level);
for (var out : outs) {
todo.add(Pair.of(out, herePos.relative(out)));
}
}
}
if (!seenPositions.contains(impetus.getBlockPos()) || seenGoodPositions.isEmpty()) {
return null;
}
var knownPositions = new HashSet<>(seenGoodPositions);
var reachedPositions = new ArrayList<BlockPos>();
reachedPositions.add(impetus.getBlockPos());
var start = seenGoodPositions.get(0);
if (caster == null) {
var colorizer = new FrozenColorizer(new ItemStack(HexItems.DYE_COLORIZERS.get(DyeColor.PURPLE)), Util.NIL_UUID);
return new CircleExecutionState(knownPositions, reachedPositions, start, impetus.getStartDirection(), new CastingImage(), null, colorizer);
} else {
var colorizer = HexAPI.instance().getColorizer(caster);
return new CircleExecutionState(knownPositions, reachedPositions, start, impetus.getStartDirection(), new CastingImage(), caster.getUUID(), colorizer);
}
}
public CompoundTag save() {
var out = new CompoundTag();
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));
}
out.put(TAG_REACHED_POSITIONS, reachedTag);
out.put(TAG_CURRENT_POS, NbtUtils.writeBlockPos(this.currentPos));
out.putByte(TAG_ENTERED_FROM, (byte) this.enteredFrom.ordinal());
out.put(TAG_IMAGE, this.currentImage.serializeToNbt());
if (this.caster != null)
out.putUUID(TAG_CASTER, this.caster);
out.put(TAG_COLORIZER, this.colorizer.serializeToNBT());
return out;
}
public static CircleExecutionState load(CompoundTag nbt, ServerLevel world) {
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 reachedTag = nbt.getList(TAG_REACHED_POSITIONS, Tag.TAG_COMPOUND);
for (var tag : reachedTag) {
reachedPositions.add(NbtUtils.readBlockPos(HexUtils.downcast(tag, CompoundTag.TYPE)));
}
var currentPos = NbtUtils.readBlockPos(nbt.getCompound(TAG_CURRENT_POS));
var enteredFrom = Direction.values()[nbt.getByte(TAG_ENTERED_FROM)];
var image = CastingImage.loadFromNbt(nbt.getCompound(TAG_IMAGE), world);
UUID caster = null;
if (nbt.hasUUID(TAG_CASTER))
caster = nbt.getUUID(TAG_CASTER);
FrozenColorizer colorizer = FrozenColorizer.fromNBT(nbt.getCompound(TAG_COLORIZER));
return new CircleExecutionState(knownPositions, reachedPositions, currentPos, enteredFrom, image, caster, colorizer);
}
/**
* Update this, also mutates the impetus.
* <p>
* Returns whether to continue.
*/
public boolean tick(BlockEntityAbstractImpetus impetus) {
var world = (ServerLevel) impetus.getLevel();
if (world == null)
return true; // if the world is null, try again next tick.
ServerPlayer caster = null;
if (this.caster != null && world.getEntity(this.caster) instanceof ServerPlayer player)
caster = player;
var env = new CircleCastEnv(world, impetus.getBlockPos(), impetus.getStartDirection(), caster, this.bounds);
var executorBlockState = world.getBlockState(this.currentPos);
if (!(executorBlockState.getBlock() instanceof ICircleComponent executor)) {
// TODO: notification of the error?
ICircleComponent.sfx(this.currentPos, executorBlockState, world, Objects.requireNonNull(env.getImpetus()), false);
return false;
}
executorBlockState = executor.startEnergized(this.currentPos, executorBlockState, world);
this.reachedPositions.add(this.currentPos);
boolean halt = false;
var ctrl = executor.acceptControlFlow(this.currentImage, env, this.enteredFrom, this.currentPos,
executorBlockState, world);
if (ctrl instanceof ICircleComponent.ControlFlow.Stop) {
// acceptControlFlow should have already posted the error
halt = true;
} else if (ctrl instanceof ICircleComponent.ControlFlow.Continue cont) {
Pair<BlockPos, Direction> found = null;
for (var exit : cont.exits) {
var there = world.getBlockState(exit.getFirst());
if (there.getBlock() instanceof ICircleComponent cc
&& cc.canEnterFromDirection(exit.getSecond(), exit.getFirst(), there, world)) {
if (found != null) {
// oh no!
impetus.postError(
Component.translatable("hexcasting.circles.many_exits",
Component.literal(this.currentPos.toShortString()).withStyle(ChatFormatting.RED)),
new ItemStack(Items.COMPASS));
ICircleComponent.sfx(this.currentPos, executorBlockState, world, Objects.requireNonNull(env.getImpetus()), false);
halt = true;
break;
} else {
found = exit;
}
}
}
if (found == null) {
// will never enter here if there were too many because found will have been set
impetus.postError(
Component.translatable("hexcasting.circles.no_exits",
Component.literal(this.currentPos.toShortString()).withStyle(ChatFormatting.RED)),
new ItemStack(Items.OAK_SIGN));
ICircleComponent.sfx(this.currentPos, executorBlockState, world, Objects.requireNonNull(env.getImpetus()), false);
halt = true;
} else {
// A single valid exit position has been found.
ICircleComponent.sfx(this.currentPos, executorBlockState, world, Objects.requireNonNull(env.getImpetus()), true);
currentPos = found.getFirst();
enteredFrom = found.getSecond();
currentImage = cont.update;
}
}
return !halt;
}
/**
* 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);
}
public void endExecution(BlockEntityAbstractImpetus impetus) {
var world = (ServerLevel) impetus.getLevel();
if (world == null)
return; // TODO: error here?
for (var pos : this.reachedPositions) {
var there = world.getBlockState(pos);
if (there.getBlock() instanceof ICircleComponent cc) {
cc.endEnergized(pos, there, world);
}
}
}
}

View file

@ -0,0 +1,155 @@
package at.petrak.hexcasting.api.casting.circles;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.casting.ParticleSpray;
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.HexSounds;
import com.mojang.datafixers.util.Pair;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Contract;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
/**
* Implement this on a block to make circles interact with it.
* <p>
* This is its own interface so you can have your blocks subclass something else, and to avoid enormous
* files. The mod doesn't check for the interface on anything but blocks.
*/
public interface ICircleComponent {
/**
* The heart of the interface! Functionally modify the casting environment.
* <p>
* With the new update you can have the side effects happen inline. In fact, you have to have the side effects
* happen inline.
* <p>
* Also, return a list of directions that the control flow can exit this block in.
* The circle environment will mishap if not exactly 1 of the returned directions can be accepted from.
*/
ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Direction enterDir, BlockPos pos,
BlockState bs, ServerLevel world);
/**
* Can this component get transferred to from a block coming in from that direction, with the given normal?
*/
@Contract(pure = true)
boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world);
/**
* This determines the directions the control flow <em>can</em> exit from. It's called at the beginning of execution
* to see if the circle actually forms a loop.
* <p>
* For most blocks, this should be the same as returned from {@link ICircleComponent#acceptControlFlow}
* Things like directrices might return otherwise. Whatever is returned when controlling flow must be a subset of
* this set.
*/
@Contract(pure = true)
EnumSet<Direction> possibleExitDirections(BlockPos pos, BlockState bs, Level world);
/**
* Given the current position and a direction, return a pair of the new position after a step
* in that direction, along with the direction (this is a helper function for creating
* {@link ICircleComponent.ControlFlow}s.
*/
@Contract(pure = true)
default Pair<BlockPos, Direction> exitPositionFromDirection(BlockPos pos, Direction dir) {
return Pair.of(pos.offset(dir.getStepX(), dir.getStepY(), dir.getStepZ()), dir);
}
/**
* Start the {@link ICircleComponent} at the given position glowing. Returns the new state
* of the given block.
* // TODO: determine if this should just be in {@link ICircleComponent#acceptControlFlow(CastingImage, CircleCastEnv, Direction, BlockPos, BlockState, ServerLevel)}.
*/
BlockState startEnergized(BlockPos pos, BlockState bs, Level world);
/**
* Returns whether the {@link ICircleComponent} at the given position is energized.
*/
boolean isEnergized(BlockPos pos, BlockState bs, Level world);
/**
* End the {@link ICircleComponent} at the given position glowing. Returns the new state of
* the given block.
*/
BlockState endEnergized(BlockPos pos, BlockState bs, Level world);
static void sfx(BlockPos pos, BlockState bs, Level world, BlockEntityAbstractImpetus impetus, boolean success) {
Vec3 vpos;
Vec3 vecOutDir;
FrozenColorizer colorizer;
UUID activator = Util.NIL_UUID;
if (impetus.getExecutionState() != null && impetus.getExecutionState().caster != null)
activator = impetus.getExecutionState().caster;
if (impetus.getExecutionState() == null)
colorizer = new FrozenColorizer(new ItemStack(HexItems.DYE_COLORIZERS.get(DyeColor.RED)), activator);
else
colorizer = impetus.getExecutionState().colorizer;
if (bs.getBlock() instanceof BlockCircleComponent bcc) {
var outDir = bcc.normalDir(pos, bs, world);
var height = bcc.particleHeight(pos, bs, world);
vecOutDir = new Vec3(outDir.step());
vpos = Vec3.atCenterOf(pos).add(vecOutDir.scale(height));
} else {
// we probably are doing this because it's an error and we removed a block
vpos = Vec3.atCenterOf(pos);
vecOutDir = new Vec3(0, 0, 0);
}
if (world instanceof ServerLevel serverLevel) {
var spray = new ParticleSpray(vpos, vecOutDir.scale(success ? 1.0 : 1.5), success ? 0.1 : 0.5,
Mth.PI / (success ? 4 : 2), success ? 30 : 100);
spray.sprayParticles(serverLevel,
success ? colorizer : new FrozenColorizer(new ItemStack(HexItems.DYE_COLORIZERS.get(DyeColor.RED)),
activator));
}
var pitch = 1f;
var sound = HexSounds.SPELL_CIRCLE_FAIL;
if (success) {
sound = HexSounds.SPELL_CIRCLE_FIND_BLOCK;
var state = impetus.getExecutionState();
// This is a good use of my time
var note = state.reachedPositions.size() - 1;
var semitone = impetus.semitoneFromScale(note);
pitch = (float) Math.pow(2.0, (semitone - 8) / 12d);
}
world.playSound(null, vpos.x, vpos.y, vpos.z, sound, SoundSource.BLOCKS, 1f, pitch);
}
abstract sealed class ControlFlow {
public static final class Continue extends ControlFlow {
public final CastingImage update;
public final List<Pair<BlockPos, Direction>> exits;
public Continue(CastingImage update, List<Pair<BlockPos, Direction>> exits) {
this.update = update;
this.exits = exits;
}
}
public static final class Stop extends ControlFlow {
public Stop() {
}
}
}
}

View file

@ -104,6 +104,11 @@ public abstract class CastingEnvironment {
*/
public abstract boolean isVecInRange(Vec3 vec);
/**
* Return whether the caster can edit blocks at the given permission (i.e. not adventure mode, etc.)
*/
public abstract boolean hasEditPermissionsAt(BlockPos vec);
public final boolean isVecInWorld(Vec3 vec) {
return this.world.isInWorldBounds(new BlockPos(vec))
&& this.world.getWorldBorder().isWithinBounds(vec.x, vec.z, 0.5);
@ -122,7 +127,7 @@ public abstract class CastingEnvironment {
*/
public final void assertVecInRange(Vec3 vec) throws MishapLocationTooFarAway {
this.assertVecInWorld(vec);
if (this.isVecInRange(vec)) {
if (!this.isVecInRange(vec)) {
throw new MishapLocationTooFarAway(vec, "too_far");
}
}
@ -132,8 +137,7 @@ public abstract class CastingEnvironment {
}
public final boolean canEditBlockAt(BlockPos vec) {
// TODO winfy: fill this in
return false;
return this.isVecInRange(Vec3.atCenterOf(vec)) && this.hasEditPermissionsAt(vec);
}
/**
@ -143,7 +147,7 @@ public abstract class CastingEnvironment {
if (!this.isVecInWorld(e.position())) {
throw new MishapEntityTooFarAway(e);
}
if (this.isVecInRange(e.position())) {
if (!this.isVecInRange(e.position())) {
throw new MishapEntityTooFarAway(e);
}
}
@ -157,10 +161,10 @@ public abstract class CastingEnvironment {
}
}
public abstract InteractionHand castingHand();
public abstract InteractionHand getCastingHand();
public InteractionHand otherHand() {
return HexUtils.otherHand(this.castingHand());
public InteractionHand getOtherHand() {
return HexUtils.otherHand(this.getCastingHand());
}
/**
@ -176,6 +180,11 @@ public abstract class CastingEnvironment {
*/
protected abstract List<ItemStack> getUsableStacks(StackDiscoveryMode mode);
/**
* Get the primary/secondary item stacks this env can use (i.e. main hand and offhand for the player).
*/
protected abstract List<HeldItemInfo> getPrimaryStacks();
/**
* Return the slot from which to take blocks and items.
*/
@ -191,12 +200,12 @@ public abstract class CastingEnvironment {
return null;
}
public static record HeldItemInfo(ItemStack stack, InteractionHand hand) {
public static record HeldItemInfo(ItemStack stack, @Nullable InteractionHand hand) {
public ItemStack component1() {
return stack;
}
public InteractionHand component2() {
public @Nullable InteractionHand component2() {
return hand;
}
}
@ -205,9 +214,14 @@ public abstract class CastingEnvironment {
* Return the slot from which to take blocks and items.
*/
// TODO winfy: resolve the null here
// @Nullable
public HeldItemInfo getHeldItemToOperateOn(Predicate<ItemStack> stackOk) {
// TODO winfy: return something properly
public @Nullable HeldItemInfo getHeldItemToOperateOn(Predicate<ItemStack> stackOk) {
var stacks = this.getPrimaryStacks();
for (HeldItemInfo stack : stacks) {
if (stackOk.test(stack.stack)) {
return stack;
}
}
return null;
}

View file

@ -0,0 +1,169 @@
package at.petrak.hexcasting.api.casting.eval.env;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.casting.ParticleSpray;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.eval.CastResult;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.MishapEnvironment;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static at.petrak.hexcasting.api.casting.eval.env.PlayerBasedCastEnv.SENTINEL_RADIUS;
public class CircleCastEnv extends CastingEnvironment {
protected EvalSound sound = HexEvalSounds.NOTHING;
protected final BlockPos impetusLoc;
protected final Direction startDir;
protected final @Nullable ServerPlayer caster;
protected final AABB bounds;
public CircleCastEnv(ServerLevel world, BlockPos impetusLoc, Direction startDir, @Nullable ServerPlayer caster, AABB bounds) {
super(world);
this.impetusLoc = impetusLoc;
this.startDir = startDir;
this.caster = caster;
this.bounds = bounds;
}
@Override
public @Nullable ServerPlayer getCaster() {
return this.caster;
}
public @Nullable BlockEntityAbstractImpetus getImpetus() {
var entity = this.world.getBlockEntity(impetusLoc);
if (entity instanceof BlockEntityAbstractImpetus)
return (BlockEntityAbstractImpetus) entity;
return null;
}
public BlockPos getImpetusLoc() {
return impetusLoc;
}
public Direction getStartDir() {
return startDir;
}
@Override
public MishapEnvironment getMishapEnvironment() {
return new CircleMishapEnv(this.world, this.impetusLoc, this.startDir, this.caster, this.bounds);
}
@Override
public EvalSound getSoundType() {
return sound;
}
@Override
public void postExecution(CastResult result) {
this.sound = this.sound.greaterOf(result.getSound());
}
@Override
public Vec3 mishapSprayPos() {
return Vec3.atCenterOf(impetusLoc);
}
@Override
public long extractMedia(long cost) {
var entity = this.getImpetus();
if (entity == null)
return cost;
var mediaAvailable = entity.getMedia();
if (mediaAvailable < 0)
return 0;
long mediaToTake = Math.min(cost, mediaAvailable);
cost -= mediaToTake;
entity.setMedia(mediaAvailable - mediaToTake);
return cost;
}
@Override
public boolean isVecInRange(Vec3 vec) {
if (this.caster != null) {
var sentinel = HexAPI.instance().getSentinel(this.caster);
if (sentinel != null
&& sentinel.extendsRange()
&& this.caster.getLevel().dimension() == sentinel.dimension()
&& vec.distanceToSqr(sentinel.position()) <= SENTINEL_RADIUS * SENTINEL_RADIUS
) {
return true;
}
}
return this.bounds.contains(vec);
}
@Override
public boolean hasEditPermissionsAt(BlockPos vec) {
return true;
}
@Override
public InteractionHand getCastingHand() {
return InteractionHand.MAIN_HAND;
}
@Override
public ItemStack getAlternateItem() {
return ItemStack.EMPTY.copy(); // TODO: adjacent inventory/item frame?
}
@Override
protected List<ItemStack> getUsableStacks(StackDiscoveryMode mode) {
return new ArrayList<>(); // TODO: Could do something like get items in inventories adjacent to the circle?
}
@Override
protected List<HeldItemInfo> getPrimaryStacks() {
return List.of(); // TODO: Adjacent inv!
}
@Override
public FrozenColorizer getColorizer() {
var out = this.getColorizerFromImpetus();
if (out != null)
return out;
// TODO: colouriser from an adjacent inventory also?
return new FrozenColorizer(new ItemStack(HexItems.DYE_COLORIZERS.get(DyeColor.PURPLE)), Util.NIL_UUID);
}
private @Nullable FrozenColorizer getColorizerFromImpetus() {
var impetus = this.getImpetus();
if (impetus == null)
return null;
var state = impetus.getExecutionState();
if (state == null)
return null;
return state.colorizer;
}
@Override
public void produceParticles(ParticleSpray particles, FrozenColorizer colorizer) {
particles.sprayParticles(this.world, colorizer);
}
}

View file

@ -0,0 +1,55 @@
package at.petrak.hexcasting.api.casting.eval.env;
import at.petrak.hexcasting.api.casting.eval.MishapEnvironment;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class CircleMishapEnv extends MishapEnvironment {
protected final BlockPos impetusLoc;
protected final Direction startDir;
protected final @Nullable ServerPlayer caster;
protected final AABB bounds;
protected CircleMishapEnv(ServerLevel world, BlockPos impetusLoc, Direction startDir, @Nullable ServerPlayer caster, AABB bounds) {
super(world, null);
this.impetusLoc = impetusLoc;
this.startDir = startDir;
this.caster = caster;
this.bounds = bounds;
}
@Override
public void yeetHeldItemsTowards(Vec3 targetPos) {
}
@Override
public void dropHeldItems() {
}
@Override
public void drown() {
}
@Override
public void damage(float healthProportion) {
}
@Override
public void removeXp(int amount) {
}
@Override
public void blind(int ticks) {
}
}

View file

@ -1,4 +1,4 @@
package at.petrak.hexcasting.common.casting.env;
package at.petrak.hexcasting.api.casting.eval.env;
import at.petrak.hexcasting.api.casting.eval.CastResult;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
@ -34,6 +34,9 @@ public class PackagedItemCastEnv extends PlayerBasedCastEnv {
@Override
public long extractMedia(long costLeft) {
if (this.caster.isCreative())
return 0;
var casterStack = this.caster.getItemInHand(this.castingHand);
var casterHexHolder = IXplatAbstractions.INSTANCE.findHexHolder(casterStack);
var canCastFromInv = casterHexHolder.canDrawMediaFromInventory();
@ -54,7 +57,7 @@ public class PackagedItemCastEnv extends PlayerBasedCastEnv {
}
@Override
public InteractionHand castingHand() {
public InteractionHand getCastingHand() {
return this.castingHand;
}

View file

@ -1,4 +1,4 @@
package at.petrak.hexcasting.common.casting.env;
package at.petrak.hexcasting.api.casting.eval.env;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
@ -16,11 +16,13 @@ import at.petrak.hexcasting.api.mod.HexStatistics;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.api.utils.MediaHelper;
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
@ -102,6 +104,16 @@ public abstract class PlayerBasedCastEnv extends CastingEnvironment {
};
}
@Override
protected List<HeldItemInfo> getPrimaryStacks() {
var primaryItem = this.caster.getItemInHand(this.castingHand);
if (primaryItem.isEmpty())
primaryItem = ItemStack.EMPTY.copy();
return List.of(new HeldItemInfo(getAlternateItem(), this.getOtherHand()), new HeldItemInfo(primaryItem, this.castingHand));
}
@Override
public boolean isVecInRange(Vec3 vec) {
var sentinel = HexAPI.instance().getSentinel(this.caster);
@ -116,6 +128,11 @@ public abstract class PlayerBasedCastEnv extends CastingEnvironment {
return vec.distanceToSqr(this.caster.position()) <= AMBIT_RADIUS * AMBIT_RADIUS;
}
@Override
public boolean hasEditPermissionsAt(BlockPos vec) {
return this.caster.gameMode.getGameModeForPlayer() != GameType.ADVENTURE && this.world.mayInteract(this.caster, vec);
}
@Override
public ItemStack getAlternateItem() {
var otherHand = HexUtils.otherHand(this.castingHand);

View file

@ -1,4 +1,4 @@
package at.petrak.hexcasting.common.casting.env;
package at.petrak.hexcasting.api.casting.eval.env;
import at.petrak.hexcasting.api.casting.eval.MishapEnvironment;
import at.petrak.hexcasting.api.casting.mishaps.Mishap;

View file

@ -1,4 +1,4 @@
package at.petrak.hexcasting.common.casting.env;
package at.petrak.hexcasting.api.casting.eval.env;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.casting.eval.CastResult;
@ -20,8 +20,13 @@ import java.util.HashSet;
import java.util.List;
public class StaffCastEnv extends PlayerBasedCastEnv {
private final InteractionHand castingHand;
public StaffCastEnv(ServerPlayer caster, InteractionHand castingHand) {
super(caster, castingHand);
this.castingHand = castingHand;
}
@Override
@ -35,6 +40,9 @@ public class StaffCastEnv extends PlayerBasedCastEnv {
@Override
public long extractMedia(long cost) {
if (this.caster.isCreative())
return 0;
var canOvercast = this.canOvercast();
var remaining = this.extractMediaFromInventory(cost, canOvercast);
if (remaining > 0 && !canOvercast) {
@ -43,6 +51,11 @@ public class StaffCastEnv extends PlayerBasedCastEnv {
return remaining;
}
@Override
public InteractionHand getCastingHand() {
return castingHand;
}
@Override
public FrozenColorizer getColorizer() {
return HexAPI.instance().getColorizer(this.caster);
@ -73,18 +86,19 @@ public class StaffCastEnv extends PlayerBasedCastEnv {
sender.awardStat(HexStatistics.PATTERNS_DRAWN);
var vm = IXplatAbstractions.INSTANCE.getStaffcastVM(sender, msg.handUsed());
// every time we send a new pattern it'll be happening in a different tick, so reset here
// i don't think we can do this in the casting vm itself because it doesn't know if `queueAndExecuteIotas`
// is being called from the top level or not
vm.getImage().getUserData().remove(HexAPI.OP_COUNT_USERDATA);
ExecutionClientView clientInfo = vm.queueAndExecuteIota(new PatternIota(msg.pattern()), sender.getLevel());
ExecutionClientView clientInfo = vm.queueExecuteAndWrapIota(new PatternIota(msg.pattern()), sender.getLevel());
if (clientInfo.isStackClear()) {
IXplatAbstractions.INSTANCE.setStaffcastImage(sender, null);
IXplatAbstractions.INSTANCE.setPatterns(sender, List.of());
} else {
IXplatAbstractions.INSTANCE.setStaffcastImage(sender, vm);
IXplatAbstractions.INSTANCE.setStaffcastImage(sender, vm.getImage());
if (!resolvedPatterns.isEmpty()) {
resolvedPatterns.get(resolvedPatterns.size() - 1).setType(clientInfo.getResolutionType());
}

View file

@ -0,0 +1,5 @@
/**
* Default impls for some casting and mishap envs for your convenience and also so i can impl
* BlockEntityAbstractImpetus in api guilt-free
*/
package at.petrak.hexcasting.api.casting.eval.env;

View file

@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable;
*
* @param sound the actual sound file
* @param priority the priority of this sound. the sound with the highest priority in a given cast will be
* playd.
* played.
* shortcutMetacasting takes precedence over this.
*/
public record EvalSound(@Nullable SoundEvent sound, int priority) {

View file

@ -39,7 +39,6 @@ data class CastingImage private constructor(
const val TAG_PARENTHESIZED = "parenthesized"
const val TAG_ESCAPE_NEXT = "escape_next"
const val TAG_USERDATA = "userdata"
const val TAG_RAVENMIND = "ravenmind"
@JvmStatic
public fun loadFromNbt(tag: CompoundTag, world: ServerLevel): CastingImage {

View file

@ -1,7 +1,6 @@
package at.petrak.hexcasting.api.casting.eval.vm
import at.petrak.hexcasting.api.HexAPI
import at.petrak.hexcasting.api.casting.PatternShapeMatch
import at.petrak.hexcasting.api.casting.PatternShapeMatch.*
import at.petrak.hexcasting.api.casting.SpellList
import at.petrak.hexcasting.api.casting.eval.*
@ -13,14 +12,9 @@ import at.petrak.hexcasting.api.casting.iota.PatternIota
import at.petrak.hexcasting.api.casting.math.HexDir
import at.petrak.hexcasting.api.casting.math.HexPattern
import at.petrak.hexcasting.api.casting.mishaps.*
import at.petrak.hexcasting.api.mod.HexConfig
import at.petrak.hexcasting.api.mod.HexTags
import at.petrak.hexcasting.api.utils.*
import at.petrak.hexcasting.common.casting.PatternRegistryManifest
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
/**
@ -31,14 +25,15 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) {
/**
* Execute a single iota.
*/
fun queueAndExecuteIota(iota: Iota, world: ServerLevel): ExecutionClientView = queueAndExecuteIotas(listOf(iota), world)
fun queueExecuteAndWrapIota(iota: Iota, world: ServerLevel): ExecutionClientView = queueExecuteAndWrapIotas(listOf(iota), world)
/**
* The main entrypoint to the VM. Given a list of iotas, execute them in sequence, and return whatever the client
* needs to see.
*
* Mutates this
*/
fun queueAndExecuteIotas(iotas: List<Iota>, world: ServerLevel): ExecutionClientView {
fun queueExecuteAndWrapIotas(iotas: List<Iota>, world: ServerLevel): ExecutionClientView {
// Initialize the continuation stack to a single top-level eval for all iotas.
var continuation = SpellContinuation.Done.pushFrame(FrameEvaluate(SpellList.LList(0, iotas), false))
// Begin aggregating info
@ -105,22 +100,7 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) {
)
}
if (iota is PatternIota) {
return executePattern(iota.pattern, world, continuation)
} else {
return CastResult(
continuation,
null,
listOf(
OperatorSideEffect.DoMishap(
MishapUnescapedValue(iota),
Mishap.Context(HexPattern(HexDir.WEST), null)
)
), // Should never matter
ResolvedPatternType.INVALID,
HexEvalSounds.MISHAP
)
}
return iota.execute(this, world, continuation)
} catch (exception: Exception) {
// This means something very bad has happened
exception.printStackTrace()
@ -142,93 +122,6 @@ class CastingVM(var image: CastingImage, val env: CastingEnvironment) {
}
}
/**
* When the server gets a packet from the client with a new pattern,
* handle it functionally.
*/
private fun executePattern(newPat: HexPattern, world: ServerLevel, continuation: SpellContinuation): CastResult {
var castedName: Component? = null
try {
val lookup = PatternRegistryManifest.matchPattern(newPat, world, false)
this.env.precheckAction(lookup)
val action = if (lookup is Normal || lookup is PerWorld) {
val key = when (lookup) {
is Normal -> lookup.key
is PerWorld -> lookup.key
else -> throw IllegalStateException()
}
val reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.actionRegistry, key, HexTags.Actions.REQUIRES_ENLIGHTENMENT)
castedName = HexAPI.instance().getActionI18n(key, reqsEnlightenment)
IXplatAbstractions.INSTANCE.actionRegistry.get(key)!!.action
} else if (lookup is Special) {
castedName = lookup.handler.name
lookup.handler.act()
} else if (lookup is PatternShapeMatch.Nothing) {
throw MishapInvalidPattern()
} else throw IllegalStateException()
val opCount = if (this.image.userData.contains(HexAPI.OP_COUNT_USERDATA)) {
this.image.userData.getInt(HexAPI.OP_COUNT_USERDATA)
} else {
this.image.userData.putInt(HexAPI.OP_COUNT_USERDATA, 0)
0
}
if (opCount + 1 > HexConfig.server().maxOpCount()) {
throw MishapEvalTooMuch()
}
this.image.userData.putInt(HexAPI.OP_COUNT_USERDATA, opCount + 1)
val sideEffects = mutableListOf<OperatorSideEffect>()
var stack2: List<Iota>? = null
var cont2 = continuation
var userData2: CompoundTag? = null
val result = action.operate(
this.env,
this.image.stack.toMutableList(),
this.image.userData.copy(),
continuation
)
cont2 = result.newContinuation
stack2 = result.newStack
userData2 = result.newUserdata
// TODO parens also break prescience
sideEffects.addAll(result.sideEffects)
val hereFd = this.image
val fd = if (stack2 != null) {
hereFd.copy(
stack = stack2,
userData = userData2,
)
} else {
hereFd
}
return CastResult(
cont2,
fd,
sideEffects,
ResolvedPatternType.EVALUATED,
env.soundType,
)
} catch (mishap: Mishap) {
return CastResult(
continuation,
null,
listOf(OperatorSideEffect.DoMishap(mishap, Mishap.Context(newPat, castedName))),
mishap.resolutionType(env),
HexEvalSounds.MISHAP
)
}
}
/**
* Execute the side effects of a pattern, updating our aggregated info.
*/

View file

@ -54,7 +54,7 @@ data class FrameForEach(
val (stackTop, newImage, newCont) = if (data.nonEmpty) {
// Increment the evaluation depth,
// push the next datum to the top of the stack,
Triple(data.car, harness.image.incDepth(), continuation
Triple(data.car, harness.image, continuation
// put the next Thoth object back on the stack for the next Thoth cycle,
.pushFrame(FrameForEach(data.cdr, code, stack, acc))
// and prep the Thoth'd code block for evaluation.

View file

@ -1,9 +1,23 @@
package at.petrak.hexcasting.api.casting.iota;
import at.petrak.hexcasting.api.casting.eval.CastResult;
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType;
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation;
import at.petrak.hexcasting.api.casting.math.HexDir;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.api.casting.mishaps.Mishap;
import at.petrak.hexcasting.api.casting.mishaps.MishapUnescapedValue;
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public abstract class Iota {
@NotNull
@ -34,6 +48,34 @@ public abstract class Iota {
*/
abstract public @NotNull Tag serialize();
/**
* This method is called when this iota is executed (i.e. Hermes is run on a list containing it, unescaped).
* By default it will return a {@link CastResult} indicating an error has occurred.
*/
public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) {
return new CastResult(
continuation,
null,
List.of(
new OperatorSideEffect.DoMishap(
new MishapUnescapedValue(this),
new Mishap.Context(new HexPattern(HexDir.WEST, List.of()), null)
)
), // Should never matter
ResolvedPatternType.INVALID,
HexEvalSounds.MISHAP
);
}
/**
* This method is called to determine whether the iota is above the max serialisation depth/serialisation count limits. It should return every "iota" that is a subelement of this iota.
* For example, if you implemented a Map<Iota, Iota>, then it should be an iterable over the keys *and* values of the map. If you implemented a typed List<Double> iota for some reason, it would
* probably be a good idea to supply an iterable over those doubles mapped to double iotas.
*/
public @Nullable Iterable<Iota> subIotas() {
return null;
}
public Component display() {
return this.type.display(this.serialize());
}

View file

@ -91,11 +91,13 @@ public abstract class IotaType<T extends Iota> {
if (totalEltsFound >= HexIotaTypes.MAX_SERIALIZATION_TOTAL) {
return true; // too bad
}
if (iota instanceof ListIota subsublist) {
var subIotas = iota.subIotas();
if (subIotas != null) {
if (depth + 1 >= HexIotaTypes.MAX_SERIALIZATION_DEPTH) {
return true;
}
listsToExamine.addLast(new Pair<>(subsublist.getList(), depth + 1));
listsToExamine.addLast(new Pair<>(subIotas, depth + 1));
}
}
}
@ -105,7 +107,7 @@ public abstract class IotaType<T extends Iota> {
/**
* This method attempts to find the type from the {@code type} key.
* See {@link HexIotaTypes#serialize(Iota)} for the storage format.
* See {@link IotaType#serialize(Iota)} for the storage format.
*
* @return {@code null} if it cannot get the type.
*/

View file

@ -73,6 +73,11 @@ public class ListIota extends Iota {
return out;
}
@Override
public @Nullable Iterable<Iota> subIotas() {
return this.getList();
}
public static IotaType<ListIota> TYPE = new IotaType<>() {
@Nullable
@Override

View file

@ -1,16 +1,40 @@
package at.petrak.hexcasting.api.casting.iota;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.casting.ActionRegistryEntry;
import at.petrak.hexcasting.api.casting.PatternShapeMatch;
import at.petrak.hexcasting.api.casting.castables.Action;
import at.petrak.hexcasting.api.casting.eval.CastResult;
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType;
import at.petrak.hexcasting.api.casting.eval.sideeffects.OperatorSideEffect;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.api.casting.mishaps.Mishap;
import at.petrak.hexcasting.api.casting.mishaps.MishapEvalTooMuch;
import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidPattern;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.api.mod.HexTags;
import at.petrak.hexcasting.api.utils.HexUtils;
import at.petrak.hexcasting.common.casting.PatternRegistryManifest;
import at.petrak.hexcasting.common.lib.hex.HexEvalSounds;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static at.petrak.hexcasting.api.utils.HexUtils.isOfTag;
public class PatternIota extends Iota {
public PatternIota(@NotNull HexPattern pattern) {
super(HexIotaTypes.PATTERN, pattern);
@ -41,9 +65,82 @@ public class PatternIota extends Iota {
return this.getPattern().serializeToNBT();
}
@Override
public @NotNull CastResult execute(CastingVM vm, ServerLevel world, SpellContinuation continuation) {
@Nullable Component castedName = null;
try {
var lookup = PatternRegistryManifest.matchPattern(this.getPattern(), world, false);
vm.getEnv().precheckAction(lookup);
Action action;
if (lookup instanceof PatternShapeMatch.Normal || lookup instanceof PatternShapeMatch.PerWorld) {
ResourceKey<ActionRegistryEntry> key;
if (lookup instanceof PatternShapeMatch.Normal normal) {
key = normal.key;
} else {
PatternShapeMatch.PerWorld perWorld = (PatternShapeMatch.PerWorld) lookup;
key = perWorld.key;
}
var reqsEnlightenment = isOfTag(IXplatAbstractions.INSTANCE.getActionRegistry(), key, HexTags.Actions.REQUIRES_ENLIGHTENMENT);
castedName = HexAPI.instance().getActionI18n(key, reqsEnlightenment);
action = Objects.requireNonNull(IXplatAbstractions.INSTANCE.getActionRegistry().get(key)).action();
} else if (lookup instanceof PatternShapeMatch.Special special) {
castedName = special.handler.getName();
action = special.handler.act();
} else if (lookup instanceof PatternShapeMatch.Nothing) {
throw new MishapInvalidPattern();
} else throw new IllegalStateException();
var opCount = 0;
if (vm.getImage().getUserData().contains(HexAPI.OP_COUNT_USERDATA)) {
opCount = vm.getImage().getUserData().getInt(HexAPI.OP_COUNT_USERDATA);
} else
vm.getImage().getUserData().putInt(HexAPI.OP_COUNT_USERDATA, 0);
if (opCount + 1 > HexConfig.server().maxOpCount()) {
throw new MishapEvalTooMuch();
}
vm.getImage().getUserData().putInt(HexAPI.OP_COUNT_USERDATA, opCount + 1);
var result = action.operate(
vm.getEnv(),
new ArrayList<>(vm.getImage().getStack()),
vm.getImage().getUserData().copy(),
continuation
);
var cont2 = result.getNewContinuation();
var stack2 = result.getNewStack();
var userData2 = result.getNewUserdata();
// TODO parens also break prescience
var sideEffects = result.getSideEffects();
var hereFd = vm.getImage();
hereFd = hereFd.copy(stack2, hereFd.getParenCount(), hereFd.getParenthesized(), hereFd.getEscapeNext(), userData2);
return new CastResult(
cont2,
hereFd,
sideEffects,
ResolvedPatternType.EVALUATED,
vm.getEnv().getSoundType()
);
} catch (Mishap mishap) {
return new CastResult(
continuation,
null,
List.of(new OperatorSideEffect.DoMishap(mishap, new Mishap.Context(this.getPattern(), castedName))),
mishap.resolutionType(vm.getEnv()),
HexEvalSounds.MISHAP
);
}
}
public static IotaType<PatternIota> TYPE = new IotaType<>() {
@Nullable
@Override
public PatternIota deserialize(Tag tag, ServerLevel world) throws IllegalArgumentException {
return PatternIota.deserialize(tag);

View file

@ -9,7 +9,7 @@ import net.minecraft.world.InteractionHand
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
class MishapBadOffhandItem(val item: ItemStack, val hand: InteractionHand, val wanted: Component) : Mishap() {
class MishapBadOffhandItem(val item: ItemStack, val hand: InteractionHand?, val wanted: Component) : Mishap() {
override fun accentColor(ctx: CastingEnvironment, errorCtx: Context): FrozenColorizer =
dyeColor(DyeColor.BROWN)
@ -24,7 +24,7 @@ class MishapBadOffhandItem(val item: ItemStack, val hand: InteractionHand, val w
companion object {
@JvmStatic
fun of(item: ItemStack, hand: InteractionHand, stub: String, vararg args: Any): MishapBadOffhandItem {
fun of(item: ItemStack, hand: InteractionHand?, stub: String, vararg args: Any): MishapBadOffhandItem {
return MishapBadOffhandItem(item, hand, "hexcasting.mishap.bad_item.$stub".asTranslatedComponent(*args))
}
}

View file

@ -24,7 +24,7 @@ public interface HexHolderItem extends MediaHolderItem {
@Nullable
List<Iota> getHex(ItemStack stack, ServerLevel level);
void writeHex(ItemStack stack, List<Iota> program, int media);
void writeHex(ItemStack stack, List<Iota> program, long media);
void clearHex(ItemStack stack);
}

View file

@ -11,25 +11,25 @@ import org.jetbrains.annotations.ApiStatus;
*/
@ApiStatus.OverrideOnly
public interface MediaHolderItem {
int getMedia(ItemStack stack);
long getMedia(ItemStack stack);
int getMaxMedia(ItemStack stack);
long getMaxMedia(ItemStack stack);
void setMedia(ItemStack stack, int media);
void setMedia(ItemStack stack, long media);
boolean canProvideMedia(ItemStack stack);
boolean canRecharge(ItemStack stack);
default float getMediaFullness(ItemStack stack) {
int max = getMaxMedia(stack);
long max = getMaxMedia(stack);
if (max == 0) {
return 0;
}
return (float) getMedia(stack) / (float) max;
}
default int withdrawMedia(ItemStack stack, int cost, boolean simulate) {
default long withdrawMedia(ItemStack stack, long cost, boolean simulate) {
var mediaHere = getMedia(stack);
if (cost < 0) {
cost = mediaHere;
@ -41,9 +41,9 @@ public interface MediaHolderItem {
return Math.min(cost, mediaHere);
}
default int insertMedia(ItemStack stack, int amount, boolean simulate) {
default long insertMedia(ItemStack stack, long amount, boolean simulate) {
var mediaHere = getMedia(stack);
int emptySpace = getMaxMedia(stack) - mediaHere;
long emptySpace = getMaxMedia(stack) - mediaHere;
if (emptySpace <= 0) {
return 0;
}
@ -51,7 +51,7 @@ public interface MediaHolderItem {
amount = emptySpace;
}
int inserting = Math.min(amount, emptySpace);
long inserting = Math.min(amount, emptySpace);
if (!simulate) {
var newMedia = mediaHere + inserting;

View file

@ -28,10 +28,10 @@ fun isMediaItem(stack: ItemStack): Boolean {
@JvmOverloads
fun extractMedia(
stack: ItemStack,
cost: Int = -1,
cost: Long = -1,
drainForBatteries: Boolean = false,
simulate: Boolean = false
): Int {
): Long {
val mediaHolder = IXplatAbstractions.INSTANCE.findMediaHolder(stack) ?: return 0
return extractMedia(mediaHolder, cost, drainForBatteries, simulate)
@ -47,10 +47,10 @@ fun extractMedia(
*/
fun extractMedia(
holder: ADMediaHolder,
cost: Int = -1,
cost: Long = -1,
drainForBatteries: Boolean = false,
simulate: Boolean = false
): Int {
): Long {
if (drainForBatteries && !holder.canConstructBattery())
return 0
@ -84,11 +84,12 @@ fun compareMediaItem(aMedia: ADMediaHolder, bMedia: ADMediaHolder): Int {
if (priority != 0)
return priority
return aMedia.withdrawMedia(-1, true) - bMedia.withdrawMedia(-1, true)
return (aMedia.withdrawMedia(-1, true) - bMedia.withdrawMedia(-1, true))
.coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
}
fun mediaBarColor(media: Int, maxMedia: Int): Int {
val amt = if (maxMedia == 0) {
fun mediaBarColor(media: Long, maxMedia: Long): Int {
val amt = if (maxMedia == 0L) {
0f
} else {
media.toFloat() / maxMedia.toFloat()
@ -100,8 +101,8 @@ fun mediaBarColor(media: Int, maxMedia: Int): Int {
return Mth.color(r / 255f, g / 255f, b / 255f)
}
fun mediaBarWidth(media: Int, maxMedia: Int): Int {
val amt = if (maxMedia == 0) {
fun mediaBarWidth(media: Long, maxMedia: Long): Int {
val amt = if (maxMedia == 0L) {
0f
} else {
media.toFloat() / maxMedia.toFloat()

View file

@ -36,7 +36,7 @@ public class HexAdditionalRenderers {
var player = Minecraft.getInstance().player;
if (player != null) {
var sentinel = IXplatAbstractions.INSTANCE.getSentinel(player);
if (sentinel.hasSentinel() && player.getLevel().dimension().equals(sentinel.dimension())) {
if (sentinel != null && player.getLevel().dimension().equals(sentinel.dimension())) {
renderSentinel(sentinel, player, ps, partialTick);
}
}

View file

@ -1,7 +1,8 @@
package at.petrak.hexcasting.client.render;
import at.petrak.hexcasting.api.block.circle.BlockAbstractImpetus;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.client.ScryingLensOverlayRegistry;
import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf;
import at.petrak.hexcasting.common.lib.HexBlocks;
@ -63,7 +64,7 @@ public class ScryingLensOverlays {
if (world.getBlockEntity(pos) instanceof BlockEntityAkashicBookshelf tile) {
var iotaTag = tile.getIotaTag();
if (iotaTag != null) {
var display = HexIotaTypes.getDisplay(iotaTag);
var display = IotaType.getDisplay(iotaTag);
lines.add(new Pair<>(new ItemStack(Items.BOOK), display));
}
}

View file

@ -1,9 +1,11 @@
package at.petrak.hexcasting.common.blocks.circles;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
@ -14,9 +16,9 @@ import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.material.PushReaction;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import java.util.List;
// As it turns out, not actually an impetus
public class BlockEmptyImpetus extends BlockCircleComponent {
@ -29,21 +31,20 @@ public class BlockEmptyImpetus extends BlockCircleComponent {
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level world) {
public ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
return new ControlFlow.Continue(imageIn, List.of(this.exitPositionFromDirection(pos, bs.getValue(FACING))));
}
@Override
public boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
return enterDir != bs.getValue(FACING);
}
@Override
public EnumSet<Direction> exitDirections(BlockPos pos, BlockState bs, Level world) {
public EnumSet<Direction> possibleExitDirections(BlockPos pos, BlockState bs, Level world) {
return EnumSet.of(bs.getValue(FACING));
}
@Override
public @Nullable HexPattern getPattern(BlockPos pos, BlockState bs, Level world) {
return null;
}
@Override
public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft) {
return normalDirOfOther(pos.relative(bs.getValue(FACING)), world, recursionLeft);

View file

@ -2,11 +2,15 @@ package at.petrak.hexcasting.common.blocks.circles;
import at.petrak.hexcasting.annotations.SoftImplement;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.PatternIota;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.common.lib.HexItems;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@ -31,7 +35,10 @@ import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
// When on the floor or ceiling FACING is the direction the *bottom* of the pattern points
// (or which way is "down").
@ -70,14 +77,37 @@ public class BlockSlate extends BlockCircleComponent implements EntityBlock, Sim
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level world) {
var thisNormal = this.normalDir(pos, bs, world);
return enterDir != thisNormal && normalDir == thisNormal;
public ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
HexPattern pattern;
if (world.getBlockEntity(pos) instanceof BlockEntitySlate tile) {
pattern = tile.pattern;
} else {
return new ControlFlow.Stop();
}
var exitDirsSet = this.possibleExitDirections(pos, bs, world);
exitDirsSet.remove(enterDir.getOpposite());
var exitDirs = exitDirsSet.stream().map((dir) -> this.exitPositionFromDirection(pos, dir));
if (pattern == null)
return new ControlFlow.Continue(imageIn, exitDirs.toList());
var vm = new CastingVM(imageIn, env);
vm.queueExecuteAndWrapIota(new PatternIota(pattern), world);
return new ControlFlow.Continue(vm.getImage(), exitDirs.toList());
}
@Override
public EnumSet<Direction> exitDirections(BlockPos pos, BlockState bs, Level world) {
public boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
var thisNormal = this.normalDir(pos, bs, world);
return enterDir != thisNormal && enterDir != thisNormal.getOpposite();
}
@Override
public EnumSet<Direction> possibleExitDirections(BlockPos pos, BlockState bs, Level world) {
var allDirs = EnumSet.allOf(Direction.class);
var normal = this.normalDir(pos, bs, world);
allDirs.remove(normal);
@ -85,16 +115,6 @@ public class BlockSlate extends BlockCircleComponent implements EntityBlock, Sim
return allDirs;
}
@Override
public @Nullable
HexPattern getPattern(BlockPos pos, BlockState bs, Level world) {
if (world.getBlockEntity(pos) instanceof BlockEntitySlate tile) {
return tile.pattern;
} else {
return null;
}
}
@SoftImplement("forge")
public ItemStack getCloneItemStack(BlockState state, HitResult target, BlockGetter level, BlockPos pos,
Player player) {

View file

@ -1,9 +1,11 @@
package at.petrak.hexcasting.common.blocks.circles.directrix;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
@ -13,9 +15,9 @@ import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.material.PushReaction;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import java.util.List;
public class BlockEmptyDirectrix extends BlockCircleComponent {
public static final EnumProperty<Direction.Axis> AXIS = BlockStateProperties.AXIS;
@ -28,20 +30,21 @@ public class BlockEmptyDirectrix extends BlockCircleComponent {
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level world) {
public ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
var sign = world.random.nextBoolean() ? Direction.AxisDirection.POSITIVE : Direction.AxisDirection.NEGATIVE;
return new ControlFlow.Continue(imageIn, List.of(this.exitPositionFromDirection(pos, Direction.fromAxisAndDirection(bs.getValue(AXIS), sign))));
}
@Override
public boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
return true;
}
@Override
public EnumSet<Direction> exitDirections(BlockPos pos, BlockState bs, Level world) {
var sign = world.random.nextBoolean() ? Direction.AxisDirection.POSITIVE : Direction.AxisDirection.NEGATIVE;
return EnumSet.of(Direction.fromAxisAndDirection(bs.getValue(AXIS), sign));
}
@Override
public @Nullable HexPattern getPattern(BlockPos pos, BlockState bs, Level world) {
return null;
public EnumSet<Direction> possibleExitDirections(BlockPos pos, BlockState bs, Level world) {
return EnumSet.of(
Direction.fromAxisAndDirection(bs.getValue(AXIS), Direction.AxisDirection.NEGATIVE),
Direction.fromAxisAndDirection(bs.getValue(AXIS), Direction.AxisDirection.POSITIVE));
}
@Override

View file

@ -1,10 +1,12 @@
package at.petrak.hexcasting.common.blocks.circles.directrix;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
@ -18,9 +20,9 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import java.util.List;
public class BlockRedstoneDirectrix extends BlockCircleComponent {
public static final DirectionProperty FACING = BlockStateProperties.FACING;
@ -35,19 +37,18 @@ public class BlockRedstoneDirectrix extends BlockCircleComponent {
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level world) {
public ControlFlow acceptControlFlow(CastingImage imageIn, CircleCastEnv env, Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
return new ControlFlow.Continue(imageIn, List.of(this.exitPositionFromDirection(pos, getRealFacing(bs))));
}
@Override
public boolean canEnterFromDirection(Direction enterDir, BlockPos pos, BlockState bs, ServerLevel world) {
return enterDir != getRealFacing(bs);
}
@Override
public EnumSet<Direction> exitDirections(BlockPos pos, BlockState bs, Level world) {
return EnumSet.of(getRealFacing(bs));
}
@Override
public @Nullable HexPattern getPattern(BlockPos pos, BlockState bs, Level world) {
return null;
public EnumSet<Direction> possibleExitDirections(BlockPos pos, BlockState bs, Level world) {
return EnumSet.of(bs.getValue(FACING), bs.getValue(FACING).getOpposite());
}
@Override

View file

@ -31,7 +31,8 @@ public class BlockRightClickImpetus extends BlockAbstractImpetus {
var tile = pLevel.getBlockEntity(pPos);
if (tile instanceof BlockEntityRightClickImpetus impetus) {
if (pPlayer instanceof ServerPlayer serverPlayer) {
impetus.activateSpellCircle(serverPlayer);
// impetus.activateSpellCircle(serverPlayer);
impetus.startExecution(serverPlayer);
}
return InteractionResult.SUCCESS;
}

View file

@ -92,9 +92,11 @@ public class BlockStoredPlayerImpetus extends BlockAbstractImpetus {
if (isPowered && pLevel.getBlockEntity(pPos) instanceof BlockEntityStoredPlayerImpetus tile) {
var player = tile.getStoredPlayer();
if (player instanceof ServerPlayer splayer) {
if (player == null) {
tile.startExecution(null);
} else if (player instanceof ServerPlayer splayer) {
// phew
tile.activateSpellCircle(splayer);
tile.startExecution(splayer);
}
}
}

View file

@ -1,7 +1,7 @@
package at.petrak.hexcasting.common.blocks.entity;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import at.petrak.hexcasting.common.lib.HexSounds;
import net.minecraft.core.BlockPos;
@ -27,11 +27,6 @@ public class BlockEntityLookingImpetus extends BlockEntityAbstractImpetus {
super(HexBlockEntities.IMPETUS_LOOK_TILE, pWorldPosition, pBlockState);
}
@Override
public boolean activatorAlwaysInRange() {
return false;
}
// https://github.com/VazkiiMods/Botania/blob/2607bcd31c4eaeb617f7d1b3ec1c1db08f59add4/Common/src/main/java/vazkii/botania/common/block/tile/TileEnderEye.java#L27
public static void serverTick(Level level, BlockPos pos, BlockState bs, BlockEntityLookingImpetus self) {
if (bs.getValue(BlockCircleComponent.ENERGIZED)) {
@ -74,7 +69,7 @@ public class BlockEntityLookingImpetus extends BlockEntityAbstractImpetus {
if (newLook != prevLookAmt) {
if (newLook == MAX_LOOK_AMOUNT) {
self.lookAmount = 0;
self.activateSpellCircle(looker);
self.startExecution(looker);
} else {
if (newLook % 5 == 1) {
var t = (float) newLook / MAX_LOOK_AMOUNT;

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.common.blocks.entity;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
@ -9,9 +9,4 @@ public class BlockEntityRightClickImpetus extends BlockEntityAbstractImpetus {
public BlockEntityRightClickImpetus(BlockPos pWorldPosition, BlockState pBlockState) {
super(HexBlockEntities.IMPETUS_RIGHTCLICK_TILE, pWorldPosition, pBlockState);
}
@Override
public boolean activatorAlwaysInRange() {
return false;
}
}

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.common.blocks.entity;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import com.mojang.authlib.GameProfile;
@ -35,17 +35,6 @@ public class BlockEntityStoredPlayerImpetus extends BlockEntityAbstractImpetus {
super(HexBlockEntities.IMPETUS_STOREDPLAYER_TILE, pWorldPosition, pBlockState);
}
@Override
public boolean activatorAlwaysInRange() {
return true;
}
@Override
protected @Nullable
Player getPlayer() {
return this.storedPlayer == null ? null : this.level.getPlayerByUUID(this.storedPlayer);
}
protected @Nullable
GameProfile getPlayerName() {
Player player = getStoredPlayer();
@ -76,13 +65,13 @@ public class BlockEntityStoredPlayerImpetus extends BlockEntityAbstractImpetus {
// just feels wrong to use the protected method
public @Nullable
Player getStoredPlayer() {
return this.getPlayer();
return null; // TODO: Fix
}
public void applyScryingLensOverlay(List<Pair<ItemStack, Component>> lines,
BlockState state, BlockPos pos, Player observer,
Level world,
Direction hitFace) {
BlockState state, BlockPos pos, Player observer,
Level world,
Direction hitFace) {
super.applyScryingLensOverlay(lines, state, pos, observer, world, hitFace);
var name = this.getPlayerName();

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.circles
import at.petrak.hexcasting.api.casting.castables.ConstMediaAction
import at.petrak.hexcasting.api.casting.asActionResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNoSpellCircle
import net.minecraft.world.phys.Vec3
@ -11,11 +12,11 @@ class OpCircleBounds(val max: Boolean) : ConstMediaAction {
override val argc = 0
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
val circle = ctx.spellCircle
if (circle == null)
if (ctx !is CircleCastEnv)
throw MishapNoSpellCircle()
val circle = ctx.impetus ?: throw MishapNoSpellCircle()
val aabb = circle.aabb
val aabb = circle.executionState!!.bounds // the circle should have an execution state since it's executing this.
return if (max)
Vec3(aabb.maxX - 0.5, aabb.maxY - 0.5, aabb.maxZ - 0.5).asActionResult

View file

@ -4,6 +4,7 @@ import at.petrak.hexcasting.api.block.circle.BlockAbstractImpetus
import at.petrak.hexcasting.api.casting.castables.ConstMediaAction
import at.petrak.hexcasting.api.casting.asActionResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNoSpellCircle
@ -11,13 +12,9 @@ object OpImpetusDir : ConstMediaAction {
override val argc = 0
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
val circle = ctx.spellCircle
if (circle == null)
if (ctx !is CircleCastEnv)
throw MishapNoSpellCircle()
val pos = circle.impetusPos
val bs = ctx.world.getBlockState(pos)
val dir = bs.getValue(BlockAbstractImpetus.FACING)
return dir.step().asActionResult
return ctx.startDir.step().asActionResult
}
}

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.circles
import at.petrak.hexcasting.api.casting.castables.ConstMediaAction
import at.petrak.hexcasting.api.casting.asActionResult
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.env.CircleCastEnv
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNoSpellCircle
@ -10,10 +11,9 @@ object OpImpetusPos : ConstMediaAction {
override val argc = 0
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
val circle = ctx.spellCircle
if (circle == null)
if (ctx !is CircleCastEnv)
throw MishapNoSpellCircle()
return circle.impetusPos.asActionResult
return ctx.impetusLoc.asActionResult
}
}

View file

@ -1,11 +1,9 @@
package at.petrak.hexcasting.common.casting.operators.eval
import at.petrak.hexcasting.api.casting.SpellList
import at.petrak.hexcasting.api.casting.asActionResult
import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage
import at.petrak.hexcasting.api.casting.eval.vm.FrameEvaluate
import at.petrak.hexcasting.api.casting.eval.vm.FrameFinishEval
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
@ -25,11 +23,6 @@ object OpEval : Action {
val datum = stack.removeLastOrNull() ?: throw MishapNotEnoughArgs(1, 0)
val instrs = evaluatable(datum, 0)
instrs.ifRight {
CastingImage.incDepth(userData)
it.asActionResult
}
// if not installed already...
// also, never make a break boundary when evaluating just one pattern
val newCont =
@ -41,6 +34,6 @@ object OpEval : Action {
val instrsList = instrs.map({ SpellList.LList(0, listOf(PatternIota(it))) }, { it })
val frame = FrameEvaluate(instrsList, true)
return OperationResult(listOf(), userData, listOf(), newCont.pushFrame(frame))
return OperationResult(stack, userData, listOf(), newCont.pushFrame(frame))
}
}

View file

@ -3,6 +3,7 @@ package at.petrak.hexcasting.common.casting.operators.rw
import at.petrak.hexcasting.api.casting.castables.ConstMediaAction
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.iota.NullIota
import at.petrak.hexcasting.api.casting.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.xplat.IXplatAbstractions
@ -13,7 +14,7 @@ object OpRead : ConstMediaAction {
val (handStack, hand) = ctx.getHeldItemToOperateOn {
val dataHolder = IXplatAbstractions.INSTANCE.findDataHolder(it)
dataHolder != null && (dataHolder.readIota(ctx.world) != null || dataHolder.emptyIota() != null)
}
} ?: return listOf(NullIota())
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack)
?: throw MishapBadOffhandItem.of(handStack, hand, "iota.read")

View file

@ -12,7 +12,7 @@ object OpReadable : ConstMediaAction {
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
val (handStack) = ctx.getHeldItemToOperateOn {
IXplatAbstractions.INSTANCE.findDataHolder(it) != null
}
} ?: return false.asActionResult
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack)
?: return false.asActionResult

View file

@ -15,7 +15,7 @@ object OpWritable : ConstMediaAction {
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(it)
datumHolder != null
}
} ?: return false.asActionResult
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack) ?: return false.asActionResult
val success = datumHolder.writeIota(NullIota(), true)

View file

@ -9,6 +9,7 @@ import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapBadOffhandItem
import at.petrak.hexcasting.api.casting.mishaps.MishapOthersName
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.item.ItemStack
// we make this a spell cause imo it's a little ... anticlimactic for it to just make no noise
object OpWrite : SpellAction {
@ -23,7 +24,7 @@ object OpWrite : SpellAction {
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(it)
datumHolder != null && datumHolder.writeIota(datum, true)
}
} ?: throw MishapBadOffhandItem.of(ItemStack.EMPTY.copy(), null, "iota.write") // TODO: hack
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack)
?: throw MishapBadOffhandItem.of(handStack, hand, "iota.write")

View file

@ -9,6 +9,9 @@ object OpGetCaster : ConstMediaAction {
override val argc = 0
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
if (ctx.caster == null)
return null.asActionResult
ctx.assertEntityInRange(ctx.caster)
return ctx.caster.asActionResult
}

View file

@ -19,6 +19,8 @@ object OpColorize : SpellAction {
ctx: CastingEnvironment
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
val (handStack, hand) = ctx.getHeldItemToOperateOn(IXplatAbstractions.INSTANCE::isColorizer)
?: throw MishapBadOffhandItem.of(ItemStack.EMPTY, null, "colorizer") // TODO: hack
if (!IXplatAbstractions.INSTANCE.isColorizer(handStack)) {
throw MishapBadOffhandItem.of(
handStack,
@ -26,6 +28,7 @@ object OpColorize : SpellAction {
"colorizer"
)
}
return Triple(
Spell(handStack),
MediaConstants.DUST_UNIT,

View file

@ -53,14 +53,14 @@ class OpConjureBlock(val light: Boolean) : SpellAction {
if (worldState.canBeReplaced(placeContext)) {
val block = if (this.light) HexBlocks.CONJURED_LIGHT else HexBlocks.CONJURED_BLOCK
if (!IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(block), ctx.caster))
if (ctx.caster != null && !IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, ItemStack(block), ctx.caster))
return
val state = block.getStateForPlacement(placeContext)
if (state != null) {
ctx.world.setBlock(pos, state, 5)
val colorizer = IXplatAbstractions.INSTANCE.getColorizer(ctx.caster)
val colorizer = ctx.colorizer
if (ctx.world.getBlockState(pos).block is BlockConjured) {
BlockConjured.setColor(ctx.world, pos, colorizer)

View file

@ -49,7 +49,7 @@ class OpCreateFluid(val cost: Int, val bucket: Item, val cauldron: BlockState, v
ctx.world.setBlock(pos, cauldron, 3)
else if (!IXplatAbstractions.INSTANCE.tryPlaceFluid(
ctx.world,
ctx.castingHand(),
ctx.getCastingHand(),
pos,
fluid
) && bucket is BucketItem) {

View file

@ -23,7 +23,8 @@ object OpErase : SpellAction {
(hexHolder?.hasHex() == true) ||
(datumHolder?.writeIota(null, true) == true)
}
} ?: throw MishapBadOffhandItem.of(ItemStack.EMPTY.copy(), null, "eraseable") // TODO: hack
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(handStack)
val datumHolder = IXplatAbstractions.INSTANCE.findDataHolder(handStack)

View file

@ -18,6 +18,7 @@ import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
// TODO: how to handle in cirles
object OpMakeBattery : SpellAction {
override val argc = 1
@ -28,6 +29,10 @@ object OpMakeBattery : SpellAction {
val entity = args.getItemEntity(0, argc)
val (handStack, hand) = ctx.getHeldItemToOperateOn { it.`is`(HexTags.Items.PHIAL_BASE) }
?: throw MishapBadOffhandItem.of(ItemStack.EMPTY.copy(), null, "bottle") // TODO: hack
if (hand == null)
throw MishapBadOffhandItem.of(handStack, null, "havent_handled_null_hand_yet") // TODO: hack!
if (!handStack.`is`(HexTags.Items.PHIAL_BASE)) {
throw MishapBadOffhandItem.of(
@ -68,10 +73,10 @@ object OpMakeBattery : SpellAction {
val entityStack = itemEntity.item.copy()
val mediamount = extractMedia(entityStack, drainForBatteries = true)
if (mediamount > 0) {
ctx.caster.setItemInHand(
ctx.caster?.setItemInHand(
hand,
ItemMediaHolder.withMedia(ItemStack(HexItems.BATTERY), mediamount, mediamount)
)
) ?: return
}
itemEntity.item = entityStack

View file

@ -14,6 +14,7 @@ import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.ItemStack
// TODO: How to handle in circles
class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) : SpellAction {
override val argc = 2
override fun execute(
@ -27,6 +28,8 @@ class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) :
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(it)
it.`is`(itemType) && hexHolder != null && !hexHolder.hasHex()
}
?: throw MishapBadOffhandItem(ItemStack.EMPTY.copy(), null, itemType.description) // TODO: hack
val hexHolder = IXplatAbstractions.INSTANCE.findHexHolder(handStack)
if (!handStack.`is`(itemType)) {
throw MishapBadOffhandItem(handStack, hand, itemType.description)
@ -47,7 +50,7 @@ class OpMakePackagedSpell<T : ItemPackagedHex>(val itemType: T, val cost: Int) :
)
}
val trueName = MishapOthersName.getTrueNameFromArgs(patterns, ctx.caster)
val trueName = ctx.caster?.let { MishapOthersName.getTrueNameFromArgs(patterns, it) }
if (trueName != null)
throw MishapOthersName(trueName)

View file

@ -10,6 +10,7 @@ import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapBadBlock
import at.petrak.hexcasting.xplat.IXplatAbstractions
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.sounds.SoundSource
@ -20,6 +21,7 @@ import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
// TODO: how to handle in cirles
object OpPlaceBlock : SpellAction {
override val argc: Int
get() = 1
@ -32,9 +34,9 @@ object OpPlaceBlock : SpellAction {
ctx.assertVecInRange(pos)
val blockHit = BlockHitResult(
Vec3.atCenterOf(pos), ctx.caster.direction, pos, false
Vec3.atCenterOf(pos), ctx.caster?.direction ?: Direction.NORTH, pos, false
)
val itemUseCtx = UseOnContext(ctx.caster, ctx.castingHand, blockHit)
val itemUseCtx = ctx.caster?.let { UseOnContext(it, ctx.castingHand, blockHit) } ?: return null
val placeContext = BlockPlaceContext(itemUseCtx)
val worldState = ctx.world.getBlockState(pos)
@ -53,34 +55,36 @@ object OpPlaceBlock : SpellAction {
if (!ctx.canEditBlockAt(pos))
return
val caster = ctx.caster ?: return // TODO: Fix!
val blockHit = BlockHitResult(
Vec3.atCenterOf(pos), ctx.caster.direction, pos, false
Vec3.atCenterOf(pos), caster.direction, pos, false
)
val bstate = ctx.world.getBlockState(pos)
val placeeStack = ctx.getOperativeSlot { it.item is BlockItem }?.copy()
val placeeStack = ctx.getHeldItemToOperateOn { it.item is BlockItem }?.stack
if (placeeStack != null) {
if (!IXplatAbstractions.INSTANCE.isPlacingAllowed(ctx.world, pos, placeeStack, ctx.caster))
return
if (!placeeStack.isEmpty) {
// https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/block/PieceTrickPlaceBlock.java#L143
val oldStack = ctx.caster.getItemInHand(ctx.castingHand)
val oldStack = caster.getItemInHand(ctx.castingHand)
val spoofedStack = placeeStack.copy()
// we temporarily give the player the stack, place it using mc code, then give them the old stack back.
spoofedStack.count = 1
ctx.caster.setItemInHand(ctx.castingHand, spoofedStack)
caster.setItemInHand(ctx.castingHand, spoofedStack)
val itemUseCtx = UseOnContext(ctx.caster, ctx.castingHand, blockHit)
val itemUseCtx = UseOnContext(caster, ctx.castingHand, blockHit)
val placeContext = BlockPlaceContext(itemUseCtx)
if (bstate.canBeReplaced(placeContext)) {
if (ctx.withdrawItem(placeeStack, 1, false)) {
if (ctx.withdrawItem({ it == placeeStack }, 1, false)) {
val res = spoofedStack.useOn(placeContext)
ctx.caster.setItemInHand(ctx.castingHand, oldStack)
caster.setItemInHand(ctx.castingHand, oldStack)
if (res != InteractionResult.FAIL) {
ctx.withdrawItem(placeeStack, 1, true)
ctx.withdrawItem({ it == placeeStack }, 1, true)
ctx.world.playSound(
ctx.caster,
@ -95,10 +99,10 @@ object OpPlaceBlock : SpellAction {
)
}
} else {
ctx.caster.setItemInHand(ctx.castingHand, oldStack)
caster.setItemInHand(ctx.castingHand, oldStack)
}
} else {
ctx.caster.setItemInHand(ctx.castingHand, oldStack)
caster.setItemInHand(ctx.castingHand, oldStack)
}
}
}

View file

@ -31,7 +31,7 @@ object OpPrint : Action {
private data class Spell(val datum: Iota) : RenderedSpell {
override fun cast(ctx: CastingEnvironment) {
ctx.caster.sendSystemMessage(datum.display())
ctx.caster?.sendSystemMessage(datum.display()) // TODO: how to handle in cirles
}
}
}

View file

@ -25,8 +25,9 @@ object OpRecharge : SpellAction {
val (handStack, hand) = ctx.getHeldItemToOperateOn {
val media = IXplatAbstractions.INSTANCE.findMediaHolder(it)
media != null && media.canRecharge() && media.insertMedia(-1, true) != 0
media != null && media.canRecharge() && media.insertMedia(-1, true) != 0L
}
?: throw MishapBadOffhandItem.of(ItemStack.EMPTY.copy(), null, "rechargable") // TODO: hack
val media = IXplatAbstractions.INSTANCE.findMediaHolder(handStack)
@ -46,7 +47,7 @@ object OpRecharge : SpellAction {
)
}
if (media.insertMedia(-1, true) == 0)
if (media.insertMedia(-1, true) == 0L)
return null
return Triple(

View file

@ -34,15 +34,17 @@ object OpTheOnlyReasonAnyoneDownloadedPsi : SpellAction {
private data class Spell(val pos: BlockPos) : RenderedSpell {
override fun cast(ctx: CastingEnvironment) {
val caster = ctx.caster ?: return // TODO: fix!
// https://github.com/VazkiiMods/Psi/blob/master/src/main/java/vazkii/psi/common/spell/trick/PieceTrickOvergrow.java
if (!ctx.world.mayInteract(ctx.caster, pos))
if (!ctx.world.mayInteract(caster, pos))
return
val hit = BlockHitResult(Vec3.ZERO, Direction.UP, pos, false)
val save: ItemStack = ctx.caster.getItemInHand(InteractionHand.MAIN_HAND)
ctx.caster.setItemInHand(InteractionHand.MAIN_HAND, ItemStack(Items.BONE_MEAL))
val fakeContext = UseOnContext(ctx.caster, InteractionHand.MAIN_HAND, hit)
ctx.caster.setItemInHand(InteractionHand.MAIN_HAND, save)
val save: ItemStack = caster.getItemInHand(InteractionHand.MAIN_HAND)
caster.setItemInHand(InteractionHand.MAIN_HAND, ItemStack(Items.BONE_MEAL))
val fakeContext = UseOnContext(caster, InteractionHand.MAIN_HAND, hit)
caster.setItemInHand(InteractionHand.MAIN_HAND, save)
Items.BONE_MEAL.useOn(fakeContext)
}
}

View file

@ -66,7 +66,7 @@ object OpBrainsweep : SpellAction {
IXplatAbstractions.INSTANCE.brainsweep(sacrifice)
if (sacrifice is Villager && HexConfig.server().doVillagersTakeOffenseAtMindMurder()) {
sacrifice.tellWitnessesThatIWasMurdered(ctx.caster)
ctx.caster?.let { sacrifice.tellWitnessesThatIWasMurdered(it) }
}
val sound = (sacrifice as AccessorLivingEntity).`hex$getDeathSound`()

View file

@ -33,7 +33,6 @@ class OpCreateSentinel(val extendsRange: Boolean) : SpellAction {
IXplatAbstractions.INSTANCE.setSentinel(
ctx.caster,
Sentinel(
true,
extendsRange,
target,
ctx.world.dimension()

View file

@ -2,7 +2,6 @@ package at.petrak.hexcasting.common.casting.operators.spells.sentinel
import at.petrak.hexcasting.api.misc.MediaConstants
import at.petrak.hexcasting.api.player.Sentinel
import at.petrak.hexcasting.api.casting.ParticleSpray
import at.petrak.hexcasting.api.casting.RenderedSpell
import at.petrak.hexcasting.api.casting.iota.Iota
@ -16,9 +15,9 @@ object OpDestroySentinel : SpellAction {
override fun execute(
args: List<Iota>,
ctx: CastingEnvironment
): Triple<RenderedSpell, Int, List<ParticleSpray>> {
): Triple<RenderedSpell, Int, List<ParticleSpray>>? {
val particles = mutableListOf<ParticleSpray>()
val sentinel = IXplatAbstractions.INSTANCE.getSentinel(ctx.caster)
val sentinel = IXplatAbstractions.INSTANCE.getSentinel(ctx.caster) ?: return null
// TODO why can't you remove things from other dimensions?
if (sentinel.dimension != ctx.world.dimension())
throw MishapLocationInWrongDimension(sentinel.dimension.location())
@ -33,7 +32,7 @@ object OpDestroySentinel : SpellAction {
private object Spell : RenderedSpell {
override fun cast(ctx: CastingEnvironment) {
IXplatAbstractions.INSTANCE.setSentinel(ctx.caster, Sentinel.none())
IXplatAbstractions.INSTANCE.setSentinel(ctx.caster, null)
}
}
}

View file

@ -13,12 +13,9 @@ object OpGetSentinelPos : ConstMediaAction {
override val argc = 0
override val mediaCost = MediaConstants.DUST_UNIT / 10
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
val sentinel = IXplatAbstractions.INSTANCE.getSentinel(ctx.caster)
val sentinel = IXplatAbstractions.INSTANCE.getSentinel(ctx.caster) ?: return listOf(NullIota())
if (sentinel.dimension != ctx.world.dimension())
throw MishapLocationInWrongDimension(sentinel.dimension.location())
return if (sentinel.hasSentinel)
sentinel.position.asActionResult
else
listOf(NullIota())
return sentinel.position.asActionResult
}
}

View file

@ -18,14 +18,11 @@ object OpGetSentinelWayfind : ConstMediaAction {
override fun execute(args: List<Iota>, ctx: CastingEnvironment): List<Iota> {
val from = args.getVec3(0, argc)
val sentinel = IXplatAbstractions.INSTANCE.getSentinel(ctx.caster)
val sentinel = IXplatAbstractions.INSTANCE.getSentinel(ctx.caster) ?: return listOf(NullIota())
if (sentinel.dimension != ctx.world.dimension())
throw MishapLocationInWrongDimension(sentinel.dimension.location())
return if (!sentinel.hasSentinel)
listOf(NullIota())
else
sentinel.position.subtract(from).normalize().asActionResult
return sentinel.position.subtract(from).normalize().asActionResult
}
}

View file

@ -24,20 +24,26 @@ object OpFisherman : Action {
val depth = let {
val x = stack.last()
stack.removeLast()
val maxIdx = stack.size - 1
if (x is DoubleIota) {
val double = x.double
val rounded = double.roundToInt()
if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded in 1..maxIdx) {
if (abs(double - rounded) <= DoubleIota.TOLERANCE && rounded in -maxIdx..maxIdx) {
return@let rounded
}
}
throw MishapInvalidIota.of(x, 0, "double.between", 1, maxIdx)
throw MishapInvalidIota.of(x, 0, "int.between", -maxIdx, maxIdx)
}
if (depth >= 0) {
val fish = stack.removeAt(stack.size - 1 - depth)
stack.add(fish)
} else {
val lure = stack.removeLast()
stack.add(stack.size + depth, lure)
}
stack.removeLast()
val fish = stack.removeAt(stack.size - depth)
stack.add(fish)
return OperationResult(stack, userData, listOf(), continuation)
}

View file

@ -4,7 +4,7 @@ import at.petrak.hexcasting.api.casting.castables.Action
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment
import at.petrak.hexcasting.api.casting.eval.OperationResult
import at.petrak.hexcasting.api.casting.eval.vm.SpellContinuation
import at.petrak.hexcasting.api.casting.getPositiveIntUnderInclusive
import at.petrak.hexcasting.api.casting.getIntBetween
import at.petrak.hexcasting.api.casting.iota.Iota
import at.petrak.hexcasting.api.casting.mishaps.MishapNotEnoughArgs
import net.minecraft.nbt.CompoundTag
@ -19,10 +19,16 @@ object OpFishermanButItCopies : Action {
if (stack.size < 2)
throw MishapNotEnoughArgs(2, stack.size)
val depth = stack.getPositiveIntUnderInclusive(stack.lastIndex, stack.size - 2)
val depth = stack.getIntBetween(stack.lastIndex, -(stack.size - 2), stack.size - 2)
stack.removeLast()
val fish = stack.get(stack.size - 1 - depth)
stack.add(fish)
if (depth >= 0) {
val fish = stack[stack.size - 1 - depth]
stack.add(fish)
} else {
val lure = stack.last()
stack.add(stack.size - 1 + depth, lure)
}
return OperationResult(stack, userData, listOf(), continuation)
}

View file

@ -38,8 +38,8 @@ public class ItemStaff extends Item {
var descs = harness.generateDescs();
IXplatAbstractions.INSTANCE.sendPacketToPlayer(serverPlayer,
new MsgOpenSpellGuiAck(hand, patterns, descs.getFirst(), descs.getSecond(), descs.getThird(),
harness.getParenCount()));
new MsgOpenSpellGuiAck(hand, patterns, descs.getFirst(), descs.getSecond(),
0)); // TODO: Fix!
}
player.awardStat(Stats.ITEM_USED.get(this));

View file

@ -5,17 +5,17 @@ import net.minecraft.world.item.ItemStack;
public record DebugUnlockerHolder(ItemStack creativeUnlocker) implements ADMediaHolder {
@Override
public int getMedia() {
public long getMedia() {
return Integer.MAX_VALUE;
}
@Override
public int getMaxMedia() {
public long getMaxMedia() {
return Integer.MAX_VALUE - 1;
}
@Override
public void setMedia(int media) {
public void setMedia(long media) {
// NO-OP
}
@ -40,15 +40,15 @@ public record DebugUnlockerHolder(ItemStack creativeUnlocker) implements ADMedia
}
@Override
public int withdrawMedia(int cost, boolean simulate) {
ItemCreativeUnlocker.addToIntArray(creativeUnlocker, ItemCreativeUnlocker.TAG_EXTRACTIONS, cost);
public long withdrawMedia(long cost, boolean simulate) {
ItemCreativeUnlocker.addToLongArray(creativeUnlocker, ItemCreativeUnlocker.TAG_EXTRACTIONS, cost);
return cost < 0 ? getMedia() : cost;
}
@Override
public int insertMedia(int amount, boolean simulate) {
ItemCreativeUnlocker.addToIntArray(creativeUnlocker, ItemCreativeUnlocker.TAG_INSERTIONS, amount);
public long insertMedia(long amount, boolean simulate) {
ItemCreativeUnlocker.addToLongArray(creativeUnlocker, ItemCreativeUnlocker.TAG_INSERTIONS, amount);
return amount;
}

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.item.MediaHolderItem;
import at.petrak.hexcasting.api.misc.DiscoveryHandlers;
import at.petrak.hexcasting.api.misc.MediaConstants;
@ -65,7 +65,10 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
});
DiscoveryHandlers.addMediaHolderDiscoverer(harness -> {
var player = harness.getCtx().getCaster();
var player = harness.getEnv().getCaster();
if (player == null)
return List.of();
if (!player.isCreative())
return List.of();
@ -112,17 +115,17 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
}
@Override
public int getMedia(ItemStack stack) {
return Integer.MAX_VALUE;
public long getMedia(ItemStack stack) {
return Long.MAX_VALUE;
}
@Override
public int getMaxMedia(ItemStack stack) {
return Integer.MAX_VALUE - 1;
public long getMaxMedia(ItemStack stack) {
return Long.MAX_VALUE;
}
@Override
public void setMedia(ItemStack stack, int media) {
public void setMedia(ItemStack stack, long media) {
// NO-OP
}
@ -146,21 +149,31 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
NBTHelper.putIntArray(stack, tag, newArr);
}
public static void addToLongArray(ItemStack stack, String tag, long n) {
long[] arr = NBTHelper.getLongArray(stack, tag);
if (arr == null) {
arr = new long[0];
}
long[] newArr = Arrays.copyOf(arr, arr.length + 1);
newArr[newArr.length - 1] = n;
NBTHelper.putLongArray(stack, tag, newArr);
}
@Override
public int withdrawMedia(ItemStack stack, int cost, boolean simulate) {
public long withdrawMedia(ItemStack stack, long cost, boolean simulate) {
// In case it's withdrawn through other means
if (!simulate && isDebug(stack, DISPLAY_MEDIA)) {
addToIntArray(stack, TAG_EXTRACTIONS, cost);
addToLongArray(stack, TAG_EXTRACTIONS, cost);
}
return cost < 0 ? getMedia(stack) : cost;
}
@Override
public int insertMedia(ItemStack stack, int amount, boolean simulate) {
public long insertMedia(ItemStack stack, long amount, boolean simulate) {
// In case it's inserted through other means
if (!simulate && isDebug(stack, DISPLAY_MEDIA)) {
addToIntArray(stack, TAG_INSERTIONS, amount);
addToLongArray(stack, TAG_INSERTIONS, amount);
}
return amount < 0 ? getMaxMedia(stack) : amount;
@ -180,21 +193,21 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
}
private void debugDisplay(ItemStack stack, String tag, String langKey, String allKey, Entity entity) {
int[] arr = NBTHelper.getIntArray(stack, tag);
long[] arr = NBTHelper.getLongArray(stack, tag);
if (arr != null) {
NBTHelper.remove(stack, tag);
for (int i : arr) {
for (long i : arr) {
if (i < 0) {
entity.sendSystemMessage(Component.translatable("hexcasting.debug.media_" + langKey,
stack.getDisplayName(),
Component.translatable("hexcasting.debug." + allKey).withStyle(ChatFormatting.GRAY))
stack.getDisplayName(),
Component.translatable("hexcasting.debug." + allKey).withStyle(ChatFormatting.GRAY))
.withStyle(ChatFormatting.LIGHT_PURPLE));
} else {
entity.sendSystemMessage(Component.translatable("hexcasting.debug.media_" + langKey + ".with_dust",
stack.getDisplayName(),
Component.literal("" + i).withStyle(ChatFormatting.WHITE),
Component.literal(String.format("%.2f", i * 1.0 / MediaConstants.DUST_UNIT)).withStyle(
ChatFormatting.WHITE))
stack.getDisplayName(),
Component.literal("" + i).withStyle(ChatFormatting.WHITE),
Component.literal(String.format("%.2f", i * 1.0 / MediaConstants.DUST_UNIT)).withStyle(
ChatFormatting.WHITE))
.withStyle(ChatFormatting.LIGHT_PURPLE));
}
}
@ -206,7 +219,8 @@ public class ItemCreativeUnlocker extends Item implements MediaHolderItem {
BlockEntity be = context.getLevel().getBlockEntity(context.getClickedPos());
if (be instanceof BlockEntityAbstractImpetus impetus) {
impetus.setInfiniteMedia();
context.getLevel().playSound(null, context.getClickedPos(), HexSounds.SPELL_CIRCLE_FIND_BLOCK, SoundSource.PLAYERS, 1f, 1f);
context.getLevel().playSound(null, context.getClickedPos(), HexSounds.SPELL_CIRCLE_FIND_BLOCK,
SoundSource.PLAYERS, 1f, 1f);
return InteractionResult.sidedSuccess(context.getLevel().isClientSide());
}
return InteractionResult.PASS;

View file

@ -35,29 +35,35 @@ public abstract class ItemMediaHolder extends Item implements MediaHolderItem {
super(pProperties);
}
public static ItemStack withMedia(ItemStack stack, int media, int maxMedia) {
public static ItemStack withMedia(ItemStack stack, long media, long maxMedia) {
Item item = stack.getItem();
if (item instanceof ItemMediaHolder) {
NBTHelper.putInt(stack, TAG_MEDIA, media);
NBTHelper.putInt(stack, TAG_MAX_MEDIA, maxMedia);
NBTHelper.putLong(stack, TAG_MEDIA, media);
NBTHelper.putLong(stack, TAG_MAX_MEDIA, maxMedia);
}
return stack;
}
@Override
public int getMedia(ItemStack stack) {
return NBTHelper.getInt(stack, TAG_MEDIA);
public long getMedia(ItemStack stack) {
if (NBTHelper.hasInt(stack, TAG_MEDIA))
return NBTHelper.getInt(stack, TAG_MEDIA);
return NBTHelper.getLong(stack, TAG_MEDIA);
}
@Override
public int getMaxMedia(ItemStack stack) {
return NBTHelper.getInt(stack, TAG_MAX_MEDIA);
public long getMaxMedia(ItemStack stack) {
if (NBTHelper.hasInt(stack, TAG_MAX_MEDIA))
return NBTHelper.getInt(stack, TAG_MAX_MEDIA);
return NBTHelper.getLong(stack, TAG_MAX_MEDIA);
}
@Override
public void setMedia(ItemStack stack, int media) {
NBTHelper.putInt(stack, TAG_MEDIA, Mth.clamp(media, 0, getMaxMedia(stack)));
public void setMedia(ItemStack stack, long media) {
NBTHelper.putLong(stack, TAG_MEDIA, Mth.clamp(media, 0, getMaxMedia(stack)));
}
@Override

View file

@ -1,11 +1,11 @@
package at.petrak.hexcasting.common.items.magic;
import at.petrak.hexcasting.api.casting.eval.env.PackagedItemCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.item.HexHolderItem;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.casting.env.PackagedItemCastEnv;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
@ -74,7 +74,7 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold
}
@Override
public void writeHex(ItemStack stack, List<Iota> program, int media) {
public void writeHex(ItemStack stack, List<Iota> program, long media) {
ListTag patsTag = new ListTag();
for (Iota pat : program) {
patsTag.add(IotaType.serialize(pat));
@ -110,7 +110,7 @@ public abstract class ItemPackagedHex extends ItemMediaHolder implements HexHold
var sPlayer = (ServerPlayer) player;
var ctx = new PackagedItemCastEnv(sPlayer, usedHand);
var harness = CastingVM.empty(ctx);
harness.queueAndExecuteIotas(instrs, sPlayer.getLevel());
harness.queueExecuteAndWrapIotas(instrs, sPlayer.getLevel());
boolean broken = breakAfterDepletion() && getMedia(stack) == 0;

View file

@ -1,9 +1,9 @@
package at.petrak.hexcasting.common.items.storage;
import at.petrak.hexcasting.api.casting.iota.Iota;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.item.IotaHolderItem;
import at.petrak.hexcasting.api.utils.NBTHelper;
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
@ -40,7 +40,7 @@ public class ItemThoughtKnot extends Item implements IotaHolderItem {
@Override
public void writeDatum(ItemStack stack, @Nullable Iota iota) {
if (iota != null) {
NBTHelper.putCompound(stack, TAG_DATA, HexIotaTypes.serialize(iota));
NBTHelper.putCompound(stack, TAG_DATA, IotaType.serialize(iota));
}
}

View file

@ -32,13 +32,10 @@ public record MsgNewSpellPatternAck(ExecutionClientView info, int index) impleme
var index = buf.readInt();
var stack = buf.readList(FriendlyByteBuf::readNbt);
var parens = buf.readList(FriendlyByteBuf::readNbt);
var raven = buf.readOptional(FriendlyByteBuf::readNbt).orElse(null);
var parenCount = buf.readVarInt();
return new MsgNewSpellPatternAck(
new ExecutionClientView(isStackEmpty, resolutionType, stack, parens, raven, parenCount), index
new ExecutionClientView(isStackEmpty, resolutionType, stack, raven), index
);
}
@ -48,29 +45,23 @@ public record MsgNewSpellPatternAck(ExecutionClientView info, int index) impleme
buf.writeEnum(this.info.getResolutionType());
buf.writeInt(this.index);
buf.writeCollection(this.info.getStack(), FriendlyByteBuf::writeNbt);
buf.writeCollection(this.info.getParenthesized(), FriendlyByteBuf::writeNbt);
buf.writeCollection(this.info.getStackDescs(), FriendlyByteBuf::writeNbt);
buf.writeOptional(Optional.ofNullable(this.info.getRavenmind()), FriendlyByteBuf::writeNbt);
buf.writeVarInt(this.info.getParenCount());
}
public static void handle(MsgNewSpellPatternAck self) {
Minecraft.getInstance().execute(new Runnable() {
@Override
public void run() {
var mc = Minecraft.getInstance();
if (self.info().isStackClear()) {
// don't pay attention to the screen, so it also stops when we die
mc.getSoundManager().stop(HexSounds.CASTING_AMBIANCE.getLocation(), null);
}
var screen = Minecraft.getInstance().screen;
if (screen instanceof GuiSpellcasting spellGui) {
if (self.info().isStackClear() && self.info.getRavenmind() == null) {
mc.setScreen(null);
} else {
spellGui.recvServerUpdate(self.info(), self.index());
}
Minecraft.getInstance().execute(() -> {
var mc = Minecraft.getInstance();
if (self.info().isStackClear()) {
// don't pay attention to the screen, so it also stops when we die
mc.getSoundManager().stop(HexSounds.CASTING_AMBIANCE.getLocation(), null);
}
var screen = Minecraft.getInstance().screen;
if (screen instanceof GuiSpellcasting spellGui) {
if (self.info().isStackClear() && self.info.getRavenmind() == null) {
mc.setScreen(null);
} else {
spellGui.recvServerUpdate(self.info(), self.index());
}
}
});

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.common.network;
import at.petrak.hexcasting.api.casting.eval.ResolvedPattern;
import at.petrak.hexcasting.api.casting.eval.env.StaffCastEnv;
import at.petrak.hexcasting.api.casting.math.HexPattern;
import at.petrak.hexcasting.common.casting.env.StaffCastEnv;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;

View file

@ -18,7 +18,6 @@ import static at.petrak.hexcasting.api.HexAPI.modLoc;
*/
public record MsgOpenSpellGuiAck(InteractionHand hand, List<ResolvedPattern> patterns,
List<CompoundTag> stack,
List<CompoundTag> parenthesized,
CompoundTag ravenmind,
int parenCount
)
@ -38,12 +37,11 @@ public record MsgOpenSpellGuiAck(InteractionHand hand, List<ResolvedPattern> pat
var patterns = buf.readList(fbb -> ResolvedPattern.fromNBT(fbb.readAnySizeNbt()));
var stack = buf.readList(FriendlyByteBuf::readNbt);
var parens = buf.readList(FriendlyByteBuf::readNbt);
var raven = buf.readAnySizeNbt();
var parenCount = buf.readVarInt();
return new MsgOpenSpellGuiAck(hand, patterns, stack, parens, raven, parenCount);
return new MsgOpenSpellGuiAck(hand, patterns, stack, raven, parenCount);
}
public void serialize(FriendlyByteBuf buf) {
@ -52,21 +50,17 @@ public record MsgOpenSpellGuiAck(InteractionHand hand, List<ResolvedPattern> pat
buf.writeCollection(this.patterns, (fbb, pat) -> fbb.writeNbt(pat.serializeToNBT()));
buf.writeCollection(this.stack, FriendlyByteBuf::writeNbt);
buf.writeCollection(this.parenthesized, FriendlyByteBuf::writeNbt);
buf.writeNbt(this.ravenmind);
buf.writeVarInt(this.parenCount);
}
public static void handle(MsgOpenSpellGuiAck msg) {
Minecraft.getInstance().execute(new Runnable() {
@Override
public void run() {
var mc = Minecraft.getInstance();
mc.setScreen(
new GuiSpellcasting(msg.hand(), msg.patterns(), msg.stack, msg.parenthesized, msg.ravenmind,
msg.parenCount));
}
Minecraft.getInstance().execute(() -> {
var mc = Minecraft.getInstance();
mc.setScreen(
new GuiSpellcasting(msg.hand(), msg.patterns(), msg.stack, msg.ravenmind,
msg.parenCount));
});
}
}

View file

@ -3,13 +3,13 @@ package at.petrak.hexcasting.common.recipe.ingredient.brainsweep;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import com.google.gson.JsonObject;
import net.minecraft.ChatFormatting;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ -37,7 +37,7 @@ public abstract class BrainsweepeeIngredient {
* Can return null in case someone did something stupid with a recipe
*/
@Nullable
public abstract Entity exampleEntity(ClientLevel level);
public abstract Entity exampleEntity(Level level);
public abstract Type ingrType();

View file

@ -2,7 +2,6 @@ package at.petrak.hexcasting.common.recipe.ingredient.brainsweep;
import com.google.gson.JsonObject;
import net.minecraft.ChatFormatting;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
@ -13,6 +12,7 @@ import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level;
import java.util.ArrayList;
import java.util.List;
@ -43,7 +43,7 @@ public class EntityTagIngredient extends BrainsweepeeIngredient {
boolean moddersDidAGoodJob = I18n.exists(key);
return moddersDidAGoodJob
? Component.translatable(key)
: Component.literal("#" + this.entityTypeTag.location().toString());
: Component.literal("#" + this.entityTypeTag.location());
}
@Override
@ -55,10 +55,10 @@ public class EntityTagIngredient extends BrainsweepeeIngredient {
var out = new ArrayList<Component>();
out.add(moddersDidAGoodJob
? Component.translatable(key)
: Component.literal("#" + loc.toString()));
: Component.literal("#" + loc));
if (advanced && moddersDidAGoodJob) {
// Print it anyways
out.add(Component.literal("#" + loc.toString()).withStyle(ChatFormatting.DARK_GRAY));
out.add(Component.literal("#" + loc).withStyle(ChatFormatting.DARK_GRAY));
}
out.add(BrainsweepeeIngredient.getModNameComponent(loc.getNamespace()));
@ -67,7 +67,7 @@ public class EntityTagIngredient extends BrainsweepeeIngredient {
}
@Override
public Entity exampleEntity(ClientLevel level) {
public Entity exampleEntity(Level level) {
var someEntityTys = Registry.ENTITY_TYPE.getTagOrEmpty(this.entityTypeTag).iterator();
if (someEntityTys.hasNext()) {
var someTy = someEntityTys.next();
@ -96,6 +96,9 @@ public class EntityTagIngredient extends BrainsweepeeIngredient {
public static EntityTagIngredient deserialize(JsonObject obj) {
var tagLoc = ResourceLocation.tryParse(GsonHelper.getAsString(obj, "tag"));
if (tagLoc == null) {
throw new IllegalArgumentException("unknown tag " + obj);
}
var type = TagKey.create(Registry.ENTITY_TYPE_REGISTRY, tagLoc);
return new EntityTagIngredient(type);
}

View file

@ -1,7 +1,6 @@
package at.petrak.hexcasting.common.recipe.ingredient.brainsweep;
import com.google.gson.JsonObject;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
@ -10,6 +9,7 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level;
import java.util.List;
import java.util.Objects;
@ -41,7 +41,7 @@ public class EntityTypeIngredient extends BrainsweepeeIngredient {
}
@Override
public Entity exampleEntity(ClientLevel level) {
public Entity exampleEntity(Level level) {
return this.entityType.create(level);
}
@ -63,7 +63,7 @@ public class EntityTypeIngredient extends BrainsweepeeIngredient {
public static EntityTypeIngredient deserialize(JsonObject obj) {
var typeLoc = ResourceLocation.tryParse(GsonHelper.getAsString(obj, "entityType"));
if (!Registry.ENTITY_TYPE.containsKey(typeLoc)) {
if (typeLoc == null || !Registry.ENTITY_TYPE.containsKey(typeLoc)) {
throw new IllegalArgumentException("unknown entity type " + typeLoc);
}
return new EntityTypeIngredient(Registry.ENTITY_TYPE.get(typeLoc));

View file

@ -2,7 +2,6 @@ package at.petrak.hexcasting.common.recipe.ingredient.brainsweep;
import com.google.gson.JsonObject;
import net.minecraft.ChatFormatting;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
@ -16,6 +15,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.npc.VillagerProfession;
import net.minecraft.world.entity.npc.VillagerType;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@ -52,7 +52,7 @@ public class VillagerIngredient extends BrainsweepeeIngredient {
}
@Override
public Entity exampleEntity(ClientLevel level) {
public Entity exampleEntity(Level level) {
var biome = Objects.requireNonNullElse(this.biome, VillagerType.PLAINS);
var profession = Objects.requireNonNullElse(this.profession, VillagerProfession.TOOLSMITH);
var tradeLevel = Math.min(this.minLevel, 1);

View file

@ -77,7 +77,7 @@ public interface IXplatAbstractions {
void setColorizer(Player target, FrozenColorizer colorizer);
void setSentinel(Player target, Sentinel sentinel);
void setSentinel(Player target, @Nullable Sentinel sentinel);
void setFlight(ServerPlayer target, @Nullable FlightAbility flight);
@ -95,7 +95,7 @@ public interface IXplatAbstractions {
FrozenColorizer getColorizer(Player player);
Sentinel getSentinel(Player player);
@Nullable Sentinel getSentinel(Player player);
CastingVM getStaffcastVM(ServerPlayer player, InteractionHand hand);

View file

@ -607,6 +607,9 @@
"hexcasting.mishap.unknown": "threw an exception (%s). This is a bug in the mod.",
"hexcasting.mishap.shame": "Shame on you!",
"hexcasting.circles.no_exit": "The flow of media at %s could not find an exit",
"hexcasting.circles.many_exits": "The flow of media at %s had too many exits",
"_comment": "Patchi stuff",
"hexcasting.landing": "I seem to have discovered a new method of magical arts, in which one draws patterns strange and wild onto a hexagonal grid. It fascinates me. I've decided to start a journal of my thoughts and findings.$(br2)$(l:https://discord.gg/4xxHGYteWk)Discord Server Link/$",
@ -989,7 +992,7 @@
"hexcasting.page.stackmanip.2dup": "Copy the top two iotas of the stack. [0, 1] becomes [0, 1, 0, 1].",
"hexcasting.page.stackmanip.stack_len": "Pushes the size of the stack as a number to the top of the stack. (For example, a stack of [0, 1] will become [0, 1, 2].)",
"hexcasting.page.stackmanip.duplicate_n": "Removes the number at the top of the stack, then copies the top iota of the stack that number of times. (A count of 2 results in two of the iota on the stack, not three.)",
"hexcasting.page.stackmanip.fisherman": "Grabs the element in the stack indexed by the number and brings it to the top.",
"hexcasting.page.stackmanip.fisherman": "Grabs the element in the stack indexed by the number and brings it to the top. If the number is negative, instead moves the top element of the stack down that many elements.",
"hexcasting.page.stackmanip.fisherman/copy": "Like $(action)Fisherman's Gambit/$, but instead of moving the iota, copies it.",
"hexcasting.page.stackmanip.mask.1": "An infinite family of actions that keep or remove elements at the top of the stack based on the sequence of dips and lines.",
"hexcasting.page.stackmanip.mask.2": "Assuming that I draw a Bookkeeper's Gambit pattern left-to-right, the number of iotas the action will require is determined by the horizontal distance covered by the pattern. From deepest in the stack to shallowest, a flat line will keep the iota, whereas a triangle dipping down will remove it.$(br2)If my stack contains $(italic)0, 1, 2/$ from deepest to shallowest, drawing the first pattern opposite will give me $(italic)1/$, the second will give me $(italic)0/$, and the third will give me $(italic)0, 2/$ (the 0 at the bottom is left untouched).",

View file

@ -11,7 +11,7 @@
"op_id": "hexcasting:get_caster",
"anchor": "hexcasting:get_caster",
"input": "",
"output": "entity",
"output": "entity | null",
"text": "hexcasting.page.basics_pattern.get_caster"
},
{

View file

@ -10,6 +10,8 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import javax.annotation.Nullable;
public class CCSentinel implements Component, AutoSyncedComponent {
public static final String
TAG_HAS_SENTINEL = "has_sentinel",
@ -18,13 +20,13 @@ public class CCSentinel implements Component, AutoSyncedComponent {
TAG_DIMENSION = "dimension";
private final Player owner;
private Sentinel sentinel = Sentinel.none();
private @Nullable Sentinel sentinel = null;
public CCSentinel(Player owner) {
this.owner = owner;
}
public Sentinel getSentinel() {
public @Nullable Sentinel getSentinel() {
return sentinel;
}
@ -41,16 +43,16 @@ public class CCSentinel implements Component, AutoSyncedComponent {
var position = HexUtils.vecFromNBT(tag.getLongArray(TAG_POSITION));
var dim = ResourceKey.create(Registry.DIMENSION_REGISTRY,
new ResourceLocation(tag.getString(TAG_DIMENSION)));
this.sentinel = new Sentinel(true, extendsRange, position, dim);
this.sentinel = new Sentinel(extendsRange, position, dim);
} else {
this.sentinel = Sentinel.none();
this.sentinel = null;
}
}
@Override
public void writeToNbt(CompoundTag tag) {
tag.putBoolean(TAG_HAS_SENTINEL, this.sentinel.hasSentinel());
if (this.sentinel.hasSentinel()) {
tag.putBoolean(TAG_HAS_SENTINEL, this.sentinel != null);
if (this.sentinel != null) {
tag.putBoolean(TAG_EXTENDS_RANGE, this.sentinel.extendsRange());
tag.put(TAG_POSITION, HexUtils.serializeToNBT(this.sentinel.position()));
tag.putString(TAG_DIMENSION, this.sentinel.dimension().location().toString());

View file

@ -1,8 +1,8 @@
package at.petrak.hexcasting.fabric.cc;
import at.petrak.hexcasting.api.casting.eval.env.StaffCastEnv;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.common.casting.env.StaffCastEnv;
import dev.onyxstudios.cca.api.v3.component.Component;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;

View file

@ -45,7 +45,7 @@ public abstract class CCHexHolder extends ItemComponent implements ADHexHolder {
}
@Override
public void writeHex(List<Iota> patterns, int media) {
public void writeHex(List<Iota> patterns, long media) {
this.hexHolder.writeHex(this.stack, patterns, media);
}

View file

@ -25,17 +25,17 @@ public abstract class CCMediaHolder extends ItemComponent implements ADMediaHold
}
@Override
public int getMedia() {
public long getMedia() {
return this.mediaHolder.getMedia(this.stack);
}
@Override
public int getMaxMedia() {
public long getMaxMedia() {
return this.mediaHolder.getMaxMedia(this.stack);
}
@Override
public void setMedia(int media) {
public void setMedia(long media) {
this.mediaHolder.setMedia(this.stack, media);
}
@ -60,12 +60,12 @@ public abstract class CCMediaHolder extends ItemComponent implements ADMediaHold
}
@Override
public int withdrawMedia(int cost, boolean simulate) {
public long withdrawMedia(long cost, boolean simulate) {
return this.mediaHolder.withdrawMedia(this.stack, cost, simulate);
}
@Override
public int insertMedia(int amount, boolean simulate) {
public long insertMedia(long amount, boolean simulate) {
return this.mediaHolder.insertMedia(this.stack, amount, simulate);
}
}
@ -81,17 +81,17 @@ public abstract class CCMediaHolder extends ItemComponent implements ADMediaHold
}
@Override
public int getMedia() {
return baseWorth.get() * stack.getCount();
public long getMedia() {
return (long) baseWorth.get() * stack.getCount();
}
@Override
public int getMaxMedia() {
public long getMaxMedia() {
return getMedia();
}
@Override
public void setMedia(int media) {
public void setMedia(long media) {
// NO-OP
}
@ -116,8 +116,8 @@ public abstract class CCMediaHolder extends ItemComponent implements ADMediaHold
}
@Override
public int withdrawMedia(int cost, boolean simulate) {
int worth = baseWorth.get();
public long withdrawMedia(long cost, boolean simulate) {
long worth = baseWorth.get();
if (cost < 0) {
cost = worth * stack.getCount();
}

View file

@ -29,7 +29,7 @@ public class TrinketsApiInterop {
});
DiscoveryHandlers.addMediaHolderDiscoverer(harness -> {
Optional<TrinketComponent> optional = TrinketsApi.getTrinketComponent(harness.getCtx().getCaster());
Optional<TrinketComponent> optional = TrinketsApi.getTrinketComponent(harness.getEnv().getCaster());
if (optional.isPresent()) {
TrinketComponent component = optional.get();
return component.getEquipped(MediaHelper::isMediaItem).stream()

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.fabric.storage
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus
import at.petrak.hexcasting.common.lib.HexBlocks
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
@ -27,7 +27,7 @@ class FabricImpetusStorage(val impetus: BlockEntityAbstractImpetus) : SingleSlot
fun insertStack(stack: ItemStack, transaction: TransactionContext) {
val copied = stack.copy()
val size = stack.count
val extractable = impetus.extractMediaFromItem(stack, false)
val extractable = impetus.extractMediaFromInsertedItem(stack, false)
mediaToTake -= extractable
val taken = size - stack.count
itemsConsumed += taken.toLong()

View file

@ -155,7 +155,7 @@ public class FabricXplatImpl implements IXplatAbstractions {
}
@Override
public void setSentinel(Player target, Sentinel sentinel) {
public void setSentinel(Player target, @Nullable Sentinel sentinel) {
var cc = HexCardinalComponents.SENTINEL.get(target);
cc.setSentinel(sentinel);
}

View file

@ -29,7 +29,7 @@ public class CapSyncers {
x.setAltiora(player, x.getAltiora(proto));
x.setSentinel(player, x.getSentinel(proto));
x.setColorizer(player, x.getColorizer(proto));
x.setStaffcastImage(player, x.getStaffcastVM(proto, InteractionHand.MAIN_HAND));
x.setStaffcastImage(player, x.getStaffcastVM(proto, InteractionHand.MAIN_HAND).getImage());
x.setPatterns(player, x.getPatternsSavedInUi(proto));
}

View file

@ -1,7 +1,7 @@
package at.petrak.hexcasting.forge.cap;
import at.petrak.hexcasting.api.addldata.*;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.iota.DoubleIota;
import at.petrak.hexcasting.api.item.ColorizerItem;
import at.petrak.hexcasting.api.item.HexHolderItem;

View file

@ -1,6 +1,6 @@
package at.petrak.hexcasting.forge.cap;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.casting.circles.BlockEntityAbstractImpetus;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.items.IItemHandler;
import org.jetbrains.annotations.NotNull;
@ -29,7 +29,8 @@ public record ForgeImpetusCapability(BlockEntityAbstractImpetus impetus) impleme
if (!simulate) {
impetus.insertMedia(stack);
} else {
impetus.extractMediaFromItem(stack, false); // Media goes nowhere, since nothing is actually being done
impetus.extractMediaFromInsertedItem(stack, false); // Media goes nowhere, since nothing is actually
// being done
}
return stack;

View file

@ -28,7 +28,7 @@ public record CapItemHexHolder(HexHolderItem holder,
}
@Override
public void writeHex(List<Iota> patterns, int media) {
public void writeHex(List<Iota> patterns, long media) {
holder.writeHex(stack, patterns, media);
}

View file

@ -11,17 +11,17 @@ public record CapItemMediaHolder(MediaHolderItem holder,
ItemStack stack) implements ADMediaHolder {
@Override
public int getMedia() {
public long getMedia() {
return holder.getMedia(stack);
}
@Override
public int getMaxMedia() {
public long getMaxMedia() {
return holder.getMaxMedia(stack);
}
@Override
public void setMedia(int media) {
public void setMedia(long media) {
holder.setMedia(stack, media);
}
@ -46,12 +46,12 @@ public record CapItemMediaHolder(MediaHolderItem holder,
}
@Override
public int withdrawMedia(int cost, boolean simulate) {
public long withdrawMedia(long cost, boolean simulate) {
return holder.withdrawMedia(stack, cost, simulate);
}
@Override
public int insertMedia(int amount, boolean simulate) {
public long insertMedia(long amount, boolean simulate) {
return holder.insertMedia(stack, amount, simulate);
}
}

View file

@ -12,17 +12,17 @@ public record CapStaticMediaHolder(Supplier<Integer> baseWorth,
int consumptionPriority,
ItemStack stack) implements ADMediaHolder {
@Override
public int getMedia() {
return baseWorth.get() * stack.getCount();
public long getMedia() {
return (long) baseWorth.get() * stack.getCount();
}
@Override
public int getMaxMedia() {
public long getMaxMedia() {
return getMedia();
}
@Override
public void setMedia(int media) {
public void setMedia(long media) {
// NO-OP
}
@ -47,8 +47,8 @@ public record CapStaticMediaHolder(Supplier<Integer> baseWorth,
}
@Override
public int withdrawMedia(int cost, boolean simulate) {
int worth = baseWorth.get();
public long withdrawMedia(long cost, boolean simulate) {
long worth = baseWorth.get();
if (cost < 0) {
cost = worth * stack.getCount();
}

View file

@ -44,7 +44,12 @@ public class CuriosApiInterop {
DiscoveryHandlers.addMediaHolderDiscoverer(harness -> {
List<ADMediaHolder> holders = Lists.newArrayList();
harness.getCtx().getCaster().getCapability(CuriosCapability.INVENTORY).ifPresent(handler -> {
var caster = harness.getEnv().getCaster();
if (caster == null)
return holders;
caster.getCapability(CuriosCapability.INVENTORY).ifPresent(handler -> {
for (var stacksHandler : handler.getCurios().values()) {
var stacks = stacksHandler.getStacks();
for (int i = 0; i < stacks.getSlots(); i++) {

View file

@ -11,12 +11,14 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
/**
* Sent server->client to synchronize the status of the sentinel.
*/
public record MsgSentinelStatusUpdateAck(Sentinel update) implements IMessage {
public record MsgSentinelStatusUpdateAck(@Nullable Sentinel update) implements IMessage {
public static final ResourceLocation ID = modLoc("sntnl");
@Override
@ -28,16 +30,25 @@ public record MsgSentinelStatusUpdateAck(Sentinel update) implements IMessage {
var buf = new FriendlyByteBuf(buffer);
var exists = buf.readBoolean();
if (!exists) {
return new MsgSentinelStatusUpdateAck(null);
}
var greater = buf.readBoolean();
var origin = new Vec3(buf.readDouble(), buf.readDouble(), buf.readDouble());
var dimension = ResourceKey.create(Registry.DIMENSION_REGISTRY, buf.readResourceLocation());
var sentinel = new Sentinel(exists, greater, origin, dimension);
var sentinel = new Sentinel(greater, origin, dimension);
return new MsgSentinelStatusUpdateAck(sentinel);
}
public void serialize(FriendlyByteBuf buf) {
buf.writeBoolean(update.hasSentinel());
if (update == null) {
buf.writeBoolean(false);
return;
}
buf.writeBoolean(true);
buf.writeBoolean(update.extendsRange());
buf.writeDouble(update.position().x);
buf.writeDouble(update.position().y);
@ -46,13 +57,10 @@ public record MsgSentinelStatusUpdateAck(Sentinel update) implements IMessage {
}
public static void handle(MsgSentinelStatusUpdateAck self) {
Minecraft.getInstance().execute(new Runnable() {
@Override
public void run() {
var player = Minecraft.getInstance().player;
if (player != null) {
IXplatAbstractions.INSTANCE.setSentinel(player, self.update());
}
Minecraft.getInstance().execute(() -> {
var player = Minecraft.getInstance().player;
if (player != null) {
IXplatAbstractions.INSTANCE.setSentinel(player, self.update());
}
});
}

View file

@ -7,9 +7,10 @@ import at.petrak.hexcasting.api.addldata.ADIotaHolder;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.casting.ActionRegistryEntry;
import at.petrak.hexcasting.api.casting.castables.SpecialHandler;
import at.petrak.hexcasting.api.casting.eval.CastingEnvironment;
import at.petrak.hexcasting.api.casting.eval.ResolvedPattern;
import at.petrak.hexcasting.api.casting.eval.env.StaffCastEnv;
import at.petrak.hexcasting.api.casting.eval.sideeffects.EvalSound;
import at.petrak.hexcasting.api.casting.eval.vm.CastingImage;
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM;
import at.petrak.hexcasting.api.casting.iota.IotaType;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
@ -195,10 +196,10 @@ public class ForgeXplatImpl implements IXplatAbstractions {
}
@Override
public void setSentinel(Player player, Sentinel sentinel) {
public void setSentinel(Player player, @Nullable Sentinel sentinel) {
CompoundTag tag = player.getPersistentData();
tag.putBoolean(TAG_SENTINEL_EXISTS, sentinel.hasSentinel());
if (sentinel.hasSentinel()) {
tag.putBoolean(TAG_SENTINEL_EXISTS, sentinel == null);
if (sentinel != null) {
tag.putBoolean(TAG_SENTINEL_GREATER, sentinel.extendsRange());
tag.put(TAG_SENTINEL_POSITION, HexUtils.serializeToNBT(sentinel.position()));
tag.putString(TAG_SENTINEL_DIMENSION, sentinel.dimension().location().toString());
@ -214,8 +215,8 @@ public class ForgeXplatImpl implements IXplatAbstractions {
}
@Override
public void setStaffcastImage(ServerPlayer player, CastingVM harness) {
player.getPersistentData().put(TAG_HARNESS, harness == null ? new CompoundTag() : harness.serializeToNBT());
public void setStaffcastImage(ServerPlayer player, @Nullable CastingImage image) {
player.getPersistentData().put(TAG_HARNESS, image == null ? new CompoundTag() : image.serializeToNbt());
}
@Override
@ -275,14 +276,14 @@ public class ForgeXplatImpl implements IXplatAbstractions {
var dimension = ResourceKey.create(Registry.DIMENSION_REGISTRY,
new ResourceLocation(tag.getString(TAG_SENTINEL_DIMENSION)));
return new Sentinel(true, extendsRange, position, dimension);
return new Sentinel(extendsRange, position, dimension);
}
@Override
public CastingVM getStaffcastVM(ServerPlayer player, InteractionHand hand) {
// This is always from a staff because we don't need to load the harness when casting from item
var ctx = new CastingEnvironment(player, hand, CastingEnvironment.CastSource.STAFF);
return CastingVM.fromNBT(player.getPersistentData().getCompound(TAG_HARNESS), ctx);
var ctx = new StaffCastEnv(player, hand);
return new CastingVM(CastingImage.loadFromNbt(player.getPersistentData().getCompound(TAG_HARNESS), player.getLevel()), ctx);
}
@Override
@ -403,7 +404,7 @@ public class ForgeXplatImpl implements IXplatAbstractions {
return ForgeUnsealedIngredient.of(stack);
}
private static Supplier<CreativeModeTab> TAB = Suppliers.memoize(() ->
private final static Supplier<CreativeModeTab> TAB = Suppliers.memoize(() ->
new CreativeModeTab(HexAPI.MOD_ID) {
@Override
public ItemStack makeIcon() {
@ -411,8 +412,8 @@ public class ForgeXplatImpl implements IXplatAbstractions {
}
@Override
public void fillItemList(NonNullList<ItemStack> p_40778_) {
super.fillItemList(p_40778_);
public void fillItemList(NonNullList<ItemStack> items) {
super.fillItemList(items);
}
});