// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.modules.entity.tracker;

import java.util.function.Supplier;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import com.hypixel.hytale.protocol.EntityUpdate;
import it.unimi.dsi.fastutil.ints.IntList;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.system.HolderSystem;
import com.hypixel.hytale.component.system.ISystem;
import it.unimi.dsi.fastutil.objects.ObjectList;
import com.hypixel.hytale.component.spatial.SpatialStructure;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.component.dependency.SystemDependency;
import com.hypixel.hytale.server.core.modules.entity.system.NetworkSendableSpatialSystem;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import java.util.Collections;
import com.hypixel.hytale.component.dependency.SystemGroupDependency;
import com.hypixel.hytale.component.dependency.Order;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.dependency.Dependency;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Iterator;
import java.util.HashSet;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.concurrent.ConcurrentHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.component.ComponentType;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.Map;
import java.util.Set;
import com.hypixel.hytale.server.core.receiver.IPacketReceiver;
import com.hypixel.hytale.component.Component;
import javax.annotation.Nullable;
import java.util.Collection;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.protocol.ComponentUpdate;
import java.util.List;
import com.hypixel.hytale.protocol.ComponentUpdateType;
import java.util.EnumSet;
import java.util.concurrent.locks.StampedLock;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.protocol.packets.entities.EntityUpdates;
import com.hypixel.hytale.component.Store;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.SystemGroup;

public class EntityTrackerSystems
{
    public static final SystemGroup<EntityStore> FIND_VISIBLE_ENTITIES_GROUP;
    public static final SystemGroup<EntityStore> QUEUE_UPDATE_GROUP;
    
    public static boolean despawnAll(@Nonnull final Ref<EntityStore> viewerRef, @Nonnull final Store<EntityStore> store) {
        final EntityViewer viewer = store.getComponent(viewerRef, EntityViewer.getComponentType());
        if (viewer == null) {
            return false;
        }
        final int networkId = viewer.sent.removeInt(viewerRef);
        final EntityUpdates packet = new EntityUpdates();
        packet.removed = viewer.sent.values().toIntArray();
        viewer.packetReceiver.writeNoCache(packet);
        clear(viewerRef, store);
        viewer.sent.put(viewerRef, networkId);
        return true;
    }
    
    public static boolean clear(@Nonnull final Ref<EntityStore> viewerRef, @Nonnull final Store<EntityStore> store) {
        final EntityViewer viewer = store.getComponent(viewerRef, EntityViewer.getComponentType());
        if (viewer == null) {
            return false;
        }
        for (final Ref<EntityStore> ref : viewer.sent.keySet()) {
            final Visible visible = store.getComponent(ref, Visible.getComponentType());
            if (visible != null) {
                visible.visibleTo.remove(viewerRef);
            }
        }
        viewer.sent.clear();
        return true;
    }
    
    static {
        FIND_VISIBLE_ENTITIES_GROUP = EntityStore.REGISTRY.registerSystemGroup();
        QUEUE_UPDATE_GROUP = EntityStore.REGISTRY.registerSystemGroup();
    }
    
    public static class EntityUpdate
    {
        @Nonnull
        private final StampedLock removeLock;
        @Nonnull
        private final EnumSet<ComponentUpdateType> removed;
        @Nonnull
        private final StampedLock updatesLock;
        @Nonnull
        private final List<ComponentUpdate> updates;
        
        public EntityUpdate() {
            this.removeLock = new StampedLock();
            this.updatesLock = new StampedLock();
            this.removed = EnumSet.noneOf(ComponentUpdateType.class);
            this.updates = new ObjectArrayList<ComponentUpdate>();
        }
        
        public EntityUpdate(@Nonnull final EntityUpdate other) {
            this.removeLock = new StampedLock();
            this.updatesLock = new StampedLock();
            this.removed = EnumSet.copyOf(other.removed);
            this.updates = new ObjectArrayList<ComponentUpdate>(other.updates);
        }
        
