From e2073ff9f7c8fe92ebf8c0fb785ce5cfba4958c0 Mon Sep 17 00:00:00 2001 From: nossr50 Date: Wed, 19 Feb 2020 15:58:53 -0800 Subject: [PATCH] Immature crop replanting, green thumb tweaks, replant accidental break protection --- Changelog.txt | 2 + .../gmail/nossr50/datatypes/meta/OldName.java | 1 + .../meta/RecentlyReplantedCropMeta.java | 17 ++++ src/main/java/com/gmail/nossr50/mcMMO.java | 1 + .../runnables/skills/DelayedCropReplant.java | 90 +++++++++++++++++++ .../skills/herbalism/HerbalismManager.java | 75 +++++++++++----- .../util/skills/ParticleEffectUtils.java | 15 +++- .../nossr50/util/sounds/SoundManager.java | 5 ++ 8 files changed, 179 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/gmail/nossr50/datatypes/meta/RecentlyReplantedCropMeta.java create mode 100644 src/main/java/com/gmail/nossr50/runnables/skills/DelayedCropReplant.java diff --git a/Changelog.txt b/Changelog.txt index 69da6676f..a10e35f58 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,4 +1,6 @@ Version 2.1.115 + Hoes no longer give free replants + There is now a feature in place to prevent breaking a newly automatically replanted (via green thumb) crop from being breakable for a few seconds after it appears Using a hoe on non-fully grown crops will replant them as a convenience feature for those who can't bother to wait for all of their plants to grow (put away the hoe to break non-fully grown crops) Fixed a bug where Salvage always gave the best results Fixed an issue with arrows causing exceptions with players not yet having data loaded diff --git a/src/main/java/com/gmail/nossr50/datatypes/meta/OldName.java b/src/main/java/com/gmail/nossr50/datatypes/meta/OldName.java index b300f92cc..137399009 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/meta/OldName.java +++ b/src/main/java/com/gmail/nossr50/datatypes/meta/OldName.java @@ -12,4 +12,5 @@ public class OldName extends FixedMetadataValue { { super(plugin, oldName); } + } diff --git a/src/main/java/com/gmail/nossr50/datatypes/meta/RecentlyReplantedCropMeta.java b/src/main/java/com/gmail/nossr50/datatypes/meta/RecentlyReplantedCropMeta.java new file mode 100644 index 000000000..5c630f7bc --- /dev/null +++ b/src/main/java/com/gmail/nossr50/datatypes/meta/RecentlyReplantedCropMeta.java @@ -0,0 +1,17 @@ +package com.gmail.nossr50.datatypes.meta; + +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.Plugin; + +public class RecentlyReplantedCropMeta extends FixedMetadataValue { + + /** + * Initializes a FixedMetadataValue with an Object + * + * @param owningPlugin the {@link Plugin} that created this metadata value + */ + public RecentlyReplantedCropMeta(Plugin owningPlugin) { + super(owningPlugin, true); + } + +} diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index b28777238..0306f6d68 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -107,6 +107,7 @@ public class mcMMO extends JavaPlugin { private static boolean isRetroModeEnabled; /* Metadata Values */ + public final static String REPLANT_META_KEY = "mcMMO: Recently Replanted"; public static final String FISH_HOOK_REF_METAKEY = "mcMMO: Fish Hook Tracker"; public static final String DODGE_TRACKER = "mcMMO: Dodge Tracker"; public static final String CUSTOM_DAMAGE_METAKEY = "mcMMO: Custom Damage"; diff --git a/src/main/java/com/gmail/nossr50/runnables/skills/DelayedCropReplant.java b/src/main/java/com/gmail/nossr50/runnables/skills/DelayedCropReplant.java new file mode 100644 index 000000000..d19411b5e --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/skills/DelayedCropReplant.java @@ -0,0 +1,90 @@ +package com.gmail.nossr50.runnables.skills; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.skills.ParticleEffectUtils; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.Ageable; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.scheduler.BukkitRunnable; + +public class DelayedCropReplant extends BukkitRunnable { + + private final int desiredCropAge; + private final Location cropLocation; + private final Material cropMaterial; + private boolean wasImmaturePlant; + private final BlockBreakEvent blockBreakEvent; + + /** + * Replants a crop after a delay setting the age to desiredCropAge + * @param cropState target {@link BlockState} + * @param desiredCropAge desired age of the crop + */ + public DelayedCropReplant(BlockBreakEvent blockBreakEvent, BlockState cropState, int desiredCropAge, boolean wasImmaturePlant) { + //The plant was either immature or something cancelled the event, therefor we need to treat it differently + this.blockBreakEvent = blockBreakEvent; + this.wasImmaturePlant = wasImmaturePlant; + this.cropMaterial = cropState.getType(); + this.desiredCropAge = desiredCropAge; + this.cropLocation = cropState.getLocation(); + } + + @Override + public void run() { + Block cropBlock = cropLocation.getBlock(); + BlockState currentState = cropBlock.getState(); + + if(blockBreakEvent.isCancelled()) { + wasImmaturePlant = true; + } + + //Two kinds of air in Minecraft + if(currentState.getType().equals(Material.AIR) || currentState.getType().equals(Material.CAVE_AIR)) { + //The space is not currently occupied by a block so we can fill it + cropBlock.setType(cropMaterial); + + //Get new state (necessary?) + BlockState newState = cropBlock.getState(); + newState.setType(cropMaterial); + newState.update(); + Ageable ageable = (Ageable) newState.getBlockData(); + + //Crop age should always be 0 if the plant was immature + if(wasImmaturePlant) { + ageable.setAge(0); + } else { + //Otherwise make the plant the desired age + ageable.setAge(desiredCropAge); + } + + //Age the crop + newState.setBlockData(ageable); + + //Play an effect + ParticleEffectUtils.playGreenThumbEffect(cropLocation); + + //Remove the metadata marking the block as recently replanted + new removePlantMeta(blockBreakEvent.getBlock().getLocation()).runTaskLater(mcMMO.p, 20); + } + + } + + private class removePlantMeta extends BukkitRunnable { + + private final Location cropLoc; + + public removePlantMeta(Location cropLoc) { + this.cropLoc = cropLoc; + } + + @Override + public void run() { + Block cropBlock = cropLoc.getBlock(); + cropBlock.removeMetadata(mcMMO.REPLANT_META_KEY, mcMMO.p); + } + } + +} diff --git a/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java b/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java index 3eb0951f2..69cd04b6e 100644 --- a/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java +++ b/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java @@ -7,6 +7,7 @@ import com.gmail.nossr50.datatypes.BlockSnapshot; import com.gmail.nossr50.datatypes.experience.XPGainReason; import com.gmail.nossr50.datatypes.experience.XPGainSource; import com.gmail.nossr50.datatypes.interactions.NotificationType; +import com.gmail.nossr50.datatypes.meta.RecentlyReplantedCropMeta; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType; @@ -14,6 +15,7 @@ import com.gmail.nossr50.datatypes.skills.SuperAbilityType; import com.gmail.nossr50.datatypes.skills.ToolType; import com.gmail.nossr50.datatypes.treasure.HylianTreasure; import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.runnables.skills.DelayedCropReplant; import com.gmail.nossr50.runnables.skills.DelayedHerbalismXPCheckTask; import com.gmail.nossr50.runnables.skills.HerbalismBlockUpdaterTask; import com.gmail.nossr50.skills.SkillManager; @@ -158,6 +160,13 @@ public class HerbalismManager extends SkillManager { private void processHerbalismOnBlocksBroken(BlockBreakEvent blockBreakEvent, HashSet brokenPlants) { BlockState originalBreak = blockBreakEvent.getBlock().getState(); + //Check if the plant was recently replanted + if(blockBreakEvent.getBlock().getMetadata(mcMMO.REPLANT_META_KEY).size() >= 1) { + //Crop is recently replanted to back out of destroying it + blockBreakEvent.setCancelled(true); + return; + } + //TODO: The design of Green Terra needs to change, this is a mess if(Permissions.greenThumbPlant(getPlayer(), originalBreak.getType())) { processGreenThumbPlants(originalBreak, blockBreakEvent, isGreenTerraActive()); @@ -639,6 +648,18 @@ public class HerbalismManager extends SkillManager { return Herbalism.convertShroomThumb(blockState); } + /** + * Starts the delayed replant task and turns + * @param desiredCropAge the desired age of the crop + * @param blockBreakEvent the {@link BlockBreakEvent} this crop was involved in + * @param cropState the {@link BlockState} of the crop + */ + private void startReplantTask(int desiredCropAge, BlockBreakEvent blockBreakEvent, BlockState cropState, boolean isImmature) { + //Mark the plant as recently replanted to avoid accidental breakage + blockBreakEvent.getBlock().setMetadata(mcMMO.REPLANT_META_KEY, new RecentlyReplantedCropMeta(mcMMO.p)); + new DelayedCropReplant(blockBreakEvent, cropState, desiredCropAge, isImmature).runTaskLater(mcMMO.p, 20 * 2); + } + /** * Process the Green Thumb ability for plants. * @@ -651,12 +672,13 @@ public class HerbalismManager extends SkillManager { if (!(blockData instanceof Ageable)) return; + Ageable ageable = (Ageable) blockData; + //If the ageable is NOT mature and the player is NOT using a hoe, abort - if(!isAgeableMature((Ageable) blockData) && !ItemUtils.isHoe(getPlayer().getItemInHand())) { + if(!isAgeableMature(ageable) && !ItemUtils.isHoe(getPlayer().getItemInHand())) { return; } - Player player = getPlayer(); PlayerInventory playerInventory = player.getInventory(); Material seed = null; @@ -696,31 +718,36 @@ public class HerbalismManager extends SkillManager { return; } - if (!processGrowingPlants(blockState, blockBreakEvent, greenTerra)) { + + if (!playerInventory.containsAtLeast(seedStack, 1)) { return; } - if(!ItemUtils.isHoe(getPlayer().getInventory().getItemInMainHand())) - { - if (!playerInventory.containsAtLeast(seedStack, 1)) { - return; - } - - playerInventory.removeItem(seedStack); - player.updateInventory(); // Needed until replacement available + if (!processGrowingPlants(blockState, ageable, blockBreakEvent, greenTerra)) { + return; } + playerInventory.removeItem(seedStack); + player.updateInventory(); // Needed until replacement available + new HerbalismBlockUpdaterTask(blockState).runTaskLater(mcMMO.p, 0); } - private boolean processGrowingPlants(BlockState blockState, BlockBreakEvent blockBreakEvent, boolean greenTerra) { - Ageable crops = (Ageable) blockState.getBlockData(); + private boolean processGrowingPlants(BlockState blockState, Ageable ageable, BlockBreakEvent blockBreakEvent, boolean greenTerra) { + //This check is needed + if(isBizarreAgeable(ageable)) { + return false; + } + + int finalAge = 0; int greenThumbStage = getGreenThumbStage(); //Immature plants will start over at 0 - if(!isAgeableMature(crops)) { - crops.setAge(0); + if(!isAgeableMature(ageable)) { blockBreakEvent.setCancelled(true); + startReplantTask(0, blockBreakEvent, blockState, true); + blockState.setType(Material.AIR); + blockState.update(); return true; } @@ -733,10 +760,10 @@ public class HerbalismManager extends SkillManager { case WHEAT: if (greenTerra) { - crops.setAge(3); + finalAge = 3; } else { - crops.setAge(greenThumbStage); + finalAge = getGreenThumbStage(); } break; @@ -744,30 +771,32 @@ public class HerbalismManager extends SkillManager { case NETHER_WART: if (greenTerra || greenThumbStage > 2) { - crops.setAge(2); + finalAge = 2; } else if (greenThumbStage == 2) { - crops.setAge(1); + finalAge = 1; } else { - crops.setAge(0); + finalAge = 0; } break; case COCOA: if (greenTerra || getGreenThumbStage() > 1) { - crops.setAge(1); + finalAge = 1; } else { - crops.setAge(0); + finalAge = 0; } break; default: return false; } - blockState.setBlockData(crops); + + //Start the delayed replant + startReplantTask(finalAge, blockBreakEvent, blockState, false); return true; } diff --git a/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java b/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java index 76cc10308..4ddf909af 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java +++ b/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java @@ -1,6 +1,8 @@ package com.gmail.nossr50.util.skills; import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.util.sounds.SoundManager; +import com.gmail.nossr50.util.sounds.SoundType; import org.bukkit.Effect; import org.bukkit.Location; import org.bukkit.Material; @@ -14,6 +16,12 @@ public final class ParticleEffectUtils { private ParticleEffectUtils() {}; + public static void playGreenThumbEffect(Location location) { + World world = location.getWorld(); + playSmokeEffect(location); + SoundManager.worldSendSoundMaxPitch(world, location, SoundType.POP); + } + public static void playBleedEffect(LivingEntity livingEntity) { if (!Config.getInstance().getBleedEffectEnabled()) { return; @@ -27,7 +35,7 @@ public final class ParticleEffectUtils { return; } - playSmokeEffect(player); + playSmokeEffect(player.getLocation()); } public static void playFluxEffect(Location location) { @@ -38,9 +46,8 @@ public final class ParticleEffectUtils { location.getWorld().playEffect(location, Effect.MOBSPAWNER_FLAMES, 1); } - public static void playSmokeEffect(LivingEntity livingEntity) { - Location location = livingEntity.getEyeLocation(); - World world = livingEntity.getWorld(); + public static void playSmokeEffect(Location location) { + World world = location.getWorld(); // Have to do it this way, because not all block directions are valid for smoke world.playEffect(location, Effect.SMOKE, BlockFace.SOUTH_EAST); diff --git a/src/main/java/com/gmail/nossr50/util/sounds/SoundManager.java b/src/main/java/com/gmail/nossr50/util/sounds/SoundManager.java index 8bf24461f..fdb4e4e80 100644 --- a/src/main/java/com/gmail/nossr50/util/sounds/SoundManager.java +++ b/src/main/java/com/gmail/nossr50/util/sounds/SoundManager.java @@ -39,6 +39,11 @@ public class SoundManager { world.playSound(location, getSound(soundType), getVolume(soundType), getPitch(soundType)); } + public static void worldSendSoundMaxPitch(World world, Location location, SoundType soundType) { + if(SoundConfig.getInstance().getIsEnabled(soundType)) + world.playSound(location, getSound(soundType), getVolume(soundType), 2.0F); + } + /** * All volume is multiplied by the master volume to get its final value * @param soundType target soundtype