Improves code structure, and performs some necessary work for commands

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

@ -0,0 +1,215 @@
package net.knarcraft.blacksmith.trait;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.util.DataKey;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.NPCSettings;
import net.knarcraft.blacksmith.manager.EconomyManager;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* The class representing the Blacksmith NPC trait
*/
public class BlacksmithTrait extends Trait {
private final Map<UUID, Calendar> coolDowns = new HashMap<>();
private ReforgeSession session;
private final NPCSettings config;
private long _sessionStart = System.currentTimeMillis();
/**
* Instantiates a new blacksmith trait
*/
public BlacksmithTrait() {
super("blacksmith");
//This should crash if the blacksmith plugin hasn't been properly registered
Bukkit.getServer().getPluginManager().getPlugin("Blacksmith");
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
*
* @param playerUUID <p>The ID of the player to add the cool-down for</p>
* @param waitUntil <p>The time when the player can interact again</p>
*/
public void addCoolDown(UUID playerUUID, Calendar waitUntil) {
coolDowns.put(playerUUID, waitUntil);
}
/**
* Unsets the session of this blacksmith, making it ready for another round
*/
public void unsetSession() {
this.session = null;
}
/**
* Loads all config values stored in citizens' config file for this NPC
*
* @param key <p>The data key used for the config root</p>
*/
@Override
public void load(DataKey key) {
config.loadVariables(key);
}
/**
* Saves all config values for this NPC
*
* @param key <p>The data key used for the config root</p>
*/
@Override
public void save(DataKey key) {
config.saveVariables(key);
}
/**
* Performs necessary work before the session is started or continued
*
* @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>
*/
public boolean prepareForSession(Player player) {
//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) {
coolDowns.remove(player.getUniqueId());
}
//Deny if permission is missing
if (!player.hasPermission("blacksmith.reforge")) {
return false;
}
//Deny if on cool-down, or remove cool-down if expired
if (coolDowns.get(player.getUniqueId()) != null) {
if (!Calendar.getInstance().after(coolDowns.get(player.getUniqueId()))) {
player.sendMessage(config.getCoolDownUnexpiredMessage());
return false;
}
coolDowns.remove(player.getUniqueId());
}
//If already in a session, but the player has failed to interact, or left the blacksmith, allow a new session
if (session != null) {
if (System.currentTimeMillis() > _sessionStart + 10 * 1000 ||
this.npc.getEntity().getLocation().distance(session.getPlayer().getLocation()) > 20) {
session = null;
}
}
return true;
}
/**
* Tries to continue the session for the given player
*
* @param player <p>The player to continue the session for</p>
*/
public void continueSession(Player player) {
//Another player is using the blacksmith
if (!session.isInSession(player)) {
player.sendMessage(config.getBusyWithPlayerMessage());
return;
}
//The blacksmith is already reforging for the player
if (session.isRunning()) {
player.sendMessage(config.getBusyReforgingMessage());
return;
}
if (session.endSession()) {
//Quit if the player cannot afford, or has changed their item
session = null;
} else {
//Start reforging for the player
reforge(npc, player);
}
}
/**
* Starts a new session, and prepares to repair the player's item
*
* @param player <p>The player to start the session for</p>
*/
public void startSession(Player player) {
ItemStack hand = player.getInventory().getItemInMainHand();
//Refuse if not repairable, or if reforge-able items is set, but doesn't include the held item
List<Material> reforgeAbleItems = config.getReforgeAbleItems();
if (!isRepairable(hand) || (!reforgeAbleItems.isEmpty() && !reforgeAbleItems.contains(hand.getType()))) {
player.sendMessage(config.getInvalidItemMessage());
return;
}
//Start a new reforge session for the player
_sessionStart = System.currentTimeMillis();
session = new ReforgeSession(this, player, npc, config);
//Tell the player the cost of repairing the item
String cost = EconomyManager.formatCost(player);
String itemName = hand.getType().name().toLowerCase().replace('_', ' ');
player.sendMessage(config.getCostMessage().replace("<price>", cost).replace("<item>", itemName));
}
/**
* Starts reforging the player's item
*
* @param npc <p>The NPC performing the reforge</p>
* @param player <p>The player that initiated the reforge</p>
*/
private void reforge(NPC npc, Player player) {
player.sendMessage(config.getStartReforgeMessage());
EconomyManager.withdraw(player);
session.beginReforge();
ItemStack heldItem = player.getInventory().getItemInMainHand();
//Display the item in the NPC's hand
if (npc.getEntity() instanceof Player) {
((Player) npc.getEntity()).getInventory().setItemInMainHand(heldItem);
} else {
Objects.requireNonNull(((LivingEntity) npc.getEntity()).getEquipment()).setItemInMainHand(heldItem);
}
//Remove the item from the player's inventory
player.getInventory().setItemInMainHand(null);
}
/**
* Gets whether the given item is repairable
*
* @param item <p>The item to check</p>
* @return <p>True if the item is repairable</p>
*/
private static boolean isRepairable(ItemStack item) {
return item.getItemMeta() instanceof Damageable;
}
}