        @Nonnull
        public EntityUpdate clone() {
            return new EntityUpdate(this);
        }
        
        public void queueRemove(@Nonnull final ComponentUpdateType type) {
            final long stamp = this.removeLock.writeLock();
            try {
                this.removed.add(type);
            }
            finally {
                this.removeLock.unlockWrite(stamp);
            }
        }
        
        public void queueUpdate(@Nonnull final ComponentUpdate update) {
            final long stamp = this.updatesLock.writeLock();
            try {
                this.updates.add(update);
            }
            finally {
                this.updatesLock.unlockWrite(stamp);
            }
        }
        
        @Nullable
        public ComponentUpdateType[] toRemovedArray() {
            return (ComponentUpdateType[])(this.removed.isEmpty() ? null : ((ComponentUpdateType[])this.removed.toArray(ComponentUpdateType[]::new)));
        }
        
        @Nullable
        public ComponentUpdate[] toUpdatesArray() {
            return (ComponentUpdate[])(this.updates.isEmpty() ? null : ((ComponentUpdate[])this.updates.toArray(ComponentUpdate[]::new)));
        }
    }
    
    public static class EntityViewer implements Component<EntityStore>
    {
        public int viewRadiusBlocks;
        public IPacketReceiver packetReceiver;
        public Set<Ref<EntityStore>> visible;
        public Map<Ref<EntityStore>, EntityUpdate> updates;
        public Object2IntMap<Ref<EntityStore>> sent;
        public int lodExcludedCount;
        public int hiddenCount;
        
        public static ComponentType<EntityStore, EntityViewer> getComponentType() {
            return EntityModule.get().getEntityViewerComponentType();
        }
        
        public EntityViewer(final int viewRadiusBlocks, final IPacketReceiver packetReceiver) {
            this.viewRadiusBlocks = viewRadiusBlocks;
            this.packetReceiver = packetReceiver;
            this.visible = new ObjectOpenHashSet<Ref<EntityStore>>();
            this.updates = new ConcurrentHashMap<Ref<EntityStore>, EntityUpdate>();
            (this.sent = new Object2IntOpenHashMap<Ref<EntityStore>>()).defaultReturnValue(-1);
        }
        
        public EntityViewer(@Nonnull final EntityViewer other) {
            this.viewRadiusBlocks = other.viewRadiusBlocks;
            this.packetReceiver = other.packetReceiver;
            this.visible = new HashSet<Ref<EntityStore>>(other.visible);
            this.updates = new ConcurrentHashMap<Ref<EntityStore>, EntityUpdate>(other.updates.size());
            for (final Map.Entry<Ref<EntityStore>, EntityUpdate> entry : other.updates.entrySet()) {
                this.updates.put(entry.getKey(), entry.getValue().clone());
            }
            (this.sent = new Object2IntOpenHashMap<Ref<EntityStore>>(other.sent)).defaultReturnValue(-1);
        }
        
        @Nonnull
        @Override
        public Component<EntityStore> clone() {
            return new EntityViewer(this);
        }
        
        public void queueRemove(final Ref<EntityStore> ref, final ComponentUpdateType type) {
            if (!this.visible.contains(ref)) {
                throw new IllegalArgumentException("Entity is not visible!");
            }
            this.updates.computeIfAbsent(ref, k -> new EntityUpdate()).queueRemove(type);
        }
        
        public void queueUpdate(final Ref<EntityStore> ref, final ComponentUpdate update) {
            if (!this.visible.contains(ref)) {
                throw new IllegalArgumentException("Entity is not visible!");
            }
            this.updates.computeIfAbsent(ref, k -> new EntityUpdate()).queueUpdate(update);
        }
    }
    
    public static class Visible implements Component<EntityStore>
    {
        @Nonnull
        private final StampedLock lock;
        @Nonnull
        public Map<Ref<EntityStore>, EntityViewer> previousVisibleTo;
        @Nonnull
        public Map<Ref<EntityStore>, EntityViewer> visibleTo;
        @Nonnull
        public Map<Ref<EntityStore>, EntityViewer> newlyVisibleTo;
        
