Compare commits

...

6 commits

Author SHA1 Message Date
gamma-delta f4cfbb0e48 idk how to do 2022-06-18 10:18:34 -05:00
gamma-delta a03f80de2e starting work on modeling. oh god. why. 2022-06-16 22:09:14 -05:00
gamma-delta 46cc0b55cc component, seethe, mald 2022-06-16 16:54:06 -05:00
gamma-delta 600e622d7b yahoo yahoo yayayayayaaayaayh 2022-06-16 16:05:53 -05:00
gamma-delta 129fbcbb53 more and more working on API design 2022-06-16 16:03:19 -05:00
gamma-delta d4df3e613c i hate git 2022-06-16 11:51:26 -05:00
45 changed files with 997 additions and 1688 deletions

View file

@ -0,0 +1,47 @@
package at.petrak.hexcasting.api.addldata;
import at.petrak.hexcasting.api.circles.BlockEdge;
import at.petrak.hexcasting.api.circles.FlowUpdate;
import at.petrak.hexcasting.api.circles.ICircleState;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
/**
* Additional data attached to block entities that make them work as parts in a Spell Circle.
* <p>
* Yes, sadly they do actually have to be block entities. Thank Forge.
* <p>
* I would love to call this something less silly than "widget" but the word "component" is already taken by
* Fabric.
* <p>
* See also {@link at.petrak.hexcasting.api.circles api.circles}.
*/
public interface ADCircleWidget {
/**
* Whether this block accepts flow from the given edge.
* <p>
* This should be immutably based on the block's state. A single slate would output {@code true} for
* all the edges it is connected to, for example.
*/
boolean acceptsFlow(BlockEdge edge);
/**
* Directions this block <i>can</i> disperse flow to.
* <p>
* This should be immutably based on the block's state. A directrix would output both of the edges it can
* exit from, for example.
*/
EnumSet<BlockEdge> possibleExitDirs(BlockEdge inputEdge);
/**
* What this block should do upon receiving the flow.
* <p>
* Returning {@code null} signals there was a problem somehow, like if it got an input from an edge it didn't
* expect. In that case the circle will throw a {@link at.petrak.hexcasting.api.spell.mishaps.MishapMessedUpSpellCircle
* MishapMessedUpSpellCircle}
*/
@Nullable
FlowUpdate onReceiveFlow(BlockEdge inputEdge, BlockEntity sender, ICircleState state);
}

View file

@ -2,9 +2,11 @@
* An "Additional Data," or AD, is what I am calling the abstraction over capabilities on Forge and
* cardinal components on Fabric.
* <p>
* An {@code ADFooBar} in this package will be implemented by a {@code CCFooBar} on Fabric.
* On Forge, there are a set of private records that implement them.
* For each AD in here, the mod provides an interface or abstract class that mirrors it.
* Implementing the given interface on whatever type that AD attaches to (items, blocks) will automatically attach an
* appropriate instance of an AD to it as a capability/CC by scanning the registry.
* For example, {@link at.petrak.hexcasting.api.item.ColorizerItem ColorizerItem}.
* <p>
* The point is, this provides an interface for interacting with however whatever platform sticks extra info on stuff.
* I do not know why we don't just implement the AD interface directly. Ask Wire.
*/
package at.petrak.hexcasting.api.addldata;

View file

@ -1,94 +0,0 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.spell.math.HexPattern;
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;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
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.Random;
// Facing dir is the direction it starts searching for slates in to start
public abstract class BlockAbstractImpetus extends BlockCircleComponent implements EntityBlock {
public static final DirectionProperty FACING = BlockStateProperties.FACING;
public BlockAbstractImpetus(Properties p_49795_) {
super(p_49795_);
this.registerDefaultState(
this.stateDefinition.any().setValue(ENERGIZED, false).setValue(FACING, Direction.NORTH));
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level world) {
return enterDir != bs.getValue(FACING);
}
@Override
public EnumSet<Direction> exitDirections(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);
}
@Override
public float particleHeight(BlockPos pos, BlockState bs, Level world) {
return 0.5f;
}
@Override
public void tick(BlockState pState, ServerLevel pLevel, BlockPos pPos, Random pRandom) {
if (pLevel.getBlockEntity(pPos) instanceof BlockEntityAbstractImpetus tile && pState.getValue(ENERGIZED)) {
tile.stepCircle();
}
}
@Override
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();
}
super.onRemove(pState, pLevel, pPos, pNewState, pIsMoving);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(FACING);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext pContext) {
return this.defaultBlockState().setValue(FACING, pContext.getNearestLookingDirection());
}
@Override
public BlockState rotate(BlockState pState, Rotation pRot) {
return pState.setValue(FACING, pRot.rotate(pState.getValue(FACING)));
}
@Override
public BlockState mirror(BlockState pState, Mirror pMirror) {
return pState.rotate(pMirror.getRotation(pState.getValue(FACING)));
}
}

View file

@ -1,77 +0,0 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.spell.math.HexPattern;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
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 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);
abstract public EnumSet<Direction> exitDirections(BlockPos pos, BlockState bs, Level world);
@Nullable
abstract public HexPattern getPattern(BlockPos pos, BlockState bs, Level world);
/**
* Which direction points "up" or "out" for this block?
* This is used for {@link BlockCircleComponent#canEnterFromDirection(Direction, Direction, BlockPos, BlockState, Level)}
* as well as particles.
*/
public Direction normalDir(BlockPos pos, BlockState bs, Level world) {
return normalDir(pos, bs, world, 16);
}
abstract public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft);
public static Direction normalDirOfOther(BlockPos other, Level world, int recursionLeft) {
if (recursionLeft <= 0) {
return Direction.UP;
}
var stateThere = world.getBlockState(other);
if (stateThere.getBlock() instanceof BlockCircleComponent bcc) {
return bcc.normalDir(other, stateThere, world, recursionLeft - 1);
} else {
return Direction.UP;
}
}
/**
* How many blocks in the {@link BlockCircleComponent#normalDir(BlockPos, BlockState, Level)} from the center
* particles should be spawned in
*/
abstract public float particleHeight(BlockPos pos, BlockState bs, Level world);
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {
pBuilder.add(ENERGIZED);
}
@Override
public boolean hasAnalogOutputSignal(BlockState pState) {
return true;
}
@Override
public int getAnalogOutputSignal(BlockState pState, Level pLevel, BlockPos pPos) {
return pState.getValue(ENERGIZED) ? 15 : 0;
}
}

View file

@ -1,559 +0,0 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.block.HexBlockEntity;
import at.petrak.hexcasting.api.misc.FrozenColorizer;
import at.petrak.hexcasting.api.misc.ManaConstants;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.api.spell.ParticleSpray;
import at.petrak.hexcasting.api.spell.casting.CastingContext;
import at.petrak.hexcasting.api.spell.casting.CastingHarness;
import at.petrak.hexcasting.api.spell.casting.SpellCircleContext;
import at.petrak.hexcasting.api.spell.iota.PatternIota;
import at.petrak.hexcasting.api.utils.ManaHelper;
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.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
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.network.chat.TranslatableComponent;
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.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.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_MANA = "mana",
TAG_LAST_MISHAP = "last_mishap";
@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 mana = 0;
public BlockEntityAbstractImpetus(BlockEntityType<?> pType, BlockPos pWorldPosition, BlockState pBlockState) {
super(pType, pWorldPosition, pBlockState);
}
abstract public boolean activatorAlwaysInRange();
public int getMana() {
return this.mana;
}
public void setMana(int mana) {
this.mana = mana;
}
@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,
LocalPlayer observer, ClientLevel world,
Direction hitFace, InteractionHand lensHand) {
if (world.getBlockEntity(pos) instanceof BlockEntityAbstractImpetus beai) {
var dustCount = (float) beai.getMana() / (float) ManaConstants.DUST_UNIT;
var dustCmp = new TranslatableComponent("hexcasting.tooltip.lens.impetus.mana",
String.format("%.2f", 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_MANA, this.mana);
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.mana = tag.getInt(TAG_MANA);
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 CastingContext(splayer, InteractionHand.MAIN_HAND,
new SpellCircleContext(this.getBlockPos(), bounds, this.activatorAlwaysInRange()));
var harness = new CastingHarness(ctx);
var makeSound = false;
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.executeIota(new PatternIota(newPattern), splayer.getLevel());
if (info.getMakesCastSound()) {
makeSound = true;
}
if (!info.getResolutionType().getSuccess()) {
erroredPos = tracked;
break;
}
}
}
}
if (makeSound) {
this.level.playSound(null, this.getBlockPos(), HexSounds.SPELL_CIRCLE_CAST, SoundSource.BLOCKS,
2f, 1f);
}
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) {
insertMana(stack);
}
@Override
public boolean stillValid(Player player) {
return false;
}
@Override
public boolean canPlaceItem(int index, ItemStack stack) {
var manamount = extractMana(stack, true);
return manamount > 0;
}
@Override
public void clearContent() {
this.mana = 0;
this.stopCasting();
this.sync();
}
public int extractMana(ItemStack stack, boolean simulate) {
return ManaHelper.extractMana(stack, remainingManaCapacity(), true, simulate);
}
public void insertMana(ItemStack stack) {
var manamount = extractMana(stack, false);
if (manamount > 0) {
this.mana += manamount;
this.sync();
}
}
public int remainingManaCapacity() {
return MAX_CAPACITY - this.mana;
}
}

