Improves code structure, and performs some necessary work for commands

This commit is contained in:
Kristian Knarvik 2022-08-08 14:14:42 +02:00
parent c557d969b7
commit cc39f8879a
12 changed files with 393 additions and 177 deletions

View File

@ -2,8 +2,14 @@ package net.knarcraft.blacksmith;
import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.CitizensAPI;
import net.knarcraft.blacksmith.command.BlackSmithCommand; import net.knarcraft.blacksmith.command.BlackSmithCommand;
import net.knarcraft.blacksmith.command.BlackSmithTabCompleter;
import net.knarcraft.blacksmith.config.GlobalSettings; import net.knarcraft.blacksmith.config.GlobalSettings;
import net.knarcraft.blacksmith.listener.NPCClickListener;
import net.knarcraft.blacksmith.listener.PlayerListener;
import net.knarcraft.blacksmith.manager.EconomyManager;
import net.knarcraft.blacksmith.trait.BlacksmithTrait;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.util.logging.Level; import java.util.logging.Level;
@ -69,8 +75,13 @@ public class BlacksmithPlugin extends JavaPlugin {
PluginCommand blacksmithCommand = this.getCommand("blacksmith"); PluginCommand blacksmithCommand = this.getCommand("blacksmith");
if (blacksmithCommand != null) { if (blacksmithCommand != null) {
blacksmithCommand.setExecutor(new BlackSmithCommand()); blacksmithCommand.setExecutor(new BlackSmithCommand());
blacksmithCommand.setTabCompleter(new BlackSmithTabCompleter());
} }
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new PlayerListener(), this);
pluginManager.registerEvents(new NPCClickListener(), this);
getLogger().log(Level.INFO, " v" + getDescription().getVersion() + " enabled."); getLogger().log(Level.INFO, " v" + getDescription().getVersion() + " enabled.");
} }

View File

@ -1,5 +1,6 @@
package net.knarcraft.blacksmith.command; package net.knarcraft.blacksmith.command;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -10,11 +11,20 @@ public class BlackSmithCommand implements CommandExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) { @NotNull String[] args) {
if (!sender.hasPermission("blacksmith.admin")) {
sender.sendMessage(ChatColor.RED + "You don't have the necessary permission for using this command.");
return true;
}
//TODO: This command should have one config sub-command which changes the default config values, and all //TODO: This command should have one config sub-command which changes the default config values, and all
// setting names which can be changed for each NPC. // setting names which can be changed for each NPC.
if (args.length > 0) { if (args.length > 0) {
if (args[0].equalsIgnoreCase("reload")) { if (args[0].equalsIgnoreCase("reload")) {
return new ReloadCommand().onCommand(sender, command, label, args); return new ReloadCommand().onCommand(sender, command, label, args);
} else if (args[0].equalsIgnoreCase("config")) {
//TODO: Allow changing any global/default setting + reloading here
} else {
return new NPCSettingCommand().onCommand(sender, command, label, args);
} }
} }
return false; return false;

View File

@ -0,0 +1,31 @@
package net.knarcraft.blacksmith.command;
import net.knarcraft.blacksmith.config.NPCSetting;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class BlackSmithTabCompleter implements TabCompleter {
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (!sender.hasPermission("blacksmith.admin")) {
return new ArrayList<>();
}
List<String> availableCommands = new ArrayList<>();
for (NPCSetting setting : NPCSetting.values()) {
availableCommands.add(setting.getCommandName());
}
availableCommands.add("reload");
return availableCommands;
}
}

View File

@ -0,0 +1,38 @@
package net.knarcraft.blacksmith.command;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC;
import net.knarcraft.blacksmith.config.NPCSetting;
import net.knarcraft.blacksmith.trait.BlacksmithTrait;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class NPCSettingCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
NPC npc = CitizensAPI.getDefaultNPCSelector().getSelected(sender);
if (npc == null || !npc.hasTrait(BlacksmithTrait.class)) {
sender.sendMessage("You must select an NPC before running this command");
return true;
}
BlacksmithTrait blacksmithTrait = npc.getTrait(BlacksmithTrait.class);
for (NPCSetting npcSetting : NPCSetting.values()) {
String commandName = npcSetting.getCommandName();
if (commandName.equalsIgnoreCase(args[0])) {
if (args.length < 2) {
sender.sendMessage("Current value of " + commandName + ": " +
blacksmithTrait.getSettings().getRawValue(npcSetting));
} else {
blacksmithTrait.getSettings().changeSetting(npcSetting, args[1]);
}
return true;
}
}
return false;
}
}

