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 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 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 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"); } }