package net.minestom.server.utils; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import net.minestom.server.coordinate.Point; import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; import net.minestom.server.instance.EntityTracker; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.Consumer; import java.util.function.Predicate; /** * Defines which players are able to see this element. */ @ApiStatus.Internal public final class ViewEngine { private final Entity entity; private final Set manualViewers = ConcurrentHashMap.newKeySet(); private EntityTracker tracker; private Point lastTrackingPoint; // Decide if this entity should be viewable to X players public final Option viewableOption; // Decide if this entity should view X entities public final Option viewerOption; private final Set set = new SetImpl(); private final Object mutex = this; public ViewEngine(@Nullable Entity entity, Consumer autoViewableAddition, Consumer autoViewableRemoval, Consumer autoViewerAddition, Consumer autoViewerRemoval) { this.entity = entity; this.viewableOption = new Option<>(Entity::autoViewEntities, autoViewableAddition, autoViewableRemoval); this.viewerOption = new Option<>(Entity::isAutoViewable, autoViewerAddition, autoViewerRemoval); } public ViewEngine() { this(null, null, null, null, null); } public void updateTracker(@NotNull Point point, @Nullable EntityTracker tracker) { synchronized (mutex) { this.tracker = tracker; this.lastTrackingPoint = point; if (tracker != null) { this.viewableOption.references = tracker.references(point, EntityTracker.Target.PLAYERS); this.viewerOption.references = tracker.references(point, EntityTracker.Target.ENTITIES); } else { this.viewableOption.references = null; this.viewerOption.references = null; } } } public boolean manualAdd(@NotNull Player player) { if (player == this.entity) return false; synchronized (mutex) { return manualViewers.add(player); } } public boolean manualRemove(@NotNull Player player) { if (player == this.entity) return false; synchronized (mutex) { return manualViewers.remove(player); } } public void forManuals(@NotNull Consumer consumer) { synchronized (mutex) { this.manualViewers.forEach(consumer); } } public boolean hasPredictableViewers() { // Verify if this entity's viewers can be predicted from surrounding entities synchronized (mutex) { return viewableOption.isAuto() && manualViewers.isEmpty(); } } public void handleAutoViewAddition(Entity entity) { handleAutoView(entity, viewerOption.addition, viewableOption.addition); } public void handleAutoViewRemoval(Entity entity) { handleAutoView(entity, viewerOption.removal, viewableOption.removal); } private void handleAutoView(Entity entity, Consumer viewer, Consumer viewable) { if (entity.getVehicle() != null) return; // Passengers are handled by the vehicle, inheriting its viewing settings if (this.entity instanceof Player && viewerOption.isAuto() && entity.isAutoViewable()) { viewer.accept(entity); // Send packet to this player } if (entity instanceof Player player && player.autoViewEntities() && viewableOption.isAuto()) { viewable.accept(player); // Send packet to the range-visible player } } private boolean validAutoViewer(Player player) { return entity == null || viewableOption.isRegistered(player); } public Object mutex() { return mutex; } public Set asSet() { return set; } public final class Option { @SuppressWarnings("rawtypes") private static final AtomicIntegerFieldUpdater