BetterEnd/src/main/java/org/betterx/betterend/rituals/EternalRitual.java

570 lines
22 KiB
Java

package org.betterx.betterend.rituals;
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
import org.betterx.bclib.blocks.BlockProperties;
import org.betterx.betterend.BetterEnd;
import org.betterx.betterend.advancements.BECriteria;
import org.betterx.betterend.blocks.EndPortalBlock;
import org.betterx.betterend.blocks.RunedFlavolite;
import org.betterx.betterend.blocks.entities.EternalPedestalEntity;
import org.betterx.betterend.network.RitualUpdate;
import org.betterx.betterend.portal.PortalBuilder;
import org.betterx.betterend.registry.EndBlocks;
import org.betterx.betterend.registry.EndPortals;
import net.minecraft.BlockUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.NetherPortalBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.border.WorldBorder;
import com.google.common.collect.Sets;
import java.awt.*;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
public class EternalRitual {
private final static Set<Point> PEDESTAL_POSITIONS = Sets.newHashSet(
new Point(-4, -5),
new Point(-4, 5),
new Point(-6, 0),
new Point(4, -5),
new Point(4, 5),
new Point(6, 0)
);
private final static Block PEDESTAL = EndBlocks.ETERNAL_PEDESTAL;
public final static BooleanProperty ACTIVE = BlockProperties.ACTIVE;
private Level world;
private Direction.Axis axis;
private ResourceLocation targetWorldId;
private BlockPos center;
private BlockPos exit;
private boolean active = false;
private boolean willActivate = false;
public EternalRitual(Level world) {
this.world = world;
}
public EternalRitual(Level world, BlockPos initial) {
this(world);
this.configure(initial);
}
public BlockPos getCenter() {
return center;
}
public Direction.Axis getAxis() {
return axis;
}
public void setWorld(Level world) {
this.world = world;
}
public Level getWorld() {
return world;
}
@Nullable
public ResourceLocation getTargetWorldId() {
return targetWorldId;
}
private boolean isInvalid() {
return world == null || world.isClientSide() || center == null || axis == null;
}
public void checkStructure(Player player) {
if (isInvalid()) return;
Direction moveX, moveY;
if (Direction.Axis.X == axis) {
moveX = Direction.EAST;
moveY = Direction.NORTH;
} else {
moveX = Direction.SOUTH;
moveY = Direction.EAST;
}
boolean valid = checkFrame(world, center.below());
Item item = null;
for (Point pos : PEDESTAL_POSITIONS) {
BlockPos.MutableBlockPos checkPos = center.mutable();
checkPos.move(moveX, pos.x).move(moveY, pos.y);
valid &= isActive(checkPos);
if (valid) {
EternalPedestalEntity pedestal = (EternalPedestalEntity) world.getBlockEntity(checkPos);
if (pedestal != null) {
Item pItem = pedestal.getItem(0).getItem();
if (item == null) {
item = pItem;
} else if (!item.equals(pItem)) {
valid = false;
}
}
}
}
if (valid && item != null) {
activatePortal(player, item);
if (player instanceof ServerPlayer sp) {
BECriteria.PORTAL_ON.trigger(sp);
}
}
}
public void updateActiveStateOnPedestals() {
if (world == null) return;
updateActiveStateOnPedestals(center, axis, active, willActivate, world, this);
DataExchangeAPI.send(new RitualUpdate(this));
}
public static void updateActiveStateOnPedestals(
BlockPos center,
Direction.Axis axis,
boolean active,
boolean willActivate,
Level world,
EternalRitual fallback
) {
Direction moveX, moveY;
if (Direction.Axis.X == axis) {
moveX = Direction.EAST;
moveY = Direction.NORTH;
} else {
moveX = Direction.SOUTH;
moveY = Direction.EAST;
}
for (Point pos : PEDESTAL_POSITIONS) {
BlockPos.MutableBlockPos checkPos = center.mutable();
checkPos.move(moveX, pos.x).move(moveY, pos.y);
if (world.getBlockEntity(checkPos) instanceof EternalPedestalEntity pedestal) {
if (pedestal.hasRitual()) {
if (fallback == null) fallback = pedestal.getRitual();
pedestal.getRitual().active = active;
pedestal.getRitual().willActivate = willActivate;
} else {
if (fallback == null) {
fallback = new EternalRitual(world);
fallback.center = center;
fallback.axis = axis;
fallback.willActivate = willActivate;
fallback.active = active;
}
pedestal.linkRitual(fallback);
}
}
}
}
private boolean checkFrame(Level world, BlockPos framePos) {
Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST;
boolean valid = true;
for (Point point : PortalBuilder.FRAME_POSITIONS) {
BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
BlockState state = world.getBlockState(pos);
valid &= state.getBlock() instanceof RunedFlavolite;
pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
state = world.getBlockState(pos);
valid &= state.getBlock() instanceof RunedFlavolite;
}
return valid;
}
public boolean isActive() {
return active;
}
public boolean willActivate() {
return willActivate;
}
private void activatePortal(Player player, Item keyItem) {
if (active) return;
willActivate = true;
updateActiveStateOnPedestals();
ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(keyItem);
int portalId = EndPortals.getPortalIdByItem(itemId);
Level targetWorld = getTargetWorld(portalId);
ResourceLocation worldId = targetWorld.dimension().location();
try {
if (exit == null) {
initPortal(player, worldId, portalId);
} else {
if (!worldId.equals(targetWorldId)) {
initPortal(player, worldId, portalId);
} else if (!checkFrame(targetWorld, exit.below())) {
Direction.Axis portalAxis = (Direction.Axis.X == axis) ? Direction.Axis.Z : Direction.Axis.X;
PortalBuilder.generatePortal(targetWorld, exit, portalAxis, portalId);
}
activatePortal(targetWorld, exit, portalId);
}
activatePortal(world, center, portalId);
if (world instanceof ServerLevel serverLevel) {
doEffects(serverLevel, center);
}
willActivate = false;
active = true;
updateActiveStateOnPedestals();
} catch (Exception ex) {
BetterEnd.LOGGER.error("Create End portals error.", ex);
removePortal(targetWorld, exit);
removePortal(world, center);
willActivate = false;
active = false;
updateActiveStateOnPedestals();
}
}
private void initPortal(Player player, ResourceLocation worldId, int portalId) {
targetWorldId = worldId;
if (world instanceof ServerLevel sourceWorld) {
ServerLevel targetLevel = (ServerLevel) getTargetWorld(portalId);
PortalBuilder builder = new PortalBuilder(world, targetLevel);
final WorldBorder worldBorder = targetLevel.getWorldBorder();
final Optional<BlockUtil.FoundRectangle> foundRectangle = builder.findPortalAround(
this.center,
worldBorder
);
if (!foundRectangle.isPresent()) {
Optional<BlockPos> centerPos;
centerPos = builder.createPortal(
PortalBuilder.getStartingPos(sourceWorld, targetLevel, player, worldBorder),
axis,
portalId
);
centerPos.ifPresent(blockPos -> this.exit = blockPos);
}
}
}
private void doEffects(ServerLevel serverWorld, BlockPos center) {
Direction moveX, moveY;
if (Direction.Axis.X == axis) {
moveX = Direction.EAST;
moveY = Direction.NORTH;
} else {
moveX = Direction.SOUTH;
moveY = Direction.EAST;
}
for (Point pos : PEDESTAL_POSITIONS) {
BlockPos.MutableBlockPos p = center.mutable();
p.move(moveX, pos.x).move(moveY, pos.y);
serverWorld.sendParticles(
ParticleTypes.PORTAL,
p.getX() + 0.5, p.getY() + 1.5, p.getZ() + 0.5,
20,
0, 0, 0, 1
);
serverWorld.sendParticles(
ParticleTypes.REVERSE_PORTAL,
p.getX() + 0.5, p.getY() + 1.5, p.getZ() + 0.5,
20,
0, 0, 0, 0.3
);
}
serverWorld.playSound(null, center, SoundEvents.END_PORTAL_SPAWN, SoundSource.NEUTRAL, 16, 1);
}
private void activatePortal(Level world, BlockPos center, int portalId) {
BlockPos framePos = center.below();
Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST;
BlockState frame = PortalBuilder.FRAME.defaultBlockState().setValue(ACTIVE, true);
PortalBuilder.FRAME_POSITIONS.forEach(point -> {
BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
BlockState state = world.getBlockState(pos);
if (state.hasProperty(ACTIVE) && !state.getValue(ACTIVE)) {
world.setBlockAndUpdate(pos, frame);
}
pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
state = world.getBlockState(pos);
if (state.hasProperty(ACTIVE) && !state.getValue(ACTIVE)) {
world.setBlockAndUpdate(pos, frame);
}
});
Direction.Axis portalAxis = Direction.Axis.X == axis ? Direction.Axis.Z : Direction.Axis.X;
BlockState portal = PortalBuilder.PORTAL.defaultBlockState()
.setValue(EndPortalBlock.AXIS, portalAxis)
.setValue(EndPortalBlock.PORTAL, portalId);
ParticleOptions effect = new BlockParticleOption(ParticleTypes.BLOCK, portal);
if (world instanceof ServerLevel serverWorld) {
PortalBuilder.PORTAL_POSITIONS.forEach(point -> {
BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
if (!world.getBlockState(pos).is(PortalBuilder.PORTAL)) {
world.setBlockAndUpdate(pos, portal);
serverWorld.sendParticles(
effect,
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
10,
0.5, 0.5, 0.5, 0.1
);
serverWorld.sendParticles(
ParticleTypes.REVERSE_PORTAL,
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
10,
0.5, 0.5, 0.5, 0.3
);
}
pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
if (!world.getBlockState(pos).is(PortalBuilder.PORTAL)) {
world.setBlockAndUpdate(pos, portal);
serverWorld.sendParticles(
effect,
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
10,
0.5, 0.5, 0.5, 0.1
);
serverWorld.sendParticles(
ParticleTypes.REVERSE_PORTAL,
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
10,
0.5, 0.5, 0.5, 0.3
);
}
});
}
}
public void disablePortal(int state) {
if (!active || isInvalid()) return;
if (!world.isClientSide())
removePortal(getTargetWorld(state), exit);
removePortal(world, center);
}
private void removePortal(Level world, BlockPos center) {
BlockPos framePos = center.below();
Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST;
PortalBuilder.FRAME_POSITIONS.forEach(point -> {
BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
BlockState state = world.getBlockState(pos);
if (state.is(PortalBuilder.FRAME) && state.getValue(ACTIVE)) {
world.setBlockAndUpdate(pos, state.setValue(ACTIVE, false));
}
pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
state = world.getBlockState(pos);
if (state.is(PortalBuilder.FRAME) && state.getValue(ACTIVE)) {
world.setBlockAndUpdate(pos, state.setValue(ACTIVE, false));
}
});
PortalBuilder.PORTAL_POSITIONS.forEach(point -> {
BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
if (world.getBlockState(pos).is(PortalBuilder.PORTAL)) {
world.removeBlock(pos, false);
}
pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
if (world.getBlockState(pos).is(PortalBuilder.PORTAL)) {
world.removeBlock(pos, false);
}
});
this.active = false;
updateActiveStateOnPedestals();
}
private Level getTargetWorld(int state) {
if (world.dimension() == Level.END) {
return EndPortals.getWorld(world.getServer(), state);
}
return Objects.requireNonNull(world.getServer()).getLevel(Level.END);
}
public void configure(BlockPos initial) {
BlockPos checkPos = initial.east(12);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.X;
this.center = initial.east(6);
return;
}
checkPos = initial.west(12);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.X;
this.center = initial.west(6);
return;
}
checkPos = initial.south(12);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.Z;
this.center = initial.south(6);
return;
}
checkPos = initial.north(12);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.Z;
this.center = initial.north(6);
return;
}
checkPos = initial.north(10);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.X;
checkPos = checkPos.east(8);
if (this.hasPedestal(checkPos)) {
this.center = initial.north(5).east(4);
} else {
this.center = initial.north(5).west(4);
}
return;
}
checkPos = initial.south(10);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.X;
checkPos = checkPos.east(8);
if (this.hasPedestal(checkPos)) {
this.center = initial.south(5).east(4);
} else {
this.center = initial.south(5).west(4);
}
return;
}
checkPos = initial.east(10);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.Z;
checkPos = checkPos.south(8);
if (this.hasPedestal(checkPos)) {
this.center = initial.east(5).south(4);
} else {
this.center = initial.east(5).north(4);
}
return;
}
checkPos = initial.west(10);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.Z;
checkPos = checkPos.south(8);
if (this.hasPedestal(checkPos)) {
this.center = initial.west(5).south(4);
} else {
this.center = initial.west(5).north(4);
}
}
}
private boolean hasPedestal(BlockPos pos) {
return world.getBlockState(pos).is(PEDESTAL);
}
private boolean isActive(BlockPos pos) {
BlockState state = world.getBlockState(pos);
if (state.is(PEDESTAL)) {
EternalPedestalEntity pedestal = (EternalPedestalEntity) world.getBlockEntity(pos);
if (pedestal != null) {
if (!pedestal.hasRitual()) {
pedestal.linkRitual(this);
} else {
EternalRitual ritual = pedestal.getRitual();
if (!ritual.equals(this)) {
pedestal.linkRitual(this);
}
}
}
return state.getValue(ACTIVE);
}
return false;
}
public CompoundTag toTag(CompoundTag tag) {
if (this.center == null || this.axis == null || this.exit == null) return null;
tag.put("center", NbtUtils.writeBlockPos(center));
tag.putString("axis", axis.getName());
tag.putBoolean("active", active);
if (targetWorldId != null) {
tag.putString("key_item", targetWorldId.toString());
}
if (exit != null) {
tag.put("exit", NbtUtils.writeBlockPos(exit));
}
return tag;
}
public void fromTag(CompoundTag tag) {
axis = Direction.Axis.byName(tag.getString("axis"));
center = NbtUtils.readBlockPos(tag.getCompound("center"));
active = tag.getBoolean("active");
if (tag.contains("exit")) {
exit = NbtUtils.readBlockPos(tag.getCompound("exit"));
}
if (tag.contains("key_item")) {
targetWorldId = new ResourceLocation(tag.getString("key_item"));
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EternalRitual ritual = (EternalRitual) o;
return world.equals(ritual.world) && Objects.equals(center, ritual.center) && Objects.equals(exit, ritual.exit);
}
public static EternalRitual findRitualForActivePortal(Level world, BlockPos startPos) {
BlockState state = world.getBlockState(startPos);
if (isActivePortalBlock(state)) {
final var direction = state.getValue(NetherPortalBlock.AXIS) == Direction.Axis.X
? Direction.EAST
: Direction.NORTH;
while (world.getBlockState(startPos.below()).is(EndBlocks.END_PORTAL_BLOCK)) {
startPos = startPos.below();
}
// X O O O O O X
// X O O O O O X
// X a c O c b X
// X a O b X
// X X X
if (world.getBlockState(startPos.relative(direction, -1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
startPos = startPos.relative(direction, 1); //pos (a)
else if (world.getBlockState(startPos.relative(direction, 1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
startPos = startPos.relative(direction, -1); //pos (b)
if (!world.getBlockState(startPos.below()).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
startPos = startPos.below(); //pos (c)
if (world.getBlockState(startPos.relative(direction, -1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
startPos = startPos.relative(direction, 1); //pos (a)
else if (world.getBlockState(startPos.relative(direction, 1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
startPos = startPos.relative(direction, -1); //pos (b)
startPos = startPos.relative(direction.getClockWise(), 6);
state = world.getBlockState(startPos);
if (state.is(EndBlocks.ETERNAL_PEDESTAL) && world.getBlockEntity(startPos) instanceof EternalPedestalEntity pedestal) {
return pedestal.getRitual();
}
return null;
}
return null;
}
private static boolean isActivePortalBlock(BlockState state) {
return state.is(EndBlocks.END_PORTAL_BLOCK)
&& state.hasProperty(NetherPortalBlock.AXIS);
}
}