        public Visible() {
            this.lock = new StampedLock();
            this.previousVisibleTo = new Object2ObjectOpenHashMap<Ref<EntityStore>, EntityViewer>();
            this.visibleTo = new Object2ObjectOpenHashMap<Ref<EntityStore>, EntityViewer>();
            this.newlyVisibleTo = new Object2ObjectOpenHashMap<Ref<EntityStore>, EntityViewer>();
        }
        
        @Nonnull
        public static ComponentType<EntityStore, Visible> getComponentType() {
            return EntityModule.get().getVisibleComponentType();
        }
        
        @Nonnull
        @Override
        public Component<EntityStore> clone() {
            return new Visible();
        }
        
        public void addViewerParallel(final Ref<EntityStore> ref, final EntityViewer entityViewer) {
            final long stamp = this.lock.writeLock();
            try {
                this.visibleTo.put(ref, entityViewer);
                if (!this.previousVisibleTo.containsKey(ref)) {
                    this.newlyVisibleTo.put(ref, entityViewer);
                }
            }
            finally {
                this.lock.unlockWrite(stamp);
            }
        }
    }
    
    public static class ClearEntityViewers extends EntityTickingSystem<EntityStore>
    {
        public static final Set<Dependency<EntityStore>> DEPENDENCIES;
        private final ComponentType<EntityStore, EntityViewer> componentType;
        
        public ClearEntityViewers(final ComponentType<EntityStore, EntityViewer> componentType) {
            this.componentType = componentType;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return ClearEntityViewers.DEPENDENCIES;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final EntityViewer viewer = archetypeChunk.getComponent(index, this.componentType);
            viewer.visible.clear();
            viewer.lodExcludedCount = 0;
            viewer.hiddenCount = 0;
        }
        
        static {
            DEPENDENCIES = Collections.singleton(new SystemGroupDependency(Order.BEFORE, EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP));
        }
    }
    
    public static class CollectVisible extends EntityTickingSystem<EntityStore>
    {
        private final ComponentType<EntityStore, EntityViewer> componentType;
        private final Query<EntityStore> query;
        @Nonnull
        private final Set<Dependency<EntityStore>> dependencies;
        
        public CollectVisible(final ComponentType<EntityStore, EntityViewer> componentType) {
            this.componentType = componentType;
            this.query = (Query<EntityStore>)Archetype.of(componentType, TransformComponent.getComponentType());
            this.dependencies = (Set<Dependency<EntityStore>>)Collections.singleton(new SystemDependency(Order.AFTER, NetworkSendableSpatialSystem.class));
        }
        
        @Nullable
        @Override
        public SystemGroup<EntityStore> getGroup() {
            return EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return this.dependencies;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.query;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final TransformComponent transform = archetypeChunk.getComponent(index, TransformComponent.getComponentType());
            final Vector3d position = transform.getPosition();
            final EntityViewer entityViewer = archetypeChunk.getComponent(index, this.componentType);
            final SpatialStructure<Ref<EntityStore>> spatialStructure = store.getResource(EntityModule.get().getNetworkSendableSpatialResourceType()).getSpatialStructure();
            final ObjectList<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
            spatialStructure.collect(position, entityViewer.viewRadiusBlocks, results);
            entityViewer.visible.addAll(results);
        }
    }
    
    public static class ClearPreviouslyVisible extends EntityTickingSystem<EntityStore>
    {
        public static final Set<Dependency<EntityStore>> DEPENDENCIES;
        private final ComponentType<EntityStore, Visible> componentType;
        
        public ClearPreviouslyVisible(final ComponentType<EntityStore, Visible> componentType) {
            this.componentType = componentType;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return ClearPreviouslyVisible.DEPENDENCIES;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final Visible visible = archetypeChunk.getComponent(index, this.componentType);
            final Map<Ref<EntityStore>, EntityViewer> oldVisibleTo = visible.previousVisibleTo;
            visible.previousVisibleTo = visible.visibleTo;
            (visible.visibleTo = oldVisibleTo).clear();
            visible.newlyVisibleTo.clear();
        }
        
