HexCasting/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/ICircleComponent.java

157 lines
6.5 KiB
Java

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.pigment.FrozenPigment;
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;
FrozenPigment colorizer;
UUID activator = Util.NIL_UUID;
if (impetus.getExecutionState() != null && impetus.getExecutionState().caster != null)
activator = impetus.getExecutionState().caster;
if (impetus.getExecutionState() == null)
colorizer = new FrozenPigment(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 FrozenPigment(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() {
}
}
}
}