Adds customization and automatic movement to crafting stations
All checks were successful
KnarCraft/BlacksmithVisuals/pipeline/head This commit looks good
All checks were successful
KnarCraft/BlacksmithVisuals/pipeline/head This commit looks good
This commit is contained in:
parent
b1dab9b2cf
commit
87fa2f7c64
4
pom.xml
4
pom.xml
@ -12,7 +12,7 @@
|
|||||||
<name>BlacksmithVisuals</name>
|
<name>BlacksmithVisuals</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>16</java.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@ -84,7 +84,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.knarcraft</groupId>
|
<groupId>net.knarcraft</groupId>
|
||||||
<artifactId>blacksmith</artifactId>
|
<artifactId>blacksmith</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.1.2-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
package net.knarcraft.blacksmithvisuals;
|
|
||||||
|
|
||||||
import com.comphenix.protocol.PacketType;
|
|
||||||
import com.comphenix.protocol.ProtocolLibrary;
|
|
||||||
import com.comphenix.protocol.ProtocolManager;
|
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
|
||||||
import net.knarcraft.blacksmith.BlacksmithPlugin;
|
|
||||||
import net.knarcraft.blacksmith.event.ActionStartEvent;
|
|
||||||
import net.knarcraft.blacksmith.event.BlacksmithReforgeFailEvent;
|
|
||||||
import net.knarcraft.blacksmith.event.BlacksmithReforgeStartEvent;
|
|
||||||
import net.knarcraft.blacksmith.event.BlacksmithReforgeSucceedEvent;
|
|
||||||
import net.knarcraft.blacksmith.event.NPCSoundEvent;
|
|
||||||
import net.knarcraft.blacksmith.event.ScrapperSalvageFailEvent;
|
|
||||||
import net.knarcraft.blacksmith.event.ScrapperSalvageStartEvent;
|
|
||||||
import net.knarcraft.blacksmith.event.ScrapperSalvageSucceedEvent;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Sound;
|
|
||||||
import org.bukkit.SoundCategory;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for blacksmith-related events
|
|
||||||
*/
|
|
||||||
public class BlacksmithListener implements Listener {
|
|
||||||
|
|
||||||
private static final Random random = new Random();
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onReforgeStart(@NotNull BlacksmithReforgeStartEvent event) {
|
|
||||||
onActionStart(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onSalvageStart(@NotNull ScrapperSalvageStartEvent event) {
|
|
||||||
onActionStart(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onDefaultSound(@NotNull NPCSoundEvent event) {
|
|
||||||
event.setCancelled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onReforgeSuccess(@NotNull BlacksmithReforgeSucceedEvent event) {
|
|
||||||
playSuccessSound(event.getNpc().getEntity());
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onSalvageSuccess(@NotNull ScrapperSalvageSucceedEvent event) {
|
|
||||||
playSuccessSound(event.getNpc().getEntity());
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onReforgeFail(@NotNull BlacksmithReforgeFailEvent event) {
|
|
||||||
playFailSound(event.getNpc().getEntity());
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
|
||||||
public void onSalvageFail(@NotNull ScrapperSalvageFailEvent event) {
|
|
||||||
playFailSound(event.getNpc().getEntity());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A method performing actions required when a blacksmith or scrapper action starts
|
|
||||||
*
|
|
||||||
* @param event <p>The event that's starting</p>
|
|
||||||
*/
|
|
||||||
private void onActionStart(@NotNull ActionStartEvent event) {
|
|
||||||
BukkitScheduler scheduler = Bukkit.getScheduler();
|
|
||||||
int playWorkSound = scheduler.scheduleSyncRepeatingTask(BlacksmithPlugin.getInstance(),
|
|
||||||
() -> displayWorkAnimation(event), 20, 5);
|
|
||||||
|
|
||||||
scheduler.scheduleSyncDelayedTask(BlacksmithPlugin.getInstance(), () -> scheduler.cancelTask(playWorkSound),
|
|
||||||
event.getActionDurationTicks());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the animation of a working NPC
|
|
||||||
*
|
|
||||||
* @param event <p>The action start event to display for</p>
|
|
||||||
*/
|
|
||||||
private void displayWorkAnimation(@NotNull ActionStartEvent event) {
|
|
||||||
if (random.nextInt(100) >= 20) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.playWorkSound(event.getNpc().getEntity());
|
|
||||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
|
||||||
PacketContainer packet = manager.createPacket(PacketType.Play.Server.ANIMATION);
|
|
||||||
packet.getIntegers().write(0, event.getNpc().getEntity().getEntityId());
|
|
||||||
packet.getIntegers().write(1, 3);
|
|
||||||
|
|
||||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
|
||||||
if (player.getWorld().equals(event.getNpc().getEntity().getWorld())) {
|
|
||||||
manager.sendServerPacket(player, packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays the fail action sound
|
|
||||||
*
|
|
||||||
* @param entity <p>The entity to play the sound at</p>
|
|
||||||
*/
|
|
||||||
private void playFailSound(@NotNull Entity entity) {
|
|
||||||
playSound(entity, Sound.ENTITY_VILLAGER_NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays the succeed action sound
|
|
||||||
*
|
|
||||||
* @param entity <p>The entity to play the sound at</p>
|
|
||||||
*/
|
|
||||||
private void playSuccessSound(@NotNull Entity entity) {
|
|
||||||
playSound(entity, Sound.BLOCK_ANVIL_USE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays a npc sound using a cancellable event
|
|
||||||
*
|
|
||||||
* @param entity <p>The entity that should play the sound</p>
|
|
||||||
*/
|
|
||||||
private void playWorkSound(@NotNull Entity entity) {
|
|
||||||
playSound(entity, Sound.ITEM_ARMOR_EQUIP_NETHERITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays a npc sound using a cancellable event
|
|
||||||
*
|
|
||||||
* @param entity <p>The entity that should play the sound</p>
|
|
||||||
*/
|
|
||||||
private void playSound(@NotNull Entity entity, Sound sound) {
|
|
||||||
World world = entity.getLocation().getWorld();
|
|
||||||
if (world == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
world.playSound(entity, sound, SoundCategory.AMBIENT, 0.5f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +1,18 @@
|
|||||||
package net.knarcraft.blacksmithvisuals;
|
package net.knarcraft.blacksmithvisuals;
|
||||||
|
|
||||||
|
import net.knarcraft.blacksmithvisuals.command.ReloadCommand;
|
||||||
|
import net.knarcraft.blacksmithvisuals.command.SetNPCPositionCommand;
|
||||||
|
import net.knarcraft.blacksmithvisuals.listener.BlacksmithListener;
|
||||||
|
import net.knarcraft.blacksmithvisuals.manager.ConfigurationManager;
|
||||||
|
import net.knarcraft.blacksmithvisuals.manager.NPCDataManager;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.PluginCommand;
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The blacksmith visual main class
|
* The blacksmith visual main class
|
||||||
@ -8,18 +20,102 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class BlacksmithVisuals extends JavaPlugin {
|
public final class BlacksmithVisuals extends JavaPlugin {
|
||||||
|
|
||||||
|
private static BlacksmithVisuals instance;
|
||||||
|
private ConfigurationManager configurationManager;
|
||||||
|
private NPCDataManager npcDataManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// Plugin startup logic
|
BlacksmithVisuals.instance = this;
|
||||||
getServer().getPluginManager().registerEvents(new BlacksmithListener(), this);
|
|
||||||
|
|
||||||
//TODO: Allow customization of sounds, volumes and pitches
|
//Copy default config to disk, and add missing configuration values
|
||||||
// Allow setting an idle position and a working position, and move the NPC to the right position at the right time.
|
this.saveDefaultConfig();
|
||||||
// Use the distance between the two locations to decide how much sooner before the success/fail sounds are played the NPC should start walking
|
this.getConfig().options().copyDefaults(true);
|
||||||
|
this.reloadConfig();
|
||||||
|
this.saveConfig();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.configurationManager = new ConfigurationManager(this.getConfig());
|
||||||
|
} catch (InvalidConfigurationException exception) {
|
||||||
|
this.getLogger().log(Level.SEVERE, "Could not properly load the configuration file. " +
|
||||||
|
"Please check it for errors!");
|
||||||
|
this.onDisable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.npcDataManager = NPCDataManager.load();
|
||||||
|
} catch (IOException e) {
|
||||||
|
this.getLogger().log(Level.SEVERE, "Could not properly load the data.yml file. " +
|
||||||
|
"Please check it for errors!");
|
||||||
|
this.onDisable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getServer().getPluginManager().registerEvents(new BlacksmithListener(this.configurationManager), this);
|
||||||
|
registerCommand("reload", new ReloadCommand());
|
||||||
|
registerCommand("setNPCPosition", new SetNPCPositionCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
// Plugin shutdown logic
|
// Plugin shutdown logic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the configuration file
|
||||||
|
*/
|
||||||
|
public void reload() {
|
||||||
|
reloadConfig();
|
||||||
|
saveConfig();
|
||||||
|
try {
|
||||||
|
this.configurationManager.load(getConfig());
|
||||||
|
} catch (InvalidConfigurationException exception) {
|
||||||
|
this.getLogger().log(Level.SEVERE, "Could not properly load the configuration file. " +
|
||||||
|
"Please check it for errors!");
|
||||||
|
this.onDisable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a command
|
||||||
|
*
|
||||||
|
* @param commandName <p>The name of the command</p>
|
||||||
|
* @param executor <p>The executor to bind to the command</p>
|
||||||
|
*/
|
||||||
|
private void registerCommand(@NotNull String commandName, @NotNull CommandExecutor executor) {
|
||||||
|
PluginCommand command = this.getCommand(commandName);
|
||||||
|
if (command != null) {
|
||||||
|
command.setExecutor(executor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the NPC data manager
|
||||||
|
*
|
||||||
|
* @return <p>The NPC data manager</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public NPCDataManager getNpcDataManager() {
|
||||||
|
return this.npcDataManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the configuration manager
|
||||||
|
*
|
||||||
|
* @return <p>The configuration manager</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public ConfigurationManager getConfigurationManager() {
|
||||||
|
return this.configurationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an instance of this plugin
|
||||||
|
*
|
||||||
|
* @return <p>An instance of this plugin</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static BlacksmithVisuals getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.command;
|
||||||
|
|
||||||
|
import net.knarcraft.blacksmithvisuals.BlacksmithVisuals;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reload command
|
||||||
|
*/
|
||||||
|
public class ReloadCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
|
||||||
|
@NotNull String[] strings) {
|
||||||
|
BlacksmithVisuals.getInstance().reload();
|
||||||
|
commandSender.sendMessage("BlacksmithVisuals has been reloaded!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.command;
|
||||||
|
|
||||||
|
import net.citizensnpcs.api.CitizensAPI;
|
||||||
|
import net.citizensnpcs.api.npc.NPC;
|
||||||
|
import net.knarcraft.blacksmith.trait.BlacksmithTrait;
|
||||||
|
import net.knarcraft.blacksmith.trait.ScrapperTrait;
|
||||||
|
import net.knarcraft.blacksmithvisuals.BlacksmithVisuals;
|
||||||
|
import net.knarcraft.blacksmithvisuals.container.NPCData;
|
||||||
|
import net.knarcraft.blacksmithvisuals.manager.NPCDataManager;
|
||||||
|
import net.knarcraft.blacksmithvisuals.property.NPCPosition;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabExecutor;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The commands for setting an NPC's positions
|
||||||
|
*/
|
||||||
|
public class SetNPCPositionCommand implements TabExecutor {
|
||||||
|
|
||||||
|
private static List<String> npcPositionNames = null;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
|
||||||
|
@NotNull String[] arguments) {
|
||||||
|
NPC npc = CitizensAPI.getDefaultNPCSelector().getSelected(commandSender);
|
||||||
|
if (npc == null || (!npc.hasTrait(BlacksmithTrait.class) && !npc.hasTrait(ScrapperTrait.class))) {
|
||||||
|
commandSender.sendMessage("You must select an NPC before executing this command");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(commandSender instanceof Player player)) {
|
||||||
|
commandSender.sendMessage("This command must be executed by a player");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.length < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NPCPosition position = NPCPosition.fromInput(arguments[0]);
|
||||||
|
if (position == null) {
|
||||||
|
commandSender.sendMessage("Invalid position specified!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NPCDataManager manager = BlacksmithVisuals.getInstance().getNpcDataManager();
|
||||||
|
NPCData data = manager.getData(npc.getUniqueId());
|
||||||
|
if (data == null) {
|
||||||
|
data = new NPCData(new HashMap<>());
|
||||||
|
}
|
||||||
|
data.positions().put(position, player.getLocation());
|
||||||
|
manager.putData(npc.getUniqueId(), data);
|
||||||
|
commandSender.sendMessage("Position " + position.getPositionName() + " set!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
|
||||||
|
@NotNull String[] arguments) {
|
||||||
|
if (arguments.length == 1) {
|
||||||
|
if (npcPositionNames == null) {
|
||||||
|
List<String> output = new ArrayList<>();
|
||||||
|
for (NPCPosition position : NPCPosition.values()) {
|
||||||
|
output.add(position.getPositionName());
|
||||||
|
}
|
||||||
|
npcPositionNames = output;
|
||||||
|
}
|
||||||
|
return npcPositionNames;
|
||||||
|
} else {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.container;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An animation data container
|
||||||
|
*
|
||||||
|
* @param animateOffHand <p>Whether to animate the off-hand at all</p>
|
||||||
|
* @param animationDelay <p>The delay between each time the animation should be attempted (max swing rate)</p>
|
||||||
|
* @param animationChance <p>The probability of the animation triggering when the animation delay has passed</p>
|
||||||
|
*/
|
||||||
|
public record AnimationData(boolean animateOffHand, int animationDelay, int animationChance) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new animation data object with sanity checks
|
||||||
|
*
|
||||||
|
* @param animateOffHand <p>Whether to animate the off-hand at all</p>
|
||||||
|
* @param animationDelay <p>The delay between each time the animation should be attempted (max swing rate)</p>
|
||||||
|
* @param animationChance <p>The probability of the animation triggering when the animation delay has passed</p>
|
||||||
|
*/
|
||||||
|
public AnimationData {
|
||||||
|
if (animationDelay < 1) {
|
||||||
|
animationDelay = 1;
|
||||||
|
}
|
||||||
|
if (animationChance < 1) {
|
||||||
|
animationChance = 1;
|
||||||
|
}
|
||||||
|
if (animationChance > 100) {
|
||||||
|
animationChance = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads sound data from the given configuration key
|
||||||
|
*
|
||||||
|
* @param configuration <p>The configuration to load values from</p>
|
||||||
|
* @param rootKey <p>The key to load the data from</p>
|
||||||
|
* @return <p>The loaded sound data</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static AnimationData load(@NotNull FileConfiguration configuration,
|
||||||
|
@NotNull String rootKey) throws InvalidConfigurationException {
|
||||||
|
ConfigurationSection section = configuration.getConfigurationSection(rootKey);
|
||||||
|
if (section == null) {
|
||||||
|
throw new InvalidConfigurationException("Could not find the configuration section " + rootKey);
|
||||||
|
}
|
||||||
|
boolean animateOffHand = section.getBoolean("animateOffhand", true);
|
||||||
|
int animationDelay = section.getInt("animationDelay", 5);
|
||||||
|
int animationChance = section.getInt("animationChance", 20);
|
||||||
|
|
||||||
|
return new AnimationData(animateOffHand, animationDelay, animationChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.container;
|
||||||
|
|
||||||
|
import net.knarcraft.blacksmithvisuals.property.NPCPosition;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for an NPC
|
||||||
|
*
|
||||||
|
* @param positions <p>A map storing all NPC positions</p>
|
||||||
|
*/
|
||||||
|
public record NPCData(@NotNull Map<NPCPosition, Location> positions) {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.container;
|
||||||
|
|
||||||
|
import net.knarcraft.blacksmithvisuals.BlacksmithVisuals;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.SoundCategory;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sound data container
|
||||||
|
*
|
||||||
|
* @param soundCategory <p>The category the sound should be played in</p>
|
||||||
|
* @param sound <p>The sound to be played</p>
|
||||||
|
* @param volume <p>The volume to play the sound at (> 0)</p>
|
||||||
|
* @param pitch <p>The pitch to play the sound at</p>
|
||||||
|
* @param offsetTicks <p>The amount of ticks to offset the sound by</p>
|
||||||
|
* @param enabled <p>Whether this sound is enabled</p>
|
||||||
|
*/
|
||||||
|
public record SoundData(@NotNull SoundCategory soundCategory, @NotNull Sound sound, float volume, float pitch,
|
||||||
|
int offsetTicks, boolean enabled) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new sound data object with sanity checks
|
||||||
|
*
|
||||||
|
* @param soundCategory <p>The category the sound should be played in</p>
|
||||||
|
* @param sound <p>The sound to be played</p>
|
||||||
|
* @param volume <p>The volume to play the sound at (> 0)</p>
|
||||||
|
* @param pitch <p>The pitch to play the sound at</p>
|
||||||
|
* @param offsetTicks <p>The amount of ticks to offset the sound by</p>
|
||||||
|
* @param enabled <p>Whether this sound is enabled</p>
|
||||||
|
*/
|
||||||
|
public SoundData {
|
||||||
|
if (volume < 0) {
|
||||||
|
volume = 1;
|
||||||
|
}
|
||||||
|
if (pitch < 0) {
|
||||||
|
pitch = 0;
|
||||||
|
} else if (pitch > 1) {
|
||||||
|
pitch = 1;
|
||||||
|
}
|
||||||
|
Objects.requireNonNull(soundCategory);
|
||||||
|
Objects.requireNonNull(sound);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads sound data from the given configuration key
|
||||||
|
*
|
||||||
|
* @param configuration <p>The configuration to load values from</p>
|
||||||
|
* @param rootKey <p>The key to load the data from</p>
|
||||||
|
* @return <p>The loaded sound data</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static SoundData load(@NotNull FileConfiguration configuration,
|
||||||
|
@NotNull String rootKey) throws InvalidConfigurationException {
|
||||||
|
ConfigurationSection section = configuration.getConfigurationSection(rootKey);
|
||||||
|
if (section == null) {
|
||||||
|
throw new InvalidConfigurationException("Could not find the configuration section " + rootKey);
|
||||||
|
}
|
||||||
|
boolean enabled = section.getBoolean("enabled", true);
|
||||||
|
SoundCategory soundCategory = parseCategory(section.getString("soundCategory", "AMBIENT"));
|
||||||
|
Sound sound = parseSound(section.getString("sound", "ENTITY_PIG_DEATH"));
|
||||||
|
float pitch = (float) section.getDouble("pitch", 1);
|
||||||
|
float volume = (float) section.getDouble("volume", 1);
|
||||||
|
int offsetTicks = section.getInt("offset", 0);
|
||||||
|
|
||||||
|
return new SoundData(soundCategory, sound, volume, pitch, offsetTicks, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely parses a sound
|
||||||
|
*
|
||||||
|
* @param soundName <p>The name of the sound to parse</p>
|
||||||
|
* @return <p>The parsed sound</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private static Sound parseSound(@NotNull String soundName) {
|
||||||
|
try {
|
||||||
|
return Sound.valueOf(soundName);
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
BlacksmithVisuals.getInstance().getLogger().log(Level.WARNING, "Invalid sound in configuration: " +
|
||||||
|
soundName);
|
||||||
|
return Sound.ENTITY_PIG_DEATH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely parses a sound category
|
||||||
|
*
|
||||||
|
* @param categoryName <p>The name of the category to parse</p>
|
||||||
|
* @return <p>The parsed category</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private static SoundCategory parseCategory(@NotNull String categoryName) {
|
||||||
|
try {
|
||||||
|
return SoundCategory.valueOf(categoryName);
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
BlacksmithVisuals.getInstance().getLogger().log(Level.WARNING, "Invalid sound category in " +
|
||||||
|
"configuration: " + categoryName);
|
||||||
|
return SoundCategory.AMBIENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,278 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.listener;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
import net.citizensnpcs.api.npc.NPC;
|
||||||
|
import net.knarcraft.blacksmith.event.ActionStartEvent;
|
||||||
|
import net.knarcraft.blacksmith.event.BlacksmithReforgeFailEvent;
|
||||||
|
import net.knarcraft.blacksmith.event.BlacksmithReforgeStartEvent;
|
||||||
|
import net.knarcraft.blacksmith.event.BlacksmithReforgeSucceedEvent;
|
||||||
|
import net.knarcraft.blacksmith.event.NPCSoundEvent;
|
||||||
|
import net.knarcraft.blacksmith.event.ScrapperSalvageFailEvent;
|
||||||
|
import net.knarcraft.blacksmith.event.ScrapperSalvageStartEvent;
|
||||||
|
import net.knarcraft.blacksmith.event.ScrapperSalvageSucceedEvent;
|
||||||
|
import net.knarcraft.blacksmithvisuals.BlacksmithVisuals;
|
||||||
|
import net.knarcraft.blacksmithvisuals.container.AnimationData;
|
||||||
|
import net.knarcraft.blacksmithvisuals.container.NPCData;
|
||||||
|
import net.knarcraft.blacksmithvisuals.container.SoundData;
|
||||||
|
import net.knarcraft.blacksmithvisuals.manager.ConfigurationManager;
|
||||||
|
import net.knarcraft.blacksmithvisuals.property.NPCPosition;
|
||||||
|
import net.knarcraft.blacksmithvisuals.property.SoundIdentifier;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for blacksmith-related events
|
||||||
|
*/
|
||||||
|
public class BlacksmithListener implements Listener {
|
||||||
|
|
||||||
|
private final Random random;
|
||||||
|
private final ConfigurationManager configurationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new blacksmith listener
|
||||||
|
*
|
||||||
|
* @param configurationManager <p>The configuration manager to get configuration values from</p>
|
||||||
|
*/
|
||||||
|
public BlacksmithListener(@NotNull ConfigurationManager configurationManager) {
|
||||||
|
this.random = new Random();
|
||||||
|
this.configurationManager = configurationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onDefaultSound(@NotNull NPCSoundEvent event) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onReforgeStart(@NotNull BlacksmithReforgeStartEvent event) {
|
||||||
|
runWorkingAnimation(event, SoundIdentifier.REFORGING_WORKING, this.configurationManager.getBlacksmithAnimationData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onSalvageStart(@NotNull ScrapperSalvageStartEvent event) {
|
||||||
|
runWorkingAnimation(event, SoundIdentifier.SALVAGING_WORKING, this.configurationManager.getScrapperAnimationData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onReforgeSuccess(@NotNull BlacksmithReforgeSucceedEvent event) {
|
||||||
|
playSound(event.getNpc().getEntity(), this.configurationManager.getSoundData(SoundIdentifier.REFORGING_SUCCESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onSalvageSuccess(@NotNull ScrapperSalvageSucceedEvent event) {
|
||||||
|
playSound(event.getNpc().getEntity(), this.configurationManager.getSoundData(SoundIdentifier.SALVAGING_SUCCESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onReforgeFail(@NotNull BlacksmithReforgeFailEvent event) {
|
||||||
|
playSound(event.getNpc().getEntity(), this.configurationManager.getSoundData(SoundIdentifier.REFORGING_FAILURE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onSalvageFail(@NotNull ScrapperSalvageFailEvent event) {
|
||||||
|
playSound(event.getNpc().getEntity(), this.configurationManager.getSoundData(SoundIdentifier.SALVAGING_FAILURE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the working animation for an NPC
|
||||||
|
*
|
||||||
|
* @param event <p>The action that started</p>
|
||||||
|
* @param soundIdentifier <p>The identifier for the sound to play</p>
|
||||||
|
* @param animationData <p>The animation data for the animation to play</p>
|
||||||
|
*/
|
||||||
|
private void runWorkingAnimation(@NotNull ActionStartEvent event, @NotNull SoundIdentifier soundIdentifier,
|
||||||
|
@NotNull AnimationData animationData) {
|
||||||
|
BlacksmithVisuals instance = BlacksmithVisuals.getInstance();
|
||||||
|
BukkitScheduler scheduler = Bukkit.getScheduler();
|
||||||
|
NPC npc = event.getNpc();
|
||||||
|
long delay = moveToWorkingPosition(npc, NPCPosition.getFromMaterial(event.getCraftingStation()));
|
||||||
|
long finishTime = event.getActionDurationTicks() - (2 * delay);
|
||||||
|
|
||||||
|
scheduler.scheduleSyncDelayedTask(instance, () -> startWorkAnimation(npc.getEntity(),
|
||||||
|
this.configurationManager.getSoundData(soundIdentifier), animationData, finishTime - 20), delay);
|
||||||
|
scheduler.scheduleSyncDelayedTask(instance, () -> moveBack(event.getNpc()), finishTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves an NPC back to its idle position
|
||||||
|
*
|
||||||
|
* @param npc <p>The NPC to move</p>
|
||||||
|
*/
|
||||||
|
private void moveBack(@NotNull NPC npc) {
|
||||||
|
if (!npc.isSpawned()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NPCData npcData = BlacksmithVisuals.getInstance().getNpcDataManager().getData(npc.getUniqueId());
|
||||||
|
if (npcData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location targetLocation = npcData.positions().get(NPCPosition.IDLE);
|
||||||
|
if (!npc.getNavigator().canNavigateTo(targetLocation)) {
|
||||||
|
BlacksmithVisuals.getInstance().getLogger().log(Level.WARNING, "Idle position for NPC " +
|
||||||
|
npc.getName() + " is unreachable!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
npc.getNavigator().setTarget(targetLocation);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().scheduleSyncDelayedTask(BlacksmithVisuals.getInstance(), () ->
|
||||||
|
npc.getEntity().teleport(targetLocation), getWalkTime(npc.getEntity().getLocation(), targetLocation));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a npc to its working position
|
||||||
|
*
|
||||||
|
* @param npc <p>The NPC to move</p>
|
||||||
|
* @param npcPosition <p>The npc position to move to</p>
|
||||||
|
* @return <p>The time the move will take</p>
|
||||||
|
*/
|
||||||
|
private long moveToWorkingPosition(@NotNull NPC npc, @Nullable NPCPosition npcPosition) {
|
||||||
|
if (!npc.isSpawned() || npcPosition == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NPCData npcData = BlacksmithVisuals.getInstance().getNpcDataManager().getData(npc.getUniqueId());
|
||||||
|
if (npcData == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location targetLocation = npcData.positions().get(npcPosition);
|
||||||
|
if (targetLocation == null && npcPosition != NPCPosition.WORKING_REPAIRABLE) {
|
||||||
|
targetLocation = npcData.positions().get(NPCPosition.WORKING_REPAIRABLE);
|
||||||
|
}
|
||||||
|
if (targetLocation == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!npc.getNavigator().canNavigateTo(targetLocation)) {
|
||||||
|
BlacksmithVisuals.getInstance().getLogger().log(Level.WARNING, "Working position for NPC " +
|
||||||
|
npc.getName() + " is unreachable!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move NPC using Citizens path-finding
|
||||||
|
npc.getNavigator().setTarget(targetLocation);
|
||||||
|
|
||||||
|
// Teleport the NPC tp get it in the exact final location
|
||||||
|
long walkTime = getWalkTime(npc.getEntity().getLocation(), targetLocation);
|
||||||
|
Location finalTargetLocation = targetLocation;
|
||||||
|
Bukkit.getScheduler().scheduleSyncDelayedTask(BlacksmithVisuals.getInstance(), () ->
|
||||||
|
npc.getEntity().teleport(finalTargetLocation), walkTime);
|
||||||
|
return walkTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the working animation and sound
|
||||||
|
*
|
||||||
|
* @param entity <p>The entity to play the sound at</p>
|
||||||
|
* @param soundData <p>The sound data for the sound to play</p>
|
||||||
|
* @param animationData <p>The animation data for the animation to play</p>
|
||||||
|
* @param durationTicks <p>The duration of the work animation</p>
|
||||||
|
*/
|
||||||
|
private void startWorkAnimation(@NotNull Entity entity, @NotNull SoundData soundData,
|
||||||
|
@NotNull AnimationData animationData, long durationTicks) {
|
||||||
|
BlacksmithVisuals instance = BlacksmithVisuals.getInstance();
|
||||||
|
BukkitScheduler scheduler = Bukkit.getScheduler();
|
||||||
|
|
||||||
|
int playWorkSound = scheduler.scheduleSyncRepeatingTask(instance,
|
||||||
|
() -> animateNPC(entity, soundData, animationData), 20, animationData.animationDelay());
|
||||||
|
scheduler.scheduleSyncDelayedTask(instance, () -> scheduler.cancelTask(playWorkSound), durationTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the time it would take to go from one location to another in ticks
|
||||||
|
*
|
||||||
|
* @param startLocation <p>The location to start from</p>
|
||||||
|
* @param targetLocation <p>The target location</p>
|
||||||
|
* @return <p>The time in ticks</p>
|
||||||
|
*/
|
||||||
|
private long getWalkTime(@NotNull Location startLocation, @NotNull Location targetLocation) {
|
||||||
|
double distance = startLocation.distance(targetLocation);
|
||||||
|
double WALK_SPEED = 4.317;
|
||||||
|
return Math.round((distance / WALK_SPEED) * 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates a npc's hand, and plays a sound
|
||||||
|
*
|
||||||
|
* @param entity <p>The entity to animate and play the sound at</p>
|
||||||
|
* @param soundData <p>The sound to be played</p>
|
||||||
|
* @param animationData <p>The animation to be played</p>
|
||||||
|
*/
|
||||||
|
private void animateNPC(@NotNull Entity entity, @NotNull SoundData soundData, @NotNull AnimationData animationData) {
|
||||||
|
if (random.nextInt(100) >= animationData.animationChance()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playSound(entity, soundData);
|
||||||
|
|
||||||
|
// Don't play disabled animations
|
||||||
|
if (!animationData.animateOffHand()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (soundData.offsetTicks() < 0) {
|
||||||
|
Bukkit.getScheduler().scheduleSyncDelayedTask(BlacksmithVisuals.getInstance(),
|
||||||
|
() -> animateOffhand(entity), -soundData.offsetTicks());
|
||||||
|
} else {
|
||||||
|
animateOffhand(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates an NPC's offhand
|
||||||
|
*
|
||||||
|
* @param entity <p>The entity to animate</p>
|
||||||
|
*/
|
||||||
|
private void animateOffhand(@NotNull Entity entity) {
|
||||||
|
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||||
|
PacketContainer packet = manager.createPacket(PacketType.Play.Server.ANIMATION);
|
||||||
|
packet.getIntegers().write(0, entity.getEntityId());
|
||||||
|
packet.getIntegers().write(1, 3);
|
||||||
|
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (player.getWorld().equals(entity.getWorld())) {
|
||||||
|
manager.sendServerPacket(player, packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a sound according to the given sound data
|
||||||
|
*
|
||||||
|
* @param entity <p>The entity to play the sound from</p>
|
||||||
|
* @param soundData <p>The data describing the sound to play</p>
|
||||||
|
*/
|
||||||
|
private void playSound(@NotNull Entity entity, @NotNull SoundData soundData) {
|
||||||
|
// Don't play disabled sounds
|
||||||
|
if (!soundData.enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = entity.getLocation().getWorld();
|
||||||
|
if (world == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delay = Math.max(soundData.offsetTicks(), 0);
|
||||||
|
Bukkit.getScheduler().scheduleSyncDelayedTask(BlacksmithVisuals.getInstance(), () ->
|
||||||
|
world.playSound(entity, soundData.sound(), soundData.soundCategory(),
|
||||||
|
soundData.volume(), soundData.pitch()), delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.manager;
|
||||||
|
|
||||||
|
import net.knarcraft.blacksmithvisuals.container.AnimationData;
|
||||||
|
import net.knarcraft.blacksmithvisuals.container.SoundData;
|
||||||
|
import net.knarcraft.blacksmithvisuals.property.SoundIdentifier;
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A manager keeping track of configuration options
|
||||||
|
*/
|
||||||
|
public class ConfigurationManager {
|
||||||
|
|
||||||
|
private Map<SoundIdentifier, SoundData> soundSettings;
|
||||||
|
private AnimationData scrapperAnimationData;
|
||||||
|
private AnimationData blacksmithAnimationData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new configuration manager
|
||||||
|
*
|
||||||
|
* @param fileConfiguration <p>The file configuration to load values from</p>
|
||||||
|
* @throws InvalidConfigurationException <p>If the configuration file has missing or invalid values</p>
|
||||||
|
*/
|
||||||
|
public ConfigurationManager(@NotNull FileConfiguration fileConfiguration) throws InvalidConfigurationException {
|
||||||
|
load(fileConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the configuration from disk
|
||||||
|
*
|
||||||
|
* @param fileConfiguration <p>The file configuration to get values from</p>
|
||||||
|
* @throws InvalidConfigurationException <p>If the configuration file has missing or invalid values</p>
|
||||||
|
*/
|
||||||
|
public void load(@NotNull FileConfiguration fileConfiguration) throws InvalidConfigurationException {
|
||||||
|
soundSettings = new HashMap<>();
|
||||||
|
for (SoundIdentifier identifier : SoundIdentifier.values()) {
|
||||||
|
soundSettings.put(identifier, SoundData.load(fileConfiguration, identifier.getConfigNode()));
|
||||||
|
}
|
||||||
|
scrapperAnimationData = AnimationData.load(fileConfiguration, "scrapper.animation");
|
||||||
|
blacksmithAnimationData = AnimationData.load(fileConfiguration, "blacksmith.animation");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the animation data for the scrapper's working animation
|
||||||
|
*
|
||||||
|
* @return <p>Scrapper animation data</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public AnimationData getScrapperAnimationData() {
|
||||||
|
return this.scrapperAnimationData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the animation data for the blacksmith's working animation
|
||||||
|
*
|
||||||
|
* @return <p>Blacksmith animation data</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public AnimationData getBlacksmithAnimationData() {
|
||||||
|
return this.blacksmithAnimationData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the sound data for the given identifier
|
||||||
|
*
|
||||||
|
* @param identifier <p>The identifier of the sound</p>
|
||||||
|
* @return <p>The sound's sound data</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public SoundData getSoundData(@NotNull SoundIdentifier identifier) {
|
||||||
|
return soundSettings.get(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.manager;
|
||||||
|
|
||||||
|
import net.knarcraft.blacksmithvisuals.BlacksmithVisuals;
|
||||||
|
import net.knarcraft.blacksmithvisuals.container.NPCData;
|
||||||
|
import net.knarcraft.blacksmithvisuals.property.NPCPosition;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A manager for NPC data
|
||||||
|
*/
|
||||||
|
public class NPCDataManager {
|
||||||
|
|
||||||
|
private final Map<UUID, NPCData> npcDataMap;
|
||||||
|
private static final File configurationFile = new File(BlacksmithVisuals.getInstance().getDataFolder(), "data.yml");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new NPC data manager
|
||||||
|
*
|
||||||
|
* @param npcDataMap <p>The map containing existing NPC data</p>
|
||||||
|
*/
|
||||||
|
public NPCDataManager(@NotNull Map<UUID, NPCData> npcDataMap) {
|
||||||
|
this.npcDataMap = npcDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the NPC data for the NPC with the given id
|
||||||
|
*
|
||||||
|
* @param npcId <p>The id of the NPC to get data for</p>
|
||||||
|
* @return <p>The NPC data, or null if not set</p>
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public NPCData getData(@NotNull UUID npcId) {
|
||||||
|
return this.npcDataMap.get(npcId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds new NPC data to an NPC
|
||||||
|
*
|
||||||
|
* @param npcId <p>The id of the NPC to add data to</p>
|
||||||
|
* @param npcData <p>The data to set</p>
|
||||||
|
*/
|
||||||
|
public void putData(@NotNull UUID npcId, @NotNull NPCData npcData) {
|
||||||
|
this.npcDataMap.put(npcId, npcData);
|
||||||
|
try {
|
||||||
|
this.save();
|
||||||
|
} catch (IOException exception) {
|
||||||
|
BlacksmithVisuals.getInstance().getLogger().log(Level.SEVERE, "Unable to save NPC data. Error was: " +
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves this data manager's data to disk
|
||||||
|
*
|
||||||
|
* @throws IOException <p>If unable to write the configuration file</p>
|
||||||
|
*/
|
||||||
|
public void save() throws IOException {
|
||||||
|
if (!configurationFile.exists() && !configurationFile.createNewFile()) {
|
||||||
|
throw new FileNotFoundException("data.yml could not be found or created. Please create it manually.");
|
||||||
|
}
|
||||||
|
FileConfiguration configuration = new YamlConfiguration();
|
||||||
|
for (Map.Entry<UUID, NPCData> entry : npcDataMap.entrySet()) {
|
||||||
|
ConfigurationSection npcSection = configuration.createSection(entry.getKey().toString());
|
||||||
|
npcSection.set("workingPositionNetherite", entry.getValue().positions().get(NPCPosition.WORKING_NETHERITE));
|
||||||
|
npcSection.set("workingPositionCrafting", entry.getValue().positions().get(NPCPosition.WORKING_CRAFTING));
|
||||||
|
npcSection.set("workingPositionRepairable", entry.getValue().positions().get(NPCPosition.WORKING_REPAIRABLE));
|
||||||
|
npcSection.set("idlePosition", entry.getValue().positions().get(NPCPosition.IDLE));
|
||||||
|
}
|
||||||
|
configuration.save(configurationFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a data manager from disk
|
||||||
|
*
|
||||||
|
* @return <p>The loaded data manager</p>
|
||||||
|
* @throws IOException <p>If unable to load the configuration file</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static NPCDataManager load() throws IOException {
|
||||||
|
if (!configurationFile.exists() && !configurationFile.createNewFile()) {
|
||||||
|
throw new FileNotFoundException("data.yml could not be found or created. Please create it manually.");
|
||||||
|
}
|
||||||
|
Map<UUID, NPCData> npcDataMap = new HashMap<>();
|
||||||
|
|
||||||
|
FileConfiguration configuration = YamlConfiguration.loadConfiguration(configurationFile);
|
||||||
|
for (String configurationSectionKey : configuration.getKeys(false)) {
|
||||||
|
ConfigurationSection section = configuration.getConfigurationSection(configurationSectionKey);
|
||||||
|
if (section == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<NPCPosition, Location> locationMap = new HashMap<>();
|
||||||
|
|
||||||
|
locationMap.put(NPCPosition.WORKING_NETHERITE, section.getLocation("workingPositionNetherite"));
|
||||||
|
locationMap.put(NPCPosition.WORKING_CRAFTING, section.getLocation("workingPositionCrafting"));
|
||||||
|
locationMap.put(NPCPosition.WORKING_REPAIRABLE, section.getLocation("workingPositionRepairable"));
|
||||||
|
locationMap.put(NPCPosition.IDLE, section.getLocation("idlePosition"));
|
||||||
|
npcDataMap.put(UUID.fromString(configurationSectionKey), new NPCData(locationMap));
|
||||||
|
}
|
||||||
|
return new NPCDataManager(npcDataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.property;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A definable NPC position
|
||||||
|
*/
|
||||||
|
public enum NPCPosition {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position the NPC should be in while idle
|
||||||
|
*/
|
||||||
|
IDLE("idle"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position the NPC should be in while undoing netherite or armor trims
|
||||||
|
*/
|
||||||
|
WORKING_NETHERITE("netherite-workstation"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position the NPC should be in while reforging armor, weapons or tools
|
||||||
|
*/
|
||||||
|
WORKING_REPAIRABLE("repairable-workstation"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position the NPC should be in while un-crafting items
|
||||||
|
*/
|
||||||
|
WORKING_CRAFTING("crafting-workstation"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String positionName;
|
||||||
|
|
||||||
|
NPCPosition(@NotNull String positionName) {
|
||||||
|
this.positionName = positionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user-friendly name for this position
|
||||||
|
*
|
||||||
|
* @return <p>The position name</p>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public String getPositionName() {
|
||||||
|
return this.positionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the NPC position the work station's material
|
||||||
|
*
|
||||||
|
* @param material <p>The material of the workstation</p>
|
||||||
|
* @return <p>The corresponding NPC position</p>
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static NPCPosition getFromMaterial(@NotNull Material material) {
|
||||||
|
return switch (material) {
|
||||||
|
case CRAFTING_TABLE -> NPCPosition.WORKING_CRAFTING;
|
||||||
|
case ANVIL -> NPCPosition.WORKING_REPAIRABLE;
|
||||||
|
case SMITHING_TABLE -> NPCPosition.WORKING_NETHERITE;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an NPC position from the given user input
|
||||||
|
*
|
||||||
|
* @param input <p>The input to get an NPC position from</p>
|
||||||
|
* @return <p>The NPC position, or null if not recognized</p>
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static NPCPosition fromInput(@NotNull String input) {
|
||||||
|
String cleaned = input.toLowerCase().replace("_", "-");
|
||||||
|
for (NPCPosition position : NPCPosition.values()) {
|
||||||
|
if (position.getPositionName().equalsIgnoreCase(cleaned)) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package net.knarcraft.blacksmithvisuals.property;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier for the available blacksmith/scrapper sounds
|
||||||
|
*/
|
||||||
|
public enum SoundIdentifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sound played while a blacksmith is working
|
||||||
|
*/
|
||||||
|
REFORGING_WORKING("blacksmith.sounds.reforgeWorking"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sound played when a blacksmith finishes successfully
|
||||||
|
*/
|
||||||
|
REFORGING_SUCCESS("blacksmith.sounds.reforgeSuccess"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sound played when a blacksmith finishes with a failure
|
||||||
|
*/
|
||||||
|
REFORGING_FAILURE("blacksmith.sounds.reforgeFailure"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sound played while a scrapper is working
|
||||||
|
*/
|
||||||
|
SALVAGING_WORKING("scrapper.sounds.salvageWorking"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sound played when a scrapper finishes successfully
|
||||||
|
*/
|
||||||
|
SALVAGING_SUCCESS("scrapper.sounds.salvageSuccess"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sound played when a scrapper finishes with a failure
|
||||||
|
*/
|
||||||
|
SALVAGING_FAILURE("scrapper.sounds.salvageFailure"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String configNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new sound identifier
|
||||||
|
*
|
||||||
|
* @param configNode <p>The sound's configuration node</p>
|
||||||
|
*/
|
||||||
|
SoundIdentifier(@NotNull String configNode) {
|
||||||
|
this.configNode = configNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the configuration node corresponding to the sound
|
||||||
|
*
|
||||||
|
* @return <p>The configuration node</p>
|
||||||
|
*/
|
||||||
|
public String getConfigNode() {
|
||||||
|
return this.configNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
98
src/main/resources/config.yml
Normal file
98
src/main/resources/config.yml
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
blacksmith:
|
||||||
|
animation:
|
||||||
|
# Whether to simulate hitting an anvil or similar by animating the blacksmith's off-hand
|
||||||
|
animateOffhand: true
|
||||||
|
# The delay between each potential arm move in ticks
|
||||||
|
animationDelay: 5
|
||||||
|
# The probability (percentage) of an arm swing happening once the animation delay is reached
|
||||||
|
animationChance: 20
|
||||||
|
# Sounds that play on specific actions or events
|
||||||
|
sounds:
|
||||||
|
# The sound played when reforging finishes, if successful
|
||||||
|
reforgeSuccess:
|
||||||
|
# If disabled, this sound will never play
|
||||||
|
enabled: true
|
||||||
|
# The sound to play (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html)
|
||||||
|
sound: BLOCK_ANVIL_USE
|
||||||
|
# The sound category to play the sound in (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/SoundCategory.html)
|
||||||
|
soundCategory: AMBIENT
|
||||||
|
# The pitch to play at, between 0 and 2. 1 is default.
|
||||||
|
pitch: 1
|
||||||
|
# The volume to play at. Between 0 and infinity. 1 is max volume, but higher value increases the range.
|
||||||
|
volume: 1
|
||||||
|
# The sound played when reforging finishes, if not successful.
|
||||||
|
reforgeFailure:
|
||||||
|
# If disabled, this sound will never play.
|
||||||
|
enabled: true
|
||||||
|
# The sound to play (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html).
|
||||||
|
sound: ENTITY_VILLAGER_NO
|
||||||
|
# The sound category to play the sound in (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/SoundCategory.html)
|
||||||
|
soundCategory: AMBIENT
|
||||||
|
# The pitch to play at, between 0 and 2. 1 is default.
|
||||||
|
pitch: 1
|
||||||
|
# The volume to play at. Between 0 and infinity. 1 is max volume, but higher value increases the range.
|
||||||
|
volume: 1
|
||||||
|
# The sound played when the NPC's arm moves.
|
||||||
|
reforgeWorking:
|
||||||
|
# If disabled, this sound will never play.
|
||||||
|
enabled: true
|
||||||
|
# The sound to play (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html).
|
||||||
|
sound: ITEM_ARMOR_EQUIP_NETHERITE
|
||||||
|
# The sound category to play the sound in (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/SoundCategory.html)
|
||||||
|
soundCategory: AMBIENT
|
||||||
|
# The pitch to play at, between 0 and 2. 1 is default.
|
||||||
|
pitch: 1
|
||||||
|
# The volume to play at. Between 0 and infinity. 1 is max volume, but higher value increases the range.
|
||||||
|
volume: 1
|
||||||
|
# The offset (delay), in ticks (negative or positive), before the sound should play.
|
||||||
|
# This can be used to better synchronize the sound to the arm movement.
|
||||||
|
offset: 0
|
||||||
|
scrapper:
|
||||||
|
animation:
|
||||||
|
# Whether to simulate hitting an anvil or similar by animating the scrapper's off-hand
|
||||||
|
animateOffhand: true
|
||||||
|
# The delay between each potential arm move in ticks
|
||||||
|
animationDelay: 5
|
||||||
|
# The probability (percentage) of an arm swing happening once the animation delay is reached
|
||||||
|
animationChance: 20
|
||||||
|
# Sounds that play on specific actions or events.
|
||||||
|
sounds:
|
||||||
|
# The sound played when salvaging finishes, if successful.
|
||||||
|
salvageSuccess:
|
||||||
|
# If disabled, this sound will never play.
|
||||||
|
enabled: true
|
||||||
|
# The sound to play (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html).
|
||||||
|
sound: BLOCK_ANVIL_USE
|
||||||
|
# The sound category to play the sound in (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/SoundCategory.html)
|
||||||
|
soundCategory: AMBIENT
|
||||||
|
# The pitch to play at, between 0 and 2. 1 is default.
|
||||||
|
pitch: 1
|
||||||
|
# The volume to play at. Between 0 and infinity. 1 is max volume, but higher value increases the range.
|
||||||
|
volume: 1
|
||||||
|
# The sound played when salvaging finishes, if not successful.
|
||||||
|
salvageFailure:
|
||||||
|
# If disabled, this sound will never play.
|
||||||
|
enabled: true
|
||||||
|
# The sound to play (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html).
|
||||||
|
sound: ENTITY_VILLAGER_NO
|
||||||
|
# The sound category to play the sound in (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/SoundCategory.html)
|
||||||
|
soundCategory: AMBIENT
|
||||||
|
# The pitch to play at, between 0 and 2. 1 is default.
|
||||||
|
pitch: 1
|
||||||
|
# The volume to play at. Between 0 and infinity. 1 is max volume, but higher value increases the range.
|
||||||
|
volume: 1
|
||||||
|
# The sound played when the NPC's arm moves.
|
||||||
|
salvageWorking:
|
||||||
|
# If disabled, this sound will never play.
|
||||||
|
enabled: true
|
||||||
|
# The sound to play (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html).
|
||||||
|
sound: ITEM_ARMOR_EQUIP_NETHERITE
|
||||||
|
# The sound category to play the sound in (https://hub.spigotmc.org/javadocs/spigot/org/bukkit/SoundCategory.html)
|
||||||
|
soundCategory: AMBIENT
|
||||||
|
# The pitch to play at, between 0 and 2. 1 is default.
|
||||||
|
pitch: 1
|
||||||
|
# The volume to play at. Between 0 and infinity. 1 is max volume, but higher value increases the range.
|
||||||
|
volume: 1
|
||||||
|
# The offset (delay), in ticks (negative or positive), before the sound should play.
|
||||||
|
# This can be used to better synchronize the sound to the arm movement.
|
||||||
|
offset: 0
|
@ -2,4 +2,35 @@ name: BlacksmithVisuals
|
|||||||
version: '${project.version}'
|
version: '${project.version}'
|
||||||
main: net.knarcraft.blacksmithvisuals.BlacksmithVisuals
|
main: net.knarcraft.blacksmithvisuals.BlacksmithVisuals
|
||||||
api-version: 1.20
|
api-version: 1.20
|
||||||
depend: [ ProtocolLib ]
|
depend: [ ProtocolLib, Blacksmith, Citizens ]
|
||||||
|
prefix: "Blacksmith Visuals"
|
||||||
|
description: "And add-on plugin to the blacksmith plugin that adds visual animation and configurable sounds."
|
||||||
|
website: "https://git.knarcraft.net/KnarCraft/BlacksmithVisuals"
|
||||||
|
|
||||||
|
commands:
|
||||||
|
reload:
|
||||||
|
permission: blacksmithvisuals.reload
|
||||||
|
usage: /<command>
|
||||||
|
description: Used to reload BlacksmithVisuals
|
||||||
|
setNPCPosition:
|
||||||
|
permission: blacksmithvisuals.setposition
|
||||||
|
usage: /<command> <position type>
|
||||||
|
description: |
|
||||||
|
Used to set a blacksmith or scrapper NPC's positions
|
||||||
|
/setNPCPosition idle
|
||||||
|
/setNPCPosition netherite-workstation
|
||||||
|
/setNPCPosition reforging-workstation
|
||||||
|
/setNPCPosition crafting-workstation
|
||||||
|
permissions:
|
||||||
|
blacksmithvisuals.*:
|
||||||
|
description: Gives all permissions
|
||||||
|
default: op
|
||||||
|
children:
|
||||||
|
- blacksmithvisuals.reload
|
||||||
|
- blacksmithvisuals.setposition
|
||||||
|
blacksmithvisuals.reload:
|
||||||
|
description: Allows reloading the plugin
|
||||||
|
default: false
|
||||||
|
blacksmithvisuals.setposition:
|
||||||
|
description: Allows use of the /setIdlePosition and /setWorkingPosition commands
|
||||||
|
default: false
|
Loading…
Reference in New Issue
Block a user