From 9bbe698877977ecfca1aeff0f4a901f9d6858e4a Mon Sep 17 00:00:00 2001 From: OldSerpskiStalker Date: Tue, 17 Sep 2024 21:23:20 +0500 Subject: [PATCH] Add entity caching class, part 1 --- .../technical/customlibrary/AuxFunctions.java | 31 +++ .../technical/worldcache/Cache.java | 222 ++++++++++++++++++ .../technical/worldcache/CacheConfig.java | 146 ++++++++++++ .../technical/worldcache/CacheEvents.java | 139 +++++++++++ .../technical/worldcache/CacheMonitor.java | 41 ++++ .../technical/worldcache/CacheStorage.java | 94 ++++++++ 6 files changed, 673 insertions(+) create mode 100644 dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/customlibrary/AuxFunctions.java create mode 100644 dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/Cache.java create mode 100644 dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheConfig.java create mode 100644 dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheEvents.java create mode 100644 dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheMonitor.java create mode 100644 dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheStorage.java diff --git a/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/customlibrary/AuxFunctions.java b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/customlibrary/AuxFunctions.java new file mode 100644 index 0000000..9e88ddd --- /dev/null +++ b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/customlibrary/AuxFunctions.java @@ -0,0 +1,31 @@ +package org.imesense.dynamicspawncontrol.technical.customlibrary; + +/** + * + */ +public class AuxFunctions +{ + /** + * + */ + public enum NameSingleScript + { + SCRIPT_MOBS_LIST_SEE_SKY("action_mobs_list_see_sky.json"), + + SCRIPT_ZOMBIE_SUMMON_AID("action_zombie_summon_aid.json"), + + SCRIPT_CACHE_MOBS("cache_mobs.json"); + + private final String _keyword; + + NameSingleScript(String keyword) + { + this._keyword = keyword; + } + + public String getKeyword() + { + return this._keyword; + } + } +} diff --git a/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/Cache.java b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/Cache.java new file mode 100644 index 0000000..dc4283b --- /dev/null +++ b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/Cache.java @@ -0,0 +1,222 @@ +package org.imesense.dynamicspawncontrol.technical.worldcache; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.monster.EntityMob; +import net.minecraft.entity.passive.EntityAnimal; +import net.minecraft.entity.passive.IAnimals; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.server.management.PlayerChunkMapEntry; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * + */ +public class Cache +{ + /** + * + */ + public static int TickCounter = 0; + + /** + * + */ + public static final int UPDATE_INTERVAL = 1200; + + /** + * + */ + public static final Set CachedValidChunks = new HashSet<>(); + + /** + * + */ + public static final Set CachedAnimals = new HashSet<>(); + + /** + * + */ + public static final Set CachedHostileEntities = new HashSet<>(); + + /** + * + */ + public static final Set CachedAllEntities = new HashSet<>(); + + /** + * + */ + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + + /** + * + */ + public static final ConcurrentMap> EntitiesByName = new ConcurrentHashMap<>(); + + /** + * + */ + public static final ConcurrentMap> EntitiesByResourceLocation = new ConcurrentHashMap<>(); + + /** + * + * @param world + */ + public static void updateCacheAsync(@Nonnull World world) + { + executor.submit(() -> updateCache(world)); + } + + /** + * + * @param world + */ + public static void updateCache(@Nonnull World world) + { + CachedAnimals.clear(); + CachedHostileEntities.clear(); + CachedAllEntities.clear(); + EntitiesByName.clear(); + EntitiesByResourceLocation.clear(); + CachedValidChunks.clear(); + + if (world instanceof WorldServer) + { + WorldServer worldServer = (WorldServer) world; + Set validChunks = totalValidChunksSpawn(worldServer); + + CachedValidChunks.addAll(validChunks); + } + + for (Entity entity : world.loadedEntityList) + { + if (entity instanceof EntityLivingBase) + { + EntityLivingBase livingEntity = (EntityLivingBase) entity; + + if (CachedValidChunks.contains(new ChunkPos(entity.chunkCoordX, entity.chunkCoordZ))) + { + if (entity instanceof IAnimals) + { + if (entity instanceof EntityAnimal) + { + CachedAnimals.add((EntityAnimal) entity); + } + else if (entity instanceof EntityMob) + { + CachedHostileEntities.add((IAnimals) entity); + } + } + + CachedAllEntities.add(livingEntity); + + String entityName = entity.getName(); + EntitiesByName.computeIfAbsent(entityName, k -> new HashSet<>()).add(livingEntity); + + ResourceLocation entityKey = EntityList.getKey(entity); + + if (entityKey != null) + { + EntitiesByResourceLocation.computeIfAbsent(entityKey, k -> new HashSet<>()).add(livingEntity); + } + } + } + } + } + + /** + * + * @param worldServer + * @return + */ + private static Set totalValidChunksSpawn(@Nonnull WorldServer worldServer) + { + Set setPos = new HashSet<>(); + + for (EntityPlayer player : worldServer.playerEntities) + { + if (!player.isSpectator()) + { + int x = MathHelper.floor(player.posX / 16.0f); + int z = MathHelper.floor(player.posZ / 16.0f); + + for (int dx = -8; dx <= 8; ++dx) + { + for (int dz = -8; dz <= 8; ++dz) + { + boolean correct = (dx == -8 || dx == 8 || dz == -8 || dz == 8); + ChunkPos pos = new ChunkPos(dx + x, dz + z); + + if (!setPos.contains(pos)) + { + if (!correct && worldServer.getWorldBorder().contains(pos)) + { + PlayerChunkMapEntry playerServer = worldServer.getPlayerChunkMap().getEntry(pos.x, pos.z); + + if (playerServer != null && playerServer.isSentToPlayers()) + { + setPos.add(pos); + } + } + } + } + } + } + } + + return setPos; + } + + /** + * + * @return + */ + public static int getAnimalCount() + { + return CachedAnimals.size(); + } + + /** + * + * @return + */ + public static int getTotalEntityCount() + { + return CachedAllEntities.size(); + } + + /** + * + * @return + */ + public static int getHostileEntityCount() + { + return CachedHostileEntities.size(); + } + + /** + * + * @param resourceLocation + * @return + */ + @Nonnull + public static Set getEntitiesByResourceLocation(@Nonnull ResourceLocation resourceLocation) + { + return EntitiesByResourceLocation.getOrDefault(resourceLocation, Collections.emptySet()); + } +} diff --git a/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheConfig.java b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheConfig.java new file mode 100644 index 0000000..7994046 --- /dev/null +++ b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheConfig.java @@ -0,0 +1,146 @@ +package org.imesense.dynamicspawncontrol.technical.worldcache; + +import com.google.gson.*; +import net.minecraft.util.ResourceLocation; +import org.imesense.dynamicspawncontrol.DynamicSpawnControl; +import org.imesense.dynamicspawncontrol.technical.customlibrary.AuxFunctions; +import org.imesense.dynamicspawncontrol.technical.customlibrary.Log; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public class CacheConfig +{ + /** + * + * @param nameClass + */ + public CacheConfig(final String nameClass) + { + Log.writeDataToLogFile(Log.TypeLog[0], nameClass); + } + + /** + * + */ + public void reloadConfig() + { + loadConfig(false); + } + + /** + * + */ + private static CacheConfig classInstance; + + /** + * + * @return + */ + public static CacheConfig getClassInstance() + { + return classInstance; + } + + /** + * + * @param initialization + */ + public void loadConfig(boolean initialization) + { + classInstance = this; + + File file = (initialization) + ? new File(DynamicSpawnControl.getGlobalPathToConfigs().getPath() + File.separator + "InfinityForceSpawnConfigs" + + File.separator + "cache", AuxFunctions.NameSingleScript.SCRIPT_CACHE_MOBS.getKeyword()) + : new File("config/InfinityForceSpawnConfigs/cache/" + + AuxFunctions.NameSingleScript.SCRIPT_CACHE_MOBS.getKeyword()); + + if (!file.exists()) + { + try + { + File parentDir = file.getParentFile(); + if (!parentDir.exists() && !parentDir.mkdirs()) + { + Log.writeDataToLogFile(Log.TypeLog[0], "Failed to create directories for script file: " + parentDir.getAbsolutePath()); + throw new RuntimeException("Failed to create directories for script file: " + parentDir.getAbsolutePath()); + } + + if (file.createNewFile()) + { + Log.writeDataToLogFile(Log.TypeLog[0], "Created new script file: " + file.getAbsolutePath()); + try (FileWriter writer = new FileWriter(file)) + { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + JsonArray emptyJsonArray = new JsonArray(); + gson.toJson(emptyJsonArray, writer); + Log.writeDataToLogFile(Log.TypeLog[0], "Initialized new script file with empty JSON array: " + file.getAbsolutePath()); + } + } + else + { + Log.writeDataToLogFile(Log.TypeLog[0], "Failed to create new script file: " + file.getAbsolutePath()); + throw new RuntimeException(); + } + } + catch (IOException e) + { + Log.writeDataToLogFile(Log.TypeLog[0], "Error creating new script file: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + try (FileReader reader = new FileReader(file)) + { + Gson gson = new Gson(); + JsonArray jsonArray = gson.fromJson(reader, JsonArray.class); + + if (jsonArray != null) + { + List entitiesList = new ArrayList<>(); + + for (JsonElement element : jsonArray) + { + JsonObject dataObject = element.getAsJsonObject(); + String entityName = dataObject.get("entity").getAsString(); + int maxCount = dataObject.get("max_count").getAsInt(); + + String[] parts = entityName.split(":"); + String domain = parts.length > 1 ? parts[0] : "minecraft"; + String path = parts.length > 1 ? parts[1] : parts[0]; + ResourceLocation entityResourceLocation = new ResourceLocation(domain, path); + + Log.writeDataToLogFile(Log.TypeLog[0], "Entity Loaded: " + entityResourceLocation + " Max Count: " + maxCount); + + entitiesList.add(new CacheStorage.EntityData(entityResourceLocation, maxCount)); + } + + CacheStorage.getInstance().EntityCacheMobs = entitiesList; + Log.writeDataToLogFile(Log.TypeLog[0], "Loaded script with data: " + entitiesList); + } + else + { + Log.writeDataToLogFile(Log.TypeLog[0], "Script does not contain key 'data'."); + throw new RuntimeException(); + } + } + catch (JsonSyntaxException exception) + { + Log.writeDataToLogFile(Log.TypeLog[0], "JSON syntax error in configuration file: " + exception.getMessage()); + throw new RuntimeException(exception); + } + catch (IOException exception) + { + Log.writeDataToLogFile(Log.TypeLog[0], "Error loading script file: " + exception.getMessage()); + throw new RuntimeException(exception); + } + } +} diff --git a/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheEvents.java b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheEvents.java new file mode 100644 index 0000000..beeaed2 --- /dev/null +++ b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheEvents.java @@ -0,0 +1,139 @@ +package org.imesense.dynamicspawncontrol.technical.worldcache; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.monster.EntityMob; +import net.minecraft.entity.passive.EntityAnimal; +import net.minecraft.entity.passive.IAnimals; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.event.entity.EntityJoinWorldEvent; +import net.minecraftforge.event.entity.living.LivingSpawnEvent; +import net.minecraftforge.fml.common.eventhandler.Event; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; + +import java.util.HashSet; + +/** + * + */ +public class CacheEvents +{ + /** + * + * @param event + */ + @SubscribeEvent(priority = EventPriority.NORMAL) + public synchronized void onWorldTick(TickEvent.WorldTickEvent event) + { + if (event.phase == TickEvent.Phase.START) + { + Cache.TickCounter++; + + if (Cache.TickCounter >= Cache.UPDATE_INTERVAL) + { + Cache.TickCounter = 0; + Cache.updateCacheAsync(event.world); + } + } + } + + /** + * + * @param event + */ + @SubscribeEvent(priority = EventPriority.NORMAL) + public synchronized void onRenderOverlay(RenderGameOverlayEvent.Post event) + { + //if (!_debugMonitorCache) + //{ + // return; + //} + + if (event.getType() == RenderGameOverlayEvent.ElementType.TEXT) + { + CacheMonitor.renderDebugInfo(event.getResolution()); + } + } + + /** + * + * @param event + */ + @SubscribeEvent(priority = EventPriority.NORMAL) + public synchronized void onEntityJoinWorld(EntityJoinWorldEvent event) + { + World world = event.getWorld(); + Entity entity = event.getEntity(); + + if (world.isRemote || !(world instanceof WorldServer)) + { + return; + } + + WorldServer worldServer = (WorldServer) world; + + Cache.updateCache(worldServer); + + if (Cache.CachedValidChunks.contains(new ChunkPos(entity.chunkCoordX, entity.chunkCoordZ))) + { + if (entity instanceof IAnimals) + { + if (entity instanceof EntityAnimal) + { + Cache.CachedAnimals.add((EntityAnimal) entity); + } + else if (entity instanceof EntityMob) + { + Cache.CachedHostileEntities.add((IAnimals) entity); + } + } + + if (entity instanceof EntityLivingBase) + { + Cache.CachedAllEntities.add((EntityLivingBase) entity); + String entityName = entity.getName(); + Cache.EntitiesByName.computeIfAbsent(entityName, k -> new HashSet<>()).add((EntityLivingBase) entity); + + ResourceLocation entityKey = EntityList.getKey(entity); + + if (entityKey != null) + { + Cache.EntitiesByResourceLocation.computeIfAbsent(entityKey, k -> new HashSet<>()).add((EntityLivingBase) entity); + } + } + } + } + + /** + * + * @param event + */ + @SubscribeEvent(priority = EventPriority.HIGH) + public synchronized void updateEntitySpawnEvent(LivingSpawnEvent.CheckSpawn event) + { + Entity entity = event.getEntity(); + ResourceLocation entityKey = EntityList.getKey(entity); + + CacheStorage.EntityData entityData = CacheStorage.getInstance().getEntityDataByResourceLocation(entityKey); + + if (entityData != null) + { + assert entityKey != null; + + int currentCount = Cache.getEntitiesByResourceLocation(entityKey).size(); + int maxCount = entityData.getMaxCount(); + + if (currentCount > maxCount) + { + event.setResult(Event.Result.DENY); + } + } + } +} diff --git a/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheMonitor.java b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheMonitor.java new file mode 100644 index 0000000..e091b32 --- /dev/null +++ b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheMonitor.java @@ -0,0 +1,41 @@ +package org.imesense.dynamicspawncontrol.technical.worldcache; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.client.gui.ScaledResolution; + +import static org.imesense.dynamicspawncontrol.technical.worldcache.Cache.*; + +/** + * + */ +public class CacheMonitor +{ + /** + * + */ + protected static int x = 10, y = 10; + + /** + * + */ + public static final Minecraft GetMinecraft = Minecraft.getMinecraft(); + + /** + * + * @param resolution + */ + public static void renderDebugInfo(ScaledResolution resolution) + { + FontRenderer fontRenderer = GetMinecraft.fontRenderer; + + String animalMessage = TextFormatting.GREEN + "Animals: " + getAnimalCount(); + String hostileMessage = TextFormatting.RED + "Hostile Entities: " + getHostileEntityCount(); + String totalMessage = TextFormatting.YELLOW + "Total Entities: " + getTotalEntityCount(); + + fontRenderer.drawString(animalMessage, x, y, 0xFFFFFF); + fontRenderer.drawString(hostileMessage, x, y + 10, 0xFFFFFF); + fontRenderer.drawString(totalMessage, x, y + 20, 0xFFFFFF); + } +} diff --git a/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheStorage.java b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheStorage.java new file mode 100644 index 0000000..2662dcc --- /dev/null +++ b/dynamicspawncontrol-1.12.2/src/main/java/org/imesense/dynamicspawncontrol/technical/worldcache/CacheStorage.java @@ -0,0 +1,94 @@ +package org.imesense.dynamicspawncontrol.technical.worldcache; + +import net.minecraft.util.ResourceLocation; +import org.imesense.dynamicspawncontrol.technical.customlibrary.Log; + +import java.util.List; + +/** + * + */ +public class CacheStorage +{ + /** + * + */ + private static CacheStorage instanceClass; + + /** + * + * @return + */ + public static CacheStorage getInstance() + { + return instanceClass; + } + + /** + * + * @param nameClass + */ + public CacheStorage(final String nameClass) + { + Log.writeDataToLogFile(Log.TypeLog[0], String.format("Create object [%s]", nameClass)); + + instanceClass = this; + } + + /** + * + */ + public List EntityCacheMobs; + + /** + * + * @return + */ + public List getEntityCacheMobs() + { + return EntityCacheMobs; + } + + /** + * + * @param resourceLocation + * @return + */ + public CacheStorage.EntityData getEntityDataByResourceLocation(ResourceLocation resourceLocation) + { + for (CacheStorage.EntityData entityData : getEntityCacheMobs()) + { + if (entityData.getEntity().equals(resourceLocation)) + { + return entityData; + } + } + + return null; + } + + /** + * + */ + public static class EntityData + { + private final int maxCount; + private final ResourceLocation entity; + + public EntityData(ResourceLocation entity, int maxCount) + { + this.entity = entity; + this.maxCount = maxCount; + } + + public ResourceLocation getEntity() + { + return entity; + } + + public int getMaxCount() + { + return maxCount; + } + } +}