diff --git a/src/main/java/net/momirealms/customcrops/ConfigReader.java b/src/main/java/net/momirealms/customcrops/ConfigReader.java index 6ff9b21..c9cb293 100644 --- a/src/main/java/net/momirealms/customcrops/ConfigReader.java +++ b/src/main/java/net/momirealms/customcrops/ConfigReader.java @@ -1,5 +1,6 @@ package net.momirealms.customcrops; +import net.kyori.adventure.key.Key; import net.momirealms.customcrops.fertilizer.Fertilizer; import net.momirealms.customcrops.fertilizer.QualityCrop; import net.momirealms.customcrops.fertilizer.RetainingSoil; @@ -12,15 +13,13 @@ import net.momirealms.customcrops.requirements.YPos; import net.momirealms.customcrops.utils.*; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; +import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; +import java.util.*; public class ConfigReader { @@ -44,6 +43,7 @@ public class ConfigReader { cropLoad(); fertilizerLoad(); Season.loadSeason(); + Sounds.loadSound(); } public static class Config{ @@ -64,10 +64,13 @@ public class ConfigReader { public static int yMin; public static int yMax; public static int sprinklerRefill; + public static int waterCanRefill; public static int timeToGrow; + public static int timeToWork; public static boolean logTime; public static boolean onlyLoadedGrow; public static boolean quality; + public static boolean canAddWater; public static double quality_1; public static double quality_2; @@ -87,6 +90,7 @@ public class ConfigReader { }); timeToGrow = config.getInt("config.time-to-grow",60)*20; + timeToWork = config.getInt("config.time-to-work",30)*20; //异步读取时间 asyncCheck = config.getBoolean("config.async-time-check",false); @@ -131,6 +135,8 @@ public class ConfigReader { } sprinklerRefill = config.getInt("config.sprinkler-refill",2); + waterCanRefill = config.getInt("config.water-can-refill",1); + canAddWater = config.getBoolean("config.water-can-add-water-to-sprinkler",true); //农作物生长的白名单世界 worlds = new ArrayList<>(); @@ -484,4 +490,47 @@ public class ConfigReader { } AdventureManager.consoleMessage("[CustomCrops] 已载入 " + FERTILIZERS.size() + " 种肥料!"); } + + public static class Sounds{ + + public static Key waterPotKey; + public static net.kyori.adventure.sound.Sound.Source waterPotSource; + + public static Key addWaterToCanKey; + public static net.kyori.adventure.sound.Sound.Source addWaterToCanSource; + + public static Key addWaterToSprinklerKey; + public static net.kyori.adventure.sound.Sound.Source addWaterToSprinklerSource; + + public static Key placeSprinklerKey; + public static net.kyori.adventure.sound.Sound.Source placeSprinklerSource; + + public static Key plantSeedKey; + public static net.kyori.adventure.sound.Sound.Source plantSeedSource; + + public static Key useFertilizerKey; + public static net.kyori.adventure.sound.Sound.Source useFertilizerSource; + + public static void loadSound(){ + YamlConfiguration config = getConfig("sounds.yml"); + + waterPotKey = Key.key(config.getString("water-pot.sound")); + waterPotSource = net.kyori.adventure.sound.Sound.Source.valueOf(config.getString("water-pot.type").toUpperCase()); + + addWaterToCanKey = Key.key(config.getString("add-water-to-can.sound")); + addWaterToCanSource = net.kyori.adventure.sound.Sound.Source.valueOf(config.getString("add-water-to-can.type").toUpperCase()); + + addWaterToSprinklerKey = Key.key(config.getString("add-water-to-sprinkler.sound")); + addWaterToSprinklerSource = net.kyori.adventure.sound.Sound.Source.valueOf(config.getString("add-water-to-sprinkler.type").toUpperCase()); + + placeSprinklerKey = Key.key(config.getString("place-sprinkler.sound")); + placeSprinklerSource = net.kyori.adventure.sound.Sound.Source.valueOf(config.getString("place-sprinkler.type").toUpperCase()); + + plantSeedKey = Key.key(config.getString("plant-seed.sound")); + plantSeedSource = net.kyori.adventure.sound.Sound.Source.valueOf(config.getString("plant-seed.type").toUpperCase()); + + useFertilizerKey = Key.key(config.getString("use-fertilizer.sound")); + useFertilizerSource = net.kyori.adventure.sound.Sound.Source.valueOf(config.getString("use-fertilizer.type").toUpperCase()); + } + } } diff --git a/src/main/java/net/momirealms/customcrops/datamanager/SprinklerManager.java b/src/main/java/net/momirealms/customcrops/datamanager/SprinklerManager.java index 1db8d12..318209b 100644 --- a/src/main/java/net/momirealms/customcrops/datamanager/SprinklerManager.java +++ b/src/main/java/net/momirealms/customcrops/datamanager/SprinklerManager.java @@ -12,9 +12,11 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.configuration.MemorySection; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.scheduler.BukkitScheduler; import java.io.File; import java.io.IOException; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; public class SprinklerManager { @@ -81,6 +83,7 @@ public class SprinklerManager { AdventureManager.consoleMessage("性能监测: 洒水器数据更新" + (time2-time1) + "ms"); } if (data.contains(worldName)){ + BukkitScheduler bukkitScheduler = Bukkit.getScheduler(); data.getConfigurationSection(worldName).getKeys(false).forEach(chunk ->{ String[] split = StringUtils.split(chunk,","); World world = Bukkit.getWorld(worldName); @@ -88,23 +91,26 @@ public class SprinklerManager { data.getConfigurationSection(worldName + "." + chunk).getValues(false).forEach((key, value) -> { String[] coordinate = StringUtils.split(key, ","); Location location = new Location(world,Double.parseDouble(coordinate[0])+0.5,Double.parseDouble(coordinate[1])+0.5,Double.parseDouble(coordinate[2])+0.5); + int random = new Random().nextInt(ConfigReader.Config.timeToWork); if (value instanceof MemorySection map){ - Bukkit.getScheduler().callSyncMethod(CustomCrops.instance, ()->{ + bukkitScheduler.callSyncMethod(CustomCrops.instance, ()->{ int water = (int) map.get("water"); int range = (int) map.get("range"); if(!IAFurniture.getFromLocation(location, world)){ data.set(worldName + "." + chunk + "." + key, null); return null; } - if (range == 0) data.set(worldName + "." + chunk + "." + key, null); if (water > 0){ data.set(worldName + "." + chunk + "." + key + ".water", water - 1); - for(int i = -range; i <= range; i++){ - for (int j = -range; j <= range; j++){ - waterPot(location.clone().add(i,-1,j)); + bukkitScheduler.runTaskLater(CustomCrops.instance, ()-> { + for(int i = -range; i <= range; i++){ + for (int j = -range; j <= range; j++){ + waterPot(location.clone().add(i,-1,j)); + } } - } + }, random); } + if (range == 0) data.set(worldName + "." + chunk + "." + key, null); return null; }); } diff --git a/src/main/java/net/momirealms/customcrops/listener/InteractEntity.java b/src/main/java/net/momirealms/customcrops/listener/InteractEntity.java index c974d87..830cbac 100644 --- a/src/main/java/net/momirealms/customcrops/listener/InteractEntity.java +++ b/src/main/java/net/momirealms/customcrops/listener/InteractEntity.java @@ -1,14 +1,19 @@ package net.momirealms.customcrops.listener; +import de.tr7zw.changeme.nbtapi.NBTCompound; +import de.tr7zw.changeme.nbtapi.NBTItem; import dev.lone.itemsadder.api.CustomFurniture; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.momirealms.customcrops.ConfigReader; import net.momirealms.customcrops.CustomCrops; import net.momirealms.customcrops.datamanager.SprinklerManager; +import net.momirealms.customcrops.utils.AdventureManager; import net.momirealms.customcrops.utils.HoloUtil; import net.momirealms.customcrops.utils.Sprinkler; +import net.momirealms.customcrops.utils.WateringCan; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.Sound; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -18,6 +23,8 @@ import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.inventory.ItemStack; import java.util.HashMap; +import java.util.List; +import java.util.Optional; public class InteractEntity implements Listener { @@ -38,7 +45,7 @@ public class InteractEntity implements Listener { if(config != null){ long time = System.currentTimeMillis(); Player player = event.getPlayer(); - if (time - (coolDown.getOrDefault(player, time - 1000)) < 1000) { + if (time - (coolDown.getOrDefault(player, time - 250)) < 250) { return; } coolDown.put(player, time); @@ -48,12 +55,12 @@ public class InteractEntity implements Listener { int x = location.getBlockX(); int z = location.getBlockZ(); int maxWater = config.getWater(); - int currentWater; + int currentWater = 0; Location loc = location.clone().subtract(0,1,0).getBlock().getLocation().add(0,1,0); Sprinkler sprinkler = SprinklerManager.Cache.get(loc); if (itemStack.getType() == Material.WATER_BUCKET){ itemStack.setType(Material.BUCKET); - player.getWorld().playSound(player.getLocation(), Sound.ITEM_BUCKET_FILL,1,1); + AdventureManager.playerSound(player, ConfigReader.Sounds.addWaterToSprinklerSource, ConfigReader.Sounds.addWaterToSprinklerKey); if (sprinkler != null){ currentWater = sprinkler.getWater(); currentWater += ConfigReader.Config.sprinklerRefill; @@ -70,12 +77,58 @@ public class InteractEntity implements Listener { } plugin.getSprinklerManager().data.set(path, currentWater); } - }else { - if (sprinkler != null){ - currentWater = sprinkler.getWater(); - }else { - String path = world + "." + x / 16 + "," + z / 16 + "." + x + "," + location.getBlockY() + "," + z + ".water"; - currentWater = plugin.getSprinklerManager().data.getInt(path); + } + else { + if (ConfigReader.Config.canAddWater && itemStack.getType() != Material.AIR){ + NBTItem nbtItem = new NBTItem(itemStack); + int water = nbtItem.getInteger("WaterAmount"); + if (water > 0){ + NBTCompound nbtCompound = nbtItem.getCompound("itemsadder"); + if (nbtCompound != null) { + String id = nbtCompound.getString("id"); + Optional can = Optional.ofNullable(ConfigReader.CANS.get(id)); + if (can.isPresent()) { + WateringCan wateringCan = can.get(); + water--; + nbtItem.setInteger("WaterAmount", water); + AdventureManager.playerSound(player, ConfigReader.Sounds.addWaterToSprinklerSource, ConfigReader.Sounds.addWaterToSprinklerKey); + if (sprinkler != null){ + currentWater = sprinkler.getWater(); + currentWater++; + if (currentWater > maxWater){ + currentWater = maxWater; + } + sprinkler.setWater(currentWater); + }else { + String path = world + "." + x / 16 + "," + z / 16 + "." + x + "," + location.getBlockY() + "," + z + ".water"; + currentWater = plugin.getSprinklerManager().data.getInt(path); + currentWater++; + if (currentWater > maxWater){ + currentWater = maxWater; + } + plugin.getSprinklerManager().data.set(path, currentWater); + } + if (ConfigReader.Message.hasWaterInfo){ + String string = ConfigReader.Message.waterLeft + ConfigReader.Message.waterFull.repeat(water) + + ConfigReader.Message.waterEmpty.repeat(wateringCan.getMax() - water) + ConfigReader.Message.waterRight; + AdventureManager.playerActionbar(player, string.replace("{max_water}", String.valueOf(wateringCan.getMax())).replace("{water}", String.valueOf(water))); + } + if (ConfigReader.Basic.hasWaterLore){ + String string = (ConfigReader.Basic.waterLeft + ConfigReader.Basic.waterFull.repeat(water) + + ConfigReader.Basic.waterEmpty.repeat(wateringCan.getMax() - water) + ConfigReader.Basic.waterRight).replace("{max_water}", String.valueOf(wateringCan.getMax())).replace("{water}", String.valueOf(water)); + List lores = nbtItem.getCompound("display").getStringList("Lore"); + lores.clear(); + ConfigReader.Basic.waterLore.forEach(lore -> lores.add(GsonComponentSerializer.gson().serialize(MiniMessage.miniMessage().deserialize(lore.replace("{water_info}", string))))); + } + itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); + } + } + }else { + currentWater = getCurrentWater(location, world, x, z, sprinkler); + } + } + else { + currentWater = getCurrentWater(location, world, x, z, sprinkler); } } if (ConfigReader.Message.hasSprinklerInfo){ @@ -88,4 +141,15 @@ public class InteractEntity implements Listener { } } } + + private int getCurrentWater(Location location, String world, int x, int z, Sprinkler sprinkler) { + int currentWater; + if (sprinkler != null){ + currentWater = sprinkler.getWater(); + }else { + String path = world + "." + x / 16 + "," + z / 16 + "." + x + "," + location.getBlockY() + "," + z + ".water"; + currentWater = plugin.getSprinklerManager().data.getInt(path); + } + return currentWater; + } } diff --git a/src/main/java/net/momirealms/customcrops/listener/RightClick.java b/src/main/java/net/momirealms/customcrops/listener/RightClick.java index 0b874c4..0c102af 100644 --- a/src/main/java/net/momirealms/customcrops/listener/RightClick.java +++ b/src/main/java/net/momirealms/customcrops/listener/RightClick.java @@ -36,7 +36,6 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -58,6 +57,7 @@ public class RightClick implements Listener { if (action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK){ ItemStack itemStack = event.getItem(); if (itemStack != null){ + if (itemStack.getType() == Material.AIR) return; NBTItem nbtItem = new NBTItem(itemStack); NBTCompound nbtCompound = nbtItem.getCompound("itemsadder"); if (nbtCompound != null){ @@ -112,6 +112,7 @@ public class RightClick implements Listener { itemStack.setAmount(itemStack.getAmount() - 1); CropManager.Cache.put(location, cropName); CustomBlock.place((nbtCompound.getString("namespace") + ":" + cropName + "_stage_1"), location); + AdventureManager.playerSound(player, ConfigReader.Sounds.plantSeedSource, ConfigReader.Sounds.plantSeedKey); return; } }else { @@ -127,16 +128,20 @@ public class RightClick implements Listener { for (Block block : lineOfSight) { if (block.getType() == Material.WATER) { if (wateringCan.getMax() > water){ - nbtItem.setInteger("WaterAmount", water + 1); + water += ConfigReader.Config.waterCanRefill; + if (water > wateringCan.getMax()){ + water = wateringCan.getMax(); + } + nbtItem.setInteger("WaterAmount", water); player.getWorld().playSound(player.getLocation(), Sound.ITEM_BUCKET_FILL,1,1); if (ConfigReader.Message.hasWaterInfo){ - String string = ConfigReader.Message.waterLeft + ConfigReader.Message.waterFull.repeat(water + 1) + - ConfigReader.Message.waterEmpty.repeat(wateringCan.getMax() - water - 1) + ConfigReader.Message.waterRight; - AdventureManager.playerActionbar(player, string.replace("{max_water}", String.valueOf(wateringCan.getMax())).replace("{water}", String.valueOf(water + 1))); + String string = ConfigReader.Message.waterLeft + ConfigReader.Message.waterFull.repeat(water) + + ConfigReader.Message.waterEmpty.repeat(wateringCan.getMax() - water) + ConfigReader.Message.waterRight; + AdventureManager.playerActionbar(player, string.replace("{max_water}", String.valueOf(wateringCan.getMax())).replace("{water}", String.valueOf(water))); } if (ConfigReader.Basic.hasWaterLore){ - String string = (ConfigReader.Basic.waterLeft + ConfigReader.Basic.waterFull.repeat(water + 1) + - ConfigReader.Basic.waterEmpty.repeat(wateringCan.getMax() - water - 1) + ConfigReader.Basic.waterRight).replace("{max_water}", String.valueOf(wateringCan.getMax())).replace("{water}", String.valueOf(water + 1)); + String string = (ConfigReader.Basic.waterLeft + ConfigReader.Basic.waterFull.repeat(water) + + ConfigReader.Basic.waterEmpty.repeat(wateringCan.getMax() - water) + ConfigReader.Basic.waterRight).replace("{max_water}", String.valueOf(wateringCan.getMax())).replace("{water}", String.valueOf(water)); List lores = nbtItem.getCompound("display").getStringList("Lore"); lores.clear(); ConfigReader.Basic.waterLore.forEach(lore -> lores.add(GsonComponentSerializer.gson().serialize(MiniMessage.miniMessage().deserialize(lore.replace("{water_info}", string))))); @@ -156,7 +161,7 @@ public class RightClick implements Listener { String namespacedID = customBlock.getNamespacedID(); if (namespacedID.equals(ConfigReader.Basic.pot) || namespacedID.equals(ConfigReader.Basic.watered_pot)){ nbtItem.setInteger("WaterAmount", water - 1); - player.getWorld().playSound(player.getLocation(), Sound.BLOCK_WATER_AMBIENT,1,1); + AdventureManager.playerSound(player, ConfigReader.Sounds.waterPotSource, ConfigReader.Sounds.waterPotKey); waterPot(wateringCan.getWidth(), wateringCan.getLength(), block.getLocation(), player.getLocation().getYaw()); if (ConfigReader.Message.hasWaterInfo){ String string = ConfigReader.Message.waterLeft + ConfigReader.Message.waterFull.repeat(water - 1) + @@ -173,7 +178,7 @@ public class RightClick implements Listener { itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); }else if (namespacedID.contains("_stage_")){ nbtItem.setInteger("WaterAmount", water - 1); - player.getWorld().playSound(player.getLocation(), Sound.BLOCK_WATER_AMBIENT,1,1); + AdventureManager.playerSound(player, ConfigReader.Sounds.waterPotSource, ConfigReader.Sounds.waterPotKey); waterPot(wateringCan.getWidth(), wateringCan.getLength(), block.getLocation().subtract(0,1,0), player.getLocation().getYaw()); if (ConfigReader.Message.hasWaterInfo){ String string = ConfigReader.Message.waterLeft + ConfigReader.Message.waterFull.repeat(water - 1) + @@ -210,19 +215,19 @@ public class RightClick implements Listener { return; }else { itemStack.setAmount(itemStack.getAmount() - 1); - player.getWorld().playSound(player.getLocation(), Sound.ITEM_HOE_TILL,1,1); + AdventureManager.playerSound(player, ConfigReader.Sounds.useFertilizerSource, ConfigReader.Sounds.useFertilizerKey); addFertilizer(fertilizerConfig, block); } }else { itemStack.setAmount(itemStack.getAmount() - 1); - player.getWorld().playSound(player.getLocation(), Sound.ITEM_HOE_TILL,1,1); + AdventureManager.playerSound(player, ConfigReader.Sounds.useFertilizerSource, ConfigReader.Sounds.useFertilizerKey); addFertilizer(fertilizerConfig, block); } }else if (namespacedID.contains("_stage_")){ if (!fertilizerConfig.isBefore()){ itemStack.setAmount(itemStack.getAmount() - 1); addFertilizer(fertilizerConfig, block); - player.getWorld().playSound(player.getLocation(), Sound.ITEM_HOE_TILL,1,1); + AdventureManager.playerSound(player, ConfigReader.Sounds.useFertilizerSource, ConfigReader.Sounds.useFertilizerKey); }else { AdventureManager.playerMessage(player, ConfigReader.Message.prefix + ConfigReader.Message.beforePlant); return; @@ -247,6 +252,7 @@ public class RightClick implements Listener { itemStack.setAmount(itemStack.getAmount() - 1); SprinklerManager.Cache.put(location.add(0,1,0), sprinklerData); IAFurniture.placeFurniture(sprinkler.get().getNamespacedID_2(),location); + AdventureManager.playerSound(player, ConfigReader.Sounds.placeSprinklerSource, ConfigReader.Sounds.placeSprinklerKey); return; } if (ConfigReader.Message.hasCropInfo && id.equals(ConfigReader.Basic.soilDetector) && action == Action.RIGHT_CLICK_BLOCK){ diff --git a/src/main/java/net/momirealms/customcrops/utils/AdventureManager.java b/src/main/java/net/momirealms/customcrops/utils/AdventureManager.java index 00bf2ac..52165ff 100644 --- a/src/main/java/net/momirealms/customcrops/utils/AdventureManager.java +++ b/src/main/java/net/momirealms/customcrops/utils/AdventureManager.java @@ -1,6 +1,8 @@ package net.momirealms.customcrops.utils; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.title.Title; @@ -39,4 +41,10 @@ public class AdventureManager { MiniMessage mm = MiniMessage.miniMessage(); au.sendActionBar(mm.deserialize(s)); } + + public static void playerSound(Player player, Sound.Source source, Key key) { + Sound sound = Sound.sound(key, source, 1, 1); + Audience au = CustomCrops.adventure.player(player); + au.playSound(sound); + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index d2ab05c..3cdf188 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -10,16 +10,18 @@ config: Lands: false #生长时间点(tick) - #洒水器将会在农作物全部完成生长后开始工作 #1000代表上午7点,农作物陆续开始生长 + #洒水器将会在农作物全部完成生长后开始工作 grow-time: - 1000 - #生长的时间(秒) - #农作物将在90秒内随机完成生长以避免在短时间内大量方块替换造成卡顿 + #生长点后农作物成长所需的时间(秒) + #农作物将在60秒内随机完成生长以避免在短时间内大量方块替换造成卡顿 #但也不建议设置过长时间,否则内存无法及时释放,区块会被持续加载 - #配合上方的默认时间点,意思为每天上午7点后,农作物将在90秒内完成生长过程 - time-to-grow: 90 + #配合上方的默认时间点,意思为每天上午7点后,农作物将在60秒内完成生长过程 + time-to-grow: 60 + #农作物全部完成生长后洒水器工作所需的时间 + time-to-work: 30 #产物品质 quality: @@ -37,6 +39,10 @@ config: #使用一次水桶可以补充几个洒水器水槽 sprinkler-refill: 2 + #水壶右键一次水方块能补充多少水量 + water-can-refill: 1 + #是否可以用水壶为洒水器加水 + water-can-add-water-to-sprinkler: true #生长生效的世界 whitelist-worlds: diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7c5879f..5e9118e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: CustomCrops -version: '1.5.7' +version: '1.5.8' main: net.momirealms.customcrops.CustomCrops api-version: 1.16 depend: diff --git a/src/main/resources/sounds.yml b/src/main/resources/sounds.yml new file mode 100644 index 0000000..12474df --- /dev/null +++ b/src/main/resources/sounds.yml @@ -0,0 +1,24 @@ +#使用水壶浇水的音效 +water-pot: + sound: minecraft:block.water.ambient + type: player +#使用水壶装水的音效 +add-water-to-can: + sound: minecraft:item.bucket.fill + type: player +#将水加入洒水器的音效 +add-water-to-sprinkler: + sound: minecraft:item.bucket.fill + type: player +#放置洒水器的音效 +place-sprinkler: + sound: minecraft:block.bone_block.place + type: player +#种植种子的音效 +plant-seed: + sound: minecraft:item.hoe.till + type: player +#使用肥料的音效 +use-fertilizer: + sound: minecraft:item.hoe.till + type: player \ No newline at end of file