package com.hypixel.hytale.server.spawning.commands; import com.hypixel.hytale.component.Archetype; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.math.util.MathUtil; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractWorldCommand; 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.npc.NPCPlugin; import com.hypixel.hytale.server.npc.components.SpawnMarkerReference; import com.hypixel.hytale.server.npc.entities.NPCEntity; import com.hypixel.hytale.server.spawning.SpawnRejection; import com.hypixel.hytale.server.spawning.spawnmarkers.SpawnMarkerEntity; import com.hypixel.hytale.server.spawning.world.ChunkEnvironmentSpawnData; import com.hypixel.hytale.server.spawning.world.component.ChunkSpawnData; import com.hypixel.hytale.server.spawning.world.component.WorldSpawnData; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import javax.annotation.Nonnull; public class SpawnStatsCommand extends AbstractWorldCommand { @Nonnull private final FlagArg environmentsArg = this.withFlagArg("environments", "server.commands.spawning.stats.arg.environments.desc"); @Nonnull private final FlagArg markersArg = this.withFlagArg("markers", "server.commands.spawning.stats.arg.markers.desc"); @Nonnull private final FlagArg verboseArg = this.withFlagArg("verbose", "server.commands.spawning.stats.arg.verbose.desc"); public SpawnStatsCommand() { super("stats", "server.commands.spawning.stats.desc"); } @Override protected void execute(@Nonnull CommandContext context, @Nonnull World world, @Nonnull Store store) { if (this.environmentsArg.get(context)) { WorldSpawnData worldSpawnData = store.getResource(WorldSpawnData.getResourceType()); AtomicInteger filtered = new AtomicInteger(); boolean verbose = this.verboseArg.get(context); worldSpawnData.forEachEnvironmentSpawnData( worldEnvironmentSpawnData -> { if (verbose || worldEnvironmentSpawnData.hasNPCs() && worldEnvironmentSpawnData.getExpectedNPCs() != 0.0) { int environmentIndex = worldEnvironmentSpawnData.getEnvironmentIndex(); String name = Environment.getAssetMap().getAsset(environmentIndex).getId(); Store chunkStore = world.getChunkStore().getStore(); double[] chunkExpected = new double[]{0.0}; worldEnvironmentSpawnData.getChunkRefSet() .forEach( ref -> { ChunkEnvironmentSpawnData chunkEnvironmentSpawnData = chunkStore.getComponent( (Ref)ref, ChunkSpawnData.getComponentType() ) .getEnvironmentSpawnData(environmentIndex); chunkExpected[0] += chunkEnvironmentSpawnData.getExpectedNPCs(); } ); String message = String.format( "Environment: %-30s Exp %6.2f Act %4d Blk %s Chunk exp: %6.2f", name, worldEnvironmentSpawnData.getExpectedNPCs(), worldEnvironmentSpawnData.getActualNPCs(), worldEnvironmentSpawnData.getSegmentCount(), chunkExpected[0] ); ((HytaleLogger.Api)NPCPlugin.get().getLogger().atInfo()).log(message); worldEnvironmentSpawnData.forEachNpcStat( (npcIndex, stats) -> { int all = stats.getSpansTried(); double failPercent = all > 0 ? MathUtil.percent(all - stats.getSpansSuccess(), all) : 0.0; String message1; if (verbose) { int successfulJobCount = stats.getSuccessfulJobCount(); int successfulBudget = stats.getSuccessfulJobTotalBudget(); int failedJobCount = stats.getFailedJobCount(); int failedBudget = stats.getFailedJobTotalBudget(); message1 = String.format( " NPC: %-30s Exp %6.2f Act %4d | Spns %8d: Succ %4d Lgt %5.1f Blk %5.1f Pos %5.1f Geo %5.1f Bre %5.1f Oth %5.1f | Fail%% %5.1f | Spwnbl %s | Succ Job Bgt %6d Avg Bgt %6.2f | Fail Jobs %6d Bgt %6d Avg Bgt %6.2f ", NPCPlugin.get().getName(npcIndex), stats.getExpected(), stats.getActual(), all, stats.getSpansSuccess(), MathUtil.percent(stats.getRejectionCount(SpawnRejection.OUTSIDE_LIGHT_RANGE), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.INVALID_SPAWN_BLOCK), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.NO_POSITION), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.INVALID_POSITION), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.NOT_BREATHABLE), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.OTHER), all), failPercent, !stats.isUnspawnable(), successfulBudget, successfulJobCount > 0 ? (double)successfulBudget / successfulJobCount : 0.0, failedJobCount, failedBudget, failedJobCount > 0 ? (double)failedBudget / failedJobCount : 0.0 ); } else { message1 = String.format( " NPC: %-30s Exp %6.2f Act %4d | Spns %8d: Succ %4d Lgt %5.1f Blk %5.1f Pos %5.1f Geo %5.1f Bre %5.1f Oth %5.1f | Fail%% %5.1f | Spwnbl %s ", NPCPlugin.get().getName(npcIndex), stats.getExpected(), stats.getActual(), all, stats.getSpansSuccess(), MathUtil.percent(stats.getRejectionCount(SpawnRejection.OUTSIDE_LIGHT_RANGE), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.INVALID_SPAWN_BLOCK), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.NO_POSITION), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.INVALID_POSITION), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.NOT_BREATHABLE), all), MathUtil.percent(stats.getRejectionCount(SpawnRejection.OTHER), all), failPercent, !stats.isUnspawnable() ); } NPCPlugin.get().getLogger().at(failPercent < 60.0 ? Level.INFO : Level.WARNING).log(message1); } ); } else { filtered.getAndIncrement(); } } ); AtomicInteger trackedNPC = new AtomicInteger(); AtomicInteger totalNPC = new AtomicInteger(); store.forEachEntityParallel(NPCEntity.getComponentType(), (index, archetypeChunk, commandBuffer) -> { totalNPC.getAndIncrement(); NPCEntity npc = archetypeChunk.getComponent(index, NPCEntity.getComponentType()); if (npc.getEnvironment() != Integer.MIN_VALUE && npc.getSpawnConfiguration() != Integer.MIN_VALUE) { trackedNPC.getAndIncrement(); } }); int spawnJobsCompleted = worldSpawnData.getTotalSpawnJobsCompleted(); String message = String.format( "Total: Exp %.2f Exp-Empty %.2f Act %d Job Pending Act %d Tracked %d Total %d Unspawnable %s AvgSegCount %s Chunks %s Filtered empty envs %d Active Jobs %d Total Jobs Run %d Avg Job Budget %.2f", worldSpawnData.getExpectedNPCs(), worldSpawnData.getExpectedNPCsInEmptyEnvironments(), worldSpawnData.getActualNPCs(), worldSpawnData.getTrackedCountFromJobs(), trackedNPC.get(), totalNPC.get(), worldSpawnData.isUnspawnable(), worldSpawnData.averageSegmentCount(), worldSpawnData.getChunkCount(), filtered.get(), worldSpawnData.getActiveSpawnJobs(), spawnJobsCompleted, spawnJobsCompleted > 0 ? (double)worldSpawnData.getTotalSpawnJobBudgetUsed() / spawnJobsCompleted : 0.0 ); ((HytaleLogger.Api)NPCPlugin.get().getLogger().atInfo()).log(message); } if (this.markersArg.get(context)) { AtomicInteger spawnMarkerCount = new AtomicInteger(); AtomicInteger inactiveSpawnMarkerCount = new AtomicInteger(); Object2IntOpenHashMap spawnMarkerTypeCounts = new Object2IntOpenHashMap(); store.forEachChunk(SpawnMarkerEntity.getComponentType(), (archetypeChunk, componentStoreCommandBuffer) -> { for (int index = 0; index < archetypeChunk.size(); index++) { SpawnMarkerEntity entity = archetypeChunk.getComponent(index, SpawnMarkerEntity.getComponentType()); spawnMarkerCount.getAndIncrement(); spawnMarkerTypeCounts.mergeInt(entity.getSpawnMarkerId(), 1, Integer::sum); if (entity.getSpawnCount() == 0) { inactiveSpawnMarkerCount.getAndIncrement(); } } }); AtomicInteger spawnMarkerNPCCount = new AtomicInteger(); Object2IntOpenHashMap roleCounts = new Object2IntOpenHashMap(); HashMap> roleCountsPerMarkerType = new HashMap<>(); store.forEachChunk( Archetype.of(NPCEntity.getComponentType(), SpawnMarkerReference.getComponentType()), (archetypeChunk, componentStoreCommandBuffer) -> { for (int index = 0; index < archetypeChunk.size(); index++) { NPCEntity entity = archetypeChunk.getComponent(index, NPCEntity.getComponentType()); SpawnMarkerReference spawnMarkerReference = archetypeChunk.getComponent(index, SpawnMarkerReference.getComponentType()); spawnMarkerNPCCount.getAndIncrement(); String roleName = entity.getRoleName(); roleCounts.mergeInt(roleName, 1, Integer::sum); Ref markerRef = spawnMarkerReference.getReference().getEntity(componentStoreCommandBuffer); SpawnMarkerEntity marker = componentStoreCommandBuffer.getComponent(markerRef, SpawnMarkerEntity.getComponentType()); Object2IntMap spawnedRoles = roleCountsPerMarkerType.computeIfAbsent(marker.getSpawnMarkerId(), key -> new Object2IntOpenHashMap()); spawnedRoles.mergeInt(roleName, 1, Integer::sum); } } ); StringBuilder sb = new StringBuilder(); sb.append("Markers: ") .append(spawnMarkerCount.get()) .append(" (With zero spawns: ") .append(inactiveSpawnMarkerCount.get()) .append(")\nSpawned NPCs: ") .append(spawnMarkerNPCCount.get()); roleCounts.object2IntEntrySet() .fastForEach(stringEntry -> sb.append("\n ").append((String)stringEntry.getKey()).append(": ").append(stringEntry.getIntValue())); sb.append("\nRoles by marker type:"); roleCountsPerMarkerType.forEach((key, spawnedRoles) -> { sb.append("\n ").append(key).append(" (Instances: ").append(spawnMarkerTypeCounts.getInt(key)).append(")"); spawnedRoles.object2IntEntrySet().forEach(entry -> sb.append("\n ").append((String)entry.getKey()).append(": ").append(entry.getIntValue())); }); ((HytaleLogger.Api)NPCPlugin.get().getLogger().atInfo()).log(sb.toString()); } context.sendMessage(Message.translation("server.commands.spawning.stats.results")); } }