View File

@ -7,54 +7,60 @@ import java.util.Arrays;
*/ */
public enum NPCSetting { public enum NPCSetting {
DROP_ITEM("defaults.drop-item", true), DROP_ITEM("defaults.drop-item", true, "dropItem"),
DISABLE_COOL_DOWN("defaults.disable-cool-down", false), DISABLE_COOL_DOWN("defaults.disable-cool-down", false, "disableCoolDown"),
DISABLE_DELAY("defaults.disable-delay", false), DISABLE_DELAY("defaults.disable-delay", false, "disableDelay"),
FAIL_CHANCE("defaults.percent-chance-to-fail-reforge", 10), FAIL_CHANCE("defaults.percent-chance-to-fail-reforge", 10, "failReforgeChance"),
EXTRA_ENCHANTMENT_CHANCE("defaults.percent-chance-for-extra-enchantment", 5), EXTRA_ENCHANTMENT_CHANCE("defaults.percent-chance-for-extra-enchantment", 5,
MAX_ENCHANTMENTS("defaults.maximum-enchantments", 3), "extraEnchantmentChance"),
MAX_REFORGE_DELAY("defaults.delays-in-seconds.maximum", 30), MAX_ENCHANTMENTS("defaults.maximum-enchantments", 3, "maxEnchantments"),
MIN_REFORGE_DELAY("defaults.delays-in-seconds.minimum", 5), MAX_REFORGE_DELAY("defaults.delays-in-seconds.maximum", 30, "maxReforgeDelay"),
REFORGE_COOL_DOWN("defaults.delays-in-seconds.reforge-cool-down", 60), MIN_REFORGE_DELAY("defaults.delays-in-seconds.minimum", 5, "minReforgeDelay"),
REFORGE_ABLE_ITEMS("defaults.reforge-able-items", new String[]{}), REFORGE_COOL_DOWN("defaults.delays-in-seconds.reforge-cool-down", 60, "reforgeCoolDown"),
REFORGE_ABLE_ITEMS("defaults.reforge-able-items", new String[]{}, "reforgeAbleItems"),
/*----------- /*-----------
| Messages | | Messages |
-----------*/ -----------*/
BUSY_WITH_PLAYER_MESSAGE("defaults.messages.busy-with-player", "§cI'm busy at the moment. Come back later!"), BUSY_WITH_PLAYER_MESSAGE("defaults.messages.busy-with-player",
BUSY_WITH_REFORGE_MESSAGE("defaults.messages.busy-with-reforge", "§cI'm working on it. Be patient!"), "§cI'm busy at the moment. Come back later!", "busyPlayerMessage"),
COOL_DOWN_UNEXPIRED_MESSAGE( BUSY_WITH_REFORGE_MESSAGE("defaults.messages.busy-with-reforge", "§cI'm working on it. Be patient!",
"defaults.messages.cool-down-not-expired", "busyReforgeMessage"),
"§cYou've already had your chance! Give me a break!"), COOL_DOWN_UNEXPIRED_MESSAGE("defaults.messages.cool-down-not-expired",
"§cYou've already had your chance! Give me a break!", "coolDownUnexpiredMessage"),
COST_MESSAGE( COST_MESSAGE(
"defaults.messages.cost", "defaults.messages.cost",
"§eIt will cost §a<price> §eto reforge that §a<item>§e! Click again to reforge!"), "§eIt will cost §a<price> §eto reforge that §a<item>§e! Click again to reforge!", "costMessage"),
FAIL_MESSAGE("defaults.messages.fail-reforge", "§cWhoops! Didn't mean to do that! Maybe next time?"), FAIL_MESSAGE("defaults.messages.fail-reforge", "§cWhoops! Didn't mean to do that! Maybe next time?",
INSUFFICIENT_FUNDS_MESSAGE( "failReforgeMessage"),
"defaults.messages.insufficient-funds", INSUFFICIENT_FUNDS_MESSAGE("defaults.messages.insufficient-funds",
"§cYou don't have enough money to reforge that item!"), "§cYou don't have enough money to reforge that item!", "insufficientFundsMessage"),
INVALID_ITEM_MESSAGE("defaults.messages.invalid-item", "§cI'm sorry, but I don't know how to reforge that!"), INVALID_ITEM_MESSAGE("defaults.messages.invalid-item", "§cI'm sorry, but I don't know how to reforge that!",
ITEM_UNEXPECTEDLY_CHANGED_MESSAGE( "invalidItemMessage"),
"defaults.messages.item-changed-during-reforge", ITEM_UNEXPECTEDLY_CHANGED_MESSAGE("defaults.messages.item-changed-during-reforge",
"§cThat's not the item you wanted to reforge before!"), "§cThat's not the item you wanted to reforge before!", "itemChangedMessage"),
START_REFORGE_MESSAGE("defaults.messages.start-reforge", "§eOk, let's see what I can do..."), START_REFORGE_MESSAGE("defaults.messages.start-reforge", "§eOk, let's see what I can do...",
SUCCESS_MESSAGE("defaults.messages.successful-reforge", "There you go! All better!"); "startReforgeMessage"),
SUCCESS_MESSAGE("defaults.messages.successful-reforge", "There you go! All better!", "successMessage");
private final String path; private final String path;
private final String childPath; private final String childPath;
private final Object value; private final Object value;
private final String commandName;
/** /**
* Instantiates a new setting * Instantiates a new setting
* *
* @param path <p>The full config path for this setting</p> * @param path <p>The full config path for this setting</p>
* @param value <p>The default value of this setting</p> * @param value <p>The default value of this setting</p>
* @param commandName <p>The name of the command used to change this setting</p>
*/ */
NPCSetting(String path, Object value) { NPCSetting(String path, Object value, String commandName) {
this.path = path; this.path = path;
this.value = value; this.value = value;
String[] pathParts = path.split("\\."); String[] pathParts = path.split("\\.");
this.childPath = String.join(".", Arrays.copyOfRange(pathParts, 1, pathParts.length)); this.childPath = String.join(".", Arrays.copyOfRange(pathParts, 1, pathParts.length));
this.commandName = commandName;
} }
/** /**
@ -80,8 +86,17 @@ public enum NPCSetting {
* *
* @return <p>The value of this setting</p> * @return <p>The value of this setting</p>
*/ */
Object getDefaultValue() { public Object getDefaultValue() {
return value; return value;
} }
/**
* The name of the command used to change this setting
*
* @return <p>The name of this setting's command</p>
*/
public String getCommandName() {
return commandName;
}
} }

View File

@ -50,6 +50,26 @@ public class NPCSettings {
} }
} }
/**
* Changes one setting to the given value
*
* @param setting <p>The setting to change</p>
* @param newValue <p>The new value of the setting</p>
*/
public void changeSetting(NPCSetting setting, Object newValue) {
currentValues.put(setting, newValue);
}
/**
* Gets the raw current value of a setting
*
* @param setting <p>The setting to get the value of</p>
* @return <p>The current value of the setting</p>
*/
public Object getRawValue(NPCSetting setting) {
return currentValues.get(setting);
}
/** /**
* Gets the message to display when the blacksmith is busy with another player * Gets the message to display when the blacksmith is busy with another player
* *

View File

@ -0,0 +1,33 @@
package net.knarcraft.blacksmith.listener;
import net.knarcraft.blacksmith.trait.BlacksmithTrait;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class NPCClickListener implements Listener {
@EventHandler
public void onRightClick(net.citizensnpcs.api.event.NPCRightClickEvent event) {
//We only care about blacksmiths
if (!event.getNPC().hasTrait(BlacksmithTrait.class)) {
return;
}
BlacksmithTrait blacksmithTrait = event.getNPC().getTrait(BlacksmithTrait.class);
//Perform any necessary pre-session work
Player player = event.getClicker();
if (!blacksmithTrait.prepareForSession(player)) {
return;
}
if (blacksmithTrait.hasSession()) {
//Continue the existing session
blacksmithTrait.continueSession(player);
} else {
//Start a new session
blacksmithTrait.startSession(player);
}
}
}

View File

@ -0,0 +1,91 @@
package net.knarcraft.blacksmith.listener;
import net.citizensnpcs.api.CitizensAPI;
import net.knarcraft.blacksmith.trait.BlacksmithTrait;
import org.bukkit.enchantments.EnchantmentTarget;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
public class PlayerListener implements Listener {
/**
* Blocks armor equipment if right-clicking a blacksmith
*
* @param event <p>The triggered event</p>
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onClick(PlayerInteractEvent event) {
if (event.getHand() == null || (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() !=
Action.RIGHT_CLICK_BLOCK)) {
return;
}
//If the player is not looking at a blacksmith, there is no need to do anything
Entity target = getTarget(event.getPlayer(), event.getPlayer().getNearbyEntities(15, 10, 15));
if (!CitizensAPI.getNPCRegistry().isNPC(target) ||
!CitizensAPI.getNPCRegistry().getNPC(target).hasTrait(BlacksmithTrait.class)) {
return;
}
//Block armor equip if interacting with a blacksmith
ItemStack usedItem = event.getPlayer().getInventory().getItem(event.getHand());
if (usedItem != null && isArmor(usedItem)) {
event.setUseItemInHand(Event.Result.DENY);
event.getPlayer().updateInventory();
}
}
/**
* Gets whether the given item is a type of armor
*
* @param item <p>The item to check if is armor or not</p>
* @return <p>True if the given item is a type of armor</p>
*/
public static boolean isArmor(ItemStack item) {
return EnchantmentTarget.WEARABLE.includes(item);
//TODO: Remove this commented-out code if the above line works
/*return switch (item.getType()) {
case LEATHER_HELMET, LEATHER_CHESTPLATE, LEATHER_LEGGINGS, LEATHER_BOOTS, CHAINMAIL_HELMET,
CHAINMAIL_CHESTPLATE, CHAINMAIL_LEGGINGS, CHAINMAIL_BOOTS, GOLDEN_HELMET, GOLDEN_CHESTPLATE,
GOLDEN_LEGGINGS, GOLDEN_BOOTS, IRON_HELMET, IRON_CHESTPLATE, IRON_LEGGINGS, IRON_BOOTS,
DIAMOND_HELMET, DIAMOND_CHESTPLATE, DIAMOND_LEGGINGS, DIAMOND_BOOTS, TURTLE_HELMET, ELYTRA,
NETHERITE_HELMET, NETHERITE_CHESTPLATE, NETHERITE_LEGGINGS, NETHERITE_BOOTS -> true;
default -> false;
};*/
}
/**
* Gets the target-entity the entity is looking at
*
* @param entity <p>The entity looking at something</p>
* @param entities <p>Entities near the player</p>
* @param <T> <p>The type of entity the player is looking at</p>
* @return <p>The entity the player is looking at, or null if no such entity exists</p>
*/
private static <T extends Entity> T getTarget(final Entity entity, final Iterable<T> entities) {
if (entity == null) {
return null;
}
T target = null;
final double threshold = 1;
for (final T other : entities) {
final Vector n = other.getLocation().toVector().subtract(entity.getLocation().toVector());
if (entity.getLocation().getDirection().normalize().crossProduct(n).lengthSquared() < threshold &&
n.normalize().dot(entity.getLocation().getDirection().normalize()) >= 0) {
if (target == null ||
target.getLocation().distanceSquared(entity.getLocation()) >
other.getLocation().distanceSquared(entity.getLocation())) {
target = other;
}
}
}
return target;
}
}

View File

@ -1,5 +1,6 @@
package net.knarcraft.blacksmith; package net.knarcraft.blacksmith.manager;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.GlobalSettings; import net.knarcraft.blacksmith.config.GlobalSettings;
import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.Economy;
import org.bukkit.Material; import org.bukkit.Material;

View File

@ -1,24 +1,17 @@
package net.knarcraft.blacksmith; package net.knarcraft.blacksmith.trait;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.trait.Trait; import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.api.util.DataKey;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.NPCSettings; import net.knarcraft.blacksmith.config.NPCSettings;
import net.knarcraft.blacksmith.manager.EconomyManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.enchantments.EnchantmentTarget;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.Damageable;
import org.bukkit.util.Vector;
import java.util.Calendar; import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
@ -47,6 +40,24 @@ public class BlacksmithTrait extends Trait {
this.config = new NPCSettings(BlacksmithPlugin.getInstance().getSettings()); this.config = new NPCSettings(BlacksmithPlugin.getInstance().getSettings());
} }
/**
* Gets the current settings for this NPC
*
* @return <p>The current settings for this NPC</p>
*/
public NPCSettings getSettings() {
return config;
}
/**
* Gets whether this blacksmith is already in a re-forge session
*
* @return <p>Whether already in a re-forge session</p>
*/
public boolean hasSession() {
return this.session != null;
}
/** /**
* Adds a cool-down for the given player's next blacksmith reforge * Adds a cool-down for the given player's next blacksmith reforge
* *
@ -84,55 +95,13 @@ public class BlacksmithTrait extends Trait {
config.saveVariables(key); config.saveVariables(key);
} }
@EventHandler(priority = EventPriority.HIGHEST)
public void onClick(PlayerInteractEvent event) {
if (event.getHand() == null || (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() !=
Action.RIGHT_CLICK_BLOCK)) {
return;
}
//If the player is not looking at a blacksmith, there is no need to do anything
Entity target = getTarget(event.getPlayer(), event.getPlayer().getNearbyEntities(15, 10, 15));
if (!CitizensAPI.getNPCRegistry().isNPC(target) ||
!CitizensAPI.getNPCRegistry().getNPC(target).hasTrait(BlacksmithTrait.class)) {
return;
}
//Block armor equip if interacting with a blacksmith
ItemStack usedItem = event.getPlayer().getInventory().getItem(event.getHand());
if (usedItem != null && isArmor(usedItem)) {
event.setUseItemInHand(Event.Result.DENY);
event.getPlayer().updateInventory();
}
}
@EventHandler
public void onRightClick(net.citizensnpcs.api.event.NPCRightClickEvent event) {
if (this.npc != event.getNPC()) {
return;
}
//Perform any necessary pre-session work
Player player = event.getClicker();
if (!prepareForSession(player)) {
return;
}
if (session != null) {
//Continue the existing session
continueSession(player);
} else {
//Start a new session
startSession(player);
}
}
/** /**
* Performs necessary work before the session is started or continued * Performs necessary work before the session is started or continued
* *
* @param player <p>The player to prepare the session for</p> * @param player <p>The player to prepare the session for</p>
* @return <p>True if preparations were successful. False if a session shouldn't be started</p> * @return <p>True if preparations were successful. False if a session shouldn't be started</p>
*/ */
private boolean prepareForSession(Player player) { public boolean prepareForSession(Player player) {
//If cool-down has been disabled after it was set for this player, remove the cool-down //If cool-down has been disabled after it was set for this player, remove the cool-down
if (config.getDisableCoolDown() && coolDowns.get(player.getUniqueId()) != null) { if (config.getDisableCoolDown() && coolDowns.get(player.getUniqueId()) != null) {
coolDowns.remove(player.getUniqueId()); coolDowns.remove(player.getUniqueId());
@ -141,6 +110,7 @@ public class BlacksmithTrait extends Trait {
if (!player.hasPermission("blacksmith.reforge")) { if (!player.hasPermission("blacksmith.reforge")) {
return false; return false;
} }
//Deny if on cool-down, or remove cool-down if expired //Deny if on cool-down, or remove cool-down if expired
if (coolDowns.get(player.getUniqueId()) != null) { if (coolDowns.get(player.getUniqueId()) != null) {
if (!Calendar.getInstance().after(coolDowns.get(player.getUniqueId()))) { if (!Calendar.getInstance().after(coolDowns.get(player.getUniqueId()))) {
@ -165,7 +135,7 @@ public class BlacksmithTrait extends Trait {
* *
* @param player <p>The player to continue the session for</p> * @param player <p>The player to continue the session for</p>
*/ */
private void continueSession(Player player) { public void continueSession(Player player) {
//Another player is using the blacksmith //Another player is using the blacksmith
if (!session.isInSession(player)) { if (!session.isInSession(player)) {
player.sendMessage(config.getBusyWithPlayerMessage()); player.sendMessage(config.getBusyWithPlayerMessage());
@ -191,7 +161,7 @@ public class BlacksmithTrait extends Trait {
* *
* @param player <p>The player to start the session for</p> * @param player <p>The player to start the session for</p>
*/ */
private void startSession(Player player) { public void startSession(Player player) {
ItemStack hand = player.getInventory().getItemInMainHand(); ItemStack hand = player.getInventory().getItemInMainHand();
//Refuse if not repairable, or if reforge-able items is set, but doesn't include the held item //Refuse if not repairable, or if reforge-able items is set, but doesn't include the held item
List<Material> reforgeAbleItems = config.getReforgeAbleItems(); List<Material> reforgeAbleItems = config.getReforgeAbleItems();
@ -232,34 +202,6 @@ public class BlacksmithTrait extends Trait {
player.getInventory().setItemInMainHand(null); player.getInventory().setItemInMainHand(null);
} }
/**
* Gets the target-entity the entity is looking at
*
* @param entity <p>The entity looking at something</p>
* @param entities <p>Entities near the player</p>
* @param <T> <p>The type of entity the player is looking at</p>
* @return <p>The entity the player is looking at, or null if no such entity exists</p>
*/
private static <T extends Entity> T getTarget(final Entity entity, final Iterable<T> entities) {
if (entity == null) {
return null;
}
T target = null;
final double threshold = 1;
for (final T other : entities) {
final Vector n = other.getLocation().toVector().subtract(entity.getLocation().toVector());
if (entity.getLocation().getDirection().normalize().crossProduct(n).lengthSquared() < threshold &&
n.normalize().dot(entity.getLocation().getDirection().normalize()) >= 0) {
if (target == null ||
target.getLocation().distanceSquared(entity.getLocation()) >
other.getLocation().distanceSquared(entity.getLocation())) {
target = other;
}
}
}
return target;
}
/** /**
* Gets whether the given item is repairable * Gets whether the given item is repairable
* *
@ -270,23 +212,4 @@ public class BlacksmithTrait extends Trait {
return item.getItemMeta() instanceof Damageable; return item.getItemMeta() instanceof Damageable;
} }
/**
* Gets whether the given item is a type of armor
*
* @param item <p>The item to check if is armor or not</p>
* @return <p>True if the given item is a type of armor</p>
*/
public static boolean isArmor(ItemStack item) {
return EnchantmentTarget.WEARABLE.includes(item);
//TODO: Remove this commented-out code if the above line works
/*return switch (item.getType()) {
case LEATHER_HELMET, LEATHER_CHESTPLATE, LEATHER_LEGGINGS, LEATHER_BOOTS, CHAINMAIL_HELMET,
CHAINMAIL_CHESTPLATE, CHAINMAIL_LEGGINGS, CHAINMAIL_BOOTS, GOLDEN_HELMET, GOLDEN_CHESTPLATE,
GOLDEN_LEGGINGS, GOLDEN_BOOTS, IRON_HELMET, IRON_CHESTPLATE, IRON_LEGGINGS, IRON_BOOTS,
DIAMOND_HELMET, DIAMOND_CHESTPLATE, DIAMOND_LEGGINGS, DIAMOND_BOOTS, TURTLE_HELMET, ELYTRA,
NETHERITE_HELMET, NETHERITE_CHESTPLATE, NETHERITE_LEGGINGS, NETHERITE_BOOTS -> true;
default -> false;
};*/
}
} }

View File

@ -1,7 +1,9 @@
package net.knarcraft.blacksmith; package net.knarcraft.blacksmith.trait;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.NPCSettings; import net.knarcraft.blacksmith.config.NPCSettings;
import net.knarcraft.blacksmith.manager.EconomyManager;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
@ -27,6 +29,7 @@ public class ReforgeSession implements Runnable {
private int taskId; private int taskId;
private final NPCSettings config; private final NPCSettings config;
private static final String[] enchantments = new String[Enchantment.values().length]; private static final String[] enchantments = new String[Enchantment.values().length];
private static final Random random = new Random();
/** /**
* Instantiates a new session * Instantiates a new session
@ -49,16 +52,24 @@ public class ReforgeSession implements Runnable {
} }
} }
/**
* Runs the actual re-forge which fixes the item and gives it back to the player
*/
@Override @Override
public void run() { public void run() {
player.sendMessage(reforgeItemInHand() ? config.getSuccessMessage() : config.getFailMessage()); player.sendMessage(reforgeItem() ? config.getSuccessMessage() : config.getFailMessage());
//Stop the re-forged item from displaying in the blacksmith's hand
if (npc.getEntity() instanceof Player) { if (npc.getEntity() instanceof Player) {
((Player) npc.getEntity()).getInventory().setItemInMainHand(null); ((Player) npc.getEntity()).getInventory().setItemInMainHand(null);
} else { } else {
Objects.requireNonNull(((LivingEntity) npc.getEntity()).getEquipment()).setItemInMainHand(null); Objects.requireNonNull(((LivingEntity) npc.getEntity()).getEquipment()).setItemInMainHand(null);
} }
//Give the item back to the player
if (!config.getDisableDelay()) { if (!config.getDisableDelay()) {
if (config.getDropItem()) { //If the player isn't online, drop the item to prevent it from disappearing
if (config.getDropItem() || !player.isOnline()) {
player.getWorld().dropItemNaturally(npc.getEntity().getLocation(), itemToReforge); player.getWorld().dropItemNaturally(npc.getEntity().getLocation(), itemToReforge);
} else { } else {
player.getInventory().addItem(itemToReforge); player.getInventory().addItem(itemToReforge);
@ -66,59 +77,91 @@ public class ReforgeSession implements Runnable {
} else { } else {
player.getInventory().setItemInMainHand(itemToReforge); player.getInventory().setItemInMainHand(itemToReforge);
} }
//Mark this blacksmith as available
blacksmithTrait.unsetSession(); blacksmithTrait.unsetSession();
// Start cool down
// Start cool-down
Calendar wait = Calendar.getInstance(); Calendar wait = Calendar.getInstance();
wait.add(Calendar.SECOND, config.getReforgeCoolDown()); wait.add(Calendar.SECOND, config.getReforgeCoolDown());
blacksmithTrait.addCoolDown(player.getUniqueId(), wait); blacksmithTrait.addCoolDown(player.getUniqueId(), wait);
} }
private boolean reforgeItemInHand() { /**
Random random = new Random(); * Performs the actual re-forge where the item's damage is reduced
*
* @return <p>Whether the re-forge was successful. False if the blacksmith failed to fully repair the item.</p>
*/
private boolean reforgeItem() {
if (random.nextInt(100) < config.getFailChance()) { if (random.nextInt(100) < config.getFailChance()) {
for (Enchantment enchantment : itemToReforge.getEnchantments().keySet()) { failReforge();
// Remove or downgrade enchantments
if (random.nextBoolean()) {
itemToReforge.removeEnchantment(enchantment);
} else {
if (itemToReforge.getEnchantmentLevel(enchantment) > 1) {
itemToReforge.removeEnchantment(enchantment);
itemToReforge.addEnchantment(enchantment, 1);
}
}
}
// Damage the item
short reforgeDurability = EconomyManager.getDurability(itemToReforge);
short durability = (short) (reforgeDurability + reforgeDurability * random.nextInt(8));
short maxDurability = itemToReforge.getType().getMaxDurability();
if (durability <= 0) {
durability = (short) (maxDurability / 3);
} else if (reforgeDurability + durability > maxDurability) {
durability = (short) (maxDurability - random.nextInt(maxDurability - 25));
}
updateDamage(itemToReforge, maxDurability - durability);
return false; return false;
} else { } else {
updateDamage(itemToReforge, 0); succeedReforge();
// Add random enchantments
int roll = random.nextInt(100);
if (roll < config.getExtraEnchantmentChance() && itemToReforge.getEnchantments().keySet().size() < config.getMaxEnchantments()) {
Enchantment enchantment = Enchantment.getByKey(NamespacedKey.fromString(enchantments[random.nextInt(enchantments.length)]));
if (Objects.requireNonNull(enchantment).canEnchantItem(itemToReforge)) {
int randomBound = enchantment.getMaxLevel() - enchantment.getStartLevel();
//A workaround for the random method's bound sometimes being negative
if (randomBound >= 0) {
itemToReforge.addEnchantment(enchantment, random.nextInt(randomBound) +
enchantment.getStartLevel());
}
}
}
return true; return true;
} }
} }
/**
* The method to run when a blacksmith successfully re-forges an item
*/
private void succeedReforge() {
// Remove any damage done to the item
updateDamage(itemToReforge, 0);
// Add random enchantments
int roll = random.nextInt(100);
if (!(roll < config.getExtraEnchantmentChance() &&
itemToReforge.getEnchantments().keySet().size() < config.getMaxEnchantments())) {
// Abort if randomness isn't on our side, or if max enchantments has been reached
return;
}
// Choose a random enchantment
Enchantment enchantment;
int maxRetries = 100;
int retries = 0;
do {
// Try to find a working enchantment for the re-forged item up to maxRetries times
enchantment = Enchantment.getByKey(NamespacedKey.fromString(enchantments[random.nextInt(enchantments.length)]));
} while ((enchantment == null || !enchantment.canEnchantItem(itemToReforge)) && (retries++ < maxRetries));
if (enchantment != null && enchantment.canEnchantItem(itemToReforge)) {
int randomBound = enchantment.getMaxLevel() - enchantment.getStartLevel();
//A workaround for the random method's bound sometimes being negative
if (randomBound >= 0) {
itemToReforge.addEnchantment(enchantment, random.nextInt(randomBound) + enchantment.getStartLevel());
}
}
}
/**
* The method to run when a blacksmith fails re-forging an item
*/
private void failReforge() {
// Remove or downgrade existing enchantments
for (Enchantment enchantment : itemToReforge.getEnchantments().keySet()) {
if (random.nextBoolean()) {
itemToReforge.removeEnchantment(enchantment);
} else {
if (itemToReforge.getEnchantmentLevel(enchantment) > 1) {
itemToReforge.removeEnchantment(enchantment);
itemToReforge.addEnchantment(enchantment, 1);
}
}
}
// Damage the item
short currentItemDurability = EconomyManager.getDurability(itemToReforge);
short newDurability = (short) (currentItemDurability + (currentItemDurability * random.nextInt(8)));
short maxDurability = itemToReforge.getType().getMaxDurability();
if (newDurability <= 0) {
newDurability = (short) (maxDurability / 3);
} else if (currentItemDurability + newDurability > maxDurability) {
newDurability = (short) (maxDurability - random.nextInt(maxDurability - 25));
}
updateDamage(itemToReforge, maxDurability - newDurability);
}
/** /**
* Updates the damage done to an item * Updates the damage done to an item
* *

View File

@ -10,7 +10,7 @@ api-version: 1.18
commands: commands:
blacksmith: blacksmith:
permission: blacksmith.admin permission: blacksmith.admin
description: reloads the config file for Blacksmith description: Used for configuring a blacksmith or the plugin
permissions: permissions:
blacksmith.admin: blacksmith.admin:
description: Allows blacksmith configuration description: Allows blacksmith configuration