View file

@ -0,0 +1,59 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.block.HexBlockEntity;
import at.petrak.hexcasting.api.circles.BlockEdge;
import at.petrak.hexcasting.api.circles.FlowUpdate;
import at.petrak.hexcasting.api.circles.ICircleState;
import at.petrak.hexcasting.api.circles.Margin;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.EnumSet;
/**
* Helper class for halfslab-sized blocks that sit on the sides of blocks.
* <p>
* On both the Forge and Fabric sides, the registry will be scanned for all blocks which extend this,
* and the appropriate cap/CC will be attached.
* <p>
* No, there isn't a more generic version for you to use. Why? In case you don't want to be locked into
* using {@link HexBlockEntity}.
*/
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
public abstract class BlockEntitySidedCircleWidget extends HexBlockEntity {
public BlockEntitySidedCircleWidget(BlockEntityType<?> type, BlockPos worldPosition, BlockState blockState) {
super(type, worldPosition, blockState);
}
// "Overrides."
// The BlockEdge is transformed into a Margin in the forge/fabric implers
public abstract boolean acceptsFlow(Margin margin);
public abstract EnumSet<BlockEdge> possibleExitDirs(Margin inputMargin);
public abstract FlowUpdate onReceiveFlow(Margin inputMargin, BlockEntity sender, ICircleState state);
/**
* Convert the given edge to a margin in the block's local marginspace (ie, post-transformation),
* or {@code null} if it's not an edge this block touches.
*/
@Nullable
public Margin getMargin(BlockEdge edge) {
var bs = this.getBlockState();
var hereNormal = bs.getValue(BlockSidedCircleWidget.FACING);
var otherNormal = edge.getOtherNormal(hereNormal);
if (otherNormal == null) {
return null;
}
var absoluteMargin = Margin.fromNormalAndDir(hereNormal, otherNormal);
var topMargin = bs.getValue(BlockSidedCircleWidget.TOP_MARGIN);
return absoluteMargin.transform(topMargin);
}
}

View file

@ -0,0 +1,151 @@
package at.petrak.hexcasting.api.block.circle;
import at.petrak.hexcasting.api.circles.Margin;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
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.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
/**
* Block holder of a {@link BlockEntitySidedCircleWidget}.
*/
abstract public class BlockSidedCircleWidget extends Block implements EntityBlock {
public static final double THICKNESS = 8;
public static final VoxelShape AABB_XP = Block.box(16 - THICKNESS, 0, 0, 16, 16, 16);
public static final VoxelShape AABB_XN = Block.box(0, 0, 0, THICKNESS, 16, 16);
public static final VoxelShape AABB_YP = Block.box(0, 0, 0, 16, THICKNESS, 16);
public static final VoxelShape AABB_YN = Block.box(0, 16 - THICKNESS, 0, 16, 16, 16);
public static final VoxelShape AABB_ZP = Block.box(0, 0, 16 - THICKNESS, 16, 16, 16);
public static final VoxelShape AABB_ZN = Block.box(0, 0, 0, 16, 16, THICKNESS);
/**
* If this has been activated yet by the circle.
*/
public static final BooleanProperty ENERGIZED = BooleanProperty.create("energized");
/**
* The normal vector of this block
*/
public static final DirectionProperty FACING = BlockStateProperties.FACING;
/**
* Which margin this block considers to be its "top." AKA, its local facing dir.
*/
public static final EnumProperty<Margin> TOP_MARGIN = EnumProperty.create("top_margin", Margin.class);
// storing these 4 properties raises the property count to 384, but minecraft noteblocks have like 768
// I'll be fiiiiiiiiiiiiine
public static final BooleanProperty TOP_CONNECTS = BooleanProperty.create("top_connects");
public static final BooleanProperty RIGHT_CONNECTS = BooleanProperty.create("right_connects");
public static final BooleanProperty BOTTOM_CONNECTS = BooleanProperty.create("bottom_connects");
public static final BooleanProperty LEFT_CONNECTS = BooleanProperty.create("left_connects");
public BlockSidedCircleWidget(Properties properties) {
super(properties);
var statedef = this.getStateDefinition().any()
.setValue(ENERGIZED, false)
.setValue(FACING, Direction.NORTH)
.setValue(TOP_MARGIN, Margin.TOP);
for (var margin : Margin.values()) {
statedef = statedef.setValue(getConnectorProp(margin), false);
}
this.registerDefaultState(statedef);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(ENERGIZED, FACING, TOP_MARGIN);
for (var margin : Margin.values()) {
builder.add(getConnectorProp(margin));
}
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return switch (state.getValue(FACING)) {
case DOWN -> AABB_YN;
case UP -> AABB_YP;
case NORTH -> AABB_ZN;
case SOUTH -> AABB_ZP;
case WEST -> AABB_XN;
case EAST -> AABB_XP;
};
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
var normal = ctx.getClickedFace();
if (ctx.isSecondaryUseActive()) {
normal = normal.getOpposite();
}
var margin = getHoveredMargin(ctx.getClickLocation(), normal);
return this.defaultBlockState()
.setValue(FACING, normal)
.setValue(TOP_MARGIN, margin);
}
/**
* Calculate the little triangle overlay thing IE does.
*/
public static Margin getHoveredMargin(Vec3 clickPos, Direction normal) {
// uv cause textures
double u, v;
switch (normal.getAxis()) {
case X -> {
u = Mth.positiveModulo(clickPos.z * -1 * normal.getAxisDirection().getStep(), 1);
v = Mth.positiveModulo(clickPos.y, 1);
}
case Y -> {
// North is "up", so +v here ...
// and u is a quarter-turn to the right, so east.
u = Mth.positiveModulo(clickPos.x * normal.getAxisDirection().getStep(), 1);
v = Mth.positiveModulo(clickPos.z * -1 * normal.getAxisDirection().getStep(), 1);
}
case Z -> {
u = Mth.positiveModulo(clickPos.x * normal.getAxisDirection().getStep(), 1);
v = Mth.positiveModulo(clickPos.y, 1);
}
default -> throw new IllegalStateException();
}
u -= 0.5;
v -= 0.5;
// i have done some futzing around with desmos
// also i know this is shoddy code, but i've never found a good way to do this matrix of boolean thing,
// especially when I cant `match` on a tuple of booleans. so i'll just comment.
// this is true when the point is in an "hourglass", within two triangles radiating up and down...
var flag1 = Math.abs(u) < Math.abs(v) ? 1 : 0;
// ... and this is true in the upper-left diagonal half of the coordinate plane
var flag2 = u < v ? 1 : 0;
var id = (flag1 << 1) | flag2;
return switch (id) {
case 0b00 -> Margin.RIGHT;
case 0b01 -> Margin.LEFT;
case 0b10 -> Margin.BOTTOM;
case 0b11 -> Margin.TOP;
default -> throw new IllegalStateException();
};
}
public static BooleanProperty getConnectorProp(Margin margin) {
return switch (margin) {
case TOP -> TOP_CONNECTS;
case LEFT -> LEFT_CONNECTS;
case BOTTOM -> BOTTOM_CONNECTS;
case RIGHT -> RIGHT_CONNECTS;
};
}
}

View file

