v1.1 - mcMMO Herbalism integration
- Added reflection-based McMMOIntegration utility - Added McMMO_Integration toggle to config (default: true) - Added mcMMO Herbalism XP award per harvest (will try to read from mcMMO's experience.yml, i think) - Added Double Drops passive skill support (scales to level 1000) - Added Verdant Bounty passive skill support (scales to level 10000) - Added particle effects (HAPPY_VILLAGER + HEART) on skill procs
This commit is contained in:
parent
5802de069b
commit
40faf76928
7 changed files with 333 additions and 7 deletions
|
|
@ -8,6 +8,7 @@ simple plugin to right click harvest crops
|
|||
- Replants automatically
|
||||
- Supports foila and paper! Mostly tested!
|
||||
- Can be configured with the json (check below)
|
||||
- mcmmo integration
|
||||
|
||||
## configuration
|
||||
|
||||
|
|
@ -17,7 +18,8 @@ The plugin generates a `harvester.json` file in its data folder:
|
|||
- `Use_Durability`: (true/false) Whether the hoe should lose durability.
|
||||
- `DropTo`: ('ground' or 'inventory') Where to put harvested items.
|
||||
- `Require_Hoe`: (true/false) Whether a hoe is required to harvest.
|
||||
- **Inventory Full Notification**: If `DropTo` is set to `inventory` and the player's inventory is full, an action bar message will appear and items will drop to the ground.
|
||||
- `Inventory Full Notification`: If `DropTo` is set to `inventory` and the player's inventory is full, an action bar message will appear and items will drop to the ground.
|
||||
- `McMMO_Integration`: (true/false): enables integration with McMMO.
|
||||
|
||||
## supported Crops
|
||||
|
||||
|
|
|
|||
6
pom.xml
6
pom.xml
|
|
@ -6,10 +6,10 @@
|
|||
|
||||
<groupId>test</groupId>
|
||||
<artifactId>harvester</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<version>1.1</version>
|
||||
|
||||
<name>Right Click Harvester</name>
|
||||
<url>https://example.com</url>
|
||||
<url>https://coldfiles.dev</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
|
@ -53,4 +53,4 @@
|
|||
<plugins>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.bukkit.event.player.PlayerInteractEvent;
|
|||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.Damageable;
|
||||
import test.managers.ConfigManager;
|
||||
import test.utils.McMMOIntegration;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
|
@ -54,6 +55,16 @@ public class CropHarvestListener implements Listener {
|
|||
Collection<ItemStack> drops = block.getDrops(itemInHand);
|
||||
Material seedType = getSeedType(block.getType());
|
||||
|
||||
// Check for mcMMO Double Drops / Verdant Bounty before processing drops
|
||||
boolean doubleDrops = false;
|
||||
boolean tripleDrops = false;
|
||||
if (config.McMMO_Integration && McMMOIntegration.tryEnable()) {
|
||||
doubleDrops = McMMOIntegration.checkDoubleDrops(player);
|
||||
if (doubleDrops) {
|
||||
tripleDrops = McMMOIntegration.checkVerdantBounty(player);
|
||||
}
|
||||
}
|
||||
|
||||
for (ItemStack drop : drops) {
|
||||
// If Drop_Seeds is false, skip seeds
|
||||
if (!config.Drop_Seeds && drop.getType() == seedType) {
|
||||
|
|
@ -72,6 +83,41 @@ public class CropHarvestListener implements Listener {
|
|||
} else {
|
||||
block.getWorld().dropItemNaturally(block.getLocation(), drop);
|
||||
}
|
||||
|
||||
// mcMMO Double Drops: drop an extra copy of the item
|
||||
if (doubleDrops && drop.getType() != seedType) {
|
||||
ItemStack bonus = drop.clone();
|
||||
if (config.DropTo.equalsIgnoreCase("inventory")) {
|
||||
Map<Integer, ItemStack> leftover = player.getInventory().addItem(bonus);
|
||||
if (!leftover.isEmpty()) {
|
||||
for (ItemStack item : leftover.values()) {
|
||||
block.getWorld().dropItemNaturally(block.getLocation(), item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
block.getWorld().dropItemNaturally(block.getLocation(), bonus);
|
||||
}
|
||||
|
||||
// Triple Drops: drop a second extra copy
|
||||
if (tripleDrops) {
|
||||
ItemStack triple = drop.clone();
|
||||
if (config.DropTo.equalsIgnoreCase("inventory")) {
|
||||
Map<Integer, ItemStack> leftover = player.getInventory().addItem(triple);
|
||||
if (!leftover.isEmpty()) {
|
||||
for (ItemStack item : leftover.values()) {
|
||||
block.getWorld().dropItemNaturally(block.getLocation(), item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
block.getWorld().dropItemNaturally(block.getLocation(), triple);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn particles if mcMMO skills procced
|
||||
if (doubleDrops) {
|
||||
McMMOIntegration.spawnSkillParticles(block.getLocation(), tripleDrops);
|
||||
}
|
||||
|
||||
// Reset age to 0 (freshly planted)
|
||||
|
|
@ -88,7 +134,19 @@ public class CropHarvestListener implements Listener {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Award mcMMO Herbalism XP from mcMMO's own experience config
|
||||
if (config.McMMO_Integration) {
|
||||
int xpAmount = McMMOIntegration.getHerbalismXpFor(block.getType());
|
||||
if (xpAmount > 0) {
|
||||
int result = McMMOIntegration.addHerbalismXp(player, xpAmount);
|
||||
if (result < 0) {
|
||||
player.getServer().getLogger().warning("[Harvester] McMMO errored or not installed, Disabling McMMO integration...");
|
||||
config.McMMO_Integration = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,5 +73,8 @@ public class ConfigManager {
|
|||
|
||||
public String _comment4 = "Require_Hoe: Whether a hoe is required to harvest (true/false)";
|
||||
public boolean Require_Hoe = true;
|
||||
|
||||
public String _comment5 = "McMMO_Integration: Enable mcMMO Herbalism XP and passive skills (true/false)";
|
||||
public boolean McMMO_Integration = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
261
src/main/java/test/utils/McMMOIntegration.java
Normal file
261
src/main/java/test/utils/McMMOIntegration.java
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
package test.utils;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Reflection-based mcMMO integration. No compile-time dependency required.
|
||||
* If mcMMO is not present at runtime, all calls are safely no-ops.
|
||||
* <p>
|
||||
* Uses mcMMO's own XP values from experience.yml, checks for Double Drops
|
||||
* and Verdant Bounty passive skills, and spawns particles on proc.
|
||||
*/
|
||||
public class McMMOIntegration {
|
||||
|
||||
private static boolean enabled = false;
|
||||
private static boolean checked = false;
|
||||
|
||||
// Cached reflection methods
|
||||
private static Method addRawXpMethod;
|
||||
private static Method getUserManagerGetPlayer;
|
||||
private static Method getExperienceConfigInstance;
|
||||
private static Method getXpMethod;
|
||||
private static Method hasUnlockedSubskill;
|
||||
private static Method isSubSkillEnabled;
|
||||
private static Method getPlayerLevel;
|
||||
private static Object experienceConfigInstance;
|
||||
|
||||
// Cached enum values
|
||||
private static Object primarySkillHerbalism;
|
||||
private static Object subSkillDoubleDrops;
|
||||
private static Object subSkillVerdantBounty;
|
||||
|
||||
/**
|
||||
* Attempt to find and cache mcMMO API methods.
|
||||
* Uses the mcMMO plugin's classloader to bypass classloader isolation.
|
||||
*/
|
||||
public static boolean tryEnable() {
|
||||
if (checked) {
|
||||
return enabled;
|
||||
}
|
||||
checked = true;
|
||||
|
||||
try {
|
||||
ClassLoader mcmmoLoader = getMcMMOClassLoader();
|
||||
if (mcmmoLoader == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cache PrimarySkillType.HERBALISM
|
||||
Class<?> primarySkillType = Class.forName("com.gmail.nossr50.datatypes.skills.PrimarySkillType", true, mcmmoLoader);
|
||||
primarySkillHerbalism = Enum.valueOf((Class<Enum>) primarySkillType, "HERBALISM");
|
||||
|
||||
// Cache SubSkillType values
|
||||
Class<?> subSkillType = Class.forName("com.gmail.nossr50.datatypes.skills.SubSkillType", true, mcmmoLoader);
|
||||
subSkillDoubleDrops = Enum.valueOf((Class<Enum>) subSkillType, "HERBALISM_DOUBLE_DROPS");
|
||||
subSkillVerdantBounty = Enum.valueOf((Class<Enum>) subSkillType, "HERBALISM_VERDANT_BOUNTY");
|
||||
|
||||
// Cache ExperienceAPI.addRawXP(Player, String, float, String, boolean)
|
||||
Class<?> experienceAPI = Class.forName("com.gmail.nossr50.api.ExperienceAPI", true, mcmmoLoader);
|
||||
addRawXpMethod = experienceAPI.getMethod("addRawXP", Player.class, String.class, float.class, String.class, boolean.class);
|
||||
|
||||
// Cache UserManager.getPlayer(Player)
|
||||
Class<?> userManager = Class.forName("com.gmail.nossr50.util.player.UserManager", true, mcmmoLoader);
|
||||
getUserManagerGetPlayer = userManager.getMethod("getPlayer", Player.class);
|
||||
|
||||
// Cache ExperienceConfig.getInstance() and getXp(PrimarySkillType, Material)
|
||||
Class<?> experienceConfig = Class.forName("com.gmail.nossr50.config.experience.ExperienceConfig", true, mcmmoLoader);
|
||||
getExperienceConfigInstance = experienceConfig.getMethod("getInstance");
|
||||
getXpMethod = experienceConfig.getMethod("getXp", primarySkillType, Material.class);
|
||||
experienceConfigInstance = getExperienceConfigInstance.invoke(null);
|
||||
|
||||
// Cache RankUtils.hasUnlockedSubskill(Player, SubSkillType)
|
||||
Class<?> rankUtils = Class.forName("com.gmail.nossr50.util.skills.RankUtils", true, mcmmoLoader);
|
||||
hasUnlockedSubskill = rankUtils.getMethod("hasUnlockedSubskill", Player.class, subSkillType);
|
||||
|
||||
// Cache Permissions.isSubSkillEnabled(Permissible, SubSkillType)
|
||||
Class<?> permissions = Class.forName("com.gmail.nossr50.util.Permissions", true, mcmmoLoader);
|
||||
Class<?> permissible = Class.forName("org.bukkit.permissions.Permissible", true, mcmmoLoader);
|
||||
isSubSkillEnabled = permissions.getMethod("isSubSkillEnabled", permissible, subSkillType);
|
||||
|
||||
// Cache ExperienceAPI.getLevel(Player, PrimarySkillType)
|
||||
getPlayerLevel = experienceAPI.getMethod("getLevel", Player.class, primarySkillType);
|
||||
|
||||
enabled = true;
|
||||
Bukkit.getLogger().info("[Harvester] mcMMO integration enabled!");
|
||||
} catch (Exception e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "[Harvester] Failed to hook mcMMO API: " + e.getMessage());
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player has their mcMMO data loaded.
|
||||
*/
|
||||
private static boolean isPlayerDataLoaded(Player player) {
|
||||
if (!tryEnable()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Object mmoPlayer = getUserManagerGetPlayer.invoke(null, player);
|
||||
return mmoPlayer != null;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mcMMO-configured XP value for a given crop material.
|
||||
*
|
||||
* @param cropMaterial the block material to look up
|
||||
* @return the XP value from mcMMO's experience.yml, or 0 if unavailable
|
||||
*/
|
||||
public static int getHerbalismXpFor(Material cropMaterial) {
|
||||
if (!tryEnable()) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return (int) getXpMethod.invoke(experienceConfigInstance, primarySkillHerbalism, cropMaterial);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the player's Double Drops passive should proc.
|
||||
* Uses mcMMO's own rank/permission checks and probability scaling.
|
||||
*
|
||||
* @param player the player to check
|
||||
* @return true if double drops should activate
|
||||
*/
|
||||
public static boolean checkDoubleDrops(Player player) {
|
||||
if (!tryEnable() || !isPlayerDataLoaded(player)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// Check if the subskill is unlocked and enabled
|
||||
boolean unlocked = (boolean) hasUnlockedSubskill.invoke(null, player, subSkillDoubleDrops);
|
||||
boolean enabled_ = (boolean) isSubSkillEnabled.invoke(null, player, subSkillDoubleDrops);
|
||||
if (!unlocked || !enabled_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get player level and calculate chance (0% at 0, 100% at 1000)
|
||||
int level = (int) getPlayerLevel.invoke(null, player, primarySkillHerbalism);
|
||||
double chance = Math.min(100.0, (level / 1000.0) * 100.0);
|
||||
return ThreadLocalRandom.current().nextDouble(100.0) < chance;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the player's Verdant Bounty passive should proc (triple drops).
|
||||
* Only rolls if double drops already procced.
|
||||
* Scales from 0% to 50% at level 10000.
|
||||
*
|
||||
* @param player the player to check
|
||||
* @return true if verdant bounty should activate
|
||||
*/
|
||||
public static boolean checkVerdantBounty(Player player) {
|
||||
if (!tryEnable() || !isPlayerDataLoaded(player)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// Check if the subskill is unlocked and enabled
|
||||
boolean unlocked = (boolean) hasUnlockedSubskill.invoke(null, player, subSkillVerdantBounty);
|
||||
boolean enabled_ = (boolean) isSubSkillEnabled.invoke(null, player, subSkillVerdantBounty);
|
||||
if (!unlocked || !enabled_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get player level and calculate chance (0% at 0, 50% at 10000)
|
||||
int level = (int) getPlayerLevel.invoke(null, player, primarySkillHerbalism);
|
||||
double chance = Math.min(50.0, (level / 10000.0) * 50.0);
|
||||
return ThreadLocalRandom.current().nextDouble(100.0) < chance;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Award Herbalism XP to a player using mcMMO's own XP pipeline.
|
||||
*
|
||||
* @param player the player to award XP to
|
||||
* @param amount the amount of XP to award
|
||||
* @return 1 if XP was awarded, 0 if player data not loaded yet (retry later), -1 on error (disable)
|
||||
*/
|
||||
public static int addHerbalismXp(Player player, int amount) {
|
||||
if (!tryEnable()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!isPlayerDataLoaded(player)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
addRawXpMethod.invoke(null, player, "HERBALISM", (float) amount, "PVE", false);
|
||||
return 1;
|
||||
} catch (Exception e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "[Harvester] mcMMO XP award failed: " + e.getMessage());
|
||||
enabled = false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn particles at a block location to indicate skill procs.
|
||||
*
|
||||
* @param location the block location
|
||||
* @param isTriple true for triple drop particles, false for double drop
|
||||
*/
|
||||
public static void spawnSkillParticles(Location location, boolean isTriple) {
|
||||
if (location == null || location.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
Location center = location.clone().add(0.5, 0.5, 0.5);
|
||||
|
||||
if (isTriple) {
|
||||
// Triple drops: bigger particle burst
|
||||
location.getWorld().spawnParticle(
|
||||
Particle.HAPPY_VILLAGER,
|
||||
center, 20, 0.4, 0.4, 0.4, 0.1
|
||||
);
|
||||
location.getWorld().spawnParticle(
|
||||
Particle.HEART,
|
||||
center, 8, 0.3, 0.3, 0.3, 0.01
|
||||
);
|
||||
} else {
|
||||
// Double drops: smaller particle burst
|
||||
location.getWorld().spawnParticle(
|
||||
Particle.HAPPY_VILLAGER,
|
||||
center, 10, 0.3, 0.3, 0.3, 0.05
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static ClassLoader getMcMMOClassLoader() {
|
||||
org.bukkit.plugin.Plugin mcmmo = Bukkit.getPluginManager().getPlugin("mcMMO");
|
||||
if (mcmmo == null || !mcmmo.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
return mcmmo.getClass().getClassLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether mcMMO integration is currently active.
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
name: Harvester
|
||||
version: 1.0.0-SNAPSHOT
|
||||
version: 1.1
|
||||
main: test.Harvester
|
||||
api-version: '1.21'
|
||||
author: test
|
||||
folia-supported: true
|
||||
softdepend: [mcMMO]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
main: test.Harvester
|
||||
version: 1.0.0-SNAPSHOT
|
||||
version: 1.1
|
||||
name: Harvester
|
||||
author: test
|
||||
api-version: 1.21
|
||||
softdepend: [mcMMO]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue