diff --git a/src/main/java/com/simibubi/create/content/optics/Beam.java b/src/main/java/com/simibubi/create/content/optics/Beam.java index fae23f9b7..b9c9ae6fe 100644 --- a/src/main/java/com/simibubi/create/content/optics/Beam.java +++ b/src/main/java/com/simibubi/create/content/optics/Beam.java @@ -1,11 +1,5 @@ package com.simibubi.create.content.optics; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -import javax.annotation.Nullable; - import com.mojang.blaze3d.matrix.MatrixStack; import com.simibubi.create.foundation.utility.VecHelper; @@ -14,6 +8,12 @@ import net.minecraft.item.DyeColor; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; +import javax.annotation.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + public class Beam extends ArrayList { private final Set lightEventListeners; @Nullable @@ -68,10 +68,16 @@ public class Beam extends ArrayList { } public boolean isRemoved() { - // || !get(0).getHandler().getOutBeams().contains(this) ILightHandler handler = getHandler(); removed = removed || isEmpty() || handler == null || handler.getTile() - .isRemoved() || (parent != null && parent.isRemoved()); + .isRemoved() || (parent != null && parent.isRemovedSimple()); + return removed; + } + + private boolean isRemovedSimple() { + ILightHandler handler = getHandler(); + removed = removed || isEmpty() || handler == null || handler.getTile() + .isRemoved(); return removed; } @@ -88,4 +94,10 @@ public class Beam extends ArrayList { return out; } + + + @Nullable + public Beam getParent() { + return parent; + } } diff --git a/src/main/java/com/simibubi/create/content/optics/LightHandlingBehaviour.java b/src/main/java/com/simibubi/create/content/optics/LightHandlingBehaviour.java new file mode 100644 index 000000000..ae95161fe --- /dev/null +++ b/src/main/java/com/simibubi/create/content/optics/LightHandlingBehaviour.java @@ -0,0 +1,119 @@ +package com.simibubi.create.content.optics; + +import com.google.common.collect.Iterators; +import com.simibubi.create.foundation.tileEntity.SmartTileEntity; +import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; +import com.simibubi.create.foundation.utility.BeaconHelper; +import com.simibubi.create.foundation.utility.VecHelper; + +import net.minecraft.tileentity.BeaconTileEntity; +import net.minecraft.tileentity.TileEntity; + +import javax.annotation.Nullable; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public abstract class LightHandlingBehaviour extends TileEntityBehaviour implements ILightHandler { + protected final T handler; + public Set beams; + private boolean isUpdating; + @Nullable + private BeaconTileEntity beacon; + private Beam beaconBeam = null; + + public LightHandlingBehaviour(T te) { + super(te); + handler = te; + isUpdating = false; + beams = new HashSet<>(); + } + + @Override + public void tick() { + super.tick(); + if (beacon != null && beacon.isRemoved()) + updateBeaconState(); + } + + protected void updateBeaconState() { + BeaconTileEntity beaconBefore = beacon; + beacon = BeaconHelper.getBeaconTE(getBlockPos(), getHandlerWorld()) + .orElse(null); + + if (beaconBefore != null) { + beaconBeam.clear(); + beaconBeam = null; + updateBeams(); + } + + if (beacon != null) { + beaconBeam = constructOutBeam(null, VecHelper.UP, beacon.getPos()); + if (beaconBeam != null && !beaconBeam.isEmpty()) { + beaconBeam.addListener(this); + beaconBeam.onCreated(); + } + } + } + + @Override + public TileEntity getTile() { + return tileEntity; + } + + @Override + public void lazyTick() { + super.lazyTick(); + updateBeaconState(); + updateBeams(); + } + + @Override + public void updateBeams() { + if (isUpdating) + return; + isUpdating = true; + + Set newBeams = new HashSet<>(); + for (Beam child : new HashSet<>(beams)) { + Beam parent = child.getParent(); + + child.onRemoved(); + if (parent == null || parent.isRemoved()) + continue; + constructSubBeams(parent, newBeams).forEach(Beam::onCreated); + } + beams = newBeams; + isUpdating = false; + } + + @Override + public Stream constructSubBeams(Beam beam) { + if (beams.stream() + .map(Beam::getParent) + .filter(Objects::nonNull) + .filter(((Predicate) Beam::isRemoved).negate()) + .map(Beam::getDirection) + .filter(Objects::nonNull) + .anyMatch(b -> b.equals(beam.getDirection()))) + return Stream.empty(); + return constructSubBeams(beam, beams); + } + + public Stream constructSubBeams(Beam beam, Set beamListing) { + return safeConstructSubBeamsFor(beam) + .filter(Objects::nonNull) + .filter(((Predicate) Beam::isEmpty).negate()) + .peek(beamListing::add); + } + + protected abstract Stream safeConstructSubBeamsFor(Beam beam); + + @Override + public Iterator getRenderBeams() { + Iterator beaconIter = beaconBeam == null ? Collections.emptyIterator() : Collections.singleton(beaconBeam) + .iterator(); + return Iterators.concat(beams.iterator(), beaconIter); + } +} diff --git a/src/main/java/com/simibubi/create/content/optics/RotatedLightHandlingBehaviour.java b/src/main/java/com/simibubi/create/content/optics/RotatedLightHandlingBehaviour.java new file mode 100644 index 000000000..b827169ca --- /dev/null +++ b/src/main/java/com/simibubi/create/content/optics/RotatedLightHandlingBehaviour.java @@ -0,0 +1,72 @@ +package com.simibubi.create.content.optics; + +import com.simibubi.create.content.contraptions.base.KineticTileEntity; +import com.simibubi.create.content.optics.mirror.RotationMode; +import com.simibubi.create.foundation.utility.ServerSpeedProvider; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.math.MathHelper; + +public abstract class RotatedLightHandlingBehaviour extends LightHandlingBehaviour { + protected float angle; + protected float clientAngleDiff; + private float prevAngle; + + public RotatedLightHandlingBehaviour(T te) { + super(te); + } + + @Override + public void write(CompoundNBT nbt, boolean clientPacket) { + nbt.putFloat("Angle", angle); + super.write(nbt, clientPacket); + } + + @Override + public void read(CompoundNBT nbt, boolean clientPacket) { + angle = nbt.getFloat("Angle"); + super.read(nbt, clientPacket); + } + + public float getInterpolatedAngle(float partialTicks) { + if (tileEntity.isVirtual()) + return MathHelper.lerp(partialTicks + .5f, prevAngle, angle); + if (handler.getMode() == RotationMode.ROTATE_LIMITED && Math.abs(angle) == 90) + return angle; + return MathHelper.lerp(partialTicks, angle, angle + getAngularSpeed()); + } + + public float getAngularSpeed() { + float speed = handler.getSpeed() * 3 / 10f; + if (handler.getSpeed() == 0) + speed = 0; + if (getHandlerWorld() != null && getHandlerWorld().isRemote) { + speed *= ServerSpeedProvider.get(); + speed += clientAngleDiff / 3f; + } + return speed; + } + + @Override + public void tick() { + super.tick(); + + prevAngle = angle; + if (getHandlerWorld() != null && getHandlerWorld().isRemote) + clientAngleDiff /= 2; + + float angularSpeed = getAngularSpeed(); + float newAngle = angle + angularSpeed; + angle = newAngle % 360; + + if (handler.getMode() == RotationMode.ROTATE_LIMITED) + angle = MathHelper.clamp(angle, -90, 90); + if (handler.getMode() == RotationMode.ROTATE_45 && angle == prevAngle) // don't snap while still rotating + angle = 45F * Math.round(Math.round(angle) / 45F); + + + if (angle != prevAngle) { + updateBeams(); + } + } +} diff --git a/src/main/java/com/simibubi/create/content/optics/mirror/MirrorBehaviour.java b/src/main/java/com/simibubi/create/content/optics/mirror/MirrorBehaviour.java index e7a3f760a..fe7aee0f0 100644 --- a/src/main/java/com/simibubi/create/content/optics/mirror/MirrorBehaviour.java +++ b/src/main/java/com/simibubi/create/content/optics/mirror/MirrorBehaviour.java @@ -1,160 +1,29 @@ package com.simibubi.create.content.optics.mirror; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import com.google.common.collect.Iterators; import com.simibubi.create.content.optics.Beam; -import com.simibubi.create.content.optics.ILightHandler; +import com.simibubi.create.content.optics.RotatedLightHandlingBehaviour; import com.simibubi.create.foundation.collision.Matrix3d; -import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType; import com.simibubi.create.foundation.utility.AngleHelper; -import com.simibubi.create.foundation.utility.BeaconHelper; -import com.simibubi.create.foundation.utility.ServerSpeedProvider; import com.simibubi.create.foundation.utility.VecHelper; -import net.minecraft.nbt.CompoundNBT; -import net.minecraft.tileentity.BeaconTileEntity; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Direction; -import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.vector.Vector3d; -public class MirrorBehaviour extends TileEntityBehaviour implements ILightHandler { - public static final BehaviourType TYPE = new BehaviourType<>(); - private final MirrorTileEntity mirror; - protected float angle; - protected float clientAngleDiff; - Map beams; - private float prevAngle; - @Nullable - private BeaconTileEntity beacon; - private Beam beaconBeam = null; - private boolean isUpdating = false; +import javax.annotation.Nonnull; +import java.util.stream.Stream; + +public class MirrorBehaviour extends RotatedLightHandlingBehaviour { + public static final BehaviourType TYPE = new BehaviourType<>(); public MirrorBehaviour(MirrorTileEntity te) { super(te); - mirror = te; - beacon = null; - beams = new HashMap<>(); - } - - @Override - public void write(CompoundNBT nbt, boolean clientPacket) { - nbt.putFloat("Angle", angle); - super.write(nbt, clientPacket); - } - - @Override - public void read(CompoundNBT nbt, boolean clientPacket) { - angle = nbt.getFloat("Angle"); - super.read(nbt, clientPacket); - } - - public float getInterpolatedAngle(float partialTicks) { - if (tileEntity.isVirtual()) - return MathHelper.lerp(partialTicks + .5f, prevAngle, angle); - if (mirror.movementMode.get() == RotationMode.ROTATE_LIMITED && Math.abs(angle) == 90) - return angle; - return MathHelper.lerp(partialTicks, angle, angle + getAngularSpeed()); - } - - public float getAngularSpeed() { - float speed = mirror.getSpeed() * 3 / 10f; - if (mirror.getSpeed() == 0) - speed = 0; - if (getHandlerWorld() != null && getHandlerWorld().isRemote) { - speed *= ServerSpeedProvider.get(); - speed += clientAngleDiff / 3f; - } - return speed; - } - - @Override - public void tick() { - super.tick(); - - prevAngle = angle; - if (getHandlerWorld() != null && getHandlerWorld().isRemote) - clientAngleDiff /= 2; - - float angularSpeed = getAngularSpeed(); - float newAngle = angle + angularSpeed; - angle = newAngle % 360; - - if (mirror.movementMode.get() == RotationMode.ROTATE_LIMITED) - angle = MathHelper.clamp(angle, -90, 90); - if (mirror.movementMode.get() == RotationMode.ROTATE_45 && angle == prevAngle) // don't snap while still rotating - angle = 45F * Math.round(Math.round(angle) / 45F); - - - if (angle != prevAngle) { - updateBeams(); - } - - if (beacon != null && beacon.isRemoved()) - updateBeaconState(); - } - - private void updateBeaconState() { - BeaconTileEntity beaconBefore = beacon; - beacon = BeaconHelper.getBeaconTE(getBlockPos(), getHandlerWorld()) - .orElse(null); - - if (beaconBefore != null) { - beaconBeam.clear(); - beaconBeam = null; - updateBeams(); - } - - if (beacon != null) { - beaconBeam = constructOutBeam(null, VecHelper.UP, beacon.getPos()); - if (beaconBeam != null && !beaconBeam.isEmpty()) { - beaconBeam.addListener(this); - beaconBeam.onCreated(); - } - } - } - - @Override - public void updateBeams() { - if (isUpdating) - return; - isUpdating = true; - - Map newBeams = new HashMap<>(); - for (Map.Entry entry : new HashSet<>(beams.entrySet())) { - entry.getValue() - .onRemoved(); - if (entry.getKey() - .isRemoved()) - continue; - Beam reflected = reflectBeam(entry.getKey()); - if (reflected != null && !reflected.isEmpty()) { - newBeams.put(entry.getKey(), reflected); - reflected.onCreated(); - entry.getKey() - .addListener(this); - } - } - beams = newBeams; - isUpdating = false; } private Vector3d getReflectionAngle(Vector3d inputAngle) { inputAngle = inputAngle.normalize(); - Direction.Axis axis = mirror.getAxis(); + Direction.Axis axis = handler.getAxis(); Vector3d normal; if (axis.isHorizontal()) normal = new Matrix3d().asIdentity() @@ -168,22 +37,11 @@ public class MirrorBehaviour extends TileEntityBehaviour implements ILightHandle return inputAngle.subtract(normal.scale(2 * inputAngle.dotProduct(normal))); } - @Override - public void lazyTick() { - super.lazyTick(); - updateBeaconState(); - updateBeams(); - } - - @Override - public TileEntity getTile() { - return tileEntity; - } @Nonnull @Override public Direction getBeamRotationAround() { - return Direction.getFacingFromAxisDirection(mirror.getAxis(), Direction.AxisDirection.POSITIVE); + return Direction.getFacingFromAxisDirection(handler.getAxis(), Direction.AxisDirection.POSITIVE); } @Override @@ -192,37 +50,11 @@ public class MirrorBehaviour extends TileEntityBehaviour implements ILightHandle } @Override - public Iterator getRenderBeams() { - Iterator beaconIter = beaconBeam == null ? Collections.emptyIterator() : Collections.singleton(beaconBeam) - .iterator(); - return Iterators.concat(beams.values() - .iterator(), beaconIter); - } - - @Override - public Stream constructSubBeams(Beam beam) { - if (beams.keySet() - .stream() - .filter(((Predicate) Beam::isRemoved).negate()) - .map(Beam::getDirection) - .filter(Objects::nonNull) - .anyMatch(b -> b.equals(beam.getDirection()))) - return Stream.empty(); - Beam reflected = reflectBeam(beam); - if (reflected != null && !reflected.isEmpty()) { - beams.put(beam, reflected); - beam.addListener(this); - return Stream.of(reflected); - } - return Stream.empty(); - } - - @Nullable - private Beam reflectBeam(Beam beam) { + protected Stream safeConstructSubBeamsFor(Beam beam) { Vector3d inDir = beam.getDirection(); if (inDir == null) - return null; + return Stream.empty(); - return constructOutBeam(beam, getReflectionAngle(inDir).normalize()); + return Stream.of(constructOutBeam(beam, getReflectionAngle(inDir).normalize())); } } diff --git a/src/main/java/com/simibubi/create/content/optics/mirror/MirrorTileEntity.java b/src/main/java/com/simibubi/create/content/optics/mirror/MirrorTileEntity.java index b121097f2..f2a8cd054 100644 --- a/src/main/java/com/simibubi/create/content/optics/mirror/MirrorTileEntity.java +++ b/src/main/java/com/simibubi/create/content/optics/mirror/MirrorTileEntity.java @@ -17,7 +17,7 @@ import net.minecraft.util.math.AxisAlignedBB; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; -public class MirrorTileEntity extends KineticTileEntity implements ILightHandlerProvider { +public class MirrorTileEntity extends KineticTileEntity implements ILightHandlerProvider, RotationMode.RotationModeProvider { protected ScrollOptionBehaviour movementMode; protected MirrorBehaviour mirror; @@ -63,4 +63,9 @@ public class MirrorTileEntity extends KineticTileEntity implements ILightHandler public ILightHandler getHandler() { return mirror; } + + @Override + public RotationMode getMode() { + return movementMode.get(); + } } diff --git a/src/main/java/com/simibubi/create/content/optics/mirror/RotationMode.java b/src/main/java/com/simibubi/create/content/optics/mirror/RotationMode.java index bf8bf0d51..3de71f7b9 100644 --- a/src/main/java/com/simibubi/create/content/optics/mirror/RotationMode.java +++ b/src/main/java/com/simibubi/create/content/optics/mirror/RotationMode.java @@ -30,4 +30,8 @@ public enum RotationMode implements INamedIconOptions { return translationKey; } + @FunctionalInterface + public interface RotationModeProvider { + RotationMode getMode(); + } }