@ -0,0 +1,117 @@
package at.petrak.hexcasting.api.circles;
import com.mojang.datafixers.util.Pair;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Edges are named after their <i>normal vectors</i>. For example, {@code XP_YP} is the top-east
* corner of the block (positive x, positive y).
*/
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
public enum BlockEdge {
XP_YP(Direction.EAST, Direction.UP),
ZP_YP(Direction.UP, Direction.SOUTH),
XN_YP(Direction.WEST, Direction.UP),
ZN_YP(Direction.UP, Direction.NORTH),
XP_ZP(Direction.EAST, Direction.SOUTH),
XN_ZP(Direction.WEST, Direction.SOUTH),
XP_ZN(Direction.EAST, Direction.NORTH),
XN_ZN(Direction.WEST, Direction.NORTH),
XP_YN(Direction.EAST, Direction.DOWN),
ZP_YN(Direction.DOWN, Direction.SOUTH),
XN_YN(Direction.WEST, Direction.DOWN),
ZN_YN(Direction.DOWN, Direction.SOUTH);
private final Direction norm1, norm2;
BlockEdge(Direction norm1, Direction norm2) {
this.norm1 = norm1;
this.norm2 = norm2;
}
/**
* Get the edge from two normals. You can provide them in any order.
*/
public static BlockEdge fromNormals(Direction norm1, Direction norm2) {
for (var edge : values()) {
if (edge.norm1 == norm1 && edge.norm2 == norm2
|| edge.norm1 == norm2 && edge.norm2 == edge.norm1) {
return edge;
}
}
throw new IllegalStateException("Couldn't find " + norm1 + " & " + norm2);
}
/**
* Return the normals of this edge. They will be returned in order {@code X, Y, Z}.
*/
public Pair<Direction, Direction> getNormals() {
return Pair.of(this.norm1, this.norm2);
}
/**
* Rotate this many quarters of a block counter-clockwise around the given axis.
* "Counter-clockwise" is from the point of view of the axis, facing inwards. So, rotating once
* about the {@code Y} axis brings {@code XP_ZP} to {@code XP_ZN}.
*/
public BlockEdge rotateAbout(Direction.Axis axis, int quarters) {
quarters = Mth.positiveModulo(quarters, 4);
if (quarters == 0) {
return this;
}
// Oh lord, group theory that minecraft has fortunately already done for me.
Direction norm1$, norm2$;
switch (quarters) {
case 1 -> {
norm1$ = this.norm1.getCounterClockWise(axis);
norm2$ = this.norm2.getCounterClockWise(axis);
}
case 2 -> {
norm1$ = this.norm1.getAxis() == axis ? this.norm1 : this.norm1.getOpposite();
norm2$ = this.norm2.getAxis() == axis ? this.norm2 : this.norm2.getOpposite();
}
case 3 -> {
norm1$ = this.norm1.getClockWise(axis);
norm2$ = this.norm2.getClockWise(axis);
}
default -> throw new IllegalStateException();
}
return fromNormals(norm1$, norm2$);
}
/**
* Rotate this many quarters of a block counter-clockwise around the given normal.
* "Counter-clockwise" is from the point of view of the normal, facing outwards. So, rotating once
* about the {@code NORTH} axis (negative Z) brings {@code XP_ZP} to {@code YP_ZP}.
*/
public BlockEdge rotateAbout(Direction direction, int quarters) {
if (quarters % 4 == 0) {
return this;
}
return rotateAbout(direction.getAxis(),
direction.getAxisDirection() == Direction.AxisDirection.NEGATIVE ? -quarters : quarters);
}
/**
* Get if the given direction is one of this block's normals, and if so, return the other normal.
*/
@Nullable
public Direction getOtherNormal(Direction norm) {
if (this.norm1 == norm) {
return this.norm2;
} else if (this.norm2 == norm) {
return this.norm1;
} else {
return null;
}
}
}

View file

@ -0,0 +1,10 @@
package at.petrak.hexcasting.api.circles;
import at.petrak.hexcasting.api.spell.casting.FunctionalData;
import net.minecraft.core.BlockPos;
/**
* What this block passes onto the next block: what block it tries to exit to and where on that block it enters.
*/
public record FlowUpdate(FunctionalData newData, BlockPos nextPos, BlockEdge entryEdge) {
}

View file

@ -0,0 +1,33 @@
package at.petrak.hexcasting.api.circles;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import java.util.Set;
/**
* Getter for lots of information about the currently operating circle's state.
*/
public interface ICircleState {
BlockPos impetusPos();
Pair<BlockPos, BlockPos> circleBounds();
/**
* Block positions the circle has floodfilled for ahead of time, when starting the circle.
* <p>
* See {@link ICircleState#isPosValid} for more info.
*/
Set<BlockPos> allScannedPositions();
/**
* Whether this block position has been considered for being in the circle.
* <p>
* Anything this returns {@code false} for will fail the circle if it tries to output there.
* This will also confuse the player, a lot, as the intention is that once a ritual starts successfully either
* it encounters a normal mishap or finishes. So please code your circle components well.
*/
default boolean isPosValid(BlockPos pos) {
return allScannedPositions().contains(pos);
}
}

View file

@ -0,0 +1,88 @@
package at.petrak.hexcasting.api.circles;
import at.petrak.hexcasting.api.block.circle.BlockEntitySidedCircleWidget;
import net.minecraft.core.Direction;
import net.minecraft.util.StringRepresentable;
import org.jetbrains.annotations.NotNull;
/**
* A helper for {@link BlockEntitySidedCircleWidget BlockSidedCircleComponent}:
* an abstraction over direction to only the 4 edges relevant to it.
* <p>
* When the normal dir is horizontal, {@code TOP} corresponds to {@code Direction.UP}, and {@code LEFT} and
* {@code RIGHT} are to the left and to the right from the POV of <i>facing</i> that normal.
* So, {@code Direction.NORTH}'s {@code RIGHT} is {@code Direction.WEST}.
* <p>
* When the normal dir is {@code Direction.UP} or {@code Direction.DOWN}, {@code TOP} corresponds to
* {@code Direction.EAST}, cause we gotta pick something and +X seems reasonable. {@code Margin} always proceeds
* counter-clockwise, so {@code Direction.UP}'s {@code RIGHT} is {@code Direction.EAST} and {@code Direction.DOWN}'s
* {@code RIGHT} is {@code Direction.WEST}.
* <p>
* Things are additionally complicated because a {@code BlockSidedCircleComponent} can <i>also</i> have its TOP
* dir facing any of the 4 margins, but that's a whole nother can of worms and I'm tired of typing doc comments.
* Javac can yell a warning for not writing a doc comment, but it can't warn me for writing a <b>bad</b> one!
*/
public enum Margin implements StringRepresentable {
TOP("top"),
LEFT("left"),
BOTTOM("bottom"),
RIGHT("right");
private final String serializedName;
Margin(String serializedName) {
this.serializedName = serializedName;
}
/**
* Return which margin the given direction points as, given a normal vector.
* <p>
* Throws an exception if the two directions are on the same axis.
*/
public static Margin fromNormalAndDir(Direction normal, Direction dir) throws IllegalArgumentException {
var normalAxis = normal.getAxis();
var cursor = switch (normalAxis) {
case X, Z -> Direction.UP;
case Y -> Direction.NORTH;
};
for (var margin : Margin.values()) {
if (cursor == dir) {
return margin;
}
cursor = normal.getAxisDirection() == Direction.AxisDirection.NEGATIVE
? cursor.getClockWise(normalAxis)
: cursor.getCounterClockWise(normalAxis);
}
throw new IllegalArgumentException("Two directions with equal axes. Normal: " + normal + "; dir: " + dir);
}
/**
* Convert this direction to the (absolute) edge given the normal vector of the block it's on.
*/
public BlockEdge toEdge(Direction normal) {
var topDir = switch (normal.getAxis()) {
case X, Z -> Direction.UP;
case Y -> Direction.NORTH;
};
var topEdge = BlockEdge.fromNormals(normal.getOpposite(), topDir);
return topEdge.rotateAbout(normal, this.ordinal());
}
/**
* If we consider {@code this} to be the "local {@code TOP}", transform {@code that} to face in the
* appropriate direction.
* <p>
* Actually, I'm pretty sure this is transitive, so it doesn't matter which one you put as the receiver or argument.
*/
public Margin transform(Margin that) {
return Margin.values()[(this.ordinal() + that.ordinal()) % Margin.values().length];
}
@Override
public @NotNull String getSerializedName() {
return this.serializedName;
}
}

View file

@ -0,0 +1,24 @@
package at.petrak.hexcasting.api.spell.mishaps
import at.petrak.hexcasting.api.misc.FrozenColorizer
import at.petrak.hexcasting.api.spell.casting.CastingContext
import at.petrak.hexcasting.api.spell.iota.Iota
import net.minecraft.network.chat.Component
/**
* Probably should never be thrown, but happens if a spell circle's flow is messed up during execution somehow.
*/
class MishapMessedUpSpellCircle : Mishap() {
override fun accentColor(ctx: CastingContext, errorCtx: Context): FrozenColorizer {
TODO("Not yet implemented")
}
override fun execute(ctx: CastingContext, errorCtx: Context, stack: MutableList<Iota>) {
TODO("Not yet implemented")
}
override fun errorMessage(ctx: CastingContext, errorCtx: Context): Component {
TODO("Not yet implemented")
}
}

View file

@ -1,82 +0,0 @@
package at.petrak.hexcasting.common.blocks.circles;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.spell.math.HexPattern;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
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 net.minecraft.world.level.material.PushReaction;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
// As it turns out, not actually an impetus
public class BlockEmptyImpetus extends BlockCircleComponent {
public static final DirectionProperty FACING = BlockStateProperties.FACING;
public BlockEmptyImpetus(Properties p_49795_) {
super(p_49795_);
this.registerDefaultState(this.stateDefinition.any()
.setValue(ENERGIZED, false).setValue(FACING, Direction.NORTH));
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level world) {
return enterDir != bs.getValue(FACING);
}
@Override
public EnumSet<Direction> exitDirections(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);
}
@Override
public float particleHeight(BlockPos pos, BlockState bs, Level world) {
return 0.5f;
}
@Override
public PushReaction getPistonPushReaction(BlockState pState) {
return PushReaction.BLOCK;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(FACING);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext pContext) {
return this.defaultBlockState().setValue(FACING, pContext.getNearestLookingDirection());
}
@Override
public BlockState rotate(BlockState pState, Rotation pRot) {
return pState.setValue(FACING, pRot.rotate(pState.getValue(FACING)));
}
@Override
public BlockState mirror(BlockState pState, Mirror pMirror) {
return pState.rotate(pMirror.getRotation(pState.getValue(FACING)));
}
}

