32 Commits

Author SHA1 Message Date
967791d275 Fixes merge problems
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2025-09-06 15:16:00 +02:00
bb46967892 Merge branch 'refs/heads/dev' into book-splitting
# Conflicts:
#	src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java
#	src/main/java/net/knarcraft/blacksmith/command/blacksmith/BlackSmithConfigCommand.java
#	src/main/java/net/knarcraft/blacksmith/formatting/BlacksmithStringFormatter.java
#	src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java
#	src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java
2025-09-06 15:09:34 +02:00
ca3d29d730 Updates KnarLib
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2025-09-06 14:58:55 +02:00
1d17239247 Adds some code for displaying an item's cost
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2025-08-04 16:55:38 +02:00
ad5066af4b Adds more unfinished code for parsing and tab-completing advanced cost
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2025-02-03 19:34:57 +01:00
42ca42c571 Adds increased scrapper cost when used excessively
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-12-21 19:48:39 +01:00
7e17122bb2 Fixes colored NPC names, and cleans up a bit
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-12-15 14:20:03 +01:00
b01ccfc537 Finishes the ActionCost implementation
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-11-27 23:06:33 +01:00
e3dbeccc14 Broken commit implementing a lot of book-splitting code
Some checks failed
EpicKnarvik97/Blacksmith/pipeline/head There was a failure building this commit
2024-11-27 12:16:57 +01:00
90d3c49c12 Catches a potential error when starting a new session
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-10-24 21:59:29 +02:00
f5bfbfd4f8 Attenpts to mitigate an NPC's entity becoming unavailable
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-09-27 01:37:07 +02:00
d4feda78ae Changes version back to snapshot
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-08-07 15:08:15 +02:00
81e65810e1 Bumps version for release
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-08-07 14:09:42 +02:00
0dab832bfd Adds a note about sticks being an NPC selector
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-08-07 13:24:06 +02:00
1510960089 Fixes missing cost message for extended salvage
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-08-03 17:05:28 +02:00
cdb0f267a3 Renames an event method
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-08-03 16:44:15 +02:00
81c15b6600 Adds information about the appropriate crafting station in ActionStartEvent
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-08-03 16:32:44 +02:00
5efba6687c Adds a separate message for clicking an NPC with an empty hand
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-08-03 13:09:09 +02:00
d0df4800f0 Changes back to SNAPSHOT
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-07-29 19:35:10 +02:00
d73fcfd778 Bumps version for release
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-07-29 19:23:14 +02:00
0993fbe15f Improves console logging
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-07-29 18:32:08 +02:00
f3372a13a5 Rewrites a lot of scrapper code to fix actual and potential bugs
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-07-29 17:53:07 +02:00
cf702c0e48 Cleans up salvage code a bit 2024-07-29 13:23:59 +02:00
afb608b609 Makes ActionStartEvent give duration in ticks instead of end time
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-07-29 01:40:08 +02:00
523e4cb47a Changes pitch for working sound
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-07-28 17:20:51 +02:00
384893b01a Randomizes the working sound somewhat 2024-07-28 17:15:25 +02:00
904adf46f0 Adds some events that trigger when NPC actions start or end, and when sounds are played 2024-07-28 17:12:26 +02:00
81dda85405 Adds sound effects to Blacksmiths and Scrappers 2024-07-28 12:48:45 +02:00
92c1b96fba Adds missing comments for BLACKSMITH filters
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-05-07 17:01:20 +02:00
62e58ba291 Adds missing scrapper notice
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-05-07 15:24:09 +02:00
21b860b5c8 Updates README header
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-05-07 14:57:24 +02:00
51d79c21c2 Changes version back to SNAPSHOT
All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
2024-05-07 14:50:34 +02:00
59 changed files with 2447 additions and 879 deletions

View File

@@ -1,7 +1,8 @@
# Blacksmith # Blacksmith
The blacksmith plugin adds a new blacksmith trait to Citizens NPCs. NPCs are able to repair a player's held item for a The blacksmith plugin adds a blacksmith trait and a scrapper to Citizens NPCs. Blacksmith NPCs are able to repair a
fee. Costs are highly customizable. player's held item for a fee. Costs are highly customizable. Scrapper NPCs can break down items into their crafting
recipe.
### Important changes from the original fork ### Important changes from the original fork
@@ -10,7 +11,7 @@ fee. Costs are highly customizable.
- By default, natural cost is used. The original fork made it cheaper the more damaged an item is, but natural cost - By default, natural cost is used. The original fork made it cheaper the more damaged an item is, but natural cost
makes the cost increase the more damaged the item is. makes the cost increase the more damaged the item is.
- EnchantmentTarget is used instead of a hard-coded list of repairable items - EnchantmentTarget is used instead of a hard-coded list of repairable items
- All settings, both global and for each blacksmith, can be changed using commands, - All settings, both global and for each blacksmith / scrapper, can be changed using commands,
and support tab-completion. and support tab-completion.
- This plugin is not directly compatible with the original. If you are using the old one, you will need to set it up - This plugin is not directly compatible with the original. If you are using the old one, you will need to set it up
again! again!
@@ -82,6 +83,11 @@ example planks into wood, four wood will be taken.
When an item is salvaged, EXP will be returned based on enchantments on the item. When an item is salvaged, EXP will be returned based on enchantments on the item.
Note that sticks are valid items to be handled by Scrappers if extended salvage is enabled. The item is also the default
item for selecting NPCs in Citizens. If you find the NPC selection message annoying, and don't normally use sticks for
selection, you can change the item in Citizens' config and replace it with STRUCTURE_VOID or some other unobtainable
item.
## Commands ## Commands
| Command | Arguments | Description | | Command | Arguments | Description |
@@ -135,10 +141,10 @@ repair all items, except diamond armor.
All currently supported presets, and available filters for each preset: All currently supported presets, and available filters for each preset:
- BLACKSMITH (WEAPON_SMITH + ARMOR_SMITH + TOOL_SMITH) - BLACKSMITH (WEAPON_SMITH + ARMOR_SMITH + TOOL_SMITH)
- GOLD - GOLD (all gold armor, tools and weapons)
- IRON - IRON (all iron armor, tools and weapons)
- DIAMOND - DIAMOND (all diamond armor, tools and weapons)
- NETHERITE - NETHERITE (all netherite armor, tools and weapons)
- WEAPON_SMITH: (RANGED + SWORD + SHIELD) - WEAPON_SMITH: (RANGED + SWORD + SHIELD)
- BOW (bows and crossbows) - BOW (bows and crossbows)
- SWORD (swords) - SWORD (swords)
@@ -229,6 +235,7 @@ All currently supported presets, and available filters for each preset:
| startReforgeMessage | &eOk, let's see what I can do... | The message displayed when a blacksmith starts reforging an item. | | startReforgeMessage | &eOk, let's see what I can do... | The message displayed when a blacksmith starts reforging an item. |
| successMessage | There you go! All better! | The message displayed when a blacksmith successfully repairs an item. | | successMessage | There you go! All better! | The message displayed when a blacksmith successfully repairs an item. |
| notDamagedMessage | &cThat item is not in need of repair | The message displayed if a player tries to reforge an item with full durability. | | notDamagedMessage | &cThat item is not in need of repair | The message displayed if a player tries to reforge an item with full durability. |
| noItemMessage | Please present the item you want to reforge | The message displayed when a blacksmith is clicked with an empty hand |
### Scrapper global-only options ### Scrapper global-only options
@@ -283,6 +290,7 @@ All currently supported presets, and available filters for each preset:
| cannotSalvageArmorTrimMessage | &cI'm sorry, but I'm unable to salvage armor trims! | The message displayed when telling a player that the scrapper cannot salvage items with armor trim. | | cannotSalvageArmorTrimMessage | &cI'm sorry, but I'm unable to salvage armor trims! | The message displayed when telling a player that the scrapper cannot salvage items with armor trim. |
| armorTrimSalvageNotFoundMessage | &cI'm sorry, but I don't know how to salvage that armor trim! | The message displayed when telling a player that the scrapper cannot find the correct items to return as armor trim salvage. This will happen if more armor trim materials are added, or the Spigot API is changed. | | armorTrimSalvageNotFoundMessage | &cI'm sorry, but I don't know how to salvage that armor trim! | The message displayed when telling a player that the scrapper cannot find the correct items to return as armor trim salvage. This will happen if more armor trim materials are added, or the Spigot API is changed. |
| cannotSalvageNetheriteMessage | &cI'm sorry, but I'm unable to salvage netherite items! | The message displayed when telling a player that the scrapper cannot salvage netherite items. | | cannotSalvageNetheriteMessage | &cI'm sorry, but I'm unable to salvage netherite items! | The message displayed when telling a player that the scrapper cannot salvage netherite items. |
| noItemMessage | Please present the item you want to salvage | The message displayed when a scrapper is clicked with an empty hand |
## Language customization ## Language customization

21
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>net.knarcraft</groupId> <groupId>net.knarcraft</groupId>
<artifactId>blacksmith</artifactId> <artifactId>blacksmith</artifactId>
<version>1.1.0</version> <version>1.1.3-SNAPSHOT</version>
<name>Blacksmith</name> <name>Blacksmith</name>
<description>Blacksmith NPC for the Citizens API</description> <description>Blacksmith NPC for the Citizens API</description>
@@ -65,7 +65,7 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.20.6-R0.1-SNAPSHOT</version> <version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -83,7 +83,7 @@
<dependency> <dependency>
<groupId>net.knarcraft</groupId> <groupId>net.knarcraft</groupId>
<artifactId>knarlib</artifactId> <artifactId>knarlib</artifactId>
<version>1.2.7</version> <version>1.2.14</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -92,6 +92,11 @@
<version>5.10.2</version> <version>5.10.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>net.objecthunter</groupId>
<artifactId>exp4j</artifactId>
<version>0.4.8</version>
</dependency>
</dependencies> </dependencies>
<!-- Build information --> <!-- Build information -->
@@ -127,6 +132,10 @@
<pattern>org.jetbrains.annotations</pattern> <pattern>org.jetbrains.annotations</pattern>
<shadedPattern>net.knarcraft.blacksmith.lib.annotations</shadedPattern> <shadedPattern>net.knarcraft.blacksmith.lib.annotations</shadedPattern>
</relocation> </relocation>
<relocation>
<pattern>net.objecthunter.exp4j</pattern>
<shadedPattern>net.knarcraft.blacksmith.lib.exp4j</shadedPattern>
</relocation>
</relocations> </relocations>
<filters> <filters>
<filter> <filter>
@@ -141,6 +150,12 @@
<include>org/jetbrains/annotations/**</include> <include>org/jetbrains/annotations/**</include>
</includes> </includes>
</filter> </filter>
<filter>
<artifact>net.objecthunter:exp4j</artifact>
<includes>
<include>net/objecthunter/exp4j/**</include>
</includes>
</filter>
</filters> </filters>
</configuration> </configuration>
</execution> </execution>

View File

@@ -13,48 +13,42 @@ import net.knarcraft.blacksmith.command.scrapper.ScrapperConfigCommand;
import net.knarcraft.blacksmith.command.scrapper.ScrapperConfigTabCompleter; import net.knarcraft.blacksmith.command.scrapper.ScrapperConfigTabCompleter;
import net.knarcraft.blacksmith.command.scrapper.ScrapperEditCommand; import net.knarcraft.blacksmith.command.scrapper.ScrapperEditCommand;
import net.knarcraft.blacksmith.command.scrapper.ScrapperEditTabCompleter; import net.knarcraft.blacksmith.command.scrapper.ScrapperEditTabCompleter;
import net.knarcraft.blacksmith.config.StargateYamlConfiguration;
import net.knarcraft.blacksmith.config.blacksmith.GlobalBlacksmithSettings; import net.knarcraft.blacksmith.config.blacksmith.GlobalBlacksmithSettings;
import net.knarcraft.blacksmith.config.scrapper.GlobalScrapperSettings; import net.knarcraft.blacksmith.config.scrapper.GlobalScrapperSettings;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.blacksmith.listener.NPCClickListener; import net.knarcraft.blacksmith.listener.NPCClickListener;
import net.knarcraft.blacksmith.listener.PlayerListener; import net.knarcraft.blacksmith.listener.PlayerListener;
import net.knarcraft.blacksmith.manager.EconomyManager; import net.knarcraft.blacksmith.manager.EconomyManager;
import net.knarcraft.blacksmith.manager.PlayerUsageManager;
import net.knarcraft.blacksmith.trait.BlacksmithTrait; import net.knarcraft.blacksmith.trait.BlacksmithTrait;
import net.knarcraft.blacksmith.trait.ScrapperTrait; import net.knarcraft.blacksmith.trait.ScrapperTrait;
import net.knarcraft.blacksmith.util.ConfigHelper; import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.TranslatableTimeUnit; import net.knarcraft.knarlib.formatting.TranslatableTimeUnit;
import net.knarcraft.knarlib.formatting.Translator; import net.knarcraft.knarlib.formatting.Translator;
import net.knarcraft.knarlib.plugin.ConfigCommentPlugin;
import net.knarcraft.knarlib.util.ConfigHelper;
import net.knarcraft.knarlib.util.UpdateChecker; import net.knarcraft.knarlib.util.UpdateChecker;
import org.bukkit.command.CommandExecutor; import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.Event;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader; import org.bukkit.plugin.java.JavaPluginLoader;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
* Blacksmith's main class * Blacksmith's main class
*/ */
public class BlacksmithPlugin extends JavaPlugin { public class BlacksmithPlugin extends ConfigCommentPlugin {
private static final String CONFIG_FILE_NAME = "config.yml";
private static BlacksmithPlugin instance; private static BlacksmithPlugin instance;
private GlobalBlacksmithSettings blacksmithConfig; private GlobalBlacksmithSettings blacksmithConfig;
private GlobalScrapperSettings scrapperConfig; private GlobalScrapperSettings scrapperConfig;
private static Translator translator; private static Translator translator;
private static StringFormatter stringFormatter;
private FileConfiguration configuration;
/** /**
* Constructor required for MockBukkit * Constructor required for MockBukkit
@@ -107,29 +101,10 @@ public class BlacksmithPlugin extends JavaPlugin {
this.reloadConfig(); this.reloadConfig();
blacksmithConfig.load(); blacksmithConfig.load();
scrapperConfig.load(); scrapperConfig.load();
translator.loadLanguages(this.getDataFolder(), "en", this.getConfiguration().getString( translator.loadLanguages(this.getDataFolder(), "en", this.getConfig().getString(
"language", "en")); "language", "en"));
} }
/**
* Gets the raw configuration
*
* @return <p>The raw configuration</p>
*/
@NotNull
public FileConfiguration getConfiguration() {
return this.configuration;
}
/**
* Gets the string formatter to use for formatting
*
* @return <p>The string formatter to use</p>
*/
public static @NotNull StringFormatter getStringFormatter() {
return BlacksmithPlugin.stringFormatter;
}
/** /**
* Gets the translator to use for translation * Gets the translator to use for translation
* *
@@ -141,26 +116,19 @@ public class BlacksmithPlugin extends JavaPlugin {
@Override @Override
public void onDisable() { public void onDisable() {
getLogger().log(Level.INFO, " v" + getDescription().getVersion() + " disabled."); log(" v" + getDescription().getVersion() + " disabled.");
} }
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
ConfigHelper.saveDefaults(this);
//Copy default config to disk, and add missing configuration values
this.saveDefaultConfig();
this.getConfig().options().copyDefaults(true);
this.reloadConfig();
this.saveConfig();
//Migrate from an earlier configuration file syntax if necessary //Migrate from an earlier configuration file syntax if necessary
if (getConfiguration().getString("scrapper.defaults.dropItem") == null) { if (getConfig().getString("scrapper.defaults.dropItem") == null) {
ConfigHelper.migrateConfig(this.getDataFolder().getPath().replaceAll("\\\\", "/"), net.knarcraft.knarlib.util.ConfigHelper.migrateConfig(this);
getConfiguration());
this.reloadConfig();
} }
initializeConfigurations(getConfiguration()); initializeConfigurations(getConfig());
//Set up Vault integration //Set up Vault integration
if (!setUpVault()) { if (!setUpVault()) {
@@ -177,33 +145,60 @@ public class BlacksmithPlugin extends JavaPlugin {
//Register all listeners //Register all listeners
registerListeners(); registerListeners();
getLogger().log(Level.INFO, " v" + getDescription().getVersion() + " enabled."); log(" v" + getDescription().getVersion() + " enabled.");
//Alert about an update in the console //Alert about an update in the console
UpdateChecker.checkForUpdate(this, "https://api.spigotmc.org/legacy/update.php?resource=105938", UpdateChecker.checkForUpdate(this, "https://api.spigotmc.org/legacy/update.php?resource=105938",
() -> this.getDescription().getVersion(), null); () -> this.getDescription().getVersion(), null);
// Remove expired scrapper usage data
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> PlayerUsageManager.removeExpiredData(
System.currentTimeMillis() - 3600000), 36000, 36000);
} }
@Override /**
public void reloadConfig() { * Calls an event
super.reloadConfig(); *
this.configuration = new StargateYamlConfiguration(); * @param event <p>The event to call</p>
this.configuration.options().copyDefaults(true); */
try { public void callEvent(@NotNull Event event) {
this.configuration.load(new File(getDataFolder(), CONFIG_FILE_NAME)); this.getServer().getPluginManager().callEvent(event);
} catch (IOException | InvalidConfigurationException exception) {
getLogger().log(Level.SEVERE, "Unable to load the configuration! Message: " + exception.getMessage());
}
} }
@Override /**
public void saveConfig() { * Prints an info message to the console
super.saveConfig(); *
try { * @param message <p>The message to print</p>
this.configuration.save(new File(getDataFolder(), CONFIG_FILE_NAME)); */
} catch (IOException exception) { public static void log(@NotNull String message) {
getLogger().log(Level.SEVERE, "Unable to save the configuration! Message: " + exception.getMessage()); BlacksmithPlugin.getInstance().getLogger().log(Level.INFO, message);
} }
/**
* Prints a warning message to the console
*
* @param message <p>The message to print</p>
*/
public static void warn(@NotNull String message) {
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, message);
}
/**
* Prints an error message to the console
*
* @param message <p>The message to print</p>
*/
public static void error(@NotNull String message) {
BlacksmithPlugin.getInstance().getLogger().log(Level.SEVERE, message);
}
/**
* Prints a debug message to the console
*
* @param message <p>The message to print</p>
*/
public static void debug(@NotNull String message) {
BlacksmithPlugin.getInstance().getLogger().log(Level.FINE, message);
} }
/** /**
@@ -221,7 +216,7 @@ public class BlacksmithPlugin extends JavaPlugin {
//Prepare the translator //Prepare the translator
translator = new Translator(); translator = new Translator();
translator.registerMessageCategory(TranslatableTimeUnit.UNIT_SECOND); translator.registerMessageCategory(TranslatableTimeUnit.UNIT_SECOND);
translator.registerMessageCategory(BlacksmithTranslatableMessage.ITEM_TYPE_ENCHANTMENT); translator.registerMessageCategory(Translatable.ITEM_TYPE_ENCHANTMENT);
translator.loadLanguages(this.getDataFolder(), "en", translator.loadLanguages(this.getDataFolder(), "en",
fileConfiguration.getString("language", "en")); fileConfiguration.getString("language", "en"));
PluginDescriptionFile description = this.getDescription(); PluginDescriptionFile description = this.getDescription();
@@ -231,7 +226,7 @@ public class BlacksmithPlugin extends JavaPlugin {
} else { } else {
prefix = description.getPrefix(); prefix = description.getPrefix();
} }
BlacksmithPlugin.stringFormatter = new StringFormatter(prefix, translator); FormatBuilder.setStringFormatter(new StringFormatter(prefix, translator));
// This reload is necessary to get values just added to the configuration for some reason // This reload is necessary to get values just added to the configuration for some reason
this.reload(); this.reload();
@@ -243,10 +238,10 @@ public class BlacksmithPlugin extends JavaPlugin {
* @return <p>True if Vault setup/integration succeeded</p> * @return <p>True if Vault setup/integration succeeded</p>
*/ */
private boolean setUpVault() { private boolean setUpVault() {
getLogger().log(Level.INFO, "Setting Up Vault now...."); log("Setting Up Vault now....");
boolean canLoad = EconomyManager.setUp(getServer().getServicesManager(), getLogger()); boolean canLoad = EconomyManager.setUp(getServer().getServicesManager());
if (!canLoad) { if (!canLoad) {
getLogger().log(Level.SEVERE, "Vault Integration Failed...."); error("Vault Integration Failed....");
getServer().getPluginManager().disablePlugin(this); getServer().getPluginManager().disablePlugin(this);
return false; return false;
} }
@@ -273,22 +268,4 @@ public class BlacksmithPlugin extends JavaPlugin {
registerCommand("preset", new PresetCommand(), new PresetTabCompleter()); registerCommand("preset", new PresetCommand(), new PresetTabCompleter());
} }
/**
* Registers a command
*
* @param commandName <p>The name of the command</p>
* @param executor <p>The executor to bind to the command</p>
* @param tabCompleter <p>The tab completer to bind to the command, or null</p>
*/
private void registerCommand(@NotNull String commandName, @NotNull CommandExecutor executor,
@Nullable TabCompleter tabCompleter) {
PluginCommand command = this.getCommand(commandName);
if (command != null) {
command.setExecutor(executor);
if (tabCompleter != null) {
command.setTabCompleter(tabCompleter);
}
}
}
} }

View File

@@ -6,12 +6,12 @@ import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.Setting; import net.knarcraft.blacksmith.config.Setting;
import net.knarcraft.blacksmith.config.SettingValueType; import net.knarcraft.blacksmith.config.SettingValueType;
import net.knarcraft.blacksmith.config.Settings; import net.knarcraft.blacksmith.config.Settings;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.blacksmith.trait.CustomTrait; import net.knarcraft.blacksmith.trait.CustomTrait;
import net.knarcraft.blacksmith.util.ConfigCommandHelper; import net.knarcraft.blacksmith.util.ConfigCommandHelper;
import net.knarcraft.blacksmith.util.InputParsingHelper; import net.knarcraft.blacksmith.util.InputParsingHelper;
import net.knarcraft.blacksmith.util.TypeValidationHelper; import net.knarcraft.blacksmith.util.TypeValidationHelper;
import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.md_5.bungee.api.ChatColor; 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;
@@ -20,11 +20,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.logging.Level;
import static net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage.getCurrentValueMessage; import static net.knarcraft.blacksmith.formatting.Translatable.getCurrentValueMessage;
import static net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage.getDefaultValueMessage; import static net.knarcraft.blacksmith.formatting.Translatable.getDefaultValueMessage;
import static net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage.getValueChangedMessage; import static net.knarcraft.blacksmith.formatting.Translatable.getValueChangedMessage;
/** /**
* A generic implementation of the edit command * A generic implementation of the edit command
@@ -65,8 +64,7 @@ public abstract class EditCommand<K extends CustomTrait<L>, L extends Setting> i
@NotNull String[] args) { @NotNull String[] args) {
NPC npc = CitizensAPI.getDefaultNPCSelector().getSelected(sender); NPC npc = CitizensAPI.getDefaultNPCSelector().getSelected(sender);
if (npc == null || !npc.hasTrait(traitClass)) { if (npc == null || !npc.hasTrait(traitClass)) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.NO_NPC_SELECTED).error(sender);
BlacksmithTranslatableMessage.NO_NPC_SELECTED);
return true; return true;
} }
@@ -127,13 +125,11 @@ public abstract class EditCommand<K extends CustomTrait<L>, L extends Setting> i
//Change the setting //Change the setting
Settings<L> settings = trait.getTraitSettings(); Settings<L> settings = trait.getTraitSettings();
if (settings == null) { if (settings == null) {
BlacksmithPlugin.getInstance().getLogger().log(Level.SEVERE, "Settings for a CustomTrait has not " + BlacksmithPlugin.error("Settings for a CustomTrait has not been initialized! Please inform the developer!");
"been initialized! Please inform the developer!");
return false; return false;
} }
settings.changeValue(setting, newValue); settings.changeValue(setting, newValue);
BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender, getValueChangedMessage(setting.getCommandName(), String.valueOf(newValue)).success(sender);
getValueChangedMessage(setting.getCommandName(), String.valueOf(newValue)));
//Save the changes immediately to prevent data loss on server crash //Save the changes immediately to prevent data loss on server crash
CitizensAPI.getNPCRegistry().saveToStore(); CitizensAPI.getNPCRegistry().saveToStore();
} }
@@ -154,26 +150,23 @@ public abstract class EditCommand<K extends CustomTrait<L>, L extends Setting> i
@NotNull CommandSender sender) { @NotNull CommandSender sender) {
Settings<L> settings = trait.getTraitSettings(); Settings<L> settings = trait.getTraitSettings();
if (settings == null) { if (settings == null) {
BlacksmithPlugin.getInstance().getLogger().log(Level.SEVERE, "Settings for a CustomTrait has not " + BlacksmithPlugin.error("Settings for a CustomTrait has not been initialized! Please inform the developer!");
"been initialized! Please inform the developer!");
return; return;
} }
StringFormatter formatter = BlacksmithPlugin.getStringFormatter();
//Find the value of the specified setting //Find the value of the specified setting
String commandName = setting.getCommandName(); String commandName = setting.getCommandName();
String currentValue = String.valueOf(settings.getRawValue(setting)); String currentValue = String.valueOf(settings.getRawValue(setting));
String defaultValue = String.valueOf(setting.getDefaultValue()); String defaultValue = String.valueOf(setting.getDefaultValue());
String marker = formatter.getUnFormattedMessage(BlacksmithTranslatableMessage.SETTING_OVERRIDDEN_MARKER); String marker = new FormatBuilder(Translatable.SETTING_OVERRIDDEN_MARKER).toString();
boolean isMessage = setting.isMessage(); boolean isMessage = setting.isMessage();
boolean isSet = !InputParsingHelper.isEmpty(currentValue); boolean isSet = !InputParsingHelper.isEmpty(currentValue);
// Display the description for how this setting is used // Display the description for how this setting is used
formatter.displaySuccessMessage(sender, setting.getDescription()); new FormatBuilder(setting.getDescription()).success(sender);
// Display the default value // Display the default value
formatter.displayNeutralMessage(sender, getDefaultValueMessage(commandName, defaultValue)); getDefaultValueMessage(commandName, defaultValue).neutral(sender);
if (isMessage) { if (isMessage) {
ConfigCommandHelper.displayRaw(sender, defaultValue); ConfigCommandHelper.displayRaw(sender, defaultValue);
} }
@@ -184,7 +177,7 @@ public abstract class EditCommand<K extends CustomTrait<L>, L extends Setting> i
} }
// Display the value with a marker if it's customized // Display the value with a marker if it's customized
formatter.displayNeutralMessage(sender, getCurrentValueMessage(commandName, currentValue) + (isSet ? marker : "")); new FormatBuilder(getCurrentValueMessage(commandName, currentValue) + (isSet ? marker : "")).neutral(sender);
if (isMessage) { if (isMessage) {
ConfigCommandHelper.displayRaw(sender, currentValue); ConfigCommandHelper.displayRaw(sender, currentValue);

View File

@@ -1,9 +1,9 @@
package net.knarcraft.blacksmith.command; package net.knarcraft.blacksmith.command;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.SmithPreset; import net.knarcraft.blacksmith.config.SmithPreset;
import net.knarcraft.blacksmith.config.SmithPresetFilter; import net.knarcraft.blacksmith.config.SmithPresetFilter;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
@@ -37,8 +37,7 @@ public class PresetCommand implements CommandExecutor {
SmithPresetFilter filter = SmithPresetFilter.valueOf(parts[1]); SmithPresetFilter filter = SmithPresetFilter.valueOf(parts[1]);
if (!smithPreset.supportsFilter(filter)) { if (!smithPreset.supportsFilter(filter)) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.INVALID_FILTER_FOR_PRESET).error(sender);
BlacksmithTranslatableMessage.INVALID_FILTER_FOR_PRESET);
return false; return false;
} }
includedMaterials = smithPreset.getFilteredMaterials(filter); includedMaterials = smithPreset.getFilteredMaterials(filter);
@@ -46,8 +45,7 @@ public class PresetCommand implements CommandExecutor {
includedMaterials = SmithPreset.valueOf(presetName).getMaterials(); includedMaterials = SmithPreset.valueOf(presetName).getMaterials();
} }
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.INVALID_PRESET_OR_FILTER).error(sender);
BlacksmithTranslatableMessage.INVALID_PRESET_OR_FILTER);
return false; return false;
} }
@@ -56,9 +54,8 @@ public class PresetCommand implements CommandExecutor {
for (Material material : includedMaterials) { for (Material material : includedMaterials) {
materialNames.add(material.name()); materialNames.add(material.name());
} }
BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender, new FormatBuilder(Translatable.PRESET_MATERIALS).replace("{materials}",
BlacksmithPlugin.getStringFormatter().replacePlaceholder(BlacksmithTranslatableMessage.PRESET_MATERIALS, String.join(", ", materialNames)).success(sender);
"{materials}", String.join(", ", materialNames)));
return true; return true;
} }

View File

@@ -1,7 +1,8 @@
package net.knarcraft.blacksmith.command; package net.knarcraft.blacksmith.command;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -20,7 +21,7 @@ public class ReloadCommand implements TabExecutor {
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) {
BlacksmithPlugin.getInstance().reload(); BlacksmithPlugin.getInstance().reload();
BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender, BlacksmithTranslatableMessage.PLUGIN_RELOADED); new FormatBuilder(Translatable.PLUGIN_RELOADED).success(sender);
return true; return true;
} }

View File

@@ -5,13 +5,14 @@ import net.knarcraft.blacksmith.command.ReloadCommand;
import net.knarcraft.blacksmith.config.SettingValueType; import net.knarcraft.blacksmith.config.SettingValueType;
import net.knarcraft.blacksmith.config.blacksmith.BlacksmithSetting; import net.knarcraft.blacksmith.config.blacksmith.BlacksmithSetting;
import net.knarcraft.blacksmith.config.blacksmith.GlobalBlacksmithSettings; import net.knarcraft.blacksmith.config.blacksmith.GlobalBlacksmithSettings;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage;
import net.knarcraft.blacksmith.formatting.ItemType; import net.knarcraft.blacksmith.formatting.ItemType;
import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.blacksmith.util.ConfigCommandHelper; import net.knarcraft.blacksmith.util.ConfigCommandHelper;
import net.knarcraft.blacksmith.util.InputParsingHelper; import net.knarcraft.blacksmith.util.InputParsingHelper;
import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.ItemHelper;
import net.knarcraft.blacksmith.util.TypeValidationHelper; import net.knarcraft.blacksmith.util.TypeValidationHelper;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey;
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;
@@ -92,19 +93,21 @@ public class BlackSmithConfigCommand implements CommandExecutor {
} else { } else {
currentValue = String.valueOf(settings.getPricePerDurabilityPoint(material)); currentValue = String.valueOf(settings.getPricePerDurabilityPoint(material));
} }
BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender, Translatable.getItemCurrentValueMessage(setting.getCommandName(),
BlacksmithTranslatableMessage.getItemCurrentValueMessage(setting.getCommandName(), ItemType.MATERIAL, material.name(), currentValue).success(sender);
ItemType.MATERIAL, material.name(), currentValue));
return true; return true;
} else if (setting == BlacksmithSetting.ENCHANTMENT_COST) { } else if (setting == BlacksmithSetting.ENCHANTMENT_COST) {
Enchantment enchantment = InputParsingHelper.matchEnchantment(selector); Enchantment enchantment = InputParsingHelper.matchEnchantment(selector);
if (enchantment == null) { if (enchantment == null) {
return false; return false;
} }
BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender, NamespacedKey enchantmentKey = enchantment.getKeyOrNull();
BlacksmithTranslatableMessage.getItemCurrentValueMessage(setting.getCommandName(), if (enchantmentKey == null) {
ItemType.ENCHANTMENT, enchantment.getKey().getKey(), return false;
String.valueOf(settings.getEnchantmentCost(enchantment)))); }
Translatable.getItemCurrentValueMessage(setting.getCommandName(),
ItemType.ENCHANTMENT, enchantmentKey.getKey(),
String.valueOf(settings.getEnchantmentCost(enchantment))).success(sender);
return true; return true;
} else { } else {
return false; return false;
@@ -154,11 +157,11 @@ public class BlackSmithConfigCommand implements CommandExecutor {
return false; return false;
} }
ItemType itemType = ItemType.ENCHANTMENT; ItemType itemType = ItemType.ENCHANTMENT;
// Note: While depreciated in Spigot, the new method is not available for Paper
@SuppressWarnings("deprecation")
String itemChanged = enchantment.getKey().getKey(); String itemChanged = enchantment.getKey().getKey();
settings.setEnchantmentCost(enchantment, newPrice); settings.setEnchantmentCost(enchantment, newPrice);
BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender, Translatable.getItemValueChangedMessage(blacksmithSetting.getCommandName(), itemType, itemChanged, newValue).success(sender);
BlacksmithTranslatableMessage.getItemValueChangedMessage(blacksmithSetting.getCommandName(),
itemType, itemChanged, newValue));
return true; return true;
} else { } else {
return false; return false;
@@ -198,9 +201,8 @@ public class BlackSmithConfigCommand implements CommandExecutor {
} }
} }
BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender, Translatable.getItemValueChangedMessage(blacksmithSetting.getCommandName(),
BlacksmithTranslatableMessage.getItemValueChangedMessage(blacksmithSetting.getCommandName(), itemType, itemChanged, String.valueOf(newPrice)).success(sender);
itemType, itemChanged, String.valueOf(newPrice)));
return true; return true;
} }

View File

@@ -55,4 +55,9 @@ public enum SettingValueType {
*/ */
ENCHANTMENT_LIST, ENCHANTMENT_LIST,
/**
* Advanced cost, that supports either a simple double, or specifying money cost, permission requirement, exp cost and an item cost
*/
ADVANCED_COST,
} }

View File

@@ -24,6 +24,7 @@ public interface Settings<K extends Setting> {
* @param setting <p>The setting to get</p> * @param setting <p>The setting to get</p>
* @return <p>The current raw setting value</p> * @return <p>The current raw setting value</p>
*/ */
@NotNull Object getRawValue(@NotNull K setting); @NotNull
Object getRawValue(@NotNull K setting);
} }

View File

@@ -12,7 +12,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
/** /**
* A representation of the presets for different kinds of smiths * A representation of the presets for different kinds of smiths
@@ -133,8 +132,8 @@ public enum SmithPreset {
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
/* This case means that either the preset or the filter given is invalid, and thus the preset string should /* This case means that either the preset or the filter given is invalid, and thus the preset string should
be ignored to prevent any problems. */ be ignored to prevent any problems. */
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, String.format("The smith preset %s is " + BlacksmithPlugin.warn(String.format("The smith preset %s is invalid, and will be ignored. Please fix it!",
"invalid, and will be ignored. Please fix it!", possiblePreset)); possiblePreset));
return ""; return "";
} }

View File

@@ -1,246 +0,0 @@
package net.knarcraft.blacksmith.config;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A YAML configuration which retains all comments
*
* <p>This configuration converts all comments to YAML values when loaded, which all start with comment_. When saved,
* those YAML values are converted to normal text comments. This ensures that the comments aren't removed by the
* YamlConfiguration during its parsing.</p>
*
* <p>Note: When retrieving a configuration section, that will include comments that start with "comment_". You should
* filter those before parsing input.</p>
*
* @author Kristian Knarvik
* @author Thorin
*/
public class StargateYamlConfiguration extends YamlConfiguration {
private static final String START_OF_COMMENT_LINE = "[HASHTAG]";
private static final String END_OF_COMMENT = "_endOfComment_";
private static final String START_OF_COMMENT = "comment_";
@Override
@NotNull
@SuppressWarnings("deprecation")
protected String buildHeader() {
return "";
}
@Override
@NotNull
public String saveToString() {
// Convert YAML comments to normal comments
return this.convertYAMLMappingsToComments(super.saveToString());
}
@Override
public void loadFromString(@NotNull String contents) throws InvalidConfigurationException {
// Convert normal comments to YAML comments to prevent them from disappearing
super.loadFromString(this.convertCommentsToYAMLMappings(contents));
}
/**
* Gets a configuration section's keys, without any comment entries
*
* @param configurationSection <p>The configuration section to get keys for</p>
* @param deep <p>Whether to get keys for child elements as well</p>
* @return <p>The configuration section's keys, with comment entries removed</p>
*/
public static Set<String> getKeysWithoutComments(@NotNull ConfigurationSection configurationSection, boolean deep) {
Set<String> keys = new HashSet<>(configurationSection.getKeys(deep));
keys.removeIf(key -> key.matches(START_OF_COMMENT + "[0-9]+"));
return keys;
}
/**
* Reads a file with comments, and recreates them into yaml mappings
*
* <p>A mapping follows this format: comment_{CommentNumber}: "The comment"
* This needs to be done as comments otherwise get removed using
* the {@link FileConfiguration#save(File)} method. The config
* needs to be saved if a config value has changed.</p>
*/
@NotNull
private String convertCommentsToYAMLMappings(@NotNull String configString) {
StringBuilder yamlBuilder = new StringBuilder();
List<String> currentComment = new ArrayList<>();
int commentId = 0;
int previousIndentation = 0;
for (String line : configString.split("\n")) {
String trimmed = line.trim();
if (trimmed.startsWith("#")) {
// Store the indentation of the block
if (currentComment.isEmpty()) {
previousIndentation = getIndentation(line);
}
//Temporarily store the comment line
addComment(currentComment, trimmed);
} else {
addYamlString(yamlBuilder, currentComment, line, previousIndentation, commentId);
commentId++;
previousIndentation = 0;
}
}
return yamlBuilder.toString();
}
/**
* Adds a YAML string to the given string builder
*
* @param yamlBuilder <p>The string builder used for building YAML</p>
* @param currentComment <p>The comment to add as a YAML string</p>
* @param line <p>The current line</p>
* @param previousIndentation <p>The indentation of the current block comment</p>
* @param commentId <p>The id of the comment</p>
*/
private void addYamlString(@NotNull StringBuilder yamlBuilder, @NotNull List<String> currentComment,
@NotNull String line, int previousIndentation, int commentId) {
String trimmed = line.trim();
//Write the full formatted comment to the StringBuilder
if (!currentComment.isEmpty()) {
int indentation = trimmed.isEmpty() ? previousIndentation : getIndentation(line);
generateCommentYAML(yamlBuilder, currentComment, commentId, indentation);
currentComment.clear();
}
//Add the non-comment line assuming it isn't empty
if (!trimmed.isEmpty()) {
yamlBuilder.append(line).append("\n");
}
}
/**
* Adds the given comment to the given list
*
* @param commentParts <p>The list to add to</p>
* @param comment <p>The comment to add</p>
*/
private void addComment(@NotNull List<String> commentParts, @NotNull String comment) {
if (comment.startsWith("# ")) {
commentParts.add(comment.replaceFirst("# ", START_OF_COMMENT_LINE));
} else {
commentParts.add(comment.replaceFirst("#", START_OF_COMMENT_LINE));
}
}
/**
* Generates a YAML-compatible string for one comment block
*
* @param yamlBuilder <p>The string builder to add the generated YAML to</p>
* @param commentLines <p>The lines of the comment to convert into YAML</p>
* @param commentId <p>The unique id of the comment</p>
* @param indentation <p>The indentation to add to every line</p>
*/
private void generateCommentYAML(@NotNull StringBuilder yamlBuilder, @NotNull List<String> commentLines,
int commentId, int indentation) {
String subIndentation = this.addIndentation(indentation + 2);
//Add the comment start marker
yamlBuilder.append(this.addIndentation(indentation)).append(START_OF_COMMENT).append(commentId).append(": |\n");
for (String commentLine : commentLines) {
//Add each comment line with the proper indentation
yamlBuilder.append(subIndentation).append(commentLine).append("\n");
}
//Add the comment end marker
yamlBuilder.append(subIndentation).append(subIndentation).append(END_OF_COMMENT).append("\n");
}
/**
* Converts the internal YAML mapping format to a readable config file
*
* <p>The internal YAML structure is converted to a string with the same format as a standard configuration file.
* The internal structure has comments in the format: START_OF_COMMENT + id + multi-line YAML string +
* END_OF_COMMENT.</p>
*
* @param yamlString <p>A string using the YAML format</p>
* @return <p>The corresponding comment string</p>
*/
@NotNull
private String convertYAMLMappingsToComments(@NotNull String yamlString) {
StringBuilder finalText = new StringBuilder();
String[] lines = yamlString.split("\n");
for (int currentIndex = 0; currentIndex < lines.length; currentIndex++) {
String line = lines[currentIndex];
String possibleComment = line.trim();
if (possibleComment.startsWith(START_OF_COMMENT)) {
//Add an empty line before every comment block
finalText.append("\n");
currentIndex = readComment(finalText, lines, currentIndex + 1, getIndentation(line));
} else {
//Output the configuration key
finalText.append(line).append("\n");
}
}
return finalText.toString().trim();
}
/**
* Fully reads a comment
*
* @param builder <p>The string builder to write to</p>
* @param lines <p>The lines to read from</p>
* @param startIndex <p>The index to start reading from</p>
* @param commentIndentation <p>The indentation of the read comment</p>
* @return <p>The index containing the next non-comment line</p>
*/
private int readComment(@NotNull StringBuilder builder, @NotNull String[] lines, int startIndex,
int commentIndentation) {
for (int currentIndex = startIndex; currentIndex < lines.length; currentIndex++) {
String line = lines[currentIndex];
String possibleComment = line.trim();
if (!line.contains(END_OF_COMMENT)) {
possibleComment = possibleComment.replace(START_OF_COMMENT_LINE, "");
builder.append(addIndentation(commentIndentation)).append("# ").append(possibleComment).append("\n");
} else {
return currentIndex;
}
}
return startIndex;
}
/**
* Gets a string containing the given indentation
*
* @param indentationSpaces <p>The number spaces to use for indentation</p>
* @return <p>A string containing the number of spaces specified</p>
*/
@NotNull
private String addIndentation(int indentationSpaces) {
return " ".repeat(Math.max(0, indentationSpaces));
}
/**
* Gets the indentation (number of spaces) of the given line
*
* @param line <p>The line to get indentation of</p>
* @return <p>The number of spaces in the line's indentation</p>
*/
private int getIndentation(@NotNull String line) {
int spacesFound = 0;
for (char aCharacter : line.toCharArray()) {
if (aCharacter == ' ') {
spacesFound++;
} else {
break;
}
}
return spacesFound;
}
}

View File

@@ -16,7 +16,6 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
/** /**
* A class which keeps track of all Blacksmith settings/config values for one NPC * A class which keeps track of all Blacksmith settings/config values for one NPC
@@ -176,6 +175,16 @@ public class BlacksmithNPCSettings implements TraitSettings<BlacksmithSetting> {
return asString(BlacksmithSetting.ITEM_UNEXPECTEDLY_CHANGED_MESSAGE); return asString(BlacksmithSetting.ITEM_UNEXPECTEDLY_CHANGED_MESSAGE);
} }
/**
* Gets the message displayed if a player presents the blacksmith with an empty hand
*
* @return <p>The no item message</p>
*/
@NotNull
public String getNoItemMessage() {
return asString(BlacksmithSetting.NO_ITEM_MESSAGE);
}
/** /**
* Gets all items reforge-able by this NPC * Gets all items reforge-able by this NPC
* *
@@ -384,8 +393,7 @@ public class BlacksmithNPCSettings implements TraitSettings<BlacksmithSetting> {
if (enchantment != null) { if (enchantment != null) {
enchantmentBlockList.add(enchantment); enchantmentBlockList.add(enchantment);
} else { } else {
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to verify " + item + BlacksmithPlugin.warn("Unable to verify " + item + " as a valid enchantment");
" as a valid enchantment");
} }
} }
return enchantmentBlockList; return enchantmentBlockList;

View File

@@ -186,6 +186,12 @@ public enum BlacksmithSetting implements Setting {
"The message to display if a player is trying to reforge an item with full durability", "The message to display if a player is trying to reforge an item with full durability",
true, true), true, true),
/**
* The message displayed when clicking a blacksmith with an empty hand
*/
NO_ITEM_MESSAGE("noItemMessage", SettingValueType.STRING, "Please present the item you want to reforge",
"The message to display when a blacksmith is clicked with an empty hand", true, true),
/*------------------ /*------------------
| Global settings | | Global settings |
------------------*/ ------------------*/
@@ -241,7 +247,8 @@ public enum BlacksmithSetting implements Setting {
* The cost for repairing a damaged anvil * The cost for repairing a damaged anvil
*/ */
ANVIL_DAMAGED_COST("damagedAnvilReforgingCost", SettingValueType.POSITIVE_DOUBLE, 20.0, ANVIL_DAMAGED_COST("damagedAnvilReforgingCost", SettingValueType.POSITIVE_DOUBLE, 20.0,
"The cost of fully repairing a damaged anvil", false, false); "The cost of fully repairing a damaged anvil", false, false),
;
private final String path; private final String path;
private final String childPath; private final String childPath;
@@ -262,7 +269,7 @@ public enum BlacksmithSetting implements Setting {
* @param isPerNPC <p>Whether this setting is per-NPC or global</p> * @param isPerNPC <p>Whether this setting is per-NPC or global</p>
* @param isMessage <p>Whether this option is for an NPC message</p> * @param isMessage <p>Whether this option is for an NPC message</p>
*/ */
BlacksmithSetting(@NotNull String key, @NotNull SettingValueType valueType, @NotNull Object value, BlacksmithSetting(@NotNull String key, @NotNull SettingValueType valueType, @Nullable Object value,
@NotNull String description, boolean isPerNPC, boolean isMessage) { @NotNull String description, boolean isPerNPC, boolean isMessage) {
if (isPerNPC) { if (isPerNPC) {
if (isMessage) { if (isMessage) {

View File

@@ -3,10 +3,10 @@ package net.knarcraft.blacksmith.config.blacksmith;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.SettingValueType; import net.knarcraft.blacksmith.config.SettingValueType;
import net.knarcraft.blacksmith.config.Settings; import net.knarcraft.blacksmith.config.Settings;
import net.knarcraft.blacksmith.config.StargateYamlConfiguration;
import net.knarcraft.blacksmith.util.ConfigHelper; import net.knarcraft.blacksmith.util.ConfigHelper;
import net.knarcraft.blacksmith.util.InputParsingHelper; import net.knarcraft.blacksmith.util.InputParsingHelper;
import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.ItemHelper;
import net.knarcraft.knarlib.config.StargateYamlConfiguration;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
@@ -19,7 +19,6 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
/** /**
* A class which keeps track of all default blacksmith NPC settings and all global blacksmith settings * A class which keeps track of all default blacksmith NPC settings and all global blacksmith settings
@@ -311,7 +310,7 @@ public class GlobalBlacksmithSettings implements Settings<BlacksmithSetting> {
*/ */
private void loadSettings() { private void loadSettings() {
instance.reloadConfig(); instance.reloadConfig();
FileConfiguration configuration = instance.getConfiguration(); FileConfiguration configuration = instance.getConfig();
for (BlacksmithSetting blacksmithSetting : BlacksmithSetting.values()) { for (BlacksmithSetting blacksmithSetting : BlacksmithSetting.values()) {
if (!configuration.contains(blacksmithSetting.getPath())) { if (!configuration.contains(blacksmithSetting.getPath())) {
@@ -344,15 +343,14 @@ public class GlobalBlacksmithSettings implements Settings<BlacksmithSetting> {
ConfigurationSection enchantmentCostNode = fileConfiguration.getConfigurationSection( ConfigurationSection enchantmentCostNode = fileConfiguration.getConfigurationSection(
getBase(BlacksmithSetting.ENCHANTMENT_COST.getPath())); getBase(BlacksmithSetting.ENCHANTMENT_COST.getPath()));
if (enchantmentCostNode == null) { if (enchantmentCostNode == null) {
instance.getLogger().log(Level.WARNING, "Could not load enchantment prices. because the " + BlacksmithPlugin.warn("Could not load enchantment prices. because the configuration section doesn't exist");
"configuration section doesn't exist");
return; return;
} }
Map<String, String> relevantKeys = getRelevantKeys(enchantmentCostNode); Map<String, String> relevantKeys = getRelevantKeys(enchantmentCostNode);
for (String key : relevantKeys.keySet()) { for (String key : relevantKeys.keySet()) {
String enchantmentName = relevantKeys.get(key); String enchantmentName = relevantKeys.get(key);
Enchantment enchantment = InputParsingHelper.matchEnchantment(enchantmentName); Enchantment enchantment = InputParsingHelper.matchEnchantment(enchantmentName);
instance.getLogger().log(Level.WARNING, "loadEnchantmentPrices " + enchantmentName); BlacksmithPlugin.warn("loadEnchantmentPrices " + enchantmentName);
setItemPrice(this.enchantmentCosts, enchantmentName, enchantment, enchantmentCostNode.getDouble(key)); setItemPrice(this.enchantmentCosts, enchantmentName, enchantment, enchantmentCostNode.getDouble(key));
} }
} }
@@ -366,8 +364,7 @@ public class GlobalBlacksmithSettings implements Settings<BlacksmithSetting> {
ConfigurationSection basePerDurabilityPriceNode = fileConfiguration.getConfigurationSection( ConfigurationSection basePerDurabilityPriceNode = fileConfiguration.getConfigurationSection(
getBase(BlacksmithSetting.PRICE_PER_DURABILITY_POINT.getPath())); getBase(BlacksmithSetting.PRICE_PER_DURABILITY_POINT.getPath()));
if (basePerDurabilityPriceNode == null) { if (basePerDurabilityPriceNode == null) {
instance.getLogger().log(Level.WARNING, "Could not load per durability prices. because the " + BlacksmithPlugin.warn("Could not load per durability prices. because the configuration section doesn't exist");
"configuration section doesn't exist");
return; return;
} }
Map<String, String> relevantKeys = getRelevantKeys(basePerDurabilityPriceNode); Map<String, String> relevantKeys = getRelevantKeys(basePerDurabilityPriceNode);
@@ -394,8 +391,7 @@ public class GlobalBlacksmithSettings implements Settings<BlacksmithSetting> {
ConfigurationSection basePriceNode = fileConfiguration.getConfigurationSection( ConfigurationSection basePriceNode = fileConfiguration.getConfigurationSection(
getBase(BlacksmithSetting.BASE_PRICE.getPath())); getBase(BlacksmithSetting.BASE_PRICE.getPath()));
if (basePriceNode == null) { if (basePriceNode == null) {
instance.getLogger().log(Level.WARNING, "Could not load base prices, because the configuration " + BlacksmithPlugin.warn("Could not load base prices, because the configuration section doesn't exist");
"section doesn't exist");
return; return;
} }
Map<String, String> relevantKeys = getRelevantKeys(basePriceNode); Map<String, String> relevantKeys = getRelevantKeys(basePriceNode);
@@ -442,7 +438,7 @@ public class GlobalBlacksmithSettings implements Settings<BlacksmithSetting> {
if (item != null) { if (item != null) {
prices.put(item, price); prices.put(item, price);
} else { } else {
instance.getLogger().log(Level.WARNING, "Unable to find a material/enchantment matching " + itemName); BlacksmithPlugin.warn("Unable to find a material/enchantment matching " + itemName);
} }
} }
@@ -505,7 +501,7 @@ public class GlobalBlacksmithSettings implements Settings<BlacksmithSetting> {
* Saves all current settings to the config file * Saves all current settings to the config file
*/ */
private void save() { private void save() {
FileConfiguration fileConfiguration = instance.getConfiguration(); FileConfiguration fileConfiguration = instance.getConfig();
//Save all default settings //Save all default settings
for (BlacksmithSetting setting : BlacksmithSetting.values()) { for (BlacksmithSetting setting : BlacksmithSetting.values()) {
@@ -527,6 +523,8 @@ public class GlobalBlacksmithSettings implements Settings<BlacksmithSetting> {
//Load all enchantment prices //Load all enchantment prices
ConfigurationSection enchantmentCostNode = getAndCreateSection(fileConfiguration, getBase(BlacksmithSetting.ENCHANTMENT_COST.getPath())); ConfigurationSection enchantmentCostNode = getAndCreateSection(fileConfiguration, getBase(BlacksmithSetting.ENCHANTMENT_COST.getPath()));
for (Enchantment enchantment : this.enchantmentCosts.keySet()) { for (Enchantment enchantment : this.enchantmentCosts.keySet()) {
// Note: While depreciated in Spigot, the new method is not available for Paper
//noinspection deprecation
enchantmentCostNode.set(unNormalizeName(enchantment.getKey().getKey()), this.enchantmentCosts.get(enchantment)); enchantmentCostNode.set(unNormalizeName(enchantment.getKey().getKey()), this.enchantmentCosts.get(enchantment));
} }

View File

@@ -16,7 +16,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
/** /**
* A class which keeps track of all default scrapper NPC settings and all global scrapper settings * A class which keeps track of all default scrapper NPC settings and all global scrapper settings
@@ -146,7 +145,7 @@ public class GlobalScrapperSettings implements Settings<ScrapperSetting> {
*/ */
private void loadSettings() { private void loadSettings() {
instance.reloadConfig(); instance.reloadConfig();
FileConfiguration configuration = instance.getConfiguration(); FileConfiguration configuration = instance.getConfig();
for (ScrapperSetting setting : ScrapperSetting.values()) { for (ScrapperSetting setting : ScrapperSetting.values()) {
if (!configuration.contains(setting.getPath())) { if (!configuration.contains(setting.getPath())) {
@@ -165,7 +164,7 @@ public class GlobalScrapperSettings implements Settings<ScrapperSetting> {
* Saves all current settings to the config file * Saves all current settings to the config file
*/ */
private void save() { private void save() {
FileConfiguration fileConfiguration = instance.getConfiguration(); FileConfiguration fileConfiguration = instance.getConfig();
//Save all default settings //Save all default settings
for (ScrapperSetting setting : ScrapperSetting.values()) { for (ScrapperSetting setting : ScrapperSetting.values()) {
@@ -213,6 +212,24 @@ public class GlobalScrapperSettings implements Settings<ScrapperSetting> {
return asDouble(ScrapperSetting.NETHERITE_SALVAGE_COST); return asDouble(ScrapperSetting.NETHERITE_SALVAGE_COST);
} }
/**
* Gets the math formula for the increase in salvage cost
*
* @return <p>The salvage cost increase formula</p>
*/
public String getSalvageCostIncrease() {
return asString(ScrapperSetting.SALVAGE_COST_INCREASE);
}
/**
* Gets the math formula for the increase in salvage cooldown
*
* @return <p>The salvage cooldown increase formula</p>
*/
public String getSalvageCooldownIncrease() {
return asString(ScrapperSetting.SALVAGE_COOLDOWN_INCREASE);
}
/** /**
* Gets trash salvage for the given material * Gets trash salvage for the given material
* *
@@ -251,8 +268,7 @@ public class GlobalScrapperSettings implements Settings<ScrapperSetting> {
for (String trashSalvageInfo : allTrashSalvage) { for (String trashSalvageInfo : allTrashSalvage) {
// Ignore invalid lines // Ignore invalid lines
if (!trashSalvageInfo.contains(":")) { if (!trashSalvageInfo.contains(":")) {
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, String.format("The trash salvage " + BlacksmithPlugin.warn(String.format("The trash salvage configuration line %s is invalid", trashSalvageInfo));
"configuration line %s is invalid", trashSalvageInfo));
continue; continue;
} }
@@ -277,4 +293,15 @@ public class GlobalScrapperSettings implements Settings<ScrapperSetting> {
} }
} }
/**
* Gets the string value of the given setting
*
* @param setting <p>The setting to get the value of</p>
* @return <p>The value of the given setting as a string</p>
*/
@NotNull
private String asString(@NotNull ScrapperSetting setting) {
return getValue(setting).toString();
}
} }

View File

@@ -83,6 +83,15 @@ public class ScrapperNPCSettings implements TraitSettings<ScrapperSetting> {
} }
} }
/**
* Gets whether this scrapper is able to split an enchanted book
*
* @return <p>True if this scrapper is able to split an enchanted book</p>
*/
public boolean splitEnchantedBook() {
return asBoolean(ScrapperSetting.SPLIT_ENCHANTED_BOOK);
}
/** /**
* Gets the raw current value of a setting * Gets the raw current value of a setting
* *
@@ -171,6 +180,15 @@ public class ScrapperNPCSettings implements TraitSettings<ScrapperSetting> {
return asString(ScrapperSetting.COST_MESSAGE_NETHERITE); return asString(ScrapperSetting.COST_MESSAGE_NETHERITE);
} }
/**
* Gets the message to use for displaying enchanted book salvage cost
*
* @return <p>The message to use for displaying enchanted book salvage cost</p>
*/
public String getEnchantedBookCostMessage() {
return asString(ScrapperSetting.COST_MESSAGE_ENCHANTED_BOOK);
}
@Override @Override
@NotNull @NotNull
public String getCoolDownUnexpiredMessage() { public String getCoolDownUnexpiredMessage() {
@@ -178,13 +196,25 @@ public class ScrapperNPCSettings implements TraitSettings<ScrapperSetting> {
} }
/** /**
* The message displayed if a player presents a different item after seeing the price to salvage an item * Gets the message displayed if a player presents a different item after seeing the price to salvage an item
*
* @return <p>The item changed message</p>
*/ */
@NotNull @NotNull
public String getItemChangedMessage() { public String getItemChangedMessage() {
return asString(ScrapperSetting.ITEM_UNEXPECTEDLY_CHANGED_MESSAGE); return asString(ScrapperSetting.ITEM_UNEXPECTEDLY_CHANGED_MESSAGE);
} }
/**
* Gets the message displayed if a player presents the scrapper with an empty hand
*
* @return <p>The no item message</p>
*/
@NotNull
public String getNoItemMessage() {
return asString(ScrapperSetting.NO_ITEM_MESSAGE);
}
/** /**
* Gets the minimum delay used to wait for a salvaging to finish. * Gets the minimum delay used to wait for a salvaging to finish.
* *
@@ -454,4 +484,24 @@ public class ScrapperNPCSettings implements TraitSettings<ScrapperSetting> {
return asString(ScrapperSetting.CANNOT_SALVAGE_NETHERITE_MESSAGE); return asString(ScrapperSetting.CANNOT_SALVAGE_NETHERITE_MESSAGE);
} }
/**
* Gets the message to display when explaining that this scrapper is unable to salvage enchanted books
*
* @return <p>The message to display when explaining that this scrapper is unable to salvage enchanted books</p>
*/
@NotNull
public String getCannotSalvageEnchantedBookMessage() {
return asString(ScrapperSetting.CANNOT_SALVAGE_ENCHANTED_BOOK_MESSAGE);
}
/**
* Gets the message to display when explaining that the scrapper cannot salvage an enchanted book with a single enchantment
*
* @return <p>The message to display when explaining that the scrapper cannot salvage an enchanted book with a single enchantment</p>
*/
@NotNull
public String getCannotSplitEnchantedBookFurtherMessage() {
return asString(ScrapperSetting.CANNOT_SPLIT_ENCHANTED_BOOK_FURTHER_MESSAGE);
}
} }

View File

@@ -91,6 +91,12 @@ public enum ScrapperSetting implements Setting {
*/ */
SALVAGE_NETHERITE("salvageNetherite", SettingValueType.BOOLEAN, true, SALVAGE_NETHERITE("salvageNetherite", SettingValueType.BOOLEAN, true,
"Whether to enable salvaging of netherite items", true, false), "Whether to enable salvaging of netherite items", true, false),
/**
* The setting for whether the NPC should allow salvaging an enchanted books with several enchantments into several books with one enchantment
*/
SPLIT_ENCHANTED_BOOK("splitEnchantedBook", SettingValueType.BOOLEAN, false, "Whether to enable " +
"splitting of enchanted books", true, false),
/*----------- /*-----------
| Messages | | Messages |
@@ -194,6 +200,13 @@ public enum ScrapperSetting implements Setting {
"&eIt will cost &a{cost}&e to salvage that &a{item}&e into diamond!", "&eIt will cost &a{cost}&e to salvage that &a{item}&e into diamond!",
"The message to display when explaining the shown item's netherite salvage cost", true, true), "The message to display when explaining the shown item's netherite salvage cost", true, true),
/**
* The message displayed when displaying the cost of salvaging the player's held enchanted book
*/
COST_MESSAGE_ENCHANTED_BOOK("costMessageEnchantedBook", SettingValueType.STRING,
"&eIt will cost &a{cost}&e to salvage that enchanted book!",
"The message to display when explaining the shown enchanted book's salvage cost", true, true),
/** /**
* The message displayed when explaining that all items will be returned as salvage * The message displayed when explaining that all items will be returned as salvage
*/ */
@@ -236,6 +249,26 @@ public enum ScrapperSetting implements Setting {
"&cI'm sorry, but I'm unable to salvage netherite items!", "&cI'm sorry, but I'm unable to salvage netherite items!",
"The message to display when asked to salvage netherite items, and that option is disabled", true, true), "The message to display when asked to salvage netherite items, and that option is disabled", true, true),
/**
* The message displayed when explaining that enchanted book salvage is disabled
*/
CANNOT_SALVAGE_ENCHANTED_BOOK_MESSAGE("cannotSalvageEnchantedBookMessage", SettingValueType.STRING,
"&cI'm sorry, but I'm unable to salvage enchanted books!",
"The message to display when asked to salvage enchanted books, and the option is disabled", true, true),
/**
* The message displayed when explaining that a player cannot salvage an enchanted book containing a single enchantment
*/
CANNOT_SPLIT_ENCHANTED_BOOK_FURTHER_MESSAGE("cannotSplitEnchantedBookFurtherMessage", SettingValueType.STRING,
"&cI'm sorry, but I cannot salvage that enchanted book any further",
"The message displayed when a player attempts to salvage an enchanted book with a single enchantment", true, true),
/**
* The message displayed when clicking a scrapper with an empty hand
*/
NO_ITEM_MESSAGE("noItemMessage", SettingValueType.STRING, "Please present the item you want to salvage",
"The message to display when a scrapper is clicked with an empty hand", true, true),
/*------------------ /*------------------
| Global settings | | Global settings |
------------------*/ ------------------*/
@@ -258,6 +291,20 @@ public enum ScrapperSetting implements Setting {
NETHERITE_SALVAGE_COST("netheriteSalvagePrice", SettingValueType.POSITIVE_DOUBLE, 15, NETHERITE_SALVAGE_COST("netheriteSalvagePrice", SettingValueType.POSITIVE_DOUBLE, 15,
"The cost of using the scrapper to remove netherite from an item", false, false), "The cost of using the scrapper to remove netherite from an item", false, false),
/**
* The mathematical formula for increasing salvage cost
*/
SALVAGE_COST_INCREASE("salvageCostIncrease", SettingValueType.STRING, "{cost}*{timesUsed}",
"The mathematical formula for salvage cost increase when continually used within the same hour. " +
"This is necessary for some servers where items can be easily farmed. Set to {cost} to disable behavior.", false, false),
/**
* The mathematical formula for increasing salvage cooldown
*/
SALVAGE_COOLDOWN_INCREASE("salvageCooldownIncrease", SettingValueType.STRING, "{cooldown}*{timesUsed}",
"The mathematical formula for salvage cooldown increase when continually used within the same hour. " +
"This is necessary for some servers where items can be easily farmed. Set to {cooldown} to disable behavior.", false, false),
/** /**
* Whether to display exact time in minutes and seconds when displaying a remaining cool-down * Whether to display exact time in minutes and seconds when displaying a remaining cool-down
*/ */
@@ -302,7 +349,7 @@ public enum ScrapperSetting implements Setting {
* @param isPerNPC <p>Whether this setting is per-NPC or global</p> * @param isPerNPC <p>Whether this setting is per-NPC or global</p>
* @param isMessage <p>Whether this option is for an NPC message</p> * @param isMessage <p>Whether this option is for an NPC message</p>
*/ */
ScrapperSetting(@NotNull String key, @NotNull SettingValueType valueType, @NotNull Object value, ScrapperSetting(@NotNull String key, @NotNull SettingValueType valueType, @Nullable Object value,
@NotNull String description, boolean isPerNPC, boolean isMessage) { @NotNull String description, boolean isPerNPC, boolean isMessage) {
if (isPerNPC) { if (isPerNPC) {
if (isMessage) { if (isMessage) {

View File

@@ -0,0 +1,280 @@
package net.knarcraft.blacksmith.container;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.manager.EconomyManager;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* The cost of performing an action
*
* @param monetaryCost <p>The monetary cost of the action</p>
* @param expCost <p>The experience cost of the action</p>
* @param itemCost <p>The item-based cost of the action</p>
* @param requiredPermissions <p>The permission required for the action</p>
*/
public record ActionCost(double monetaryCost, int expCost, @Nullable ItemStack itemCost,
@NotNull Set<String> requiredPermissions) implements ConfigurationSerializable {
/**
* Changes the monetary cost of this action
*
* @param monetaryCost <p>The new monetary cost</p>
* @return <p>The resulting action cost</p>
*/
public ActionCost changeMonetaryCost(double monetaryCost) {
return new ActionCost(monetaryCost, this.expCost, this.itemCost, this.requiredPermissions);
}
/**
* Changes the experience cost of this action
*
* @param expCost <p>The new experience cost</p>
* @return <p>The resulting action cost</p>
*/
public ActionCost changeExpCost(int expCost) {
return new ActionCost(this.monetaryCost, expCost, this.itemCost, this.requiredPermissions);
}
/**
* Changes the item cost of this action
*
* @param itemCost <p>The new item cost</p>
* @return <p>The resulting action cost</p>
*/
public ActionCost changeItemCost(@Nullable ItemStack itemCost) {
return new ActionCost(this.monetaryCost, this.expCost, itemCost, this.requiredPermissions);
}
/**
* Changes the permission cost of this action
*
* @param requiredPermissions <p>The new permission cost</p>
* @return <p>The resulting action cost</p>
*/
public ActionCost changePermissionCost(@NotNull Set<String> requiredPermissions) {
return new ActionCost(this.monetaryCost, this.expCost, this.itemCost, requiredPermissions);
}
/**
* Displays this action cost as a string
*
* @param player <p>The player to calculate the cost for</p>
* @return <p>The string representation of this action cost</p>
*/
@NotNull
public String displayCost(@NotNull Player player) {
StringBuilder builder = new StringBuilder();
if (monetaryCost > 0) {
builder.append(EconomyManager.format(monetaryCost)).append(", ").append("\n");
}
if (expCost > 0) {
builder.append(expCost).append("exp, ").append("\n");
}
if (itemCost != null) {
NamespacedKey itemName = itemCost.getType().getKeyOrNull();
if (itemName != null) {
builder.append(itemCost.getAmount()).append(" x ").append(itemName);
ItemMeta itemMeta = itemCost.getItemMeta();
if (itemMeta != null && itemMeta.hasDisplayName()) {
builder.append("(").append(itemMeta.getDisplayName()).append(")");
}
if (itemMeta != null && itemMeta.hasLore() && itemMeta.getLore() != null) {
for (String lore : itemMeta.getLore()) {
builder.append("\n").append(lore);
}
}
builder.append("\n");
}
}
if (!requiredPermissions().isEmpty()) {
for (String permission : requiredPermissions()) {
if (player.hasPermission(permission)) {
builder.append(ChatColor.DARK_GREEN).append("O ").append(permission).append("\n");
} else {
builder.append(ChatColor.DARK_RED).append("X ").append(permission).append("\n");
}
}
}
return builder.toString();
}
/**
* Checks whether the given player is able to pay this action cost
*
* @param player <p>The player to check</p>
* @return <p>True if the player is able to pay</p>
*/
public boolean canPay(@NotNull Player player) {
for (String permission : this.requiredPermissions) {
if (!player.hasPermission(permission)) {
return false;
}
}
if (player.getExp() < this.expCost || !EconomyManager.hasEnough(player, this.monetaryCost)) {
return false;
}
return hasEnoughValidItemsInInventory(player);
}
/**
* Takes exp, money and items from the player, according to this cost
*
* @param player <p>The player to take the payment from</p>
*/
public void takePayment(@NotNull Player player) {
player.giveExp(-expCost);
EconomyManager.withdraw(player, monetaryCost);
takeItemCost(player);
}
/**
* Checks whether the given player has enough items specified as the item cost
*
* @param player <p>The player to check</p>
* @return <p>True if the player has enough items in their inventory</p>
*/
private boolean hasEnoughValidItemsInInventory(@NotNull Player player) {
if (this.itemCost == null) {
return true;
}
int amountInInventory = 0;
for (Map.Entry<Integer, Integer> entry : getValidItems(player).entrySet()) {
amountInInventory += entry.getValue();
}
return this.itemCost.getAmount() >= amountInInventory;
}
/**
* Gets all items in a player's inventory equal to the specified item
*
* @param player <p>The player to get valid items for</p>
* @return <p>All valid items in the format: Inventory id -> Amount</p>
*/
@NotNull
private Map<Integer, Integer> getValidItems(@NotNull Player player) {
if (this.itemCost == null) {
return new HashMap<>();
}
PlayerInventory playerInventory = player.getInventory();
ItemMeta targetMeta = this.itemCost.getItemMeta();
String displayName = null;
List<String> lore = null;
if (targetMeta != null) {
displayName = targetMeta.hasDisplayName() ? targetMeta.getDisplayName() : null;
lore = targetMeta.hasLore() ? targetMeta.getLore() : null;
}
Map<Integer, Integer> output = new HashMap<>();
HashMap<Integer, ? extends ItemStack> itemsOfType = playerInventory.all(this.itemCost.getType());
for (Map.Entry<Integer, ? extends ItemStack> entry : itemsOfType.entrySet()) {
if (targetMeta != null) {
// Only consider item if the name and lore is the same
ItemMeta meta = entry.getValue().getItemMeta();
if (meta == null || (displayName != null && (!meta.hasDisplayName() ||
!meta.getDisplayName().equals(displayName)) || lore != null && (!meta.hasLore() ||
meta.getLore() == null || !meta.getLore().equals(lore)))) {
continue;
}
}
output.put(entry.getKey(), entry.getValue().getAmount());
}
return output;
}
/**
* Takes the amount of items specified as the cost for this action
*
* @param player <p>The player to take the items from</p>
*/
private void takeItemCost(@NotNull Player player) {
if (this.itemCost == null) {
return;
}
int clearedAmount = 0;
for (Map.Entry<Integer, Integer> entry : getValidItems(player).entrySet()) {
int inventory = entry.getKey();
int amount = entry.getValue();
if (amount <= this.itemCost.getAmount() - clearedAmount) {
clearedAmount += amount;
player.getInventory().clear(entry.getKey());
} else {
clearedAmount = this.itemCost.getAmount();
ItemStack item = player.getInventory().getItem(inventory);
if (item != null) {
item.setAmount(amount - clearedAmount);
} else {
BlacksmithPlugin.error("An item changed after calculating item cost. Was unable to take " +
amount + " items");
}
}
if (clearedAmount >= this.itemCost.getAmount()) {
break;
}
}
}
/**
* Loads an action cost from a configuration section
*
* @param configurationSection <p>The configuration section to load from</p>
* @param key <p>The key of the cost to load</p>
* @return <p>The loaded cost</p>
*/
private static ActionCost loadActionCost(@NotNull ConfigurationSection configurationSection, @NotNull String key) {
double cost = configurationSection.getDouble(key, Double.MIN_VALUE);
if (cost != Double.MIN_VALUE) {
return new ActionCost(cost, 0, null, Set.of());
} else {
return (ActionCost) configurationSection.get(key);
}
}
/**
* Deserializes an action cost
*
* @param serialized <p>The serialized action cost</p>
* @return <p>The deserialized action cost</p>
*/
@SuppressWarnings({"unused", "unchecked"})
public static ActionCost deserialize(Map<String, Object> serialized) {
double monetaryCost = (double) serialized.get("monetaryCost");
int expCost = (int) serialized.get("expCost");
ItemStack itemCost = (ItemStack) serialized.get("itemCost");
Set<String> requiredPermissions = (Set<String>) serialized.get("requiredPermissions");
return new ActionCost(monetaryCost, expCost, itemCost, requiredPermissions);
}
@Override
public @NotNull Map<String, Object> serialize() {
Map<String, Object> serialized = new HashMap<>();
serialized.put("monetaryCost", Optional.of(this.monetaryCost));
serialized.put("expCost", Optional.of(this.expCost));
serialized.put("itemCost", itemCost);
serialized.put("requiredPermissions", this.requiredPermissions);
return serialized;
}
}

View File

@@ -0,0 +1,16 @@
package net.knarcraft.blacksmith.container;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* The result of a recipe's salvage
*
* @param recipe <p>The selected recipe</p>
* @param salvage <p>The resulting salvage</p>
*/
public record RecipeResult(@NotNull Recipe recipe, @NotNull List<ItemStack> salvage) {
}

View File

@@ -0,0 +1,20 @@
package net.knarcraft.blacksmith.container;
import net.knarcraft.blacksmith.property.SalvageMethod;
import net.knarcraft.blacksmith.property.SalvageState;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* The result of an attempted salvage
*
* @param salvageMethod <p>The salvage method used</p>
* @param salvage <p>The produced salvage</p>
* @param salvageState <p>The state of the salvage result</p>
* @param requiredAmount <p>The amount of items required for the salvage</p>
*/
public record SalvageResult(@NotNull SalvageMethod salvageMethod, @NotNull List<ItemStack> salvage,
@NotNull SalvageState salvageState, int requiredAmount) {
}

View File

@@ -0,0 +1,50 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.jetbrains.annotations.NotNull;
/**
* The base class for all blacksmith plugin events
*/
@SuppressWarnings("unused")
public abstract class AbstractBlacksmithPluginEvent extends Event implements BlacksmithPluginEvent {
protected final NPC npc;
protected final Player player;
protected final Entity entity;
/**
* Instantiates a new blacksmith plugin event
*
* @param npc <p>The NPC involved in the event</p>
* @param entity <p>The entity of the NPC</p>
* @param player <p>The player involved in the event</p>
*/
public AbstractBlacksmithPluginEvent(@NotNull NPC npc, @NotNull Entity entity, @NotNull Player player) {
this.npc = npc;
this.entity = entity;
this.player = player;
}
@Override
@NotNull
public NPC getNpc() {
return this.npc;
}
@Override
@NotNull
public Entity getEntity() {
return this.entity;
}
@Override
@NotNull
public Player getPlayer() {
return this.player;
}
}

View File

@@ -0,0 +1,25 @@
package net.knarcraft.blacksmith.event;
import org.bukkit.Material;
/**
* An event triggered when a blacksmith or scrapper starts reforging or salvaging an item
*/
@SuppressWarnings("unused")
public interface ActionStartEvent extends BlacksmithPluginEvent {
/**
* Gets the amount of ticks this action will take
*
* @return <p>The duration in ticks</p>
*/
long getActionDurationTicks();
/**
* Gets the appropriate crafting station for this action
*
* @return <p>The appropriate crafting station</p>
*/
Material getCraftingStation();
}

View File

@@ -0,0 +1,38 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* An event triggered by the Blacksmith plugin
*/
@SuppressWarnings("unused")
public interface BlacksmithPluginEvent {
/**
* Gets the NPC involved in the event
*
* @return <p>The NPC</p>
*/
@NotNull
NPC getNpc();
/**
* Gets the entity of the NPC
*
* @return <p>The NPC entity</p>
*/
@NotNull
Entity getEntity();
/**
* Gets the player involved in the event
*
* @return <p>The player</p>
*/
@NotNull
Player getPlayer();
}

View File

@@ -0,0 +1,44 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* The event triggered when a blacksmith reforging fails
*/
public class BlacksmithReforgeFailEvent extends AbstractBlacksmithPluginEvent implements ReforgeEndEvent {
private static final HandlerList handlers = new HandlerList();
/**
* Instantiates a new blacksmith reforge fail event
*
* @param npc <p>The NPC involved in the event</p>
* @param entity <p>The entity of the NPC</p>
* @param player <p>The player that initiated the session</p>
*/
public BlacksmithReforgeFailEvent(@NotNull NPC npc, @NotNull Entity entity, @NotNull Player player) {
super(npc, entity, player);
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
@SuppressWarnings("unused")
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,62 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* The event triggered when a blacksmith reforging starts
*/
public class BlacksmithReforgeStartEvent extends AbstractBlacksmithPluginEvent implements ActionStartEvent {
private static final HandlerList handlers = new HandlerList();
private final long durationTicks;
private final Material craftingStation;
/**
* Instantiates a new blacksmith reforge start event
*
* @param npc <p>The NPC involved in the event</p>
* @param entity <p>The entity of the NPC</p>
* @param player <p>The player involved in the event</p>
* @param durationTicks <p>The duration of the reforge</p>
* @param craftingStation <p>The appropriate crafting station for this reforging</p>
*/
public BlacksmithReforgeStartEvent(@NotNull NPC npc, @NotNull Entity entity, @NotNull Player player, long durationTicks,
@NotNull Material craftingStation) {
super(npc, entity, player);
this.durationTicks = durationTicks;
this.craftingStation = craftingStation;
}
@Override
public long getActionDurationTicks() {
return durationTicks;
}
@Override
public Material getCraftingStation() {
return craftingStation;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
@SuppressWarnings("unused")
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,44 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* The event triggered when a blacksmith reforging succeeds
*/
public class BlacksmithReforgeSucceedEvent extends AbstractBlacksmithPluginEvent implements ReforgeEndEvent {
private static final HandlerList handlers = new HandlerList();
/**
* Instantiates a new blacksmith reforge succeed event
*
* @param npc <p>The NPC involved in the event</p>
* @param entity <p>The entity of the NPC</p>
* @param player <p>The player involved in the event</p>
*/
public BlacksmithReforgeSucceedEvent(@NotNull NPC npc, @NotNull Entity entity, @NotNull Player player) {
super(npc, entity, player);
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@SuppressWarnings("unused")
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,144 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* An event triggered when an NPC plays a sound indicating an action
*/
@SuppressWarnings("unused")
public class NPCSoundEvent extends AbstractBlacksmithPluginEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
private float pitch;
private float volume;
private SoundCategory soundCategory;
private Sound sound;
/**
* Instantiates a new NPC sound event
*
* @param npc <p>The NPC playing the sound</p>
* @param entity <p>The entity playing the sound</p>
* @param player <p>The player whose interaction triggered the sound</p>
* @param soundCategory <p>The category the sound is to play in</p>
* @param sound <p>The sound to play</p>
* @param volume <p>The volume of the played sound</p>
* @param pitch <p>The pitch of the played sound</p>
*/
public NPCSoundEvent(@NotNull NPC npc, @NotNull Entity entity, @NotNull Player player, @NotNull SoundCategory soundCategory,
@NotNull Sound sound, float volume, float pitch) {
super(npc, entity, player);
this.soundCategory = soundCategory;
this.sound = sound;
this.volume = volume;
this.pitch = pitch;
}
/**
* Gets the pitch of the played sound
*
* @return <p>The pitch of the played sound</p>
*/
public float getPitch() {
return this.pitch;
}
/**
* Gets the volume of the played sound
*
* @return <p>The volume of the played sound</p>
*/
public float getVolume() {
return this.volume;
}
/**
* Gets the category the sound is played in
*
* @return <p>The sound category used</p>
*/
public @NotNull SoundCategory getSoundCategory() {
return this.soundCategory;
}
/**
* Gets the sound to play
*
* @return <p>The sound to play</p>
*/
public @NotNull Sound getSound() {
return this.sound;
}
/**
* Sets the pitch of the played sound
*
* @param pitch <p>The new pitch</p>
*/
public void setPitch(float pitch) {
this.pitch = pitch;
}
/**
* Sets the volume of the played sound
*
* @param volume <p>The new volume</p>
*/
public void setVolume(float volume) {
this.volume = volume;
}
/**
* Sets the sound to play
*
* @param sound <p>The new sound to play</p>
*/
public void setSound(@NotNull Sound sound) {
this.sound = sound;
}
/**
* Sets the category the sound is played in
*
* @param soundCategory <p>The new sound category</p>
*/
public void setSoundCategory(@NotNull SoundCategory soundCategory) {
this.soundCategory = soundCategory;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@NotNull
@SuppressWarnings("unused")
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,7 @@
package net.knarcraft.blacksmith.event;
/**
* An event triggered when a reforge finishes
*/
public interface ReforgeEndEvent extends BlacksmithPluginEvent {
}

View File

@@ -0,0 +1,7 @@
package net.knarcraft.blacksmith.event;
/**
* An event triggered when a salvaging finishes
*/
public interface SalvageEndEvent extends BlacksmithPluginEvent {
}

View File

@@ -0,0 +1,44 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* The event triggered when a scrapper salvaging fails
*/
public class ScrapperSalvageFailEvent extends AbstractBlacksmithPluginEvent implements SalvageEndEvent {
private static final HandlerList handlers = new HandlerList();
/**
* Instantiates a new scrapper salvage fail event
*
* @param npc <p>The NPC involved in the event</p>
* @param entity <p>The entity of the NPC</p>
* @param player <p>The player involved in the event</p>
*/
public ScrapperSalvageFailEvent(@NotNull NPC npc, @NotNull Entity entity, @NotNull Player player) {
super(npc, entity, player);
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@SuppressWarnings("unused")
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,63 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* The event triggered when a scrapper salvaging starts
*/
@SuppressWarnings("unused")
public class ScrapperSalvageStartEvent extends AbstractBlacksmithPluginEvent implements ActionStartEvent {
private static final HandlerList handlers = new HandlerList();
private final long durationTicks;
private final Material craftingStation;
/**
* Instantiates a new scrapper salvage start event
*
* @param npc <p>The NPC involved in the event</p>
* @param entity <p>The entity of the NPC</p>
* @param player <p>The player involved in the event</p>
* @param durationTicks <p>The duration of the salvage</p>
* @param craftingStation <p>The appropriate crafting station for this salvaging</p>
*/
public ScrapperSalvageStartEvent(@NotNull NPC npc, @NotNull Entity entity, @NotNull Player player, long durationTicks,
@NotNull Material craftingStation) {
super(npc, entity, player);
this.durationTicks = durationTicks;
this.craftingStation = craftingStation;
}
@Override
public long getActionDurationTicks() {
return this.durationTicks;
}
@Override
public Material getCraftingStation() {
return this.craftingStation;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@SuppressWarnings("unused")
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,44 @@
package net.knarcraft.blacksmith.event;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* The event triggered when a scrapper salvaging succeeds
*/
public class ScrapperSalvageSucceedEvent extends AbstractBlacksmithPluginEvent implements SalvageEndEvent {
private static final HandlerList handlers = new HandlerList();
/**
* Instantiates a new scrapper salvage succeed event
*
* @param npc <p>The NPC involved in the event</p>
* @param entity <p>The entity of the NPC</p>
* @param player <p>The player involved in the event</p>
*/
public ScrapperSalvageSucceedEvent(@NotNull NPC npc, @NotNull Entity entity, @NotNull Player player) {
super(npc, entity, player);
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
@SuppressWarnings("unused")
@NotNull
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -1,33 +0,0 @@
package net.knarcraft.blacksmith.formatting;
import net.citizensnpcs.api.npc.NPC;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import org.bukkit.entity.Player;
import java.util.List;
/**
* A formatter for formatting displayed messages
*/
public final class BlacksmithStringFormatter {
private BlacksmithStringFormatter() {
}
/**
* Sends a message from a blacksmith NPC to a player
*
* @param npc <p>The NPC sending the message</p>
* @param player <p>The player to send the message to</p>
* @param message <p>The message to send</p>
*/
public static void sendNPCMessage(NPC npc, Player player, String message) {
player.sendMessage(BlacksmithPlugin.getStringFormatter().replacePlaceholders(
BlacksmithTranslatableMessage.NPC_TALK_FORMAT, List.of("{npc}", "{message}"),
List.of(npc.getName(), ColorHelper.translateColorCodes(message, ColorConversion.RGB))));
}
}

View File

@@ -1,7 +1,6 @@
package net.knarcraft.blacksmith.formatting; package net.knarcraft.blacksmith.formatting;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.knarlib.formatting.StringFormatter;
/** /**
* An enum representing all item types used in messages * An enum representing all item types used in messages
@@ -17,11 +16,9 @@ public enum ItemType {
* @return <p>The name of this item type</p> * @return <p>The name of this item type</p>
*/ */
public String getItemTypeName() { public String getItemTypeName() {
StringFormatter stringFormatter = BlacksmithPlugin.getStringFormatter();
return switch (this) { return switch (this) {
case MATERIAL -> stringFormatter.getUnFormattedMessage(BlacksmithTranslatableMessage.ITEM_TYPE_MATERIAL); case MATERIAL -> new FormatBuilder(Translatable.ITEM_TYPE_MATERIAL).toString();
case ENCHANTMENT -> case ENCHANTMENT -> new FormatBuilder(Translatable.ITEM_TYPE_ENCHANTMENT).toString();
stringFormatter.getUnFormattedMessage(BlacksmithTranslatableMessage.ITEM_TYPE_ENCHANTMENT);
}; };
} }

View File

@@ -0,0 +1,40 @@
package net.knarcraft.blacksmith.formatting;
import net.citizensnpcs.api.npc.NPC;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* A formatter for formatting displayed messages
*/
public final class NPCFormatter {
private NPCFormatter() {
}
/**
* Sends a message from a blacksmith NPC to a player
*
* @param npc <p>The NPC sending the message</p>
* @param player <p>The player to send the message to</p>
* @param message <p>The message to send</p>
*/
public static void sendNPCMessage(@NotNull NPC npc, @NotNull Player player, @NotNull String message) {
player.sendMessage(new FormatBuilder(Translatable.NPC_TALK_FORMAT).replace("{npc}",
npc.getName()).replace("{message}", message).color().toString());
}
/**
* Sends a message from a blacksmith NPC to a player
*
* @param npc <p>The NPC sending the message</p>
* @param player <p>The player to send the message to</p>
* @param formatBuilder <p>The format builder to send</p>
*/
public static void sendNPCMessage(@NotNull NPC npc, @NotNull Player player, @NotNull FormatBuilder formatBuilder) {
sendNPCMessage(npc, player, formatBuilder.toString());
}
}

View File

@@ -1,6 +1,7 @@
package net.knarcraft.blacksmith.formatting; package net.knarcraft.blacksmith.formatting;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -53,8 +54,7 @@ public final class TimeFormatter {
* @return <p>Text describing the time interval</p> * @return <p>Text describing the time interval</p>
*/ */
private static String getMessageFromInterval(TimeInterval interval) { private static String getMessageFromInterval(TimeInterval interval) {
String text = BlacksmithPlugin.getStringFormatter().getUnFormattedMessage( String text = new FormatBuilder(Translatable.valueOf(interval.name())).toString();
BlacksmithTranslatableMessage.valueOf(interval.name()));
//Choose a random entry if a comma-separated list is provided //Choose a random entry if a comma-separated list is provided
if (text.contains(",")) { if (text.contains(",")) {

View File

@@ -1,19 +1,16 @@
package net.knarcraft.blacksmith.formatting; package net.knarcraft.blacksmith.formatting;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.knarlib.formatting.StringReplacer;
import net.knarcraft.knarlib.formatting.TranslatableMessage; import net.knarcraft.knarlib.formatting.TranslatableMessage;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List;
/** /**
* An enum containing all translatable global messages * An enum containing all translatable global messages
* *
* <p>This does not include NPC messages as they are configurable per-npc, and can be translated by changing the * <p>This does not include NPC messages as they are configurable per-npc, and can be translated by changing the
* default message values in the main config file.</p> * default message values in the main config file.</p>
*/ */
public enum BlacksmithTranslatableMessage implements TranslatableMessage { public enum Translatable implements TranslatableMessage {
/** /**
* The message displayed when a configuration value has been successfully changed * The message displayed when a configuration value has been successfully changed
@@ -143,18 +140,22 @@ public enum BlacksmithTranslatableMessage implements TranslatableMessage {
/** /**
* The format to use for formatting any message spoken by a blacksmith NPC * The format to use for formatting any message spoken by a blacksmith NPC
*/ */
NPC_TALK_FORMAT; NPC_TALK_FORMAT,
/**
* The text to display when explaining that a cost must be a positive double
*/
DOUBLE_COST_REQUIRED;
/** /**
* Gets the message to display when displaying the raw value of messages * Gets the message to display when displaying the raw value of messages
* *
* @param rawValue <p>The raw value to display</p> * @param rawValue <p>The raw value to display</p>
* @return <p>The message to display</p> * @return <p>The format builder to display</p>
*/ */
@NotNull @NotNull
public static String getRawValueMessage(@NotNull String rawValue) { public static FormatBuilder getRawValueMessage(@NotNull String rawValue) {
return BlacksmithPlugin.getStringFormatter().replacePlaceholder(BlacksmithTranslatableMessage.RAW_VALUE, return new FormatBuilder(Translatable.RAW_VALUE).replace("{rawValue}", rawValue);
"{rawValue}", rawValue);
} }
/** /**
@@ -162,12 +163,12 @@ public enum BlacksmithTranslatableMessage implements TranslatableMessage {
* *
* @param setting <p>The setting whose value has been changed</p> * @param setting <p>The setting whose value has been changed</p>
* @param newValue <p>The new value of the setting</p> * @param newValue <p>The new value of the setting</p>
* @return <p>The string to display to a user</p> * @return <p>The format builder to display to a user</p>
*/ */
@NotNull @NotNull
public static String getValueChangedMessage(@NotNull String setting, @NotNull String newValue) { public static FormatBuilder getValueChangedMessage(@NotNull String setting, @NotNull String newValue) {
return BlacksmithPlugin.getStringFormatter().replacePlaceholders(BlacksmithTranslatableMessage.VALUE_CHANGED, return new FormatBuilder(Translatable.VALUE_CHANGED).replace("{setting}", setting).
List.of("{setting}", "{newValue}"), List.of(setting, newValue)); replace("{newValue}", newValue);
} }
/** /**
@@ -177,18 +178,14 @@ public enum BlacksmithTranslatableMessage implements TranslatableMessage {
* @param itemType <p>The type of item changed ("material" or "enchantment")</p> * @param itemType <p>The type of item changed ("material" or "enchantment")</p>
* @param item <p>The item the setting was changed for (a material or an enchantment name)</p> * @param item <p>The item the setting was changed for (a material or an enchantment name)</p>
* @param newValue <p>The new value of the setting</p> * @param newValue <p>The new value of the setting</p>
* @return <p>The string to display to a user</p> * @return <p>The format builder to display to a user</p>
*/ */
@NotNull @NotNull
public static String getItemValueChangedMessage(@NotNull String setting, @NotNull ItemType itemType, public static FormatBuilder getItemValueChangedMessage(@NotNull String setting, @NotNull ItemType itemType,
@NotNull String item, @NotNull String newValue) { @NotNull String item, @NotNull String newValue) {
StringReplacer stringReplacer = new StringReplacer(BlacksmithPlugin.getStringFormatter().getUnFormattedMessage( return new FormatBuilder(Translatable.VALUE_FOR_ITEM_CHANGED).replace("{setting}", setting).
BlacksmithTranslatableMessage.VALUE_FOR_ITEM_CHANGED)); replace("{itemType}", itemType.getItemTypeName()).replace("{item}", item).
stringReplacer.add("{setting}", setting); replace("{newValue}", newValue);
stringReplacer.add("{itemType}", itemType.getItemTypeName());
stringReplacer.add("{item}", item);
stringReplacer.add("{newValue}", newValue);
return stringReplacer.replace();
} }
/** /**
@@ -196,13 +193,12 @@ public enum BlacksmithTranslatableMessage implements TranslatableMessage {
* *
* @param setting <p>The setting whose value is shown</p> * @param setting <p>The setting whose value is shown</p>
* @param defaultValue <p>The default value of the setting</p> * @param defaultValue <p>The default value of the setting</p>
* @return <p>The string to display to a user</p> * @return <p>The format builder to display to a user</p>
*/ */
@NotNull @NotNull
public static String getDefaultValueMessage(@NotNull String setting, @NotNull String defaultValue) { public static FormatBuilder getDefaultValueMessage(@NotNull String setting, @NotNull String defaultValue) {
return BlacksmithPlugin.getStringFormatter().replacePlaceholders(BlacksmithTranslatableMessage.DEFAULT_VALUE, return new FormatBuilder(Translatable.DEFAULT_VALUE).replace("{setting}", setting).
List.of("{setting}", "{defaultValue}"), replace("{defaultValue}", defaultValue);
List.of(setting, defaultValue));
} }
/** /**
@@ -210,13 +206,12 @@ public enum BlacksmithTranslatableMessage implements TranslatableMessage {
* *
* @param setting <p>The setting whose value is shown</p> * @param setting <p>The setting whose value is shown</p>
* @param currentValue <p>The current value of the setting</p> * @param currentValue <p>The current value of the setting</p>
* @return <p>The string to display to a user</p> * @return <p>The format builder to display to a user</p>
*/ */
@NotNull @NotNull
public static String getCurrentValueMessage(@NotNull String setting, @NotNull String currentValue) { public static FormatBuilder getCurrentValueMessage(@NotNull String setting, @NotNull String currentValue) {
return BlacksmithPlugin.getStringFormatter().replacePlaceholders(BlacksmithTranslatableMessage.CURRENT_VALUE, return new FormatBuilder(Translatable.CURRENT_VALUE).replace("{setting}", setting).
List.of("{setting}", "{currentValue}"), replace("{currentValue}", currentValue);
List.of(setting, currentValue));
} }
/** /**
@@ -226,24 +221,20 @@ public enum BlacksmithTranslatableMessage implements TranslatableMessage {
* @param itemType <p>The type of item shown ("material" or "enchantment")</p> * @param itemType <p>The type of item shown ("material" or "enchantment")</p>
* @param item <p>The item the setting is shown for (a material or an enchantment name)</p> * @param item <p>The item the setting is shown for (a material or an enchantment name)</p>
* @param currentValue <p>The current value of the setting</p> * @param currentValue <p>The current value of the setting</p>
* @return <p>The string to display to a user</p> * @return <p>The format builder to display to a user</p>
*/ */
@NotNull @NotNull
public static String getItemCurrentValueMessage(@NotNull String setting, @NotNull ItemType itemType, public static FormatBuilder getItemCurrentValueMessage(@NotNull String setting, @NotNull ItemType itemType,
@NotNull String item, @NotNull String currentValue) { @NotNull String item, @NotNull String currentValue) {
StringReplacer stringReplacer = new StringReplacer(BlacksmithPlugin.getStringFormatter().getUnFormattedMessage( return new FormatBuilder(Translatable.CURRENT_VALUE_FOR_ITEM).replace("{setting}", setting).
BlacksmithTranslatableMessage.CURRENT_VALUE_FOR_ITEM)); replace("{itemType}", itemType.getItemTypeName()).replace("{item}", item).
stringReplacer.add("{setting}", setting); replace("{currentValue}", currentValue);
stringReplacer.add("{itemType}", itemType.getItemTypeName());
stringReplacer.add("{item}", item);
stringReplacer.add("{currentValue}", currentValue);
return stringReplacer.replace();
} }
@Override @Override
@NotNull @NotNull
public TranslatableMessage[] getAllMessages() { public TranslatableMessage[] getAllMessages() {
return BlacksmithTranslatableMessage.values(); return Translatable.values();
} }
} }

View File

@@ -2,18 +2,17 @@ package net.knarcraft.blacksmith.listener;
import net.citizensnpcs.api.event.NPCRightClickEvent; import net.citizensnpcs.api.event.NPCRightClickEvent;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.blacksmith.trait.BlacksmithTrait; import net.knarcraft.blacksmith.trait.BlacksmithTrait;
import net.knarcraft.blacksmith.trait.CustomTrait; import net.knarcraft.blacksmith.trait.CustomTrait;
import net.knarcraft.blacksmith.trait.ScrapperTrait; import net.knarcraft.blacksmith.trait.ScrapperTrait;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.logging.Level;
/** /**
* A listener for detecting and handling a Blacksmith being right-clicked * A listener for detecting and handling a Blacksmith being right-clicked
*/ */
@@ -21,7 +20,7 @@ public class NPCClickListener implements Listener {
@EventHandler @EventHandler
public void onRightClick(@NotNull NPCRightClickEvent event) { public void onRightClick(@NotNull NPCRightClickEvent event) {
//We only care about blacksmiths //We only care about blacksmiths and scrappers
if (event.getNPC().hasTrait(BlacksmithTrait.class)) { if (event.getNPC().hasTrait(BlacksmithTrait.class)) {
handleNPCClick(event, event.getNPC().getTraitNullable(BlacksmithTrait.class)); handleNPCClick(event, event.getNPC().getTraitNullable(BlacksmithTrait.class));
} else if (event.getNPC().hasTrait(ScrapperTrait.class)) { } else if (event.getNPC().hasTrait(ScrapperTrait.class)) {
@@ -37,7 +36,7 @@ public class NPCClickListener implements Listener {
*/ */
private void handleNPCClick(@NotNull NPCRightClickEvent event, @Nullable CustomTrait<?> customTrait) { private void handleNPCClick(@NotNull NPCRightClickEvent event, @Nullable CustomTrait<?> customTrait) {
if (customTrait == null) { if (customTrait == null) {
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Could not get trait from NPC!"); BlacksmithPlugin.warn("Could not get trait from NPC!");
return; return;
} }
@@ -46,8 +45,7 @@ public class NPCClickListener implements Listener {
//Permission check //Permission check
if (!player.hasPermission("blacksmith.use")) { if (!player.hasPermission("blacksmith.use")) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(player, new FormatBuilder(Translatable.PERMISSION_DENIED).error(player);
BlacksmithTranslatableMessage.PERMISSION_DENIED);
return; return;
} }

View File

@@ -1,11 +1,14 @@
package net.knarcraft.blacksmith.manager; package net.knarcraft.blacksmith.manager;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.SalvageMethod;
import net.knarcraft.blacksmith.config.blacksmith.GlobalBlacksmithSettings; import net.knarcraft.blacksmith.config.blacksmith.GlobalBlacksmithSettings;
import net.knarcraft.blacksmith.config.scrapper.GlobalScrapperSettings; import net.knarcraft.blacksmith.config.scrapper.GlobalScrapperSettings;
import net.knarcraft.blacksmith.property.SalvageMethod;
import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.ItemHelper;
import net.knarcraft.knarlib.formatting.StringReplacer;
import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.Economy;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -14,9 +17,6 @@ import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.ServicesManager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* A class which deals with everything economy * A class which deals with everything economy
*/ */
@@ -32,15 +32,14 @@ public class EconomyManager {
* Sets up Vault economy support * Sets up Vault economy support
* *
* @param servicesManager <p>The services manager to use for finding a Vault provider</p> * @param servicesManager <p>The services manager to use for finding a Vault provider</p>
* @param logger <p>The logger to use for logging</p>
* @return <p>True if Vault was successfully set up</p> * @return <p>True if Vault was successfully set up</p>
*/ */
public static boolean setUp(@NotNull ServicesManager servicesManager, @NotNull Logger logger) { public static boolean setUp(@NotNull ServicesManager servicesManager) {
//If already set up, there is nothing to do //If already set up, there is nothing to do
if (economy != null) { if (economy != null) {
return true; return true;
} }
return setupVault(servicesManager, logger); return setupVault(servicesManager);
} }
/** /**
@@ -58,10 +57,12 @@ public class EconomyManager {
* *
* @param player <p>The player holding an item</p> * @param player <p>The player holding an item</p>
* @param salvageMethod <p>The salvage method to check</p> * @param salvageMethod <p>The salvage method to check</p>
* @param item <p>The item to be salvaged</p>
* @return <p>Whether the player cannot pay for the salvage</p> * @return <p>Whether the player cannot pay for the salvage</p>
*/ */
public static boolean cannotPayForSalvage(@NotNull Player player, @NotNull SalvageMethod salvageMethod) { public static boolean cannotPayForSalvage(@NotNull Player player, @NotNull SalvageMethod salvageMethod, @NotNull ItemStack item) {
return economy.getBalance(player) - getSalvageCost(salvageMethod) < 0; // TODO: Account for advanced cost options
return economy.getBalance(player) - getSalvageCost(salvageMethod, item, player) < 0;
} }
/** /**
@@ -80,26 +81,61 @@ public class EconomyManager {
* Gets the human-readable cost of salvaging an item * Gets the human-readable cost of salvaging an item
* *
* @param salvageMethod <p>The salvage method to get the cost for</p> * @param salvageMethod <p>The salvage method to get the cost for</p>
* @param item <p>The item to be salvaged</p>
* @param player <p>The player to provide the cost to</p>
* @return <p>The formatted cost</p> * @return <p>The formatted cost</p>
*/ */
@NotNull @NotNull
public static String formatSalvageCost(@NotNull SalvageMethod salvageMethod) { public static String formatSalvageCost(@NotNull SalvageMethod salvageMethod, @NotNull ItemStack item,
return economy.format(getSalvageCost(salvageMethod)); @NotNull Player player) {
return economy.format(getSalvageCost(salvageMethod, item, player));
}
/**
* Gets whether the given player has enough money to pay the given cost
*
* @param player <p>The player to check</p>
* @param cost <p>The required cost</p>
* @return <p>True if the player has enough money to cover the cost</p>
*/
public static boolean hasEnough(@NotNull Player player, double cost) {
return economy.getBalance(player) >= cost;
}
/**
* Formats a number as an economy cost
*
* @param cost <p>The cost to format</p>
* @return <p>The formatted cost</p>
*/
@NotNull
public static String format(double cost) {
return economy.format(cost);
} }
/** /**
* Gets the cost of salvaging using the specified method * Gets the cost of salvaging using the specified method
* *
* @param salvageMethod <p>The salvage method to get cost for</p> * @param salvageMethod <p>The salvage method to get cost for</p>
* @param item <p>The item to be salvaged</p>
* @return <p>The salvage cost</p> * @return <p>The salvage cost</p>
*/ */
private static double getSalvageCost(@NotNull SalvageMethod salvageMethod) { private static double getSalvageCost(@NotNull SalvageMethod salvageMethod, @NotNull ItemStack item,
@NotNull Player player) {
GlobalScrapperSettings settings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings(); GlobalScrapperSettings settings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings();
return switch (salvageMethod) { double baseCost = switch (salvageMethod) {
case SALVAGE -> settings.getSalvageCost(); case SALVAGE, EXTENDED_SALVAGE -> settings.getSalvageCost();
case NETHERITE -> settings.getNetheriteSalvageCost(); case NETHERITE -> settings.getNetheriteSalvageCost();
case ARMOR_TRIM -> settings.getArmorTrimSalvageCost(); case ARMOR_TRIM -> settings.getArmorTrimSalvageCost();
case ENCHANTED_BOOK -> getEnchantedBookSalvageCost(item);
}; };
StringReplacer replacer = new StringReplacer(settings.getSalvageCostIncrease());
replacer.add("{cost}", String.valueOf(baseCost));
replacer.add("{timesUsed}", String.valueOf(PlayerUsageManager.getUsages(player,
System.currentTimeMillis() - 3600000)));
Expression expression = new ExpressionBuilder(replacer.replace()).build();
return Math.max(expression.evaluate(), baseCost);
} }
/** /**
@@ -123,12 +159,26 @@ public class EconomyManager {
* @param salvageMethod <p>The salvage method to withdraw for</p> * @param salvageMethod <p>The salvage method to withdraw for</p>
*/ */
public static void withdrawScrapper(@NotNull Player player, @NotNull SalvageMethod salvageMethod) { public static void withdrawScrapper(@NotNull Player player, @NotNull SalvageMethod salvageMethod) {
double cost = getSalvageCost(salvageMethod); double cost = getSalvageCost(salvageMethod, player.getInventory().getItemInMainHand(), player);
if (cost > 0) { if (cost > 0) {
economy.withdrawPlayer(player, cost); economy.withdrawPlayer(player, cost);
} }
} }
/**
* Withdraws money from a player
*
* @param player <p>The player to withdraw from</p>
* @param monetaryCost <p>The cost to withdraw</p>
* @throws IllegalArgumentException <p>If a negative cost is given</p>
*/
public static void withdraw(@NotNull Player player, double monetaryCost) {
if (monetaryCost < 0) {
throw new IllegalArgumentException("Cannot withdraw a negative amount");
}
economy.withdrawPlayer(player, monetaryCost);
}
/** /**
* Gets the cost of the item in the given player's main hand * Gets the cost of the item in the given player's main hand
* *
@@ -187,14 +237,31 @@ public class EconomyManager {
return price; return price;
} }
/**
* Gets the cost of scrapping an enchanted book
*
* @param item <p>The enchanted book to calculate cost for</p>
* @return <p>The cost of scrapping the enchanted book</p>
*/
private static double getEnchantedBookSalvageCost(@NotNull ItemStack item) {
// TODO: Properly implement this
/*GlobalScrapperSettings settings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings();
double cost = settings.getEnchantedBookSalvageCost();
if (settings.multiplyEnchantedBookSalvageCost()) {
cost *= SalvageHelper.getEnchantmentCount(item) - 1;
}
return SalvageHelper.getEnchantmentCount(item) * cost;*/
return 1000000;
}
/** /**
* Sets up Vault for economy * Sets up Vault for economy
* *
* @param servicesManager <p>The services manager to use for finding a Vault provider</p> * @param servicesManager <p>The services manager to use for finding a Vault provider</p>
* @param logger <p>The logger to use for logging</p>
* @return <p>True if Vault was successfully set up</p> * @return <p>True if Vault was successfully set up</p>
*/ */
private static boolean setupVault(@NotNull ServicesManager servicesManager, @NotNull Logger logger) { private static boolean setupVault(@NotNull ServicesManager servicesManager) {
// Setup Vault // Setup Vault
RegisteredServiceProvider<Economy> economyProvider = servicesManager.getRegistration(Economy.class); RegisteredServiceProvider<Economy> economyProvider = servicesManager.getRegistration(Economy.class);
if (economyProvider != null) { if (economyProvider != null) {
@@ -202,7 +269,7 @@ public class EconomyManager {
return true; return true;
} else { } else {
// Disable if no economy plugin was found // Disable if no economy plugin was found
logger.log(Level.SEVERE, "Failed to load an economy plugin. Disabling..."); BlacksmithPlugin.error("Failed to load an economy plugin. Disabling...");
return false; return false;
} }
} }

View File

@@ -0,0 +1,75 @@
package net.knarcraft.blacksmith.manager;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A manager for keeping track of players using scrappers
*/
public class PlayerUsageManager {
private static final Map<Player, Set<Long>> playerUsages = new HashMap<>();
/**
* Register that a player has used a scrapper
*
* @param player <p>The player using a scrapper</p>
*/
public static void registerUsage(@NotNull Player player) {
playerUsages.putIfAbsent(player, new HashSet<>());
Set<Long> usages = playerUsages.get(player);
usages.add(System.currentTimeMillis());
}
/**
* Gets how many times the given player has used a scrapper after the given timestamp
*
* @param player <p>The player to check usages for</p>
* @param afterTimestamp <p>The timestamp of when to start looking for usages</p>
* @return <p>The number of scrapper uses for the given player</p>
*/
public static int getUsages(@NotNull Player player, @NotNull Long afterTimestamp) {
Set<Long> usages = playerUsages.get(player);
if (usages == null) {
return 0;
}
int uses = 0;
Set<Long> expired = new HashSet<>();
for (Long usageTime : usages) {
if (usageTime < afterTimestamp) {
expired.add(usageTime);
} else {
uses++;
}
}
// Remove expired data
usages.removeAll(expired);
return uses;
}
/**
* Removes all expired timestamps
*
* @param afterTimestamp <p>The timestamp for the earliest time of valid timestamps</p>
*/
public static void removeExpiredData(@NotNull Long afterTimestamp) {
for (Set<Long> usageSet : playerUsages.values()) {
Set<Long> expired = new HashSet<>();
for (Long item : usageSet) {
if (item < afterTimestamp) {
expired.add(item);
}
}
usageSet.removeAll(expired);
}
}
}

View File

@@ -0,0 +1,49 @@
package net.knarcraft.blacksmith.property;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The type of costs applicable to an advanced cost configuration
*/
public enum CostType {
/**
* A monetary cost from an economy plugin
*/
ECONOMY,
/**
* A permission requirement, rather than a cost
*/
PERMISSION,
/**
* In-game experience levels
*/
EXP,
/**
* A specific item is consumed
*/
ITEM,
;
/**
* Parses a text string denoting a cost type
*
* @param input <p>The input to parse</p>
* @return <p>The parsed cost type, or null if not matched</p>
*/
@Nullable
public static CostType parse(@NotNull String input) {
String normalized = input.trim().toUpperCase();
for (CostType costType : CostType.values()) {
if (normalized.equals(costType.name())) {
return costType;
}
}
return null;
}
}

View File

@@ -1,4 +1,4 @@
package net.knarcraft.blacksmith.config; package net.knarcraft.blacksmith.property;
/** /**
* A representation of the different ways an item can be salvaged * A representation of the different ways an item can be salvaged
@@ -10,6 +10,11 @@ public enum SalvageMethod {
*/ */
SALVAGE, SALVAGE,
/**
* Salvaging unrepairable items normally by returning the item's crafting recipe, but with unrestricted
*/
EXTENDED_SALVAGE,
/** /**
* Removing the armor trim from an item * Removing the armor trim from an item
*/ */
@@ -20,4 +25,9 @@ public enum SalvageMethod {
*/ */
NETHERITE, NETHERITE,
/**
* Splitting enchantments of an enchanted book
*/
ENCHANTED_BOOK,
} }

View File

@@ -0,0 +1,23 @@
package net.knarcraft.blacksmith.property;
/**
* The state of trying to find salvage for an item
*/
public enum SalvageState {
/**
* Found useful salvage that can be given to a player
*/
FOUND_SALVAGE,
/**
* The item cannot be salvaged using the used method
*/
INCORRECT_METHOD,
/**
* While the method was correct, no useful salvage was created
*/
NO_SALVAGE,
}

View File

@@ -4,9 +4,10 @@ import net.citizensnpcs.api.util.DataKey;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.blacksmith.BlacksmithNPCSettings; import net.knarcraft.blacksmith.config.blacksmith.BlacksmithNPCSettings;
import net.knarcraft.blacksmith.config.blacksmith.BlacksmithSetting; import net.knarcraft.blacksmith.config.blacksmith.BlacksmithSetting;
import net.knarcraft.blacksmith.formatting.NPCFormatter;
import net.knarcraft.blacksmith.manager.EconomyManager; import net.knarcraft.blacksmith.manager.EconomyManager;
import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.ItemHelper;
import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.FormatBuilder;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -15,12 +16,11 @@ import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.sendNPCMessage;
/** /**
* The class representing the Blacksmith NPC trait * The class representing the Blacksmith NPC trait
*/ */
public class BlacksmithTrait extends CustomTrait<BlacksmithSetting> { public class BlacksmithTrait extends CustomTrait<BlacksmithSetting> {
private final BlacksmithNPCSettings config; private final BlacksmithNPCSettings config;
/** /**
@@ -67,6 +67,11 @@ public class BlacksmithTrait extends CustomTrait<BlacksmithSetting> {
@Override @Override
public void startSession(@NotNull Player player) { public void startSession(@NotNull Player player) {
ItemStack hand = player.getInventory().getItemInMainHand(); ItemStack hand = player.getInventory().getItemInMainHand();
if (hand.getType().isAir()) {
NPCFormatter.sendNPCMessage(this.npc, player, config.getNoItemMessage());
return;
}
//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();
@@ -75,26 +80,31 @@ public class BlacksmithTrait extends CustomTrait<BlacksmithSetting> {
(!reforgeAbleItems.isEmpty() && !reforgeAbleItems.contains(hand.getType())); (!reforgeAbleItems.isEmpty() && !reforgeAbleItems.contains(hand.getType()));
if (notHoldingAnvil && notHoldingRepairable) { if (notHoldingAnvil && notHoldingRepairable) {
String invalidMessage = StringFormatter.replacePlaceholder(config.getInvalidItemMessage(), String invalidMessage = new FormatBuilder(config.getInvalidItemMessage()).replace("{title}",
"{title}", config.getBlacksmithTitle()); config.getBlacksmithTitle()).toString();
sendNPCMessage(this.npc, player, invalidMessage); NPCFormatter.sendNPCMessage(this.npc, player, invalidMessage);
return; return;
} }
if (ItemHelper.getDamage(hand) == 0 && !ItemHelper.isAnvil(hand.getType(), true)) { if (ItemHelper.getDamage(hand) == 0 && !ItemHelper.isAnvil(hand.getType(), true)) {
sendNPCMessage(this.npc, player, config.getNotDamagedMessage()); NPCFormatter.sendNPCMessage(this.npc, player, config.getNotDamagedMessage());
return; return;
} }
//Start a new reforge session for the player //Start a new reforge session for the player
currentSessionStartTime = System.currentTimeMillis(); currentSessionStartTime = System.currentTimeMillis();
session = new ReforgeSession(this, player, npc, config); try {
session = new ReforgeSession(this, player, npc, config);
} catch (IllegalArgumentException exception) {
BlacksmithPlugin.error(exception.getMessage());
return;
}
//Tell the player the cost of repairing the item //Tell the player the cost of repairing the item
String cost = EconomyManager.formatBlacksmithCost(player); String cost = EconomyManager.formatBlacksmithCost(player);
String itemName = hand.getType().name().toLowerCase().replace('_', ' '); String itemName = hand.getType().name().toLowerCase().replace('_', ' ');
sendNPCMessage(this.npc, player, config.getCostMessage().replace("{cost}", cost).replace("{item}", NPCFormatter.sendNPCMessage(this.npc, player, config.getCostMessage().replace("{cost}", cost).
itemName)); replace("{item}", itemName));
} }
@Override @Override

View File

@@ -2,15 +2,18 @@ package net.knarcraft.blacksmith.trait;
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.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.Setting; import net.knarcraft.blacksmith.config.Setting;
import net.knarcraft.blacksmith.config.Settings; import net.knarcraft.blacksmith.config.Settings;
import net.knarcraft.blacksmith.config.TraitSettings; import net.knarcraft.blacksmith.config.TraitSettings;
import net.knarcraft.blacksmith.formatting.NPCFormatter;
import net.knarcraft.blacksmith.formatting.TimeFormatter; import net.knarcraft.blacksmith.formatting.TimeFormatter;
import net.knarcraft.blacksmith.manager.EconomyManager; import net.knarcraft.blacksmith.manager.EconomyManager;
import net.knarcraft.blacksmith.util.SalvageHelper; import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.knarlib.formatting.StringFormatter; 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.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -19,11 +22,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.Calendar; import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.sendNPCMessage;
/** /**
* A custom trait that utilizes sessions * A custom trait that utilizes sessions
*/ */
@@ -76,7 +76,7 @@ public abstract class CustomTrait<K extends Setting> extends Trait {
public void continueSession(@NotNull Player player) { public void continueSession(@NotNull Player player) {
//Another player is using the blacksmith //Another player is using the blacksmith
if (session.isNotInSession(player)) { if (session.isNotInSession(player)) {
sendNPCMessage(this.npc, player, config.getBusyWithPlayerMessage()); NPCFormatter.sendNPCMessage(this.npc, player, config.getBusyWithPlayerMessage());
return; return;
} }
@@ -84,8 +84,8 @@ public abstract class CustomTrait<K extends Setting> extends Trait {
if (session.isRunning()) { if (session.isRunning()) {
int timeRemaining = (int) ((session.getFinishTime() - System.currentTimeMillis()) / 1000); int timeRemaining = (int) ((session.getFinishTime() - System.currentTimeMillis()) / 1000);
boolean showExactTime = showExactTime(); boolean showExactTime = showExactTime();
sendNPCMessage(this.npc, player, StringFormatter.replacePlaceholder(config.getBusyWorkingMessage(), NPCFormatter.sendNPCMessage(this.npc, player, new FormatBuilder(config.getBusyWorkingMessage()).
"{time}", TimeFormatter.formatTime(showExactTime, timeRemaining))); replace("{time}", TimeFormatter.formatTime(showExactTime, timeRemaining)));
return; return;
} }
if (session.isSessionInvalid()) { if (session.isSessionInvalid()) {
@@ -146,16 +146,21 @@ public abstract class CustomTrait<K extends Setting> extends Trait {
if (!calendar.after(coolDowns.get(playerId))) { if (!calendar.after(coolDowns.get(playerId))) {
int secondDifference = (int) (coolDowns.get(playerId).getTimeInMillis() - calendar.getTimeInMillis()) / 1000; int secondDifference = (int) (coolDowns.get(playerId).getTimeInMillis() - calendar.getTimeInMillis()) / 1000;
boolean exactTime = showExactTime(); boolean exactTime = showExactTime();
sendNPCMessage(this.npc, player, StringFormatter.replacePlaceholder(config.getCoolDownUnexpiredMessage(), NPCFormatter.sendNPCMessage(this.npc, player, new FormatBuilder(config.getCoolDownUnexpiredMessage()).
"{time}", TimeFormatter.formatTime(exactTime, secondDifference))); replace("{time}", TimeFormatter.formatTime(exactTime, secondDifference)));
return false; return false;
} }
coolDowns.remove(playerId); coolDowns.remove(playerId);
} }
//If already in a session, but the player has failed to interact, and left the blacksmith, allow a new session //If already in a session, but the player has failed to interact, and left the blacksmith, allow a new session
Entity entity = npc.getEntity();
if (entity == null) {
BlacksmithPlugin.error("NPC session could not be started, as the NPC does not have a valid entity");
return false;
}
if (session != null && !session.isRunning() && (System.currentTimeMillis() > currentSessionStartTime + 10 * 1000 || if (session != null && !session.isRunning() && (System.currentTimeMillis() > currentSessionStartTime + 10 * 1000 ||
this.npc.getEntity().getLocation().distance(session.getPlayer().getLocation()) > 20)) { entity.getLocation().distance(session.getPlayer().getLocation()) > 20)) {
session = null; session = null;
} }
return true; return true;
@@ -175,7 +180,7 @@ public abstract class CustomTrait<K extends Setting> extends Trait {
* @param player <p>The player that initiated the reforge</p> * @param player <p>The player that initiated the reforge</p>
*/ */
private void startMainAction(@NotNull NPC npc, @NotNull Player player) { private void startMainAction(@NotNull NPC npc, @NotNull Player player) {
sendNPCMessage(this.npc, player, config.getStartWorkingMessage()); NPCFormatter.sendNPCMessage(this.npc, player, config.getStartWorkingMessage());
boolean isBlacksmith = this instanceof BlacksmithTrait; boolean isBlacksmith = this instanceof BlacksmithTrait;
@@ -184,21 +189,26 @@ public abstract class CustomTrait<K extends Setting> extends Trait {
} else if (this.session instanceof SalvageSession salvageSession) { } else if (this.session instanceof SalvageSession salvageSession) {
EconomyManager.withdrawScrapper(player, salvageSession.salvageMethod); EconomyManager.withdrawScrapper(player, salvageSession.salvageMethod);
} }
session.scheduleAction();
PlayerInventory playerInventory = player.getInventory(); PlayerInventory playerInventory = player.getInventory();
ItemStack heldItem = player.getInventory().getItemInMainHand(); ItemStack heldItem = player.getInventory().getItemInMainHand();
session.scheduleAction();
//Display the item in the NPC's hand //Display the item in the NPC's hand
if (npc.getEntity() instanceof Player) { Entity entity = npc.getEntity();
((Player) npc.getEntity()).getInventory().setItemInMainHand(heldItem); if (entity instanceof Player playerNPC) {
} else { playerNPC.getInventory().setItemInMainHand(heldItem);
Objects.requireNonNull(((LivingEntity) npc.getEntity()).getEquipment()).setItemInMainHand(heldItem); } else if (entity instanceof LivingEntity) {
EntityEquipment equipment = ((LivingEntity) entity).getEquipment();
if (equipment != null) {
((LivingEntity) entity).getEquipment().setItemInMainHand(heldItem);
} else {
BlacksmithPlugin.warn("Failed to update NPC item, as its equipment was irretrievable.");
}
} }
//Remove the item from the player's inventory //Remove the item from the player's inventory
if (!isBlacksmith) { if (this.session instanceof SalvageSession salvageSession) {
// For scrappers, just reduce the amounts of items, unless the remaining stack is salvaged // For scrappers, just reduce the amounts of items, unless the remaining stack is salvaged
int amount = SalvageHelper.getRequiredAmountForSalvage(player.getServer(), heldItem); int amount = salvageSession.getItemsConsumed();
if (amount != heldItem.getAmount()) { if (amount != heldItem.getAmount()) {
heldItem.setAmount(heldItem.getAmount() - amount); heldItem.setAmount(heldItem.getAmount() - amount);
playerInventory.setItemInMainHand(heldItem); playerInventory.setItemInMainHand(heldItem);

View File

@@ -3,26 +3,27 @@ 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.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.blacksmith.BlacksmithNPCSettings; import net.knarcraft.blacksmith.config.blacksmith.BlacksmithNPCSettings;
import net.knarcraft.blacksmith.event.BlacksmithReforgeFailEvent;
import net.knarcraft.blacksmith.event.BlacksmithReforgeSucceedEvent;
import net.knarcraft.blacksmith.formatting.NPCFormatter;
import net.knarcraft.blacksmith.manager.EconomyManager; import net.knarcraft.blacksmith.manager.EconomyManager;
import net.knarcraft.blacksmith.util.InputParsingHelper; import net.knarcraft.blacksmith.util.InputParsingHelper;
import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.ItemHelper;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Registry; import org.bukkit.Registry;
import org.bukkit.Sound;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Random; import java.util.Random;
import java.util.logging.Level;
import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.sendNPCMessage;
/** /**
* A representation of the session between a player and a blacksmith * A representation of the session between a player and a blacksmith
@@ -30,7 +31,6 @@ import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.send
public class ReforgeSession extends Session implements Runnable { public class ReforgeSession extends Session implements Runnable {
private final BlacksmithTrait blacksmithTrait; private final BlacksmithTrait blacksmithTrait;
private final NPC npc;
private final ItemStack itemToReforge; private final ItemStack itemToReforge;
private final BlacksmithNPCSettings config; private final BlacksmithNPCSettings config;
private static List<String> enchantments = null; private static List<String> enchantments = null;
@@ -45,9 +45,8 @@ public class ReforgeSession extends Session implements Runnable {
*/ */
ReforgeSession(@NotNull BlacksmithTrait blacksmithTrait, @NotNull Player player, @NotNull NPC npc, ReforgeSession(@NotNull BlacksmithTrait blacksmithTrait, @NotNull Player player, @NotNull NPC npc,
@NotNull BlacksmithNPCSettings config) { @NotNull BlacksmithNPCSettings config) {
super(player); super(player, npc);
this.blacksmithTrait = blacksmithTrait; this.blacksmithTrait = blacksmithTrait;
this.npc = npc;
this.itemToReforge = player.getInventory().getItemInMainHand(); this.itemToReforge = player.getInventory().getItemInMainHand();
this.config = config; this.config = config;
@@ -60,6 +59,8 @@ public class ReforgeSession extends Session implements Runnable {
enchantments = new ArrayList<>(); enchantments = new ArrayList<>();
for (Enchantment enchantment : enchantmentRegistry) { for (Enchantment enchantment : enchantmentRegistry) {
// Note: While depreciated in Spigot, the new method is not available for Paper
//noinspection deprecation
enchantments.add(enchantment.getKey().getKey()); enchantments.add(enchantment.getKey().getKey());
} }
} }
@@ -70,12 +71,12 @@ public class ReforgeSession extends Session implements Runnable {
// Prevent player from switching items during session // Prevent player from switching items during session
ItemStack itemInHand = this.player.getInventory().getItemInMainHand(); ItemStack itemInHand = this.player.getInventory().getItemInMainHand();
if (!itemToReforge.equals(itemInHand)) { if (!itemToReforge.equals(itemInHand)) {
sendNPCMessage(this.npc, this.player, this.config.getItemChangedMessage()); NPCFormatter.sendNPCMessage(this.npc, this.player, this.config.getItemChangedMessage());
return true; return true;
} }
// The player is unable to pay // The player is unable to pay
if (EconomyManager.cannotPayForHeldItemReforge(this.player)) { if (EconomyManager.cannotPayForHeldItemReforge(this.player)) {
sendNPCMessage(this.npc, this.player, this.config.getInsufficientFundsMessage()); NPCFormatter.sendNPCMessage(this.npc, this.player, this.config.getInsufficientFundsMessage());
return true; return true;
} }
return false; return false;
@@ -92,18 +93,30 @@ public class ReforgeSession extends Session implements Runnable {
} }
} }
@Override
protected @NotNull Material getCraftingStation() {
return Material.ANVIL;
}
/** /**
* Runs the actual reforge which fixes the item and gives it back to the player * Runs the actual reforge which fixes the item and gives it back to the player
*/ */
@Override @Override
public void run() { public void run() {
sendNPCMessage(this.npc, this.player, reforgeItem() ? this.config.getSuccessMessage() : this.config.getFailMessage()); boolean success = reforgeItem();
NPCFormatter.sendNPCMessage(this.npc, this.player, success ? this.config.getSuccessMessage() : this.config.getFailMessage());
playSound(success ? Sound.BLOCK_ANVIL_USE : Sound.ENTITY_VILLAGER_NO);
//Stop the reforged item from displaying in the blacksmith's hand //Stop the reforged item from displaying in the blacksmith's hand
if (this.npc.getEntity() instanceof Player) { if (this.entity instanceof Player playerNPC) {
((Player) this.npc.getEntity()).getInventory().setItemInMainHand(null); playerNPC.getInventory().setItemInMainHand(null);
} else { } else if (this.entity instanceof LivingEntity) {
Objects.requireNonNull(((LivingEntity) this.npc.getEntity()).getEquipment()).setItemInMainHand(null); EntityEquipment equipment = ((LivingEntity) this.entity).getEquipment();
if (equipment != null) {
equipment.setItemInMainHand(null);
} else {
BlacksmithPlugin.warn("Failed to clear Blacksmith item, as its equipment was irretrievable.");
}
} }
//Give the item back to the player //Give the item back to the player
@@ -122,8 +135,7 @@ public class ReforgeSession extends Session implements Runnable {
* Gives the reforged item back to the player * Gives the reforged item back to the player
*/ */
private void giveReforgedItem() { private void giveReforgedItem() {
giveResultingItem(this.config.getMaxReforgeDelay() > 0, this.config.getDropItem(), this.npc, giveResultingItem(this.config.getMaxReforgeDelay() > 0, this.config.getDropItem(), this.itemToReforge);
this.itemToReforge);
} }
/** /**
@@ -134,9 +146,11 @@ public class ReforgeSession extends Session implements Runnable {
private boolean reforgeItem() { private boolean reforgeItem() {
if (random.nextInt(100) < this.config.getFailChance()) { if (random.nextInt(100) < this.config.getFailChance()) {
failReforge(); failReforge();
BlacksmithPlugin.getInstance().callEvent(new BlacksmithReforgeFailEvent(this.npc, this.entity, this.player));
return false; return false;
} else { } else {
succeedReforge(); succeedReforge();
BlacksmithPlugin.getInstance().callEvent(new BlacksmithReforgeSucceedEvent(this.npc, this.entity, this.player));
return true; return true;
} }
} }
@@ -147,8 +161,7 @@ public class ReforgeSession extends Session implements Runnable {
private void succeedReforge() { private void succeedReforge() {
// Remove any damage done to the item // Remove any damage done to the item
if (ItemHelper.updateDamage(this.itemToReforge, 0)) { if (ItemHelper.updateDamage(this.itemToReforge, 0)) {
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to update damage for " + BlacksmithPlugin.warn("Unable to update damage for " + this.itemToReforge);
this.itemToReforge);
} }
//Replace damaged anvils with a normal anvil //Replace damaged anvils with a normal anvil

View File

@@ -1,31 +1,38 @@
package net.knarcraft.blacksmith.trait; package net.knarcraft.blacksmith.trait;
import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPC;
import net.knarcraft.blacksmith.config.SalvageMethod; import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.scrapper.ScrapperNPCSettings; import net.knarcraft.blacksmith.config.scrapper.ScrapperNPCSettings;
import net.knarcraft.blacksmith.event.ScrapperSalvageFailEvent;
import net.knarcraft.blacksmith.event.ScrapperSalvageSucceedEvent;
import net.knarcraft.blacksmith.formatting.NPCFormatter;
import net.knarcraft.blacksmith.manager.EconomyManager; import net.knarcraft.blacksmith.manager.EconomyManager;
import net.knarcraft.blacksmith.manager.PlayerUsageManager;
import net.knarcraft.blacksmith.property.SalvageMethod;
import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.ItemHelper;
import net.knarcraft.blacksmith.util.SalvageHelper; import net.knarcraft.blacksmith.util.SalvageHelper;
import net.knarcraft.knarlib.formatting.StringReplacer;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Random; import java.util.Random;
import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.sendNPCMessage;
/** /**
* A representation of the session between a player and a scrapper * A representation of the session between a player and a scrapper
*/ */
public class SalvageSession extends Session implements Runnable { public class SalvageSession extends Session implements Runnable {
private final ScrapperTrait scrapperTrait; private final ScrapperTrait scrapperTrait;
private final NPC npc;
private final ItemStack itemToSalvage; private final ItemStack itemToSalvage;
private final ScrapperNPCSettings config; private final ScrapperNPCSettings config;
private final List<ItemStack> salvage; private final List<ItemStack> salvage;
@@ -44,12 +51,11 @@ public class SalvageSession extends Session implements Runnable {
* @param salvageMethod <p>The salvage method performed in this session</p> * @param salvageMethod <p>The salvage method performed in this session</p>
* @param itemsConsumed <p>The number of items actually consumed, in case a salvaging fails</p> * @param itemsConsumed <p>The number of items actually consumed, in case a salvaging fails</p>
*/ */
SalvageSession(@NotNull ScrapperTrait scrapperTrait, @NotNull Player player, @NotNull NPC npc, public SalvageSession(@NotNull ScrapperTrait scrapperTrait, @NotNull Player player, @NotNull NPC npc,
@NotNull ScrapperNPCSettings config, @NotNull List<ItemStack> salvage, @NotNull ScrapperNPCSettings config, @NotNull List<ItemStack> salvage,
@NotNull SalvageMethod salvageMethod, int itemsConsumed) { @NotNull SalvageMethod salvageMethod, int itemsConsumed) {
super(player); super(player, npc);
this.scrapperTrait = scrapperTrait; this.scrapperTrait = scrapperTrait;
this.npc = npc;
this.itemToSalvage = player.getInventory().getItemInMainHand().clone(); this.itemToSalvage = player.getInventory().getItemInMainHand().clone();
this.config = config; this.config = config;
this.salvage = salvage; this.salvage = salvage;
@@ -63,12 +69,14 @@ public class SalvageSession extends Session implements Runnable {
// Prevent player from switching items during session // Prevent player from switching items during session
ItemStack itemInHand = this.player.getInventory().getItemInMainHand(); ItemStack itemInHand = this.player.getInventory().getItemInMainHand();
if (!itemToSalvage.equals(itemInHand)) { if (!itemToSalvage.equals(itemInHand)) {
sendNPCMessage(this.npc, this.player, this.config.getItemChangedMessage()); NPCFormatter.sendNPCMessage(this.npc, this.player, this.config.getItemChangedMessage());
return true; return true;
} }
if (EconomyManager.cannotPayForSalvage(this.player, this.salvageMethod)) { // TODO: Check item cost. If
sendNPCMessage(this.npc, this.player, this.config.getInsufficientFundsMessage());
if (EconomyManager.cannotPayForSalvage(this.player, this.salvageMethod, this.itemToSalvage)) {
NPCFormatter.sendNPCMessage(this.npc, this.player, this.config.getInsufficientFundsMessage());
return true; return true;
} }
return false; return false;
@@ -85,6 +93,16 @@ public class SalvageSession extends Session implements Runnable {
} }
} }
@Override
protected @NotNull Material getCraftingStation() {
return switch (this.salvageMethod) {
case EXTENDED_SALVAGE -> Material.CRAFTING_TABLE;
case SALVAGE -> Material.ANVIL;
case NETHERITE, ARMOR_TRIM -> Material.SMITHING_TABLE;
case ENCHANTED_BOOK -> Material.ENCHANTING_TABLE;
};
}
/** /**
* Runs the actual salvage which fixes the item and gives it back to the player * Runs the actual salvage which fixes the item and gives it back to the player
*/ */
@@ -93,10 +111,15 @@ public class SalvageSession extends Session implements Runnable {
salvageItem(); salvageItem();
//Stop the reforged item from displaying in the scrapper's hand //Stop the reforged item from displaying in the scrapper's hand
if (this.npc.getEntity() instanceof Player) { if (this.entity instanceof Player playerNPC) {
((Player) this.npc.getEntity()).getInventory().setItemInMainHand(null); playerNPC.getInventory().setItemInMainHand(null);
} else { } else if (this.entity instanceof LivingEntity) {
Objects.requireNonNull(((LivingEntity) this.npc.getEntity()).getEquipment()).setItemInMainHand(null); EntityEquipment equipment = ((LivingEntity) this.entity).getEquipment();
if (equipment != null) {
equipment.setItemInMainHand(null);
} else {
BlacksmithPlugin.warn("Failed to clear Scrapper item, as its equipment was irretrievable.");
}
} }
//Mark this scrapper as available //Mark this scrapper as available
@@ -104,8 +127,23 @@ public class SalvageSession extends Session implements Runnable {
// Start cool-down // Start cool-down
Calendar wait = Calendar.getInstance(); Calendar wait = Calendar.getInstance();
wait.add(Calendar.SECOND, this.config.getSalvageCoolDown()); StringReplacer replacer = new StringReplacer(BlacksmithPlugin.getInstance().getGlobalScrapperSettings().getSalvageCooldownIncrease());
replacer.add("{cooldown}", String.valueOf(this.config.getSalvageCoolDown()));
replacer.add("{timesUsed}", String.valueOf(PlayerUsageManager.getUsages(player, System.currentTimeMillis() - 3600000)));
Expression expression = new ExpressionBuilder(replacer.replace()).build();
wait.add(Calendar.SECOND, Math.max((int) expression.evaluate(), this.config.getSalvageCoolDown()));
this.scrapperTrait.addCoolDown(this.player.getUniqueId(), wait); this.scrapperTrait.addCoolDown(this.player.getUniqueId(), wait);
PlayerUsageManager.registerUsage(this.player);
}
/**
* Gets the number of items consumed in order to perform the salvage
*
* @return <p>The number of items consumed as part of this salvage</p>
*/
public int getItemsConsumed() {
return this.itemsConsumed;
} }
/** /**
@@ -113,10 +151,14 @@ public class SalvageSession extends Session implements Runnable {
*/ */
private void salvageItem() { private void salvageItem() {
if (random.nextInt(100) < this.config.getFailChance()) { if (random.nextInt(100) < this.config.getFailChance()) {
playSound(Sound.ENTITY_VILLAGER_NO);
failSalvage(); failSalvage();
BlacksmithPlugin.getInstance().callEvent(new ScrapperSalvageFailEvent(this.npc, this.entity, this.player));
} else { } else {
playSound(Sound.BLOCK_ANVIL_USE);
giveSalvage(); giveSalvage();
sendNPCMessage(this.npc, this.player, this.config.getSuccessMessage()); BlacksmithPlugin.getInstance().callEvent(new ScrapperSalvageSucceedEvent(this.npc, this.entity, this.player));
NPCFormatter.sendNPCMessage(this.npc, this.player, this.config.getSuccessMessage());
} }
} }
@@ -130,16 +172,15 @@ public class SalvageSession extends Session implements Runnable {
if (ItemHelper.getMaxDurability(this.itemToSalvage) > 0) { if (ItemHelper.getMaxDurability(this.itemToSalvage) > 0) {
//Damage the item if possible //Damage the item if possible
damageItemRandomly(this.itemToSalvage); damageItemRandomly(this.itemToSalvage);
giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), this.npc, giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), this.itemToSalvage);
this.itemToSalvage); NPCFormatter.sendNPCMessage(this.npc, this.player, this.config.getFailSalvageMessage());
sendNPCMessage(this.npc, this.player, this.config.getFailSalvageMessage());
} else { } else {
// Give half the salvage // Give half the salvage
List<ItemStack> halfSalvage = SalvageHelper.pickRandomSalvage(this.salvage, new ArrayList<>(), 0.5); List<ItemStack> halfSalvage = SalvageHelper.pickRandomSalvage(this.salvage, new ArrayList<>(), 0.5);
for (ItemStack item : halfSalvage) { for (ItemStack item : halfSalvage) {
giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), this.npc, item); giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), item);
} }
sendNPCMessage(this.npc, this.player, this.config.getFailExtendedSalvageMessage()); NPCFormatter.sendNPCMessage(this.npc, this.player, this.config.getFailExtendedSalvageMessage());
} }
} }
@@ -150,8 +191,9 @@ public class SalvageSession extends Session implements Runnable {
// TODO: Find a better calculation than 1 enchantment level = 1 exp level // TODO: Find a better calculation than 1 enchantment level = 1 exp level
// Gives the player back some of the EXP used on an item // Gives the player back some of the EXP used on an item
this.player.giveExpLevels(this.enchantmentLevels); this.player.giveExpLevels(this.enchantmentLevels);
BlacksmithPlugin.debug("Giving salvage " + this.salvage);
for (ItemStack item : this.salvage) { for (ItemStack item : this.salvage) {
giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), this.npc, item); giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), item);
} }
} }

View File

@@ -2,30 +2,35 @@ package net.knarcraft.blacksmith.trait;
import net.citizensnpcs.api.util.DataKey; import net.citizensnpcs.api.util.DataKey;
import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.SalvageMethod;
import net.knarcraft.blacksmith.config.SmithPreset; import net.knarcraft.blacksmith.config.SmithPreset;
import net.knarcraft.blacksmith.config.SmithPresetFilter; import net.knarcraft.blacksmith.config.SmithPresetFilter;
import net.knarcraft.blacksmith.config.scrapper.GlobalScrapperSettings;
import net.knarcraft.blacksmith.config.scrapper.ScrapperNPCSettings; import net.knarcraft.blacksmith.config.scrapper.ScrapperNPCSettings;
import net.knarcraft.blacksmith.config.scrapper.ScrapperSetting; import net.knarcraft.blacksmith.config.scrapper.ScrapperSetting;
import net.knarcraft.blacksmith.container.RecipeResult;
import net.knarcraft.blacksmith.container.SalvageResult;
import net.knarcraft.blacksmith.formatting.NPCFormatter;
import net.knarcraft.blacksmith.manager.EconomyManager; import net.knarcraft.blacksmith.manager.EconomyManager;
import net.knarcraft.blacksmith.property.SalvageMethod;
import net.knarcraft.blacksmith.property.SalvageState;
import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.ItemHelper;
import net.knarcraft.blacksmith.util.SalvageHelper; import net.knarcraft.blacksmith.util.SalvageHelper;
import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.knarcraft.knarlib.formatting.StringReplacer; import net.knarcraft.knarlib.formatting.StringReplacer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ArmorMeta; import org.bukkit.inventory.meta.ArmorMeta;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.sendNPCMessage;
/** /**
* The class representing a scrapper NPC trait * The class representing a scrapper NPC trait
*/ */
@@ -81,73 +86,203 @@ public class ScrapperTrait extends CustomTrait<ScrapperSetting> {
*/ */
public void startSession(@NotNull Player player) { public void startSession(@NotNull Player player) {
ItemStack itemInHand = player.getInventory().getItemInMainHand().clone(); ItemStack itemInHand = player.getInventory().getItemInMainHand().clone();
if (itemInHand.getType().isAir()) {
NPCFormatter.sendNPCMessage(this.npc, player, config.getNoItemMessage());
return;
}
List<Material> salvageAbleItems = getSettings().getSalvageAbleItems(); List<Material> salvageAbleItems = getSettings().getSalvageAbleItems();
boolean extended = getSettings().extendedSalvageEnabled(); boolean extended = getSettings().extendedSalvageEnabled();
// Check if the item can be salvaged // Check if the item can be salvaged
if (!canBeSalvaged(itemInHand, salvageAbleItems, extended)) { if (!canBeSalvaged(itemInHand, salvageAbleItems, extended)) {
sendNPCMessage(this.npc, player, StringFormatter.replacePlaceholder(getSettings().getInvalidItemMessage(), NPCFormatter.sendNPCMessage(this.npc, player, new FormatBuilder(getSettings().getInvalidItemMessage()).
"{title}", getSettings().getScrapperTitle())); replace("{title}", getSettings().getScrapperTitle()));
BlacksmithPlugin.debug("Cannot salvage provided item: " + itemInHand);
return; return;
} }
List<ItemStack> salvage = null; SalvageResult result = getBestResult(player, itemInHand, extended);
// Deal with armor trim salvage if (result == null || result.salvage().isEmpty()) {
SalvageMethod salvageMethod = SalvageMethod.SALVAGE;
if (itemInHand.getItemMeta() instanceof ArmorMeta armorMeta && armorMeta.hasTrim()) {
if (!getSettings().salvageArmorTrims()) {
sendNPCMessage(this.npc, player, getSettings().getCannotSalvageArmorTrimMessage());
return;
}
salvage = SalvageHelper.getArmorTrimSalvage(itemInHand, armorMeta);
if (salvage == null) {
sendNPCMessage(this.npc, player, getSettings().getArmorTrimSalvageNotFoundMessage());
return;
}
salvageMethod = SalvageMethod.ARMOR_TRIM;
}
// Remove the netherite ingot from the item
if (salvage == null && SmithPreset.BLACKSMITH.getFilteredMaterials(SmithPresetFilter.NETHERITE).contains(itemInHand.getType())) {
if (!getSettings().salvageNetherite()) {
sendNPCMessage(this.npc, player, getSettings().getCannotSalvageNetheriteMessage());
return;
}
salvage = SalvageHelper.getNetheriteSalvage(itemInHand);
salvageMethod = SalvageMethod.NETHERITE;
}
// As there is no recipe for netherite items, the check needs to be after the netherite salvage check
if (salvageMethod == SalvageMethod.SALVAGE && !SalvageHelper.isSalvageable(player.getServer(), itemInHand)) {
sendNPCMessage(this.npc, player, StringFormatter.replacePlaceholder(getSettings().getInvalidItemMessage(),
"{title}", getSettings().getScrapperTitle()));
return;
}
// Check if the item is enchanted, and whether this blacksmith can salvage it
if (!itemInHand.getEnchantments().isEmpty() && !getSettings().salvageEnchanted()) {
sendNPCMessage(this.npc, player, getSettings().getCannotSalvageEnchantedMessage());
return;
}
// Check if any salvage will be produced
if (salvage == null) {
salvage = getSalvage(itemInHand, extended);
}
boolean noUsefulSalvage = salvage == null || salvage.isEmpty();
if (noUsefulSalvage) {
sendNPCMessage(this.npc, player, getSettings().getTooDamagedMessage());
return; return;
} }
//Start a new scrapper session for the player //Start a new scrapper session for the player
currentSessionStartTime = System.currentTimeMillis(); currentSessionStartTime = System.currentTimeMillis();
int itemsConsumed = SalvageHelper.getRequiredAmountForSalvage(player.getServer(), itemInHand); try {
session = new SalvageSession(this, player, npc, getSettings(), salvage, salvageMethod, itemsConsumed); session = new SalvageSession(this, player, npc, getSettings(), result.salvage(),
result.salvageMethod(), result.requiredAmount());
} catch (IllegalArgumentException exception) {
BlacksmithPlugin.error(exception.getMessage());
return;
}
// Print the cost to the player // Print the cost to the player
printCostMessage(player, itemInHand, EconomyManager.formatSalvageCost(salvageMethod), salvageMethod); printCostMessage(player, itemInHand, EconomyManager.formatSalvageCost(result.salvageMethod(), itemInHand, player),
result.salvageMethod());
}
/**
* Gets the best available salvage result
*
* @param player <p>The player attempting to salvage an item</p>
* @param itemInHand <p>The item the player is attempting to salvage</p>
* @param extended <p>Whether extended salvage is enabled</p>
* @return <p>The best result, or null if no valid result exists</p>
*/
@Nullable
private SalvageResult getBestResult(@NotNull Player player, @NotNull ItemStack itemInHand, boolean extended) {
SalvageResult result = isArmorTrimSalvage(player, itemInHand);
if (result.salvageState() == SalvageState.NO_SALVAGE) {
return null;
} else if (result.salvageState() == SalvageState.FOUND_SALVAGE) {
return result;
}
result = isNetheriteSalvage(player, itemInHand);
if (result.salvageState() == SalvageState.NO_SALVAGE) {
return null;
} else if (result.salvageState() == SalvageState.FOUND_SALVAGE) {
return result;
}
result = isEnchantedBookSalvage(player, itemInHand);
if (result.salvageState() == SalvageState.NO_SALVAGE) {
return null;
} else if (result.salvageState() == SalvageState.FOUND_SALVAGE) {
return result;
}
result = isNormalSalvage(player, itemInHand, extended);
if (result.salvageState() == SalvageState.NO_SALVAGE) {
return null;
} else if (result.salvageState() == SalvageState.FOUND_SALVAGE) {
return result;
}
return null;
}
/**
* Gets the result of trying to salvage the item normally
*
* @param player <p>The player trying to salvage the item</p>
* @param itemInHand <p>The item to be salvaged</p>
* @param extended <p>Whether extended salvage is enabled</p>
* @return <p>The result of attempting the salvage</p>
*/
@NotNull
private SalvageResult isNormalSalvage(@NotNull Player player, @NotNull ItemStack itemInHand, boolean extended) {
// As there is no recipe for netherite items, the check needs to be after the netherite salvage check
if (!SalvageHelper.isSalvageable(player.getServer(), itemInHand)) {
NPCFormatter.sendNPCMessage(this.npc, player, new FormatBuilder(getSettings().getInvalidItemMessage()).
replace("{title}", getSettings().getScrapperTitle()));
BlacksmithPlugin.debug("Provided with non-salvage-able item " + itemInHand);
return new SalvageResult(SalvageMethod.SALVAGE, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
// Check if the item is enchanted, and whether this scrapper can salvage it
if (!itemInHand.getEnchantments().isEmpty() && !getSettings().salvageEnchanted()) {
NPCFormatter.sendNPCMessage(this.npc, player, getSettings().getCannotSalvageEnchantedMessage());
return new SalvageResult(SalvageMethod.SALVAGE, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
// Check if any salvage will be produced
RecipeResult recipeResult = getSalvage(itemInHand, extended);
SalvageMethod method = ItemHelper.isRepairable(itemInHand) ? SalvageMethod.SALVAGE : SalvageMethod.EXTENDED_SALVAGE;
boolean noUsefulSalvage = recipeResult == null || recipeResult.salvage().isEmpty();
if (noUsefulSalvage) {
NPCFormatter.sendNPCMessage(this.npc, player, getSettings().getTooDamagedMessage());
return new SalvageResult(method, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
} else {
return new SalvageResult(method, recipeResult.salvage(), SalvageState.FOUND_SALVAGE,
recipeResult.recipe().getResult().getAmount());
}
}
/**
* Gets the result of trying to salvage the item as an enchanted book
*
* @param player <p>The player trying to salvage the item</p>
* @param itemInHand <p>The item to be salvaged</p>
* @return <p>The result of attempting the salvage</p>
*/
private SalvageResult isEnchantedBookSalvage(@NotNull Player player, @NotNull ItemStack itemInHand) {
if (itemInHand.getType() != Material.ENCHANTED_BOOK ||
!(itemInHand.getItemMeta() instanceof EnchantmentStorageMeta)) {
return new SalvageResult(SalvageMethod.ENCHANTED_BOOK, new ArrayList<>(), SalvageState.INCORRECT_METHOD, 0);
}
if (!getSettings().splitEnchantedBook()) {
NPCFormatter.sendNPCMessage(this.npc, player, getSettings().getCannotSalvageEnchantedBookMessage());
return new SalvageResult(SalvageMethod.ENCHANTED_BOOK, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
if (SalvageHelper.getEnchantmentCount(itemInHand) <= 1) {
NPCFormatter.sendNPCMessage(this.npc, player, getSettings().getCannotSplitEnchantedBookFurtherMessage());
return new SalvageResult(SalvageMethod.ENCHANTED_BOOK, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
List<ItemStack> salvage = SalvageHelper.getEnchantedBookSalvage(itemInHand);
boolean noUsefulSalvage = salvage == null || salvage.isEmpty();
if (noUsefulSalvage) {
return new SalvageResult(SalvageMethod.ENCHANTED_BOOK, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
return new SalvageResult(SalvageMethod.ENCHANTED_BOOK, salvage, SalvageState.FOUND_SALVAGE, 1);
}
/**
* Gets the result of trying to salvage the item as a netherite applied item
*
* @param player <p>The player trying to salvage the item</p>
* @param itemInHand <p>The item to be salvaged</p>
* @return <p>The result of attempting the salvage</p>
*/
@NotNull
private SalvageResult isNetheriteSalvage(@NotNull Player player, @NotNull ItemStack itemInHand) {
if (!SmithPreset.BLACKSMITH.getFilteredMaterials(SmithPresetFilter.NETHERITE).contains(itemInHand.getType())) {
return new SalvageResult(SalvageMethod.NETHERITE, new ArrayList<>(), SalvageState.INCORRECT_METHOD, 0);
}
if (!getSettings().salvageNetherite()) {
NPCFormatter.sendNPCMessage(this.npc, player, getSettings().getCannotSalvageNetheriteMessage());
return new SalvageResult(SalvageMethod.NETHERITE, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
List<ItemStack> salvage = SalvageHelper.getNetheriteSalvage(itemInHand);
if (salvage == null || salvage.isEmpty()) {
return new SalvageResult(SalvageMethod.NETHERITE, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
return new SalvageResult(SalvageMethod.NETHERITE, salvage, SalvageState.FOUND_SALVAGE, 1);
}
/**
* Gets the result of trying to salvage the item as an armor trim
*
* @param player <p>The player trying to salvage the item</p>
* @param itemInHand <p>The item to be salvaged</p>
* @return <p>The result of attempting the salvage</p>
*/
@NotNull
private SalvageResult isArmorTrimSalvage(@NotNull Player player, @NotNull ItemStack itemInHand) {
if (!(itemInHand.getItemMeta() instanceof ArmorMeta armorMeta) || !armorMeta.hasTrim()) {
return new SalvageResult(SalvageMethod.ARMOR_TRIM, new ArrayList<>(), SalvageState.INCORRECT_METHOD, 0);
}
if (!getSettings().salvageArmorTrims()) {
NPCFormatter.sendNPCMessage(this.npc, player, getSettings().getCannotSalvageArmorTrimMessage());
return new SalvageResult(SalvageMethod.ARMOR_TRIM, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
List<ItemStack> salvage = SalvageHelper.getArmorTrimSalvage(itemInHand, armorMeta);
if (salvage == null || salvage.isEmpty()) {
NPCFormatter.sendNPCMessage(this.npc, player, getSettings().getArmorTrimSalvageNotFoundMessage());
return new SalvageResult(SalvageMethod.ARMOR_TRIM, new ArrayList<>(), SalvageState.NO_SALVAGE, 0);
}
return new SalvageResult(SalvageMethod.ARMOR_TRIM, salvage, SalvageState.FOUND_SALVAGE, 1);
} }
/** /**
@@ -164,8 +299,8 @@ public class ScrapperTrait extends CustomTrait<ScrapperSetting> {
replacer.add("{cost}", cost); replacer.add("{cost}", cost);
replacer.add("{item}", itemInHand.getType().name().toLowerCase().replace('_', ' ')); replacer.add("{item}", itemInHand.getType().name().toLowerCase().replace('_', ' '));
if (salvageMethod == SalvageMethod.ARMOR_TRIM) { if (salvageMethod == SalvageMethod.ARMOR_TRIM) {
sendNPCMessage(this.npc, player, replacer.replace(getSettings().getArmorTrimCostMessage())); NPCFormatter.sendNPCMessage(this.npc, player, replacer.replace(getSettings().getArmorTrimCostMessage()));
} else if (salvageMethod == SalvageMethod.SALVAGE) { } else if (salvageMethod == SalvageMethod.SALVAGE || salvageMethod == SalvageMethod.EXTENDED_SALVAGE) {
String expectedYield; String expectedYield;
if (ItemHelper.getDamage(itemInHand) <= 0) { if (ItemHelper.getDamage(itemInHand) <= 0) {
expectedYield = getSettings().getFullSalvageMessage(); expectedYield = getSettings().getFullSalvageMessage();
@@ -173,12 +308,35 @@ public class ScrapperTrait extends CustomTrait<ScrapperSetting> {
expectedYield = getSettings().getPartialSalvageMessage(); expectedYield = getSettings().getPartialSalvageMessage();
} }
replacer.add("{yield}", expectedYield); replacer.add("{yield}", expectedYield);
sendNPCMessage(this.npc, player, replacer.replace(getSettings().getCostMessage())); NPCFormatter.sendNPCMessage(this.npc, player, replacer.replace(getSettings().getCostMessage()));
} else if (salvageMethod == SalvageMethod.NETHERITE) { } else if (salvageMethod == SalvageMethod.NETHERITE) {
sendNPCMessage(this.npc, player, replacer.replace(getSettings().getNetheriteCostMessage())); NPCFormatter.sendNPCMessage(this.npc, player, replacer.replace(getSettings().getNetheriteCostMessage()));
} else if (salvageMethod == SalvageMethod.ENCHANTED_BOOK) {
StringReplacer replacer2 = new StringReplacer();
replacer2.add("{cost}", getEnchantedBookCost());
NPCFormatter.sendNPCMessage(this.npc, player, replacer2.replace(getSettings().getEnchantedBookCostMessage()));
} else {
BlacksmithPlugin.error("Unrecognized salvage method " + salvageMethod);
} }
} }
private String getEnchantedBookCost() {
// TODO: If both an item and money is required, print both requirements
// TODO: If only money is required, print the money requirement
// TODO: If an item requirement is set, print the item requirement
GlobalScrapperSettings scrapperSettings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings();
/*String moneyCost = EconomyManager.format(scrapperSettings.getEnchantedBookSalvageCost());
//ItemStack itemCost = scrapperSettings.
if (scrapperSettings.requireMoneyAndItemForEnchantedBookSalvage()) {
// TODO: Print both with a + between them (if item has a special name, use that instead of the material name)
} else {
// TODO: If the item is not null, print it, otherwise print the monetary cost
}*/
return "";
}
@Override @Override
protected boolean showExactTime() { protected boolean showExactTime() {
return BlacksmithPlugin.getInstance().getGlobalScrapperSettings().showExactTime(); return BlacksmithPlugin.getInstance().getGlobalScrapperSettings().showExactTime();
@@ -193,8 +351,8 @@ public class ScrapperTrait extends CustomTrait<ScrapperSetting> {
* @return <p>True if the item can be theoretically salvaged</p> * @return <p>True if the item can be theoretically salvaged</p>
*/ */
private boolean canBeSalvaged(@NotNull ItemStack item, @NotNull List<Material> salvageAbleItems, boolean extended) { private boolean canBeSalvaged(@NotNull ItemStack item, @NotNull List<Material> salvageAbleItems, boolean extended) {
return (extended || ItemHelper.isRepairable(item)) && return item.getType() == Material.ENCHANTED_BOOK || ((extended || ItemHelper.isRepairable(item)) &&
(salvageAbleItems.isEmpty() || salvageAbleItems.contains(item.getType())); (salvageAbleItems.isEmpty() || salvageAbleItems.contains(item.getType())));
} }
/** /**
@@ -205,7 +363,7 @@ public class ScrapperTrait extends CustomTrait<ScrapperSetting> {
* @return <p>The possible salvage, or null if not salvage-able</p> * @return <p>The possible salvage, or null if not salvage-able</p>
*/ */
@Nullable @Nullable
private List<ItemStack> getSalvage(@NotNull ItemStack item, boolean extended) { private RecipeResult getSalvage(@NotNull ItemStack item, boolean extended) {
// Get the salvage, for the item, but ignore some materials if set, and the item isn't at full durability // Get the salvage, for the item, but ignore some materials if set, and the item isn't at full durability
Set<Material> trashSalvage = BlacksmithPlugin.getInstance().getGlobalScrapperSettings().getTrashSalvage( Set<Material> trashSalvage = BlacksmithPlugin.getInstance().getGlobalScrapperSettings().getTrashSalvage(
item.getType()); item.getType());

View File

@@ -2,14 +2,21 @@ 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.BlacksmithPlugin;
import net.knarcraft.blacksmith.event.BlacksmithReforgeStartEvent;
import net.knarcraft.blacksmith.event.NPCSoundEvent;
import net.knarcraft.blacksmith.event.ScrapperSalvageStartEvent;
import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.ItemHelper;
import org.bukkit.Material;
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.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Random; import java.util.Random;
import java.util.logging.Level;
/** /**
* A runnable session for performing a reforging/salvage task * A runnable session for performing a reforging/salvage task
@@ -18,6 +25,8 @@ public abstract class Session implements Runnable {
protected static final Random random = new Random(); protected static final Random random = new Random();
protected final Player player; protected final Player player;
protected final NPC npc;
protected final Entity entity;
protected long finishTime; protected long finishTime;
protected int taskId; protected int taskId;
@@ -25,9 +34,15 @@ public abstract class Session implements Runnable {
* Instantiates a new session * Instantiates a new session
* *
* @param player <p>The player the session belongs to</p> * @param player <p>The player the session belongs to</p>
* @param npc <p>The NPC involved in the session</p>
*/ */
public Session(@NotNull Player player) { public Session(@NotNull Player player, @NotNull NPC npc) {
this.player = player; this.player = player;
this.npc = npc;
this.entity = npc.getEntity();
if (entity == null) {
throw new IllegalArgumentException("Tried to start session for an NPC without an assigned entity.");
}
} }
/** /**
@@ -89,7 +104,16 @@ public abstract class Session implements Runnable {
BukkitScheduler scheduler = BlacksmithPlugin.getInstance().getServer().getScheduler(); BukkitScheduler scheduler = BlacksmithPlugin.getInstance().getServer().getScheduler();
int actionDelay = getActionDelay(); int actionDelay = getActionDelay();
this.finishTime = System.currentTimeMillis() + (actionDelay * 1000L); this.finishTime = System.currentTimeMillis() + (actionDelay * 1000L);
taskId = scheduler.scheduleSyncDelayedTask(BlacksmithPlugin.getInstance(), this, actionDelay * 20L); long actionDelayTicks = actionDelay * 20L;
BlacksmithPlugin instance = BlacksmithPlugin.getInstance();
if (this instanceof ReforgeSession) {
instance.callEvent(new BlacksmithReforgeStartEvent(this.npc, this.entity, this.player, actionDelayTicks, getCraftingStation()));
} else if (this instanceof SalvageSession) {
instance.callEvent(new ScrapperSalvageStartEvent(this.npc, this.entity, this.player, actionDelayTicks, getCraftingStation()));
}
taskId = scheduler.scheduleSyncDelayedTask(BlacksmithPlugin.getInstance(), this, actionDelayTicks);
} }
/** /**
@@ -97,14 +121,13 @@ public abstract class Session implements Runnable {
* *
* @param hasDelay <p>Whether the session was delayed, or if the item was processed instantly</p> * @param hasDelay <p>Whether the session was delayed, or if the item was processed instantly</p>
* @param dropItem <p>Whether the item should be dropped on the ground</p> * @param dropItem <p>Whether the item should be dropped on the ground</p>
* @param npc <p>The NPC holding the item</p>
* @param itemToReturn <p>The item to return to the player</p> * @param itemToReturn <p>The item to return to the player</p>
*/ */
protected void giveResultingItem(boolean hasDelay, boolean dropItem, @NotNull NPC npc, @NotNull ItemStack itemToReturn) { protected void giveResultingItem(boolean hasDelay, boolean dropItem, @NotNull ItemStack itemToReturn) {
if (hasDelay) { if (hasDelay) {
//If the player isn't online, or the player cannot fit the item, drop the item to prevent it from disappearing //If the player isn't online, or the player cannot fit the item, drop the item to prevent it from disappearing
if (dropItem || !this.player.isOnline() || !ItemHelper.canFitItem(this.player.getInventory(), itemToReturn)) { if (dropItem || !this.player.isOnline() || !ItemHelper.canFitItem(this.player.getInventory(), itemToReturn)) {
this.player.getWorld().dropItemNaturally(npc.getEntity().getLocation(), itemToReturn); this.player.getWorld().dropItemNaturally(this.entity.getLocation(), itemToReturn);
} else { } else {
this.player.getInventory().addItem(itemToReturn); this.player.getInventory().addItem(itemToReturn);
} }
@@ -129,7 +152,7 @@ public abstract class Session implements Runnable {
newDurability = (short) (maxDurability - random.nextInt(maxDurability - 25)); newDurability = (short) (maxDurability - random.nextInt(maxDurability - 25));
} }
if (ItemHelper.updateDamage(item, maxDurability - newDurability)) { if (ItemHelper.updateDamage(item, maxDurability - newDurability)) {
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to update damage for " + item); BlacksmithPlugin.warn("Unable to update damage for " + item);
} }
} }
@@ -140,4 +163,41 @@ public abstract class Session implements Runnable {
*/ */
protected abstract int getActionDelay(); protected abstract int getActionDelay();
/**
* Gets the appropriate crafting station for this session
*
* @return <p>The appropriate crafting station</p>
*/
protected abstract @NotNull Material getCraftingStation();
/**
* Plays an NPC sound
*
* @param sound <p>The sound to play</p>
*/
protected void playSound(Sound sound) {
playSound(this.entity, sound);
}
/**
* Plays a npc sound using a cancellable event
*
* @param entity <p>The entity that should play the sound</p>
* @param sound <p>The sound to play</p>
*/
private void playSound(@NotNull Entity entity, @NotNull Sound sound) {
World world = entity.getLocation().getWorld();
if (world == null) {
return;
}
NPCSoundEvent event = new NPCSoundEvent(this.npc, this.entity, this.player, SoundCategory.AMBIENT, sound, 0.5f, 1.0f);
BlacksmithPlugin.getInstance().callEvent(event);
if (event.isCancelled()) {
return;
}
world.playSound(event.getEntity(), event.getSound(), event.getSoundCategory(), event.getVolume(), event.getPitch());
}
} }

View File

@@ -1,20 +1,19 @@
package net.knarcraft.blacksmith.util; package net.knarcraft.blacksmith.util;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.Setting; import net.knarcraft.blacksmith.config.Setting;
import net.knarcraft.blacksmith.config.SettingValueType; import net.knarcraft.blacksmith.config.SettingValueType;
import net.knarcraft.blacksmith.config.Settings; import net.knarcraft.blacksmith.config.Settings;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.FormatBuilder;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays; import java.util.Arrays;
import static net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage.getCurrentValueMessage; import static net.knarcraft.blacksmith.formatting.Translatable.getCurrentValueMessage;
import static net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage.getDefaultValueMessage; import static net.knarcraft.blacksmith.formatting.Translatable.getDefaultValueMessage;
import static net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage.getValueChangedMessage; import static net.knarcraft.blacksmith.formatting.Translatable.getValueChangedMessage;
/** /**
* A helper class for the configuration command * A helper class for the configuration command
@@ -43,8 +42,7 @@ public final class ConfigCommandHelper {
newValue = String.join(" ", Arrays.asList(args).subList(1, args.length)); newValue = String.join(" ", Arrays.asList(args).subList(1, args.length));
} }
settings.changeValue(detectedSetting, newValue); settings.changeValue(detectedSetting, newValue);
BlacksmithPlugin.getStringFormatter().displaySuccessMessage(sender, getValueChangedMessage(detectedSetting.getCommandName(), newValue).success(sender);
getValueChangedMessage(detectedSetting.getCommandName(), newValue));
} }
/** /**
@@ -57,7 +55,6 @@ public final class ConfigCommandHelper {
public static <K extends Setting> void displayCurrentValue(@NotNull K setting, public static <K extends Setting> void displayCurrentValue(@NotNull K setting,
@NotNull Settings<K> settings, @NotNull Settings<K> settings,
@NotNull CommandSender sender) { @NotNull CommandSender sender) {
StringFormatter formatter = BlacksmithPlugin.getStringFormatter();
boolean printRawValue = false; boolean printRawValue = false;
//Find the value of the specified setting //Find the value of the specified setting
@@ -70,16 +67,16 @@ public final class ConfigCommandHelper {
} }
// Display the description of the setting // Display the description of the setting
formatter.displaySuccessMessage(sender, setting.getDescription()); new FormatBuilder(setting.getDescription()).success(sender);
// Display the setting's default value // Display the setting's default value
formatter.displayNeutralMessage(sender, getDefaultValueMessage(setting.getCommandName(), defaultValue)); getDefaultValueMessage(setting.getCommandName(), defaultValue).neutral(sender);
if (printRawValue) { if (printRawValue) {
displayRaw(sender, defaultValue); displayRaw(sender, defaultValue);
} }
// Display the current value of the setting // Display the current value of the setting
formatter.displayNeutralMessage(sender, getCurrentValueMessage(setting.getCommandName(), currentValue)); getCurrentValueMessage(setting.getCommandName(), currentValue).neutral(sender);
if (printRawValue) { if (printRawValue) {
displayRaw(sender, currentValue); displayRaw(sender, currentValue);
} }
@@ -92,8 +89,7 @@ public final class ConfigCommandHelper {
* @param value <p>The value to display raw</p> * @param value <p>The value to display raw</p>
*/ */
public static void displayRaw(@NotNull CommandSender sender, @NotNull String value) { public static void displayRaw(@NotNull CommandSender sender, @NotNull String value) {
sender.sendMessage(BlacksmithTranslatableMessage.getRawValueMessage( sender.sendMessage(Translatable.getRawValueMessage(value.replace(ChatColor.COLOR_CHAR, '&')).toString());
value.replace(ChatColor.COLOR_CHAR, '&')));
} }
} }

View File

@@ -1,23 +1,10 @@
package net.knarcraft.blacksmith.util; package net.knarcraft.blacksmith.util;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.StargateYamlConfiguration;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.FileHelper;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemorySection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.logging.Level;
/** /**
* A helper class for getting an object value as the correct type * A helper class for getting an object value as the correct type
@@ -107,104 +94,4 @@ public final class ConfigHelper {
} }
} }
/**
* Changes all configuration values from the old name to the new name
*
* @param dataFolderPath <p>The path to this plugin's data</p>
* @param currentConfiguration <p>The current config to back up</p>
*/
public static void migrateConfig(@NotNull String dataFolderPath, @NotNull FileConfiguration currentConfiguration) {
BlacksmithPlugin instance = BlacksmithPlugin.getInstance();
//Save the old config just in case something goes wrong
try {
currentConfiguration.save(new File(dataFolderPath, "config.yml.old"));
} catch (IOException exception) {
instance.getLogger().log(Level.WARNING, "Unable to save old backup and do migration");
return;
}
//Load old and new configuration
instance.reloadConfig();
FileConfiguration oldConfiguration = instance.getConfig();
InputStream configStream = FileHelper.getInputStreamForInternalFile("/config.yml");
if (configStream == null) {
instance.getLogger().log(Level.SEVERE, "Could not migrate the configuration, as the internal " +
"configuration could not be read!");
return;
}
YamlConfiguration newConfiguration = StargateYamlConfiguration.loadConfiguration(
FileHelper.getBufferedReaderFromInputStream(configStream));
//Read all available config migrations
Map<String, String> migrationFields;
try {
InputStream migrationStream = FileHelper.getInputStreamForInternalFile("/config-migrations.txt");
if (migrationStream == null) {
instance.getLogger().log(Level.SEVERE, "Could not migrate the configuration, as the internal migration paths could not be read!");
return;
}
migrationFields = FileHelper.readKeyValuePairs(FileHelper.getBufferedReaderFromInputStream(migrationStream),
"=", ColorConversion.NORMAL);
} catch (IOException exception) {
instance.getLogger().log(Level.WARNING, "Unable to load config migration file");
return;
}
//Replace old config names with the new ones
for (String key : migrationFields.keySet()) {
if (oldConfiguration.contains(key)) {
migrateProperty(migrationFields, key, oldConfiguration);
}
}
// Copy all keys to the new config
for (String key : StargateYamlConfiguration.getKeysWithoutComments(oldConfiguration, true)) {
if (oldConfiguration.get(key) instanceof MemorySection) {
continue;
}
newConfiguration.set(key, oldConfiguration.get(key));
}
try {
newConfiguration.save(new File(dataFolderPath, "config.yml"));
} catch (IOException exception) {
instance.getLogger().log(Level.WARNING, "Unable to save migrated config");
}
instance.reloadConfig();
}
/**
* Migrates one configuration property
*
* @param migrationFields <p>The configuration fields to be migrated</p>
* @param key <p>The key/path of the property to migrate</p>
* @param oldConfiguration <p>The original pre-migration configuration</p>
*/
private static void migrateProperty(@NotNull Map<String, String> migrationFields, @NotNull String key,
@NotNull FileConfiguration oldConfiguration) {
String newPath = migrationFields.get(key);
Object oldValue = oldConfiguration.get(key);
if (!newPath.trim().isEmpty()) {
if (oldConfiguration.isConfigurationSection(key)) {
// Copy each value of a configuration section
ConfigurationSection sourceSection = oldConfiguration.getConfigurationSection(key);
ConfigurationSection destinationSection = oldConfiguration.createSection(newPath);
if (sourceSection == null) {
return;
}
for (String path : StargateYamlConfiguration.getKeysWithoutComments(sourceSection, true)) {
destinationSection.set(path, sourceSection.get(path));
}
} else {
// Copy the value to the new path
oldConfiguration.set(newPath, oldValue);
}
}
// Remove the old path's value
oldConfiguration.set(key, null);
}
} }

View File

@@ -0,0 +1,288 @@
package net.knarcraft.blacksmith.util;
import net.knarcraft.blacksmith.container.ActionCost;
import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.blacksmith.property.CostType;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
/**
* A helper class for setting action costs
*/
public final class CostHelper {
private static List<String> plugins;
private static Map<String, List<String>> permissions;
private CostHelper() {
}
/**
* Parses a simple double cost
*
* @param commandSender <p>The command sender to notify of any problems</p>
* @param costString <p>The string to parse into a cost</p>
* @return <p>The parsed cost</p>
* @throws IllegalArgumentException <p>If the specified cost is invalid</p>
*/
public static double parseSimpleCost(@NotNull CommandSender commandSender,
@NotNull String costString) throws IllegalArgumentException {
try {
double cost = Double.parseDouble(costString);
if (cost < 0) {
throw new NumberFormatException();
}
return cost;
} catch (NumberFormatException exception) {
new FormatBuilder(Translatable.DOUBLE_COST_REQUIRED).error(commandSender);
throw new IllegalArgumentException("Invalid cost given");
}
}
/**
* Shows tab-completion actions for altering a cost
*
* @param arguments <p>The arguments given by the user</p>
* @return <p>The available tab-complete options</p>
*/
public static @Nullable List<String> tabCompleteCost(@NotNull String[] arguments) {
if (plugins == null) {
loadAvailablePermissions();
}
if (arguments.length == 1) {
return List.of("simple", "advanced");
} else if (arguments.length == 2) {
if (arguments[0].equalsIgnoreCase("simple")) {
return List.of("");
} else {
return List.of("add", "set", "clear");
}
} else if (arguments.length == 3) {
if (arguments[0].equalsIgnoreCase("simple")) {
return List.of("");
} else {
return List.of("economy", "item", "exp", "permission");
}
} else if (arguments.length == 4) {
CostType costType = CostType.parse(arguments[2]);
if (costType == null) {
return null;
}
return switch (costType) {
case PERMISSION -> tabCompletePermission(arguments[3]);
case ECONOMY -> List.of("0.5", "1", "10");
case EXP -> List.of("1, 5, 10");
case ITEM -> List.of();
};
}
return List.of();
}
/**
* Modifies the cost of an action
*
* @param arguments <p>The arguments given by a player</p>
* @param oldCost <p>The previous cost of the action</p>
* @param player <p>The player attempting to alter the action cost</p>
* @return <p>The modified action cost, or null if something went wrong</p>
*/
@Nullable
public static ActionCost modifyActionCost(@NotNull String[] arguments, @NotNull ActionCost oldCost,
@NotNull Player player) {
CostType costType;
if (arguments.length > 2) {
costType = CostType.parse(arguments[2]);
} else {
costType = null;
}
switch (arguments[1].toLowerCase()) {
case "add":
if (costType == null) {
player.sendMessage("Invalid cost type specified!");
return null;
}
return parseCost(costType, Arrays.copyOfRange(arguments, 2, arguments.length), oldCost, player);
case "clear":
// Clears one or all fields of a cost
if (costType == null) {
return new ActionCost(0, 0, null, Set.of());
} else {
return switch (costType) {
case EXP -> oldCost.changeExpCost(0);
case ITEM -> oldCost.changeItemCost(null);
case ECONOMY -> oldCost.changeMonetaryCost(0);
case PERMISSION -> oldCost.changePermissionCost(Set.of());
};
}
case "set":
if (costType == null) {
player.sendMessage("Invalid cost type specified!");
return null;
}
return parseCost(costType, Arrays.copyOfRange(arguments, 2, arguments.length),
new ActionCost(0, 0, null, Set.of()), player);
default:
return null;
}
}
/**
* Parses an advanced action cost
*
* @param costType <p>The type of cost to parse</p>
* @param arguments <p>The arguments given by the player</p>
* @param oldCost <p>The old cost to modify</p>
* @param player <p>The player changing the value</p>
* @return <p>The new action cost, or null if the process failed.</p>
*/
@Nullable
private static ActionCost parseCost(@NotNull CostType costType, @NotNull String[] arguments, @NotNull ActionCost oldCost,
@NotNull Player player) {
switch (costType) {
case ECONOMY:
double economyCost = Double.parseDouble(arguments[0]);
if (economyCost < 0) {
player.sendMessage("Cost cannot be negative!");
return null;
}
return oldCost.changeMonetaryCost(economyCost);
case ITEM:
ItemStack itemCost = player.getInventory().getItemInMainHand();
if (itemCost.getType().isAir()) {
player.sendMessage("You have no item in your main hand");
return null;
}
return oldCost.changeItemCost(itemCost);
case PERMISSION:
Set<String> permissionSet = new HashSet<>(Arrays.asList(arguments[0].split(",")));
return oldCost.changePermissionCost(permissionSet);
case EXP:
int expCost = Integer.parseInt(arguments[0]);
if (expCost < 0) {
player.sendMessage("Exp cost cannot be negative!");
return null;
}
return oldCost.changeExpCost(expCost);
default:
return null;
}
}
/**
* Gets the tab complete value for the permission typed
*
* @param typedNode <p>The full permission node typed by the player</p>
* @return <p>All known valid auto-complete options</p>
*/
private static @NotNull List<String> tabCompletePermission(@NotNull String typedNode) {
StringBuilder permissionListString = new StringBuilder();
if (typedNode.contains(",")) {
String[] permissionList = typedNode.split(",");
for (int i = 0; i < permissionList.length - 1; i++) {
permissionListString.append(permissionList[i]);
permissionListString.append(",");
}
typedNode = permissionList[permissionList.length - 1];
}
String permissionPrefix = permissionListString.toString();
List<String> output;
if (typedNode.contains(".")) {
List<String> matchingPermissions = permissions.get(typedNode.substring(0, typedNode.lastIndexOf(".")));
if (matchingPermissions == null) {
if (permissionPrefix.isEmpty()) {
output = new ArrayList<>();
} else {
List<String> onlyPrefix = new ArrayList<>();
onlyPrefix.add(permissionPrefix);
output = onlyPrefix;
}
} else {
//Filter by the typed text
output = filterMatching(matchingPermissions, typedNode);
}
} else {
output = plugins;
}
//Add previous permissions in the comma-separated lists as a prefix
if (permissionPrefix.isEmpty()) {
return output;
} else {
List<String> prefixed = new ArrayList<>(output.size());
for (String matchingCompletion : output) {
prefixed.add(permissionPrefix + matchingCompletion);
}
return prefixed;
}
}
/**
* Find completable strings which match the text typed by the command's sender
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values which start with the player's typed text</p>
*/
private static @NotNull List<String> filterMatching(@NotNull List<String> values, @NotNull String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Loads all permissions available from bukkit plugins
*/
private static void loadAvailablePermissions() {
plugins = new ArrayList<>();
permissions = new HashMap<>();
for (Permission permission : Bukkit.getPluginManager().getPermissions()) {
loadPermission(permission.getName());
}
}
/**
* Loads a given permission into the proper lists and maps
*
* @param permissionName <p>The permission to load</p>
*/
private static void loadPermission(@NotNull String permissionName) {
String[] permissionParts = permissionName.split("\\.");
if (permissionParts.length == 1 && !plugins.contains(permissionParts[0])) {
plugins.add(permissionParts[0]);
} else if (permissionParts.length > 1) {
StringJoiner pathJoiner = new StringJoiner(".");
for (int j = 0; j < permissionParts.length - 1; j++) {
pathJoiner.add(permissionParts[j]);
}
String path = pathJoiner.toString();
List<String> permissionList = permissions.computeIfAbsent(path, k -> new ArrayList<>());
permissionList.add(permissionName);
loadPermission(path);
}
}
}

View File

@@ -14,7 +14,6 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
/** /**
* A helper class for getting information about items * A helper class for getting information about items
@@ -105,6 +104,9 @@ public final class ItemHelper {
public static @NotNull Set<Material> getAllReforgeAbleMaterials() { public static @NotNull Set<Material> getAllReforgeAbleMaterials() {
Set<Material> reforgeAbleMaterials = new HashSet<>(); Set<Material> reforgeAbleMaterials = new HashSet<>();
for (Material material : Material.values()) { for (Material material : Material.values()) {
if (!material.isItem()) {
continue;
}
ItemStack item = new ItemStack(material); ItemStack item = new ItemStack(material);
if (isRepairable(item)) { if (isRepairable(item)) {
reforgeAbleMaterials.add(material); reforgeAbleMaterials.add(material);
@@ -227,8 +229,7 @@ public final class ItemHelper {
blacklisted.add(material); blacklisted.add(material);
} }
} else { } else {
BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to verify " + item + BlacksmithPlugin.warn("Unable to verify " + item + " as a valid repairable item");
" as a valid repairable item");
} }
} }

View File

@@ -1,5 +1,8 @@
package net.knarcraft.blacksmith.util; package net.knarcraft.blacksmith.util;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.container.RecipeResult;
import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
@@ -8,6 +11,8 @@ import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe; import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.inventory.meta.ArmorMeta; import org.bukkit.inventory.meta.ArmorMeta;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.trim.ArmorTrim; import org.bukkit.inventory.meta.trim.ArmorTrim;
import org.bukkit.inventory.meta.trim.TrimMaterial; import org.bukkit.inventory.meta.trim.TrimMaterial;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -42,6 +47,51 @@ public final class SalvageHelper {
trimMaterialToMaterial.put(TrimMaterial.REDSTONE, Material.REDSTONE); trimMaterialToMaterial.put(TrimMaterial.REDSTONE, Material.REDSTONE);
} }
/**
* Gets salvage for an enchanted book
*
* @param itemStack <p>The enchanted book(s) to salvage</p>
* @return <p>The resulting salvage</p>
* @throws RuntimeException <p>If generated enchanted book metadata is null</p>
*/
@Nullable
public static List<ItemStack> getEnchantedBookSalvage(@NotNull ItemStack itemStack) {
ItemMeta meta = itemStack.getItemMeta();
List<ItemStack> output = new ArrayList<>();
if (!(meta instanceof EnchantmentStorageMeta enchantmentStorageMeta)) {
return null;
}
Map<Enchantment, Integer> enchantmentMap = enchantmentStorageMeta.getStoredEnchants();
for (Map.Entry<Enchantment, Integer> enchantmentEntry : enchantmentMap.entrySet()) {
EnchantmentStorageMeta enchantmentMeta = (EnchantmentStorageMeta) Bukkit.getServer().getItemFactory().getItemMeta(Material.ENCHANTED_BOOK);
if (enchantmentMeta == null) {
throw new RuntimeException("Unable to create enchanted book metadata.");
}
enchantmentMeta.addStoredEnchant(enchantmentEntry.getKey(), enchantmentEntry.getValue(), true);
ItemStack newBook = new ItemStack(Material.ENCHANTED_BOOK, 1);
newBook.setItemMeta(enchantmentMeta);
output.add(newBook);
}
return output;
}
/**
* Gets the number of enchantments applied to an enchanted book
*
* @param itemStack <p>The enchanted book to check</p>
* @return <p>The number of enchantments, or -1 if not an enchanted book</p>
*/
public static int getEnchantmentCount(@NotNull ItemStack itemStack) {
ItemMeta meta = itemStack.getItemMeta();
if (!(meta instanceof EnchantmentStorageMeta enchantmentStorageMeta)) {
return -1;
}
return enchantmentStorageMeta.getStoredEnchants().size();
}
/** /**
* Gets salvage for a given netherite item * Gets salvage for a given netherite item
* *
@@ -86,6 +136,8 @@ public final class SalvageHelper {
} else { } else {
return null; return null;
} }
// Note: While depreciated in Spigot, the new method is not available for Paper
@SuppressWarnings("deprecation")
Material patternMaterial = Material.matchMaterial(armorTrim.getPattern().getKey().getKey() + Material patternMaterial = Material.matchMaterial(armorTrim.getPattern().getKey().getKey() +
"_ARMOR_TRIM_SMITHING_TEMPLATE"); "_ARMOR_TRIM_SMITHING_TEMPLATE");
if (patternMaterial != null) { if (patternMaterial != null) {
@@ -122,23 +174,6 @@ public final class SalvageHelper {
return false; return false;
} }
/**
* Gets the amount of an item that's required to salvage that item
*
* @param server <p>The server to get recipes from</p>
* @param item <p>The item to check</p>
* @return <p>The number of items required for salvage, or -1 if the recipe was not found</p>
*/
public static int getRequiredAmountForSalvage(@NotNull Server server, @NotNull ItemStack item) {
for (Recipe recipe : server.getRecipesFor(new ItemStack(item.getType(), item.getAmount()))) {
// Only crafting recipes are allowed.
if (recipe instanceof ShapedRecipe || recipe instanceof ShapelessRecipe) {
return recipe.getResult().getAmount();
}
}
return 1;
}
/** /**
* Gets the sum of all enchantment levels for the given item * Gets the sum of all enchantment levels for the given item
* *
@@ -164,19 +199,34 @@ public final class SalvageHelper {
* @param extended <p>Whether to enable extended salvage, ignoring the repairable restriction</p> * @param extended <p>Whether to enable extended salvage, ignoring the repairable restriction</p>
* @return <p>The items to return to the user, or null if not salvageable</p> * @return <p>The items to return to the user, or null if not salvageable</p>
*/ */
public static @Nullable List<ItemStack> getSalvage(@NotNull Server server, @Nullable ItemStack salvagedItem, public static @Nullable RecipeResult getSalvage(@NotNull Server server, @Nullable ItemStack salvagedItem,
@NotNull Collection<Material> trashSalvage, boolean extended) { @NotNull Collection<Material> trashSalvage, boolean extended) {
if (salvagedItem == null || salvagedItem.getAmount() < 1 || if (salvagedItem == null || salvagedItem.getAmount() < 1 ||
(!extended && !ItemHelper.isRepairable(salvagedItem))) { (!extended && !ItemHelper.isRepairable(salvagedItem))) {
return null; return null;
} }
for (Recipe recipe : server.getRecipesFor(new ItemStack(salvagedItem.getType(), salvagedItem.getAmount()))) { for (Recipe recipe : server.getRecipesFor(new ItemStack(salvagedItem.getType(), salvagedItem.getAmount()))) {
if (recipe instanceof ShapedRecipe || recipe instanceof ShapelessRecipe) { BlacksmithPlugin.debug("Considering recipe: " + recipe.getResult() + " -> " + getRawRecipeSalvage(recipe));
List<ItemStack> salvage = getRecipeSalvage(recipe, salvagedItem, trashSalvage);
if (salvage != null && !salvage.isEmpty()) { // Only consider crafting table recipes
return salvage; if (!(recipe instanceof ShapedRecipe) && !(recipe instanceof ShapelessRecipe)) {
} BlacksmithPlugin.debug("Recipe had invalid type");
continue;
}
// Make sure the player has enough items
if (salvagedItem.getAmount() < recipe.getResult().getAmount()) {
BlacksmithPlugin.debug("Too few items for recipe");
continue;
}
// Get actual salvage, as long as any can be produced
List<ItemStack> salvage = getRecipeSalvage(recipe, salvagedItem, trashSalvage);
if (salvage != null && !salvage.isEmpty()) {
BlacksmithPlugin.debug("Valid recipe: " + recipe.getResult() + " -> " + getRawRecipeSalvage(recipe));
BlacksmithPlugin.debug("Actual salvage: " + salvage);
return new RecipeResult(recipe, salvage);
} }
} }
@@ -193,19 +243,17 @@ public final class SalvageHelper {
*/ */
private static @Nullable List<ItemStack> getRecipeSalvage(@NotNull Recipe recipe, @NotNull ItemStack salvagedItem, private static @Nullable List<ItemStack> getRecipeSalvage(@NotNull Recipe recipe, @NotNull ItemStack salvagedItem,
@NotNull Collection<Material> trashSalvage) { @NotNull Collection<Material> trashSalvage) {
List<ItemStack> ingredients; List<ItemStack> ingredients = getRawRecipeSalvage(recipe);
if (recipe instanceof ShapedRecipe shapedRecipe) { if (ingredients == null) {
ingredients = getIngredients(shapedRecipe);
} else if (recipe instanceof ShapelessRecipe shapelessRecipe) {
ingredients = shapelessRecipe.getIngredientList();
} else {
//Recipes other than crafting shouldn't be considered for salvaging
return null; return null;
} }
//Make things easier by eliminating identical stacks List<ItemStack> copy = copyItems(ingredients);
ingredients = combineStacks(ingredients); BlacksmithPlugin.debug("Copied salvage: " + copy);
List<ItemStack> salvage = getSalvage(copy, salvagedItem, trashSalvage);
return combineStacks(getSalvage(copyItems(ingredients), salvagedItem, trashSalvage)); BlacksmithPlugin.debug("Combining salvage: " + salvage);
List<ItemStack> combined = combineStacks(salvage);
BlacksmithPlugin.debug("Combined : " + combined);
return combined;
} }
/** /**
@@ -240,12 +288,15 @@ public final class SalvageHelper {
int maxDurability = ItemHelper.getMaxDurability(salvagedItem); int maxDurability = ItemHelper.getMaxDurability(salvagedItem);
// Prevent divide by zero for items that don't have a set max durability // Prevent divide by zero for items that don't have a set max durability
if (maxDurability == 0) { if (maxDurability <= 0) {
maxDurability = 1; maxDurability = 1;
durability = 1; durability = 1;
} }
BlacksmithPlugin.debug("Durability: " + durability + "/" + maxDurability);
double percentageRemaining = (double) durability / maxDurability; double percentageRemaining = (double) durability / maxDurability;
BlacksmithPlugin.debug("Remaining: " + percentageRemaining);
return pickRandomSalvage(recipeItems, trashSalvage, percentageRemaining); return pickRandomSalvage(recipeItems, trashSalvage, percentageRemaining);
} }
@@ -266,36 +317,47 @@ public final class SalvageHelper {
percentageRemaining = 1; percentageRemaining = 1;
} }
int totalItems = totalItemAmount(itemsToChooseFrom); // If not damaged, just give everything
//Get the amount of recipe items to be returned if (percentageRemaining == 1) {
int itemsToReturn = (int) Math.floor(percentageRemaining * totalItems); BlacksmithPlugin.debug("100% Remaining. Copying " + itemsToChooseFrom);
int bound = itemsToChooseFrom.size(); return copyItems(itemsToChooseFrom);
}
// Split into good items and trash items
List<ItemStack> goodItems = copyItems(itemsToChooseFrom); List<ItemStack> goodItems = copyItems(itemsToChooseFrom);
goodItems.removeIf((item) -> trashSalvage.contains(item.getType())); goodItems.removeIf((item) -> trashSalvage.contains(item.getType()));
int goodSalvage = totalItemAmount(goodItems); List<ItemStack> trashItems = copyItems(itemsToChooseFrom);
trashItems.removeIf((item) -> !trashSalvage.contains(item.getType()));
List<ItemStack> salvage = new ArrayList<>(); int itemsToReturn = (int) Math.floor(totalItemAmount(itemsToChooseFrom) * percentageRemaining);
for (int i = 0; i < itemsToReturn; i++) { int goodItemAmount = totalItemAmount(goodItems);
// Pick random item int pickedItems = 0;
int itemIndex = SalvageHelper.random.nextInt(bound); List<ItemStack> salvage = new ArrayList<>(itemsToReturn);
ItemStack itemStack = itemsToChooseFrom.get(itemIndex);
// The selected item is trash, so skip it while (pickedItems < itemsToReturn && pickedItems < goodItemAmount) {
if (trashSalvage.contains(itemStack.getType()) && i < goodSalvage) { int index = SalvageHelper.random.nextInt(goodItems.size());
i--; ItemStack itemStack = goodItems.get(index);
if (itemStack.getType().isAir() || itemStack.getAmount() == 0) {
continue; continue;
} }
//Make sure to never give more of one item than the amount which exists in the recipe
if (itemStack.getAmount() <= 0) {
i--;
continue;
}
itemStack.setAmount(itemStack.getAmount() - 1);
salvage.add(new ItemStack(itemStack.getType(), 1)); salvage.add(new ItemStack(itemStack.getType(), 1));
itemStack.setAmount(itemStack.getAmount() - 1);
pickedItems++;
} }
while (pickedItems < itemsToReturn) {
int index = SalvageHelper.random.nextInt(trashItems.size());
ItemStack itemStack = trashItems.get(index);
if (itemStack.getType().isAir() || itemStack.getAmount() == 0) {
continue;
}
salvage.add(new ItemStack(itemStack.getType(), 1));
itemStack.setAmount(itemStack.getAmount() - 1);
pickedItems++;
}
return salvage; return salvage;
} }
@@ -326,7 +388,7 @@ public final class SalvageHelper {
Map<Material, Integer> itemAmounts = new HashMap<>(); Map<Material, Integer> itemAmounts = new HashMap<>();
for (ItemStack item : items) { for (ItemStack item : items) {
Material itemType = item.getType(); Material itemType = item.getType();
itemAmounts.put(itemType, itemAmounts.getOrDefault(itemType, 0) + 1); itemAmounts.put(itemType, itemAmounts.getOrDefault(itemType, 0) + item.getAmount());
} }
List<ItemStack> combined = new ArrayList<>(); List<ItemStack> combined = new ArrayList<>();
@@ -336,6 +398,26 @@ public final class SalvageHelper {
return combined; return combined;
} }
/**
* Gets the raw salvage of a recipe, assuming full salvage would be possible
*
* @param recipe <p>The recipe to get salvage for</p>
* @return <p>The salvage resulting from the recipe</p>
*/
private static List<ItemStack> getRawRecipeSalvage(@NotNull Recipe recipe) {
List<ItemStack> ingredients;
if (recipe instanceof ShapedRecipe shapedRecipe) {
ingredients = getIngredients(shapedRecipe);
} else if (recipe instanceof ShapelessRecipe shapelessRecipe) {
ingredients = shapelessRecipe.getIngredientList();
} else {
//Recipes other than crafting shouldn't be considered for salvaging
return null;
}
//Make things easier by eliminating identical stacks
return combineStacks(ingredients);
}
/** /**
* Gets all ingredients contained in the given shaped recipe * Gets all ingredients contained in the given shaped recipe
* *

View File

@@ -38,6 +38,8 @@ public final class TabCompleteValuesHelper {
case MATERIAL -> getAllReforgeAbleMaterialNames(); case MATERIAL -> getAllReforgeAbleMaterialNames();
case ENCHANTMENT, ENCHANTMENT_LIST -> getAllEnchantments(); case ENCHANTMENT, ENCHANTMENT_LIST -> getAllEnchantments();
case STRING_LIST -> List.of("*_SHOVEL,*_PICKAXE,*_AXE,*_HOE,*_SWORD:STICK,SMITHING_TABLE:*_PLANKS"); case STRING_LIST -> List.of("*_SHOVEL,*_PICKAXE,*_AXE,*_HOE,*_SWORD:STICK,SMITHING_TABLE:*_PLANKS");
// TODO: Change this to something that makes sense
case ADVANCED_COST -> List.of();
}; };
} }
@@ -77,6 +79,8 @@ public final class TabCompleteValuesHelper {
List<String> enchantments = new ArrayList<>(); List<String> enchantments = new ArrayList<>();
for (Enchantment enchantment : enchantmentRegistry) { for (Enchantment enchantment : enchantmentRegistry) {
// Note: While depreciated in Spigot, the new method is not available for Paper
//noinspection deprecation
enchantments.add(enchantment.getKey().getKey()); enchantments.add(enchantment.getKey().getKey());
} }
return enchantments; return enchantments;

View File

@@ -1,8 +1,8 @@
package net.knarcraft.blacksmith.util; package net.knarcraft.blacksmith.util;
import net.knarcraft.blacksmith.BlacksmithPlugin;
import net.knarcraft.blacksmith.config.SettingValueType; import net.knarcraft.blacksmith.config.SettingValueType;
import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import net.knarcraft.blacksmith.formatting.Translatable;
import net.knarcraft.knarlib.formatting.FormatBuilder;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
@@ -36,7 +36,8 @@ public final class TypeValidationHelper {
case STRING -> isNonEmptyString(value, sender); case STRING -> isNonEmptyString(value, sender);
case POSITIVE_INTEGER -> isPositiveInteger(value, sender); case POSITIVE_INTEGER -> isPositiveInteger(value, sender);
case PERCENTAGE -> isPercentage(value, sender); case PERCENTAGE -> isPercentage(value, sender);
case BOOLEAN -> true; // TODO: Implement proper advanced cost checking
case BOOLEAN, ADVANCED_COST -> true;
case REFORGE_ABLE_ITEMS, STRING_LIST -> isStringList(value, sender); case REFORGE_ABLE_ITEMS, STRING_LIST -> isStringList(value, sender);
case MATERIAL -> isMaterial(value, sender); case MATERIAL -> isMaterial(value, sender);
case ENCHANTMENT -> isEnchantment(value, sender); case ENCHANTMENT -> isEnchantment(value, sender);
@@ -59,8 +60,7 @@ public final class TypeValidationHelper {
boolean isMaterial = value instanceof Material || (value instanceof String string && boolean isMaterial = value instanceof Material || (value instanceof String string &&
InputParsingHelper.matchMaterial(string) != null); InputParsingHelper.matchMaterial(string) != null);
if (!isMaterial && sender != null) { if (!isMaterial && sender != null) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.ITEM_TYPE_MATERIAL).error(sender);
BlacksmithTranslatableMessage.ITEM_TYPE_MATERIAL);
} }
return isMaterial; return isMaterial;
} }
@@ -104,8 +104,7 @@ public final class TypeValidationHelper {
boolean isEnchantment = value instanceof Enchantment || (value instanceof String string && boolean isEnchantment = value instanceof Enchantment || (value instanceof String string &&
InputParsingHelper.matchEnchantment(string) != null); InputParsingHelper.matchEnchantment(string) != null);
if (!isEnchantment && sender != null) { if (!isEnchantment && sender != null) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.ITEM_TYPE_ENCHANTMENT).error(sender);
BlacksmithTranslatableMessage.ITEM_TYPE_ENCHANTMENT);
} }
return isEnchantment; return isEnchantment;
} }
@@ -120,8 +119,7 @@ public final class TypeValidationHelper {
private static boolean isStringList(@Nullable Object value, @Nullable CommandSender sender) { private static boolean isStringList(@Nullable Object value, @Nullable CommandSender sender) {
boolean isStringList = value instanceof String[] || value instanceof List<?> || value instanceof String; boolean isStringList = value instanceof String[] || value instanceof List<?> || value instanceof String;
if (!isStringList && sender != null) { if (!isStringList && sender != null) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.INPUT_STRING_LIST_REQUIRED).error(sender);
BlacksmithTranslatableMessage.INPUT_STRING_LIST_REQUIRED);
} }
return isStringList; return isStringList;
} }
@@ -137,8 +135,7 @@ public final class TypeValidationHelper {
boolean isPercentage = value != null && isPositiveInteger(value, null) && boolean isPercentage = value != null && isPositiveInteger(value, null) &&
ConfigHelper.asInt(value) >= 0 && ConfigHelper.asInt(value) <= 100; ConfigHelper.asInt(value) >= 0 && ConfigHelper.asInt(value) <= 100;
if (!isPercentage && sender != null) { if (!isPercentage && sender != null) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.INPUT_PERCENTAGE_REQUIRED).error(sender);
BlacksmithTranslatableMessage.INPUT_PERCENTAGE_REQUIRED);
} }
return isPercentage; return isPercentage;
} }
@@ -153,8 +150,7 @@ public final class TypeValidationHelper {
private static boolean isNonEmptyString(@Nullable Object value, @Nullable CommandSender sender) { private static boolean isNonEmptyString(@Nullable Object value, @Nullable CommandSender sender) {
boolean isString = value instanceof String string && !string.isBlank(); boolean isString = value instanceof String string && !string.isBlank();
if (!isString && sender != null) { if (!isString && sender != null) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.INPUT_STRING_REQUIRED).error(sender);
BlacksmithTranslatableMessage.INPUT_STRING_REQUIRED);
} }
return isString; return isString;
} }
@@ -171,8 +167,7 @@ public final class TypeValidationHelper {
return value != null && ConfigHelper.asDouble(value) >= 0.0; return value != null && ConfigHelper.asDouble(value) >= 0.0;
} catch (NumberFormatException | NullPointerException exception) { } catch (NumberFormatException | NullPointerException exception) {
if (sender != null) { if (sender != null) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.INPUT_POSITIVE_DOUBLE_REQUIRED).error(sender);
BlacksmithTranslatableMessage.INPUT_POSITIVE_DOUBLE_REQUIRED);
} }
return false; return false;
} }
@@ -190,8 +185,7 @@ public final class TypeValidationHelper {
return value != null && ConfigHelper.asInt(value) >= 0; return value != null && ConfigHelper.asInt(value) >= 0;
} catch (NumberFormatException | NullPointerException exception) { } catch (NumberFormatException | NullPointerException exception) {
if (sender != null) { if (sender != null) {
BlacksmithPlugin.getStringFormatter().displayErrorMessage(sender, new FormatBuilder(Translatable.INPUT_POSITIVE_INTEGER_REQUIRED).error(sender);
BlacksmithTranslatableMessage.INPUT_POSITIVE_INTEGER_REQUIRED);
} }
return false; return false;
} }

View File

@@ -111,6 +111,9 @@ blacksmith:
# The message to display if a player is trying to reforge an item with full durability # The message to display if a player is trying to reforge an item with full durability
notDamagedMessage: "&cThat item is not in need of repair" notDamagedMessage: "&cThat item is not in need of repair"
# The message to display when a blacksmith is clicked with an empty hand
noItemMessage: "Please present the item you want to reforge"
# Settings for the scrapper trait # Settings for the scrapper trait
scrapper: scrapper:
@@ -138,6 +141,14 @@ scrapper:
# The cost of using the scrapper to remove netherite from an item # The cost of using the scrapper to remove netherite from an item
netheriteSalvagePrice: 15 netheriteSalvagePrice: 15
# The mathematical formula for salvage cost increase when continually used within the same hour. This is necessary
# for some servers where items can be easily farmed. Set to {cost} to disable behavior.
salvageCostIncrease: "{cost}*{timesUsed}"
# The mathematical formula for salvage cooldown increase when continually used within the same hour. This is
# necessary for some servers where items can be easily farmed. Set to {cooldown} to disable behavior.
salvageCooldownIncrease: "{cooldown}*{timesUsed}"
# The settings which are set to any new scrapper NPC. To change any of these settings for an existing NPC, you must # The settings which are set to any new scrapper NPC. To change any of these settings for an existing NPC, you must
# change the Citizens NPC file, or use the /scrapper command # change the Citizens NPC file, or use the /scrapper command
@@ -179,6 +190,9 @@ scrapper:
# Whether to enable salvaging of netherite items # Whether to enable salvaging of netherite items
salvageNetherite: true salvageNetherite: true
# Whether to enable salvaging an enchanted books with several enchantments into several books with one enchantment
splitEnchantedBook: false
# Default values for messages used by NPCs # Default values for messages used by NPCs
messages: messages:
# The message to display when another player is using the scrapper # The message to display when another player is using the scrapper
@@ -223,6 +237,9 @@ scrapper:
# The message to display when explaining the shown item's netherite salvage cost # The message to display when explaining the shown item's netherite salvage cost
costMessageNetherite: "&eIt will cost &a{cost}&e to salvage that &a{item}&e into diamond!" costMessageNetherite: "&eIt will cost &a{cost}&e to salvage that &a{item}&e into diamond!"
# The message to display when explaining the shown enchanted book's salvage cost
costMessageEnchantedBook: "&eIt will cost &a{cost}&e to salvage that enchanted book!"
# The yield message to display if trying to salvage a non-damaged item # The yield message to display if trying to salvage a non-damaged item
fullSalvageMessage: "&aI should be able to extract all components from that pristine item.&r" fullSalvageMessage: "&aI should be able to extract all components from that pristine item.&r"
@@ -239,4 +256,13 @@ scrapper:
armorTrimSalvageNotFoundMessage: "&cI'm sorry, but I don't know how to salvage that armor trim!" armorTrimSalvageNotFoundMessage: "&cI'm sorry, but I don't know how to salvage that armor trim!"
# The message to display when asked to salvage netherite items, and that option is disabled # The message to display when asked to salvage netherite items, and that option is disabled
cannotSalvageNetheriteMessage: "&cI'm sorry, but I'm unable to salvage netherite items!" cannotSalvageNetheriteMessage: "&cI'm sorry, but I'm unable to salvage netherite items!"
# The message displayed when explaining that enchanted book salvage is disabled
cannotSalvageEnchantedBookMessage: "&cI'm sorry, but I'm unable to salvage enchanted books!"
# The message displayed when a player attempts to salvage an enchanted book with a single enchantment
cannotSplitEnchantedBookFurtherMessage: "&cI'm sorry, but I cannot salvage that enchanted book any further"
# The message to display when a scrapper is clicked with an empty hand
noItemMessage: "Please present the item you want to salvage"

View File

@@ -3,6 +3,7 @@ authors: [ EpicKnarvik97, aPunch, jrbudda, HurricanKai ]
version: '${project.version}' version: '${project.version}'
main: net.knarcraft.blacksmith.BlacksmithPlugin main: net.knarcraft.blacksmith.BlacksmithPlugin
depend: [ Citizens, Vault ] depend: [ Citizens, Vault ]
softdepend: [ ProtocolLib ]
prefix: "Blacksmith" prefix: "Blacksmith"
description: "A plugin that adds Blacksmith and Scrapper traits compatible with Citizens, allowing players to repair and salvage items" description: "A plugin that adds Blacksmith and Scrapper traits compatible with Citizens, allowing players to repair and salvage items"
website: "https://www.spigotmc.org/resources/blacksmith.105938/" website: "https://www.spigotmc.org/resources/blacksmith.105938/"

View File

@@ -43,7 +43,7 @@ en:
# The format used when displaying any duration remaining # The format used when displaying any duration remaining
DURATION_FORMAT: "in {time} {unit}" DURATION_FORMAT: "in {time} {unit}"
# The format used when NPCs talk to players ({npc} = The NPC's name, {message} is the actual message contents) # The format used when NPCs talk to players ({npc} = The NPC's name, {message} is the actual message contents)
NPC_TALK_FORMAT: "&a[{npc}] -> You:&r {message}" NPC_TALK_FORMAT: "&a[{npc}&r&a] -> You:&r {message}"
# Translation of the duration of less than a second # Translation of the duration of less than a second
UNIT_NOW: "imminently" UNIT_NOW: "imminently"
# Translation of seconds in singular form # Translation of seconds in singular form
@@ -89,4 +89,6 @@ en:
# The text shown if the player has to wait for more than 5 minutes # The text shown if the player has to wait for more than 5 minutes
INTERVAL_MORE_THAN_5_MINUTES: "in quite a while" INTERVAL_MORE_THAN_5_MINUTES: "in quite a while"
# The marker shown when displaying values overridden for the selected NPC (not shown if the NPC inherits the default value) # The marker shown when displaying values overridden for the selected NPC (not shown if the NPC inherits the default value)
SETTING_OVERRIDDEN_MARKER: " [NPC]" SETTING_OVERRIDDEN_MARKER: " [NPC]"
# The translation of the text displayed when a cost is expected, but a non-number is provided
DOUBLE_COST_REQUIRED: "You must supply a numeric (double) cost"