diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..158129713 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0e49f92aa..1e971f02e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ me.lokka30 LevelledMobs - 3.3.3 b604 + 3.4.0 b624 jar LevelledMobs @@ -143,7 +143,7 @@ io.papermc.paper paper-api - 1.17.1-R0.1-SNAPSHOT + 1.18.1-R0.1-SNAPSHOT provided @@ -161,13 +161,13 @@ org.spigotmc spigot-api - 1.17.1-R0.1-SNAPSHOT + 1.18.1-R0.1-SNAPSHOT provided org.bstats bstats-bukkit - 2.2.1 + 3.0.0 compile @@ -185,7 +185,7 @@ com.github.dmulloy2 ProtocolLib - 4.7.0 + 4.8.0 provided @@ -197,13 +197,13 @@ simplepets.brainsynder API - 5.0-BUILD-98 + 5.0-BUILD-146 provided com.google.code.gson gson - 2.8.9 + 2.9.0 provided diff --git a/src/main/java/me/lokka30/levelledmobs/Companion.java b/src/main/java/me/lokka30/levelledmobs/Companion.java index cbe76c8be..3cbb62b9d 100644 --- a/src/main/java/me/lokka30/levelledmobs/Companion.java +++ b/src/main/java/me/lokka30/levelledmobs/Companion.java @@ -24,9 +24,10 @@ import me.lokka30.levelledmobs.listeners.PlayerInteractEventListener; import me.lokka30.levelledmobs.listeners.PlayerJoinListener; import me.lokka30.levelledmobs.listeners.PlayerPortalEventListener; -import me.lokka30.levelledmobs.managers.ExternalCompatibilityManager; import me.lokka30.levelledmobs.managers.LevelManager; import me.lokka30.levelledmobs.managers.PlaceholderApiIntegration; + +import me.lokka30.levelledmobs.misc.ChunkKillInfo; import me.lokka30.levelledmobs.misc.DebugType; import me.lokka30.levelledmobs.misc.FileLoader; import me.lokka30.levelledmobs.misc.FileMigrator; @@ -45,7 +46,10 @@ import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,10 +58,13 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.InvalidObjectException; +import java.time.Duration; import java.time.Instant; -import java.util.Collections; + +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -88,6 +95,8 @@ public class Companion { this.spawner_CopyIds = new LinkedList<>(); this.spawner_InfoIds = new LinkedList<>(); this.debugsEnabled = new LinkedList<>(); + this.entityDeathInChunkCounter = new HashMap<>(); + this.chunkKillNoticationTracker = new HashMap<>(); } final private WeakHashMap recentlyJoinedPlayers; @@ -95,6 +104,10 @@ public class Companion { public HashSet groups_AquaticMobs; public HashSet groups_PassiveMobs; public List updateResult; + private boolean hadRulesLoadError; + public boolean useAdventure; + final private HashMap> entityDeathInChunkCounter; + final private HashMap> chunkKillNoticationTracker; final public Map playerNetherPortals; final public Map playerWorldPortals; final public List spawner_CopyIds; @@ -102,8 +115,11 @@ public class Companion { final public List debugsEnabled; final private PluginManager pluginManager = Bukkit.getPluginManager(); final private MetricsInfo metricsInfo; + private BukkitTask hashMapCleanUp; final static private Object playerLogonTimes_Lock = new Object(); final static private Object playerNetherPortals_Lock = new Object(); + final static private Object entityDeathInChunkCounter_Lock = new Object(); + final static private Object entityDeathInChunkNotifier_Lock = new Object(); //Checks if the server version is supported public void checkCompatibility() { @@ -118,8 +134,18 @@ public void checkCompatibility() { "Compatible MC versions: &b" + String.join("&7,&b ", Utils.getSupportedServerVersions()) + "&7."); } - if (!ExternalCompatibilityManager.hasProtocolLibInstalled()) { - incompatibilities.add("Your server does not have &bProtocolLib&7 installed! This means that no levelled nametags will appear on the mobs. If you wish to see custom nametags above levelled mobs, then you must install ProtocolLib."); + final Plugin protocolLibPlugin = Bukkit.getPluginManager().getPlugin("ProtocolLib"); + + if (protocolLibPlugin == null) { + incompatibilities.add("Your server does not have ProtocolLib installed! This means that you will" + + "not be able to see any custom nametags/labels above any levelled mobs' heads. To fix this, " + + "install the latest compatible version of ProtocolLib for your server."); + } else { + if(VersionUtils.isOneEighteen() && protocolLibPlugin.getDescription().getVersion().equals("4.7.0")) { + incompatibilities.add("You are running an outdated version of ProtocolLib! This version of " + + "ProtocolLib does not support 1.18+ servers, so you will receive lots of errors if " + + "you try to use it. Update to the latest ProtocolLib version as soon as possible."); + } } main.incompatibilitiesAmount = incompatibilities.size(); @@ -131,6 +157,10 @@ public void checkCompatibility() { } } + public boolean getHadRulesLoadError(){ + return this.hadRulesLoadError; + } + private int getSettingsVersion(){ final File file = new File(main.getDataFolder(), "settings.yml"); if (!file.exists()) return 0; @@ -146,7 +176,9 @@ boolean loadFiles(final boolean isReload) { // save license.txt FileLoader.saveResourceIfNotExists(main, new File(main.getDataFolder(), "license.txt")); - main.rulesParsingManager.parseRulesMain(FileLoader.loadFile(main, "rules", FileLoader.RULES_FILE_VERSION)); + final YamlConfiguration rulesFile = FileLoader.loadFile(main, "rules", FileLoader.RULES_FILE_VERSION); + this.hadRulesLoadError = rulesFile == null; + main.rulesParsingManager.parseRulesMain(rulesFile); main.configUtils.playerLevellingEnabled = main.rulesManager.isPlayerLevellingEnabled(); @@ -192,6 +224,7 @@ boolean loadFiles(final boolean isReload) { main.configUtils.load(); main.playerLevellingMinRelevelTime = main.helperSettings.getInt(main.settingsCfg, "player-levelling-relevel-min-time", 5000); + this.useAdventure = main.helperSettings.getBoolean(main.settingsCfg, "use-adventure", true); return true; } @@ -307,6 +340,121 @@ void setupMetrics() { metrics.addCustomChart(new SimpleBarChart("enabled-compatibility", metricsInfo::enabledCompats)); } + void startCleanupTask(){ + this.hashMapCleanUp = new BukkitRunnable() { + @Override + public void run() { + synchronized (entityDeathInChunkCounter_Lock) { + chunkKillLimitCleanup(); + } + synchronized (entityDeathInChunkNotifier_Lock){ + chunkKillNoticationCleanup(); + } + } + }.runTaskTimerAsynchronously(main, 100, 40); + } + + private void chunkKillLimitCleanup(){ + final List chunkKeysToRemove = new LinkedList<>(); + + for (final long chunkKey : entityDeathInChunkCounter.keySet()){ + // Cooldown time, entity counts + final Map pairList = entityDeathInChunkCounter.get(chunkKey); + + if (pairList == null) continue; + final Instant now = Instant.now(); + + for (final EntityType entityType : pairList.keySet()) { + final ChunkKillInfo chunkKillInfo = pairList.get(entityType); + + chunkKillInfo.getEntrySet().removeIf( + e -> e.getKey().compareTo(now.minusSeconds(e.getValue())) < 0 + ); + } + + pairList.entrySet().removeIf(e -> e.getValue().isEmpty()); + + if(pairList.isEmpty()){ + // Remove the object to prevent iterate over exceed amount of empty pairList + chunkKeysToRemove.add(chunkKey); + } + } + + for (final long chunkKey : chunkKeysToRemove) + entityDeathInChunkCounter.remove(chunkKey); + } + + private void chunkKillNoticationCleanup(){ + final Iterator iterator = this.chunkKillNoticationTracker.keySet().iterator(); + + while (iterator.hasNext()){ + final long chunkKey = iterator.next(); + final Map playerTimestamps = this.chunkKillNoticationTracker.get(chunkKey); + playerTimestamps.entrySet().removeIf(e -> Duration.between(e.getValue(), Instant.now()).toSeconds() > 30L); + + if (playerTimestamps.isEmpty()) iterator.remove(); + } + } + + @NotNull + public Map getorAddPairForSpecifiedChunk(final long chunkKey){ + synchronized (entityDeathInChunkCounter_Lock){ + return this.entityDeathInChunkCounter.computeIfAbsent(chunkKey, k -> new HashMap<>()); + } + } + + @NotNull + public List> getorAddPairForSpecifiedChunks(final @NotNull List chunkKeys){ + final List> results = new ArrayList<>(chunkKeys.size()); + + synchronized (entityDeathInChunkCounter_Lock){ + for (final long chunkKey : chunkKeys) + results.add(this.entityDeathInChunkCounter.computeIfAbsent(chunkKey, k -> new HashMap<>())); + } + + return results; + } + + public boolean doesUserHaveCooldown(final @NotNull List chunkKeys, final @NotNull UUID userId){ + final List> chunkInfos = new LinkedList<>(); + + synchronized (entityDeathInChunkNotifier_Lock){ + for (final long chunkKey : chunkKeys) { + if (this.chunkKillNoticationTracker.containsKey(chunkKey)) + chunkInfos.add(this.chunkKillNoticationTracker.get(chunkKey)); + } + } + + if (chunkInfos.isEmpty()) return false; + + for (final Map chunkInfo : chunkInfos){ + if (chunkInfo == null || !chunkInfo.containsKey(userId)) continue; + final Instant instant = chunkInfo.get(userId); + if (Duration.between(instant, Instant.now()).toSeconds() <= 30L) + return true; + } + + return false; + } + + public void addUserCooldown(final @NotNull List chunkKeys, final @NotNull UUID userId){ + synchronized (entityDeathInChunkNotifier_Lock){ + for (final long chunkKey : chunkKeys){ + final Map entry = this.chunkKillNoticationTracker.computeIfAbsent(chunkKey, k -> new HashMap<>()); + entry.put(userId, Instant.now()); + } + } + } + + public void clearChunkKillCache(){ + synchronized (entityDeathInChunkCounter_Lock){ + this.entityDeathInChunkCounter.clear(); + } + synchronized (entityDeathInChunkNotifier_Lock){ + this.chunkKillNoticationTracker.clear(); + } + } + //Check for updates on the Spigot page. void checkUpdates() { if (main.helperSettings.getBoolean(main.settingsCfg,"use-update-checker", true)) { @@ -334,7 +482,7 @@ void checkUpdates() { } if (isNewerVersion) { - updateResult = Collections.singletonList( + updateResult = List.of( "&7Your &bLevelledMobs&7 version is &ba pre-release&7. Latest release version is &bv%latestVersion%&7. &8(&7You're running &bv%currentVersion%&8)"); updateResult = Utils.replaceAllInList(updateResult, "%currentVersion%", currentVersion); @@ -385,6 +533,7 @@ void shutDownAsyncTasks() { Utils.logger.info("&fTasks: &7Shutting down other async tasks..."); main._mobsQueueManager.stop(); main.nametagQueueManager_.stop(); + if (hashMapCleanUp != null) hashMapCleanUp.cancel(); Bukkit.getScheduler().cancelTasks(main); } diff --git a/src/main/java/me/lokka30/levelledmobs/LevelledMobs.java b/src/main/java/me/lokka30/levelledmobs/LevelledMobs.java index b5dfbce56..58bd0a88f 100644 --- a/src/main/java/me/lokka30/levelledmobs/LevelledMobs.java +++ b/src/main/java/me/lokka30/levelledmobs/LevelledMobs.java @@ -116,6 +116,7 @@ public void onEnable() { levelManager.startNametagAutoUpdateTask(); levelManager.startNametagTimer(); } + companion.startCleanupTask(); companion.setupMetrics(); companion.checkUpdates(); diff --git a/src/main/java/me/lokka30/levelledmobs/LivingEntityInterface.java b/src/main/java/me/lokka30/levelledmobs/LivingEntityInterface.java index 4974eaaea..1d38d22b5 100644 --- a/src/main/java/me/lokka30/levelledmobs/LivingEntityInterface.java +++ b/src/main/java/me/lokka30/levelledmobs/LivingEntityInterface.java @@ -44,6 +44,10 @@ public interface LivingEntityInterface { void setSpawnedTimeOfDay(final int ticks); + Integer getSummonedLevel(); + + boolean isWasSummoned(); + void clearEntityData(); void free(); diff --git a/src/main/java/me/lokka30/levelledmobs/commands/LevelledMobsCommand.java b/src/main/java/me/lokka30/levelledmobs/commands/LevelledMobsCommand.java index 153c5eacc..d097fe7f2 100644 --- a/src/main/java/me/lokka30/levelledmobs/commands/LevelledMobsCommand.java +++ b/src/main/java/me/lokka30/levelledmobs/commands/LevelledMobsCommand.java @@ -141,6 +141,8 @@ public List onTabComplete(final @NotNull CommandSender sender, final @No return summonSubcommand.parseTabCompletions(main, sender, args); case "egg": return spawnerEggCommand.parseTabCompletions(main, sender, args); + case "debug": + return debugSubcommand.parseTabCompletions(main, sender, args); // the missing subcommands don't have tab completions, don't bother including them. default: return Collections.emptyList(); diff --git a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/DebugSubcommand.java b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/DebugSubcommand.java index 4b3ce14ce..a09fadd27 100644 --- a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/DebugSubcommand.java +++ b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/DebugSubcommand.java @@ -3,19 +3,27 @@ import me.lokka30.levelledmobs.LevelledMobs; import me.lokka30.levelledmobs.commands.MessagesBase; import me.lokka30.levelledmobs.misc.DebugCreator; +import me.lokka30.levelledmobs.misc.Utils; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.List; +/** + * Parses commands for various debug stuff + * + * @author stumper66 + * @since 3.2.0 + */ + public class DebugSubcommand extends MessagesBase implements Subcommand { public DebugSubcommand(final LevelledMobs main) { super(main); } @Override - public void parseSubcommand(final LevelledMobs main, final @NotNull CommandSender sender, final String label, final String[] args) { + public void parseSubcommand(final LevelledMobs main, final @NotNull CommandSender sender, final String label, final String @NotNull [] args) { commandSender = sender; messageLabel = label; @@ -24,15 +32,46 @@ public void parseSubcommand(final LevelledMobs main, final @NotNull CommandSende return; } - if (args.length == 3 && "create".equalsIgnoreCase(args[1]) && "confirm".equalsIgnoreCase(args[2])) - DebugCreator.createDebug(main, sender); + if (args.length <= 1){ + sender.sendMessage("Options: create / chunk_kill_count"); + return; + } + + if ("create".equalsIgnoreCase(args[1])) { + if (args.length >= 3 && "confirm".equalsIgnoreCase(args[2])) + DebugCreator.createDebug(main, sender); + else + showMessage("other.create-debug"); + } + else if ("chunk_kill_count".equalsIgnoreCase(args[1])) { + chunkKillCount(sender, args); + } else showMessage("other.create-debug"); } + private void chunkKillCount(final CommandSender sender, final String @NotNull [] args){ + if (args.length >= 3 && "reset".equalsIgnoreCase(args[2])){ + main.companion.clearChunkKillCache(); + sender.sendMessage("cache has been cleared"); + return; + } + + showChunkKillCountSyntax(sender); + } + + private void showChunkKillCountSyntax(final @NotNull CommandSender sender){ + sender.sendMessage("Options: reset"); + } + @Override - public List parseTabCompletions(final LevelledMobs main, final CommandSender sender, final String[] args) { - // This subcommand has no tab completions. + public List parseTabCompletions(final LevelledMobs main, final CommandSender sender, final String @NotNull [] args) { + Utils.logger.info(String.format("%s", args.length)); + if (args.length <= 2) + return List.of("create", "chunk_kill_count"); + if ("chunk_kill_count".equalsIgnoreCase(args[1])) + return List.of("reset"); + return Collections.emptyList(); } } diff --git a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/KillSubcommand.java b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/KillSubcommand.java index dd61f92dc..1c3e8234d 100644 --- a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/KillSubcommand.java +++ b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/KillSubcommand.java @@ -23,7 +23,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; /** * Allows you to kill LevelledMobs with various options including all levelled mobs, specific worlds @@ -77,7 +80,7 @@ public void parseSubcommand(final LevelledMobs main, final @NotNull CommandSende if (checkArgs == 2) { if (sender instanceof Player) { final Player player = (Player) sender; - parseKillAll(Collections.singletonList(player.getWorld()), main, useNoDrops, rl); + parseKillAll(List.of(player.getWorld()), main, useNoDrops, rl); } else showMessage("command.levelledmobs.kill.all.usage-console"); @@ -98,7 +101,7 @@ public void parseSubcommand(final LevelledMobs main, final @NotNull CommandSende //messages = Utils.replaceAllInList(messages, "%world%", args[2]); //This is after the list is colourised to ensure that an input of a world name '&aGreen' will not be colourised. return; } - parseKillAll(Collections.singletonList(world), main, useNoDrops, rl); + parseKillAll(List.of(world), main, useNoDrops, rl); } } else @@ -224,7 +227,7 @@ else if ("/levels".equalsIgnoreCase(arg)) } if (args.length == 2) { - return Arrays.asList("all", "near"); + return List.of("all", "near"); } if (args[1].equalsIgnoreCase("all") && (args.length == 3 || args.length == 4)) { @@ -253,7 +256,7 @@ else if (args.length == 3) } } - final List result = new ArrayList<>(); + final List result = new LinkedList<>(); if (!containsNoDrops) result.add("/nodrops"); if (!containsLevels) diff --git a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/ReloadSubcommand.java b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/ReloadSubcommand.java index da1f3903a..2e1bfffa0 100644 --- a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/ReloadSubcommand.java +++ b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/ReloadSubcommand.java @@ -5,7 +5,9 @@ package me.lokka30.levelledmobs.commands.subcommands; import me.lokka30.levelledmobs.LevelledMobs; +import me.lokka30.levelledmobs.misc.FileLoader; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import java.util.Collections; @@ -27,6 +29,9 @@ public void parseSubcommand(final LevelledMobs main, final @NotNull CommandSende } main.reloadLM(sender); + + if (main.companion.getHadRulesLoadError() && sender instanceof Player) + sender.sendMessage(FileLoader.getFileLoadErrorMessage()); } @Override diff --git a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/RulesSubcommand.java b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/RulesSubcommand.java index a6c3c42a5..74d9a5a17 100644 --- a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/RulesSubcommand.java +++ b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/RulesSubcommand.java @@ -34,6 +34,7 @@ import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Arrays; @@ -173,11 +174,13 @@ private void resetRules(final CommandSender sender, @NotNull final String @NotNu ResetDifficulty difficulty = ResetDifficulty.UNSPECIFIED; switch (args[2].toLowerCase()){ - case "easy": difficulty = ResetDifficulty.EASY; + case "basic": difficulty = ResetDifficulty.BASIC; break; - case "normal": difficulty = ResetDifficulty.NORMAL; + case "average": difficulty = ResetDifficulty.AVERAGE; break; - case "hard": difficulty = ResetDifficulty.HARD; + case "advanced": difficulty = ResetDifficulty.ADVANCED; + break; + case "extreme": difficulty = ResetDifficulty.EXTREME; break; } @@ -198,25 +201,40 @@ private void resetRules(final @NotNull CommandSender sender, final @NotNull Rese final String prefix = main.configUtils.getPrefix(); showMessage("command.levelledmobs.rules.resetting", "%difficulty%", String.valueOf(difficulty)); - final String filename; + final String filename = "rules.yml"; + final String[] replaceWhat = new String[] {" - average_challenge", " - weighted_random_average", "", ""}; + final String[] replaceWith = new String[] {"# - average_challenge", "# - weighted_random_average", "", ""}; switch (difficulty){ - case EASY: filename = "predefined/rules_easy.yml"; + case BASIC: + replaceWhat[2] = "# - basic_challenge"; replaceWith[2] = " - basic_challenge"; + replaceWhat[3] = "# - weighted_random_basic"; replaceWith[3] = " - weighted_random_basic"; break; - case HARD: filename = "predefined/rules_hard.yml"; + case ADVANCED: + replaceWhat[2] = "# - advanced_challenge"; replaceWith[2] = " - advanced_challenge"; + replaceWhat[3] = "# - weighted_random_advanced"; replaceWith[3] = " - weighted_random_advanced_difficulty"; break; - default: filename = "rules.yml"; + case EXTREME: + replaceWhat[2] = "# - extreme_challenge"; replaceWith[2] = " - extreme_challenge"; + replaceWhat[3] = "# - weighted_random_extreme"; replaceWith[3] = " - weighted_random_extreme"; break; } - try (final InputStream stream = main.getResource(filename)) { if (stream == null){ Utils.logger.error(prefix + " Input stream was null"); return; } - final File rulesFile = new File(main.getDataFolder(), "rules.yml"); + String rulesText = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + if (difficulty != ResetDifficulty.AVERAGE) { + rulesText = rulesText.replace(replaceWhat[0], replaceWith[0]) + .replace(replaceWhat[1], replaceWith[1]) + .replace(replaceWhat[2], replaceWith[2]) + .replace(replaceWhat[3], replaceWith[3]); + } + + final File rulesFile = new File(main.getDataFolder(), filename); File rulesBackupFile = new File(main.getDataFolder(), "rules.yml.backup"); for (int i = 0; i < 10; i++){ @@ -225,7 +243,7 @@ private void resetRules(final @NotNull CommandSender sender, final @NotNull Rese } Files.copy(rulesFile.toPath(), rulesBackupFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - Files.copy(stream, rulesFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.writeString(rulesFile.toPath(), rulesText, StandardCharsets.UTF_8); } catch (final IOException ex) { ex.printStackTrace(); return; @@ -236,7 +254,7 @@ private void resetRules(final @NotNull CommandSender sender, final @NotNull Rese } private enum ResetDifficulty{ - EASY, NORMAL, HARD, UNSPECIFIED + BASIC, AVERAGE, ADVANCED, EXTREME, UNSPECIFIED } private void showHyperlink(final CommandSender sender, final String message, final String url){ @@ -460,11 +478,9 @@ public List parseTabCompletions(final LevelledMobs main, final @NotNull if (args.length == 2) return Arrays.asList("force_all", "help_discord", "help_wiki", "reset", "show_all", "show_effective", "show_rule"); else if (args.length >= 3) { - if ("reset".equalsIgnoreCase(args[1]) && args.length == 3) { - suggestions.add("easy"); - suggestions.add("normal"); - suggestions.add("hard"); - } else if ("show_all".equalsIgnoreCase(args[1])) { + if ("reset".equalsIgnoreCase(args[1]) && args.length == 3) + suggestions.addAll(List.of("basic", "average", "enhanced", "extreme")); + else if ("show_all".equalsIgnoreCase(args[1])) { boolean showOnConsole = false; for (int i = 2; i < args.length; i++) { final String arg = args[i].toLowerCase(); diff --git a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SpawnerBaseClass.java b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SpawnerBaseClass.java index 1ef19eb07..57b88bf0f 100644 --- a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SpawnerBaseClass.java +++ b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SpawnerBaseClass.java @@ -94,7 +94,7 @@ private String parseFlagValue(final String keyName, final int argNumber, final S static void setMetaItems(final @Nullable ItemMeta meta, final @NotNull CustomSpawnerInfo info, final @NotNull String defaultName){ if (meta == null) return; - if (VersionUtils.isRunningPaper()) + if (VersionUtils.isRunningPaper() && info.main.companion.useAdventure) PaperUtils.updateItemDisplayName(meta, info.customName == null ? defaultName : info.customName); else SpigotUtils.updateItemDisplayName(meta, info.customName == null ? defaultName : info.customName); @@ -132,7 +132,7 @@ static void setMetaItems(final @Nullable ItemMeta meta, final @NotNull CustomSpa if (!info.noLore && info.lore == null && info.customLore == null) { lore = Utils.colorizeAllInList(lore); - if (VersionUtils.isRunningPaper()) + if (VersionUtils.isRunningPaper() && info.main.companion.useAdventure) PaperUtils.updateItemMetaLore(meta, lore); else SpigotUtils.updateItemMetaLore(meta, lore); @@ -150,7 +150,7 @@ else if (!info.noLore || info.customLore != null){ lore.clear(); lore.addAll(List.of(useLore.split("\n"))); - if (VersionUtils.isRunningPaper()) + if (VersionUtils.isRunningPaper() && info.main.companion.useAdventure) PaperUtils.updateItemMetaLore(meta, lore); else SpigotUtils.updateItemMetaLore(meta, lore); diff --git a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SummonSubcommand.java b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SummonSubcommand.java index 8a40820ad..01cf4f1be 100644 --- a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SummonSubcommand.java +++ b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SummonSubcommand.java @@ -452,7 +452,7 @@ private void summonMobs(@NotNull final SummonMobOptions options) { if (useDistFromPlayer <= 0) break; location = getLocationNearPlayer(target, origLocation, useDistFromPlayer); - final Location location_YMinus1 = new Location(location.getWorld(), location.getBlockX(), location.getBlockY() - 1, location.getBlockZ()); + final Location location_YMinus1 = location.add(0.0, -1.0, 0.0); if (location.getBlock().isPassable() && location_YMinus1.getBlock().isPassable()) break; // found an open spot } @@ -473,6 +473,7 @@ private void summonMobs(@NotNull final SummonMobOptions options) { if (entity instanceof LivingEntity) { final LivingEntityWrapper lmEntity = LivingEntityWrapper.getInstance((LivingEntity) entity, main); + lmEntity.setSummonedLevel(useLevel); synchronized (LevelManager.summonedOrSpawnEggs_Lock){ main.levelManager.summonedOrSpawnEggs.put(lmEntity.getLivingEntity(), null); } @@ -652,13 +653,15 @@ private Location addVarianceToLocation(final Location oldLocation) { final double min = 0.5; final double max = 2.5; + //Creates 3x new Random()s for a different seed each time + final Random random1 = new Random(); + final Random random2 = new Random(); + for (int i = 0; i < 20; i++) { - //Creates 3x new Random()s for a different seed each time - final double x = min + (max - min) * new Random().nextDouble(); - final double y = min + (max - min) * new Random().nextDouble(); - final double z = min + (max - min) * new Random().nextDouble(); + final double x = oldLocation.getX() + min + (max - min) * random1.nextDouble(); + final double z = oldLocation.getZ() + min + (max - min) * random2.nextDouble(); - final Location newLocation = new Location(oldLocation.getWorld(), x, y, z); + final Location newLocation = new Location(oldLocation.getWorld(), x, oldLocation.getY(), z); if (newLocation.getBlock().isPassable() && newLocation.add(0, 1, 0).getBlock().isPassable()) return newLocation; } diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropBase.java b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropBase.java index 1235bf75a..9fb1e76fb 100644 --- a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropBase.java +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropBase.java @@ -6,7 +6,6 @@ import me.lokka30.levelledmobs.misc.CachedModalList; import me.lokka30.levelledmobs.misc.Utils; -import org.bukkit.event.entity.EntityDamageEvent; import org.jetbrains.annotations.NotNull; import java.util.LinkedList; @@ -37,6 +36,7 @@ public class CustomDropBase implements Cloneable { int maxDropGroup; int minPlayerLevel; int maxPlayerLevel; + public boolean useChunkKillMax; public float chance; boolean playerCausedOnly; boolean noSpawner; diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsDefaults.java b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsDefaults.java index e0b68e2f6..723d3ec91 100644 --- a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsDefaults.java +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsDefaults.java @@ -5,7 +5,6 @@ package me.lokka30.levelledmobs.customdrops; import me.lokka30.levelledmobs.misc.CachedModalList; -import org.bukkit.event.entity.EntityDamageEvent; import org.jetbrains.annotations.NotNull; import java.util.LinkedList; @@ -33,6 +32,7 @@ class CustomDropsDefaults { int minPlayerLevel; int maxPlayerLevel; public float chance; + public boolean useChunkKillMax; float equippedSpawnChance; Float overallChance; String groupId; @@ -63,6 +63,7 @@ class CustomDropsDefaults { void setDefaultsFromDropItem(@NotNull final CustomDropItem drop) { this.chance = drop.chance; + this.useChunkKillMax = drop.useChunkKillMax; this.amount = drop.getAmount(); this.minLevel = drop.minLevel; this.maxLevel = drop.maxLevel; diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsHandler.java b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsHandler.java index e1753a546..333e43e91 100644 --- a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsHandler.java +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsHandler.java @@ -5,6 +5,7 @@ package me.lokka30.levelledmobs.customdrops; import me.lokka30.levelledmobs.LevelledMobs; +import me.lokka30.levelledmobs.managers.ExternalCompatibilityManager; import me.lokka30.levelledmobs.misc.Addition; import me.lokka30.levelledmobs.misc.DebugType; import me.lokka30.levelledmobs.misc.LivingEntityWrapper; @@ -371,9 +372,23 @@ else if (dropBase instanceof CustomCommand) { boolean didNotMakeChance = false; float chanceRole = 0.0F; + if (!info.equippedOnly && dropBase.useChunkKillMax && info.wasKilledByPlayer && hasReachedChunkKillLimit(info.lmEntity)) { + if (isCustomDropsDebuggingEnabled()) { + if (dropBase instanceof CustomDropItem) { + info.addDebugMessage(String.format("&8- &7level: &b%s&7, item: &b%s&7, gId: &b%s&7, chunk kill count reached", + info.lmEntity.getMobLevel(), ((CustomDropItem) dropBase).getMaterial().name(), dropBase.groupId)); + } else { + info.addDebugMessage(String.format("&8- &7level: &b%s&7, item: custom command, gId: &b%s&7, chunk kill count reached", + info.lmEntity.getMobLevel(), dropBase.groupId)); + } + } + return; + } + if (!info.equippedOnly && dropBase.chance < 1.0){ chanceRole = (float) ThreadLocalRandom.current().nextInt(0, 100001) * 0.00001F; - if (1.0F - chanceRole >= dropBase.chance) didNotMakeChance = true; + if (1.0F - chanceRole >= dropBase.chance) + didNotMakeChance = true; } if (didNotMakeChance && !info.equippedOnly && isCustomDropsDebuggingEnabled()) { @@ -498,9 +513,9 @@ else if (dropBase instanceof CustomCommand) { if (meta != null && dropItem.lore != null && !dropItem.lore.isEmpty()){ final List newLore = new ArrayList<>(dropItem.lore.size()); for (final String lore : dropItem.lore){ - newLore.add(main.levelManager.updateNametag(info.lmEntity, lore, false)); + newLore.add(main.levelManager.updateNametag(info.lmEntity, lore, false, false)); - if (VersionUtils.isRunningPaper()) + if (VersionUtils.isRunningPaper() && main.companion.useAdventure) PaperUtils.updateItemMetaLore(meta, newLore); else SpigotUtils.updateItemMetaLore(meta, newLore); @@ -508,11 +523,12 @@ else if (dropBase instanceof CustomCommand) { } if (meta != null && dropItem.customName != null && !dropItem.customName.isEmpty()) { - final String displayName = MessageUtils.colorizeAll(main.levelManager.updateNametag(info.lmEntity, dropItem.customName, false)); - if (VersionUtils.isRunningPaper()) + final String displayName = main.levelManager.updateNametag(info.lmEntity, dropItem.customName, false, false); + + if (VersionUtils.isRunningPaper() && main.companion.useAdventure) PaperUtils.updateItemDisplayName(meta, displayName); else - SpigotUtils.updateItemDisplayName(meta, displayName); + SpigotUtils.updateItemDisplayName(meta, MessageUtils.colorizeAll(displayName)); } newItem.setItemMeta(meta); @@ -533,6 +549,11 @@ else if (dropBase instanceof CustomCommand) { info.stackToItem.put(newItem, dropItem); } + private boolean hasReachedChunkKillLimit(final @NotNull LivingEntityWrapper lmEntity){ + final int maximumDeathInChunkThreshold = main.rulesManager.getMaximumDeathInChunkThreshold(lmEntity); + return lmEntity.chunkKillcount >= maximumDeathInChunkThreshold; + } + private boolean shouldDenyDeathCause(final @NotNull CustomDropBase dropBase, final @NotNull CustomDropProcessingInfo info){ if (dropBase.causeOfDeathReqs == null || info.deathCause == null) return false; @@ -595,7 +616,6 @@ private boolean checkIfMadeEquippedDropChance(final CustomDropProcessingInfo inf if (item.equippedSpawnChance >= 1.0F || !item.onlyDropIfEquipped) return true; if (item.equippedSpawnChance <= 0.0F) return false; - return isMobWearingItem(item.getItemStack(), info.lmEntity.getLivingEntity(), item); } @@ -658,8 +678,8 @@ private boolean madePlayerLevelRequirement(final @NotNull CustomDropProcessingIn info.playerLevelVariableCache.put(variableToUse, levelToUse); } - if (dropBase.minPlayerLevel > -1 && levelToUse < dropBase.minPlayerLevel || - dropBase.maxPlayerLevel > -1 && levelToUse > dropBase.maxPlayerLevel){ + if (dropBase.minPlayerLevel > 0 && levelToUse < dropBase.minPlayerLevel || + dropBase.maxPlayerLevel > 0 && levelToUse > dropBase.maxPlayerLevel){ if (isCustomDropsDebuggingEnabled()){ if (dropBase instanceof CustomDropItem) { info.addDebugMessage(String.format( @@ -688,7 +708,9 @@ private void executeCommand(@NotNull final CustomCommand customCommand, @NotNull command = Utils.replaceEx(command, "%player%", playerName); command = processRangedCommand(command, customCommand); - command = main.levelManager.updateNametag(info.lmEntity, command,false); + command = main.levelManager.replaceStringPlaceholders(command, info.lmEntity, true, false); + if (command.contains("%") && ExternalCompatibilityManager.hasPAPI_Installed()) + command = ExternalCompatibilityManager.getPAPI_Placeholder(info.mobKiller, command); final int maxAllowedTimesToRun = ymlHelper.getInt(main.settingsCfg, "customcommand-amount-limit", 10); int timesToRun = customCommand.getAmount(); @@ -726,14 +748,16 @@ private void executeTheCommand(final String command, final int timesToRun){ } @NotNull - private String processRangedCommand(@NotNull String command, final @NotNull CustomCommand cc){ + private String processRangedCommand(final @NotNull String command, final @NotNull CustomCommand cc){ if (cc.rangedEntries.isEmpty()) return command; + String newCommand = command; + for (final Map.Entry rangeds : cc.rangedEntries.entrySet()) { final String rangedKey = rangeds.getKey(); final String rangedValue = rangeds.getValue(); if (!rangedValue.contains("-")) { - command = command.replace("%" + rangedKey + "%", rangedValue); + newCommand = newCommand.replace("%" + rangedKey + "%", rangedValue); continue; } @@ -746,10 +770,10 @@ private String processRangedCommand(@NotNull String command, final @NotNull Cust if (max < min) min = max; final int rangedNum = main.random.nextInt(max - min + 1) + min; - command = command.replace("%" + rangedKey + "%", String.valueOf(rangedNum)); + newCommand = newCommand.replace("%" + rangedKey + "%", String.valueOf(rangedNum)); } - return command; + return newCommand; } private ItemStack getCookedVariantOfMeat(@NotNull final ItemStack itemStack){ diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsParser.java b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsParser.java index 693740cd7..e9758aeb2 100644 --- a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsParser.java +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsParser.java @@ -11,12 +11,9 @@ import me.lokka30.levelledmobs.misc.CustomUniversalGroups; import me.lokka30.levelledmobs.misc.DebugType; import me.lokka30.levelledmobs.misc.NBTApplyResult; -import me.lokka30.levelledmobs.misc.PaperUtils; -import me.lokka30.levelledmobs.misc.SpigotUtils; import me.lokka30.levelledmobs.misc.Utils; import me.lokka30.levelledmobs.misc.YmlParsingHelper; import me.lokka30.levelledmobs.rules.RuleInfo; -import me.lokka30.microlib.other.VersionUtils; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.configuration.ConfigurationSection; @@ -340,6 +337,7 @@ else if (itemEntry.getValue() instanceof ArrayList) private void parseCustomDropsAttributes(@NotNull final CustomDropBase dropBase, @NotNull final ConfigurationSection cs, final @NotNull CustomDropInstance dropInstance){ dropBase.chance = ymlHelper.getFloat(cs, "chance", this.defaults.chance); + dropBase.useChunkKillMax = ymlHelper.getBoolean(cs, "use-chunk-kill-max", this.defaults.useChunkKillMax); dropBase.permissions.addAll(this.defaults.permissions); dropBase.permissions.addAll(ymlHelper.getStringSet(cs, "permission")); dropBase.minLevel = ymlHelper.getInt(cs,"minlevel", this.defaults.minLevel); @@ -555,26 +553,6 @@ private void applyMetaAttributes(@NotNull final CustomDropItem item){ boolean madeChanges = false; - if (item.lore != null && !item.lore.isEmpty()){ - if (VersionUtils.isRunningPaper()) - PaperUtils.updateItemMetaLore(meta, item.lore); - else - SpigotUtils.updateItemMetaLore(meta, item.lore); - - item.getItemStack().setItemMeta(meta); - madeChanges = true; - } - - if (item.customName != null && !item.customName.isEmpty()){ - if (VersionUtils.isRunningPaper()) - PaperUtils.updateItemDisplayName(meta, item.customName); - else - SpigotUtils.updateItemDisplayName(meta, item.customName); - - item.getItemStack().setItemMeta(meta); - madeChanges = true; - } - if (item.customModelDataId != this.defaults.customModelData){ meta.setCustomModelData(item.customModelDataId); item.getItemStack().setItemMeta(meta); diff --git a/src/main/java/me/lokka30/levelledmobs/listeners/EntityDeathListener.java b/src/main/java/me/lokka30/levelledmobs/listeners/EntityDeathListener.java index 86719fb9c..8fd5c3e3b 100644 --- a/src/main/java/me/lokka30/levelledmobs/listeners/EntityDeathListener.java +++ b/src/main/java/me/lokka30/levelledmobs/listeners/EntityDeathListener.java @@ -6,8 +6,16 @@ import me.lokka30.levelledmobs.LevelledMobs; import me.lokka30.levelledmobs.customdrops.CustomDropResult; +import me.lokka30.levelledmobs.misc.AdjacentChunksResult; +import me.lokka30.levelledmobs.misc.ChunkKillInfo; +import me.lokka30.levelledmobs.misc.DebugType; import me.lokka30.levelledmobs.misc.LivingEntityWrapper; import me.lokka30.levelledmobs.misc.NametagTimerChecker; +import me.lokka30.levelledmobs.misc.Utils; +import me.lokka30.microlib.messaging.MessageUtils; +import org.bukkit.Chunk; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -15,11 +23,13 @@ import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.HashSet; +import java.time.Instant; import java.util.LinkedList; import java.util.List; +import java.util.Map; + /** * Listens for when an entity dies so it's drops can be multiplied, manipulated, etc @@ -35,18 +45,18 @@ public EntityDeathListener(final LevelledMobs main) { this.main = main; } - // These entities will be forced not to have levelled drops - private final HashSet bypassDrops = new HashSet<>(Arrays.asList("ARMOR_STAND", "ITEM_FRAME", "DROPPED_ITEM", "PAINTING")); + // These entity types will be forced not to be processed + private final List bypassEntity = List.of(EntityType.ARMOR_STAND, EntityType.ITEM_FRAME, EntityType.DROPPED_ITEM, EntityType.PAINTING); @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) public void onDeath(@NotNull final EntityDeathEvent event) { - synchronized (NametagTimerChecker.entityTarget_Lock){ + if (event.getEntity() instanceof Player) return; + if (bypassEntity.contains(event.getEntityType())) return; + + synchronized (NametagTimerChecker.entityTarget_Lock) { main.nametagTimerChecker.entityTargetMap.remove(event.getEntity()); } - if (bypassDrops.contains(event.getEntityType().toString())) - return; - final LivingEntityWrapper lmEntity = LivingEntityWrapper.getInstance(event.getEntity(), main); if (event.getEntity().getKiller() != null) lmEntity.playerForPermissionsCheck = event.getEntity().getKiller(); @@ -58,6 +68,13 @@ public void onDeath(@NotNull final EntityDeathEvent event) { if (lmEntity.getLivingEntity().getKiller() != null && main.placeholderApiIntegration != null) main.placeholderApiIntegration.putPlayerOrMobDeath(lmEntity.getLivingEntity().getKiller(), lmEntity); + if (lmEntity.isLevelled() && lmEntity.getLivingEntity().getKiller() != null && main.rulesManager.getMaximumDeathInChunkThreshold(lmEntity) > 0) { + + // Only counts if mob is killed by player + if (hasReachedEntityDeathChunkMax(lmEntity, lmEntity.getLivingEntity().getKiller()) && main.rulesManager.disableVanillaDropsOnChunkMax(lmEntity)) + event.getDrops().clear(); + } + if (lmEntity.isLevelled()) { // Set levelled item drops @@ -75,6 +92,76 @@ public void onDeath(@NotNull final EntityDeathEvent event) { event.getDrops().addAll(drops); } + lmEntity.free(); } -} + + private boolean hasReachedEntityDeathChunkMax(final @NotNull LivingEntityWrapper lmEntity, final @NotNull Player player) { + final long chunkKey = Utils.getChunkKey(lmEntity.getLocation().getChunk()); + final Map pairList = main.companion.getorAddPairForSpecifiedChunk(chunkKey); + int numberOfEntityDeathInChunk = pairList.containsKey(lmEntity.getEntityType()) ? + pairList.get(lmEntity.getEntityType()).getCount() : 0; + + final AdjacentChunksResult adjacentChunksResult = getNumberOfEntityDeathsInAdjacentChunks(lmEntity); + if (adjacentChunksResult != null) { + numberOfEntityDeathInChunk += adjacentChunksResult.entities; + adjacentChunksResult.chunkKeys.add(chunkKey); + } + + lmEntity.chunkKillcount = numberOfEntityDeathInChunk; + final int maximumDeathInChunkThreshold = main.rulesManager.getMaximumDeathInChunkThreshold(lmEntity); + final int maxCooldownTime = main.rulesManager.getMaxChunkCooldownTime(lmEntity); + + if (numberOfEntityDeathInChunk < maximumDeathInChunkThreshold) { + final ChunkKillInfo chunkKillInfo = pairList.computeIfAbsent(lmEntity.getEntityType(), k -> new ChunkKillInfo()); + chunkKillInfo.entityCounts.put(Instant.now(), maxCooldownTime); + return false; + } + + if (main.helperSettings.getBoolean(main.settingsCfg, "exceed-kill-in-chunk-message", true)) { + final List chunkKeys = adjacentChunksResult != null ? + adjacentChunksResult.chunkKeys : List.of(chunkKey); + if (main.companion.doesUserHaveCooldown(chunkKeys, player.getUniqueId())) + return true; + + Utils.debugLog(main, DebugType.CHUNK_KILL_COUNT, String.format("%s: player: %s, entity: %s, reached chunk kill limit, max: %s", + Utils.displayChunkLocation(lmEntity.getLocation()), player.getName(), lmEntity.getTypeName(), maximumDeathInChunkThreshold)); + + final String prefix = main.configUtils.getPrefix(); + final String msg = main.messagesCfg.getString("other.no-drop-in-chunk"); + + if (msg != null) + player.sendMessage(MessageUtils.colorizeAll(msg.replace("%prefix%", prefix))); + + main.companion.addUserCooldown(chunkKeys, player.getUniqueId()); + } + + return true; + } + + @Nullable + private AdjacentChunksResult getNumberOfEntityDeathsInAdjacentChunks(final @NotNull LivingEntityWrapper lmEntity) { + final int adjacentChunksToCheck = main.rulesManager.getAdjacentChunksToCheck(lmEntity); + if (adjacentChunksToCheck <= 0) return null; + + final Chunk startingChunk = lmEntity.getLocation().getChunk(); + final AdjacentChunksResult result = new AdjacentChunksResult(); + + for (int x = -adjacentChunksToCheck; x < adjacentChunksToCheck; x++){ + for (int z = -adjacentChunksToCheck; z < adjacentChunksToCheck; z++){ + if (x == 0 && z == 0) continue; + + final Chunk chunk = lmEntity.getWorld().getChunkAt(startingChunk.getX() + x, startingChunk.getZ() + z); + if (!chunk.isLoaded()) continue; + + result.chunkKeys.add(Utils.getChunkKey(chunk)); + } + } + + final List> pairLists = main.companion.getorAddPairForSpecifiedChunks(result.chunkKeys); + for (final Map pairList : pairLists) + result.entities += pairList.containsKey(lmEntity.getEntityType()) ? pairList.get(lmEntity.getEntityType()).getCount() : 0; + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/me/lokka30/levelledmobs/listeners/EntitySpawnListener.java b/src/main/java/me/lokka30/levelledmobs/listeners/EntitySpawnListener.java index 1e8adc4e0..cf5336949 100644 --- a/src/main/java/me/lokka30/levelledmobs/listeners/EntitySpawnListener.java +++ b/src/main/java/me/lokka30/levelledmobs/listeners/EntitySpawnListener.java @@ -179,11 +179,15 @@ public void run() { runnable.runTaskLater(main, delay); } - private void lmSpawnerSpawn(final LivingEntityWrapper lmEntity, @NotNull final SpawnerSpawnEvent event) { + private void lmSpawnerSpawn(final @NotNull LivingEntityWrapper lmEntity, @NotNull final SpawnerSpawnEvent event) { final CreatureSpawner cs = event.getSpawner(); // mob was spawned from a custom LM spawner - createParticleEffect(cs.getLocation().add(0, 1, 0)); + final Particle useParticle = main.rulesManager.getSpawnerParticle(lmEntity); + final int particleCount = main.rulesManager.getSpawnerParticleCount(lmEntity); + + if (useParticle != null && particleCount > 0) + createParticleEffect(cs.getLocation().add(0.5, 1.0, 0.5), useParticle, particleCount); final Integer minLevel = cs.getPersistentDataContainer().get(main.namespaced_keys.keySpawner_MinLevel, PersistentDataType.INTEGER); final Integer maxLevel = cs.getPersistentDataContainer().get(main.namespaced_keys.keySpawner_MaxLevel, PersistentDataType.INTEGER); @@ -203,6 +207,7 @@ private void lmSpawnerSpawn(final LivingEntityWrapper lmEntity, @NotNull final S } lmEntity.setSourceSpawnerName(spawnerName); + lmEntity.setSpawnReason(LevelledMobSpawnReason.LM_SPAWNER, true); Utils.debugLog(main, DebugType.MOB_SPAWNER, String.format( "Spawned mob from LM spawner: &b%s&7, minLevel:&b %s&7, maxLevel: &b%s&7, generatedLevel: &b%s&b%s", @@ -212,7 +217,7 @@ private void lmSpawnerSpawn(final LivingEntityWrapper lmEntity, @NotNull final S false, true, new HashSet<>(Collections.singletonList(AdditionalLevelInformation.NOT_APPLICABLE))); } - private void createParticleEffect(@NotNull final Location location){ + private void createParticleEffect(@NotNull final Location location, @NotNull final Particle particle, final int count){ final World world = location.getWorld(); if (world == null) return; @@ -220,8 +225,8 @@ private void createParticleEffect(@NotNull final Location location){ @Override public void run() { try { - for (int i = 0; i < 10; i++) { - world.spawnParticle(Particle.SOUL, location, 20, 0, 0, 0, 0.1); + for (int i = 0; i < count; i++) { + world.spawnParticle(particle, location, 20, 0, 0, 0, 0.1); Thread.sleep(50); } } catch (final InterruptedException ignored) { } diff --git a/src/main/java/me/lokka30/levelledmobs/listeners/PlayerJoinListener.java b/src/main/java/me/lokka30/levelledmobs/listeners/PlayerJoinListener.java index 8de86e15d..44c80ae36 100644 --- a/src/main/java/me/lokka30/levelledmobs/listeners/PlayerJoinListener.java +++ b/src/main/java/me/lokka30/levelledmobs/listeners/PlayerJoinListener.java @@ -5,6 +5,7 @@ package me.lokka30.levelledmobs.listeners; import me.lokka30.levelledmobs.LevelledMobs; +import me.lokka30.levelledmobs.misc.FileLoader; import me.lokka30.levelledmobs.misc.LivingEntityWrapper; import me.lokka30.levelledmobs.misc.PlayerQueueItem; import me.lokka30.levelledmobs.misc.Utils; @@ -55,8 +56,12 @@ public void onJoin(@NotNull final PlayerJoinEvent event) { updateNametagsInWorldAsync(event.getPlayer(), event.getPlayer().getWorld().getEntities()); - if (main.migratedFromPre30 && event.getPlayer().isOp()){ - event.getPlayer().sendMessage(MessageUtils.colorizeStandardCodes("&b&lLevelledMobs: &cWARNING &7You have migrated from an older version. All settings have been reverted. Please edit rules.yml")); + if (event.getPlayer().isOp()){ + if (main.companion.getHadRulesLoadError()) + event.getPlayer().sendMessage(FileLoader.getFileLoadErrorMessage()); + + if (main.migratedFromPre30) + event.getPlayer().sendMessage(MessageUtils.colorizeStandardCodes("&b&lLevelledMobs: &cWARNING &7You have migrated from an older version. All settings have been reverted. Please edit rules.yml")); } } diff --git a/src/main/java/me/lokka30/levelledmobs/listeners/paper/PlayerDeathListener.java b/src/main/java/me/lokka30/levelledmobs/listeners/paper/PlayerDeathListener.java index 3ea3a005c..549968933 100644 --- a/src/main/java/me/lokka30/levelledmobs/listeners/paper/PlayerDeathListener.java +++ b/src/main/java/me/lokka30/levelledmobs/listeners/paper/PlayerDeathListener.java @@ -116,7 +116,7 @@ private String extractPlayerName(final @NotNull TranslatableComponent tc){ } } - return playerKilled != null && playerKilled.isEmpty() ? + return playerKilled == null || playerKilled.isEmpty() ? null : playerKilled; } } diff --git a/src/main/java/me/lokka30/levelledmobs/managers/ExternalCompatibilityManager.java b/src/main/java/me/lokka30/levelledmobs/managers/ExternalCompatibilityManager.java index 6b8ebae0e..d11dd7b7a 100644 --- a/src/main/java/me/lokka30/levelledmobs/managers/ExternalCompatibilityManager.java +++ b/src/main/java/me/lokka30/levelledmobs/managers/ExternalCompatibilityManager.java @@ -81,16 +81,15 @@ private static boolean isExternalCompatibilityEnabled(final ExternalCompatibilit return (!list.containsKey(externalCompatibility) || list.get(externalCompatibility) != null && list.get(externalCompatibility)); } - static boolean hasPAPI_Installed(){ return (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null); } + public static boolean hasPAPI_Installed(){ return (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null); } public static boolean hasNBTAPI_Installed(){ return Bukkit.getPluginManager().getPlugin("NBTAPI") != null; } @NotNull - static String getPAPI_Placeholder(final Player player, final String placeholder){ + public static String getPAPI_Placeholder(final Player player, final String placeholder){ return me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, placeholder); - } public static boolean hasProtocolLibInstalled() { @@ -167,8 +166,12 @@ public static String getMythicMobInternalName(@NotNull final LivingEntityWrapper final Plugin p = Bukkit.getPluginManager().getPlugin("MythicMobs"); if (p == null) return ""; - if (!p.getDescription().getVersion().startsWith("4.12") && !p.getDescription().getVersion().startsWith("5.")) { - // MM version 5 must use this method for internal name detection + final boolean useNamespaceKey = + p.getDescription().getVersion().startsWith("5.") && + !p.getDescription().getVersion().startsWith("5.01") && + !p.getDescription().getVersion().startsWith("5.00"); + + if (useNamespaceKey) { final NamespacedKey mmKey = new NamespacedKey(p, "type"); synchronized (lmEntity.getLivingEntity().getPersistentDataContainer()) { if (lmEntity.getPDC().has(mmKey, PersistentDataType.STRING)) { @@ -179,7 +182,7 @@ public static String getMythicMobInternalName(@NotNull final LivingEntityWrapper } } - // MM version 4 detection below: + // MM version 4, 5.00 and 5.01 detection below: if (!lmEntity.getLivingEntity().hasMetadata("mobname")) return ""; diff --git a/src/main/java/me/lokka30/levelledmobs/managers/LevelManager.java b/src/main/java/me/lokka30/levelledmobs/managers/LevelManager.java index 6c0435799..9e0b2889b 100644 --- a/src/main/java/me/lokka30/levelledmobs/managers/LevelManager.java +++ b/src/main/java/me/lokka30/levelledmobs/managers/LevelManager.java @@ -564,17 +564,23 @@ public String getNametag(final LivingEntityWrapper lmEntity, final boolean isDea @NotNull public String updateNametag(final LivingEntityWrapper lmEntity, @NotNull String nametag, final boolean useCustomNameForNametags) { + return updateNametag(lmEntity, nametag, useCustomNameForNametags, true); + } + + @NotNull + public String updateNametag(final LivingEntityWrapper lmEntity, @NotNull String nametag, final boolean useCustomNameForNametags, final boolean colorize) { if (nametag.isEmpty()) return nametag; final String overridenName = main.rulesManager.getRule_EntityOverriddenName(lmEntity, useCustomNameForNametags); + String displayName = overridenName == null ? - Utils.capitalize(lmEntity.getTypeName().replaceAll("_", " ")) : - MessageUtils.colorizeAll(overridenName); + Utils.capitalize(lmEntity.getTypeName().replaceAll("_", " ")) : + overridenName; if (lmEntity.getLivingEntity().getCustomName() != null && !useCustomNameForNametags) displayName = lmEntity.getLivingEntity().getCustomName(); - nametag = replaceStringPlaceholders(nametag, lmEntity); + nametag = replaceStringPlaceholders(nametag, lmEntity, colorize); // This is after colorize so that color codes in nametags dont get translated nametag = nametag.replace("%displayname%", displayName); @@ -645,6 +651,14 @@ else if (indicator.tiers.containsKey(0)) } public String replaceStringPlaceholders(final String nametag, @NotNull final LivingEntityWrapper lmEntity){ + return replaceStringPlaceholders(nametag, lmEntity, true); + } + + public String replaceStringPlaceholders(final String nametag, @NotNull final LivingEntityWrapper lmEntity, final boolean colorize){ + return replaceStringPlaceholders(nametag, lmEntity, colorize, true); + } + + public String replaceStringPlaceholders(final String nametag, @NotNull final LivingEntityWrapper lmEntity, final boolean colorize, final boolean usePAPI){ String result = nametag; final double maxHealth = getMobAttributeValue(lmEntity); @@ -681,10 +695,11 @@ public String replaceStringPlaceholders(final String nametag, @NotNull final Liv result = result.replace("%y%", String.valueOf(lmEntity.getLivingEntity().getLocation().getBlockY())); result = result.replace("%z%", String.valueOf(lmEntity.getLivingEntity().getLocation().getBlockZ())); - if (result.contains("%") && ExternalCompatibilityManager.hasPAPI_Installed()) + if (usePAPI && result.contains("%") && ExternalCompatibilityManager.hasPAPI_Installed()) result = ExternalCompatibilityManager.getPAPI_Placeholder(null, result); - result = MessageUtils.colorizeAll(result); + if (colorize) + result = MessageUtils.colorizeAll(result); return result; } @@ -704,9 +719,13 @@ public void run() { } public void updateNametag(final LivingEntityWrapper lmEntity){ + String nametag = getNametag(lmEntity, false); + if (nametag != null) + nametag = MessageUtils.colorizeAll(nametag); + final QueueItem queueItem = new QueueItem( lmEntity, - getNametag(lmEntity, false), + nametag, lmEntity.getLivingEntity().getWorld().getPlayers() ); @@ -905,7 +924,9 @@ private void checkLevelledEntity(@NotNull final LivingEntityWrapper lmEntity, @N location.getWorld().equals(lmEntity.getWorld()) && lmEntity.getLocation().distanceSquared(location) <= maxDistance) { //if within distance, update nametag. - main.nametagQueueManager_.addToQueue(new QueueItem(lmEntity, main.levelManager.getNametag(lmEntity, false), Collections.singletonList(player))); + String nametag = main.levelManager.getNametag(lmEntity, false); + if (nametag != null) nametag = MessageUtils.colorizeAll(nametag); + main.nametagQueueManager_.addToQueue(new QueueItem(lmEntity, nametag, Collections.singletonList(player))); } } @@ -963,8 +984,11 @@ private void applyLevelledAttributes(@NotNull final LivingEntityWrapper lmEntity case ATTRIBUTE_KNOCKBACK_RESISTANCE: attribute = Attribute.GENERIC_KNOCKBACK_RESISTANCE; break; case ATTRIBUTE_FLYING_SPEED: attribute = Attribute.GENERIC_FLYING_SPEED; break; case ATTRIBUTE_ATTACK_KNOCKBACK: attribute = Attribute.GENERIC_ATTACK_KNOCKBACK; break; - case ATTRIBUTE_ZOMBIE_SPAWN_REINFORCEMENTS: attribute = Attribute.ZOMBIE_SPAWN_REINFORCEMENTS; break; case ATTRIBUTE_FOLLOW_RANGE: attribute = Attribute.GENERIC_FOLLOW_RANGE; break; + case ATTRIBUTE_ZOMBIE_SPAWN_REINFORCEMENTS: + if (lmEntity.getSpawnReason() == LevelledMobSpawnReason.REINFORCEMENTS) return; + attribute = Attribute.ZOMBIE_SPAWN_REINFORCEMENTS; + break; default: throw new IllegalStateException("Addition must be an Attribute, if so, it has not been considered in this method"); @@ -1183,6 +1207,7 @@ public void applyLevelToMob(@NotNull final LivingEntityWrapper lmEntity, int lev } if (isSummoned) { + lmEntity.setSpawnReason(LevelledMobSpawnReason.LM_SUMMON, true); final SummonedMobPreLevelEvent summonedMobPreLevelEvent = new SummonedMobPreLevelEvent(lmEntity.getLivingEntity(), level); Bukkit.getPluginManager().callEvent(summonedMobPreLevelEvent); @@ -1200,9 +1225,11 @@ public void applyLevelToMob(@NotNull final LivingEntityWrapper lmEntity, int lev } } - final boolean hasNoLevelKey; - synchronized (lmEntity.getLivingEntity().getPersistentDataContainer()) { - hasNoLevelKey = lmEntity.getPDC().has(main.namespaced_keys.noLevelKey, PersistentDataType.STRING); + boolean hasNoLevelKey = false; + if (!isSummoned) { + synchronized (lmEntity.getLivingEntity().getPersistentDataContainer()) { + hasNoLevelKey = lmEntity.getPDC().has(main.namespaced_keys.noLevelKey, PersistentDataType.STRING); + } } if (hasNoLevelKey) { diff --git a/src/main/java/me/lokka30/levelledmobs/managers/MobHeadManager.java b/src/main/java/me/lokka30/levelledmobs/managers/MobHeadManager.java index 9d01f6603..70153deed 100644 --- a/src/main/java/me/lokka30/levelledmobs/managers/MobHeadManager.java +++ b/src/main/java/me/lokka30/levelledmobs/managers/MobHeadManager.java @@ -177,7 +177,7 @@ public ItemStack getMobHeadFromPlayerHead(final ItemStack playerHead, final Livi } else useName = "Mob Head"; - if (VersionUtils.isRunningPaper()) + if (VersionUtils.isRunningPaper() && main.companion.useAdventure) PaperUtils.updateItemDisplayName(meta, useName); else SpigotUtils.updateItemDisplayName(meta, useName); diff --git a/src/main/java/me/lokka30/levelledmobs/misc/AdjacentChunksResult.java b/src/main/java/me/lokka30/levelledmobs/misc/AdjacentChunksResult.java new file mode 100644 index 000000000..b7481b096 --- /dev/null +++ b/src/main/java/me/lokka30/levelledmobs/misc/AdjacentChunksResult.java @@ -0,0 +1,22 @@ +package me.lokka30.levelledmobs.misc; + +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedList; +import java.util.List; + +/** + * Used in conjunction with the chunk kill count feature + * + * @author stumper66 + * @since 3.4.0 + */ + +public class AdjacentChunksResult { + public AdjacentChunksResult(){ + this.chunkKeys = new LinkedList<>(); + } + + public int entities; + public final @NotNull List chunkKeys; +} diff --git a/src/main/java/me/lokka30/levelledmobs/misc/ChunkKillInfo.java b/src/main/java/me/lokka30/levelledmobs/misc/ChunkKillInfo.java new file mode 100644 index 000000000..0fd1d0276 --- /dev/null +++ b/src/main/java/me/lokka30/levelledmobs/misc/ChunkKillInfo.java @@ -0,0 +1,39 @@ +package me.lokka30.levelledmobs.misc; + +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Records entity deaths for use in the chunk kill max feature + * + * @author stumper66 + * @since 3.4.0 + */ +public class ChunkKillInfo { + public ChunkKillInfo(){ + this.entityCounts = new HashMap<>(); + } + + // timestamp of death, max cooldown time + final public @NotNull Map entityCounts; + + public @NotNull Set> getEntrySet(){ + return entityCounts.entrySet(); + } + + public boolean isEmpty(){ + return this.entityCounts.isEmpty(); + } + + public int getCount(){ + return this.entityCounts.size(); + } + + public String toString(){ + return entityCounts.toString(); + } +} diff --git a/src/main/java/me/lokka30/levelledmobs/misc/DebugCreator.java b/src/main/java/me/lokka30/levelledmobs/misc/DebugCreator.java index 5774e6b16..794d94e7f 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/DebugCreator.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/DebugCreator.java @@ -19,6 +19,13 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +/** + * Creates debug.zip for troubleshooting purposes + * + * @author stumper66 + * @since 3.2.0 + */ + public class DebugCreator { public static void createDebug(final @NotNull LevelledMobs main, final CommandSender sender){ final String pluginDir = main.getDataFolder().getAbsolutePath(); diff --git a/src/main/java/me/lokka30/levelledmobs/misc/DebugType.java b/src/main/java/me/lokka30/levelledmobs/misc/DebugType.java index c7c3e4cc2..8f603462e 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/DebugType.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/DebugType.java @@ -139,5 +139,7 @@ public enum DebugType { SCOREBOARD_TAGS, - SKYLIGHT_LEVEL + SKYLIGHT_LEVEL, + + CHUNK_KILL_COUNT } diff --git a/src/main/java/me/lokka30/levelledmobs/misc/FileLoader.java b/src/main/java/me/lokka30/levelledmobs/misc/FileLoader.java index 01bb17f7c..44e53fd3b 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/FileLoader.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/FileLoader.java @@ -4,6 +4,7 @@ package me.lokka30.levelledmobs.misc; +import me.lokka30.microlib.messaging.MessageUtils; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.Plugin; import org.bukkit.util.FileUtil; @@ -22,10 +23,10 @@ */ public final class FileLoader { - public static final int SETTINGS_FILE_VERSION = 32; // Last changed: v3.1.5 b503 - public static final int MESSAGES_FILE_VERSION = 7; // Last changed: v3.3.0 b567 + public static final int SETTINGS_FILE_VERSION = 33; // Last changed: v3.4.0 b621 + public static final int MESSAGES_FILE_VERSION = 8; // Last changed: v3.4.0 b621 public static final int CUSTOMDROPS_FILE_VERSION = 10; // Last changed: v3.1.0 b474 - public static final int RULES_FILE_VERSION = 2; // Last changed: v3.2.0 b529 + public static final int RULES_FILE_VERSION = 3; // Last changed: v3.4.0 b621 private FileLoader() { throw new UnsupportedOperationException(); @@ -43,7 +44,17 @@ public static YamlConfiguration loadFile(@NotNull final Plugin plugin, String cf try (final FileInputStream fs = new FileInputStream(file)) { new Yaml().load(fs); } catch (final Exception e) { - Utils.logger.error("&4Error reading " + cfgName + ". " + e.getMessage()); + final String parseErrorMessage = "LevelledMobs was unable to read file &b%s&r due to a user-caused YAML syntax error.\n" + + "Please copy the contents of your file into a YAML Parser website, such as &b(https://tinyurl.com/yamlp)&r to help locate the line of the mistake.\n" + + "Failure to resolve this issue will cause LevelledMobs to function improperly, or likely not at all.\n" + + "Below represents where LevelledMobs became confused while attempting to read your file:\n" + + "&b---- START ERROR ----&r\n" + + "&4%s&r\n" + + "&b---- END ERROR ----&r\n" + + "If you have attempted to resolve this issue yourself, and are unable to, then please &b#create-a-ticket&r in the Official Arcane Plugins Support Discord:\n" + + "&bhttps://discord.io/arcaneplugins"; + + Utils.logger.error(String.format(parseErrorMessage, cfgName, e)); return null; } @@ -55,7 +66,8 @@ public static YamlConfiguration loadFile(@NotNull final Plugin plugin, String cf final boolean isCustomDrops = cfgName.equals("customdrops.yml"); final boolean isRules = cfgName.equals("rules.yml"); - if (fileVersion < compatibleVersion) { + // not migrating rules version 2 or newer + if ((!isRules || fileVersion < 2) && fileVersion < compatibleVersion) { final File backedupFile = new File(plugin.getDataFolder(), cfgName + ".v" + fileVersion + ".old"); // copy to old file @@ -84,6 +96,12 @@ else if (!isRules) return cfg; } + public static @NotNull String getFileLoadErrorMessage(){ + return MessageUtils.colorizeStandardCodes( + "&4An error occured&r whilst attempting to parse the file &brules.yml&r due to a user-caused YAML syntax error. Please see the console logs for more details." + ); + } + public static void saveResourceIfNotExists(final Plugin instance, @NotNull final File file) { if (!file.exists()) { Utils.logger.info("&fFile Loader: &7File '&b" + file.getName() + "&7' doesn't exist, creating it now..."); diff --git a/src/main/java/me/lokka30/levelledmobs/misc/FileMigrator.java b/src/main/java/me/lokka30/levelledmobs/misc/FileMigrator.java index fe76b1237..517cc1ce7 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/FileMigrator.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/FileMigrator.java @@ -395,6 +395,10 @@ static void copyYmlValues(final File from, @NotNull final File to, final int old "command.levelledmobs.spawner.spawner-give-message" ); + final List messagesExempt_v7 = List.of( + "command.levelledmobs.rules.reset" + ); + final String useCustomDrops = "use-custom-item-drops-for-mobs"; @@ -437,6 +441,7 @@ else if (currentKey.size() > depth) { if (isSettings && oldVersion <= 20 && !version20KeysToKeep.contains(key)) continue; if (isMessages && oldVersion <= 5 && messagesExempt_v5.contains(key)) continue; + if (isMessages && oldVersion <= 7 && messagesExempt_v7.contains(key)) continue; if (oldConfigMap.containsKey(oldKey) && newConfigMap.containsKey(key)) { final FileMigrator.FieldInfo fiOld = oldConfigMap.get(oldKey); @@ -489,6 +494,7 @@ else if (currentKey.size() > depth) { if (isSettings && oldVersion > 24 && oldVersion <= 26 && version26Resets.contains(key)) continue; if (isMessages && oldVersion <= 5 && messagesExempt_v5.contains(key)) continue; + if (isMessages && oldVersion <= 7 && messagesExempt_v7.contains(key)) continue; if (key.toLowerCase().startsWith("file-version")) continue; if (isSettings && key.equalsIgnoreCase("creature-nametag") && oldVersion > 20 && oldVersion < 26 && migratedValue.equals("'&8[&7Level %level%&8 | &f%displayname%&8 | &c%health%&8/&c%max_health% %heart_symbol%&8]'")) { @@ -517,6 +523,7 @@ else if (currentKey.size() > depth) { if (isSettings && oldVersion > 20 && oldVersion <= 24 && version24Resets.contains(oldValue)) continue; if (isSettings && oldVersion > 24 && oldVersion <= 26 && version26Resets.contains(oldValue)) continue; if (isMessages && oldVersion <= 5 && messagesExempt_v5.contains(key)) continue; + if (isMessages && oldVersion <= 7 && messagesExempt_v7.contains(key)) continue; final FileMigrator.FieldInfo fiOld = entry.getValue(); if (fiOld.isList()) continue; @@ -544,6 +551,7 @@ else if (currentKey.size() > depth) { final String value = line.trim().substring(1).trim(); if (isMessages && oldVersion <= 5 && messagesExempt_v5.contains(key)) continue; + if (isMessages && oldVersion <= 7 && messagesExempt_v7.contains(key)) continue; // we have an array value present in the new config but not the old, so it must've been removed if (oldConfigMap.containsKey(key) && oldConfigMap.get(key).isList() && !oldConfigMap.get(key).valueList.contains(value)) { diff --git a/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityPlaceHolder.java b/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityPlaceHolder.java index 1170fb76a..d83c3c803 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityPlaceHolder.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityPlaceHolder.java @@ -6,7 +6,6 @@ import me.lokka30.levelledmobs.LevelledMobs; import me.lokka30.levelledmobs.LivingEntityInterface; -import me.lokka30.levelledmobs.rules.ApplicableRulesResult; import me.lokka30.levelledmobs.rules.RuleInfo; import org.bukkit.Location; import org.bukkit.World; @@ -113,4 +112,12 @@ public int getSpawnedTimeOfDay(){ return result; } + + public Integer getSummonedLevel() { + return summonedLevel; + } + + public boolean isWasSummoned(){ + return summonedLevel != null; + } } diff --git a/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapper.java b/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapper.java index 9533355a5..e76b8b9db 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapper.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapper.java @@ -83,6 +83,8 @@ public LivingEntityWrapper(final @NotNull LivingEntity livingEntity, final @NotN private boolean hasCache; private boolean isBuildingCache; private boolean groupsAreBuilt; + private boolean wasSummoned; + public int chunkKillcount; private Integer mobLevel; private Integer skylightLevelAtSpawn; private int nametagCooldownTime; @@ -151,6 +153,7 @@ private void setLivingEntity(final @NotNull LivingEntity livingEntity){ public void clearEntityData(){ this.livingEntity = null; + this.chunkKillcount = 0; this.applicableGroups.clear(); this.applicableRules.clear(); this.mobExternalTypes.clear(); @@ -177,6 +180,7 @@ public void clearEntityData(){ this.playerLevellingAllowDecrease = null; this.pendingPlayerIdToSet = null; this.skylightLevelAtSpawn = null; + this.wasSummoned = false; super.clearEntityData(); } @@ -195,6 +199,11 @@ private void buildCache(){ this.mobLevel = main.levelInterface.isLevelled(livingEntity) ? main.levelInterface.getLevelOfMob(livingEntity) : null; + try{ + this.wasSummoned = getPDC().has(main.namespaced_keys.wasSummoned, PersistentDataType.INTEGER); + } + catch (Exception ignored){ } + this.spawnedWGRegions = ExternalCompatibilityManager.getWGRegionsAtLocation(this); this.hasCache = true; @@ -531,12 +540,16 @@ private int getCurrentSkyLightLevel(){ } public void setSpawnReason(final LevelledMobSpawnReason spawnReason) { + setSpawnReason(spawnReason, false); + } + + public void setSpawnReason(final LevelledMobSpawnReason spawnReason, final boolean doForce) { this.spawnReason = spawnReason; if (!getPDCLock()) return; try { - if (!livingEntity.getPersistentDataContainer().has(main.namespaced_keys.spawnReasonKey, PersistentDataType.STRING)) { + if (doForce || !livingEntity.getPersistentDataContainer().has(main.namespaced_keys.spawnReasonKey, PersistentDataType.STRING)) { livingEntity.getPersistentDataContainer().set(main.namespaced_keys.spawnReasonKey, PersistentDataType.STRING, spawnReason.toString()); } } @@ -730,6 +743,16 @@ public int getSpawnedTimeOfDay(){ return result; } + public Integer getSummonedLevel() { + return summonedLevel; + } + + public boolean isWasSummoned(){ + if (!hasCache) buildCache(); + + return this.wasSummoned; + } + @NotNull private Set buildApplicableGroupsForMob(){ final Set groups = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); diff --git a/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapperBase.java b/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapperBase.java index 65a22c178..ca306e388 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapperBase.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapperBase.java @@ -30,6 +30,7 @@ public class LivingEntityWrapperBase { private Location location; @NotNull final LevelledMobs main; + Integer summonedLevel; Integer spawnedTimeOfDay; private boolean isPopulated; public final AtomicInteger inUseCount; @@ -47,6 +48,7 @@ void clearEntityData(){ this.spawnedTimeOfDay = null; this.inUseCount.set(0); this.isPopulated = false; + this.summonedLevel = null; } @SuppressWarnings("BooleanMethodIsAlwaysInverted") @@ -89,4 +91,8 @@ public String getWorldName(){ return this.world.getName(); } + + public void setSummonedLevel(final Integer summonedLevel){ + this.summonedLevel = summonedLevel; + } } diff --git a/src/main/java/me/lokka30/levelledmobs/misc/PaperUtils.java b/src/main/java/me/lokka30/levelledmobs/misc/PaperUtils.java index 9f73a5549..0321a69b3 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/PaperUtils.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/PaperUtils.java @@ -4,6 +4,8 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.inventory.meta.ItemMeta; @@ -31,7 +33,8 @@ public static void updateItemMetaLore(final @NotNull ItemMeta meta, final @Nulla final List newLore = new ArrayList<>(lore.size()); for (final String loreLine : lore) - newLore.add(Component.text().content(loreLine).build()); + newLore.add(Component.text().decoration(TextDecoration.ITALIC, false).append( + LegacyComponentSerializer.legacyAmpersand().deserialize(loreLine)).build()); meta.lore(newLore); } @@ -39,7 +42,8 @@ public static void updateItemMetaLore(final @NotNull ItemMeta meta, final @Nulla public static void updateItemDisplayName(final @NotNull ItemMeta meta, final @Nullable String displayName){ if (displayName == null) return; - meta.displayName(Component.text().content(displayName).build()); + meta.displayName(Component.text().decoration(TextDecoration.ITALIC, false).append( + LegacyComponentSerializer.legacyAmpersand().deserialize(displayName)).build()); } @NotNull diff --git a/src/main/java/me/lokka30/levelledmobs/misc/SpigotUtils.java b/src/main/java/me/lokka30/levelledmobs/misc/SpigotUtils.java index 422872dff..1832959ee 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/SpigotUtils.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/SpigotUtils.java @@ -37,7 +37,10 @@ public static void sendHyperlink(final @NotNull CommandSender sender, final Stri } public static void updateItemMetaLore(final @NotNull ItemMeta meta, final @Nullable List lore){ - meta.setLore(lore); + if (lore == null) + meta.setLore(null); + else + meta.setLore(Utils.colorizeAllInList(lore)); } public static void updateItemDisplayName(final @NotNull ItemMeta meta, final @Nullable String displayName){ diff --git a/src/main/java/me/lokka30/levelledmobs/misc/Utils.java b/src/main/java/me/lokka30/levelledmobs/misc/Utils.java index 03d132ac4..d55a911ae 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/Utils.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/Utils.java @@ -10,11 +10,12 @@ import me.lokka30.levelledmobs.rules.RulesManager; import me.lokka30.microlib.messaging.MessageUtils; import me.lokka30.microlib.messaging.MicroLogger; +import me.lokka30.microlib.other.VersionUtils; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.entity.Player; -import org.bukkit.event.entity.EntityDamageEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -309,4 +310,16 @@ else if (player.getWorld().getEnvironment() == World.Environment.NORMAL){ return new PlayerNetherOrWorldSpawnResult(location, isNetherPortalCoord, isWorldPortalCoord); } + + public static long getChunkKey(final @NotNull Chunk chunk) { + if (VersionUtils.isRunningPaper()) + return chunk.getChunkKey(); + + final int x = chunk.getX() >> 4, z = chunk.getZ() >> 4; + return (long) x & 0xffffffffL | ((long) z & 0xffffffffL) << 32; + } + + public static String displayChunkLocation(final @NotNull Location location){ + return String.format("%s,%s", location.getChunk().getX(), location.getChunk().getZ()); + } } diff --git a/src/main/java/me/lokka30/levelledmobs/rules/FineTuningAttributes.java b/src/main/java/me/lokka30/levelledmobs/rules/FineTuningAttributes.java index c59e6c055..c8a7d372b 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/FineTuningAttributes.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/FineTuningAttributes.java @@ -4,9 +4,10 @@ package me.lokka30.levelledmobs.rules; -import org.bukkit.entity.EntityType; import org.jetbrains.annotations.Nullable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; @@ -36,25 +37,40 @@ public class FineTuningAttributes implements Cloneable { void mergeAttributes(final @Nullable FineTuningAttributes attributes){ if (attributes == null) return; - if (attributes.maxHealth != null) this.maxHealth = attributes.maxHealth; - if (attributes.attackDamage != null) this.attackDamage = attributes.attackDamage; - if (attributes.itemDrop != null) this.itemDrop = attributes.itemDrop; - if (attributes.xpDrop != null) this.xpDrop = attributes.xpDrop; - if (attributes.movementSpeed != null) this.movementSpeed = attributes.movementSpeed; - if (attributes.rangedAttackDamage != null) this.rangedAttackDamage = attributes.rangedAttackDamage; - if (attributes.creeperExplosionRadius != null) this.creeperExplosionRadius = attributes.creeperExplosionRadius; + try { + for (final Field f : attributes.getClass().getDeclaredFields()) { + if (!Modifier.isPublic(f.getModifiers())) continue; + if (f.get(attributes) == null) continue; + final Object presetValue = f.get(attributes); + + if (presetValue instanceof Integer && ((Integer) presetValue == 0)) continue; + if (presetValue instanceof Double && ((Double) presetValue == 0.0)) continue; + + this.getClass().getDeclaredField(f.getName()).set(this, presetValue); + } + } catch (final IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + } } public String toString(){ final StringBuilder sb = new StringBuilder(); final List list = new LinkedList<>(); if (maxHealth != null) list.add("maxHlth: " + maxHealth); - if (attackDamage != null) list.add("attkDamage: " + attackDamage); + if (attackDamage != null) list.add("attkDmg: " + attackDamage); if (itemDrop != null) list.add("itemDrp: " + itemDrop); if (xpDrop != null) list.add("xpDrp: " + xpDrop); if (movementSpeed != null) list.add("moveSpd: " + movementSpeed); if (rangedAttackDamage != null) list.add("rangdAtkDmg: " + rangedAttackDamage); if (creeperExplosionRadius != null) list.add("creeperDmg: " + creeperExplosionRadius); + if (armorBonus != null) list.add("armrBns: " + armorBonus); + if (armorToughness != null) list.add("armrTuf: " + armorToughness); + if (attackKnockback != null) list.add("attkKnbk: " + attackKnockback); + if (flyingSpeed != null) list.add("flySpd: " + flyingSpeed); + if (knockbackResistance != null) list.add("knbkRst: " + knockbackResistance); + if (horseJumpStrength != null) list.add("horseJump: " + horseJumpStrength); + if (zombieReinforcements != null) list.add("zmbRnfrce: " + zombieReinforcements); + if (followRange != null) list.add("flwRng: " + followRange); for (final String item : list){ if (sb.length() > 0) sb.append(", "); diff --git a/src/main/java/me/lokka30/levelledmobs/rules/LevelledMobSpawnReason.java b/src/main/java/me/lokka30/levelledmobs/rules/LevelledMobSpawnReason.java index 91a191c82..e984c4602 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/LevelledMobSpawnReason.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/LevelledMobSpawnReason.java @@ -52,5 +52,6 @@ public enum LevelledMobSpawnReason { COMMAND, CUSTOM, SPELL, + LM_SUMMON, DEFAULT } diff --git a/src/main/java/me/lokka30/levelledmobs/rules/RuleInfo.java b/src/main/java/me/lokka30/levelledmobs/rules/RuleInfo.java index 828e0b8e6..79d4fd689 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/RuleInfo.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/RuleInfo.java @@ -8,6 +8,7 @@ import me.lokka30.levelledmobs.misc.CachedModalList; import me.lokka30.levelledmobs.rules.strategies.LevellingStrategy; import me.lokka30.microlib.messaging.MessageUtils; +import org.bukkit.Particle; import org.bukkit.block.Biome; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,6 +39,8 @@ public RuleInfo(final String id){ private String ruleName; @DoNotMerge boolean ruleIsEnabled; + Boolean disableVanillaDropsOnChunkMax; + boolean useNoSpawnerParticles; Boolean babyMobsInheritAdultSetting; Boolean mobLevelInheritance; public Boolean customDrops_UseForMobs; @@ -48,6 +51,7 @@ public RuleInfo(final String id){ Boolean passengerMatchLevel; @DoNotMerge int rulePriority; + Integer spawnerParticlesCount; Integer maxRandomVariance; Integer creeperMaxDamageRadius; Integer conditions_MinLevel; @@ -60,6 +64,9 @@ public RuleInfo(final String id){ Integer conditions_MinDistanceFromSpawn; Integer conditions_MaxDistanceFromSpawn; Integer nametagVisibleTime; + Integer maximumDeathInChunkThreshold; + Integer chunkMaxCoolDownTime; + Integer maxAdjacentChunks; Float conditions_Chance; Double sunlightBurnAmount; public String nametag; @@ -79,6 +86,7 @@ public RuleInfo(final String id){ public List nametagVisibilityEnum; @NotNull @DoNotMerge public final Map ruleSourceNames; + public Particle spawnerParticle; List tieredColoringInfos; Map enabledExtCompats; MergeableStringList mobNBT_Data; diff --git a/src/main/java/me/lokka30/levelledmobs/rules/RulesManager.java b/src/main/java/me/lokka30/levelledmobs/rules/RulesManager.java index deb00e5cb..65b02f91f 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/RulesManager.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/RulesManager.java @@ -12,6 +12,8 @@ import me.lokka30.levelledmobs.misc.LivingEntityWrapper; import me.lokka30.levelledmobs.misc.Utils; import me.lokka30.levelledmobs.rules.strategies.LevellingStrategy; +import me.lokka30.levelledmobs.rules.strategies.RandomLevellingStrategy; +import org.bukkit.Particle; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.entity.Tameable; @@ -21,6 +23,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.EnumMap; import java.util.LinkedList; import java.util.List; @@ -221,7 +224,9 @@ public LevellingStrategy getRule_LevellingStrategy(@NotNull final LivingEntityWr LevellingStrategy levellingStrategy = null; for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()){ - if (ruleInfo.levellingStrategy != null) { + if (ruleInfo.useRandomLevelling != null && ruleInfo.useRandomLevelling) + levellingStrategy = new RandomLevellingStrategy(); + else if (ruleInfo.levellingStrategy != null) { if (levellingStrategy != null && levellingStrategy.getClass().equals(ruleInfo.levellingStrategy.getClass())) levellingStrategy.mergeRule(ruleInfo.levellingStrategy); else @@ -262,6 +267,9 @@ public MobTamedStatus getRule_MobTamedStatus(@NotNull final LivingEntityWrapper } public int getRule_MobMinLevel(@NotNull final LivingEntityInterface lmInterface) { + if (lmInterface.getSummonedLevel() != null) + return lmInterface.getSummonedLevel(); + int minLevel = 1; for (final RuleInfo ruleInfo : lmInterface.getApplicableRules()) { @@ -273,9 +281,21 @@ public int getRule_MobMinLevel(@NotNull final LivingEntityInterface lmInterface) public int getRule_MobMaxLevel(@NotNull final LivingEntityInterface lmInterface){ int maxLevel = 0; + int firstMaxLevel = -1; for (final RuleInfo ruleInfo : lmInterface.getApplicableRules()) { - if (ruleInfo.restrictions_MaxLevel != null) maxLevel = ruleInfo.restrictions_MaxLevel; + if (ruleInfo.restrictions_MaxLevel != null) { + maxLevel = ruleInfo.restrictions_MaxLevel; + if (firstMaxLevel < 0 && maxLevel > 0) firstMaxLevel = maxLevel; + } + } + + if (maxLevel <= 0 && lmInterface.getSummonedLevel() != null){ + if (maxLevel == 0 && firstMaxLevel > 0) + maxLevel = firstMaxLevel; + + int summonedLevel = lmInterface.getSummonedLevel(); + if (summonedLevel > maxLevel) maxLevel = summonedLevel; } return maxLevel; @@ -351,10 +371,15 @@ public HealthIndicator getRule_NametagIndicator(@NotNull final LivingEntityWrapp public List getRule_CreatureNametagVisbility(@NotNull final LivingEntityWrapper lmEntity){ List result = null; - for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()){ - if (ruleInfo == null) continue; - if (ruleInfo.nametagVisibilityEnum != null) - result = ruleInfo.nametagVisibilityEnum; + try { + for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()) { + if (ruleInfo == null) continue; + if (ruleInfo.nametagVisibilityEnum != null) + result = ruleInfo.nametagVisibilityEnum; + } + } + catch (ConcurrentModificationException e){ + Utils.logger.info("Got ConcurrentModificationException in getRule_CreatureNametagVisbility"); } if (result == null || result.isEmpty()) @@ -489,6 +514,74 @@ else if (lmEntity.getNameIfBaby().equalsIgnoreCase(tier.mobName) && tier.isAppli return allEntities; } + @Nullable + public Particle getSpawnerParticle(final @NotNull LivingEntityWrapper lmEntity){ + Particle result = Particle.SOUL; + + for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()){ + if (ruleInfo.spawnerParticle != null) + result = ruleInfo.spawnerParticle; + else if (ruleInfo.useNoSpawnerParticles) + result = null; + } + + return result; + } + + public int getSpawnerParticleCount(final @NotNull LivingEntityWrapper lmEntity){ + int result = 10; + + for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()){ + if (ruleInfo.spawnerParticlesCount != null) + result = ruleInfo.spawnerParticlesCount; + } + + // max limit of 100 counts which would take 5 seconds to show + if (result > 100) result = 100; + + return result; + } + + public int getMaximumDeathInChunkThreshold(final @NotNull LivingEntityWrapper lmEntity){ + int result = 0; + + for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()) { + if (ruleInfo.maximumDeathInChunkThreshold != null) result = ruleInfo.maximumDeathInChunkThreshold; + } + + return result; + } + + public int getMaxChunkCooldownTime(final @NotNull LivingEntityWrapper lmEntity){ + int result = 0; + + for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()) { + if (ruleInfo.chunkMaxCoolDownTime != null) result = ruleInfo.chunkMaxCoolDownTime; + } + + return result; + } + + public boolean disableVanillaDropsOnChunkMax(final @NotNull LivingEntityWrapper lmEntity){ + boolean result = false; + + for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()) { + if (ruleInfo.disableVanillaDropsOnChunkMax != null) result = ruleInfo.disableVanillaDropsOnChunkMax; + } + + return result; + } + + public int getAdjacentChunksToCheck(final @NotNull LivingEntityWrapper lmEntity){ + int result = 0; + + for (final RuleInfo ruleInfo : lmEntity.getApplicableRules()) { + if (ruleInfo.maxAdjacentChunks != null) result = ruleInfo.maxAdjacentChunks; + } + + return result; + } + @NotNull public ApplicableRulesResult getApplicableRules(final LivingEntityInterface lmInterface){ final ApplicableRulesResult applicableRules = new ApplicableRulesResult(); @@ -595,7 +688,7 @@ private boolean isRuleApplicable_Entity(final LivingEntityWrapper lmEntity, @Not if (checkName == null) checkName = "(none)"; if (!ri.conditions_SpawnegEggNames.isEnabledInList(checkName, lmEntity)) { - Utils.debugLog(main, DebugType.DENIED_RULE_SPAWN_REASON, String.format("&b%s&7, mob: &b%s&7, spawn_egg: &b%s&7", + Utils.debugLog(main, DebugType.DENIED_RULE_SPAWNER_NAME, String.format("&b%s&7, mob: &b%s&7, spawn_egg: &b%s&7", ri.getRuleName(), lmEntity.getNameIfBaby(), checkName)); return false; } @@ -670,7 +763,7 @@ private boolean isRuleApplicable_Entity(final LivingEntityWrapper lmEntity, @Not } } - if (ri.conditions_Worlds != null && !ri.conditions_Worlds.isEnabledInList(lmInterface.getWorld().getName(), null)) { + if (!(lmInterface.isWasSummoned()) && ri.conditions_Worlds != null && !ri.conditions_Worlds.isEnabledInList(lmInterface.getWorld().getName(), null)) { Utils.debugLog(main, DebugType.DENIED_RULE_WORLD_LIST, String.format("&b%s&7, mob: &b%s&7, mob world: &b%s&7", ri.getRuleName(), lmInterface.getTypeName(), lmInterface.getWorld().getName())); return new RuleCheckResult(false); diff --git a/src/main/java/me/lokka30/levelledmobs/rules/RulesParsingManager.java b/src/main/java/me/lokka30/levelledmobs/rules/RulesParsingManager.java index bca5b2a01..ae4fcab23 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/RulesParsingManager.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/RulesParsingManager.java @@ -13,6 +13,7 @@ import me.lokka30.levelledmobs.rules.strategies.RandomLevellingStrategy; import me.lokka30.levelledmobs.rules.strategies.SpawnDistanceStrategy; import me.lokka30.levelledmobs.rules.strategies.YDistanceStrategy; +import org.bukkit.Particle; import org.bukkit.block.Biome; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.MemoryConfiguration; @@ -632,6 +633,14 @@ private void parseApplySettings(final ConfigurationSection cs){ parseNBT_Data(cs); parsingInfo.passengerMatchLevel = ymlHelper.getBoolean2(cs, "passenger-match-level", parsingInfo.passengerMatchLevel); parsingInfo.nametagVisibleTime = ymlHelper.getInt2(cs, "nametag-visible-time", parsingInfo.nametagVisibleTime); + parsingInfo.maximumDeathInChunkThreshold = ymlHelper.getInt2(cs, "maximum-death-in-chunk-threshold", parsingInfo.maximumDeathInChunkThreshold); + parsingInfo.chunkMaxCoolDownTime = ymlHelper.getInt2(cs, "chunk-max-cooldown-seconds", parsingInfo.chunkMaxCoolDownTime); + parsingInfo.disableVanillaDropsOnChunkMax = ymlHelper.getBoolean2(cs, "disable-vanilla-drops-on-chunk-max", parsingInfo.disableVanillaDropsOnChunkMax); + parsingInfo.spawnerParticlesCount = ymlHelper.getInt2(cs, "spawner-particles-count", parsingInfo.spawnerParticlesCount); + parsingInfo.maxAdjacentChunks = ymlHelper.getInt2(cs, "max-adjacent-chunks", parsingInfo.maxAdjacentChunks); + if (parsingInfo.maxAdjacentChunks != null && parsingInfo.maxAdjacentChunks > 10) + parsingInfo.maxAdjacentChunks = 10; + parseSpawnerPartile(ymlHelper.getString(cs, "spawner-particles")); final Set nametagVisibility = ymlHelper.getStringSet(cs, "nametag-visibility-method"); final List nametagVisibilityEnums = new LinkedList<>(); @@ -660,6 +669,23 @@ else if (cs.get(ymlHelper.getKeyNameFromConfig(cs, "creature-nametag-always-visi } } + private void parseSpawnerPartile(final @Nullable String particle){ + if (particle == null) return; + + if ("none".equalsIgnoreCase(particle)) { + parsingInfo.spawnerParticle = null; + parsingInfo.useNoSpawnerParticles = true; + return; + } + + try{ + parsingInfo.spawnerParticle = Particle.valueOf(particle.toUpperCase()); + } + catch (Exception ignored){ + Utils.logger.warning("Invalid value in spawner-particles: " + particle + ", in rule: " + parsingInfo.getRuleName()); + } + } + private void parseNBT_Data(final @Nullable ConfigurationSection cs){ if (cs == null) return; diff --git a/src/main/resources/customdrops.yml b/src/main/resources/customdrops.yml index 6829d69bf..6d1ce97f7 100644 --- a/src/main/resources/customdrops.yml +++ b/src/main/resources/customdrops.yml @@ -38,6 +38,7 @@ # +-------------------------------------------------------------------------------------------LM3 defaults: chance: 0.2 + use-chunk-kill-max: true amount: 1 minLevel: -1 maxLevel: -1 diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index ab003be4f..892f242b7 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -259,8 +259,8 @@ command: rules-reprocessed: - '%prefix% Rules Reprocessed for &b%entitycount%&7 mobs in &b%worldcount%&7 world(s)' reset: - - '%prefix% Running this command will reset your rules to one of 3 defaults.' - - 'You must select if you want easy/normal/hard difficulty.' + - '%prefix% Running this command will reset your rules to one of 4 defaults.' + - 'You must select if you want basic/average/advanced/extreme difficulty.' - 'A backup will be made and your rules.yml reset to default.' resetting: - '%prefix% Resetting rules to %difficulty%' @@ -338,6 +338,7 @@ other: send-on-join: true mob-head-drop-name: '%mob_name%''s head' + no-drop-in-chunk: '%prefix% &7Your levelled mob kill count has reached the maximum for this chunk. You will no longer receive levelled drops from these mobs. Please come back after a while.' create-debug: - '&b&nCreate a Debugging ZIP' - '&7You should only run this command if a LevelledMobs developer has asked you to. It is used to assist users who are experiencing issues with the plugin.' @@ -359,4 +360,4 @@ other: # + DO NOT EDIT BEYOND HERE WITHOUT DEVELOPER APPROVAL +LM3 # + +LM3 # +-------------------------------------------------------------------------------------------LM3 -file-version: 7 +file-version: 8 diff --git a/src/main/resources/predefined/rules_easy.yml b/src/main/resources/predefined/rules_easy.yml deleted file mode 100644 index 961721212..000000000 --- a/src/main/resources/predefined/rules_easy.yml +++ /dev/null @@ -1,473 +0,0 @@ -# _ _ _ _ __ __ _ -# | | _____ _____| | | ___ __| | \/ | ___ | |__ ___ -# | | / _ \ \ / / _ \ | |/ _ \/ _` | |\/| |/ _ \| '_ \/ __| -# | |__| __/\ V / __/ | | __/ (_| | | | | (_) | |_) \__ \ -# |_____\___| \_/ \___|_|_|\___|\__,_|_| |_|\___/|_.__/|___/ -# The Ultimate RPG Levelled Mobs Solution -# -# --------------------------------------------------------------------------------------------LM3 -# + ++ +LM3 -# + Main Developers ++ Need support documentation? +LM3 -# + lokka30 ++ https://github.com/lokka30/LevelledMobs/wiki +LM3 -# + Author and developer ++ +LM3 -# + https://ko-fi.com/lokka30 ++ ArcanePlugins Support Discord +LM3 -# + ++ https://discord.gg/RQ8M4A5 +LM3 -# + ++ +LM3 -# + stumper66 ++ Special thanks: +LM3 -# + Developer since v2 ++ ArcanePlugins developers and testers! +LM3 -# + https://ko-fi.com/penalbuffalo ++ Donators, patrons, and other supporters! +LM3 -# + ++ SpigotMC, PaperMC, ProtocolLib, NBT-API, +LM3 -# + ++ PlaceholderAPI, and bStats! +LM3 -# + UltimaOath ++ +LM3 -# + Docs and configs since v2 ++ Other Contributors: +LM3 -# + ++ Eyrian, iCodinqs, deiphiz, CoolBoy, Esophose, +LM3 -# + ++ 7smile7, Hugo5551, konsolas, Shevchik, +LM3 -# + ++ limzikiki, and Zombie_Striker +LM3 -# + ++ +LM3 -# --------------------------------------------------------------------------------------------LM3 -# _____ _ -# | __ |_ _| |___ ___ -# | -| | | | -_|_ -| -# |__|__|___|_|___|___| -# -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + Section 01 - Custom Entity and Biome Groups +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -mob-groups: - commons: - - ZOMBIE - - SKELETON - - CREEPER - farm: - - CHICKEN - - COW - - SHEEP - - PIG - - -biome-groups: - taigas: - - TAIGA - - TAIGA_MOUNTAINS - - SNOWY_TUNDRA - - SNOWY_TAIGA - - SNOWY_TAIGA_HILLS - - GIANT_TREE_TAIGA - - GIANT_SPRUCE_TAIGA - - GIANT_SPRUCE_TAIGA_HILLS - - GIANT_TREE_TAIGA_HILLS - - SNOWY_TAIGA_MOUNTAINS - flowers: - - FLOWER_FOREST - - SUNFLOWER_PLAINS - -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + Section 02 - Presets +LM3 -# + +LM3 -# + This section comes prebuilt with several sets of PRESETS to use with LevelledMobs! +LM3 -# + Out-of-the-box most of these PRESETS are not enabled, and are only here to both +LM3 -# + demonstrate the system and allow you to quickly change or use many of the most +LM3 -# + popular features LevelledMobs has to offer! If you need help understanding PRESETS, +LM3 -# + read the official LevelledMobs Wiki! +LM3 -# + https://github.com/lokka30/LevelledMobs/wiki/Documentation---rules.yml#presets +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -presets: - spawn_Levelling: - # This Strategy Preset controls the Spawn-Distance-Levelling system. - name: 'LVLling Strategy - Distance-From-Spawn' - strategies: - distance-from-spawn: - increase-level-distance: 150 - start-distance: 250 - spawn-location: - x: default - z: default - - blended_Levelling: - # This Strategy Preset controls the Blended-Levelling sub-system to Spawn-Distance-Levelling. - name: 'LVLling Strategy - Blended-Levelling' - strategies: - distance-from-spawn: - blended-levelling: - enabled: true - transition-y-height: 62 - lvl-multiplier: 0.05 - multiplier-period: 10 - scale-downward: true - - random_Levelling: - # This Strategy Preset controls the Random-Levelling system. - name: 'LVLling Strategy - Random' - strategies: - random: true - lower-mob-level-bias-factor: 5 - - weighted_random_hard: - # This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias HARD DIFFICULTY' - strategies: - weighted-random: - 1-10: 5 - 11-20: 4 - 21-30: 3 - 31-40: 2 - 41-50: 1 - - weighted_random_normal: - # This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias NORMAL DIFFICULTY' - strategies: - weighted-random: - 1-5: 5 - 6-10: 4 - 11-15: 3 - 16-20: 2 - 21-25: 1 - - weighted_random_easy: - # This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias EASY DIFFICULTY' - strategies: - weighted-random: - 1-2: 5 - 3-4: 4 - 5-6: 3 - 7-8: 2 - 9-10: 1 - - player_Levelling: - # This Strategy Preset controls the player-stat based levelling system. - name: 'LVLling Strategy - Player Based NORMAL DIFFIULTY' - strategies: - player-levelling: - match-level: true - use-player-max-level: false - decrease-level: true - player-level-scale: 1.0 - level-cap: 30 - tiers: - 1-15: 1-10 - 16-30: 11-20 - 31-45: 21-25 - variable: '%level%' - - apply_LevellingVariance: - # This Strategy Preset controls the random level variance applied. - name: 'LVLling Strategy - Apply Variance' - strategies: - max-random-variance: 2 - - ycoord_Levelling: - # This Strategy Preset controls Y-Coordinate-Levelling. - name: 'LVLling Strategy - Y-LVLling' - strategies: - y-coordinate: - start: 100 - end: 20 - period: 0 - - allowed_worlds: - # This controls the allowed worlds to apply levels too. - name: 'Excluded Worldlist' - conditions: - worlds: - # allowed-list: ['*'] - excluded-list: ['world_the_end'] - - hard_difficulty: - # This establishes the minimum and maximum levels, and the multipliers, for HARD. - name: 'HARD Difficulty Multipliers' - apply-settings: - minLevel: 1 - maxLevel: 50 - multipliers: - max-health: 8.0 - movement-speed: 0.25 - attack-damage: 3.5 - ranged-attack-damage: 2.75 - creeper-blast-damage: 1.25 - follow-range: 0.25 - item-drop: 3 - xp-drop: 5 - # Special Multipliers (0.0 Min - 1.0 Max) - armor-bonus: 0.3 - armor-toughness: 0.3 - attack-knockback: 0.5 - knockback-resistance: 0.5 - zombie-spawn-reinforcements: 0.45 - - creeper-max-damage-radius: 5 - tiered-coloring: - 1-9: 'E76B' #Green - 10-19: 'ȐCFF' #Blue - 20-29: '&#FFCD56' #Yellow - 30-39: '&#F2003D' #Red - 40-50: '&#B447FF' #Purple - default: '&#FFFFFF' #White - health-indicator: - scale: 8 - max: 5 - merge: true - - normal_difficulty: - # This establishes the minimum and maximum levels, and the multipliers, for NORMAL. - name: 'NORMAL Difficulty Multipliers' - apply-settings: - minLevel: 1 - maxLevel: 25 - multipliers: - # All Entities/Default Attributes - max-health: 5.0 - movement-speed: 0.15 - attack-damage: 2.25 - ranged-attack-damage: 2.0 - creeper-blast-damage: 1.0 - follow-range: 0 - item-drop: 3 - xp-drop: 5 - # Special Multipliers (0.0 Min - 1.0 Max) - armor-bonus: 0.2 - armor-toughness: 0.15 - attack-knockback: 0.25 - knockback-resistance: 0.2 - zombie-spawn-reinforcements: 0.25 - - creeper-max-damage-radius: 3 - tiered-coloring: - 1-5: 'E76B' #Green - 6-10: 'ȐCFF' #Blue - 11-15: '&#FFCD56' #Yellow - 16-20: '&#F2003D' #Red - 21-25: '&#B447FF' #Purple - default: '&#FFFFFF' #White - health-indicator: - scale: 4 - max: 5 - merge: true - - easy_difficulty: - # This establishes the minimum and maximum levels, and the multipliers, for EASY. - name: 'EASY Difficulty Multipliers' - apply-settings: - minLevel: 1 - maxLevel: 10 - multipliers: - max-health: 2.5 - movement-speed: 0.05 - attack-damage: 1.0 - ranged-attack-damage: 1.0 - creeper-blast-damage: 0.5 - follow-range: 0 - item-drop: 3 - xp-drop: 5 - # Special Multipliers (0.0 Min - 1.0 Max) - armor-bonus: 0.1 - armor-toughness: 0.0 - attack-knockback: 0.1 - knockback-resistance: 0.15 - zombie-spawn-reinforcements: 0.1 - - creeper-max-damage-radius: 1 - tiered-coloring: - 1-2: 'E76B' #Green - 3-4: 'ȐCFF' #Blue - 5-6: '&#FFCD56' #Yellow - 7-8: '&#F2003D' #Red - 9-10: '&#B447FF' #Purple - default: '&#FFFFFF' #White - health-indicator: - scale: 2 - max: 5 - merge: true - - nametag_using_indicator: - # This controls the nametag, where the health is displayed using %health-indicator% - name: 'Nametag - Health Indicator' - apply-settings: - nametag: '&8&l༺ %tiered%Lvl %mob-lvl%&8 | &f%displayname%&8 | &f%entity-health-rounded% %tiered%%heart_symbol% &r%health-indicator% &8&l༻' - health-indicator: - indicator: '█' - indicator-half: '▌' - colored-tiers: - tier-1: 'E76B' #Green - tier-2: 'ȐCFF' #Blue - tier-3: '&#FFCD56' #Yellow - tier-4: '&#FE803C' #Orange - tier-5: '&#F2003D' #Red - tier-6: '&#B447FF' #Purple - default: '&#FFFFFF' #White - merge: true - - nametag_using_numbers: - # This controls the nametag, where the health is displayed using %entity-health-rounded% - name: 'Nametag - Health Numerical' - apply-settings: - nametag: '&8&l༺ %tiered%Lvl %mob-lvl%&8 | &f%displayname%&8 | &f%entity-health-rounded%&8/&f%entity-max-health-rounded% %tiered%%heart_symbol% &8&l༻' - - -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + L O W E S T P R I O R I T Y +LM3 -# + Section 03 - Default Rules +LM3 -# + +LM3 -# + This section sets the minimum required information for LevelledMobs to function! +LM3 -# + Much of this is managed by the PRESETS system, however a few configuration lines are +LM3 -# + listed individually as well. If you need help understanding the DEFAULT RULES, +LM3 -# + read the official LevelledMobs Wiki! +LM3 -# + https://github.com/lokka30/LevelledMobs/wiki/Documentation---rules.yml#default-rule +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -default-rule: - use-preset: - - allowed_worlds - - nametag_using_numbers - # - nametag_using_indicator - # - hard_difficulty - # - normal_difficulty - - easy_difficulty - # - apply_LevellingVariance - # - weighted_random_hard - # - weighted_random_normal - - weighted_random_easy - # - spawn_Levelling - # - blended_Levelling - # - ycoord_Levelling - # - random_Levelling - # - player_Levelling - - conditions: - allowed-spawn-reasons: - excluded-list: ['SPAWNER'] - - level-plugins: - DANGEROUS_CAVES: false - ECO_BOSSES: false - MYTHIC_MOBS: false - ELITE_MOBS: false - ELITE_MOBS_NPCS: false - ELITE_MOBS_SUPER_MOBS: false - INFERNAL_MOBS: false - CITIZENS: false - SHOPKEEPERS: false - SIMPLE_PETS: false - ELITE_BOSSES: false - BLOOD_NIGHT: false - - # apply-above-y: 64 - # apply-below-y: 59 - # min-distance-from-spawn: 1000 - # max-distance-from-spawn: 5000 - - mob-customname-status: EITHER - mob-tamed-status: EITHER - - apply-settings: - sunlight-intensity: 5 - - entity-name-override: - # HUSK: ['Husk', 'Desert Zombie', 'Lost Soul', 'Dessicated Corpse'] - # SKELETON: ['Bones', 'Billy Bones', 'Verta Brae'] - # PILLAGER: ['Raider', 'Barbarian'] - - creature-death-nametag: '%tiered%Lvl %mob-lvl%&8 | &f%displayname%' - nametag-placeholder-levelled: '' - nametag-placeholder-unlevelled: '' - nametag-visible-time: 1000 - nametag-visibility-method: ['TARGETED', 'ATTACKED', 'TRACKING'] - - baby-mobs-inherit-adult-setting: true - level-inheritance: true - passenger-match-level: false - - use-custom-item-drops-for-mobs: false - - -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + H I G H E S T P R I O R I T Y +LM3 -# + Section 04 - Custom Rules +LM3 -# + +LM3 -# + This section applies CUSTOM RULES, or exceptions, to the DEFAULT RULE. These rules +LM3 -# + modify the DEFAULT RULE to achieve your desired effect. Establish a CONDITION to +LM3 -# + check for, then set either a STRATEGY or APPLY-SETTINGS when the CONDITION is met. +LM3 -# + If you need help understanding CUSTOM RULES, read the official LevelledMobs Wiki! +LM3 -# + https://github.com/lokka30/LevelledMobs/wiki/Documentation---rules.yml#custom-rules +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -custom-rules: - - enabled: true - priority: 1 - name: 'CR - NoLevel All Passive + EntityTypes' - use-preset: allowed_worlds - conditions: - entities: - allowed-groups: ['all_passive_mobs'] - allowed-list: ['BABY_', 'ENDER_DRAGON', 'WITHER', 'VILLAGER', 'ZOMBIE_VILLAGER', 'WANDERING_TRADER', 'PHANTOM', 'BAT', 'RAVAGER'] - apply-settings: - maxLevel: 0 - - - enabled: true - name: 'CR - Custom Nether Levelling' - use-preset: normal_difficulty, apply_LevellingVariance - conditions: - worlds: - allowed-list: ['world_nether'] - strategies: - y-coordinate: - start: 100 - end: 40 - period: 0 - - - enabled: true - name: 'CR - Custom Entity Attributes' - use-preset: allowed_worlds - apply-settings: - multipliers: - custom-mob-level: - ENDERMAN: - max-health: 0.0 - movement-speed: 0.0 - SILVERFISH: - max-health: 0.0 - movement-speed: 0.0 - CREEPER: - movement-speed: 0.025 - WITHER_SKELETON: - max-health: 1.0 - - - enabled: true - name: 'CR - Custom Zombie Piglin Levelling' - conditions: - worlds: - allowed-list: ['world_nether'] - entities: - allowed-list: ['ZOMBIFIED_PIGLIN'] - strategies: - random: true - apply-settings: - minLevel: 1 - maxLevel: 5 - multipliers: - custom-mob-level: - ZOMBIFIED_PIGLIN: - max-health: 1.0 - movement-speed: 0.25 - attack-damage: 0.5 - ranged-attack-damage: 0.0 - item-drop: 0.5 - xp-drop: 1.0 - tiered-coloring: - 1-5: 'E76B' #Green - - -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + Section 05 - Advanced Message Settings +LM3 -# + DO NOT EDIT BEYOND HERE WITHOUT DEVELOPER APPROVAL +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -file-version: 2 diff --git a/src/main/resources/predefined/rules_hard.yml b/src/main/resources/predefined/rules_hard.yml deleted file mode 100644 index 4aa0b9a05..000000000 --- a/src/main/resources/predefined/rules_hard.yml +++ /dev/null @@ -1,473 +0,0 @@ -# _ _ _ _ __ __ _ -# | | _____ _____| | | ___ __| | \/ | ___ | |__ ___ -# | | / _ \ \ / / _ \ | |/ _ \/ _` | |\/| |/ _ \| '_ \/ __| -# | |__| __/\ V / __/ | | __/ (_| | | | | (_) | |_) \__ \ -# |_____\___| \_/ \___|_|_|\___|\__,_|_| |_|\___/|_.__/|___/ -# The Ultimate RPG Levelled Mobs Solution -# -# --------------------------------------------------------------------------------------------LM3 -# + ++ +LM3 -# + Main Developers ++ Need support documentation? +LM3 -# + lokka30 ++ https://github.com/lokka30/LevelledMobs/wiki +LM3 -# + Author and developer ++ +LM3 -# + https://ko-fi.com/lokka30 ++ ArcanePlugins Support Discord +LM3 -# + ++ https://discord.gg/RQ8M4A5 +LM3 -# + ++ +LM3 -# + stumper66 ++ Special thanks: +LM3 -# + Developer since v2 ++ ArcanePlugins developers and testers! +LM3 -# + https://ko-fi.com/penalbuffalo ++ Donators, patrons, and other supporters! +LM3 -# + ++ SpigotMC, PaperMC, ProtocolLib, NBT-API, +LM3 -# + ++ PlaceholderAPI, and bStats! +LM3 -# + UltimaOath ++ +LM3 -# + Docs and configs since v2 ++ Other Contributors: +LM3 -# + ++ Eyrian, iCodinqs, deiphiz, CoolBoy, Esophose, +LM3 -# + ++ 7smile7, Hugo5551, konsolas, Shevchik, +LM3 -# + ++ limzikiki, and Zombie_Striker +LM3 -# + ++ +LM3 -# --------------------------------------------------------------------------------------------LM3 -# _____ _ -# | __ |_ _| |___ ___ -# | -| | | | -_|_ -| -# |__|__|___|_|___|___| -# -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + Section 01 - Custom Entity and Biome Groups +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -mob-groups: - commons: - - ZOMBIE - - SKELETON - - CREEPER - farm: - - CHICKEN - - COW - - SHEEP - - PIG - - -biome-groups: - taigas: - - TAIGA - - TAIGA_MOUNTAINS - - SNOWY_TUNDRA - - SNOWY_TAIGA - - SNOWY_TAIGA_HILLS - - GIANT_TREE_TAIGA - - GIANT_SPRUCE_TAIGA - - GIANT_SPRUCE_TAIGA_HILLS - - GIANT_TREE_TAIGA_HILLS - - SNOWY_TAIGA_MOUNTAINS - flowers: - - FLOWER_FOREST - - SUNFLOWER_PLAINS - -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + Section 02 - Presets +LM3 -# + +LM3 -# + This section comes prebuilt with several sets of PRESETS to use with LevelledMobs! +LM3 -# + Out-of-the-box most of these PRESETS are not enabled, and are only here to both +LM3 -# + demonstrate the system and allow you to quickly change or use many of the most +LM3 -# + popular features LevelledMobs has to offer! If you need help understanding PRESETS, +LM3 -# + read the official LevelledMobs Wiki! +LM3 -# + https://github.com/lokka30/LevelledMobs/wiki/Documentation---rules.yml#presets +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -presets: - spawn_Levelling: - # This Strategy Preset controls the Spawn-Distance-Levelling system. - name: 'LVLling Strategy - Distance-From-Spawn' - strategies: - distance-from-spawn: - increase-level-distance: 150 - start-distance: 250 - spawn-location: - x: default - z: default - - blended_Levelling: - # This Strategy Preset controls the Blended-Levelling sub-system to Spawn-Distance-Levelling. - name: 'LVLling Strategy - Blended-Levelling' - strategies: - distance-from-spawn: - blended-levelling: - enabled: true - transition-y-height: 62 - lvl-multiplier: 0.05 - multiplier-period: 10 - scale-downward: true - - random_Levelling: - # This Strategy Preset controls the Random-Levelling system. - name: 'LVLling Strategy - Random' - strategies: - random: true - lower-mob-level-bias-factor: 5 - - weighted_random_hard: - # This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias HARD DIFFICULTY' - strategies: - weighted-random: - 1-10: 5 - 11-20: 4 - 21-30: 3 - 31-40: 2 - 41-50: 1 - - weighted_random_normal: - # This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias NORMAL DIFFICULTY' - strategies: - weighted-random: - 1-5: 5 - 6-10: 4 - 11-15: 3 - 16-20: 2 - 21-25: 1 - - weighted_random_easy: - # This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias EASY DIFFICULTY' - strategies: - weighted-random: - 1-2: 5 - 3-4: 4 - 5-6: 3 - 7-8: 2 - 9-10: 1 - - player_Levelling: - # This Strategy Preset controls the player-stat based levelling system. - name: 'LVLling Strategy - Player Based NORMAL DIFFIULTY' - strategies: - player-levelling: - match-level: true - use-player-max-level: false - decrease-level: true - player-level-scale: 1.0 - level-cap: 30 - tiers: - 1-15: 1-10 - 16-30: 11-20 - 31-45: 21-25 - variable: '%level%' - - apply_LevellingVariance: - # This Strategy Preset controls the random level variance applied. - name: 'LVLling Strategy - Apply Variance' - strategies: - max-random-variance: 2 - - ycoord_Levelling: - # This Strategy Preset controls Y-Coordinate-Levelling. - name: 'LVLling Strategy - Y-LVLling' - strategies: - y-coordinate: - start: 100 - end: 20 - period: 0 - - allowed_worlds: - # This controls the allowed worlds to apply levels too. - name: 'Excluded Worldlist' - conditions: - worlds: - # allowed-list: ['*'] - excluded-list: ['world_the_end'] - - hard_difficulty: - # This establishes the minimum and maximum levels, and the multipliers, for HARD. - name: 'HARD Difficulty Multipliers' - apply-settings: - minLevel: 1 - maxLevel: 50 - multipliers: - max-health: 8.0 - movement-speed: 0.25 - attack-damage: 3.5 - ranged-attack-damage: 2.75 - creeper-blast-damage: 1.25 - follow-range: 0.25 - item-drop: 3 - xp-drop: 5 - # Special Multipliers (0.0 Min - 1.0 Max) - armor-bonus: 0.3 - armor-toughness: 0.3 - attack-knockback: 0.5 - knockback-resistance: 0.5 - zombie-spawn-reinforcements: 0.45 - - creeper-max-damage-radius: 5 - tiered-coloring: - 1-9: 'E76B' #Green - 10-19: 'ȐCFF' #Blue - 20-29: '&#FFCD56' #Yellow - 30-39: '&#F2003D' #Red - 40-50: '&#B447FF' #Purple - default: '&#FFFFFF' #White - health-indicator: - scale: 8 - max: 5 - merge: true - - normal_difficulty: - # This establishes the minimum and maximum levels, and the multipliers, for NORMAL. - name: 'NORMAL Difficulty Multipliers' - apply-settings: - minLevel: 1 - maxLevel: 25 - multipliers: - # All Entities/Default Attributes - max-health: 5.0 - movement-speed: 0.15 - attack-damage: 2.25 - ranged-attack-damage: 2.0 - creeper-blast-damage: 1.0 - follow-range: 0 - item-drop: 3 - xp-drop: 5 - # Special Multipliers (0.0 Min - 1.0 Max) - armor-bonus: 0.2 - armor-toughness: 0.15 - attack-knockback: 0.25 - knockback-resistance: 0.2 - zombie-spawn-reinforcements: 0.25 - - creeper-max-damage-radius: 3 - tiered-coloring: - 1-5: 'E76B' #Green - 6-10: 'ȐCFF' #Blue - 11-15: '&#FFCD56' #Yellow - 16-20: '&#F2003D' #Red - 21-25: '&#B447FF' #Purple - default: '&#FFFFFF' #White - health-indicator: - scale: 4 - max: 5 - merge: true - - easy_difficulty: - # This establishes the minimum and maximum levels, and the multipliers, for EASY. - name: 'EASY Difficulty Multipliers' - apply-settings: - minLevel: 1 - maxLevel: 10 - multipliers: - max-health: 2.5 - movement-speed: 0.05 - attack-damage: 1.0 - ranged-attack-damage: 1.0 - creeper-blast-damage: 0.5 - follow-range: 0 - item-drop: 3 - xp-drop: 5 - # Special Multipliers (0.0 Min - 1.0 Max) - armor-bonus: 0.1 - armor-toughness: 0.0 - attack-knockback: 0.1 - knockback-resistance: 0.15 - zombie-spawn-reinforcements: 0.1 - - creeper-max-damage-radius: 1 - tiered-coloring: - 1-2: 'E76B' #Green - 3-4: 'ȐCFF' #Blue - 5-6: '&#FFCD56' #Yellow - 7-8: '&#F2003D' #Red - 9-10: '&#B447FF' #Purple - default: '&#FFFFFF' #White - health-indicator: - scale: 2 - max: 5 - merge: true - - nametag_using_indicator: - # This controls the nametag, where the health is displayed using %health-indicator% - name: 'Nametag - Health Indicator' - apply-settings: - nametag: '&8&l༺ %tiered%Lvl %mob-lvl%&8 | &f%displayname%&8 | &f%entity-health-rounded% %tiered%%heart_symbol% &r%health-indicator% &8&l༻' - health-indicator: - indicator: '█' - indicator-half: '▌' - colored-tiers: - tier-1: 'E76B' #Green - tier-2: 'ȐCFF' #Blue - tier-3: '&#FFCD56' #Yellow - tier-4: '&#FE803C' #Orange - tier-5: '&#F2003D' #Red - tier-6: '&#B447FF' #Purple - default: '&#FFFFFF' #White - merge: true - - nametag_using_numbers: - # This controls the nametag, where the health is displayed using %entity-health-rounded% - name: 'Nametag - Health Numerical' - apply-settings: - nametag: '&8&l༺ %tiered%Lvl %mob-lvl%&8 | &f%displayname%&8 | &f%entity-health-rounded%&8/&f%entity-max-health-rounded% %tiered%%heart_symbol% &8&l༻' - - -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + L O W E S T P R I O R I T Y +LM3 -# + Section 03 - Default Rules +LM3 -# + +LM3 -# + This section sets the minimum required information for LevelledMobs to function! +LM3 -# + Much of this is managed by the PRESETS system, however a few configuration lines are +LM3 -# + listed individually as well. If you need help understanding the DEFAULT RULES, +LM3 -# + read the official LevelledMobs Wiki! +LM3 -# + https://github.com/lokka30/LevelledMobs/wiki/Documentation---rules.yml#default-rule +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -default-rule: - use-preset: - - allowed_worlds - - nametag_using_numbers - # - nametag_using_indicator - - hard_difficulty - # - normal_difficulty - # - easy_difficulty - # - apply_LevellingVariance - - weighted_random_hard - # - weighted_random_normal - # - weighted_random_easy - # - spawn_Levelling - # - blended_Levelling - # - ycoord_Levelling - # - random_Levelling - # - player_Levelling - - conditions: - allowed-spawn-reasons: - excluded-list: ['SPAWNER'] - - level-plugins: - DANGEROUS_CAVES: false - ECO_BOSSES: false - MYTHIC_MOBS: false - ELITE_MOBS: false - ELITE_MOBS_NPCS: false - ELITE_MOBS_SUPER_MOBS: false - INFERNAL_MOBS: false - CITIZENS: false - SHOPKEEPERS: false - SIMPLE_PETS: false - ELITE_BOSSES: false - BLOOD_NIGHT: false - - # apply-above-y: 64 - # apply-below-y: 59 - # min-distance-from-spawn: 1000 - # max-distance-from-spawn: 5000 - - mob-customname-status: EITHER - mob-tamed-status: EITHER - - apply-settings: - sunlight-intensity: 5 - - entity-name-override: - # HUSK: ['Husk', 'Desert Zombie', 'Lost Soul', 'Dessicated Corpse'] - # SKELETON: ['Bones', 'Billy Bones', 'Verta Brae'] - # PILLAGER: ['Raider', 'Barbarian'] - - creature-death-nametag: '%tiered%Lvl %mob-lvl%&8 | &f%displayname%' - nametag-placeholder-levelled: '' - nametag-placeholder-unlevelled: '' - nametag-visible-time: 1000 - nametag-visibility-method: ['TARGETED', 'ATTACKED', 'TRACKING'] - - baby-mobs-inherit-adult-setting: true - level-inheritance: true - passenger-match-level: false - - use-custom-item-drops-for-mobs: false - - -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + H I G H E S T P R I O R I T Y +LM3 -# + Section 04 - Custom Rules +LM3 -# + +LM3 -# + This section applies CUSTOM RULES, or exceptions, to the DEFAULT RULE. These rules +LM3 -# + modify the DEFAULT RULE to achieve your desired effect. Establish a CONDITION to +LM3 -# + check for, then set either a STRATEGY or APPLY-SETTINGS when the CONDITION is met. +LM3 -# + If you need help understanding CUSTOM RULES, read the official LevelledMobs Wiki! +LM3 -# + https://github.com/lokka30/LevelledMobs/wiki/Documentation---rules.yml#custom-rules +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -custom-rules: - - enabled: true - priority: 1 - name: 'CR - NoLevel All Passive + EntityTypes' - use-preset: allowed_worlds - conditions: - entities: - allowed-groups: ['all_passive_mobs'] - allowed-list: ['BABY_', 'ENDER_DRAGON', 'WITHER', 'VILLAGER', 'ZOMBIE_VILLAGER', 'WANDERING_TRADER', 'PHANTOM', 'BAT', 'RAVAGER'] - apply-settings: - maxLevel: 0 - - - enabled: true - name: 'CR - Custom Nether Levelling' - use-preset: normal_difficulty, apply_LevellingVariance - conditions: - worlds: - allowed-list: ['world_nether'] - strategies: - y-coordinate: - start: 100 - end: 40 - period: 0 - - - enabled: true - name: 'CR - Custom Entity Attributes' - use-preset: allowed_worlds - apply-settings: - multipliers: - custom-mob-level: - ENDERMAN: - max-health: 0.0 - movement-speed: 0.0 - SILVERFISH: - max-health: 0.0 - movement-speed: 0.0 - CREEPER: - movement-speed: 0.025 - WITHER_SKELETON: - max-health: 1.0 - - - enabled: true - name: 'CR - Custom Zombie Piglin Levelling' - conditions: - worlds: - allowed-list: ['world_nether'] - entities: - allowed-list: ['ZOMBIFIED_PIGLIN'] - strategies: - random: true - apply-settings: - minLevel: 1 - maxLevel: 5 - multipliers: - custom-mob-level: - ZOMBIFIED_PIGLIN: - max-health: 1.0 - movement-speed: 0.25 - attack-damage: 0.5 - ranged-attack-damage: 0.0 - item-drop: 0.5 - xp-drop: 1.0 - tiered-coloring: - 1-5: 'E76B' #Green - - -# +-------------------------------------------------------------------------------------------LM3 -# + +LM3 -# + Section 05 - Advanced Message Settings +LM3 -# + DO NOT EDIT BEYOND HERE WITHOUT DEVELOPER APPROVAL +LM3 -# + +LM3 -# +-------------------------------------------------------------------------------------------LM3 -file-version: 2 diff --git a/src/main/resources/rules.yml b/src/main/resources/rules.yml index 74eaf3ae0..0f279cf9d 100644 --- a/src/main/resources/rules.yml +++ b/src/main/resources/rules.yml @@ -106,20 +106,20 @@ presets: random: true lower-mob-level-bias-factor: 5 - weighted_random_hard: -# This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias HARD DIFFICULTY' + weighted_random_basic: + # This Strategy Preset controls Weighted Random Bias. + name: 'LVLling Strategy - Weighted Random Bias BASIC Challenge' strategies: weighted-random: - 1-10: 5 - 11-20: 4 - 21-30: 3 - 31-40: 2 - 41-50: 1 + 1-2: 5 + 3-4: 4 + 5-6: 3 + 7-8: 2 + 9-10: 1 - weighted_random_normal: -# This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias NORMAL DIFFICULTY' + weighted_random_average: + # This Strategy Preset controls Weighted Random Bias. + name: 'LVLling Strategy - Weighted Random Bias AVERAGE Challenge' strategies: weighted-random: 1-5: 5 @@ -128,20 +128,31 @@ presets: 16-20: 2 21-25: 1 - weighted_random_easy: + weighted_random_advanced: # This Strategy Preset controls Weighted Random Bias. - name: 'LVLling Strategy - Weighted Random Bias EASY DIFFICULTY' + name: 'LVLling Strategy - Weighted Random Bias ADVANCED Challenge' strategies: weighted-random: - 1-2: 5 - 3-4: 4 - 5-6: 3 - 7-8: 2 - 9-10: 1 + 1-9: 5 + 10-19: 4 + 20-29: 3 + 30-39: 2 + 40-50: 1 + + weighted_random_extreme: +# This Strategy Preset controls Weighted Random Bias. + name: 'LVLling Strategy - Weighted Random Bias EXTREME Challenge' + strategies: + weighted-random: + 1-19: 5 + 20-39: 4 + 40-59: 3 + 60-79: 2 + 80-100: 1 player_Levelling: # This Strategy Preset controls the player-stat based levelling system. - name: 'LVLling Strategy - Player Based NORMAL DIFFIULTY' + name: 'LVLling Strategy - Player Based AVERAGE CHALLENGE' strategies: player-levelling: match-level: true @@ -178,65 +189,47 @@ presets: # allowed-list: ['*'] excluded-list: ['world_the_end'] - hard_difficulty: -# This establishes the minimum and maximum levels, and the multipliers, for HARD. - name: 'HARD Difficulty Multipliers' + basic_challenge: + name: 'Basic-Challenge Multipliers' apply-settings: minLevel: 1 - maxLevel: 50 + maxLevel: 10 multipliers: - max-health: 8.0 - movement-speed: 0.25 - attack-damage: 3.5 - ranged-attack-damage: 2.75 - creeper-blast-damage: 1.25 - follow-range: 0.25 + max-health: 2.5 + attack-damage: 1.0 + ranged-attack-damage: 1.0 item-drop: 3 xp-drop: 5 -# Special Multipliers (0.0 Min - 1.0 Max) - armor-bonus: 0.3 - armor-toughness: 0.3 - attack-knockback: 0.5 - knockback-resistance: 0.5 - zombie-spawn-reinforcements: 0.45 - creeper-max-damage-radius: 5 tiered-coloring: - 1-9: 'E76B' #Green - 10-19: 'ȐCFF' #Blue - 20-29: '&#FFCD56' #Yellow - 30-39: '&#F2003D' #Red - 40-50: '&#B447FF' #Purple + 1-2: 'E76B' #Green + 3-4: 'ȐCFF' #Blue + 5-6: '&#FFCD56' #Yellow + 7-8: '&#F2003D' #Red + 9-10: '&#B447FF' #Purple default: '&#FFFFFF' #White health-indicator: - scale: 8 + scale: 2 max: 5 merge: true - normal_difficulty: -# This establishes the minimum and maximum levels, and the multipliers, for NORMAL. - name: 'NORMAL Difficulty Multipliers' + average_challenge: + name: 'Average-Challenge Multipliers' apply-settings: minLevel: 1 maxLevel: 25 multipliers: -# All Entities/Default Attributes max-health: 5.0 movement-speed: 0.15 attack-damage: 2.25 - ranged-attack-damage: 2.0 - creeper-blast-damage: 1.0 - follow-range: 0 + ranged-attack-damage: 2.25 + creeper-blast-damage: 0.75 item-drop: 3 xp-drop: 5 # Special Multipliers (0.0 Min - 1.0 Max) armor-bonus: 0.2 armor-toughness: 0.15 - attack-knockback: 0.25 - knockback-resistance: 0.2 - zombie-spawn-reinforcements: 0.25 - creeper-max-damage-radius: 3 tiered-coloring: 1-5: 'E76B' #Green 6-10: 'ȐCFF' #Blue @@ -249,38 +242,68 @@ presets: max: 5 merge: true - easy_difficulty: -# This establishes the minimum and maximum levels, and the multipliers, for EASY. - name: 'EASY Difficulty Multipliers' + advanced_challenge: + name: 'Advanced-Challenge Multipliers' apply-settings: minLevel: 1 - maxLevel: 10 + maxLevel: 50 multipliers: - max-health: 2.5 - movement-speed: 0.05 - attack-damage: 1.0 - ranged-attack-damage: 1.0 - creeper-blast-damage: 0.5 - follow-range: 0 + max-health: 8.0 + movement-speed: 0.35 + attack-damage: 3.5 + ranged-attack-damage: 2.75 + creeper-blast-damage: 1.75 + follow-range: 0.25 item-drop: 3 xp-drop: 5 # Special Multipliers (0.0 Min - 1.0 Max) - armor-bonus: 0.1 - armor-toughness: 0.0 - attack-knockback: 0.1 - knockback-resistance: 0.15 - zombie-spawn-reinforcements: 0.1 + armor-bonus: 0.3 + armor-toughness: 0.3 + attack-knockback: 0.5 + knockback-resistance: 0.5 - creeper-max-damage-radius: 1 tiered-coloring: - 1-2: 'E76B' #Green - 3-4: 'ȐCFF' #Blue - 5-6: '&#FFCD56' #Yellow - 7-8: '&#F2003D' #Red - 9-10: '&#B447FF' #Purple + 1-9: 'E76B' #Green + 10-19: 'ȐCFF' #Blue + 20-29: '&#FFCD56' #Yellow + 30-39: '&#F2003D' #Red + 40-50: '&#B447FF' #Purple default: '&#FFFFFF' #White health-indicator: - scale: 2 + scale: 8 + max: 5 + merge: true + + extreme_challenge: + name: 'Extreme-Challenge Multipliers' + apply-settings: + minLevel: 1 + maxLevel: 100 + multipliers: + max-health: 15.0 + movement-speed: 1.0 + attack-damage: 5.0 + ranged-attack-damage: 4.0 + creeper-blast-damage: 2.5 + follow-range: 0.5 + item-drop: 3 + xp-drop: 5 +# Special Multipliers (0.0 Min - 1.0 Max) + armor-bonus: 0.5 + armor-toughness: 0.5 + attack-knockback: 0.5 + knockback-resistance: 0.5 + zombie-spawn-reinforcements: 0.15 + + tiered-coloring: + 1-19: 'E76B' #Green + 20-39: 'ȐCFF' #Blue + 40-59: '&#FFCD56' #Yellow + 60-79: '&#F2003D' #Red + 80-100: '&#B447FF' #Purple + default: '&#FFFFFF' #White + health-indicator: + scale: 16 max: 5 merge: true @@ -326,13 +349,15 @@ default-rule: - allowed_worlds - nametag_using_numbers # - nametag_using_indicator -# - hard_difficulty - - normal_difficulty -# - easy_difficulty +# - basic_challenge + - average_challenge +# - advanced_challenge +# - extreme_challenge # - apply_LevellingVariance -# - weighted_random_hard - - weighted_random_normal -# - weighted_random_easy +# - weighted_random_basic + - weighted_random_average +# - weighted_random_advanced +# - weighted_random_extreme # - spawn_Levelling # - blended_Levelling # - ycoord_Levelling @@ -383,8 +408,15 @@ default-rule: level-inheritance: true passenger-match-level: false - use-custom-item-drops-for-mobs: false + use-custom-item-drops-for-mobs: true + + maximum-death-in-chunk-threshold: 0 + max-adjacent-chunks: 3 + chunk-max-cooldown-seconds: 300 + disable-vanilla-drops-on-chunk-max: false + spawner-particles: 'SOUL' + spawner-particles-count: 10 # +-------------------------------------------------------------------------------------------LM3 # + +LM3 @@ -400,7 +432,6 @@ default-rule: # +-------------------------------------------------------------------------------------------LM3 custom-rules: - enabled: true - priority: 1 name: 'CR - NoLevel All Passive + EntityTypes' use-preset: allowed_worlds conditions: @@ -412,7 +443,7 @@ custom-rules: - enabled: true name: 'CR - Custom Nether Levelling' - use-preset: normal_difficulty, apply_LevellingVariance + use-preset: apply_LevellingVariance conditions: worlds: allowed-list: ['world_nether'] @@ -470,4 +501,4 @@ custom-rules: # + DO NOT EDIT BEYOND HERE WITHOUT DEVELOPER APPROVAL +LM3 # + +LM3 # +-------------------------------------------------------------------------------------------LM3 -file-version: 2 +file-version: 3 diff --git a/src/main/resources/settings.yml b/src/main/resources/settings.yml index 2135aa6fd..b2de5d484 100644 --- a/src/main/resources/settings.yml +++ b/src/main/resources/settings.yml @@ -123,6 +123,11 @@ kill-skip-conditions: # || by default as to not harm their economies. mobs-multiply-head-drops: false +# || When player kill exceed amount of levelled mobs in +# || a chunk(within given period). Should we send message +# || to player to inform him? +exceed-kill-in-chunk-message: true + # || ADVANCED USERS ONLY # || Do not touch this unless a LM developer tells you to. # || These settings pertian to Player Levelling, obviously @@ -133,10 +138,19 @@ player-levelling-relevel-min-time: 5000 # || for it to register under `%levelledmobs_mob-target%`. nametag-placeholder-maxblocks: 30 +# If your server software (e.g. Spigot/Paper) has +# the Adventure library in it, then LevelledMobs +# will use it, so long this setting is enabled. +# CraftBukkit and Spigot servers do not have +# this library, although PaperMC servers and +# all PaperMC forks (Airplane, etc) do. +# It is recommended that you keep this enabled. +use-adventure: true + # || ADVANCED USERS ONLY # || Do not touch this unless a LM developer tells you to. # || This value must not be modified, else your configuration # || may be corrupted by the migrator, since the migrator uses # || this value to check what changes the file requires to # || become updated to the latest file version. -file-version: 32 +file-version: 33