diff --git a/gradle.properties b/gradle.properties index c747956..1511d5d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,7 +29,7 @@ mod_name=ServerWarashi # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=1.0.0 +mod_version=1.0.1 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..31c3884 Binary files /dev/null and b/icon.png differ diff --git a/src/main/java/com/moepus/serverwarashi/ChunkLoadInfo.java b/src/main/java/com/moepus/serverwarashi/ChunkLoadInfo.java new file mode 100644 index 0000000..0ece4b5 --- /dev/null +++ b/src/main/java/com/moepus/serverwarashi/ChunkLoadInfo.java @@ -0,0 +1,56 @@ +package com.moepus.serverwarashi; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ChunkPos; +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +public class ChunkLoadInfo { + public int blockEntityCount; + public int entityCount; + + public static void dumpToCsv(@NotNull HashMap chunkLoadInfoMap) { + Path logDir = Paths.get("chunk_load"); + var timestamp = LocalDateTime.now().toString().replace(":", "-").replace(".", "-"); + var fileName = "chunk_load_info_" + timestamp + ".csv"; + Path csvPath = logDir.resolve(fileName); + + try { + Files.createDirectories(logDir); + + try (BufferedWriter writer = Files.newBufferedWriter(csvPath)) { + writer.write("chunk_x,chunk_z,block_x,block_y,block_z,blockEntityCount,entityCount"); + writer.newLine(); + + for (Map.Entry entry : chunkLoadInfoMap.entrySet()) { + long chunkLong = entry.getKey(); + ChunkLoadInfo info = entry.getValue(); + + ChunkPos chunkPos = new ChunkPos(chunkLong); + BlockPos blockPos = chunkPos.getWorldPosition(); + + writer.write( + chunkPos.x + "," + + chunkPos.z + "," + + blockPos.getX() + "," + + 0 + "," + + blockPos.getZ() + "," + + info.blockEntityCount + "," + + info.entityCount + ); + writer.newLine(); + } + } + + } catch (IOException ignored) { + } + } +} diff --git a/src/main/java/com/moepus/serverwarashi/TicketOwner.java b/src/main/java/com/moepus/serverwarashi/TicketOwner.java index d6930e0..19fab3a 100644 --- a/src/main/java/com/moepus/serverwarashi/TicketOwner.java +++ b/src/main/java/com/moepus/serverwarashi/TicketOwner.java @@ -4,7 +4,9 @@ import net.minecraft.core.BlockPos; import net.minecraft.network.chat.*; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.Ticket; +import net.minecraft.server.level.TicketType; import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.block.state.BlockState; import java.lang.reflect.Field; @@ -46,6 +48,21 @@ public class TicketOwner { this.name = "Unknown entity"; } } + } else if (ticket.getType() == TicketType.PLAYER) { + owner = "player"; + final double[] mostNearDistance = {Double.MAX_VALUE}; + BlockPos ticketPos = ((ChunkPos) key).getWorldPosition(); + level.players().forEach(serverPlayer -> { + double distance = serverPlayer.blockPosition().distSqr(ticketPos); + if (distance < mostNearDistance[0]) { + mostNearDistance[0] = distance; + this.name = serverPlayer.getName().getString(); + pos = serverPlayer.blockPosition(); + } + }); + } else if (ticket.getType() == TicketType.START) { + owner = "world_start"; + this.pos = level.getSharedSpawnPos(); } else { owner = key; if (owner instanceof BlockPos blockPos) { diff --git a/src/main/java/com/moepus/serverwarashi/WarashiCommands.java b/src/main/java/com/moepus/serverwarashi/WarashiCommands.java index 79a40dc..893a298 100644 --- a/src/main/java/com/moepus/serverwarashi/WarashiCommands.java +++ b/src/main/java/com/moepus/serverwarashi/WarashiCommands.java @@ -1,6 +1,9 @@ package com.moepus.serverwarashi; import com.moepus.serverwarashi.mixin.DistanceManagerAccessor; +import com.moepus.serverwarashi.mixin.EntitySectionStorageAccessor; +import com.moepus.serverwarashi.mixin.PersistentEntitySectionManagerAccessor; +import com.moepus.serverwarashi.mixin.ServerLevelAcessor; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; @@ -14,15 +17,20 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.server.level.*; import net.minecraft.util.SortedArraySet; +import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.entity.EntitySection; +import net.minecraft.world.level.entity.EntitySectionStorage; +import net.minecraft.world.level.entity.PersistentEntitySectionManager; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; public class WarashiCommands { private static final java.util.function.Predicate ADMIN_PERMISSION = @@ -119,8 +127,8 @@ public class WarashiCommands { private static Ticket GetTicket(SortedArraySet> tickets, boolean currentWorking) { for (Ticket ticket : tickets) { - if (ticket.getType() == TicketType.PLAYER) continue; - if (ticket.getType() == TicketType.START) continue; + // if (ticket.getType() == TicketType.PLAYER) continue; + // if (ticket.getType() == TicketType.START) continue; int level; if (currentWorking) { @@ -129,7 +137,7 @@ public class WarashiCommands { IPauseableTicket pauseable = (IPauseableTicket) (Object) ticket; level = pauseable.serverWarashi$getLevel(); } - if (level > 32) continue; + if (level > 33) continue; return ticket; } @@ -157,25 +165,54 @@ public class WarashiCommands { ownerMap.computeIfAbsent(owner, k -> new HashSet<>()).add(chunkPos); } + PersistentEntitySectionManager entityManager = ((ServerLevelAcessor) level).getEntityManager(); + PersistentEntitySectionManagerAccessor entityAccessor = (PersistentEntitySectionManagerAccessor) entityManager; + EntitySectionStorage sectionStorage = entityAccessor.getSectionStorage(); + EntitySectionStorageAccessor sectionAccessor = (EntitySectionStorageAccessor) sectionStorage; + + HashMap chunkLoadInfoMap = new HashMap<>(); + for (var entry : ownerMap.entrySet()) { TicketOwner owner = entry.getKey(); Set chunks = entry.getValue(); message.append(owner.asComponent()); - message.append(" loads " + chunks.size() + " chunks "); + message.append(" C:" + chunks.size() + ","); + // Block Entity count int totalBlockEntities = 0; for (long chunkPos : chunks) { ChunkPos pos = new ChunkPos(chunkPos); ChunkAccess chunk = level.getChunk(pos.x, pos.z, ChunkStatus.FULL, false); if (chunk != null) { - totalBlockEntities += chunk.getBlockEntitiesPos().size(); + int chunkBlockEntities = chunk.getBlockEntitiesPos().size(); + totalBlockEntities += chunkBlockEntities; + chunkLoadInfoMap.computeIfAbsent(chunkPos, k -> new ChunkLoadInfo()).blockEntityCount = chunkBlockEntities; } } - message.append("with " + totalBlockEntities + " block entities\n"); + message.append(" BE: " + totalBlockEntities + ","); + + // Entity count + int totalEntities = 0; + for (long chunkPos : chunks) { + ChunkPos pos = new ChunkPos(chunkPos); + AtomicInteger chunkEntities = new AtomicInteger(); + sectionAccessor.invokeGetChunkSections(pos.x, pos.z).forEach(sectionIndex -> { + EntitySection section = sectionStorage.getSection(sectionIndex); + if (section == null) + return; + chunkEntities.addAndGet(section.size()); + }); + totalEntities += chunkEntities.get(); + chunkLoadInfoMap.computeIfAbsent(chunkPos, k -> new ChunkLoadInfo()).entityCount = chunkEntities.get(); + } + message.append(" E: " + totalEntities + ".\n"); } if (ownerMap.isEmpty()) { message.append("No tickets found\n"); } + if (!chunkLoadInfoMap.isEmpty()) { + ChunkLoadInfo.dumpToCsv(chunkLoadInfoMap); + } return message; }, true) ; diff --git a/src/main/java/com/moepus/serverwarashi/mixin/EntitySectionStorageAccessor.java b/src/main/java/com/moepus/serverwarashi/mixin/EntitySectionStorageAccessor.java new file mode 100644 index 0000000..e2d1c30 --- /dev/null +++ b/src/main/java/com/moepus/serverwarashi/mixin/EntitySectionStorageAccessor.java @@ -0,0 +1,12 @@ +package com.moepus.serverwarashi.mixin; + +import it.unimi.dsi.fastutil.longs.LongSortedSet; +import net.minecraft.world.level.entity.EntitySectionStorage; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(value = EntitySectionStorage.class, remap = false) +public interface EntitySectionStorageAccessor { + @Invoker + LongSortedSet invokeGetChunkSections(int x, int z); +} diff --git a/src/main/java/com/moepus/serverwarashi/mixin/PersistentEntitySectionManagerAccessor.java b/src/main/java/com/moepus/serverwarashi/mixin/PersistentEntitySectionManagerAccessor.java new file mode 100644 index 0000000..aad8c21 --- /dev/null +++ b/src/main/java/com/moepus/serverwarashi/mixin/PersistentEntitySectionManagerAccessor.java @@ -0,0 +1,12 @@ +package com.moepus.serverwarashi.mixin; + +import net.minecraft.world.level.entity.EntitySectionStorage; +import net.minecraft.world.level.entity.PersistentEntitySectionManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(value = PersistentEntitySectionManager.class, remap = false) +public interface PersistentEntitySectionManagerAccessor { + @Accessor + EntitySectionStorage getSectionStorage(); +} diff --git a/src/main/java/com/moepus/serverwarashi/mixin/ServerLevelAcessor.java b/src/main/java/com/moepus/serverwarashi/mixin/ServerLevelAcessor.java new file mode 100644 index 0000000..bff1cb7 --- /dev/null +++ b/src/main/java/com/moepus/serverwarashi/mixin/ServerLevelAcessor.java @@ -0,0 +1,13 @@ +package com.moepus.serverwarashi.mixin; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.entity.PersistentEntitySectionManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(value = ServerLevel.class, remap = false) +public interface ServerLevelAcessor { + @Accessor + PersistentEntitySectionManager getEntityManager(); +} diff --git a/src/main/resources/serverwarashi.mixins.json b/src/main/resources/serverwarashi.mixins.json index 35928f6..8a05865 100644 --- a/src/main/resources/serverwarashi.mixins.json +++ b/src/main/resources/serverwarashi.mixins.json @@ -5,6 +5,9 @@ "compatibilityLevel": "JAVA_21", "mixins": [ "DistanceManagerAccessor", + "EntitySectionStorageAccessor", + "PersistentEntitySectionManagerAccessor", + "ServerLevelAcessor", "TicketMixin" ], "client": [