Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The shaded JAR is produced at `target/MiningLevels-<version>.jar`.

## External Dependencies

- **CrucialLib 3.0.0** — JSON persistence, localization, boss bars, player effects
- **CrucialLib 3.0.1** — JSON persistence, localization, boss bars, player effects
- **PlaceholderAPI** (optional) — exposes level/XP placeholders and command rewards

## Testing Notes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ All dependencies are managed automatically by Maven.
* Paper API
* Version: 1.21
* CrucialLib
* Version: 3.0.0
* Version: 3.0.1
* [GitHub](https://github.com/ChafficPlugins/CrucialLib)
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ MiningLevels is a Spigot plugin that adds levels to your mining experience, brin

- Java 21+
- Paper or Spigot 1.21+
- [CrucialLib 3.0.0+](https://github.com/ChafficPlugins/CrucialLib)
- [CrucialLib 3.0.1+](https://github.com/ChafficPlugins/CrucialLib)
- [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/) (optional, required for placeholders and level-up commands)

## License
Expand Down
4 changes: 4 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ plugins/MiningLevels/
```

Default `levels.json` and `blocks.json` are bundled inside the plugin JAR and copied automatically on first run. No internet connection is required for setup.

### Player Auto-Registration

Since version 1.3.1, players are automatically registered when they interact with any MiningLevels feature (mining, commands, placeholders, etc.). Previously, if a player was missing from `players.json`, actions like mining or running commands would fail with an error. Now the plugin creates a new player entry (level 0, 0 XP) on the fly and saves it immediately. Players are still also registered on join as before.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>de.chafficplugins</groupId>
<artifactId>MiningLevels</artifactId>
<version>1.3.0</version>
<version>1.3.1</version>
<packaging>jar</packaging>

<name>MiningLevels</name>
Expand Down Expand Up @@ -98,7 +98,7 @@
<dependency>
<groupId>com.github.ChafficPlugins</groupId>
<artifactId>CrucialLib</artifactId>
<version>v3.0.0</version>
<version>v3.0.1</version>
</dependency>
<dependency>
<groupId>me.clip</groupId>
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/de/chafficplugins/mininglevels/api/MiningPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,25 @@ public static MiningPlayer getMiningPlayer(UUID uuid) {
return null;
}

/**
* Gets the MiningPlayer with the given UUID, or creates a new one if it doesn't exist.
* @param uuid The UUID of the player to get or create.
* @return The MiningPlayer with the given UUID.
*/
public static MiningPlayer getOrCreateMiningPlayer(UUID uuid) {
MiningPlayer existing = getMiningPlayer(uuid);
if (existing != null) {
return existing;
}
MiningPlayer newPlayer = new MiningPlayer(uuid, 0, 0);
try {
save();
} catch (IOException e) {
getPlugin().error("Failed to save new player: " + e.getMessage());
}
return newPlayer;
}

/**
* Checks if the given player is a MiningPlayer.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ public MiningLevelProfile(OfflinePlayer player) {

@Override
public void populate() {
MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(player.getUniqueId());
if (miningPlayer == null) return;
MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(player.getUniqueId());
ItemStack stack = Stack.getStack(miningPlayer.getUUID(), miningPlayer.getOfflinePlayer().getName(), List.of(
Localizer.getLocalizedString(LOCALIZED_IDENTIFIER + "_" + CURRENT_LEVEL, miningPlayer.getLevel().getName()),
Localizer.getLocalizedString(LOCALIZED_IDENTIFIER + "_" + CURRENT_XP, miningPlayer.getXp() + "", miningPlayer.getLevel().getNextLevelXP() + "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St

private boolean showLevelInfo(CommandSender sender) {
Player player = (Player) sender;
MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(player.getUniqueId());
if(miningPlayer == null) {
sendMessage(sender, ERROR_OCCURRED);
return true;
}
MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(player.getUniqueId());

new MiningLevelProfile(player).open(player);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
if(command.getName().equalsIgnoreCase("miningrewards")) {
if(sender instanceof Player) {
Player player = (Player) sender;
MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(player.getUniqueId());
if(miningPlayer == null) {
return true;
}
MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(player.getUniqueId());
switch (miningPlayer.claim()) {
case 0 -> sendMessage(player, NO_REWARDS, ChatColor.RED);
case 1 -> sendMessage(player, REWARDS_CLAIMED, ChatColor.GREEN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ public static void setLevel(CommandSender sender, String[] args) {
if(args.length == 3) {
Player player = Bukkit.getPlayer(args[1]);
if(player != null) {
MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(player.getUniqueId());
if(miningPlayer != null) {
int level = Integer.parseInt(args[2]);
if(level >= 0 && level < MiningLevel.miningLevels.size()) {
miningPlayer.setLevel(level);
sendMessage(player, NEW_LEVEL, String.valueOf(level));
return;
}
MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(player.getUniqueId());
int level = Integer.parseInt(args[2]);
if(level >= 0 && level < MiningLevel.miningLevels.size()) {
miningPlayer.setLevel(level);
sendMessage(player, NEW_LEVEL, String.valueOf(level));
return;
}
} else {
sendMessage(sender, PLAYER_NOT_EXIST, args[1]);
Expand All @@ -40,14 +38,12 @@ public static void setXP(CommandSender sender, String[] args) {
if(args.length == 3) {
Player player = Bukkit.getPlayer(args[1]);
if(player != null) {
MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(player.getUniqueId());
if(miningPlayer != null) {
int xp = Integer.parseInt(args[2]);
if(xp >= 0) {
miningPlayer.changeXp(xp);
sendMessage(player, XP_RECEIVED, String.valueOf(xp));
return;
}
MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(player.getUniqueId());
int xp = Integer.parseInt(args[2]);
if(xp >= 0) {
miningPlayer.changeXp(xp);
sendMessage(player, XP_RECEIVED, String.valueOf(xp));
return;
}
} else {
sendMessage(sender, PLAYER_NOT_EXIST, args[1]);
Expand All @@ -61,11 +57,9 @@ public static void level(CommandSender sender, String[] args) {
if(args.length == 2) {
Player player = Bukkit.getPlayer(args[1]);
if(player != null) {
MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(player.getUniqueId());
if(miningPlayer != null) {
sendMessage(sender, LEVEL_OF, player.getDisplayName(), String.valueOf(miningPlayer.getLevel().getName()));
return;
}
MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(player.getUniqueId());
sendMessage(sender, LEVEL_OF, player.getDisplayName(), String.valueOf(miningPlayer.getLevel().getName()));
return;
} else {
sendMessage(sender, PLAYER_NOT_EXIST, args[1]);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,32 +30,27 @@ public void onBlockDamage(final BlockDamageEvent event) {
final MiningBlock block = MiningBlock.getMiningBlock(event.getBlock().getType());
sendDebug(event.getPlayer(), "BlockDamageEvent: " + event.getBlock().getType().name());
if(block != null) {
final MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(event.getPlayer().getUniqueId());
if(miningPlayer != null) {
if (miningPlayer.getLevel().getOrdinal() < block.getMinLevel()) {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "Level too low.");
MiningLevel level = MiningLevel.get(block.getMinLevel());
if (level == null) return;
miningPlayer.showMessage(LEVEL_NEEDED, ChatColor.RED, level.getName());
event.setCancelled(true);
} else {
ItemStack itemInUse = event.getPlayer().getInventory().getItemInMainHand();
if (isMiningItem(itemInUse.getType())) {
if (miningPlayer.getLevel().getHasteLevel() > 0) {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "Haste level: " + miningPlayer.getLevel().getHasteLevel());
event.getPlayer().addPotionEffect(new PotionEffect(PotionEffectType.HASTE, 5 * 20, miningPlayer.getLevel().getHasteLevel()));
}
if (MathUtils.randomDouble(0, 100) < miningPlayer.getLevel().getInstantBreakProbability()) {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "Instant break.");
event.setInstaBreak(true); //Insta break
}
} else {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "The held item " + itemInUse.getType() + " is not a mining item.");
final MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(event.getPlayer().getUniqueId());
if (miningPlayer.getLevel().getOrdinal() < block.getMinLevel()) {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "Level too low.");
MiningLevel level = MiningLevel.get(block.getMinLevel());
if (level == null) return;
miningPlayer.showMessage(LEVEL_NEEDED, ChatColor.RED, level.getName());
event.setCancelled(true);
} else {
ItemStack itemInUse = event.getPlayer().getInventory().getItemInMainHand();
if (isMiningItem(itemInUse.getType())) {
if (miningPlayer.getLevel().getHasteLevel() > 0) {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "Haste level: " + miningPlayer.getLevel().getHasteLevel());
event.getPlayer().addPotionEffect(new PotionEffect(PotionEffectType.HASTE, 5 * 20, miningPlayer.getLevel().getHasteLevel()));
}
if (MathUtils.randomDouble(0, 100) < miningPlayer.getLevel().getInstantBreakProbability()) {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "Instant break.");
event.setInstaBreak(true); //Insta break
}
} else {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "The held item " + itemInUse.getType() + " is not a mining item.");
}
} else {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "Error: Player is not registered to the plugin!");
event.setCancelled(true);
}
} else {
sendDebug(event.getPlayer(), "BlockDamageEvent: " + "Not a mining block.");
Expand All @@ -71,51 +66,46 @@ public void onBlockBreak(final BlockBreakEvent event) {
final MiningBlock block = MiningBlock.getMiningBlock(event.getBlock().getType());
sendDebug(event.getPlayer(), "BlockBreakEvent: " + event.getBlock().getType().name());
if(block != null) {
final MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(event.getPlayer().getUniqueId());
if(miningPlayer != null) {
if(miningPlayer.getLevel().getOrdinal() < block.getMinLevel()) {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Level too low.");
event.setCancelled(true);
MiningLevel level = MiningLevel.get(block.getMinLevel());
if(level == null) return;
miningPlayer.showMessage(LEVEL_NEEDED, ChatColor.RED, level.getName());
} else {
//check if the block was placed by a player
if (noXpBlocks.contains(event.getBlock())) {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Config options disallow block to drop xp.");
return;
}
ItemStack itemInUse = event.getPlayer().getInventory().getItemInMainHand();
if (!isMiningItem(itemInUse.getType())) {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "The held " + itemInUse.getType() + " item is not a mining item.");
final MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(event.getPlayer().getUniqueId());
if(miningPlayer.getLevel().getOrdinal() < block.getMinLevel()) {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Level too low.");
event.setCancelled(true);
MiningLevel level = MiningLevel.get(block.getMinLevel());
if(level == null) return;
miningPlayer.showMessage(LEVEL_NEEDED, ChatColor.RED, level.getName());
} else {
//check if the block was placed by a player
if (noXpBlocks.contains(event.getBlock())) {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Config options disallow block to drop xp.");
return;
}
ItemStack itemInUse = event.getPlayer().getInventory().getItemInMainHand();
if (!isMiningItem(itemInUse.getType())) {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "The held " + itemInUse.getType() + " item is not a mining item.");
return;
}

Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (event.isCancelled()) {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Event cancelled.");
return;
}
miningPlayer.alterXp(block.getXp());
MiningLevel level = miningPlayer.getLevel();

Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (event.isCancelled()) {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Event cancelled.");
return;
}
miningPlayer.alterXp(block.getXp());
MiningLevel level = miningPlayer.getLevel();
if (MathUtils.randomDouble(0, 100) < level.getExtraOreProbability()) {
Block actualBlock = event.getBlock();

if (MathUtils.randomDouble(0, 100) < level.getExtraOreProbability()) {
Block actualBlock = event.getBlock();
if (actualBlock.getDrops().isEmpty()) {return;}

if (actualBlock.getDrops().isEmpty()) {return;}

int amount = (int) MathUtils.randomDouble(1, level.getMaxExtraOre());
ItemStack item = actualBlock.getDrops().iterator().next();
for (int i = 0; i < amount; i++) {
actualBlock.getDrops().add(item);
}
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Dropped " + amount + " extra ores.");
int amount = (int) MathUtils.randomDouble(1, level.getMaxExtraOre());
ItemStack item = actualBlock.getDrops().iterator().next();
for (int i = 0; i < amount; i++) {
actualBlock.getDrops().add(item);
}
} , 2L);
}
} else {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Error: Player is not registered to the plugin!");
event.setCancelled(true);
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Dropped " + amount + " extra ores.");
}
} , 2L);
}
} else {
sendDebug(event.getPlayer(), "BlockBreakEvent: " + "Not a mining block.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ public void onBlockExplode(final EntityExplodeEvent event) {
ArrayList<Block> blocksToRemove = new ArrayList<>();
for(final Block block : event.blockList()) {
MiningBlock miningBlock = MiningBlock.getMiningBlock(block.getType());
MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(playerWithTNT.getPlayer().getUniqueId());
if(miningBlock != null && miningPlayer != null) {
MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(playerWithTNT.getPlayer().getUniqueId());
if(miningBlock != null) {
if(miningPlayer.getLevel().getOrdinal() < miningBlock.getMinLevel()) {
blocksToRemove.add(block);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ public boolean persist(){

@Override
public String onRequest(OfflinePlayer player, @NotNull String identifier) {
MiningPlayer miningPlayer = MiningPlayer.getMiningPlayer(player.getUniqueId());
if(miningPlayer == null) return "error";
MiningPlayer miningPlayer = MiningPlayer.getOrCreateMiningPlayer(player.getUniqueId());
switch (identifier) {
case "player_name" -> {
return miningPlayer.getOfflinePlayer().getName();
Expand Down