package com.thebrokenrail.energonrelics.block.entity; import com.thebrokenrail.energonrelics.api.block.entity.core.EnergyReceiverBlockEntity; import com.thebrokenrail.energonrelics.api.energy.Action; import com.thebrokenrail.energonrelics.config.HardcodedConfig; import com.thebrokenrail.energonrelics.EnergonRelics; import com.thebrokenrail.energonrelics.block.DefensiveLaserBlock; import com.thebrokenrail.energonrelics.mixin.DamageSourceAccessor; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntityType; import net.minecraft.entity.Entity; import net.minecraft.entity.ItemEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.player.PlayerEntity; 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.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.RayTraceContext; import net.minecraft.world.World; import net.minecraft.world.explosion.Explosion; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; public class DefensiveLaserBlockEntity extends EnergyReceiverBlockEntity { private static final float ROTATION_INCREMENT = 4; public final Rotation rotation = new Rotation(); private static Vec3d getPosVec(BlockPos pos) { return new Vec3d(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); } public Vec3d getPosVec() { return getPosVec(getPos()); } @Override public BlockState getCachedState() { if (hasWorld()) { return super.getCachedState(); } else { return EnergonRelics.DEFENSIVE_LASER_BLOCK.getDefaultState(); } } public static class Rotation { private float yaw = 0; private float pitch = 0; private static final String KEY = "Rotation"; private Rotation() { setRaw(0f, 0f); } 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 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 = pitch % 360; } public float getYaw() { return yaw; } public float getPitch() { return pitch; } private void change(float diffYaw, float diffPitch) { setRaw(yaw + diffYaw, pitch + diffPitch); } public Vec3d getRayVector() { return DefensiveLaserBlockEntity.getRayVector(yaw, pitch); } 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) { float yawChange = getAngleChange(yaw, targetYaw); float pitchChange = getAngleChange(pitch, targetPitch); change(yawChange, pitchChange); } } private static Vec3d getRayVector(float yaw, float pitch) { float pitchRad = pitch * (float) DEG2RAD; float yawRad = yaw * (float) DEG2RAD; float cosYaw = MathHelper.cos(yawRad); float sinYaw = MathHelper.sin(yawRad); float cosPitch = MathHelper.cos(pitchRad); float sinPitch = MathHelper.sin(pitchRad); return new Vec3d(sinYaw * cosPitch, -sinPitch, cosYaw * cosPitch); } 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(Vec3d targetPos) { Vec3d pos = getPosVec(); double diffX = targetPos.getX() - pos.getX(); double diffY = targetPos.getY() - pos.getY(); double diffZ = targetPos.getZ() - pos.getZ(); double g = MathHelper.sqrt(diffX * diffX + diffZ * diffZ); return (float) -(MathHelper.atan2(diffY, g) * RAD2DEG); } private float getTargetYaw(Vec3d targetPos) { Vec3d pos = getPosVec(); double diffX = targetPos.getX() - pos.getX(); double diffZ = targetPos.getZ() - pos.getZ(); return -((float) (MathHelper.atan2(diffZ, diffX) * RAD2DEG) - 90f); } private static final int COUNTDOWN = 28; private LivingEntity target; private int countdown = 0; private Predicate getTargetPredicate(boolean useCurrentRotation) { return entity -> { if (entity.isAlive() && (!(entity instanceof PlayerEntity) || !((PlayerEntity) entity).isCreative()) && !entity.isInvisible() && entity.getPos().distanceTo(getPosVec()) <= HardcodedConfig.DEFENSIVE_LASER_RANGE) { Vec3d entityPos = entity.getPos(); Vec3d start; if (useCurrentRotation) { start = rotation.getRayVector().add(getPosVec()); } else { start = getRayVector(getTargetYaw(entityPos), getTargetPitch(entityPos)).add(getPosVec()); } HitResult result = entity.getEntityWorld().rayTrace(new RayTraceContext(start, entityPos, RayTraceContext.ShapeType.COLLIDER, RayTraceContext.FluidHandling.ANY, getDummyEntity())); return result.getType() == HitResult.Type.MISS; } else { return false; } }; } private List getEntities(Predicate predicate) { return Objects.requireNonNull(getWorld()).getEntitiesByClass(LivingEntity.class, new Box(getPos()).expand(HardcodedConfig.DEFENSIVE_LASER_RANGE), predicate); } private Entity getDummyEntity() { Vec3d posVec = getPosVec(); return new ItemEntity(getWorld(), posVec.getX(), posVec.getY(), posVec.getZ()); } private static final DamageSource DAMAGE_SOURCE = DamageSourceAccessor.createDamageSource(EnergonRelics.NAMESPACE + ".defensive_laser").setScaledWithDifficulty().setExplosive(); private void fire(World world, BlockPos pos) { List entities = getEntities(entity -> true); Vec3d posVec = getPosVec(pos); Vec3d collision = null; for (LivingEntity entity : entities) { Optional optional = entity.getBoundingBox().rayTrace(posVec, posVec.add(rotation.getRayVector().multiply(HardcodedConfig.DEFENSIVE_LASER_RANGE))); if (optional.isPresent()) { Vec3d vec = optional.get(); if (collision == null || vec.distanceTo(posVec) < collision.distanceTo(posVec)) { collision = optional.get(); } } } Vec3d rotationVec = rotation.getRayVector(); Vec3d start = rotationVec.add(posVec); Vec3d end = rotationVec.multiply(HardcodedConfig.DEFENSIVE_LASER_RANGE).add(posVec); HitResult result = world.rayTrace(new RayTraceContext(start, end, RayTraceContext.ShapeType.COLLIDER, RayTraceContext.FluidHandling.ANY, getDummyEntity())); if (result.getType() != HitResult.Type.MISS && (collision == null || result.getPos().distanceTo(posVec) < collision.distanceTo(posVec))) { collision = result.getPos(); } if (collision != null) { world.createExplosion(null, DAMAGE_SOURCE, null, collision.getX(), collision.getY(), collision.getZ(), 2f, false, Explosion.DestructionType.NONE); double distance = posVec.distanceTo(collision); double multiplier = 4d; distance = distance * multiplier; for (int i = 0; i < distance; i++) { Vec3d vec = rotationVec.multiply(i / multiplier).add(posVec); ((ServerWorld) world).spawnParticles(ParticleTypes.END_ROD, vec.getX(), vec.getY(), vec.getZ(), 1, 0, 0, 0, 0.4f); } } } @Override protected void energyTick() { assert getWorld() != null; addAction(Action.createBlockStatePropertyAction(HardcodedConfig.DEFENSIVE_LASER_IDLE_ENERGY_REQUIRED, DefensiveLaserBlock.POWERED, true, false)); if (getCachedState().get(DefensiveLaserBlock.POWERED)) { if (countdown > 0) { countdown--; } if (countdown == 1) { addAction(new Action(HardcodedConfig.DEFENSIVE_LASER_FIRE_ENERGY_REQUIRED, (world, pos, state) -> fire(world, pos), (world, pos, state) -> {})); target = null; } else if (countdown < 1) { if (target == null) { List entities = getEntities(getTargetPredicate(false)); if (entities.size() > 0) { Vec3d posVec = getPosVec(); entities.sort(Comparator.comparingDouble(entity -> entity.getPos().distanceTo(posVec))); target = entities.get(0); } } else { if (getTargetPredicate(false).test(target)) { Optional optional = target.getBoundingBox().rayTrace(getPosVec(), getPosVec().add(rotation.getRayVector().multiply(HardcodedConfig.DEFENSIVE_LASER_RANGE))); if (optional.isPresent() && getTargetPredicate(true).test(target)) { countdown = COUNTDOWN; } else { Vec3d targetPos = target.getPos(); float targetYaw = getTargetYaw(targetPos); float targetPitch = getTargetPitch(targetPos); rotation.target(targetYaw, targetPitch); } } else { target = null; } } } markDirty(); } } @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"); } }