diff --git a/Human-Tests.md b/Human-Tests.md
new file mode 100644
index 0000000..9cf7ad8
--- /dev/null
+++ b/Human-Tests.md
@@ -0,0 +1,41 @@
+# MobScores Human Testing
+
+Use this checklist on a disposable Paper 26.2 or 26.3 server with Java 25+, MobScores, and ScoreKeeper 0.2.2 or newer installed. MobScores depends on ScoreKeeper for its `mob-scores` bucket, and its main behavior is driven by real combat and player-join events.
+
+## Test Setup
+
+- Back up the world and both plugin data folders. Configure a test server with the same plugin versions intended for production.
+- Use at least two ordinary player accounts and an operator for setup. Keep the server log visible to catch dependency, config, and offline-award warnings.
+- Save the generated `plugins/MobScores/config.yml` before changing it. Restart after config edits; MobScores reads the score table during plugin startup.
+- Record player UUIDs, mob types, configured values, active score buckets, and scores before and after each kill.
+
+## Human Checklist
+
+### Plugin loading and configuration
+
+- Start with both plugins installed. Confirm ScoreKeeper loads before MobScores, MobScores enables, and the `mob-scores` bucket exists with the expected labels and initial value.
+- On a disposable server, test startup without ScoreKeeper and with a ScoreKeeper version older than 0.2.2. Paper should reject the missing hard dependency; MobScores should disable itself with a version warning for an outdated dependency. Then restore the supported version.
+- On first startup, inspect the generated `ScoreTable`. Confirm the documented default entity values are present, including a zero-valued entry.
+- Change a few values, restart, and verify they are loaded and preserved. Include a zero value and an unknown entity key; confirm zero-valued entities are omitted from join messages and invalid entries are logged without preventing valid entries from loading.
+- Confirm the table uses Paper `EntityType` names and that malformed values or a missing score-table section are handled as intended.
+
+### Kill credit and scoring
+
+- As a player, kill configured entities using direct melee damage and player-fired projectiles. Confirm the expected points are added to that player's `mob-scores` score.
+- Check the boundary cases: a non-player attacker, environmental death, an entity type absent from the table, and a configured zero-point entity. Confirm no unintended points are awarded.
+- Have two players damage the same mob before it dies. The current implementation records the most recent direct or player-projectile attacker; verify that this credit policy matches the server's expectations.
+- Damage a mob with a player, then let it die from fall, fire, or another environmental cause. Confirm whether retaining that player's credit is the intended behavior; the current implementation keeps the last recorded player attribution until the mob dies.
+- Test a player who disconnects after damaging the mob but before it dies. The current implementation cannot award an offline player and logs a warning; confirm this is acceptable and that no other player's score changes.
+- Check mob death without any previous qualifying player damage, and verify no stale or reused entity attribution grants points.
+
+### Player messaging and ScoreKeeper workflows
+
+- Join with a player account. Confirm the MobScores score table is sent once, includes configured nonzero entity types with the right values, and excludes zero-valued types.
+- Run `/score-bucket mob-scores` followed by `/score-get`. Confirm the score reflects kills and the bucket switch message uses the expected singular/plural label. Verify MobScores does not change the player's active bucket automatically on join or kill.
+- Give two players different kill totals and switch between `mob-scores` and another ScoreKeeper bucket. Confirm each bucket retains its own value and MobScores awards only to `mob-scores`.
+- Restart the server and verify MobScores retains its configured table and ScoreKeeper retains the players' mob scores. Reconnect and confirm the join table still matches the active config.
+- Exercise normal gameplay around claimed mobs: unload/reload chunks, kill several entities rapidly, and test multiplayer combat. Watch for missed, duplicated, or cross-player awards.
+
+## Automated Coverage
+
+There are currently no Java test sources in MobScores and no `integrationTest` source set/task. Although Gradle declares JUnit, `./gradlew test` currently has no MobScores behavior tests. The testing tasks marked “Ready” in `TODO.md` have not been implemented. Build success alone does not verify real event attribution or compatibility with the installed ScoreKeeper jar.
diff --git a/build.gradle b/build.gradle
index b812a94..66614dd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,6 +5,7 @@
*/
plugins {
+ id 'com.diffplug.spotless' version '8.10.3'
id 'java-library'
id 'maven-publish'
id 'org.danilopianini.git-sensitive-semantic-versioning' version '7.0.24'
@@ -91,3 +92,14 @@ processResources {
expand(version: pluginVersion)
}
}
+
+spotless {
+ java {
+ googleJavaFormat('1.36.1')
+ importOrder 'java', 'javax', 'com', 'net', 'org', ''
+ licenseHeaderFile 'config/license-header.txt'
+ target 'src/**/*.java'
+ removeUnusedImports()
+ toggleOffOn()
+ }
+}
diff --git a/License-Header.txt b/config/license-header.txt
similarity index 54%
rename from License-Header.txt
rename to config/license-header.txt
index 9d44058..43729d6 100644
--- a/License-Header.txt
+++ b/config/license-header.txt
@@ -1,17 +1,17 @@
/*
-This file is part of MobScores.
+This file is part of Mob Scores.
-MobScores is free software: you can redistribute it and/or modify
+Mob Scores is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
-MobScores is distributed in the hope that it will be useful,
+Mob Scores is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
-along with MobScores. If not, see .
+along with Mob Scores. If not, see .
*/
diff --git a/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/MobScoresPlugin.java b/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/MobScoresPlugin.java
index 4fdf3ba..0bacaab 100644
--- a/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/MobScoresPlugin.java
+++ b/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/MobScoresPlugin.java
@@ -1,263 +1,268 @@
-/*
-This file is part of MobScores.
-
-MobScores is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-MobScores is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with MobScores. If not, see .
-*/
-
-package com.majinnaibu.minecraft.plugins.mobscores;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-import java.util.logging.Level;
-
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.EntityType;
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.PluginManager;
-import org.bukkit.plugin.java.JavaPlugin;
-
-import com.majinnaibu.minecraft.plugins.mobscores.listeners.MobDeathListener;
-import com.majinnaibu.minecraft.plugins.mobscores.listeners.PlayerConnectListener;
-import com.majinnaibu.minecraft.plugins.scorekeeper.ScoreKeeperPlugin;
-
-public class MobScoresPlugin extends JavaPlugin {
- private static final String SCORE_BUCKET_ID = "mob-scores";
- private static final String MIN_SCORE_KEEPER_VERSION = "0.2.2";
-
- private Map _claimedMobs = new HashMap();
- private Map _scoreTable = new HashMap();
- private ScoreKeeperPlugin _scoreKeeper = null;
-
- private final String _logPrefix = "[MobScores] ";
- private final Component _messagePrefix = Component.text("[")
- .append(Component.text("MobScores").color(NamedTextColor.AQUA))
- .append(Component.text("] ").color(NamedTextColor.WHITE));
-
- @Override
- public void onDisable() {
- getConfig().set("ScoreTable", serializeScoreTable(_scoreTable));
- saveConfig();
- }
-
- @Override
- public void onEnable() {
- // Create the default config if it doesn't exist.
- saveDefaultConfig();
-
- // Load our score table from config or set defaults.
- if (getConfig().isConfigurationSection("ScoreTable")) {
- _scoreTable = deserializeScoreTable(getConfig().getConfigurationSection("ScoreTable").getValues(false));
- } else {
- _scoreTable = getDefaultScoreTable();
- getConfig().set("ScoreTable", serializeScoreTable(_scoreTable));
- saveConfig();
- }
-
- PluginManager pm = getServer().getPluginManager();
- ScoreKeeperPlugin scoreKeeper = (ScoreKeeperPlugin)pm.getPlugin("ScoreKeeper");
-
- if (scoreKeeper == null) {
- logWarning("Unable to find ScoreKeeper plugin.");
- pm.disablePlugin(this);
- return;
- }
-
- String scoreKeeperVersion = scoreKeeper.getPluginMeta().getVersion();
- if (!isVersionAtLeast(scoreKeeperVersion, MIN_SCORE_KEEPER_VERSION)) {
- logWarning(
- "ScoreKeeper "
- + MIN_SCORE_KEEPER_VERSION
- + " or newer is required; found "
- + scoreKeeperVersion
- + ".");
- pm.disablePlugin(this);
- return;
- }
- _scoreKeeper = scoreKeeper;
- if (_scoreKeeper.getBucket(SCORE_BUCKET_ID) == null) {
- _scoreKeeper.createBucket(SCORE_BUCKET_ID, "mob point", "mob points", 0);
- }
-
- pm.registerEvents(new MobDeathListener(this), this);
- pm.registerEvents(new PlayerConnectListener(this), this);
-
- logInfo(getPluginMeta().getName() + " version " + getPluginMeta().getVersion() + " is enabled!");
- }
-
- private HashMap getDefaultScoreTable() {
- HashMap scores = new HashMap();
-
- scores.put(EntityType.BLAZE, 100);
- scores.put(EntityType.CAVE_SPIDER, 100);
- scores.put(EntityType.CREEPER, 50);
- scores.put(EntityType.DROWNED, 25);
- scores.put(EntityType.ELDER_GUARDIAN, 250);
- scores.put(EntityType.ENDERMAN, 100);
- scores.put(EntityType.ENDERMITE, 1000);
- scores.put(EntityType.END_CRYSTAL, 250);
- scores.put(EntityType.GHAST, 250);
- scores.put(EntityType.GIANT, 250);
- scores.put(EntityType.GUARDIAN, 250);
- scores.put(EntityType.HUSK, 25);
- scores.put(EntityType.PILLAGER, 25);
- scores.put(EntityType.RAVAGER, 50);
- scores.put(EntityType.SHULKER, 50);
- scores.put(EntityType.SILVERFISH, 0);
- scores.put(EntityType.SKELETON, 50);
- scores.put(EntityType.WITHER_SKELETON, 100);
- scores.put(EntityType.ZOMBIE, 25);
- scores.put(EntityType.SLIME, 100);
- scores.put(EntityType.SPIDER, 50);
-
- return scores;
- }
-
- public void claimMob(Entity entity, Player damager) {
- if (_scoreTable.containsKey(entity.getType())) {
- _claimedMobs.put(entity.getUniqueId(), damager.getUniqueId());
- }
- }
-
- public void awardScore(Entity entity) {
- UUID playerId = _claimedMobs.remove(entity.getUniqueId());
- if(playerId != null){
- EntityType type = entity.getType();
- if (_scoreTable.containsKey(type)) {
- Player player = getServer().getPlayer(playerId);
- if (player == null) {
- logWarning("Unable to award score because the credited player is offline.");
- return;
- }
- int score = _scoreTable.get(type);
- _scoreKeeper.addScore(player, SCORE_BUCKET_ID, score);
- } else {
- logWarning("Unable to award score for {" + type.toString() + "}");
- }
- }
- }
-
- public void sendPlayerScoreTable(Player player) {
- for (Map.Entry entry : _scoreTable.entrySet()) {
- if (entry.getValue() != 0) {
- EntityType type = entry.getKey();
- Component message = Component.translatable(Objects.requireNonNull(type.translationKey()))
- .append(Component.text(" = "))
- .append(Component.text(Objects.requireNonNull(String.valueOf(entry.getValue())))
- .color(NamedTextColor.GREEN));
- sendPlayerMessage(player, message);
- }
- }
- }
-
- private Map deserializeScoreTable(Map rawValue) {
- HashMap scoreTable = new HashMap<>();
- if (rawValue instanceof Map) {
- scoreTable = new HashMap<>();
- for (Map.Entry entry : rawValue.entrySet()) {
- try {
- EntityType type = EntityType.valueOf(entry.getKey());
- Integer value = ((Number) entry.getValue()).intValue();
- scoreTable.put(type, value);
- } catch (Exception ex) {
- logError(ex);
- }
- }
- return scoreTable;
- } else {
- return getDefaultScoreTable();
- }
- }
-
- private Map serializeScoreTable(Map scoreTable) {
- Map serializable = new HashMap<>();
- for (Map.Entry entry : scoreTable.entrySet()) {
- serializable.put(entry.getKey().name(), entry.getValue());
- }
- return serializable;
- }
-
- public void sendPlayerMessage(Player player, Component message) {
- player.sendMessage(_messagePrefix.append(Objects.requireNonNull(message)));
- }
-
- public void sendPlayerMessage(Player player, String message) {
- player.sendMessage(_messagePrefix.append(Component.text(Objects.requireNonNull(message))));
- }
-
- public void logError(Exception ex) {
- getLogger().log(Level.SEVERE, _logPrefix + ex.toString());
- }
-
- public void logInfo(String messag) {
- getLogger().info(_logPrefix + messag);
- }
-
- public void logWarning(String message) {
- getLogger().warning(_logPrefix + message);
- }
-
- private boolean isVersionAtLeast(String version, String minimumVersion) {
- int[] actual = parseVersion(version);
- int[] minimum = parseVersion(minimumVersion);
- if (actual == null || minimum == null) {
- return false;
- }
- for (int index = 0; index < actual.length; index++) {
- if (actual[index] != minimum[index]) {
- return actual[index] > minimum[index];
- }
- }
- return !isPrerelease(version);
- }
-
- private int[] parseVersion(String version) {
- if (version == null) {
- return null;
- }
- String coreVersion = version.replaceFirst("^[vV]", "");
- int metadataIndex = coreVersion.indexOf('+');
- if (metadataIndex >= 0) {
- coreVersion = coreVersion.substring(0, metadataIndex);
- }
- int prereleaseIndex = coreVersion.indexOf('-');
- if (prereleaseIndex >= 0) {
- coreVersion = coreVersion.substring(0, prereleaseIndex);
- }
- String[] parts = coreVersion.split("\\.");
- if (parts.length < 3) {
- return null;
- }
- try {
- return new int[] {
- Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2])
- };
- } catch (NumberFormatException ex) {
- return null;
- }
- }
-
- private boolean isPrerelease(String version) {
- String release = version.replaceFirst("^[vV]", "");
- int metadataIndex = release.indexOf('+');
- if (metadataIndex >= 0) {
- release = release.substring(0, metadataIndex);
- }
- return release.indexOf('-') >= 0;
- }
-}
+/*
+This file is part of Mob Scores.
+
+Mob Scores is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mob Scores is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with Mob Scores. If not, see .
+*/
+
+package com.majinnaibu.minecraft.plugins.mobscores;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.logging.Level;
+
+import com.majinnaibu.minecraft.plugins.mobscores.listeners.MobDeathListener;
+import com.majinnaibu.minecraft.plugins.mobscores.listeners.PlayerConnectListener;
+import com.majinnaibu.minecraft.plugins.scorekeeper.ScoreKeeperPlugin;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.PluginManager;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class MobScoresPlugin extends JavaPlugin {
+ private static final String SCORE_BUCKET_ID = "mob-scores";
+ private static final String MIN_SCORE_KEEPER_VERSION = "0.2.2";
+
+ private Map _claimedMobs = new HashMap();
+ private Map _scoreTable = new HashMap();
+ private ScoreKeeperPlugin _scoreKeeper = null;
+
+ private final String _logPrefix = "[MobScores] ";
+ private final Component _messagePrefix =
+ Component.text("[")
+ .append(Component.text("MobScores").color(NamedTextColor.AQUA))
+ .append(Component.text("] ").color(NamedTextColor.WHITE));
+
+ @Override
+ public void onDisable() {
+ getConfig().set("ScoreTable", serializeScoreTable(_scoreTable));
+ saveConfig();
+ }
+
+ @Override
+ public void onEnable() {
+ // Create the default config if it doesn't exist.
+ saveDefaultConfig();
+
+ // Load our score table from config or set defaults.
+ if (getConfig().isConfigurationSection("ScoreTable")) {
+ _scoreTable =
+ deserializeScoreTable(getConfig().getConfigurationSection("ScoreTable").getValues(false));
+ } else {
+ _scoreTable = getDefaultScoreTable();
+ getConfig().set("ScoreTable", serializeScoreTable(_scoreTable));
+ saveConfig();
+ }
+
+ PluginManager pm = getServer().getPluginManager();
+ ScoreKeeperPlugin scoreKeeper = (ScoreKeeperPlugin) pm.getPlugin("ScoreKeeper");
+
+ if (scoreKeeper == null) {
+ logWarning("Unable to find ScoreKeeper plugin.");
+ pm.disablePlugin(this);
+ return;
+ }
+
+ String scoreKeeperVersion = scoreKeeper.getPluginMeta().getVersion();
+ if (!isVersionAtLeast(scoreKeeperVersion, MIN_SCORE_KEEPER_VERSION)) {
+ logWarning(
+ "ScoreKeeper "
+ + MIN_SCORE_KEEPER_VERSION
+ + " or newer is required; found "
+ + scoreKeeperVersion
+ + ".");
+ pm.disablePlugin(this);
+ return;
+ }
+ _scoreKeeper = scoreKeeper;
+ if (_scoreKeeper.getBucket(SCORE_BUCKET_ID) == null) {
+ _scoreKeeper.createBucket(SCORE_BUCKET_ID, "mob point", "mob points", 0);
+ }
+
+ pm.registerEvents(new MobDeathListener(this), this);
+ pm.registerEvents(new PlayerConnectListener(this), this);
+
+ logInfo(
+ getPluginMeta().getName() + " version " + getPluginMeta().getVersion() + " is enabled!");
+ }
+
+ private HashMap getDefaultScoreTable() {
+ HashMap scores = new HashMap();
+
+ scores.put(EntityType.BLAZE, 100);
+ scores.put(EntityType.CAVE_SPIDER, 100);
+ scores.put(EntityType.CREEPER, 50);
+ scores.put(EntityType.DROWNED, 25);
+ scores.put(EntityType.ELDER_GUARDIAN, 250);
+ scores.put(EntityType.ENDERMAN, 100);
+ scores.put(EntityType.ENDERMITE, 1000);
+ scores.put(EntityType.END_CRYSTAL, 250);
+ scores.put(EntityType.GHAST, 250);
+ scores.put(EntityType.GIANT, 250);
+ scores.put(EntityType.GUARDIAN, 250);
+ scores.put(EntityType.HUSK, 25);
+ scores.put(EntityType.PILLAGER, 25);
+ scores.put(EntityType.RAVAGER, 50);
+ scores.put(EntityType.SHULKER, 50);
+ scores.put(EntityType.SILVERFISH, 0);
+ scores.put(EntityType.SKELETON, 50);
+ scores.put(EntityType.WITHER_SKELETON, 100);
+ scores.put(EntityType.ZOMBIE, 25);
+ scores.put(EntityType.SLIME, 100);
+ scores.put(EntityType.SPIDER, 50);
+
+ return scores;
+ }
+
+ public void claimMob(Entity entity, Player damager) {
+ if (_scoreTable.containsKey(entity.getType())) {
+ _claimedMobs.put(entity.getUniqueId(), damager.getUniqueId());
+ }
+ }
+
+ public void awardScore(Entity entity) {
+ UUID playerId = _claimedMobs.remove(entity.getUniqueId());
+ if (playerId != null) {
+ EntityType type = entity.getType();
+ if (_scoreTable.containsKey(type)) {
+ Player player = getServer().getPlayer(playerId);
+ if (player == null) {
+ logWarning("Unable to award score because the credited player is offline.");
+ return;
+ }
+ int score = _scoreTable.get(type);
+ _scoreKeeper.addScore(player, SCORE_BUCKET_ID, score);
+ } else {
+ logWarning("Unable to award score for {" + type.toString() + "}");
+ }
+ }
+ }
+
+ public void sendPlayerScoreTable(Player player) {
+ for (Map.Entry entry : _scoreTable.entrySet()) {
+ if (entry.getValue() != 0) {
+ EntityType type = entry.getKey();
+ Component message =
+ Component.translatable(Objects.requireNonNull(type.translationKey()))
+ .append(Component.text(" = "))
+ .append(
+ Component.text(Objects.requireNonNull(String.valueOf(entry.getValue())))
+ .color(NamedTextColor.GREEN));
+ sendPlayerMessage(player, message);
+ }
+ }
+ }
+
+ private Map deserializeScoreTable(Map rawValue) {
+ HashMap scoreTable = new HashMap<>();
+ if (rawValue instanceof Map) {
+ scoreTable = new HashMap<>();
+ for (Map.Entry entry : rawValue.entrySet()) {
+ try {
+ EntityType type = EntityType.valueOf(entry.getKey());
+ Integer value = ((Number) entry.getValue()).intValue();
+ scoreTable.put(type, value);
+ } catch (Exception ex) {
+ logError(ex);
+ }
+ }
+ return scoreTable;
+ } else {
+ return getDefaultScoreTable();
+ }
+ }
+
+ private Map serializeScoreTable(Map scoreTable) {
+ Map serializable = new HashMap<>();
+ for (Map.Entry entry : scoreTable.entrySet()) {
+ serializable.put(entry.getKey().name(), entry.getValue());
+ }
+ return serializable;
+ }
+
+ public void sendPlayerMessage(Player player, Component message) {
+ player.sendMessage(_messagePrefix.append(Objects.requireNonNull(message)));
+ }
+
+ public void sendPlayerMessage(Player player, String message) {
+ player.sendMessage(_messagePrefix.append(Component.text(Objects.requireNonNull(message))));
+ }
+
+ public void logError(Exception ex) {
+ getLogger().log(Level.SEVERE, _logPrefix + ex.toString());
+ }
+
+ public void logInfo(String messag) {
+ getLogger().info(_logPrefix + messag);
+ }
+
+ public void logWarning(String message) {
+ getLogger().warning(_logPrefix + message);
+ }
+
+ private boolean isVersionAtLeast(String version, String minimumVersion) {
+ int[] actual = parseVersion(version);
+ int[] minimum = parseVersion(minimumVersion);
+ if (actual == null || minimum == null) {
+ return false;
+ }
+ for (int index = 0; index < actual.length; index++) {
+ if (actual[index] != minimum[index]) {
+ return actual[index] > minimum[index];
+ }
+ }
+ return !isPrerelease(version);
+ }
+
+ private int[] parseVersion(String version) {
+ if (version == null) {
+ return null;
+ }
+ String coreVersion = version.replaceFirst("^[vV]", "");
+ int metadataIndex = coreVersion.indexOf('+');
+ if (metadataIndex >= 0) {
+ coreVersion = coreVersion.substring(0, metadataIndex);
+ }
+ int prereleaseIndex = coreVersion.indexOf('-');
+ if (prereleaseIndex >= 0) {
+ coreVersion = coreVersion.substring(0, prereleaseIndex);
+ }
+ String[] parts = coreVersion.split("\\.");
+ if (parts.length < 3) {
+ return null;
+ }
+ try {
+ return new int[] {
+ Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2])
+ };
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+
+ private boolean isPrerelease(String version) {
+ String release = version.replaceFirst("^[vV]", "");
+ int metadataIndex = release.indexOf('+');
+ if (metadataIndex >= 0) {
+ release = release.substring(0, metadataIndex);
+ }
+ return release.indexOf('-') >= 0;
+ }
+}
diff --git a/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/listeners/MobDeathListener.java b/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/listeners/MobDeathListener.java
index ea3fed0..46fc4a7 100644
--- a/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/listeners/MobDeathListener.java
+++ b/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/listeners/MobDeathListener.java
@@ -1,66 +1,64 @@
-/*
-This file is part of MobScores.
-
-MobScores is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-MobScores is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with MobScores. If not, see .
-*/
-
-package com.majinnaibu.minecraft.plugins.mobscores.listeners;
-
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.Player;
-import org.bukkit.entity.Projectile;
-import org.bukkit.event.entity.EntityDamageByEntityEvent;
-import org.bukkit.event.entity.EntityDamageEvent;
-import org.bukkit.event.entity.EntityDeathEvent;
-import org.bukkit.event.entity.EntityExplodeEvent;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-
-import com.majinnaibu.minecraft.plugins.mobscores.MobScoresPlugin;
-
-public class MobDeathListener implements Listener {
- private MobScoresPlugin _plugin = null;
-
- public MobDeathListener(MobScoresPlugin plugin) {
- _plugin = plugin;
- }
-
- @EventHandler
- public void onEntityDamage(EntityDamageEvent event) {
- if (!(event instanceof EntityDamageByEntityEvent damageEvent)) {
- return;
- }
- Entity damager = damageEvent.getDamager();
- Player creditedPlayer = null;
- if (damager instanceof Player directPlayer) {
- creditedPlayer = directPlayer;
- } else if (damager instanceof Projectile projectile
- && projectile.getShooter() instanceof Player projectileShooter) {
- creditedPlayer = projectileShooter;
- }
- if (creditedPlayer != null) {
- _plugin.claimMob(event.getEntity(), creditedPlayer);
- }
- }
-
- @EventHandler
- public void onEntityDeath(EntityDeathEvent event) {
- _plugin.awardScore(event.getEntity());
- }
-
- @EventHandler
- public void onEntityExplode(EntityExplodeEvent event) {
- }
-
-}
+/*
+This file is part of Mob Scores.
+
+Mob Scores is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mob Scores is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with Mob Scores. If not, see .
+*/
+
+package com.majinnaibu.minecraft.plugins.mobscores.listeners;
+
+import com.majinnaibu.minecraft.plugins.mobscores.MobScoresPlugin;
+
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Projectile;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.entity.EntityExplodeEvent;
+
+public class MobDeathListener implements Listener {
+ private MobScoresPlugin _plugin = null;
+
+ public MobDeathListener(MobScoresPlugin plugin) {
+ _plugin = plugin;
+ }
+
+ @EventHandler
+ public void onEntityDamage(EntityDamageEvent event) {
+ if (!(event instanceof EntityDamageByEntityEvent damageEvent)) {
+ return;
+ }
+ Entity damager = damageEvent.getDamager();
+ Player creditedPlayer = null;
+ if (damager instanceof Player directPlayer) {
+ creditedPlayer = directPlayer;
+ } else if (damager instanceof Projectile projectile
+ && projectile.getShooter() instanceof Player projectileShooter) {
+ creditedPlayer = projectileShooter;
+ }
+ if (creditedPlayer != null) {
+ _plugin.claimMob(event.getEntity(), creditedPlayer);
+ }
+ }
+
+ @EventHandler
+ public void onEntityDeath(EntityDeathEvent event) {
+ _plugin.awardScore(event.getEntity());
+ }
+
+ @EventHandler
+ public void onEntityExplode(EntityExplodeEvent event) {}
+}
diff --git a/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/listeners/PlayerConnectListener.java b/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/listeners/PlayerConnectListener.java
index cc38fda..52858c7 100644
--- a/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/listeners/PlayerConnectListener.java
+++ b/src/main/java/com/majinnaibu/minecraft/plugins/mobscores/listeners/PlayerConnectListener.java
@@ -1,37 +1,37 @@
-/*
-This file is part of MobScores.
-
-MobScores is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-MobScores is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with MobScores. If not, see .
-*/
-
-package com.majinnaibu.minecraft.plugins.mobscores.listeners;
-
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.event.Listener;
-import org.bukkit.event.EventHandler;
-
-import com.majinnaibu.minecraft.plugins.mobscores.MobScoresPlugin;
-
-public class PlayerConnectListener implements Listener {
-private MobScoresPlugin _plugin = null;
-
- public PlayerConnectListener(MobScoresPlugin plugin) {
- _plugin = plugin;
- }
-
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent event) {
- _plugin.sendPlayerScoreTable(event.getPlayer());
- }
-}
+/*
+This file is part of Mob Scores.
+
+Mob Scores is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mob Scores is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with Mob Scores. If not, see .
+*/
+
+package com.majinnaibu.minecraft.plugins.mobscores.listeners;
+
+import com.majinnaibu.minecraft.plugins.mobscores.MobScoresPlugin;
+
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+
+public class PlayerConnectListener implements Listener {
+ private MobScoresPlugin _plugin = null;
+
+ public PlayerConnectListener(MobScoresPlugin plugin) {
+ _plugin = plugin;
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ _plugin.sendPlayerScoreTable(event.getPlayer());
+ }
+}