From c10f805480ae00275477102611643db69ee4509f Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Thu, 12 Feb 2026 19:36:57 +0100 Subject: [PATCH 1/9] feat: Enhance environment level computation by incorporating NPC entity for randomness --- .../azuredoom/levelingcore/utils/MobLevelingUtil.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java b/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java index a74bbcf..9c2492a 100644 --- a/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java +++ b/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java @@ -51,7 +51,7 @@ public static int computeDynamicLevel( case ZONE -> computeZoneLevel(store); case ENVIRONMENT -> - computeEnvironmentLevel(transform, store); + computeEnvironmentLevel(transform, store, npc); case INSTANCE -> computeInstanceLevel(store); }) @@ -134,7 +134,7 @@ public static int computeBiomeLevel(Store store) { return biomeMapping.getOrDefault(currentBiome.toLowerCase(), 1); } - public static int computeEnvironmentLevel(TransformComponent transform, Store store) { + public static int computeEnvironmentLevel(TransformComponent transform, Store store, NPCEntity npc) { var world = store.getExternalData().getWorld(); var mobPos = transform.getPosition(); var chunk = world.getChunk(ChunkUtil.indexChunkFromBlock((int) mobPos.x, (int) mobPos.z)); @@ -160,8 +160,12 @@ public static int computeEnvironmentLevel(TransformComponent transform, Store store) { From dbe8a5eb64269ad1bb71b3ee1ac752e4351a0983 Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Thu, 12 Feb 2026 19:38:12 +0100 Subject: [PATCH 2/9] Update .vscode/settings.json --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file From 047c3e8cd16bd0fb59fccb104f7bddb5e976507a Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Fri, 13 Feb 2026 23:08:09 +0100 Subject: [PATCH 3/9] feat: Add mob level override mapping functionality and default configuration --- .../azuredoom/levelingcore/LevelingCore.java | 5 + .../mobs/mapping/MobOverrideMapping.java | 108 ++++++++++++++++++ .../levelingcore/utils/MobLevelingUtil.java | 30 +++-- .../resources/defaultmoboverridemapping.csv | 2 + 4 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/azuredoom/levelingcore/level/mobs/mapping/MobOverrideMapping.java create mode 100644 src/main/resources/defaultmoboverridemapping.csv diff --git a/src/main/java/com/azuredoom/levelingcore/LevelingCore.java b/src/main/java/com/azuredoom/levelingcore/LevelingCore.java index dce7b6c..ed5a42d 100644 --- a/src/main/java/com/azuredoom/levelingcore/LevelingCore.java +++ b/src/main/java/com/azuredoom/levelingcore/LevelingCore.java @@ -38,6 +38,7 @@ import com.azuredoom.levelingcore.level.mobs.mapping.MobBiomeMapping; import com.azuredoom.levelingcore.level.mobs.mapping.MobEnvironmentMapping; import com.azuredoom.levelingcore.level.mobs.mapping.MobInstanceMapping; +import com.azuredoom.levelingcore.level.mobs.mapping.MobOverrideMapping; import com.azuredoom.levelingcore.level.mobs.mapping.MobZoneMapping; import com.azuredoom.levelingcore.level.rewards.LevelRewards; import com.azuredoom.levelingcore.level.rewards.RewardEntry; @@ -99,6 +100,10 @@ public class LevelingCore extends JavaPlugin { LevelingCore.configPath ); + public static final Map mobOverrideMapping = MobOverrideMapping.loadOrCreate( + LevelingCore.configPath + ); + public static final MobLevelRegistry mobLevelRegistry = new MobLevelRegistry(); public static final MobLevelPersistence mobLevelPersistence = new MobLevelPersistence(); diff --git a/src/main/java/com/azuredoom/levelingcore/level/mobs/mapping/MobOverrideMapping.java b/src/main/java/com/azuredoom/levelingcore/level/mobs/mapping/MobOverrideMapping.java new file mode 100644 index 0000000..8b1ee6a --- /dev/null +++ b/src/main/java/com/azuredoom/levelingcore/level/mobs/mapping/MobOverrideMapping.java @@ -0,0 +1,108 @@ +package com.azuredoom.levelingcore.level.mobs.mapping; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; + +import com.azuredoom.levelingcore.LevelingCore; +import com.azuredoom.levelingcore.config.internal.ConfigManager; +import com.azuredoom.levelingcore.exceptions.LevelingCoreException; + +public class MobOverrideMapping { + + public static final String FILE_NAME = "moboverridemapping.csv"; + + public static final String RESOURCE_DEFAULT = "/defaultmoboverridemapping.csv"; + + private MobOverrideMapping() {} + + public static Map loadOrCreate(Path dataDir) { + try { + Files.createDirectories(dataDir); + var configPath = dataDir.resolve(FILE_NAME); + + if (Files.notExists(configPath)) { + try (InputStream in = ConfigManager.class.getResourceAsStream(RESOURCE_DEFAULT)) { + if (in == null) { + throw new LevelingCoreException( + "defaultmoboverridemapping.csv not found in resources (expected at " + RESOURCE_DEFAULT + + ")" + ); + } + LevelingCore.LOGGER.at(Level.INFO) + .log("Creating default Mob Override Levels Mapping config at " + configPath); + Files.copy(in, configPath, StandardCopyOption.REPLACE_EXISTING); + } + } + + var mapping = readXpCsv(configPath); + + LevelingCore.LOGGER.at(Level.INFO) + .log( + "Loaded Mob Override Levels Mapping mapping from " + configPath + " " + mapping.size() + " entries)" + ); + return mapping; + + } catch (Exception e) { + throw new LevelingCoreException("Failed to load Mob Override Levels Mapping config", e); + } + } + + private static Map readXpCsv(Path csvPath) throws Exception { + Map out = new LinkedHashMap<>(); + + try (var reader = Files.newBufferedReader(csvPath, StandardCharsets.UTF_8)) { + String line; + var firstNonEmptyLine = true; + + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) + continue; + if (line.startsWith("#")) + continue; + + if (firstNonEmptyLine) { + firstNonEmptyLine = false; + if (line.equalsIgnoreCase("npc_id,lvl")) { + continue; + } + } + + var parts = line.split(",", 2); + if (parts.length != 2) { + LevelingCore.LOGGER.at(Level.WARNING).log("Skipping invalid CSV line: " + line); + continue; + } + + var npcId = parts[0].trim(); + var lvlStr = parts[1].trim(); + + if (npcId.isEmpty()) { + LevelingCore.LOGGER.at(Level.WARNING).log("Skipping CSV line with empty NPC ID: " + line); + continue; + } + + int lvl; + try { + lvl = Integer.parseInt(lvlStr); + } catch (NumberFormatException nfe) { + LevelingCore.LOGGER.at(Level.WARNING) + .log( + "Invalid NPC ID value for " + npcId + ": " + lvlStr + " (line: " + line + ")" + ); + continue; + } + + out.put(npcId, lvl); + } + } + + return out; + } +} diff --git a/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java b/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java index 9c2492a..9feddef 100644 --- a/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java +++ b/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java @@ -35,6 +35,11 @@ public static int computeDynamicLevel( Store store ) { var modeStr = config.get().getLevelMode(); + var overrideLevel = computeNPCOverrideLevel(npc); + + if (overrideLevel != 0) { + return overrideLevel; + } if (modeStr == null) { return computeNearbyPlayersMeanLevel(transform, store); @@ -42,18 +47,12 @@ public static int computeDynamicLevel( return CoreLevelMode.fromString(modeStr) .map(mode -> switch (mode) { - case SPAWN_ONLY -> - computeSpawnLevel(npc); - case NEARBY_PLAYERS_MEAN -> - computeNearbyPlayersMeanLevel(transform, store); - case BIOME -> - computeBiomeLevel(store); - case ZONE -> - computeZoneLevel(store); - case ENVIRONMENT -> - computeEnvironmentLevel(transform, store, npc); - case INSTANCE -> - computeInstanceLevel(store); + case SPAWN_ONLY -> computeSpawnLevel(npc); + case NEARBY_PLAYERS_MEAN -> computeNearbyPlayersMeanLevel(transform, store); + case BIOME -> computeBiomeLevel(store); + case ZONE -> computeZoneLevel(store); + case ENVIRONMENT -> computeEnvironmentLevel(transform, store, npc); + case INSTANCE -> computeInstanceLevel(store); }) .orElseGet(() -> { LevelingCore.LOGGER.at(Level.INFO) @@ -197,4 +196,11 @@ public static int computeNearbyPlayersMeanLevel(TransformComponent transform, St var mean = (double) sum / (double) count; return (int) Math.round(mean); } + + public static int computeNPCOverrideLevel(NPCEntity npc) { + var npcTypeID = npc.getNPCTypeId(); + var overrideMapping = LevelingCore.mobOverrideMapping; + + return overrideMapping.getOrDefault(npcTypeID.toLowerCase(), 0); + } } diff --git a/src/main/resources/defaultmoboverridemapping.csv b/src/main/resources/defaultmoboverridemapping.csv new file mode 100644 index 0000000..91c8ab9 --- /dev/null +++ b/src/main/resources/defaultmoboverridemapping.csv @@ -0,0 +1,2 @@ +npc_id,lvl +bunny,5 \ No newline at end of file From cfc4b9ca39691ea611178c48956c8cb7736eb4fd Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Sat, 14 Feb 2026 00:05:10 +0100 Subject: [PATCH 4/9] feat: Add mob override mapping to Bootstrap initialization --- .../levelingcore/config/internal/ConfigBootstrap.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/azuredoom/levelingcore/config/internal/ConfigBootstrap.java b/src/main/java/com/azuredoom/levelingcore/config/internal/ConfigBootstrap.java index 79fa656..5d66ad8 100644 --- a/src/main/java/com/azuredoom/levelingcore/config/internal/ConfigBootstrap.java +++ b/src/main/java/com/azuredoom/levelingcore/config/internal/ConfigBootstrap.java @@ -38,6 +38,7 @@ public record Bootstrap( Map mobZoneMapping, Map mobBiomeMapping, Map mobEnvironmentMapping, + Map mobOverrideMapping, AutoCloseable closeable ) {} @@ -76,6 +77,7 @@ public static Bootstrap bootstrap(Path dataDir) { var mobZoneMapping = LevelingCore.mobZoneMapping; var mobBiomeMapping = LevelingCore.mobBiomeMapping; var mobEnvironmentMapping = LevelingCore.mobEnvironmentMapping; + var mobOverrideMapping = LevelingCore.mobOverrideMapping; return new Bootstrap( service, @@ -87,6 +89,7 @@ public static Bootstrap bootstrap(Path dataDir) { mobZoneMapping, mobBiomeMapping, mobEnvironmentMapping, + mobOverrideMapping, repo::close ); } From 56c9be04c46e619222127c4c39f201173166e691 Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Sat, 14 Feb 2026 00:10:59 +0100 Subject: [PATCH 5/9] feat: Add level variance configuration to GUI settings --- .../azuredoom/levelingcore/config/GUIConfig.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/azuredoom/levelingcore/config/GUIConfig.java b/src/main/java/com/azuredoom/levelingcore/config/GUIConfig.java index 3daa358..6670e58 100644 --- a/src/main/java/com/azuredoom/levelingcore/config/GUIConfig.java +++ b/src/main/java/com/azuredoom/levelingcore/config/GUIConfig.java @@ -235,6 +235,12 @@ public class GUIConfig { (exConfig, extraInfo) -> exConfig.levelMode ) .add() + .append( + new KeyedCodec("LevelVariance", Codec.INTEGER), + (exConfig, anInteger, extraInfo) -> exConfig.levelVariance = anInteger, + (exConfig, extraInfo) -> exConfig.levelVariance + ) + .add() .append( new KeyedCodec("MobHealthMultiplier", Codec.FLOAT), (exConfig, aFloat, extraInfo) -> exConfig.mobHealthMultiplier = aFloat, @@ -377,6 +383,8 @@ public class GUIConfig { private String levelMode = "NEARBY_PLAYERS_MEAN"; + private int levelVariance = 0; + private float mobHealthMultiplier = 2.10F; private float mobDamageMultiplier = 0.25F; @@ -623,6 +631,14 @@ public float getConStatMultiplier() { public String getLevelMode() { return levelMode; } + /** + * Retrieves the configured level variance used by the leveling system. + * + * @return the level variance as an integer. + */ + public int getLevelVariance() { + return levelVariance; + } public float getMobHealthMultiplier() { return mobHealthMultiplier; From 62cd7a8c5ca343d20f7e32714d4283133094d0b2 Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Sat, 14 Feb 2026 00:24:49 +0100 Subject: [PATCH 6/9] feat: Enhance mob level computation with NPC entity integration and randomization --- .../levelingcore/utils/MobLevelingUtil.java | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java b/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java index 9feddef..a9dedaf 100644 --- a/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java +++ b/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java @@ -42,22 +42,22 @@ public static int computeDynamicLevel( } if (modeStr == null) { - return computeNearbyPlayersMeanLevel(transform, store); + return computeNearbyPlayersMeanLevel(transform, store, npc); } return CoreLevelMode.fromString(modeStr) .map(mode -> switch (mode) { case SPAWN_ONLY -> computeSpawnLevel(npc); - case NEARBY_PLAYERS_MEAN -> computeNearbyPlayersMeanLevel(transform, store); - case BIOME -> computeBiomeLevel(store); - case ZONE -> computeZoneLevel(store); + case NEARBY_PLAYERS_MEAN -> computeNearbyPlayersMeanLevel(transform, store, npc); + case BIOME -> computeBiomeLevel(store, npc); + case ZONE -> computeZoneLevel(store, npc); case ENVIRONMENT -> computeEnvironmentLevel(transform, store, npc); - case INSTANCE -> computeInstanceLevel(store); + case INSTANCE -> computeInstanceLevel(store, npc); }) .orElseGet(() -> { LevelingCore.LOGGER.at(Level.INFO) .log("Unknown level mode " + modeStr + " defaulting to NEARBY_PLAYERS_MEAN"); - return computeNearbyPlayersMeanLevel(transform, store); + return computeNearbyPlayersMeanLevel(transform, store, npc); }); } @@ -96,7 +96,7 @@ public static int computeSpawnLevel(NPCEntity npc) { return spawnMin + rng.nextInt((spawnMax - spawnMin) + 1); } - public static int computeInstanceLevel(Store store) { + public static int computeInstanceLevel(Store store, NPCEntity npc) { var world = store.getExternalData().getWorld(); var instanceName = world.getName(); var instanceMapping = LevelingCore.mobInstanceMapping; @@ -106,10 +106,11 @@ public static int computeInstanceLevel(Store store) { return 0; } - return instanceMapping.getOrDefault(instanceName.toLowerCase(), 1); + var baseLevel = instanceMapping.getOrDefault(instanceName.toLowerCase(), 1); + return randomizeLevel(baseLevel, npc); } - public static int computeZoneLevel(Store store) { + public static int computeZoneLevel(Store store, NPCEntity npc) { var world = store.getExternalData().getWorld(); var worldMapTracker = world.getPlayers().getFirst().getWorldMapTracker(); var currentZone = worldMapTracker.getCurrentZone(); @@ -117,10 +118,11 @@ public static int computeZoneLevel(Store store) { return 0; var zoneMapping = LevelingCore.mobZoneMapping; - return zoneMapping.getOrDefault(currentZone.zoneName().toLowerCase(), 1); + var baseLevel = zoneMapping.getOrDefault(currentZone.zoneName().toLowerCase(), 1); + return randomizeLevel(baseLevel, npc); } - public static int computeBiomeLevel(Store store) { + public static int computeBiomeLevel(Store store, NPCEntity npc) { var world = store.getExternalData().getWorld(); var worldMapTracker = world.getPlayers().getFirst().getWorldMapTracker(); var currentBiome = worldMapTracker.getCurrentBiomeName(); @@ -129,8 +131,8 @@ public static int computeBiomeLevel(Store store) { return 6; var biomeMapping = LevelingCore.mobBiomeMapping; - - return biomeMapping.getOrDefault(currentBiome.toLowerCase(), 1); + var baseLevel = biomeMapping.getOrDefault(currentBiome.toLowerCase(), 1); + return randomizeLevel(baseLevel, npc); } public static int computeEnvironmentLevel(TransformComponent transform, Store store, NPCEntity npc) { @@ -159,15 +161,11 @@ public static int computeEnvironmentLevel(TransformComponent transform, Store store) { + public static int computeNearbyPlayersMeanLevel(TransformComponent transform, Store store, NPCEntity npc) { var world = store.getExternalData().getWorld(); var mobPos = transform.getPosition(); var players = world.getPlayers(); @@ -194,7 +192,8 @@ public static int computeNearbyPlayersMeanLevel(TransformComponent transform, St return 5; var mean = (double) sum / (double) count; - return (int) Math.round(mean); + var baseLevel = (int) Math.round(mean); + return randomizeLevel(baseLevel, npc); } public static int computeNPCOverrideLevel(NPCEntity npc) { @@ -203,4 +202,14 @@ public static int computeNPCOverrideLevel(NPCEntity npc) { return overrideMapping.getOrDefault(npcTypeID.toLowerCase(), 0); } + + public static int randomizeLevel(int baseLevel, NPCEntity npc) { + var variance = LevelingCore.getConfig().get().getLevelVariance(); + if(variance <= 0){ return baseLevel; } + var seed = npc.getUuid().getMostSignificantBits() ^ npc.getUuid().getLeastSignificantBits(); + var rng = new Random(seed); + + var range = baseLevel + variance; + return baseLevel + rng.nextInt(range - baseLevel); + } } From b480e1f0c8c725858946a983188dd46246e88d5f Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Sat, 14 Feb 2026 00:25:22 +0100 Subject: [PATCH 7/9] fix: Add .vscode directory to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index daafb05..72caa1b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ build data run libs/PartyPro + +.vscode From 2219dbd82af643206aee6533f1f2449a577bb73a Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Sat, 14 Feb 2026 00:28:14 +0100 Subject: [PATCH 8/9] refactor: Improve code readability by formatting method parameters and control flow in MobLevelingUtil --- .../com/azuredoom/levelingcore/config/GUIConfig.java | 1 + .../azuredoom/levelingcore/utils/MobLevelingUtil.java | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/azuredoom/levelingcore/config/GUIConfig.java b/src/main/java/com/azuredoom/levelingcore/config/GUIConfig.java index 6670e58..69ea69b 100644 --- a/src/main/java/com/azuredoom/levelingcore/config/GUIConfig.java +++ b/src/main/java/com/azuredoom/levelingcore/config/GUIConfig.java @@ -631,6 +631,7 @@ public float getConStatMultiplier() { public String getLevelMode() { return levelMode; } + /** * Retrieves the configured level variance used by the leveling system. * diff --git a/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java b/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java index a9dedaf..3e609a2 100644 --- a/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java +++ b/src/main/java/com/azuredoom/levelingcore/utils/MobLevelingUtil.java @@ -165,7 +165,11 @@ public static int computeEnvironmentLevel(TransformComponent transform, Store store, NPCEntity npc) { + public static int computeNearbyPlayersMeanLevel( + TransformComponent transform, + Store store, + NPCEntity npc + ) { var world = store.getExternalData().getWorld(); var mobPos = transform.getPosition(); var players = world.getPlayers(); @@ -205,7 +209,9 @@ public static int computeNPCOverrideLevel(NPCEntity npc) { public static int randomizeLevel(int baseLevel, NPCEntity npc) { var variance = LevelingCore.getConfig().get().getLevelVariance(); - if(variance <= 0){ return baseLevel; } + if (variance <= 0) { + return baseLevel; + } var seed = npc.getUuid().getMostSignificantBits() ^ npc.getUuid().getLeastSignificantBits(); var rng = new Random(seed); From 38d690396cece21c334ad08462fb1f2269c5f1fc Mon Sep 17 00:00:00 2001 From: GlobalHive Date: Sat, 14 Feb 2026 00:38:07 +0100 Subject: [PATCH 9/9] Stop tracking .vscode --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c5f3f6b..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "interactive" -} \ No newline at end of file