View file

@ -1,45 +0,0 @@
package at.petrak.hexcasting.common.blocks.circles;
import at.petrak.hexcasting.api.block.HexBlockEntity;
import at.petrak.hexcasting.api.spell.math.HexPattern;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
public class BlockEntitySlate extends HexBlockEntity {
public static final String TAG_PATTERN = "pattern";
@Nullable
public HexPattern pattern;
public BlockEntitySlate(BlockPos pos, BlockState state) {
super(HexBlockEntities.SLATE_TILE, pos, state);
}
@Override
protected void saveModData(CompoundTag tag) {
if (this.pattern != null) {
tag.put(TAG_PATTERN, this.pattern.serializeToNBT());
} else {
tag.put(TAG_PATTERN, new CompoundTag());
}
}
@Override
protected void loadModData(CompoundTag tag) {
if (tag.contains(TAG_PATTERN, Tag.TAG_COMPOUND)) {
CompoundTag patternTag = tag.getCompound(TAG_PATTERN);
if (HexPattern.isPattern(patternTag)) {
this.pattern = HexPattern.fromNBT(patternTag);
} else {
this.pattern = null;
}
} else {
this.pattern = null;
}
}
}

View file

@ -1,217 +0,0 @@
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.spell.iota.PatternIota;
import at.petrak.hexcasting.api.spell.math.HexPattern;
import at.petrak.hexcasting.common.lib.HexItems;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.*;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import java.util.EnumSet;
// When on the floor or ceiling FACING is the direction the *bottom* of the pattern points
// (or which way is "down").
// When on the wall FACING is the direction of the *front* of the block
public class BlockSlate extends BlockCircleComponent implements EntityBlock, SimpleWaterloggedBlock {
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public static final EnumProperty<AttachFace> ATTACH_FACE = BlockStateProperties.ATTACH_FACE;
public static final double THICKNESS = 1;
public static final VoxelShape AABB_FLOOR = Block.box(0, 0, 0, 16, THICKNESS, 16);
public static final VoxelShape AABB_CEILING = Block.box(0, 16 - THICKNESS, 0, 16, 16, 16);
public static final VoxelShape AABB_EAST_WALL = Block.box(0, 0, 0, THICKNESS, 16, 16);
public static final VoxelShape AABB_WEST_WALL = Block.box(16 - THICKNESS, 0, 0, 16, 16, 16);
public static final VoxelShape AABB_SOUTH_WALL = Block.box(0, 0, 0, 16, 16, THICKNESS);
public static final VoxelShape AABB_NORTH_WALL = Block.box(0, 0, 16 - THICKNESS, 16, 16, 16);
public BlockSlate(Properties p_53182_) {
super(p_53182_);
this.registerDefaultState(
this.stateDefinition.any()
.setValue(ENERGIZED, false)
.setValue(FACING, Direction.NORTH)
.setValue(WATERLOGGED, false));
}
@Override
public boolean propagatesSkylightDown(BlockState state, @Nonnull BlockGetter reader, @Nonnull BlockPos pos) {
return !state.getValue(WATERLOGGED);
}
@Nonnull
@Override
public FluidState getFluidState(BlockState state) {
return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state);
}
@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;
}
@Override
public EnumSet<Direction> exitDirections(BlockPos pos, BlockState bs, Level world) {
var allDirs = EnumSet.allOf(Direction.class);
var normal = this.normalDir(pos, bs, world);
allDirs.remove(normal);
allDirs.remove(normal.getOpposite());
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) {
BlockEntity be = level.getBlockEntity(pos);
if (be instanceof BlockEntitySlate slate) {
ItemStack stack = new ItemStack(HexItems.SLATE);
if (slate.pattern != null) {
HexItems.SLATE.writeDatum(stack, new PatternIota(slate.pattern));
}
return stack;
}
return new ItemStack(this);
}
@Override
public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft) {
return switch (bs.getValue(ATTACH_FACE)) {
case FLOOR -> Direction.UP;
case CEILING -> Direction.DOWN;
case WALL -> bs.getValue(FACING);
};
}
@Override
public float particleHeight(BlockPos pos, BlockState bs, Level world) {
return 0.5f - 15f / 16f;
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) {
return new BlockEntitySlate(pPos, pState);
}
@Override
public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) {
return switch (pState.getValue(ATTACH_FACE)) {
case FLOOR -> AABB_FLOOR;
case CEILING -> AABB_CEILING;
case WALL -> switch (pState.getValue(FACING)) {
case NORTH -> AABB_NORTH_WALL;
case EAST -> AABB_EAST_WALL;
case SOUTH -> AABB_SOUTH_WALL;
// NORTH; up and down don't happen (but we need branches for them)
default -> AABB_WEST_WALL;
};
};
}
@Override
public PushReaction getPistonPushReaction(BlockState pState) {
return PushReaction.DESTROY;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(FACING, ATTACH_FACE, WATERLOGGED);
}
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext pContext) {
FluidState fluidState = pContext.getLevel().getFluidState(pContext.getClickedPos());
for (Direction direction : pContext.getNearestLookingDirections()) {
BlockState blockstate;
if (direction.getAxis() == Direction.Axis.Y) {
blockstate = this.defaultBlockState()
.setValue(ATTACH_FACE, direction == Direction.UP ? AttachFace.CEILING : AttachFace.FLOOR)
.setValue(FACING, pContext.getHorizontalDirection().getOpposite());
} else {
blockstate = this.defaultBlockState()
.setValue(ATTACH_FACE, AttachFace.WALL)
.setValue(FACING, direction.getOpposite());
}
blockstate = blockstate.setValue(WATERLOGGED,
fluidState.is(FluidTags.WATER) && fluidState.getAmount() == 8);
if (blockstate.canSurvive(pContext.getLevel(), pContext.getClickedPos())) {
return blockstate;
}
}
return null;
}
// i do as the FaceAttachedHorizontalDirectionBlock.java guides
@Override
public boolean canSurvive(BlockState pState, LevelReader pLevel, BlockPos pPos) {
return canAttach(pLevel, pPos, getConnectedDirection(pState).getOpposite());
}
@Override
public BlockState updateShape(BlockState pState, Direction pFacing, BlockState pFacingState, LevelAccessor pLevel,
BlockPos pCurrentPos, BlockPos pFacingPos) {
if (pState.getValue(WATERLOGGED)) {
pLevel.scheduleTick(pCurrentPos, Fluids.WATER, Fluids.WATER.getTickDelay(pLevel));
}
return getConnectedDirection(pState).getOpposite() == pFacing
&& !pState.canSurvive(pLevel, pCurrentPos) ?
pState.getFluidState().createLegacyBlock()
: super.updateShape(pState, pFacing, pFacingState, pLevel, pCurrentPos, pFacingPos);
}
public static boolean canAttach(LevelReader pReader, BlockPos pPos, Direction pDirection) {
BlockPos blockpos = pPos.relative(pDirection);
return pReader.getBlockState(blockpos).isFaceSturdy(pReader, blockpos, pDirection.getOpposite());
}
protected static Direction getConnectedDirection(BlockState pState) {
return switch (pState.getValue(ATTACH_FACE)) {
case CEILING -> Direction.DOWN;
case FLOOR -> Direction.UP;
default -> pState.getValue(FACING);
};
}
}

View file

@ -1,79 +0,0 @@
package at.petrak.hexcasting.common.blocks.circles.directrix;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.spell.math.HexPattern;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Rotation;
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.EnumProperty;
import net.minecraft.world.level.material.PushReaction;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
public class BlockEmptyDirectrix extends BlockCircleComponent {
public static final EnumProperty<Direction.Axis> AXIS = BlockStateProperties.AXIS;
public BlockEmptyDirectrix(Properties p_49795_) {
super(p_49795_);
this.registerDefaultState(this.stateDefinition.any()
.setValue(ENERGIZED, false)
.setValue(AXIS, Direction.Axis.X));
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level 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;
}
@Override
public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft) {
return Direction.UP;
}
@Override
public float particleHeight(BlockPos pos, BlockState bs, Level world) {
return 0.5f;
}
@Override
public PushReaction getPistonPushReaction(BlockState pState) {
return PushReaction.BLOCK;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(AXIS);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext pContext) {
return this.defaultBlockState().setValue(AXIS, pContext.getNearestLookingDirection().getAxis());
}
@Override
public BlockState rotate(BlockState pState, Rotation pRot) {
return pState.setValue(AXIS,
pRot.rotate(Direction.get(Direction.AxisDirection.POSITIVE, pState.getValue(AXIS))).getAxis());
}
}

View file

