diff --git a/src/main/java/dev/micle/xptools/config/Config.java b/src/main/java/dev/micle/xptools/config/Config.java index 687ee33..1f8b219 100644 --- a/src/main/java/dev/micle/xptools/config/Config.java +++ b/src/main/java/dev/micle/xptools/config/Config.java @@ -76,6 +76,11 @@ public final class Config { private static ForgeConfigSpec.ConfigValue> blockBreakOperationsRaw; public static List blockBreakOperationItems; + private static ForgeConfigSpec.ConfigValue> entityKillGlobalOperationsRaw; + public static List entityKillGlobalOperationItems; + private static ForgeConfigSpec.ConfigValue> entityKillOperationsRaw; + public static List entityKillOperationItems; + Server(ForgeConfigSpec.Builder builder) { builder.comment("Settings for debugging").push("debug"); debugExtra = builder @@ -85,9 +90,11 @@ public final class Config { builder.comment("Settings for optimizations").push("optimization"); optimizationUseCache = builder - .comment("When enabled, the list of operations to perform per unique_id will be cached after the first calculation.") - .comment("Although this does increase performance at the cost of RAM, the overall performance hit of this mod is tiny anyway... but oh well") - .define("optimizationUseCache", true); + .comment("Allows saving lists of operations per unique id (block_id/entity_id etc.).") + .comment("This will speed up getting the order of operations after the initial calculation.") + .comment("The downside is that it uses more RAM of course (I don't know how much), while it shouldn't be a lot it might not be worth it if you don't have many operations.") + .comment("The cache is not persistent and gets cleared whenever the config gets reloaded.") + .define("optimizationUseCache", false); builder.pop(); builder.comment("Settings for block breaking").push("block_breaking"); @@ -105,11 +112,28 @@ public final class Config { .comment("'#forge:ores,multiply,1,2,1,false' - Multiplies xp drop of all blocks tagged forge:ores by 1-2, allows additional operations.") .define("blockBreakOperations", new ArrayList<>()); builder.pop(); + + builder.comment("Settings for entity killing").push("entity_killing"); + builder.comment("Available operations: " + Arrays.toString(OperationType.values())); + entityKillGlobalOperationsRaw = builder + .comment("List of global operations. Format: '[operation],[min],[max],[priority]'") + .comment("Global operations are run before any unique operations.") + .comment("Examples:") + .comment("'set,0,0,0' - Sets the xp of all entities to 0.") + .define("entityKillGlobalOperations", new ArrayList<>()); + entityKillOperationsRaw = builder + .comment("List of unique operations. Format: '[entity_id/tag_id],[operation],[min],[max],[priority],[is_last]'") + .comment("Examples:") + .comment("'minecraft:creeper,set,2,2,0,true' - Sets the xp drop for killing creepers to 2, takes highest priority and stops any additional operations.") + .comment("'#some_mod:some_tag,multiply,1,2,1,false' - Multiplies the xp drop for killing all entities tagged some_mod:some_tag by 1-2, allows additional operations.") + .define("entityKillOperations", new ArrayList<>()); + builder.pop(); } private static void onConfigReload() { // Clear cache OperationCache.clearBlockBreakCache(); + OperationCache.clearEntityKillCache(); // Parse all block break global operations blockBreakGlobalOperationItems = new ArrayList<>(); @@ -123,6 +147,19 @@ public final class Config { for (String s : blockBreakOperationsRaw.get()) { blockBreakOperationItems.add(OperationItem.fromConfig(s)); } + + // Parse all entity kill global operations + entityKillGlobalOperationItems = new ArrayList<>(); + for (String s : entityKillGlobalOperationsRaw.get()) { + entityKillGlobalOperationItems.add(GlobalOperationItem.fromConfig(s)); + } + entityKillGlobalOperationItems.sort(Comparator.comparingInt(OperationItem::getPriority)); + + // Parse all entity kill unique operations + entityKillOperationItems = new ArrayList<>(); + for (String s : entityKillOperationsRaw.get()) { + entityKillOperationItems.add(OperationItem.fromConfig(s)); + } } } } diff --git a/src/main/java/dev/micle/xptools/events/common/OnBlockBreakEventHandler.java b/src/main/java/dev/micle/xptools/events/common/OnBlockBreakEventHandler.java index cce9704..2dfd2a8 100644 --- a/src/main/java/dev/micle/xptools/events/common/OnBlockBreakEventHandler.java +++ b/src/main/java/dev/micle/xptools/events/common/OnBlockBreakEventHandler.java @@ -4,24 +4,21 @@ import dev.micle.xptools.XpTools; import dev.micle.xptools.config.Config; import dev.micle.xptools.operation.OperationCache; import dev.micle.xptools.operation.OperationItem; +import dev.micle.xptools.operation.OperationUtils; import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; -import net.minecraft.world.level.block.Block; import net.minecraftforge.event.level.BlockEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.registries.ForgeRegistries; import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.Comparator; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; public class OnBlockBreakEventHandler { @SubscribeEvent public void OnBlockBreakEvent(BlockEvent.BreakEvent event) { Instant start = Instant.now(); + boolean usedCache = true; float xpToDrop = event.getExpToDrop(); // Get Block id @@ -38,37 +35,14 @@ public class OnBlockBreakEventHandler { } if (operations == null) { - operations = new ArrayList<>(); + usedCache = false; - // Collect operations on relevant block_id - if (!block_id.isEmpty()) { - for (OperationItem operationItem : Config.Server.blockBreakOperationItems) { - if (!operationItem.isTag() && operationItem.getId().equals(block_id)) { - operations.add(operationItem); - } - } - } - - // Collect operations on relevant tag_id - for (TagKey tagKey : event.getState().getTags().toList()) { - String tag_id = tagKey.location().toString(); - for (OperationItem operationItem : Config.Server.blockBreakOperationItems) { - if (operationItem.isTag() && operationItem.getId().equals(tag_id)) { - operations.add(operationItem); - } - } - } - - // Sort operations based on priority - operations.sort(Comparator.comparingInt(OperationItem::getPriority)); - - // Remove any operations after last operation - for (OperationItem operationItem : operations) { - if (operationItem.isLast()) { - operations = operations.subList(0, operations.indexOf(operationItem) + 1); - break; - } - } + // Calculate operations + operations = OperationUtils.calculateOperationList( + block_id, + event.getState().getTags().toList(), + Config.Server.blockBreakOperationItems + ); // Save operations to cache OperationCache.addBlockBreakCacheEntry(block_id, operations); @@ -78,41 +52,13 @@ public class OnBlockBreakEventHandler { operations.addAll(0, Config.Server.blockBreakGlobalOperationItems); // Apply operations to xp drops - for (OperationItem operation : operations) { - // Calculate operation value - float opValue = (operation.getMin() == operation.getMax()) ? - operation.getMin() : - ThreadLocalRandom.current().nextFloat(operation.getMin(), operation.getMax()); - - // Apply operation - switch (operation.getType()) { - case SET: - xpToDrop = opValue; - break; - case ADD: - xpToDrop += opValue; - break; - case SUBTRACT: - xpToDrop -= opValue; - break; - case MULTIPLY: - xpToDrop *= opValue; - break; - case DIVIDE: - xpToDrop /= opValue; - break; - } - - // Stop if this is the last operation - if (operation.isLast()) { - break; - } - } + xpToDrop = OperationUtils.calculateNewXpAmount(xpToDrop, operations); // Debug logging if (Config.Server.debugExtra.get()) { XpTools.LOGGER.debug("Completed block break event:"); XpTools.LOGGER.debug("\tOperations: {}", operations); + XpTools.LOGGER.debug("\tUsed cache: {}", usedCache); XpTools.LOGGER.debug("\tTime taken (nano seconds): {}", Duration.between(start, Instant.now()).toNanos()); XpTools.LOGGER.debug("\tXP: {} -> {}", event.getExpToDrop(), xpToDrop); } diff --git a/src/main/java/dev/micle/xptools/events/common/OnLivingExperienceDropEventHandler.java b/src/main/java/dev/micle/xptools/events/common/OnLivingExperienceDropEventHandler.java new file mode 100644 index 0000000..91f5917 --- /dev/null +++ b/src/main/java/dev/micle/xptools/events/common/OnLivingExperienceDropEventHandler.java @@ -0,0 +1,69 @@ +package dev.micle.xptools.events.common; + +import dev.micle.xptools.XpTools; +import dev.micle.xptools.config.Config; +import dev.micle.xptools.operation.OperationCache; +import dev.micle.xptools.operation.OperationItem; +import dev.micle.xptools.operation.OperationUtils; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.event.entity.living.LivingExperienceDropEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.registries.ForgeRegistries; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; + +public class OnLivingExperienceDropEventHandler { + @SubscribeEvent + public void onLivingExperienceDropEvent(LivingExperienceDropEvent event) { + Instant start = Instant.now(); + boolean usedCache = true; + float xpToDrop = event.getDroppedExperience(); + + // Get Entity id + ResourceLocation entity_rl = ForgeRegistries.ENTITY_TYPES.getKey(event.getEntity().getType()); + String entity_id = ""; + if (entity_rl != null) { + entity_id = entity_rl.toString(); + } + + // Collect operations + List operations = null; + if (Config.Server.optimizationUseCache.get()) { + operations = OperationCache.getEntityKillCacheEntry(entity_id); + } + + if (operations == null) { + usedCache = false; + + // Calculate operations + operations = OperationUtils.calculateOperationList( + entity_id, + event.getEntity().getType().getTags().toList(), + Config.Server.entityKillOperationItems + ); + + // Save operations to cache + OperationCache.addEntityKillCacheEntry(entity_id, operations); + } + + // Add global operations before all others + operations.addAll(0, Config.Server.entityKillGlobalOperationItems); + + // Apply operations to xp drops + xpToDrop = OperationUtils.calculateNewXpAmount(xpToDrop, operations); + + // Debug logging + if (Config.Server.debugExtra.get()) { + XpTools.LOGGER.debug("Completed entity kill event:"); + XpTools.LOGGER.debug("\tOperations: {}", operations); + XpTools.LOGGER.debug("\tUsed cache: {}", usedCache); + XpTools.LOGGER.debug("\tTime taken (nano seconds): {}", Duration.between(start, Instant.now()).toNanos()); + XpTools.LOGGER.debug("\tXP: {} -> {}", event.getDroppedExperience(), xpToDrop); + } + + // Apply xp drop + event.setDroppedExperience((int)xpToDrop); + } +} diff --git a/src/main/java/dev/micle/xptools/operation/OperationCache.java b/src/main/java/dev/micle/xptools/operation/OperationCache.java index 446d716..8a35668 100644 --- a/src/main/java/dev/micle/xptools/operation/OperationCache.java +++ b/src/main/java/dev/micle/xptools/operation/OperationCache.java @@ -7,11 +7,16 @@ import java.util.List; public class OperationCache { private static HashMap> blockBreakCache; + private static HashMap> entityKillCache; public static void clearBlockBreakCache() { blockBreakCache = new HashMap<>(); } + public static void clearEntityKillCache() { + entityKillCache = new HashMap<>(); + } + public static @Nullable List getBlockBreakCacheEntry(String key) { if (blockBreakCache.containsKey(key)) { return new ArrayList<>(blockBreakCache.get(key)); @@ -19,7 +24,18 @@ public class OperationCache { return null; } + public static @Nullable List getEntityKillCacheEntry(String key) { + if (entityKillCache.containsKey(key)) { + return new ArrayList<>(entityKillCache.get(key)); + } + return null; + } + public static void addBlockBreakCacheEntry(String key, List value) { blockBreakCache.putIfAbsent(key, new ArrayList<>(value)); } + + public static void addEntityKillCacheEntry(String key, List value) { + entityKillCache.putIfAbsent(key, new ArrayList<>(value)); + } } diff --git a/src/main/java/dev/micle/xptools/operation/OperationUtils.java b/src/main/java/dev/micle/xptools/operation/OperationUtils.java new file mode 100644 index 0000000..f0ed28e --- /dev/null +++ b/src/main/java/dev/micle/xptools/operation/OperationUtils.java @@ -0,0 +1,76 @@ +package dev.micle.xptools.operation; + +import net.minecraft.tags.TagKey; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class OperationUtils { + public static > List calculateOperationList(String id, List tagList, List operationItems) { + List operations = new ArrayList<>(); + + // Collect operations on relevant id + if (!id.isEmpty()) { + for (OperationItem operationItem : operationItems) { + if (!operationItem.isTag() && operationItem.getId().equals(id)) { + operations.add(operationItem); + } + } + } + + // Collect operations on relevant tag_id + for (T tagKey : tagList) { + String tag_id = tagKey.location().toString(); + for (OperationItem operationItem : operationItems) { + if (operationItem.isTag() && operationItem.getId().equals(tag_id)) { + operations.add(operationItem); + } + } + } + + // Sort operations based on priority + operations.sort(Comparator.comparingInt(OperationItem::getPriority)); + + // Remove any operations after last operation + for (OperationItem operationItem : operations) { + if (operationItem.isLast()) { + operations = operations.subList(0, operations.indexOf(operationItem) + 1); + break; + } + } + + return operations; + } + + public static float calculateNewXpAmount(float xp, List operations) { + for (OperationItem operation : operations) { + // Calculate operation value + float opValue = (operation.getMin() == operation.getMax()) ? + operation.getMin() : + ThreadLocalRandom.current().nextFloat(operation.getMin(), operation.getMax()); + + // Apply operation + switch (operation.getType()) { + case SET: + xp = opValue; + break; + case ADD: + xp += opValue; + break; + case SUBTRACT: + xp -= opValue; + break; + case MULTIPLY: + xp *= opValue; + break; + case DIVIDE: + xp /= opValue; + break; + } + } + + return xp; + } +} diff --git a/src/main/java/dev/micle/xptools/proxy/Proxy.java b/src/main/java/dev/micle/xptools/proxy/Proxy.java index eefac49..58ea058 100644 --- a/src/main/java/dev/micle/xptools/proxy/Proxy.java +++ b/src/main/java/dev/micle/xptools/proxy/Proxy.java @@ -3,6 +3,7 @@ package dev.micle.xptools.proxy; import dev.micle.xptools.XpTools; import dev.micle.xptools.config.Config; import dev.micle.xptools.events.common.OnBlockBreakEventHandler; +import dev.micle.xptools.events.common.OnLivingExperienceDropEventHandler; import net.minecraft.client.Minecraft; import net.minecraft.server.MinecraftServer; import net.minecraft.world.entity.player.Player; @@ -34,6 +35,7 @@ public class Proxy implements IProxy { // Register event handlers MinecraftForge.EVENT_BUS.register(new OnBlockBreakEventHandler()); + MinecraftForge.EVENT_BUS.register(new OnLivingExperienceDropEventHandler()); } private static void setup(FMLCommonSetupEvent event) {}