        static {
            DEPENDENCIES = Set.of(new SystemDependency(Order.AFTER, (Class<ISystem>)ClearEntityViewers.class), new SystemGroupDependency<EntityStore>(Order.AFTER, EntityTrackerSystems.FIND_VISIBLE_ENTITIES_GROUP));
        }
    }
    
    public static class EnsureVisibleComponent extends EntityTickingSystem<EntityStore>
    {
        public static final Set<Dependency<EntityStore>> DEPENDENCIES;
        private final ComponentType<EntityStore, EntityViewer> entityViewerComponentType;
        private final ComponentType<EntityStore, Visible> visibleComponentType;
        
        public EnsureVisibleComponent(final ComponentType<EntityStore, EntityViewer> entityViewerComponentType, final ComponentType<EntityStore, Visible> visibleComponentType) {
            this.entityViewerComponentType = entityViewerComponentType;
            this.visibleComponentType = visibleComponentType;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return EnsureVisibleComponent.DEPENDENCIES;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.entityViewerComponentType;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            for (final Ref<EntityStore> ref : archetypeChunk.getComponent(index, this.entityViewerComponentType).visible) {
                if (!commandBuffer.getArchetype(ref).contains(this.visibleComponentType)) {
                    commandBuffer.ensureComponent(ref, this.visibleComponentType);
                }
            }
        }
        
        static {
            DEPENDENCIES = Collections.singleton(new SystemDependency(Order.AFTER, ClearPreviouslyVisible.class));
        }
    }
    
    public static class AddToVisible extends EntityTickingSystem<EntityStore>
    {
        public static final Set<Dependency<EntityStore>> DEPENDENCIES;
        private final ComponentType<EntityStore, EntityViewer> entityViewerComponentType;
        private final ComponentType<EntityStore, Visible> visibleComponentType;
        
        public AddToVisible(final ComponentType<EntityStore, EntityViewer> entityViewerComponentType, final ComponentType<EntityStore, Visible> visibleComponentType) {
            this.entityViewerComponentType = entityViewerComponentType;
            this.visibleComponentType = visibleComponentType;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return AddToVisible.DEPENDENCIES;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.entityViewerComponentType;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final Ref<EntityStore> viewerRef = archetypeChunk.getReferenceTo(index);
            final EntityViewer viewer = archetypeChunk.getComponent(index, this.entityViewerComponentType);
            for (final Ref<EntityStore> ref : viewer.visible) {
                commandBuffer.getComponent(ref, this.visibleComponentType).addViewerParallel(viewerRef, viewer);
            }
        }
        
        static {
            DEPENDENCIES = Collections.singleton(new SystemDependency(Order.AFTER, EnsureVisibleComponent.class));
        }
    }
    
    public static class RemoveEmptyVisibleComponent extends EntityTickingSystem<EntityStore>
    {
        public static final Set<Dependency<EntityStore>> DEPENDENCIES;
        private final ComponentType<EntityStore, Visible> componentType;
        
        public RemoveEmptyVisibleComponent(final ComponentType<EntityStore, Visible> componentType) {
            this.componentType = componentType;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return RemoveEmptyVisibleComponent.DEPENDENCIES;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            if (archetypeChunk.getComponent(index, this.componentType).visibleTo.isEmpty()) {
                commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.componentType);
            }
        }
        
