forked from Upstream/VillagerTradeLimiter
Version 1.5.0-pre2:
* Add per-player restock cooldowns * Finished commenting code TO-DO: * Add enchantment, custom model data, names, and lores to ingredient & result settings? * GUI Editor
This commit is contained in:
parent
5110befd30
commit
e708187b33
12
pom.xml
12
pom.xml
@ -43,6 +43,18 @@
|
||||
<version>1.18.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.27</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.36.0.3</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.tr7zw</groupId>
|
||||
<artifactId>functional-annotations</artifactId>
|
||||
|
@ -2,11 +2,15 @@ package com.pretzel.dev.villagertradelimiter;
|
||||
|
||||
import com.pretzel.dev.villagertradelimiter.commands.CommandManager;
|
||||
import com.pretzel.dev.villagertradelimiter.commands.CommandBase;
|
||||
import com.pretzel.dev.villagertradelimiter.data.PlayerData;
|
||||
import com.pretzel.dev.villagertradelimiter.database.DatabaseManager;
|
||||
import com.pretzel.dev.villagertradelimiter.listeners.InventoryListener;
|
||||
import com.pretzel.dev.villagertradelimiter.settings.ConfigUpdater;
|
||||
import com.pretzel.dev.villagertradelimiter.lib.Metrics;
|
||||
import com.pretzel.dev.villagertradelimiter.lib.Util;
|
||||
import com.pretzel.dev.villagertradelimiter.listeners.PlayerListener;
|
||||
import com.pretzel.dev.villagertradelimiter.settings.Lang;
|
||||
import com.pretzel.dev.villagertradelimiter.settings.Settings;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
@ -25,13 +29,16 @@ public class VillagerTradeLimiter extends JavaPlugin {
|
||||
private FileConfiguration cfg;
|
||||
private Lang lang;
|
||||
private CommandManager commandManager;
|
||||
private DatabaseManager databaseManager;
|
||||
private PlayerListener playerListener;
|
||||
private HashMap<UUID, PlayerData> playerData;
|
||||
|
||||
//Initial plugin load/unload
|
||||
/** Initial plugin load/unload */
|
||||
public void onEnable() {
|
||||
//Initialize instance variables
|
||||
this.cfg = null;
|
||||
this.commandManager = new CommandManager(this);
|
||||
this.playerData = new HashMap<>();
|
||||
|
||||
//Copy default settings & load settings
|
||||
this.getConfig().options().copyDefaults();
|
||||
@ -40,7 +47,6 @@ public class VillagerTradeLimiter extends JavaPlugin {
|
||||
this.loadBStats();
|
||||
|
||||
//Register commands and listeners
|
||||
this.playerListener = new PlayerListener(this);
|
||||
this.registerCommands();
|
||||
this.registerListeners();
|
||||
|
||||
@ -48,7 +54,15 @@ public class VillagerTradeLimiter extends JavaPlugin {
|
||||
Util.consoleMsg(PREFIX+PLUGIN_NAME+" is running!");
|
||||
}
|
||||
|
||||
//Loads or reloads config.yml and messages.yml
|
||||
/** Save database on plugin stop, server stop */
|
||||
public void onDisable() {
|
||||
for(UUID uuid : playerData.keySet()) {
|
||||
this.databaseManager.savePlayer(uuid, false);
|
||||
}
|
||||
this.playerData.clear();
|
||||
}
|
||||
|
||||
/** Loads or reloads config.yml and messages.yml */
|
||||
public void loadSettings() {
|
||||
final String mainPath = this.getDataFolder().getPath()+"/";
|
||||
final File file = new File(mainPath, "config.yml");
|
||||
@ -59,35 +73,45 @@ public class VillagerTradeLimiter extends JavaPlugin {
|
||||
}
|
||||
this.cfg = YamlConfiguration.loadConfiguration(file);
|
||||
this.lang = new Lang(this, this.getTextResource("messages.yml"), mainPath);
|
||||
|
||||
//Load/reload database manager
|
||||
if(this.databaseManager == null) this.databaseManager = new DatabaseManager(this);
|
||||
this.databaseManager.load();
|
||||
}
|
||||
|
||||
//Load and initialize the bStats class with the plugin id
|
||||
/** Load and initialize the bStats class with the plugin id */
|
||||
private void loadBStats() {
|
||||
if(this.cfg.getBoolean("bStats", true)) {
|
||||
new Metrics(this, BSTATS_ID);
|
||||
}
|
||||
}
|
||||
|
||||
//Registers plugin commands
|
||||
/** Registers plugin commands */
|
||||
private void registerCommands() {
|
||||
final CommandBase cmd = this.commandManager.getCommands();
|
||||
this.getCommand("villagertradelimiter").setExecutor(cmd);
|
||||
this.getCommand("villagertradelimiter").setTabCompleter(cmd);
|
||||
}
|
||||
|
||||
//Registers plugin listeners
|
||||
/** Registers plugin listeners */
|
||||
private void registerListeners() {
|
||||
final Settings settings = new Settings(this);
|
||||
this.playerListener = new PlayerListener(this, settings);
|
||||
this.getServer().getPluginManager().registerEvents(this.playerListener, this);
|
||||
this.getServer().getPluginManager().registerEvents(new InventoryListener(this, settings), this);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------- Getters -------------------------
|
||||
//Returns the settings from config.yml
|
||||
/** Returns the settings from config.yml */
|
||||
public FileConfiguration getCfg() { return this.cfg; }
|
||||
|
||||
//Returns a language setting from messages.yml
|
||||
/** Returns a language setting from messages.yml */
|
||||
public String getLang(final String path) { return this.lang.get(path); }
|
||||
|
||||
//Returns this plugin's player listener
|
||||
/** Returns this plugin's player listener */
|
||||
public PlayerListener getPlayerListener() { return this.playerListener; }
|
||||
|
||||
/** Returns a player's data container */
|
||||
public HashMap<UUID, PlayerData> getPlayerData() { return this.playerData; }
|
||||
}
|
||||
|
33
src/com/pretzel/dev/villagertradelimiter/data/Cooldown.java
Normal file
33
src/com/pretzel/dev/villagertradelimiter/data/Cooldown.java
Normal file
@ -0,0 +1,33 @@
|
||||
package com.pretzel.dev.villagertradelimiter.data;
|
||||
|
||||
import com.pretzel.dev.villagertradelimiter.lib.Util;
|
||||
|
||||
public class Cooldown {
|
||||
private enum Interval {
|
||||
s(1000L),
|
||||
m(60000L),
|
||||
h(3600000L),
|
||||
d(86400000L),
|
||||
w(604800000L);
|
||||
|
||||
final long factor;
|
||||
Interval(long factor) {
|
||||
this.factor = factor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeStr The cooldown time as written in config.yml (7d, 30s, 5m, etc)
|
||||
* @return The cooldown time in milliseconds
|
||||
*/
|
||||
public static long parseTime(final String timeStr) {
|
||||
try {
|
||||
long time = Long.parseLong(timeStr.substring(0, timeStr.length()-1));
|
||||
String interval = timeStr.substring(timeStr.length()-1).toLowerCase();
|
||||
return time * Interval.valueOf(interval).factor;
|
||||
} catch (Exception e) {
|
||||
Util.errorMsg(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -1,22 +1,23 @@
|
||||
package com.pretzel.dev.villagertradelimiter.data;
|
||||
|
||||
import com.pretzel.dev.villagertradelimiter.wrappers.VillagerWrapper;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class PlayerData {
|
||||
private final Player player;
|
||||
private final HashMap<String, Long> tradingCooldowns;
|
||||
private VillagerWrapper tradingVillager;
|
||||
|
||||
public PlayerData(final Player player) {
|
||||
this.player = player;
|
||||
public PlayerData() {
|
||||
this.tradingCooldowns = new HashMap<>();
|
||||
this.tradingVillager = null;
|
||||
}
|
||||
|
||||
/** @param tradingVillager The villager that this player is currently trading with */
|
||||
public void setTradingVillager(VillagerWrapper tradingVillager) { this.tradingVillager = tradingVillager; }
|
||||
/** @return The map of items to timestamps for the player's trading history */
|
||||
public HashMap<String, Long> getTradingCooldowns() { return this.tradingCooldowns; }
|
||||
|
||||
/** @return The player that this data is for */
|
||||
public Player getPlayer() { return this.player; }
|
||||
/** @param tradingVillager The villager that this player is currently trading with */
|
||||
public void setTradingVillager(final VillagerWrapper tradingVillager) { this.tradingVillager = tradingVillager; }
|
||||
|
||||
/** @return The villager that this player is currently trading with */
|
||||
public VillagerWrapper getTradingVillager() { return this.tradingVillager; }
|
||||
|
@ -0,0 +1,65 @@
|
||||
package com.pretzel.dev.villagertradelimiter.database;
|
||||
|
||||
import com.pretzel.dev.villagertradelimiter.lib.Callback;
|
||||
import com.pretzel.dev.villagertradelimiter.lib.Util;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class Database {
|
||||
protected final JavaPlugin instance;
|
||||
|
||||
public Database(final JavaPlugin instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
//Tests a DataSource
|
||||
public void test() {
|
||||
try {
|
||||
try (Connection conn = this.getSource().getConnection()) {
|
||||
if (!conn.isValid(1000)) throw new SQLException("Could not connect to database!");
|
||||
else Util.consoleMsg("Connected to database!");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
Util.consoleMsg("Could not connect to database!");
|
||||
}
|
||||
}
|
||||
|
||||
//Executes a statement or query in the database
|
||||
public ArrayList<String> execute(final String sql, boolean query) {
|
||||
try(Connection conn = this.getSource().getConnection(); PreparedStatement statement = conn.prepareStatement(sql)) {
|
||||
if(query) {
|
||||
final ResultSet result = statement.executeQuery();
|
||||
int columns = result.getMetaData().getColumnCount();
|
||||
final ArrayList<String> res = new ArrayList<>();
|
||||
while(result.next()){
|
||||
String row = "";
|
||||
for(int j = 0; j < columns; j++)
|
||||
row += result.getString(j+1)+(j < columns-1?",":"");
|
||||
res.add(row);
|
||||
}
|
||||
return res;
|
||||
} else statement.execute();
|
||||
} catch (SQLException e) {
|
||||
Util.errorMsg(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public void execute(final String sql, boolean query, final Callback<ArrayList<String>> callback) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this.instance, () -> {
|
||||
final ArrayList<String> result = execute(sql, query);
|
||||
if(callback != null) Bukkit.getScheduler().runTask(this.instance, () -> callback.call(result));
|
||||
});
|
||||
}
|
||||
|
||||
public abstract void load(final ConfigurationSection cfg);
|
||||
public abstract boolean isMySQL();
|
||||
protected abstract DataSource getSource();
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.pretzel.dev.villagertradelimiter.database;
|
||||
|
||||
import com.pretzel.dev.villagertradelimiter.VillagerTradeLimiter;
|
||||
import com.pretzel.dev.villagertradelimiter.data.Cooldown;
|
||||
import com.pretzel.dev.villagertradelimiter.data.PlayerData;
|
||||
import com.pretzel.dev.villagertradelimiter.lib.Util;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class DatabaseManager {
|
||||
private static final String CREATE_TABLE_COOLDOWN =
|
||||
"CREATE TABLE IF NOT EXISTS vtl_cooldown("+
|
||||
"uuid CHAR(36) NOT NULL,"+
|
||||
"item VARCHAR(255) NOT NULL,"+
|
||||
"time BIGINT NOT NULL,"+
|
||||
"PRIMARY KEY(uuid, item));";
|
||||
private static final String SELECT_ITEMS = "SELECT * FROM vtl_cooldown;";
|
||||
private static final String INSERT_ITEM = "INSERT OR IGNORE INTO vtl_cooldown(uuid,item,time) VALUES?;"; //INSERT IGNORE INTO for MySQL
|
||||
private static final String DELETE_ITEMS = "DELETE FROM vtl_cooldown WHERE uuid='?';";
|
||||
|
||||
private final VillagerTradeLimiter instance;
|
||||
private Database database;
|
||||
|
||||
public DatabaseManager(final VillagerTradeLimiter instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
final ConfigurationSection cfg = instance.getCfg().getConfigurationSection("database");
|
||||
if(cfg == null) {
|
||||
Util.consoleMsg("Database settings missing from config.yml!");
|
||||
this.database = null;
|
||||
return;
|
||||
}
|
||||
boolean mysql = cfg.getBoolean("mysql", false);
|
||||
if(this.database != null && ((mysql && this.database.isMySQL()) || (!mysql && !this.database.isMySQL()))) this.database.load(cfg);
|
||||
else this.database = (mysql?new MySQL(instance, cfg):new SQLite(instance));
|
||||
this.database.execute(CREATE_TABLE_COOLDOWN, false);
|
||||
|
||||
//Loads all the data
|
||||
this.database.execute(SELECT_ITEMS, true, (result,args) -> {
|
||||
if(result != null) {
|
||||
for(String row : result) {
|
||||
final String[] tokens = row.split(",");
|
||||
|
||||
UUID uuid = UUID.fromString(tokens[0]);
|
||||
String item = tokens[1];
|
||||
long time = Long.parseLong(tokens[2]);
|
||||
|
||||
PlayerData playerData = instance.getPlayerData().get(uuid);
|
||||
if(playerData == null) {
|
||||
playerData = new PlayerData();
|
||||
instance.getPlayerData().put(uuid, playerData);
|
||||
}
|
||||
|
||||
String cooldown = instance.getCfg().getString("Overrides."+item+".Cooldown", null);
|
||||
if(cooldown != null && System.currentTimeMillis() < time + Cooldown.parseTime(cooldown)) {
|
||||
playerData.getTradingCooldowns().put(item, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void savePlayer(final UUID uuid, boolean async) {
|
||||
if(this.database == null) return;
|
||||
|
||||
//Delete existing rows for player
|
||||
final String uuidStr = uuid.toString();
|
||||
if(async) this.database.execute(DELETE_ITEMS.replace("?", uuidStr), false, (result,args) -> save(uuid, true));
|
||||
else {
|
||||
this.database.execute(DELETE_ITEMS.replace("?", uuidStr), false);
|
||||
save(uuid, false);
|
||||
}
|
||||
}
|
||||
private void save(final UUID uuid, boolean async) {
|
||||
//Insert new rows for player pages
|
||||
final PlayerData playerData = instance.getPlayerData().get(uuid);
|
||||
if(playerData == null) return;
|
||||
|
||||
String values = "";
|
||||
for(String item : playerData.getTradingCooldowns().keySet()) {
|
||||
long time = playerData.getTradingCooldowns().get(item);
|
||||
|
||||
if(!values.isEmpty()) values += ",";
|
||||
values += "('"+uuid+"','"+item+"','"+time+"')";
|
||||
}
|
||||
if(values.isEmpty()) return;
|
||||
String sql = INSERT_ITEM.replace("?", values);
|
||||
if(this.database.isMySQL()) sql = sql.replace(" OR ", " ");
|
||||
if(async) this.database.execute(sql, false, (result,args) -> {});
|
||||
else this.database.execute(sql, false);
|
||||
}
|
||||
}
|
||||
|
35
src/com/pretzel/dev/villagertradelimiter/database/MySQL.java
Normal file
35
src/com/pretzel/dev/villagertradelimiter/database/MySQL.java
Normal file
@ -0,0 +1,35 @@
|
||||
package com.pretzel.dev.villagertradelimiter.database;
|
||||
|
||||
import com.mysql.cj.jdbc.MysqlConnectionPoolDataSource;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class MySQL extends Database {
|
||||
private final MysqlConnectionPoolDataSource source;
|
||||
|
||||
public MySQL(final JavaPlugin instance, final ConfigurationSection cfg) {
|
||||
super(instance);
|
||||
this.source = new MysqlConnectionPoolDataSource();
|
||||
this.load(cfg);
|
||||
}
|
||||
|
||||
public void load(final ConfigurationSection cfg) {
|
||||
this.source.setServerName(cfg.getString("host", "localhost"));
|
||||
this.source.setPort(cfg.getInt("port", 3306));
|
||||
this.source.setDatabaseName(cfg.getString("database", "sagas_holo"));
|
||||
this.source.setUser(cfg.getString("username", "root"));
|
||||
this.source.setPassword(cfg.getString("password", "root"));
|
||||
try {
|
||||
this.source.setCharacterEncoding(cfg.getString("encoding", "utf8"));
|
||||
this.source.setUseSSL(cfg.getBoolean("useSSL", false));
|
||||
} catch (SQLException e) {}
|
||||
|
||||
this.test();
|
||||
}
|
||||
|
||||
public boolean isMySQL() { return true; }
|
||||
public DataSource getSource() { return this.source; }
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.pretzel.dev.villagertradelimiter.database;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.sqlite.javax.SQLiteConnectionPoolDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class SQLite extends Database {
|
||||
private final SQLiteConnectionPoolDataSource source;
|
||||
|
||||
public SQLite(final JavaPlugin instance) {
|
||||
super(instance);
|
||||
this.source = new SQLiteConnectionPoolDataSource();
|
||||
this.load(null);
|
||||
}
|
||||
|
||||
public void load(final ConfigurationSection cfg) {
|
||||
this.source.setUrl("jdbc:sqlite:"+instance.getDataFolder().getPath()+"/database.db");
|
||||
this.test();
|
||||
}
|
||||
|
||||
public boolean isMySQL() { return false; }
|
||||
public DataSource getSource() { return this.source; }
|
||||
}
|
@ -6,5 +6,5 @@ public interface Callback<T> {
|
||||
* @param result Any type of result to be passed into the callback function
|
||||
* @param args Any extra arguments to be passed into the callback function
|
||||
*/
|
||||
void call(T result, String[] args);
|
||||
void call(T result, String... args);
|
||||
}
|
||||
|
@ -14,28 +14,49 @@ import org.bukkit.entity.Villager;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
public class Util {
|
||||
//Sends a message to the sender of a command
|
||||
public static void sendMsg(String msg, Player p) {
|
||||
if(p == null) consoleMsg(msg);
|
||||
else p.sendMessage(msg);
|
||||
/**
|
||||
* Sends a message to the sender of a command
|
||||
* @param msg The message to send
|
||||
* @param player The player (or console) to sent the message to
|
||||
*/
|
||||
public static void sendMsg(String msg, Player player) {
|
||||
if(player == null) consoleMsg(msg);
|
||||
else player.sendMessage(msg);
|
||||
}
|
||||
|
||||
//Sends a message to a player if they have permission
|
||||
/**
|
||||
* Sends a message to a player if they have permission
|
||||
* @param perm The name of the permission to check
|
||||
* @param msg The message to send
|
||||
* @param player The player (or console) to sent the message to
|
||||
*/
|
||||
public static void sendIfPermitted(String perm, String msg, Player player) {
|
||||
if(player.hasPermission(perm)) player.sendMessage(msg);
|
||||
}
|
||||
|
||||
//Sends a message to the console
|
||||
/**
|
||||
* Sends a message to the console
|
||||
* @param msg The message to send
|
||||
*/
|
||||
public static void consoleMsg(String msg) {
|
||||
if(msg != null) Bukkit.getServer().getConsoleSender().sendMessage(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the color tags to bukkit color tags
|
||||
* @param in The string to replace color tags for
|
||||
* @return The string with replaced colors
|
||||
*/
|
||||
public static String replaceColors(String in) {
|
||||
if(in == null) return null;
|
||||
return in.replace("&", "\u00A7");
|
||||
}
|
||||
|
||||
//Sends an error message to the console
|
||||
/**
|
||||
* Sends an error message to the console
|
||||
* @param e The error to send a message for
|
||||
*/
|
||||
public static void errorMsg(Exception e) {
|
||||
String error = e.toString();
|
||||
for(StackTraceElement x : e.getStackTrace()) {
|
||||
@ -44,26 +65,47 @@ public class Util {
|
||||
consoleMsg(ChatColor.RED+"ERROR: "+error);
|
||||
}
|
||||
|
||||
//Returns whether a player is a Citizens NPC or not
|
||||
/**
|
||||
* Checks whether a player is a Citizens NPC or not
|
||||
* @param player The player to check
|
||||
* @return True if the player is an NPC, false otherwise
|
||||
*/
|
||||
public static boolean isNPC(Player player) {
|
||||
return player.hasMetadata("NPC");
|
||||
}
|
||||
|
||||
//Returns whether a villager is a Citizens NPC or not
|
||||
/**
|
||||
* Returns whether a villager is a Citizens NPC or not
|
||||
* @param villager The villager to check
|
||||
* @return True if the villager is an NPC, false otherwise
|
||||
*/
|
||||
public static boolean isNPC(Villager villager) {
|
||||
return villager.hasMetadata("NPC");
|
||||
}
|
||||
|
||||
//Converts an int array to a string
|
||||
public static String intArrayToString(int[] arr) {
|
||||
/**
|
||||
* Combines the elements of an int[] into a string
|
||||
* @param arr The int[] to combine
|
||||
* @param separator (optional) The string to place between elements of the int[]
|
||||
* @return The combined string of the int[]
|
||||
*/
|
||||
public static String intArrayToString(int[] arr, String separator) {
|
||||
String res = "";
|
||||
for(int a : arr) { res += a+""; }
|
||||
for(int a : arr) { res += a+separator; }
|
||||
return res;
|
||||
}
|
||||
public static String intArrayToString(int[] arr) {
|
||||
return intArrayToString(arr, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the lines of a file into a String[]
|
||||
* @param reader The file reader
|
||||
* @return The lines of the file, as a String[]
|
||||
*/
|
||||
public static String[] readFile(Reader reader) {
|
||||
String out = "";
|
||||
BufferedReader br = null;
|
||||
BufferedReader br;
|
||||
try {
|
||||
br = new BufferedReader(reader);
|
||||
String line;
|
||||
@ -76,6 +118,12 @@ public class Util {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the lines of a file into a String[]
|
||||
* @param file The file
|
||||
* @return The lines of the file, as a String[]
|
||||
*/
|
||||
public static String[] readFile(File file) {
|
||||
try {
|
||||
return readFile(new FileReader(file));
|
||||
@ -85,8 +133,13 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a String to a file
|
||||
* @param file The file to write the String to
|
||||
* @param out The String to write to the file
|
||||
*/
|
||||
public static void writeFile(File file, String out) {
|
||||
BufferedWriter bw = null;
|
||||
BufferedWriter bw;
|
||||
try {
|
||||
bw = new BufferedWriter(new FileWriter(file));
|
||||
bw.write(out);
|
||||
|
@ -0,0 +1,114 @@
|
||||
package com.pretzel.dev.villagertradelimiter.listeners;
|
||||
|
||||
import com.pretzel.dev.villagertradelimiter.VillagerTradeLimiter;
|
||||
import com.pretzel.dev.villagertradelimiter.data.PlayerData;
|
||||
import com.pretzel.dev.villagertradelimiter.lib.Util;
|
||||
import com.pretzel.dev.villagertradelimiter.settings.Settings;
|
||||
import com.pretzel.dev.villagertradelimiter.wrappers.VillagerWrapper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.MerchantRecipe;
|
||||
|
||||
public class InventoryListener implements Listener {
|
||||
private final VillagerTradeLimiter instance;
|
||||
private final Settings settings;
|
||||
|
||||
/**
|
||||
* @param instance The instance of VillagerTradeLimiter.java
|
||||
* @param settings The settings instance
|
||||
*/
|
||||
public InventoryListener(final VillagerTradeLimiter instance, final Settings settings) {
|
||||
this.instance = instance;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/** Handles when a player stops trading with a villager */
|
||||
@EventHandler
|
||||
public void onPlayerStopTrading(final InventoryCloseEvent event) {
|
||||
//Don't do anything unless the player is actually finished trading with a villager
|
||||
if(event.getInventory().getType() != InventoryType.MERCHANT) return;
|
||||
if(!(event.getInventory().getHolder() instanceof Villager)) return;
|
||||
if(!(event.getPlayer() instanceof Player)) return;
|
||||
final Player player = (Player)event.getPlayer();
|
||||
if(Util.isNPC(player)) return;
|
||||
|
||||
//Reset the villager's NBT data when a player is finished trading
|
||||
final PlayerData playerData = instance.getPlayerData().get(player.getUniqueId());
|
||||
if(playerData == null) return;
|
||||
|
||||
final VillagerWrapper villager = playerData.getTradingVillager();
|
||||
if(villager == null) return;
|
||||
playerData.setTradingVillager(null);
|
||||
villager.reset();
|
||||
}
|
||||
|
||||
/** Handles when a player successfully trades with a villager */
|
||||
@EventHandler
|
||||
public void onPlayerMakeTrade(final InventoryClickEvent event) {
|
||||
if(event.getInventory().getType() != InventoryType.MERCHANT) return;
|
||||
if(!(event.getInventory().getHolder() instanceof Villager)) return;
|
||||
if(!(event.getWhoClicked() instanceof Player)) return;
|
||||
if(event.getRawSlot() != 2) return;
|
||||
final Player player = (Player)event.getWhoClicked();
|
||||
if(Util.isNPC(player)) return;
|
||||
|
||||
//Get the items involved in the trade
|
||||
final ItemStack result = event.getCurrentItem();
|
||||
ItemStack ingredient1 = event.getInventory().getItem(0);
|
||||
ItemStack ingredient2 = event.getInventory().getItem(1);
|
||||
if(result == null || result.getType() == Material.AIR) return;
|
||||
if(ingredient1 == null) ingredient1 = new ItemStack(Material.AIR, 1);
|
||||
if(ingredient2 == null) ingredient2 = new ItemStack(Material.AIR, 1);
|
||||
|
||||
//Check if there is a cooldown set for the trade
|
||||
final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides");
|
||||
if(overrides == null) return;
|
||||
|
||||
final String type = settings.getType(result, ingredient1, ingredient2);
|
||||
if(type == null || !overrides.contains(type+".Cooldown")) return;
|
||||
|
||||
//Get the selected recipe by the items in the slots
|
||||
final MerchantRecipe selectedRecipe = getSelectedRecipe((Villager)event.getInventory().getHolder(), ingredient1, ingredient2, result);
|
||||
if(selectedRecipe == null) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//Add a cooldown to the trade if the player has reached the max uses
|
||||
final PlayerData playerData = instance.getPlayerData().get(player.getUniqueId());
|
||||
if(playerData == null || playerData.getTradingVillager() == null) return;
|
||||
Bukkit.getScheduler().runTaskLater(instance, () -> {
|
||||
int uses = selectedRecipe.getUses();
|
||||
if(!playerData.getTradingCooldowns().containsKey(type) && uses >= selectedRecipe.getMaxUses()) {
|
||||
playerData.getTradingCooldowns().put(type, System.currentTimeMillis());
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param villager The villager to get the recipe from
|
||||
* @param ingredient1 The item in the first ingredient slot of the trade interface
|
||||
* @param ingredient2 The item in the second ingredient slot of the trade interface
|
||||
* @param result The item in the result slot of the trade interface
|
||||
* @return The villager's recipe that matches the items in the slots
|
||||
*/
|
||||
private MerchantRecipe getSelectedRecipe(final Villager villager, final ItemStack ingredient1, final ItemStack ingredient2, final ItemStack result) {
|
||||
for(MerchantRecipe recipe : villager.getRecipes()) {
|
||||
final ItemStack item1 = recipe.getIngredients().get(0);
|
||||
final ItemStack item2 = recipe.getIngredients().get(1);
|
||||
if(!recipe.getResult().isSimilar(result)) continue;
|
||||
if((item1.isSimilar(ingredient1) && item2.isSimilar(ingredient2)) || (item1.isSimilar(ingredient2) && item2.isSimilar(ingredient1)))
|
||||
return recipe;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.pretzel.dev.villagertradelimiter.listeners;
|
||||
|
||||
import com.pretzel.dev.villagertradelimiter.VillagerTradeLimiter;
|
||||
import com.pretzel.dev.villagertradelimiter.data.Cooldown;
|
||||
import com.pretzel.dev.villagertradelimiter.data.PlayerData;
|
||||
import com.pretzel.dev.villagertradelimiter.lib.Util;
|
||||
import com.pretzel.dev.villagertradelimiter.settings.Settings;
|
||||
@ -11,31 +12,28 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryPickupItemEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class PlayerListener implements Listener {
|
||||
private final VillagerTradeLimiter instance;
|
||||
private final Settings settings;
|
||||
private final HashMap<Player, PlayerData> playerData;
|
||||
|
||||
/** @param instance The instance of VillagerTradeLimiter.java */
|
||||
public PlayerListener(VillagerTradeLimiter instance) {
|
||||
/**
|
||||
* @param instance The instance of VillagerTradeLimiter.java
|
||||
* @param settings The settings instance
|
||||
*/
|
||||
public PlayerListener(final VillagerTradeLimiter instance, final Settings settings) {
|
||||
this.instance = instance;
|
||||
this.settings = new Settings(instance);
|
||||
this.playerData = new HashMap<>();
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/** Handles when a player begins trading with a villager */
|
||||
@EventHandler
|
||||
public void onPlayerBeginTrading(PlayerInteractEntityEvent event) {
|
||||
public void onPlayerBeginTrading(final PlayerInteractEntityEvent event) {
|
||||
if(!(event.getRightClicked() instanceof Villager)) return;
|
||||
final Villager villager = (Villager)event.getRightClicked();
|
||||
if(Util.isNPC(villager)) return; //Skips NPCs
|
||||
@ -44,11 +42,13 @@ public class PlayerListener implements Listener {
|
||||
|
||||
//DisableTrading feature
|
||||
if(instance.getCfg().isBoolean("DisableTrading")) {
|
||||
//If all trading is disabled
|
||||
if(instance.getCfg().getBoolean("DisableTrading", false)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
//If trading in the world the player is in is disabled
|
||||
final List<String> disabledWorlds = instance.getCfg().getStringList("DisableTrading");
|
||||
final String world = event.getPlayer().getWorld().getName();
|
||||
for(String disabledWorld : disabledWorlds) {
|
||||
@ -59,28 +59,15 @@ public class PlayerListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
//Cancel the original event, and open the adjusted trade view
|
||||
event.setCancelled(true);
|
||||
final Player player = event.getPlayer();
|
||||
if(!instance.getPlayerData().containsKey(player.getUniqueId())) {
|
||||
instance.getPlayerData().put(player.getUniqueId(), new PlayerData());
|
||||
}
|
||||
this.see(villager, player, player);
|
||||
}
|
||||
|
||||
/** Handles when a player stops trading with a villager */
|
||||
@EventHandler
|
||||
public void onPlayerStopTrading(final InventoryCloseEvent event) {
|
||||
//Don't do anything unless the player is actually finished trading with a villager
|
||||
if(event.getInventory().getType() != InventoryType.MERCHANT) return;
|
||||
if(!(event.getPlayer() instanceof Player)) return;
|
||||
final Player player = (Player)event.getPlayer();
|
||||
if(Util.isNPC(player)) return;
|
||||
if(getPlayerData(player).getTradingVillager() == null) return;
|
||||
|
||||
//Reset the villager's NBT data when a player is finished trading
|
||||
final VillagerWrapper villager = playerData.get(player).getTradingVillager();
|
||||
getPlayerData(player).setTradingVillager(null);
|
||||
if(villager == null) return;
|
||||
villager.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the villager's trading menu, with the adjusted trades of another player (or the same player)
|
||||
* @param villager The villager whose trades you want to see
|
||||
@ -93,6 +80,9 @@ public class PlayerListener implements Listener {
|
||||
final PlayerWrapper otherWrapper = new PlayerWrapper(other);
|
||||
if(Util.isNPC(villager) || Util.isNPC(player) || otherWrapper.isNPC()) return; //Skips NPCs
|
||||
|
||||
final PlayerData playerData = instance.getPlayerData().get(other.getUniqueId());
|
||||
if(playerData != null) playerData.setTradingVillager(villagerWrapper);
|
||||
|
||||
//Checks if the version is old, before the 1.16 UUID changes
|
||||
String version = instance.getServer().getClass().getPackage().getName();
|
||||
boolean isOld = version.contains("1_13_") || version.contains("1_14_") || version.contains("1_15_");
|
||||
@ -108,7 +98,7 @@ public class PlayerListener implements Listener {
|
||||
recipe.setSpecialPrice(getDiscount(recipe, totalReputation, hotvDiscount));
|
||||
|
||||
//Set ingredient materials and amounts
|
||||
final ConfigurationSection override = settings.getOverride(recipe);
|
||||
final ConfigurationSection override = settings.getOverride(recipe.getItemStack("buy"), recipe.getItemStack("sell"));
|
||||
if(override != null) {
|
||||
setIngredient(override.getConfigurationSection("Item1"), recipe.getIngredient1());
|
||||
setIngredient(override.getConfigurationSection("Item2"), recipe.getIngredient2());
|
||||
@ -116,19 +106,13 @@ public class PlayerListener implements Listener {
|
||||
}
|
||||
|
||||
//Set the maximum number of uses (trades/day)
|
||||
recipe.setMaxUses(getMaxUses(recipe));
|
||||
recipe.setMaxUses(getMaxUses(recipe, other));
|
||||
}
|
||||
|
||||
//Open the villager's trading menu
|
||||
getPlayerData(player).setTradingVillager(villagerWrapper);
|
||||
player.openMerchant(villager, false);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPickupItem(InventoryPickupItemEvent event) {
|
||||
Util.consoleMsg("Picked up!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param recipe The recipe to get the base price for
|
||||
* @return The initial price of a recipe/trade, before any discounts are applied
|
||||
@ -178,11 +162,28 @@ public class PlayerListener implements Listener {
|
||||
* @param recipe The recipe to get the MaxUses for
|
||||
* @return The current maximum number of times a player can make a trade before the villager restocks
|
||||
*/
|
||||
private int getMaxUses(final RecipeWrapper recipe) {
|
||||
private int getMaxUses(final RecipeWrapper recipe, final OfflinePlayer player) {
|
||||
int uses = recipe.getMaxUses();
|
||||
int maxUses = settings.fetchInt(recipe, "MaxUses", -1);
|
||||
boolean disabled = settings.fetchBoolean(recipe, "Disabled", false);
|
||||
|
||||
final PlayerData playerData = instance.getPlayerData().get(player.getUniqueId());
|
||||
if(playerData != null && playerData.getTradingVillager() != null) {
|
||||
final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides");
|
||||
if(overrides != null) {
|
||||
final String type = settings.getType(recipe.getItemStack("sell"), recipe.getItemStack("buy"), recipe.getItemStack("buyB"));
|
||||
if(type != null && overrides.contains(type+".Cooldown")) {
|
||||
if(playerData.getTradingCooldowns().containsKey(type)) {
|
||||
if(System.currentTimeMillis() >= playerData.getTradingCooldowns().get(type) + Cooldown.parseTime(overrides.getString(type+".Cooldown"))) {
|
||||
playerData.getTradingCooldowns().remove(type);
|
||||
} else {
|
||||
maxUses = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(maxUses < 0) maxUses = uses;
|
||||
if(disabled) maxUses = 0;
|
||||
return maxUses;
|
||||
@ -220,15 +221,4 @@ public class PlayerListener implements Listener {
|
||||
ingredient.setMaterialId("minecraft:"+item.getString("Material", ingredient.getMaterialId()).replace("minecraft:",""));
|
||||
ingredient.setAmount(item.getInt("Amount", ingredient.getAmount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player The player to get the data container for
|
||||
* @return The data container for the given player
|
||||
*/
|
||||
private PlayerData getPlayerData(final Player player) {
|
||||
if(playerData.containsKey(player) && playerData.get(player) != null) return playerData.get(player);
|
||||
final PlayerData pd = new PlayerData(player);
|
||||
playerData.put(player, pd);
|
||||
return pd;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ public class Settings {
|
||||
*/
|
||||
public boolean fetchBoolean(final RecipeWrapper recipe, String key, boolean defaultValue) {
|
||||
boolean global = instance.getCfg().getBoolean(key, defaultValue);
|
||||
final ConfigurationSection override = getOverride(recipe);
|
||||
final ConfigurationSection override = getOverride(recipe.getItemStack("buy"), recipe.getItemStack("sell"));
|
||||
if(override != null) return override.getBoolean(key, global);
|
||||
return global;
|
||||
}
|
||||
@ -38,7 +38,7 @@ public class Settings {
|
||||
*/
|
||||
public int fetchInt(final RecipeWrapper recipe, String key, int defaultValue) {
|
||||
int global = instance.getCfg().getInt(key, defaultValue);
|
||||
final ConfigurationSection override = getOverride(recipe);
|
||||
final ConfigurationSection override = getOverride(recipe.getItemStack("buy"), recipe.getItemStack("sell"));
|
||||
if(override != null) return override.getInt(key, global);
|
||||
return global;
|
||||
}
|
||||
@ -51,20 +51,51 @@ public class Settings {
|
||||
*/
|
||||
public double fetchDouble(final RecipeWrapper recipe, String key, double defaultValue) {
|
||||
double global = instance.getCfg().getDouble(key, defaultValue);
|
||||
final ConfigurationSection override = getOverride(recipe);
|
||||
final ConfigurationSection override = getOverride(recipe.getItemStack("buy"), recipe.getItemStack("sell"));
|
||||
if(override != null) return override.getDouble(key, global);
|
||||
return global;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param recipe The wrapped recipe to fetch any overrides for
|
||||
* @param result The itemstack for the recipe's result
|
||||
* @param ingredient1 The itemstack for the recipe's first ingredient
|
||||
* @param ingredient2 The itemstack for the recipe's second ingredient
|
||||
* @return The matched type of the item, if any
|
||||
*/
|
||||
public String getType(final ItemStack result, final ItemStack ingredient1, final ItemStack ingredient2) {
|
||||
final String resultType = result.getType().name().toLowerCase();
|
||||
final String ingredient1Type = ingredient1.getType().name().toLowerCase();
|
||||
final String ingredient2Type = ingredient2.getType().name().toLowerCase();
|
||||
|
||||
if(result.getType() == Material.ENCHANTED_BOOK) {
|
||||
final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) result.getItemMeta();
|
||||
if(meta == null) return null;
|
||||
for(Enchantment key : meta.getStoredEnchants().keySet()) {
|
||||
if (key != null) {
|
||||
final String itemType = key.getKey().getKey() +"_"+meta.getStoredEnchantLevel(key);
|
||||
if(getItem(ingredient1, result, itemType) != null) return itemType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final ItemStack ingredient = (ingredient1.getType() == Material.AIR ? ingredient2 : ingredient1);
|
||||
if(getItem(ingredient, result, resultType) != null) return resultType;
|
||||
if(getItem(ingredient, result, ingredient1Type) != null) return ingredient1Type;
|
||||
if(getItem(ingredient, result, ingredient2Type) != null) return ingredient2Type;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param buy The first ingredient of the recipe
|
||||
* @param sell The result of the recipe
|
||||
* @return The corresponding override config section for the recipe, if it exists, or null
|
||||
*/
|
||||
public ConfigurationSection getOverride(final RecipeWrapper recipe) {
|
||||
public ConfigurationSection getOverride(final ItemStack buy, ItemStack sell) {
|
||||
final ConfigurationSection overrides = instance.getCfg().getConfigurationSection("Overrides");
|
||||
if(overrides != null) {
|
||||
for(final String override : overrides.getKeys(false)) {
|
||||
final ConfigurationSection item = this.getItem(recipe, override);
|
||||
final ConfigurationSection item = this.getItem(buy, sell, override);
|
||||
if(item != null) return item;
|
||||
}
|
||||
}
|
||||
@ -72,17 +103,18 @@ public class Settings {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param recipe The wrapped recipe to fetch any overrides for
|
||||
* @param buy The first ingredient of the recipe
|
||||
* @param sell The result of the recipe
|
||||
* @param key The key where the override settings are stored in config.yml
|
||||
* @return The corresponding override config section for the recipe, if it exists, or null
|
||||
*/
|
||||
public ConfigurationSection getItem(final RecipeWrapper recipe, final String key) {
|
||||
public ConfigurationSection getItem(final ItemStack buy, final ItemStack sell, final String key) {
|
||||
final ConfigurationSection item = instance.getCfg().getConfigurationSection("Overrides."+key);
|
||||
if(item == null) return null;
|
||||
|
||||
if(!key.contains("_")) {
|
||||
//Return the item if the item name is valid
|
||||
if(this.verify(recipe, Material.matchMaterial(key))) return item;
|
||||
if(this.verify(buy, sell, Material.matchMaterial(key))) return item;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -90,15 +122,15 @@ public class Settings {
|
||||
try {
|
||||
//Return the enchanted book item if there's a number in the item name
|
||||
final int level = Integer.parseInt(words[words.length-1]);
|
||||
if(recipe.getSellItemStack().getType() == Material.ENCHANTED_BOOK) {
|
||||
final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) recipe.getSellItemStack().getItemMeta();
|
||||
if(sell.getType() == Material.ENCHANTED_BOOK) {
|
||||
final EnchantmentStorageMeta meta = (EnchantmentStorageMeta) sell.getItemMeta();
|
||||
final Enchantment enchantment = EnchantmentWrapper.getByKey(NamespacedKey.minecraft(key.substring(0, key.lastIndexOf("_"))));
|
||||
if (meta == null || enchantment == null) return null;
|
||||
if (meta.hasStoredEnchant(enchantment) && meta.getStoredEnchantLevel(enchantment) == level) return item;
|
||||
}
|
||||
} catch(NumberFormatException e) {
|
||||
//Return the item if the item name is valid
|
||||
if(this.verify(recipe, Material.matchMaterial(key)))
|
||||
if(this.verify(buy, sell, Material.matchMaterial(key)))
|
||||
return item;
|
||||
return null;
|
||||
} catch(Exception e2) {
|
||||
@ -109,11 +141,12 @@ public class Settings {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param recipe The wrapped recipe to match with the override setting
|
||||
* @param buy The first ingredient of the recipe
|
||||
* @param sell The result of the recipe
|
||||
* @param material The material to compare the recipe against
|
||||
* @return True if a recipe matches an override section, false otherwise
|
||||
*/
|
||||
private boolean verify(final RecipeWrapper recipe, final Material material) {
|
||||
return ((recipe.getSellItemStack().getType() == material) || (recipe.getBuyItemStack().getType() == material));
|
||||
private boolean verify(final ItemStack buy, final ItemStack sell, final Material material) {
|
||||
return ((buy.getType() == material) || (sell.getType() == material));
|
||||
}
|
||||
}
|
||||
|
@ -71,9 +71,6 @@ public class RecipeWrapper {
|
||||
/** @return The maximum number of times a player can make a trade before the villager restocks */
|
||||
public int getMaxUses() { return recipe.getInteger("maxUses"); }
|
||||
|
||||
/** @return The ItemStack representation of the first ingredient */
|
||||
public ItemStack getBuyItemStack() { return recipe.getItemStack("buy"); }
|
||||
|
||||
/** @return The ItemStack representation of the result */
|
||||
public ItemStack getSellItemStack() { return recipe.getItemStack("sell"); }
|
||||
/** @return The ItemStack representation of an ingredient or the result */
|
||||
public ItemStack getItemStack(final String key) { return recipe.getItemStack(key); }
|
||||
}
|
||||
|
@ -8,6 +8,17 @@
|
||||
# This helps me keep track of what server versions are being used. Please leave this set to true.
|
||||
bStats: true
|
||||
|
||||
# Database connection settings
|
||||
database:
|
||||
mysql: false
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
database: villagertradelimiter
|
||||
username: root
|
||||
password: root
|
||||
encoding: utf8
|
||||
useSSL: false
|
||||
|
||||
# Add world names for worlds that you want to completely disable ALL villager trading. Set to [] to disable this feature.
|
||||
DisableTrading:
|
||||
- world_nether
|
||||
@ -17,7 +28,7 @@ DisableTrading:
|
||||
# * Set to -1 to disable this feature and keep vanilla behavior.
|
||||
# * Set to a number between 0 and 5 to set the maximum HotV effect level players can have
|
||||
# For more information, see https://minecraft.fandom.com/wiki/Hero_of_the_Village#Price_decrement
|
||||
MaxHeroLevel: 1
|
||||
MaxHeroLevel: -1
|
||||
|
||||
# The maximum discount (%) you can get from trading/healing zombie villagers. This limits reputation-based price decreases.
|
||||
# * Set to -1.0 to disable this feature and keep vanilla behavior
|
||||
@ -55,12 +66,13 @@ Overrides:
|
||||
MaxDiscount: -1.0
|
||||
MaxDemand: 60
|
||||
MaxUses: 2
|
||||
Cooldown: 7d
|
||||
Item1:
|
||||
Material: "book"
|
||||
Amount: 64
|
||||
Item2:
|
||||
Material: "ink_sac"
|
||||
Amount: 64
|
||||
Amount: 48
|
||||
Result:
|
||||
Material: "name_tag"
|
||||
Amount: 2
|
||||
|
Loading…
Reference in New Issue
Block a user