package com.hypixel.hytale.server.spawning.suppression.system; import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap; import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent; import com.hypixel.hytale.assetstore.event.RemovedAssetsEvent; import com.hypixel.hytale.assetstore.map.IndexedAssetMap; import com.hypixel.hytale.builtin.tagset.TagSetPlugin; import com.hypixel.hytale.builtin.tagset.config.NPCGroup; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Archetype; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.SystemGroup; import com.hypixel.hytale.component.query.Query; import com.hypixel.hytale.component.spatial.SpatialResource; import com.hypixel.hytale.component.system.HolderSystem; import com.hypixel.hytale.component.system.RefSystem; import com.hypixel.hytale.component.system.StoreSystem; import com.hypixel.hytale.event.EventRegistry; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.math.util.MathUtil; import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.server.core.entity.UUIDComponent; import com.hypixel.hytale.server.core.modules.entity.EntityModule; import com.hypixel.hytale.server.core.modules.entity.component.FromPrefab; import com.hypixel.hytale.server.core.modules.entity.component.FromWorldGen; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent; import com.hypixel.hytale.server.core.universe.Universe; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.spawning.SpawningPlugin; import com.hypixel.hytale.server.spawning.assets.spawnsuppression.SpawnSuppression; import com.hypixel.hytale.server.spawning.spawnmarkers.SpawnMarkerEntity; import com.hypixel.hytale.server.spawning.suppression.SpawnSuppressorEntry; import com.hypixel.hytale.server.spawning.suppression.component.ChunkSuppressionEntry; import com.hypixel.hytale.server.spawning.suppression.component.ChunkSuppressionQueue; import com.hypixel.hytale.server.spawning.suppression.component.SpawnSuppressionComponent; import com.hypixel.hytale.server.spawning.suppression.component.SpawnSuppressionController; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class SpawnSuppressionSystems { private static void suppressSpawns( @Nonnull ResourceType chunkSuppressionQueueResourceType, @Nonnull ComponentType spawnMarkerEntityComponentType, UUID uuid, @Nonnull SpawnSuppressorEntry entry, @Nonnull SpawnSuppressionController suppressionController, @Nonnull Store store, @Nonnull ChunkStore chunkComponentStore ) { String suppressionId = entry.getSuppressionId(); SpawningPlugin.get().getLogger().at(Level.FINEST).log("Suppressing spawns with id '%s' from suppressor %s", suppressionId, uuid); SpawnSuppression suppression = SpawnSuppression.getAssetMap().getAsset(suppressionId); if (suppression == null) { SpawningPlugin.get().getLogger().at(Level.WARNING).log("Spawn suppression config '%s' does not exist", suppressionId); } else { int[] suppressedGroups = suppression.getSuppressedGroupIds(); IntSet suppressedRoles; if (suppressedGroups != null && suppressedGroups.length > 0) { suppressedRoles = new IntOpenHashSet(); for (int suppressedGroup : suppressedGroups) { IntSet set = TagSetPlugin.get(NPCGroup.class).getSet(suppressedGroup); if (set != null) { suppressedRoles.addAll(set); } } } else { suppressedRoles = null; } double radius = suppression.getRadius(); Vector3d position = entry.getPosition(); int minChunkX = MathUtil.floor(position.x - radius) >> 5; int minChunkZ = MathUtil.floor(position.z - radius) >> 5; int maxChunkX = MathUtil.floor(position.x + radius) >> 5; int maxChunkZ = MathUtil.floor(position.z + radius) >> 5; int minY = MathUtil.floor(position.y - radius); int maxY = MathUtil.floor(position.y + radius); Long2ObjectConcurrentHashMap chunkSuppressionMap = suppressionController.getChunkSuppressionMap(); for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ); SpawningPlugin.get().getLogger().at(Level.FINEST).log("Suppressing chunk index %s with id '%s'", chunkIndex, suppressionId); ChunkSuppressionEntry oldEntry = chunkSuppressionMap.get(chunkIndex); List suppressionSpanList; if (oldEntry != null) { suppressionSpanList = new ObjectArrayList(oldEntry.getSuppressionSpans()); } else { suppressionSpanList = new ObjectArrayList(); } suppressionSpanList.add(new ChunkSuppressionEntry.SuppressionSpan(uuid, minY, maxY, suppressedRoles)); suppressionSpanList.sort(Comparator.comparingInt(ChunkSuppressionEntry.SuppressionSpan::getMinY)); ChunkSuppressionEntry chunkEntry = new ChunkSuppressionEntry(suppressionSpanList); chunkSuppressionMap.put(chunkIndex, chunkEntry); Ref chunkReference = chunkComponentStore.getChunkReference(chunkIndex); if (chunkReference != null) { ChunkSuppressionQueue chunkSuppressionQueue = chunkComponentStore.getStore().getResource(chunkSuppressionQueueResourceType); chunkSuppressionQueue.queueForAdd(chunkReference, chunkEntry); SpawningPlugin.get().getLogger().at(Level.FINEST).log("Queueing annotation of chunk index %s with id '%s'", chunkIndex, suppressionId); } } } if (suppression.isSuppressSpawnMarkers()) { ObjectList> results = SpatialResource.getThreadLocalReferenceList(); SpatialResource, EntityStore> spatialResource = store.getResource(SpawningPlugin.get().getSpawnMarkerSpatialResource()); spatialResource.getSpatialStructure().collect(position, radius, results); for (int i = 0; i < results.size(); i++) { Ref markerRef = (Ref)results.get(i); SpawnMarkerEntity marker = store.getComponent(markerRef, spawnMarkerEntityComponentType); marker.suppress(uuid); HytaleLogger.Api context = SpawningPlugin.get().getLogger().at(Level.FINEST); if (context.isEnabled()) { context.log("Suppressing spawn marker %s", store.getComponent(markerRef, UUIDComponent.getComponentType()).getUuid()); } } } } } public static class EnsureNetworkSendable extends HolderSystem { private final Query query = Query.and(SpawnSuppressionComponent.getComponentType(), Query.not(NetworkId.getComponentType())); @Override public void onEntityAdd(@Nonnull Holder holder, @Nonnull AddReason reason, @Nonnull Store store) { holder.addComponent(NetworkId.getComponentType(), new NetworkId(store.getExternalData().takeNextNetworkId())); } @Override public void onEntityRemoved(@Nonnull Holder holder, @Nonnull RemoveReason reason, @Nonnull Store store) { } @Nonnull @Override public Query getQuery() { return this.query; } } public static class Load extends StoreSystem { private final ResourceType spawnSuppressionControllerResourceType; private final ComponentType spawnMarkerEntityComponentType; private final ResourceType chunkSuppressionQueueResourceType; private final ComponentType chunkSuppressionEntryComponentType; @Nonnull private final EventRegistry eventRegistry; public Load( ResourceType spawnSuppressionControllerResourceType, ComponentType spawnMarkerEntityComponentType, ResourceType chunkSuppressionQueueResourceType, ComponentType chunkSuppressionEntryComponentType ) { this.spawnSuppressionControllerResourceType = spawnSuppressionControllerResourceType; this.spawnMarkerEntityComponentType = spawnMarkerEntityComponentType; this.chunkSuppressionQueueResourceType = chunkSuppressionQueueResourceType; this.chunkSuppressionEntryComponentType = chunkSuppressionEntryComponentType; this.eventRegistry = new EventRegistry(new CopyOnWriteArrayList<>(), () -> true, null, SpawningPlugin.get().getEventRegistry()); this.eventRegistry.register(LoadedAssetsEvent.class, SpawnSuppression.class, this::onSpawnSuppressionsLoaded); this.eventRegistry.register(RemovedAssetsEvent.class, SpawnSuppression.class, this::onSpawnSuppressionsRemoved); } @Override public void onSystemAddedToStore(@Nonnull Store store) { SpawnSuppressionController resource = store.getResource(this.spawnSuppressionControllerResourceType); Map spawnSuppressorMap = resource.getSpawnSuppressorMap(); spawnSuppressorMap.forEach( (id, entry) -> SpawnSuppressionSystems.suppressSpawns( this.chunkSuppressionQueueResourceType, this.spawnMarkerEntityComponentType, id, entry, resource, store, store.getExternalData().getWorld().getChunkStore() ) ); } @Override public void onSystemRemovedFromStore(@Nonnull Store store) { } @Override public void onSystemUnregistered() { this.eventRegistry.shutdown(); } private void onSpawnSuppressionsLoaded(@Nonnull LoadedAssetsEvent> event) { Map loadedAssets = event.getLoadedAssets(); Universe.get().getWorlds().forEach((name, world) -> world.execute(() -> { boolean hasChanges = false; Store store = world.getEntityStore().getStore(); SpawnSuppressionController suppressionController = store.getResource(this.spawnSuppressionControllerResourceType); Map spawnSuppressorMap = suppressionController.getSpawnSuppressorMap(); for (SpawnSuppressorEntry entry : spawnSuppressorMap.values()) { if (loadedAssets.containsKey(entry.getSuppressionId())) { hasChanges = true; break; } } if (hasChanges) { this.rebuildSuppressionMap(world, store, suppressionController); } })); } private void onSpawnSuppressionsRemoved(@Nonnull RemovedAssetsEvent> event) { Set removedAssets = event.getRemovedAssets(); Universe.get().getWorlds().forEach((name, world) -> world.execute(() -> { boolean hasChanges = false; Store store = world.getEntityStore().getStore(); SpawnSuppressionController suppressionController = store.getResource(this.spawnSuppressionControllerResourceType); Map spawnSuppressorMap = suppressionController.getSpawnSuppressorMap(); for (SpawnSuppressorEntry entry : spawnSuppressorMap.values()) { if (removedAssets.contains(entry.getSuppressionId())) { hasChanges = true; break; } } if (hasChanges) { this.rebuildSuppressionMap(world, store, suppressionController); } })); } private void rebuildSuppressionMap(@Nonnull World world, @Nonnull Store store, @Nonnull SpawnSuppressionController suppressionController) { SpawningPlugin.get().getLogger().at(Level.INFO).log("Rebuilding spawn suppression map for world %s", world.getName()); ChunkStore chunkComponentStore = world.getChunkStore(); Store chunkStore = chunkComponentStore.getStore(); Long2ObjectConcurrentHashMap chunkSuppressionMap = suppressionController.getChunkSuppressionMap(); LongIterator spawnSuppressorMap = chunkSuppressionMap.keySet().iterator(); while (spawnSuppressorMap.hasNext()) { long key = (Long)spawnSuppressorMap.next(); Ref chunkReference = chunkComponentStore.getChunkReference(key); if (chunkReference != null) { chunkStore.tryRemoveComponent(chunkReference, this.chunkSuppressionEntryComponentType); } } chunkSuppressionMap.clear(); store.forEachEntityParallel( SpawnMarkerEntity.getComponentType(), (index, archetypeChunk, commandBuffer) -> archetypeChunk.getComponent(index, SpawnMarkerEntity.getComponentType()).clearAllSuppressions() ); Map spawnSuppressorMapx = suppressionController.getSpawnSuppressorMap(); spawnSuppressorMapx.forEach( (id, entry) -> SpawnSuppressionSystems.suppressSpawns( this.chunkSuppressionQueueResourceType, this.spawnMarkerEntityComponentType, id, entry, suppressionController, store, store.getExternalData().getWorld().getChunkStore() ) ); } } public static class Suppressor extends RefSystem { private final ComponentType spawnSuppressorComponentType; private final ResourceType spawnSuppressionControllerResourceType; private final ComponentType spawnMarkerEntityComponentType; private final ResourceType chunkSuppressionQueueResourceType; private final ResourceType, EntityStore>> spawnMarkerSpatialResourceType; private final ComponentType transformComponentType = TransformComponent.getComponentType(); private final ComponentType uuidComponentType = UUIDComponent.getComponentType(); @Nonnull private final Query query; public Suppressor( ComponentType spawnSuppressorComponentType, ResourceType spawnSuppressionControllerResourceType, ComponentType spawnMarkerEntityComponentType, ResourceType chunkSuppressionQueueResourceType, ResourceType, EntityStore>> spawnMarkerSpatialResourceType ) { this.spawnSuppressorComponentType = spawnSuppressorComponentType; this.spawnSuppressionControllerResourceType = spawnSuppressionControllerResourceType; this.spawnMarkerEntityComponentType = spawnMarkerEntityComponentType; this.chunkSuppressionQueueResourceType = chunkSuppressionQueueResourceType; this.spawnMarkerSpatialResourceType = spawnMarkerSpatialResourceType; this.query = Query.and(spawnSuppressorComponentType, this.transformComponentType, this.uuidComponentType); } @Nonnull @Override public Query getQuery() { return this.query; } @Nullable @Override public SystemGroup getGroup() { return EntityModule.get().getPreClearMarkersGroup(); } @Override public void onEntityAdded( @Nonnull Ref reference, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { Archetype archetype = store.getArchetype(reference); SpawnSuppressionComponent suppressor = store.getComponent(reference, this.spawnSuppressorComponentType); TransformComponent transform = commandBuffer.getComponent(reference, this.transformComponentType); UUIDComponent uuidComponent = commandBuffer.getComponent(reference, this.uuidComponentType); boolean fromExternal = archetype.contains(FromPrefab.getComponentType()) || archetype.contains(FromWorldGen.getComponentType()); if (reason == AddReason.SPAWN || fromExternal) { SpawnSuppressionController suppressionController = store.getResource(this.spawnSuppressionControllerResourceType); SpawnSuppressorEntry entry = new SpawnSuppressorEntry(suppressor.getSpawnSuppression(), transform.getPosition().clone()); UUID uuid = uuidComponent.getUuid(); SpawnSuppressorEntry prev = suppressionController.getSpawnSuppressorMap().put(uuid, entry); if (prev != null) { throw new IllegalStateException(String.format("A spawn suppressor with the ID %s is already registered.", uuid)); } else { SpawnSuppressionSystems.suppressSpawns( this.chunkSuppressionQueueResourceType, this.spawnMarkerEntityComponentType, uuid, entry, suppressionController, store, store.getExternalData().getWorld().getChunkStore() ); commandBuffer.ensureComponent(reference, PrefabCopyableComponent.getComponentType()); } } } @Override public void onEntityRemove( @Nonnull Ref reference, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer ) { if (reason == RemoveReason.REMOVE) { SpawnSuppressionController suppressionController = store.getResource(this.spawnSuppressionControllerResourceType); UUIDComponent uuidComponent = commandBuffer.getComponent(reference, this.uuidComponentType); Map spawnSuppressorMap = suppressionController.getSpawnSuppressorMap(); UUID uuid = uuidComponent.getUuid(); SpawnSuppressorEntry entry = spawnSuppressorMap.remove(uuid); String suppressionId = entry.getSuppressionId(); SpawnSuppression suppression = SpawnSuppression.getAssetMap().getAsset(suppressionId); if (suppression == null) { SpawningPlugin.get().getLogger().at(Level.WARNING).log("Spawn suppression config '%s' does not exist", suppressionId); } else { double radius = suppression.getRadius(); Vector3d position = entry.getPosition(); int minChunkX = MathUtil.floor(position.x - radius) >> 5; int minChunkZ = MathUtil.floor(position.z - radius) >> 5; int maxChunkX = MathUtil.floor(position.x + radius) >> 5; int maxChunkZ = MathUtil.floor(position.z + radius) >> 5; ChunkStore chunkComponentStore = store.getExternalData().getWorld().getChunkStore(); Store chunkStore = chunkComponentStore.getStore(); Long2ObjectConcurrentHashMap chunkSuppressionMap = suppressionController.getChunkSuppressionMap(); for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ); ChunkSuppressionEntry chunkEntry = null; ChunkSuppressionEntry oldEntry = chunkSuppressionMap.get(chunkIndex); if (oldEntry.containsOnly(uuid)) { chunkSuppressionMap.remove(chunkIndex); } else { List oldSpans = oldEntry.getSuppressionSpans(); ObjectArrayList suppressedSpans = new ObjectArrayList(); for (ChunkSuppressionEntry.SuppressionSpan span : oldSpans) { if (!span.getSuppressorId().equals(uuid)) { suppressedSpans.add(span); } } chunkEntry = new ChunkSuppressionEntry(suppressedSpans); chunkSuppressionMap.put(chunkIndex, chunkEntry); } Ref chunkReference = chunkComponentStore.getChunkReference(chunkIndex); if (chunkReference != null) { ChunkSuppressionQueue chunkSuppressionQueue = chunkStore.getResource(this.chunkSuppressionQueueResourceType); if (chunkEntry == null) { chunkSuppressionQueue.queueForRemove(chunkReference); } else { chunkSuppressionQueue.queueForAdd(chunkReference, chunkEntry); } SpawningPlugin.get() .getLogger() .at(Level.FINEST) .log("Queuing removal of suppression from chunk index %s, %s", chunkIndex, suppressionId); } } } if (suppression.isSuppressSpawnMarkers()) { ObjectList> results = SpatialResource.getThreadLocalReferenceList(); SpatialResource, EntityStore> spatialResource = store.getResource(this.spawnMarkerSpatialResourceType); spatialResource.getSpatialStructure().collect(position, radius, results); for (int i = 0; i < results.size(); i++) { Ref markerRef = (Ref)results.get(i); SpawnMarkerEntity marker = commandBuffer.getComponent(markerRef, this.spawnMarkerEntityComponentType); marker.releaseSuppression(uuid); HytaleLogger.Api context = SpawningPlugin.get().getLogger().at(Level.FINEST); if (context.isEnabled()) { context.log("Releasing suppression of spawn marker %s", commandBuffer.getComponent(markerRef, this.uuidComponentType).getUuid()); } } } } } } } }