        static {
            DEPENDENCIES = Set.of(new SystemDependency(Order.AFTER, (Class<ISystem>)AddToVisible.class), new SystemGroupDependency<EntityStore>(Order.BEFORE, EntityTrackerSystems.QUEUE_UPDATE_GROUP));
        }
    }
    
    public static class RemoveVisibleComponent extends HolderSystem<EntityStore>
    {
        private final ComponentType<EntityStore, Visible> componentType;
        
        public RemoveVisibleComponent(final ComponentType<EntityStore, Visible> componentType) {
            this.componentType = componentType;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void onEntityAdd(@Nonnull final Holder<EntityStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<EntityStore> store) {
        }
        
        @Override
        public void onEntityRemoved(@Nonnull final Holder<EntityStore> holder, @Nonnull final RemoveReason reason, @Nonnull final Store<EntityStore> store) {
            holder.removeComponent(this.componentType);
        }
    }
    
    public static class EffectControllerSystem extends EntityTickingSystem<EntityStore>
    {
        @Nonnull
        private final ComponentType<EntityStore, Visible> visibleComponentType;
        @Nonnull
        private final ComponentType<EntityStore, EffectControllerComponent> effectControllerComponentType;
        @Nonnull
        private final Query<EntityStore> query;
        
        public EffectControllerSystem(@Nonnull final ComponentType<EntityStore, Visible> visibleComponentType, @Nonnull final ComponentType<EntityStore, EffectControllerComponent> effectControllerComponentType) {
            this.visibleComponentType = visibleComponentType;
            this.effectControllerComponentType = effectControllerComponentType;
            this.query = (Query<EntityStore>)Query.and(visibleComponentType, effectControllerComponentType);
        }
        
        @Nullable
        @Override
        public SystemGroup<EntityStore> getGroup() {
            return EntityTrackerSystems.QUEUE_UPDATE_GROUP;
        }
        
        @Nonnull
        @Override
        public Query<EntityStore> getQuery() {
            return this.query;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final Visible visibleComponent = archetypeChunk.getComponent(index, this.visibleComponentType);
            assert visibleComponent != null;
            final Ref<EntityStore> entityRef = archetypeChunk.getReferenceTo(index);
            final EffectControllerComponent effectControllerComponent = archetypeChunk.getComponent(index, this.effectControllerComponentType);
            assert effectControllerComponent != null;
            if (!visibleComponent.newlyVisibleTo.isEmpty()) {
                queueFullUpdate(entityRef, effectControllerComponent, visibleComponent.newlyVisibleTo);
            }
            if (effectControllerComponent.consumeNetworkOutdated()) {
                queueUpdatesFor(entityRef, effectControllerComponent, visibleComponent.visibleTo, visibleComponent.newlyVisibleTo);
            }
        }
        
        private static void queueFullUpdate(@Nonnull final Ref<EntityStore> ref, @Nonnull final EffectControllerComponent effectControllerComponent, @Nonnull final Map<Ref<EntityStore>, EntityViewer> visibleTo) {
            final ComponentUpdate update = new ComponentUpdate();
            update.type = ComponentUpdateType.EntityEffects;
            update.entityEffectUpdates = effectControllerComponent.createInitUpdates();
            for (final EntityViewer viewer : visibleTo.values()) {
                viewer.queueUpdate(ref, update);
            }
        }
        
        private static void queueUpdatesFor(@Nonnull final Ref<EntityStore> ref, @Nonnull final EffectControllerComponent effectControllerComponent, @Nonnull final Map<Ref<EntityStore>, EntityViewer> visibleTo, @Nonnull final Map<Ref<EntityStore>, EntityViewer> exclude) {
            final ComponentUpdate update = new ComponentUpdate();
            update.type = ComponentUpdateType.EntityEffects;
            update.entityEffectUpdates = effectControllerComponent.consumeChanges();
            if (exclude.isEmpty()) {
                for (final EntityViewer viewer : visibleTo.values()) {
                    viewer.queueUpdate(ref, update);
                }
                return;
            }
            for (final Map.Entry<Ref<EntityStore>, EntityViewer> entry : visibleTo.entrySet()) {
                if (exclude.containsKey(entry.getKey())) {
                    continue;
                }
                entry.getValue().queueUpdate(ref, update);
            }
        }
    }
    
    public static class SendPackets extends EntityTickingSystem<EntityStore>
    {
        public static final HytaleLogger LOGGER;
        public static final ThreadLocal<IntList> INT_LIST_THREAD_LOCAL;
        public static final Set<Dependency<EntityStore>> DEPENDENCIES;
        private final ComponentType<EntityStore, EntityViewer> componentType;
        
        public SendPackets(final ComponentType<EntityStore, EntityViewer> componentType) {
            this.componentType = componentType;
        }
        
        @Nullable
        @Override
        public SystemGroup<EntityStore> getGroup() {
            return EntityStore.SEND_PACKET_GROUP;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return SendPackets.DEPENDENCIES;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final EntityViewer viewer = archetypeChunk.getComponent(index, this.componentType);
            final IntList removedEntities = SendPackets.INT_LIST_THREAD_LOCAL.get();
            removedEntities.clear();
            final int before = viewer.updates.size();
            viewer.updates.entrySet().removeIf(v -> !v.getKey().isValid());
            if (before != viewer.updates.size()) {
                SendPackets.LOGGER.atWarning().log("Removed %d invalid updates for removed entities.", before - viewer.updates.size());
            }
            final ObjectIterator<Object2IntMap.Entry<Ref<EntityStore>>> iterator = viewer.sent.object2IntEntrySet().iterator();
            while (iterator.hasNext()) {
                final Object2IntMap.Entry<Ref<EntityStore>> entry = iterator.next();
                final Ref<EntityStore> ref = entry.getKey();
                if (!ref.isValid() || !viewer.visible.contains(ref)) {
                    removedEntities.add(entry.getIntValue());
                    iterator.remove();
                    if (viewer.updates.remove(ref) == null) {
                        continue;
                    }
                    SendPackets.LOGGER.atSevere().log("Entity can't be removed and also receive an update! " + String.valueOf(ref));
                }
            }
            if (!removedEntities.isEmpty() || !viewer.updates.isEmpty()) {
                final Iterator<Ref<EntityStore>> iterator2 = viewer.updates.keySet().iterator();
                while (iterator2.hasNext()) {
                    final Ref<EntityStore> ref2 = iterator2.next();
                    if (!ref2.isValid() || ref2.getStore() != store) {
                        iterator2.remove();
                    }
                    else {
                        if (viewer.sent.containsKey(ref2)) {
                            continue;
                        }
                        final int networkId = commandBuffer.getComponent(ref2, NetworkId.getComponentType()).getId();
                        if (networkId == -1) {
                            throw new IllegalArgumentException("Invalid entity network id: " + String.valueOf(ref2));
                        }
                        viewer.sent.put(ref2, networkId);
                    }
                }
                final EntityUpdates packet = new EntityUpdates();
                packet.removed = (int[])(removedEntities.isEmpty() ? null : removedEntities.toIntArray());
                packet.updates = new com.hypixel.hytale.protocol.EntityUpdate[viewer.updates.size()];
                int i = 0;
                for (final Map.Entry<Ref<EntityStore>, EntityUpdate> entry2 : viewer.updates.entrySet()) {
                    final com.hypixel.hytale.protocol.EntityUpdate[] updates = packet.updates;
                    final int n = i++;
                    final com.hypixel.hytale.protocol.EntityUpdate entityUpdate2 = new com.hypixel.hytale.protocol.EntityUpdate();
                    updates[n] = entityUpdate2;
                    final com.hypixel.hytale.protocol.EntityUpdate entityUpdate = entityUpdate2;
                    entityUpdate.networkId = viewer.sent.getInt(entry2.getKey());
                    final EntityUpdate update = entry2.getValue();
                    entityUpdate.removed = update.toRemovedArray();
                    entityUpdate.updates = update.toUpdatesArray();
                }
                viewer.updates.clear();
                viewer.packetReceiver.writeNoCache(packet);
            }
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
            INT_LIST_THREAD_LOCAL = ThreadLocal.withInitial((Supplier<? extends IntList>)IntArrayList::new);
            DEPENDENCIES = Set.of(new SystemGroupDependency(Order.AFTER, EntityTrackerSystems.QUEUE_UPDATE_GROUP));
        }
    }
}
