355 lines
13 KiB
Java
355 lines
13 KiB
Java
|
package com.thebrokenrail.energonrelics.block.entity;
|
||
|
|
||
|
import com.thebrokenrail.energonrelics.Config;
|
||
|
import com.thebrokenrail.energonrelics.EnergonRelics;
|
||
|
import com.thebrokenrail.energonrelics.block.DefensiveLaserBlock;
|
||
|
import com.thebrokenrail.energonrelics.energy.core.EnergyReceiverBlockEntity;
|
||
|
import com.thebrokenrail.energonrelics.energy.core.util.Action;
|
||
|
import net.minecraft.block.BlockState;
|
||
|
import net.minecraft.block.entity.BlockEntityType;
|
||
|
import net.minecraft.entity.LivingEntity;
|
||
|
import net.minecraft.nbt.CompoundTag;
|
||
|
import net.minecraft.particle.ParticleTypes;
|
||
|
import net.minecraft.server.world.ServerWorld;
|
||
|
import net.minecraft.util.hit.HitResult;
|
||
|
import net.minecraft.util.math.Box;
|
||
|
import net.minecraft.util.math.Direction;
|
||
|
import net.minecraft.util.math.MathHelper;
|
||
|
import net.minecraft.util.math.Vec3d;
|
||
|
import net.minecraft.world.RayTraceContext;
|
||
|
|
||
|
import java.util.Collections;
|
||
|
import java.util.List;
|
||
|
import java.util.Optional;
|
||
|
import java.util.function.Predicate;
|
||
|
|
||
|
public class DefensiveLaserBlockEntity extends EnergyReceiverBlockEntity {
|
||
|
private static final float MAX_PITCH = 60;
|
||
|
private static final float MIN_PITCH = -20;
|
||
|
|
||
|
private static final float ROTATION_INCREMENT = 2;
|
||
|
|
||
|
private static final int COUNTDOWN = 6;
|
||
|
|
||
|
public final Rotation rotation = new Rotation(0, 0, "Rotation");
|
||
|
private LivingEntity target;
|
||
|
private int countdown = 0;
|
||
|
|
||
|
private final Predicate<LivingEntity> predicate = entity -> {
|
||
|
if (entity.getBlockPos().isWithinDistance(getPos(), Config.DEFENSIVE_LASER_RANGE)) {
|
||
|
Vec3d start = getPosVec().add(rotation.getRayVector());
|
||
|
Vec3d end = entity.getPos();
|
||
|
HitResult result = entity.getEntityWorld().rayTrace(new RayTraceContext(start, end, RayTraceContext.ShapeType.COLLIDER, RayTraceContext.FluidHandling.ANY, entity));
|
||
|
return result.getType() == HitResult.Type.MISS;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
private Vec3d getPosVec() {
|
||
|
return new Vec3d(getPos().getX() + 0.5d, getPos().getY() + 0.5d, getPos().getZ() + 0.5d);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public BlockState getCachedState() {
|
||
|
if (hasWorld()) {
|
||
|
return super.getCachedState();
|
||
|
} else {
|
||
|
return EnergonRelics.DEFENSIVE_LASER_BLOCK.getDefaultState();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private interface SwapFunction {
|
||
|
class SwapData {
|
||
|
private final float yaw;
|
||
|
private final float pitch;
|
||
|
|
||
|
public SwapData(float yaw, float pitch) {
|
||
|
this.yaw = yaw;
|
||
|
this.pitch = pitch;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Vec3d swap(SwapData data);
|
||
|
SwapData line(Vec3d data);
|
||
|
|
||
|
static SwapFunction upDown(boolean up) {
|
||
|
return new SwapFunction() {
|
||
|
@Override
|
||
|
public Vec3d swap(SwapData data) {
|
||
|
return new Vec3d(-data.yaw, data.pitch, 0);
|
||
|
}
|
||
|
|
||
|
private SwapData reverseSwap(Vec3d data) {
|
||
|
return new SwapData((float) -data.getX(), (float) data.getY());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public SwapData line(Vec3d data) {
|
||
|
SwapData reverse = reverseSwap(data);
|
||
|
return new SwapData(-reverse.yaw, reverse.pitch);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
static SwapFunction northSouth(boolean north) {
|
||
|
return new SwapFunction() {
|
||
|
@Override
|
||
|
public Vec3d swap(SwapData data) {
|
||
|
if (north) {
|
||
|
return new Vec3d(0, data.pitch, data.yaw);
|
||
|
} else {
|
||
|
return new Vec3d(0, -data.pitch, data.yaw);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private SwapData reverseSwap(Vec3d data) {
|
||
|
if (north) {
|
||
|
return new SwapData((float) data.getZ(), (float) data.getY());
|
||
|
} else {
|
||
|
return new SwapData((float) data.getZ(), (float) -data.getY());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public SwapData line(Vec3d data) {
|
||
|
SwapData reverse = reverseSwap(data);
|
||
|
if (north) {
|
||
|
return new SwapData(reverse.pitch + 90, reverse.yaw + 90);
|
||
|
} else {
|
||
|
return new SwapData(reverse.pitch - 180, reverse.yaw - 90);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
SwapFunction SWAP_PITCH_ROLL = new SwapFunction() {
|
||
|
@Override
|
||
|
public Vec3d swap(SwapData data) {
|
||
|
return new Vec3d(data.yaw, data.pitch, 0);
|
||
|
}
|
||
|
|
||
|
private SwapData reverseSwap(Vec3d data) {
|
||
|
return new SwapData((float) data.getX(), (float) data.getY());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public SwapData line(Vec3d data) {
|
||
|
SwapData reverse = reverseSwap(data);
|
||
|
return new SwapData(-reverse.pitch, reverse.yaw);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public class Rotation {
|
||
|
private float yaw = 0;
|
||
|
private float pitch = 0;
|
||
|
private final String key;
|
||
|
|
||
|
private Rotation(float yaw, float pitch, String key) {
|
||
|
setRaw(yaw, pitch);
|
||
|
this.key = key;
|
||
|
}
|
||
|
|
||
|
private void fromTag(CompoundTag tag) {
|
||
|
CompoundTag rotation = tag.getCompound(key);
|
||
|
setRaw(rotation.getInt("Yaw"), rotation.getInt("Pitch"));
|
||
|
}
|
||
|
|
||
|
private void toTag(CompoundTag tag) {
|
||
|
CompoundTag rotation = new CompoundTag();
|
||
|
rotation.putFloat("Yaw", yaw);
|
||
|
rotation.putFloat("Pitch", pitch);
|
||
|
tag.put(key, rotation);
|
||
|
}
|
||
|
|
||
|
private void change(float yaw, float pitch) {
|
||
|
setRaw(this.yaw + yaw, this.pitch + pitch);
|
||
|
markDirty();
|
||
|
}
|
||
|
|
||
|
private void setRaw(float yaw, float pitch) {
|
||
|
setYaw(yaw);
|
||
|
setPitch(pitch);
|
||
|
}
|
||
|
|
||
|
private void setYaw(float yaw) {
|
||
|
this.yaw = yaw % 360;
|
||
|
}
|
||
|
|
||
|
private void setPitch(float pitch) {
|
||
|
this.pitch = Math.max(MIN_PITCH, Math.min(MAX_PITCH, pitch % 360));
|
||
|
}
|
||
|
|
||
|
public float getYaw(boolean display) {
|
||
|
return (float) getBaseVector(display).getX();
|
||
|
}
|
||
|
|
||
|
public float getPitch(boolean display) {
|
||
|
return (float) getBaseVector(display).getY();
|
||
|
}
|
||
|
|
||
|
public float getRoll(boolean display) {
|
||
|
return (float) getBaseVector(display).getZ();
|
||
|
}
|
||
|
|
||
|
private Vec3d getBaseModifier() {
|
||
|
Direction facing = getCachedState().get(DefensiveLaserBlock.FACING);
|
||
|
switch (facing) {
|
||
|
case DOWN: {
|
||
|
return new Vec3d(0, 0, 0);
|
||
|
}
|
||
|
case UP: {
|
||
|
return new Vec3d(0, 180, 0);
|
||
|
}
|
||
|
case NORTH: {
|
||
|
return new Vec3d(0, 0, 90);
|
||
|
}
|
||
|
case SOUTH: {
|
||
|
return new Vec3d(180, 90, 90);
|
||
|
}
|
||
|
case EAST: {
|
||
|
return new Vec3d(180, 0, 90);
|
||
|
}
|
||
|
case WEST: {
|
||
|
return new Vec3d(0, 0, -90);
|
||
|
}
|
||
|
default: {
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public SwapFunction getSwapFunction() {
|
||
|
Direction facing = getCachedState().get(DefensiveLaserBlock.FACING);
|
||
|
switch (facing) {
|
||
|
case DOWN: {
|
||
|
return SwapFunction.upDown(false);
|
||
|
}
|
||
|
case UP: {
|
||
|
return SwapFunction.upDown(true);
|
||
|
}
|
||
|
case EAST:
|
||
|
case WEST: {
|
||
|
return SwapFunction.SWAP_PITCH_ROLL;
|
||
|
}
|
||
|
case NORTH: {
|
||
|
return SwapFunction.northSouth(true);
|
||
|
}
|
||
|
case SOUTH: {
|
||
|
return SwapFunction.northSouth(false);
|
||
|
}
|
||
|
default: {
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Vec3d getBaseVector(boolean display) {
|
||
|
Vec3d modifier = getBaseModifier();
|
||
|
return getSwapFunction().swap(new SwapFunction.SwapData(yaw, -(pitch - (display ? 45 : 0)))).add(modifier);
|
||
|
}
|
||
|
|
||
|
private Vec3d getRayVector() {
|
||
|
SwapFunction.SwapData data = getSwapFunction().line(getBaseVector(false));
|
||
|
float pitch = data.pitch * (float) DEG2RAD;
|
||
|
float yaw = data.yaw * (float) DEG2RAD;
|
||
|
float cosYaw = MathHelper.cos(yaw);
|
||
|
float sinYaw = MathHelper.sin(yaw);
|
||
|
float cosPitch = MathHelper.cos(pitch);
|
||
|
float sinPitch = MathHelper.sin(pitch);
|
||
|
return new Vec3d(sinYaw * cosPitch, -sinPitch, cosYaw * cosPitch);
|
||
|
}
|
||
|
|
||
|
private float getAngleChange(float from, float to) {
|
||
|
float diff = MathHelper.subtractAngles(from, to);
|
||
|
return MathHelper.clamp(diff, -ROTATION_INCREMENT, ROTATION_INCREMENT);
|
||
|
}
|
||
|
|
||
|
private void target(float targetYaw, float targetPitch) {
|
||
|
SwapFunction function = getSwapFunction();
|
||
|
SwapFunction.SwapData data = function.line(getBaseVector(false));
|
||
|
Vec3d vec = new Vec3d(targetYaw, targetPitch, 0);
|
||
|
float yawChange = getAngleChange(data.yaw, (float) vec.getX());
|
||
|
float pitchChange = getAngleChange(data.pitch, (float) vec.getY());
|
||
|
change(yawChange, pitchChange);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public DefensiveLaserBlockEntity(BlockEntityType<?> type) {
|
||
|
super(type);
|
||
|
}
|
||
|
|
||
|
public static final double DEG2RAD = Math.PI / 180d;
|
||
|
private static final double RAD2DEG = 180d / Math.PI;
|
||
|
|
||
|
private float getTargetPitch(double x, double y, double z) {
|
||
|
Vec3d pos = getPosVec();
|
||
|
double diffX = x - pos.getX();
|
||
|
double diffY = y - pos.getY();
|
||
|
double diffZ = z - pos.getZ();
|
||
|
double g = MathHelper.sqrt(diffX * diffX + diffZ * diffZ);
|
||
|
return (float) -(MathHelper.atan2(diffY, g) * RAD2DEG);
|
||
|
}
|
||
|
|
||
|
private float getTargetYaw(double x, double z) {
|
||
|
Vec3d pos = getPosVec();
|
||
|
double diffX = x - pos.getX();
|
||
|
double diffZ = z - pos.getZ();
|
||
|
return (float) (MathHelper.atan2(diffZ, diffX) * RAD2DEG) - 90.0F;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void tickEnergy() {
|
||
|
assert getWorld() != null;
|
||
|
Vec3d vec = rotation.getRayVector().add(getPosVec());
|
||
|
((ServerWorld) getWorld()).spawnParticles(ParticleTypes.END_ROD, vec.getX(), vec.getY(), vec.getZ(), 1, 0, 0, 0, 0);
|
||
|
addAction(Action.createBlockStatePropertyAction(Config.DEFENSIVE_LASER_IDLE_ENERGY_REQUIRED, DefensiveLaserBlock.POWERED, true, false));
|
||
|
rotation.target(45, 45);
|
||
|
if (getCachedState().get(DefensiveLaserBlock.POWERED)) {
|
||
|
if (countdown > 0) {
|
||
|
countdown--;
|
||
|
}
|
||
|
if (countdown == 1) {
|
||
|
// Fire!
|
||
|
target = null;
|
||
|
} else if (countdown < 1) {
|
||
|
if (target == null) {
|
||
|
List<LivingEntity> entities = getWorld().getEntities(LivingEntity.class, new Box(getPos()).expand(Config.DEFENSIVE_LASER_RANGE), predicate);
|
||
|
if (entities.size() > 0) {
|
||
|
Collections.shuffle(entities);
|
||
|
target = entities.get(0);
|
||
|
}
|
||
|
} else {
|
||
|
if (predicate.test(target)) {
|
||
|
Optional<Vec3d> optional = target.getBoundingBox().rayTrace(getPosVec(), getPosVec().add(rotation.getRayVector().multiply(Config.DEFENSIVE_LASER_RANGE)));
|
||
|
if (optional.isPresent()) {
|
||
|
countdown = COUNTDOWN;
|
||
|
} else {
|
||
|
Vec3d targetPos = target.getPos();
|
||
|
float targetYaw = getTargetYaw(targetPos.getX(), targetPos.getZ());
|
||
|
float targetPitch = getTargetPitch(targetPos.getX(), targetPos.getY(), targetPos.getZ());
|
||
|
rotation.target(targetYaw, targetPitch);
|
||
|
}
|
||
|
} else {
|
||
|
target = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public CompoundTag toTag(CompoundTag tag) {
|
||
|
super.toTag(tag);
|
||
|
rotation.toTag(tag);
|
||
|
tag.putInt("Countdown", countdown);
|
||
|
return tag;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void fromTag(BlockState state, CompoundTag tag) {
|
||
|
super.fromTag(state, tag);
|
||
|
rotation.fromTag(tag);
|
||
|
countdown = tag.getInt("Countdown");
|
||
|
}
|
||
|
}
|