flan/common/src/main/java/io/github/flemmli97/flan/player/display/ClaimDisplay.java

207 lines
8.3 KiB
Java

package io.github.flemmli97.flan.player.display;
import io.github.flemmli97.flan.claim.Claim;
import io.github.flemmli97.flan.claim.ParticleIndicators;
import io.github.flemmli97.flan.config.ConfigHandler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.SectionPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ClaimDisplay {
private int displayTime;
private final int height;
private final DisplayBox display;
public final EnumDisplayType type;
private int[][] corners;
private int[][] middlePoss;
private DisplayBox.Box prevDims;
private final DustParticleOptions corner, middle;
public ClaimDisplay(Claim claim, EnumDisplayType type, int y) {
this(claim.display(), claim.getWorld(), type, y);
}
public ClaimDisplay(DisplayBox display, Level level, EnumDisplayType type, int y) {
this.display = display;
this.displayTime = ConfigHandler.config.claimDisplayTime;
this.prevDims = display.box();
this.type = type;
this.height = Math.max(1 + level.getMinBuildHeight(), y);
switch (type) {
case SUB -> {
this.corner = ParticleIndicators.SUBCLAIMCORNER;
this.middle = ParticleIndicators.SUBCLAIMMIDDLE;
}
case CONFLICT -> {
this.corner = ParticleIndicators.OVERLAPCLAIM;
this.middle = ParticleIndicators.OVERLAPCLAIM;
}
case EDIT -> {
this.corner = ParticleIndicators.EDITCLAIMCORNER;
this.middle = ParticleIndicators.EDITCLAIMMIDDLE;
}
default -> {
this.corner = ParticleIndicators.CLAIMCORNER;
this.middle = ParticleIndicators.CLAIMMIDDLE;
}
}
}
public boolean display(ServerPlayer player, boolean remove) {
if (--this.displayTime % 2 == 0)
return this.display.isRemoved();
DisplayBox.Box dims = this.display.box();
if (this.corners == null || this.changed(dims)) {
this.middlePoss = calculateDisplayPos(player.serverLevel(), dims, this.height, this.display.excludedSides());
this.corners = new int[][]{
getPosFrom(player.serverLevel(), dims.minX(), dims.minZ(), this.height),
getPosFrom(player.serverLevel(), dims.maxX(), dims.minZ(), this.height),
getPosFrom(player.serverLevel(), dims.minX(), dims.maxZ(), this.height),
getPosFrom(player.serverLevel(), dims.maxX(), dims.maxZ(), this.height),
};
}
for (int[] pos : this.corners) {
if (pos[1] != pos[2])
player.connection.send(new ClientboundLevelParticlesPacket(this.corner, true, pos[0] + 0.5, pos[2] + 0.25, pos[3] + 0.5, 0, 0.5f, 0, 0, 1));
player.connection.send(new ClientboundLevelParticlesPacket(this.corner, true, pos[0] + 0.5, pos[1] + 0.25, pos[3] + 0.5, 0, 0.5f, 0, 0, 1));
}
if (this.middlePoss != null)
for (int[] pos : this.middlePoss) {
if (pos[1] != pos[2])
player.connection.send(new ClientboundLevelParticlesPacket(this.middle, true, pos[0] + 0.5, pos[2] + 0.25, pos[3] + 0.5, 0, 0.5f, 0, 0, 1));
player.connection.send(new ClientboundLevelParticlesPacket(this.middle, true, pos[0] + 0.5, pos[1] + 0.25, pos[3] + 0.5, 0, 0.5f, 0, 0, 1));
}
this.prevDims = dims;
return this.display.isRemoved() || (remove && this.displayTime < 0);
}
private boolean changed(DisplayBox.Box dims) {
return !this.prevDims.equals(dims);
}
public static int[][] calculateDisplayPos(ServerLevel world, DisplayBox.Box from, int height, Set<Direction> exclude) {
List<int[]> l = new ArrayList<>();
Set<Integer> xs = new HashSet<>();
addEvenly(from.minX(), from.maxX(), 10, xs);
xs.add(from.minX() + 1);
xs.add(from.maxX() - 1);
Set<Integer> zs = new HashSet<>();
addEvenly(from.minZ(), from.maxZ(), 10, zs);
zs.add(from.minZ() + 1);
zs.add(from.maxZ() - 1);
for (int x : xs) {
if (!exclude.contains(Direction.NORTH))
l.add(getPosFrom(world, x, from.minZ(), height));
if (!exclude.contains(Direction.SOUTH))
l.add(getPosFrom(world, x, from.maxZ(), height));
}
for (int z : zs) {
if (!exclude.contains(Direction.WEST))
l.add(getPosFrom(world, from.minX(), z, height));
if (!exclude.contains(Direction.EAST))
l.add(getPosFrom(world, from.maxX(), z, height));
}
return l.toArray(new int[0][]);
}
private static void addEvenly(int min, int max, int step, Set<Integer> l) {
if (max - min < step * 1.5)
return;
if (max - min > 0 && max - min <= step * 0.5) {
l.add(max - step + 1);
l.add(min + step - 1);
return;
}
l.add(max - step);
l.add(min + step);
addEvenly(min + step, max - step, step, l);
}
/**
* Returns an array of form [x,y1,y2,z] where y1 = height of the lowest replaceable block and y2 = height of the
* lowest air block above water (if possible)
*/
public static int[] getPosFrom(ServerLevel world, int x, int z, int maxY) {
LevelChunk chunk = world.getChunk(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z));
int[] y = nextAirAndWaterBlockFrom(chunk, x, maxY, z);
return new int[]{x, y[0], y[1], z};
}
// SAFETY: Ensure that the X/Z coordinates are for the given chunk
// since the position is mutating only up or down, it's always in the same chunk
private static int[] nextAirAndWaterBlockFrom(LevelChunk chunk, int x, int y, int z) {
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(x, y, z);
BlockState state = chunk.getBlockState(pos);
if (state.canBeReplaced()) {
//Move Down
boolean startedInLiquid = state.liquid();
boolean liquidCheck = false;
int liquidHeight = pos.getY();
while (state.canBeReplaced() && !chunk.isOutsideBuildHeight(pos)) {
pos.move(0, -1, 0);
state = chunk.getBlockState(pos);
if (!startedInLiquid && !liquidCheck && state.liquid()) {
liquidCheck = true;
liquidHeight = pos.getY();
}
}
int[] yRet = {pos.getY() + 1, (liquidCheck ? liquidHeight : pos.getY()) + 1};
if (startedInLiquid) {
pos.set(pos.getX(), liquidHeight + 1, pos.getZ());
state = chunk.getBlockState(pos);
while (state.liquid() && !chunk.isOutsideBuildHeight(pos)) {
pos.move(0, 1, 0);
state = chunk.getBlockState(pos);
}
if (state.canBeReplaced())
yRet[1] = pos.getY();
}
return yRet;
}
//Move Up
while (!state.canBeReplaced() && !chunk.isOutsideBuildHeight(pos)) {
pos.move(0, 1, 0);
state = chunk.getBlockState(pos);
}
int[] yRet = {pos.getY(), pos.getY()};
while (state.liquid() && !chunk.isOutsideBuildHeight(pos)) {
pos.move(0, 1, 0);
state = chunk.getBlockState(pos);
}
if (state.canBeReplaced())
yRet[1] = pos.getY();
return yRet;
}
@Override
public int hashCode() {
return this.display.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj instanceof ClaimDisplay)
return this.display.equals(((ClaimDisplay) obj).display);
return false;
}
}