@ -1,127 +0,0 @@
package at.petrak.hexcasting.common.blocks.circles.directrix;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.spell.math.HexPattern;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
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.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.Random;
public class BlockRedstoneDirectrix extends BlockCircleComponent {
public static final DirectionProperty FACING = BlockStateProperties.FACING;
public static final BooleanProperty REDSTONE_POWERED = BlockStateProperties.POWERED;
public BlockRedstoneDirectrix(Properties p_49795_) {
super(p_49795_);
this.registerDefaultState(this.stateDefinition.any()
.setValue(REDSTONE_POWERED, false)
.setValue(ENERGIZED, false)
.setValue(FACING, Direction.NORTH));
}
@Override
public boolean canEnterFromDirection(Direction enterDir, Direction normalDir, BlockPos pos, BlockState bs,
Level 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;
}
@Override
public Direction normalDir(BlockPos pos, BlockState bs, Level world, int recursionLeft) {
return normalDirOfOther(pos.relative(getRealFacing(bs)), world, recursionLeft);
}
@Override
public float particleHeight(BlockPos pos, BlockState bs, Level world) {
return 0.5f;
}
protected Direction getRealFacing(BlockState bs) {
var facing = bs.getValue(FACING);
if (bs.getValue(REDSTONE_POWERED)) {
return facing.getOpposite();
} else {
return facing;
}
}
@Override
public void neighborChanged(BlockState pState, Level pLevel, BlockPos pPos, Block pBlock, BlockPos pFromPos,
boolean pIsMoving) {
super.neighborChanged(pState, pLevel, pPos, pBlock, pFromPos, pIsMoving);
if (!pLevel.isClientSide) {
boolean currentlyPowered = pState.getValue(REDSTONE_POWERED);
if (currentlyPowered != pLevel.hasNeighborSignal(pPos)) {
pLevel.setBlock(pPos, pState.setValue(REDSTONE_POWERED, !currentlyPowered), 2);
}
}
}
@Override
public void animateTick(BlockState bs, Level pLevel, BlockPos pos, Random rand) {
if (bs.getValue(REDSTONE_POWERED)) {
for (int i = 0; i < 2; i++) {
var step = bs.getValue(FACING).getOpposite().step();
var center = Vec3.atCenterOf(pos).add(step.x() * 0.5, step.y() * 0.5, step.z() * 0.5);
double x = center.x + (rand.nextDouble() - 0.5) * 0.5D;
double y = center.y + (rand.nextDouble() - 0.5) * 0.5D;
double z = center.z + (rand.nextDouble() - 0.5) * 0.5D;
pLevel.addParticle(DustParticleOptions.REDSTONE, x, y, z,
step.x() * 0.1, step.y() * 0.1, step.z() * 0.1);
}
}
}
@Override
public PushReaction getPistonPushReaction(BlockState pState) {
return PushReaction.BLOCK;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(REDSTONE_POWERED, FACING);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext pContext) {
return this.defaultBlockState().setValue(FACING, pContext.getNearestLookingDirection());
}
@Override
public BlockState rotate(BlockState pState, Rotation pRot) {
return pState.setValue(FACING, pRot.rotate(pState.getValue(FACING)));
}
@Override
public BlockState mirror(BlockState pState, Mirror pMirror) {
return pState.rotate(pMirror.getRotation(pState.getValue(FACING)));
}
}

View file

@ -1,45 +0,0 @@
package at.petrak.hexcasting.common.blocks.circles.impetuses;
import at.petrak.hexcasting.api.block.circle.BlockAbstractImpetus;
import at.petrak.hexcasting.common.blocks.entity.BlockEntityLookingImpetus;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
public class BlockLookingImpetus extends BlockAbstractImpetus {
public BlockLookingImpetus(Properties p_49795_) {
super(p_49795_);
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) {
return new BlockEntityLookingImpetus(pPos, pState);
}
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level pLevel, BlockState pState,
BlockEntityType<T> type) {
if (!pLevel.isClientSide) {
return createTickerHelper(type, HexBlockEntities.IMPETUS_LOOK_TILE,
BlockEntityLookingImpetus::serverTick);
} else {
return null;
}
}
// uegh
@Nullable
@SuppressWarnings("unchecked")
protected static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A> createTickerHelper(
BlockEntityType<A> type, BlockEntityType<E> targetType, BlockEntityTicker<? super E> ticker) {
return targetType == type ? (BlockEntityTicker<A>) ticker : null;
}
}

View file

@ -1,41 +0,0 @@
package at.petrak.hexcasting.common.blocks.circles.impetuses;
import at.petrak.hexcasting.api.block.circle.BlockAbstractImpetus;
import at.petrak.hexcasting.common.blocks.entity.BlockEntityRightClickImpetus;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
public class BlockRightClickImpetus extends BlockAbstractImpetus {
public BlockRightClickImpetus(Properties p_49795_) {
super(p_49795_);
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) {
return new BlockEntityRightClickImpetus(pPos, pState);
}
@Override
public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand,
BlockHitResult pHit) {
if (!pPlayer.isShiftKeyDown()) {
var tile = pLevel.getBlockEntity(pPos);
if (tile instanceof BlockEntityRightClickImpetus impetus) {
if (pPlayer instanceof ServerPlayer serverPlayer) {
impetus.activateSpellCircle(serverPlayer);
}
return InteractionResult.SUCCESS;
}
}
return InteractionResult.PASS;
}
}

View file