@ -0,0 +1,248 @@
package net.knarcraft.blacksmith.trait;
import net.citizensnpcs.api.npc.NPC;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.NPCSettings;
import net.knarcraft.blacksmith.manager.EconomyManager;
import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.Calendar;
import java.util.Objects;
import java.util.Random;
import java.util.logging.Level;
/**
* A representation of the session between a player and a blacksmith
*/
public class ReforgeSession implements Runnable {
private final BlacksmithTrait blacksmithTrait;
private final Player player;
private final NPC npc;
private final ItemStack itemToReforge;
private int taskId;
private final NPCSettings config;
private static final String[] enchantments = new String[Enchantment.values().length];
private static final Random random = new Random();
/**
* Instantiates a new session
*
* @param blacksmithTrait <p>A reference to the blacksmith trait</p>
* @param player <p>The player initiating the session</p>
* @param npc <p>The Blacksmith NPC involved in the session</p>
* @param config <p>The config to use for the session</p>
*/
ReforgeSession(BlacksmithTrait blacksmithTrait, Player player, NPC npc, NPCSettings config) {
this.blacksmithTrait = blacksmithTrait;
this.player = player;
this.npc = npc;
itemToReforge = player.getInventory().getItemInMainHand();
this.config = config;
int i = 0;
for (Enchantment enchantment : Enchantment.values()) {
enchantments[i++] = enchantment.getKey().toString();
}
}
/**
* Runs the actual re-forge which fixes the item and gives it back to the player
*/
@Override
public void run() {
player.sendMessage(reforgeItem() ? config.getSuccessMessage() : config.getFailMessage());
//Stop the re-forged item from displaying in the blacksmith's hand
if (npc.getEntity() instanceof Player) {
((Player) npc.getEntity()).getInventory().setItemInMainHand(null);
} else {
Objects.requireNonNull(((LivingEntity) npc.getEntity()).getEquipment()).setItemInMainHand(null);
}
//Give the item back to the player
if (!config.getDisableDelay()) {
//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);
} else {
player.getInventory().addItem(itemToReforge);
}
} else {
player.getInventory().setItemInMainHand(itemToReforge);
}
//Mark this blacksmith as available
blacksmithTrait.unsetSession();
// Start cool-down
Calendar wait = Calendar.getInstance();
wait.add(Calendar.SECOND, config.getReforgeCoolDown());
blacksmithTrait.addCoolDown(player.getUniqueId(), wait);
}
/**
* 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()) {
failReforge();
return false;
} else {
succeedReforge();
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
*
* @param item <p>The item to update damage for</p>
* @param newDamage <p>The new damage done</p>
*/
private void updateDamage(ItemStack item, int newDamage) {
ItemMeta meta = item.getItemMeta();
Damageable damageable = (Damageable) meta;
if (damageable != null) {
damageable.setDamage(newDamage);
} else {
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to change damage of " + item);
}
item.setItemMeta(meta);
}
/**
* Gets whether to end the current session
*
* <p>If the player has switched their item, or the player cannot pay, this returns true.</p>
*
* @return <p>True if the current session should end</p>
*/
public boolean endSession() {
// Prevent player from switching items during session
ItemStack itemInHand = player.getInventory().getItemInMainHand();
if (!itemToReforge.equals(itemInHand)) {
player.sendMessage(config.getItemChangedMessage());
return true;
}
// The player is unable to pay
if (!EconomyManager.canPay(player)) {
player.sendMessage(config.getInsufficientFundsMessage());
return true;
}
return false;
}
/**
* Gets whether the current session is still running
*
* @return <p>True if the current session is still running</p>
*/
public boolean isRunning() {
return BlacksmithPlugin.getInstance().getServer().getScheduler().isQueued(taskId);
}
/**
* Gets whether the given player is currently in a re-forge session
*
* @param other <p>The player to check if is in session</p>
* @return <p>True if the given player is in a re-forge session</p>
*/
public boolean isInSession(Player other) {
return player.getName().equals(other.getName());
}
/**
* Begins the actual item reforging
*/
public void beginReforge() {
if (!config.getDisableCoolDown()) {
//Finish the re-forge after a random delay between the max and min
taskId = BlacksmithPlugin.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(
BlacksmithPlugin.getInstance(), this, (new Random().nextInt(config.getMaxReforgeDelay()) +
config.getMinReforgeDelay()) * 20L);
} else {
//Finish the re-forge as soon as possible
taskId = BlacksmithPlugin.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(
BlacksmithPlugin.getInstance(), this, 0);
}
}
/**
* Gets the player currently in this reforge session
*
* @return <p>The player currently in this reforge session</p>
*/
public Player getPlayer() {
return player;
}
}