@ -1,104 +0,0 @@
package at.petrak.hexcasting.common.blocks.circles.impetuses;
import at.petrak.hexcasting.api.block.circle.BlockAbstractImpetus;
import at.petrak.hexcasting.api.spell.iota.EntityIota;
import at.petrak.hexcasting.common.blocks.entity.BlockEntityStoredPlayerImpetus;
import at.petrak.hexcasting.common.lib.HexSounds;
import at.petrak.hexcasting.xplat.IXplatAbstractions;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
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.BooleanProperty;
import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
public class BlockStoredPlayerImpetus extends BlockAbstractImpetus {
public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
public BlockStoredPlayerImpetus(Properties p_49795_) {
super(p_49795_);
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pPos, BlockState pState) {
return new BlockEntityStoredPlayerImpetus(pPos, pState);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(POWERED);
}
@Override
public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand,
BlockHitResult pHit) {
if (pLevel.getBlockEntity(pPos) instanceof BlockEntityStoredPlayerImpetus tile) {
var usedStack = pPlayer.getItemInHand(pHand);
var datumContainer = IXplatAbstractions.INSTANCE.findDataHolder(usedStack);
if (datumContainer != null) {
if (pLevel instanceof ServerLevel level) {
var stored = datumContainer.readIota(level);
if (stored instanceof EntityIota eieio) {
var entity = eieio.getEntity();
if (entity instanceof Player player) {
// phew, we got something
tile.setPlayer(player.getGameProfile(), entity.getUUID());
level.sendBlockUpdated(pPos, pState, pState, Block.UPDATE_CLIENTS);
pLevel.playSound(pPlayer, pPos, HexSounds.IMPETUS_STOREDPLAYER_DING,
SoundSource.BLOCKS, 1f, 1f);
}
}
}
return InteractionResult.SUCCESS;
}
}
return InteractionResult.PASS;
}
@Override
public void tick(BlockState pState, ServerLevel pLevel, BlockPos pPos, Random pRandom) {
super.tick(pState, pLevel, pPos, pRandom);
if (pLevel.getBlockEntity(pPos) instanceof BlockEntityStoredPlayerImpetus tile) {
tile.updatePlayerProfile();
}
}
@Override
public void neighborChanged(BlockState pState, Level pLevel, BlockPos pPos, Block pBlock, BlockPos pFromPos,
boolean pIsMoving) {
super.neighborChanged(pState, pLevel, pPos, pBlock, pFromPos, pIsMoving);
if (!pLevel.isClientSide()) {
boolean prevPowered = pState.getValue(POWERED);
boolean isPowered = pLevel.hasNeighborSignal(pPos);
if (prevPowered != isPowered) {
pLevel.setBlockAndUpdate(pPos, pState.setValue(POWERED, isPowered));
if (isPowered && pLevel.getBlockEntity(pPos) instanceof BlockEntityStoredPlayerImpetus tile) {
var player = tile.getStoredPlayer();
if (player instanceof ServerPlayer splayer) {
// phew
tile.activateSpellCircle(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.block.circle.BlockEntityCircleWidget;
import at.petrak.hexcasting.common.lib.HexBlockEntities;
import at.petrak.hexcasting.common.lib.HexSounds;
import net.minecraft.core.BlockPos;
@ -34,7 +34,7 @@ public class BlockEntityLookingImpetus extends BlockEntityAbstractImpetus {
// 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)) {
if (bs.getValue(BlockEntityCircleWidget.ENERGIZED)) {
return;
}

View file

@ -1,19 +1,11 @@
package at.petrak.hexcasting.common.lib;
import at.petrak.hexcasting.api.block.circle.BlockAbstractImpetus;
import at.petrak.hexcasting.common.blocks.BlockConjured;
import at.petrak.hexcasting.common.blocks.BlockConjuredLight;
import at.petrak.hexcasting.common.blocks.BlockFlammable;
import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicBookshelf;
import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicLigature;
import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicRecord;
import at.petrak.hexcasting.common.blocks.circles.BlockEmptyImpetus;
import at.petrak.hexcasting.common.blocks.circles.BlockSlate;
import at.petrak.hexcasting.common.blocks.circles.directrix.BlockEmptyDirectrix;
import at.petrak.hexcasting.common.blocks.circles.directrix.BlockRedstoneDirectrix;
import at.petrak.hexcasting.common.blocks.circles.impetuses.BlockLookingImpetus;
import at.petrak.hexcasting.common.blocks.circles.impetuses.BlockRightClickImpetus;
import at.petrak.hexcasting.common.blocks.circles.impetuses.BlockStoredPlayerImpetus;
import at.petrak.hexcasting.common.blocks.decoration.*;
import com.mojang.datafixers.util.Pair;
import net.minecraft.resources.ResourceLocation;
@ -118,6 +110,7 @@ public class HexBlocks {
.isViewBlocking(HexBlocks::never)),
new Item.Properties());
/*
// "no" item because we add it manually
public static final BlockSlate SLATE = blockNoItem("slate", new BlockSlate(slateish()));
@ -137,6 +130,7 @@ public class HexBlocks {
new BlockEmptyDirectrix(slateish()));
public static final BlockRedstoneDirectrix DIRECTRIX_REDSTONE = blockItem("directrix_redstone",
new BlockRedstoneDirectrix(slateish()));
*/
public static final BlockAkashicRecord AKASHIC_RECORD = blockItem("akashic_record",
new BlockAkashicRecord(akashicWoodyHard().lightLevel(bs -> 15)));

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"texture_size": [32, 32],
"textures": {
"particle": "hexcasting:block/circle/common",
"common": "hexcasting:block/circle/common",
"core": "hexcasting:block/circle/impetus"
},
"elements": [
{
"name": "body",
"from": [2, 0, 2],
"to": [14, 4, 14],
"faces": {
"north": {"uv": [0, 1, 6, 3], "texture": "#common"},
"east": {"uv": [0, 1, 6, 3], "texture": "#common"},
"south": {"uv": [0, 1, 6, 3], "texture": "#common"},
"west": {"uv": [0, 1, 6, 3], "texture": "#common"},
"up": {"uv": [2, 2, 14, 14], "texture": "#core"},
"down": {"uv": [9, 1, 15, 7], "rotation": 270, "texture": "#common"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 45, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 225, 0],
"scale": [0.4, 0.4, 0.4]
},
"ground": {
"translation": [0, 3, 0],
"scale": [0.25, 0.25, 0.25]
},
"gui": {
"rotation": [30, 225, 0],
"scale": [0.625, 0.625, 0.625]
},
"fixed": {
"scale": [0.5, 0.5, 0.5]
}
}
}

View file

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"texture_size": [32, 32],
"textures": {
"particle": "hexcasting:block/circle/common",
"common": "hexcasting:block/circle/common",
"core": "hexcasting:block/circle/impetus"
},
"elements": [
{
"name": "east",
"from": [14, 0, 4],
"to": [16, 2, 12],
"faces": {
"north": {"uv": [14, 2, 16, 4], "texture": "#core"},
"east": {"uv": [0, 0, 4, 1], "texture": "#common"},
"south": {"uv": [16, 2, 14, 4], "texture": "#core"},
"west": {"uv": [0, 0, 4, 1], "texture": "#missing"},
"up": {"uv": [14, 4, 16, 12], "texture": "#core"},
"down": {"uv": [14, 4, 16, 12], "rotation": 180, "texture": "#core"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 45, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 225, 0],
"scale": [0.4, 0.4, 0.4]
},
"ground": {
"translation": [0, 3, 0],
"scale": [0.25, 0.25, 0.25]
},
"gui": {
"rotation": [30, 225, 0],
"scale": [0.625, 0.625, 0.625]
},
"fixed": {
"scale": [0.5, 0.5, 0.5]
}
}
}

View file

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"texture_size": [32, 32],
"textures": {
"particle": "hexcasting:block/circle/common",
"common": "hexcasting:block/circle/common",
"core": "hexcasting:block/circle/impetus"
},
"elements": [
{
"name": "north",
"from": [4, 0, 0],
"to": [12, 2, 2],
"faces": {
"north": {"uv": [0, 0, 4, 1], "texture": "#common"},
"east": {"uv": [2, 0, 4, 2], "texture": "#core"},
"south": {"uv": [0, 0, 4, 1], "texture": "#missing"},
"west": {"uv": [2, 0, 4, 2], "texture": "#core"},
"up": {"uv": [4, 0, 12, 2], "texture": "#core"},
"down": {"uv": [4, 0, 12, 2], "rotation": 180, "texture": "#core"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 45, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 225, 0],
"scale": [0.4, 0.4, 0.4]
},
"ground": {
"translation": [0, 3, 0],
"scale": [0.25, 0.25, 0.25]
},
"gui": {
"rotation": [30, 225, 0],
"scale": [0.625, 0.625, 0.625]
},
"fixed": {
"scale": [0.5, 0.5, 0.5]
}
}
}

View file

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"texture_size": [32, 32],
"textures": {
"particle": "hexcasting:block/circle/common",
"common": "hexcasting:block/circle/common",
"core": "hexcasting:block/circle/impetus"
},
"elements": [
{
"name": "south",
"from": [4, 0, 14],
"to": [12, 2, 16],
"faces": {
"north": {"uv": [0, 0, 4, 1], "texture": "#missing"},
"east": {"uv": [2, 14, 4, 16], "texture": "#core"},
"south": {"uv": [0, 0, 4, 1], "texture": "#common"},
"west": {"uv": [2, 14, 4, 16], "texture": "#core"},
"up": {"uv": [4, 0, 12, 2], "texture": "#core"},
"down": {"uv": [4, 14, 12, 16], "rotation": 180, "texture": "#core"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 45, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 225, 0],
"scale": [0.4, 0.4, 0.4]
},
"ground": {
"translation": [0, 3, 0],
"scale": [0.25, 0.25, 0.25]
},
"gui": {
"rotation": [30, 225, 0],
"scale": [0.625, 0.625, 0.625]
},
"fixed": {
"scale": [0.5, 0.5, 0.5]
}
}
}

View file

@ -0,0 +1,55 @@
{
"credit": "Made with Blockbench",
"texture_size": [32, 32],
"textures": {
"particle": "hexcasting:block/circle/common",
"common": "hexcasting:block/circle/common",
"core": "hexcasting:block/circle/impetus"
},
"elements": [
{
"name": "west",
"from": [0, 0, 4],
"to": [2, 2, 12],
"faces": {
"north": {"uv": [0, 2, 2, 4], "texture": "#core"},
"east": {"uv": [0, 0, 4, 1], "texture": "#missing"},
"south": {"uv": [0, 2, 2, 4], "texture": "#core"},
"west": {"uv": [0, 0, 4, 1], "texture": "#common"},
"up": {"uv": [0, 4, 2, 12], "texture": "#core"},
"down": {"uv": [0, 4, 2, 12], "rotation": 180, "texture": "#core"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 45, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 225, 0],
"scale": [0.4, 0.4, 0.4]
},
"ground": {
"translation": [0, 3, 0],
"scale": [0.25, 0.25, 0.25]
},
"gui": {
"rotation": [30, 225, 0],
"scale": [0.625, 0.625, 0.625]
},
"fixed": {
"scale": [0.5, 0.5, 0.5]
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

View file

@ -1,6 +1,7 @@
package at.petrak.hexcasting.fabric.cc;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.block.circle.BlockEntitySidedCircleWidget;
import at.petrak.hexcasting.api.item.ColorizerItem;
import at.petrak.hexcasting.api.item.HexHolderItem;
import at.petrak.hexcasting.api.item.IotaHolderItem;
@ -8,10 +9,9 @@ import at.petrak.hexcasting.api.item.MediaHolderItem;
import at.petrak.hexcasting.api.mod.HexConfig;
import at.petrak.hexcasting.api.spell.iota.DoubleIota;
import at.petrak.hexcasting.common.lib.HexItems;
import at.petrak.hexcasting.fabric.cc.adimpl.CCColorizer;
import at.petrak.hexcasting.fabric.cc.adimpl.CCHexHolder;
import at.petrak.hexcasting.fabric.cc.adimpl.CCIotaHolder;
import at.petrak.hexcasting.fabric.cc.adimpl.CCMediaHolder;
import at.petrak.hexcasting.fabric.cc.adimpl.*;
import dev.onyxstudios.cca.api.v3.block.BlockComponentFactoryRegistry;
import dev.onyxstudios.cca.api.v3.block.BlockComponentInitializer;
import dev.onyxstudios.cca.api.v3.component.ComponentKey;
import dev.onyxstudios.cca.api.v3.component.ComponentRegistry;
import dev.onyxstudios.cca.api.v3.entity.EntityComponentFactoryRegistry;
@ -25,7 +25,7 @@ import net.minecraft.world.item.Items;
import static at.petrak.hexcasting.api.HexAPI.modLoc;
public class HexCardinalComponents implements EntityComponentInitializer, ItemComponentInitializer {
public class HexCardinalComponents implements EntityComponentInitializer, ItemComponentInitializer, BlockComponentInitializer {
// entities
public static final ComponentKey<CCBrainswept> BRAINSWEPT = ComponentRegistry.getOrCreate(modLoc("brainswept"),
CCBrainswept.class);
@ -48,6 +48,9 @@ public class HexCardinalComponents implements EntityComponentInitializer, ItemCo
CCMediaHolder.class);
public static final ComponentKey<CCHexHolder> HEX_HOLDER = ComponentRegistry.getOrCreate(modLoc("hex_holder"),
CCHexHolder.class);
public static final ComponentKey<CCCircleWidget> CIRCLE_WIDGET = ComponentRegistry.getOrCreate(
modLoc("circle_widget"),
CCCircleWidget.class);
@Override
public void registerEntityComponentFactories(EntityComponentFactoryRegistry registry) {
@ -83,4 +86,11 @@ public class HexCardinalComponents implements EntityComponentInitializer, ItemCo
registry.register(i -> i instanceof HexHolderItem, HEX_HOLDER, CCHexHolder.ItemBased::new);
}
@Override
public void registerBlockComponentFactories(BlockComponentFactoryRegistry registry) {
registry.registerFor(BlockEntitySidedCircleWidget.class, CIRCLE_WIDGET,
CCCircleWidget.SidedCircleWidgetBased::new);
}
}

View file

@ -0,0 +1,62 @@
package at.petrak.hexcasting.fabric.cc.adimpl;
import at.petrak.hexcasting.api.addldata.ADCircleWidget;
import at.petrak.hexcasting.api.block.circle.BlockEntitySidedCircleWidget;
import at.petrak.hexcasting.api.circles.BlockEdge;
import at.petrak.hexcasting.api.circles.FlowUpdate;
import at.petrak.hexcasting.api.circles.ICircleState;
import dev.onyxstudios.cca.api.v3.component.Component;
import dev.onyxstudios.cca.api.v3.component.sync.AutoSyncedComponent;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
public abstract class CCCircleWidget implements ADCircleWidget, Component, AutoSyncedComponent {
// The serde methods here are no-ops because any and all state should be synced by the implementor.
@Override
public void readFromNbt(CompoundTag tag) {
// NO-OP
}
@Override
public void writeToNbt(CompoundTag tag) {
// NO-OP
}
public static class SidedCircleWidgetBased extends CCCircleWidget {
private final BlockEntitySidedCircleWidget inner;
public SidedCircleWidgetBased(BlockEntitySidedCircleWidget inner) {
this.inner = inner;
}
@Override
public boolean acceptsFlow(BlockEdge edge) {
var margin = inner.getMargin(edge);
if (margin == null) {
return false;
}
return inner.acceptsFlow(margin);
}
@Override
public EnumSet<BlockEdge> possibleExitDirs(BlockEdge inputEdge) {
var margin = inner.getMargin(inputEdge);
if (margin == null) {
return EnumSet.noneOf(BlockEdge.class);
}
return inner.possibleExitDirs(margin);
}
@Override
public @Nullable FlowUpdate onReceiveFlow(BlockEdge inputEdge, BlockEntity sender, ICircleState state) {
var margin = inner.getMargin(inputEdge);
if (margin == null) {
return null;
}
return inner.onReceiveFlow(margin, sender, state);
}
}
}

View file

@ -69,7 +69,8 @@
"hexcasting:colorizer",
"hexcasting:iota_holder",
"hexcasting:media_holder",
"hexcasting:hex_holder"
"hexcasting:hex_holder",
"hexcasting:circle_widget"
]
}
}

View file

@ -1,10 +1,10 @@
package at.petrak.hexcasting.forge.cap;
import at.petrak.hexcasting.api.addldata.ADColorizer;
import at.petrak.hexcasting.api.addldata.ADHexHolder;
import at.petrak.hexcasting.api.addldata.ADIotaHolder;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.block.circle.BlockEntityAbstractImpetus;
import at.petrak.hexcasting.api.addldata.*;
import at.petrak.hexcasting.api.block.circle.BlockEntitySidedCircleWidget;
import at.petrak.hexcasting.api.circles.BlockEdge;
import at.petrak.hexcasting.api.circles.FlowUpdate;
import at.petrak.hexcasting.api.circles.ICircleState;
import at.petrak.hexcasting.api.item.ColorizerItem;
import at.petrak.hexcasting.api.item.HexHolderItem;
import at.petrak.hexcasting.api.item.IotaHolderItem;
@ -28,10 +28,10 @@ import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullSupplier;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.items.CapabilityItemHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import java.util.function.BooleanSupplier;
@ -65,6 +65,10 @@ public class ForgeCapabilityHandler {
* Items that work as pigments.
*/
public static final ResourceLocation PIGMENT_CAP = modLoc("colorizer");
/**
* Block entities that work as circle widgets.
*/
public static final ResourceLocation CIRCLE_WIDGET_CAP = modLoc("circle_widget");
private static final ResourceLocation IMPETUS_HANDLER = modLoc("impetus_items");
@ -114,10 +118,19 @@ public class ForgeCapabilityHandler {
}
public static void attachBlockEntityCaps(AttachCapabilitiesEvent<BlockEntity> evt) {
// TODO
/*
if (evt.getObject() instanceof BlockEntityAbstractImpetus impetus) {
evt.addCapability(IMPETUS_HANDLER, provide(impetus, CapabilityItemHandler.ITEM_HANDLER_CAPABILITY,
() -> new ForgeImpetusCapability(impetus)));
}
*/
var be = evt.getObject();
if (be instanceof BlockEntitySidedCircleWidget bescw) {
evt.addCapability(CIRCLE_WIDGET_CAP,
provide(be, HexCapabilities.CIRCLE_WIDGET, () -> new BESidedCircleWidgetBased(bescw, be)));
}
}
private static <CAP> SimpleProvider<CAP> provide(BlockEntity be, Capability<CAP> capability,
@ -338,4 +351,35 @@ public class ForgeCapabilityHandler {
return holder.color(stack, owner, time, position);
}
}
// TODO can we *please* stop putting all of these records in this god object please
private record BESidedCircleWidgetBased(BlockEntitySidedCircleWidget inner,
BlockEntity be) implements ADCircleWidget {
@Override
public boolean acceptsFlow(BlockEdge edge) {
var margin = inner.getMargin(edge);
if (margin == null) {
return false;
}
return inner.acceptsFlow(margin);
}
@Override
public EnumSet<BlockEdge> possibleExitDirs(BlockEdge inputEdge) {
var margin = inner.getMargin(inputEdge);
if (margin == null) {
return EnumSet.noneOf(BlockEdge.class);
}
return inner.possibleExitDirs(margin);
}
@Override
public @Nullable FlowUpdate onReceiveFlow(BlockEdge inputEdge, BlockEntity sender, ICircleState state) {
var margin = inner.getMargin(inputEdge);
if (margin == null) {
return null;
}
return inner.onReceiveFlow(margin, sender, state);
}
}
}

View file

@ -1,9 +1,6 @@
package at.petrak.hexcasting.forge.cap;
import at.petrak.hexcasting.api.addldata.ADColorizer;
import at.petrak.hexcasting.api.addldata.ADHexHolder;
import at.petrak.hexcasting.api.addldata.ADIotaHolder;
import at.petrak.hexcasting.api.addldata.ADMediaHolder;
import at.petrak.hexcasting.api.addldata.*;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.CapabilityToken;
@ -18,4 +15,6 @@ public final class HexCapabilities {
});
public static final Capability<ADColorizer> COLOR = CapabilityManager.get(new CapabilityToken<>() {
});
public static final Capability<ADCircleWidget> CIRCLE_WIDGET = CapabilityManager.get(new CapabilityToken<>() {
});
}

View file

@ -1,17 +1,15 @@
package at.petrak.hexcasting.forge.datagen.xplat;
import at.petrak.hexcasting.api.HexAPI;
import at.petrak.hexcasting.api.block.circle.BlockCircleComponent;
import at.petrak.hexcasting.api.block.circle.BlockSidedCircleWidget;
import at.petrak.hexcasting.api.circles.Margin;
import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicBookshelf;
import at.petrak.hexcasting.common.blocks.circles.BlockSlate;
import at.petrak.hexcasting.common.blocks.circles.directrix.BlockRedstoneDirectrix;
import at.petrak.hexcasting.common.lib.HexBlocks;
import at.petrak.paucal.api.forge.datagen.PaucalBlockStateAndModelProvider;
import it.unimi.dsi.fastutil.booleans.Boolean2ObjectFunction;
import net.minecraft.core.Direction;
import net.minecraft.data.DataGenerator;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraftforge.client.model.generators.BlockModelBuilder;
import net.minecraftforge.client.model.generators.ConfiguredModel;
import net.minecraftforge.common.data.ExistingFileHelper;
@ -26,101 +24,6 @@ public class HexBlockStatesAndModels extends PaucalBlockStateAndModelProvider {
@Override
protected void registerStatesAndModels() {
var slateModel = models().getExistingFile(modLoc("slate"));
getVariantBuilder(HexBlocks.SLATE).forAllStatesExcept(bs -> {
int rotationX = 0;
int rotationY = 0;
switch (bs.getValue(BlockSlate.ATTACH_FACE)) {
case CEILING -> rotationX = 180;
case WALL -> {
rotationX = 90;
rotationY = bs.getValue(BlockSlate.FACING).getOpposite().get2DDataValue() * 90;
}
}
return ConfiguredModel.builder()
.modelFile(slateModel)
.rotationX(rotationX)
.rotationY(rotationY)
.uvLock(true)
.build();
}, BlockSlate.WATERLOGGED);
impetus(HexBlocks.IMPETUS_RIGHTCLICK, "impetus_rightclick", "rightclick");
impetus(HexBlocks.IMPETUS_LOOK, "impetus_look", "look");
impetus(HexBlocks.IMPETUS_STOREDPLAYER, "impetus_storedplayer", "storedplayer");
arrowCircleBlock(HexBlocks.EMPTY_IMPETUS, "empty_impetus", modLoc("block/slate"),
"impetus/front_empty",
"impetus/back_empty",
"impetus/up_empty",
"impetus/down_empty",
"impetus/left_empty",
"impetus/right_empty"
);
// auugh
getVariantBuilder(HexBlocks.DIRECTRIX_REDSTONE).forAllStates(bs -> {
var isLit = bs.getValue(BlockCircleComponent.ENERGIZED);
var litness = isLit ? "lit" : "dim";
var isPowered = bs.getValue(BlockRedstoneDirectrix.REDSTONE_POWERED);
var poweredness = isPowered ? "powered" : "unpowered";
var dir = bs.getValue(BlockStateProperties.FACING);
var up = modLoc("block/directrix/redstone/up_" + poweredness + "_" + litness);
var left = modLoc("block/directrix/redstone/left_" + poweredness + "_" + litness);
var right = modLoc("block/directrix/redstone/right_" + poweredness + "_" + litness);
var down = modLoc("block/directrix/redstone/down_" + poweredness + "_" + litness);
var front = modLoc("block/directrix/redstone/front_" + litness);
var back = modLoc("block/directrix/redstone/back_" + poweredness);
var routing = routeReslocsForArrowBlock(dir, front, back, up, down, left, right);
var modelName = "redstone_directrix_" + poweredness + "_" + litness + "_" + dir.getName();
var model = models().cube(modelName, routing[0], routing[1], routing[2], routing[3], routing[4], routing[5])
.texture("particle", modLoc("block/slate"));
if (!isLit && !isPowered && dir == Direction.NORTH) {
simpleBlockItem(HexBlocks.DIRECTRIX_REDSTONE, model);
}
return ConfiguredModel.builder()
.modelFile(model)
.build();
});
getVariantBuilder(HexBlocks.EMPTY_DIRECTRIX).forAllStates(bs -> {
var isLit = bs.getValue(BlockCircleComponent.ENERGIZED);
var litness = isLit ? "lit" : "dim";
var axis = bs.getValue(BlockStateProperties.AXIS);
var horiz = modLoc("block/directrix/empty/horiz_" + litness);
var vert = modLoc("block/directrix/empty/vert_" + litness);
var end = modLoc("block/directrix/empty/end_" + litness);
ResourceLocation x = null, y = null, z = null;
switch (axis) {
case X -> {
x = end;
y = horiz;
z = horiz;
}
case Y -> {
x = vert;
y = end;
z = vert;
}
case Z -> {
x = horiz;
y = vert;
z = end;
}
}
var modelName = "empty_directrix_" + litness + "_" + axis.getName();
var model = models().cube(modelName, y, y, z, z, x, x)
.texture("particle", modLoc("block/slate"));
if (!isLit && axis == Direction.Axis.Z) {
simpleBlockItem(HexBlocks.EMPTY_DIRECTRIX, model);
}
return ConfiguredModel.builder()
.modelFile(model)
.build();
});
var akashicRecordModel = models().getExistingFile(modLoc("block/akashic_record"));
simpleBlock(HexBlocks.AKASHIC_RECORD, akashicRecordModel);
@ -236,99 +139,58 @@ public class HexBlockStatesAndModels extends PaucalBlockStateAndModelProvider {
simpleBlock(HexBlocks.CONJURED_LIGHT, conjuredModel);
}
private void impetus(Block block, String name, String stub) {
arrowCircleBlock(block, name, modLoc("block/slate"),
"impetus/" + stub,
"impetus/back",
"impetus/up",
"impetus/down",
"impetus/left",
"impetus/right"
);
}
private void sidedCircleWidget(BlockSidedCircleWidget widget, String commonName, String coreName) {
// We do really need to do this with a multipart builder. Unfortunately we cannot combine this with a variant builder,
// so there's gonna be a *lot* of model files.
// Each block needs a body + 4 connector models, for unenergized and energized versions, for non-margin
// and margin versions. ("Margin" = the overlay model we stick on top, so some of the model rotates and some doesn't.)
// Due to the third dimension being a mistake, we *also* need a version of the model that points up and one that points sideways.
// This is because minecraft only accepts N quarters around the X axis and N quarters around the Y axis, and this doesn't
// let us get all the 24 rotations we need.
//
// To avoid the generated folder becoming incredibly thicc and to save resource pack makers we put everything in a folder.
//
// Files are named {blockpath}/{part}['_' margin]['_' energized].
// So, `toolsmith_impetus/body_energized` for the large chunk in the middle when active,
// `farmer_locus/north_margin` for the north connector when rendering the overlay, etc.
var cursor = getMultipartBuilder(widget);
var parts = new String[]{
"body", "north", "south", "east", "west"
};
for (var part : parts) {
for (boolean energized : BlockSidedCircleWidget.ENERGIZED.getPossibleValues()) {
String energizedStr = energized ? "" : "_energized";
Boolean2ObjectFunction<BlockModelBuilder> modelMaker = (margin) -> {
String marginStr = margin ? "" : "_margin";
return models().withExistingParent(widget.getRegistryName().getPath() + "/" + part
+ marginStr + energizedStr,
modLoc("template_sided_circle_widget_" + part))
.texture("common", modLoc("block/circle/" + commonName + energizedStr))
.texture("core", modLoc("block/circle/" + coreName + marginStr + energizedStr));
private void arrowCircleBlock(Block block, String name, ResourceLocation particle, String frontStub,
String backStub, String upStub, String downStub, String leftStub, String rightStub) {
getVariantBuilder(block).forAllStates(bs -> {
var isLit = bs.getValue(BlockCircleComponent.ENERGIZED);
var litness = isLit ? "lit" : "dim";
var dir = bs.getValue(BlockStateProperties.FACING);
};
var unmarginModel = modelMaker.apply(false);
var marginModel = modelMaker.apply(true);
var up = modLoc("block/" + upStub + "_" + litness);
var front = modLoc("block/" + frontStub + "_" + litness);
var back = modLoc("block/" + backStub + "_" + litness);
var left = modLoc("block/" + leftStub + "_" + litness);
var right = modLoc("block/" + rightStub + "_" + litness);
var down = modLoc("block/" + downStub + "_" + litness);
for (var normal : Direction.values()) {
// For each normal dir we add the base model, THEN the margin-textured model on top of it.
// Phew.
int xRot = normal == Direction.DOWN ? 180 : normal.getAxis().isHorizontal() ? 90 : 0;
int yRot = normal.getAxis().isVertical() ? 0 : (int) normal.toYRot();
cursor.part()
.modelFile(unmarginModel)
.rotationX(xRot)
.rotationY(yRot)
.nextModel();
// Additionally rotate the margin model by the margin amount, about the normal vector ...
// but we need to transform that into a sequence of X and Y rotations.
// Uegh.
for (var margin : Margin.values()) {
var routing = routeReslocsForArrowBlock(dir, front, back, up, down, left, right);
var modelName = name + "_" + litness + "_" + dir.getName();
var model = models().cube(modelName, routing[0], routing[1], routing[2], routing[3], routing[4], routing[5])
.texture("particle", particle);
// Ordinarily i would use north, because north is the lower-right direction in the inv
// and that's where other blocks face.
// But impetuses are only distinguished by their front faces and I don't want it covered
// by the number.
if (!isLit && dir == Direction.EAST) {
simpleBlockItem(block, model);
}
return ConfiguredModel.builder()
.modelFile(model)
.build();
});
}
private static ResourceLocation[] routeReslocsForArrowBlock(Direction dir, ResourceLocation front,
ResourceLocation back,
ResourceLocation up, ResourceLocation down,
ResourceLocation left, ResourceLocation right) {
ResourceLocation bottom = null, top = null, north = null, south = null, east = null, west = null;
switch (dir) {
case UP -> {
top = front;
bottom = back;
north = east = south = west = up;
}
case DOWN -> {
bottom = front;
top = back;
north = east = south = west = down;
}
case NORTH -> {
north = front;
south = back;
west = left;
east = right;
top = up;
bottom = down;
}
case SOUTH -> {
south = front;
north = back;
west = right;
east = left;
top = down;
bottom = up;
}
case WEST -> {
west = front;
east = back;
north = right;
south = left;
top = left;
bottom = left;
}
case EAST -> {
east = front;
west = back;
north = left;
south = right;
top = right;
bottom = right;
}
}
}
}
return new ResourceLocation[]{bottom, top, north, south, east, west};
}
}
}