Oh, so very much formatting and stuffs

This commit is contained in:
gomeow
2013-05-23 22:57:16 -07:00
parent e2c327bd46
commit e3516774f3
19 changed files with 519 additions and 428 deletions
@@ -7,25 +7,19 @@ import org.bukkit.inventory.ItemStack;
import com.drtshock.playervaults.PlayerVaults;
/**
* A class that contains a method to drop the contents of a player's vault when they die.
*/
public class DropOnDeath {
public static PlayerVaults PLUGIN;
public DropOnDeath(PlayerVaults instance) {
DropOnDeath.PLUGIN = instance;
}
static VaultManager VAULT_MANAGER = new VaultManager(PLUGIN);
/**
* Drops all items when a player dies.
* @param player
* @param player The player to drop the inventory of.
*/
public static void drop(Player player) {
Location loc = player.getLocation();
for(int count = 1; count <= PlayerVaults.INVENTORIES_TO_DROP; count++) {
Inventory inv = VAULT_MANAGER.getVault(player, count);
Inventory inv = PlayerVaults.VM.getVault(player.getName(), count);
ItemStack[] stack = inv.getContents();
for(ItemStack is:stack) {
loc.getWorld().dropItemNaturally(loc, is);
@@ -13,6 +13,9 @@ import org.bukkit.entity.Player;
import com.drtshock.playervaults.PlayerVaults;
/**
* A class that handles all economy operations.
*/
public class EconomyOperations {
private static File CONFIG_FILE;
@@ -28,17 +31,16 @@ public class EconomyOperations {
/**
* Have a player pay to open a vault.
* Returns true if successful. Otherwise false.
* @param player
* @return transaction success
* @param player The player to pay.
* @return The transaction success.
*/
public static boolean payToOpen(Player player) {
if(!BUKKIT_CONFIG.getBoolean("economy.enabled") || player.hasPermission("playervaults.free") || !PlayerVaults.USE_VAULT)
if (!BUKKIT_CONFIG.getBoolean("economy.enabled") || player.hasPermission("playervaults.free") || !PlayerVaults.USE_VAULT)
return true;
double cost = BUKKIT_CONFIG.getDouble("economy.cost-to-open", 10);
EconomyResponse resp = PlayerVaults.ECON.withdrawPlayer(player.getName(), cost);
if(resp.transactionSuccess()) {
if (resp.transactionSuccess()) {
player.sendMessage(Lang.TITLE.toString() + Lang.COST_TO_OPEN.toString().replaceAll("%price", "" + cost));
return true;
}
@@ -48,17 +50,16 @@ public class EconomyOperations {
/**
* Have a player pay to create a vault.
* Returns true if successful. Otherwise false.
* @param player
* @return transaction success
* @param player The player to pay.
* @return The transaction success
*/
public static boolean payToCreate(Player player) {
if(!BUKKIT_CONFIG.getBoolean("economy.enabled") || player.hasPermission("playervaults.free") || !PlayerVaults.USE_VAULT)
if (!BUKKIT_CONFIG.getBoolean("economy.enabled") || player.hasPermission("playervaults.free") || !PlayerVaults.USE_VAULT)
return true;
double cost = BUKKIT_CONFIG.getDouble("economy.cost-to-create", 100);
EconomyResponse resp = PlayerVaults.ECON.withdrawPlayer(player.getName(), cost);
if(resp.transactionSuccess()) {
if (resp.transactionSuccess()) {
player.sendMessage(Lang.TITLE.toString() + Lang.COST_TO_CREATE.toString().replaceAll("%price", "" + cost));
return true;
}
@@ -68,20 +69,19 @@ public class EconomyOperations {
/**
* Have a player get his money back when vault is deleted.
* Returns true if successful. Otherwise false.
* @param player
* @return transaction success.
* @param player The player to receive the money.
* @return The transaction success.
*/
public static boolean refundOnDelete(Player player, int number) {
String directory = "plugins" + File.separator + "PlayerVaults" + File.separator + "vaults";
if(!BUKKIT_CONFIG.getBoolean("economy.enabled") || player.hasPermission("playervaults.free") || !PlayerVaults.USE_VAULT)
if (!BUKKIT_CONFIG.getBoolean("economy.enabled") || player.hasPermission("playervaults.free") || !PlayerVaults.USE_VAULT) {
return true;
}
String name = player.getName().toLowerCase();
File file = new File(directory + File.separator + name.toLowerCase() + ".yml");
YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file);
if(file.exists()) {
if(playerFile.getString("vault" + number) == null) {
if (file.exists()) {
if (playerFile.getString("vault" + number) == null) {
player.sendMessage(Lang.TITLE.toString() + ChatColor.RED + Lang.VAULT_DOES_NOT_EXIST);
return false;
}
@@ -92,7 +92,7 @@ public class EconomyOperations {
}
double cost = BUKKIT_CONFIG.getDouble("economy.refund-on-delete");
EconomyResponse resp = PlayerVaults.ECON.depositPlayer(player.getName(), cost);
if(resp.transactionSuccess()) {
if (resp.transactionSuccess()) {
player.sendMessage(Lang.TITLE.toString() + Lang.REFUND_AMOUNT.toString().replaceAll("%price", String.valueOf(cost)));
return true;
}
@@ -3,6 +3,9 @@ package com.drtshock.playervaults.util;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.YamlConfiguration;
/**
* An enum for requesting strings from the language file.
*/
public enum Lang {
TITLE("title-name", "&4[&fPlayerVaults&4]:"),
OPEN_VAULT("open-vault", "&fOpening vault &a%v"),
@@ -21,34 +24,51 @@ public enum Lang {
COST_TO_OPEN("cost-to-open", "&fYou were charged &c%price &ffor opening that vault."),
VAULT_DOES_NOT_EXIST("vault-does-not-exist", "&cThat vault does not exist!"),
CLICK_A_SIGN("click-a-sign", "&fNow click a sign!"),
NOT_A_SIGN("not-a-sign","&cYou must click a sign!"),
NOT_A_SIGN("not-a-sign", "&cYou must click a sign!"),
SET_SIGN("set-sign-success", "&fYou have successfully set a PlayerVault access sign!"),
OPEN_WITH_SIGN("open-with-sign", "&fOpening vault &a%v &fof &a%p");
private String path;
private String def; // Default string
private String def;
private static YamlConfiguration LANG;
/**
* Lang enum constructor.
* @param path The string path.
* @param start The default string.
*/
Lang(String path, String start) {
this.path = path;
this.def = start;
}
public static void setFile(YamlConfiguration yc) {
LANG = yc;
/**
* Set the {@code YamlConfiguration} to use.
* @param config The config to set.
*/
public static void setFile(YamlConfiguration config) {
LANG = config;
}
@Override
public String toString() {
if(this == TITLE)
if (this == TITLE)
return ChatColor.translateAlternateColorCodes('&', LANG.getString(this.path, def)) + " ";
return ChatColor.translateAlternateColorCodes('&', LANG.getString(this.path, def));
}
/**
* Get the default value of the path.
* @return The default value of the path.
*/
public String getDefault() {
return this.def;
}
/**
* Get the path to the string.
* @return The path to the string.
*/
public String getPath() {
return this.path;
}
@@ -134,7 +134,7 @@ public class Metrics {
private volatile int taskId = -1;
public Metrics(final Plugin plugin) throws IOException {
if(plugin == null) {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null");
}
@@ -149,7 +149,7 @@ public class Metrics {
configuration.addDefault("guid", UUID.randomUUID().toString());
// Do we need to create the file?
if(configuration.get("guid", null) == null) {
if (configuration.get("guid", null) == null) {
configuration.options().header("http://mcstats.org").copyDefaults(true);
configuration.save(configurationFile);
}
@@ -166,7 +166,7 @@ public class Metrics {
* @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
*/
public Graph createGraph(final String name) {
if(name == null) {
if (name == null) {
throw new IllegalArgumentException("Graph name cannot be null");
}
@@ -186,7 +186,7 @@ public class Metrics {
* @param graph The name of the graph
*/
public void addGraph(final Graph graph) {
if(graph == null) {
if (graph == null) {
throw new IllegalArgumentException("Graph cannot be null");
}
@@ -199,7 +199,7 @@ public class Metrics {
* @param plotter The plotter to use to plot custom data
*/
public void addCustomData(final Plotter plotter) {
if(plotter == null) {
if (plotter == null) {
throw new IllegalArgumentException("Plotter cannot be null");
}
@@ -219,14 +219,14 @@ public class Metrics {
*/
@SuppressWarnings("deprecation")
public boolean start() {
synchronized (optOutLock) {
synchronized(optOutLock) {
// Did we opt out?
if(isOptOut()) {
if (isOptOut()) {
return false;
}
// Is metrics already running?
if(taskId >= 0) {
if (taskId >= 0) {
return true;
}
@@ -238,10 +238,10 @@ public class Metrics {
public void run() {
try {
// This has to be synchronized or it can collide with the disable method.
synchronized (optOutLock) {
synchronized(optOutLock) {
// Disable Task, if it is running and the server owner decided to
// opt-out
if(isOptOut() && taskId > 0) {
if (isOptOut() && taskId > 0) {
plugin.getServer().getScheduler().cancelTask(taskId);
taskId = -1;
// Tell all plotters to stop gathering information.
@@ -276,7 +276,7 @@ public class Metrics {
* @return true if metrics should be opted out of it
*/
public boolean isOptOut() {
synchronized (optOutLock) {
synchronized(optOutLock) {
try {
// Reload the metrics file
configuration.load(getConfigFile());
@@ -298,15 +298,15 @@ public class Metrics {
*/
public void enable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (optOutLock) {
synchronized(optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if(isOptOut()) {
if (isOptOut()) {
configuration.set("opt-out", false);
configuration.save(configurationFile);
}
// Enable Task, if it is not running
if(taskId < 0) {
if (taskId < 0) {
start();
}
}
@@ -319,15 +319,15 @@ public class Metrics {
*/
public void disable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (optOutLock) {
synchronized(optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if(!isOptOut()) {
if (!isOptOut()) {
configuration.set("opt-out", true);
configuration.save(configurationFile);
}
// Disable Task, if it is running
if(taskId > 0) {
if (taskId > 0) {
this.plugin.getServer().getScheduler().cancelTask(taskId);
taskId = -1;
}
@@ -369,16 +369,16 @@ public class Metrics {
encodeDataPair(data, "revision", String.valueOf(REVISION));
// If we're pinging, append it
if(isPing) {
if (isPing) {
encodeDataPair(data, "ping", "true");
}
// Acquire a lock on the graphs, which lets us make the assumption we also lock everything
// inside of the graph (e.g plotters)
synchronized (graphs) {
synchronized(graphs) {
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
while(iter.hasNext()) {
final Graph graph = iter.next();
for(Plotter plotter:graph.getPlotters()) {
@@ -405,7 +405,7 @@ public class Metrics {
// Mineshafter creates a socks proxy, so we can safely bypass it
// It does not reroute POST requests so we need to go around it
if(isMineshafterPresent()) {
if (isMineshafterPresent()) {
connection = url.openConnection(Proxy.NO_PROXY);
} else {
connection = url.openConnection();
@@ -426,15 +426,15 @@ public class Metrics {
writer.close();
reader.close();
if(response == null || response.startsWith("ERR")) {
if (response == null || response.startsWith("ERR")) {
throw new IOException(response); // Throw the exception
} else {
// Is this the first update this hour?
if(response.contains("OK This is your first update this hour")) {
synchronized (graphs) {
if (response.contains("OK This is your first update this hour")) {
synchronized(graphs) {
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
while(iter.hasNext()) {
final Graph graph = iter.next();
for(Plotter plotter:graph.getPlotters()) {
@@ -550,7 +550,7 @@ public class Metrics {
@Override
public boolean equals(final Object object) {
if(!(object instanceof Graph)) {
if (!(object instanceof Graph)) {
return false;
}
@@ -624,7 +624,7 @@ public class Metrics {
@Override
public boolean equals(final Object object) {
if(!(object instanceof Plotter)) {
if (!(object instanceof Plotter)) {
return false;
}
@@ -17,17 +17,18 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Fancy JSON serialization mostly by evilmidget38.
* @author evilmidget38, gomeow
*
*/
public class Serialization {
/*
* All normal functions
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> toMap(JSONObject object) throws JSONException {
Map<String, Object> map = new HashMap<String, Object>();
Iterator<String> keys = object.keys();
while (keys.hasNext()) {
while(keys.hasNext()) {
String key = (String) keys.next();
map.put(key, fromJson(object.get(key)));
}
@@ -35,11 +36,11 @@ public class Serialization {
}
private static Object fromJson(Object json) throws JSONException {
if(json == JSONObject.NULL) {
if (json == JSONObject.NULL) {
return null;
} else if(json instanceof JSONObject) {
} else if (json instanceof JSONObject) {
return toMap((JSONObject) json);
} else if(json instanceof JSONArray) {
} else if (json instanceof JSONArray) {
return toList((JSONArray) json);
} else {
return json;
@@ -61,7 +62,7 @@ public class Serialization {
items.add(is);
}
for(ConfigurationSerializable cs:items) {
if(cs == null) {
if (cs == null) {
result.add("null");
}
else {
@@ -75,7 +76,7 @@ public class Serialization {
Inventory inv = Bukkit.createInventory(null, 54, ChatColor.RED + "Vault #" + number);
List<ItemStack> contents = new ArrayList<ItemStack>();
for(String piece:stringItems) {
if(piece.equalsIgnoreCase("null")) {
if (piece.equalsIgnoreCase("null")) {
contents.add(null);
}
else {
@@ -97,7 +98,7 @@ public class Serialization {
public static Map<String, Object> serialize(ConfigurationSerializable cs) {
Map<String, Object> serialized = recreateMap(cs.serialize());
for(Entry<String, Object> entry:serialized.entrySet()) {
if(entry.getValue() instanceof ConfigurationSerializable) {
if (entry.getValue() instanceof ConfigurationSerializable) {
entry.setValue(serialize((ConfigurationSerializable) entry.getValue()));
}
}
@@ -113,20 +114,14 @@ public class Serialization {
return map;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
public static ConfigurationSerializable deserialize(Map<String, Object> map) {
for(Entry<String, Object> entry:map.entrySet()) {
// Check if any of its sub-maps are ConfigurationSerializable. They need to be done
// first.
if(entry.getValue() instanceof Map && ((Map) entry.getValue()).containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
if (entry.getValue() instanceof Map && ((Map) entry.getValue()).containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) {
entry.setValue(deserialize((Map) entry.getValue()));
}
}
return ConfigurationSerialization.deserializeObject(map);
}
/*
* All old methods for transferring
*/
}
@@ -15,6 +15,9 @@ import org.json.JSONObject;
import com.drtshock.playervaults.PlayerVaults;
/**
* A class for updating the lang.yml and checking for updates at DBO.
*/
public class Updater extends PlayerVaults {
SortedMap<String, String> lang = new TreeMap<String, String>();
@@ -22,7 +25,7 @@ public class Updater extends PlayerVaults {
public Updater() {
YamlConfiguration langConf = super.getLang();
for(Lang item:Lang.values()) {
if(langConf.getString(item.getPath()) == null) {
if (langConf.getString(item.getPath()) == null) {
langConf.set(item.getPath(), item.getDefault());
}
}
@@ -35,7 +38,13 @@ public class Updater extends PlayerVaults {
}
}
public boolean getUpdate(String v) throws IOException {
/**
* Check whether or not there is a new update.
* @param currentVersion The current running version.
* @return Whether or not an update is available.
* @throws IOException Oh no!
*/
public boolean getUpdate(String currentVersion) throws IOException {
JSONObject json;
try {
json = getInfo();
@@ -44,7 +53,7 @@ public class Updater extends PlayerVaults {
PlayerVaults.NEWVERSION = version;
String goodLink = new BufferedReader(new InputStreamReader(new URL("http://is.gd/create.php?format=simple&url=" + link).openStream())).readLine();
PlayerVaults.LINK = goodLink;
if(!version.equalsIgnoreCase(v)) {
if (!version.equalsIgnoreCase(currentVersion)) {
return true;
}
} catch(JSONException e) {
@@ -53,6 +62,11 @@ public class Updater extends PlayerVaults {
return false;
}
/**
* Get the information about versions from DBO.
* @return The information in JSON.
* @throws IOException Oh no!
*/
public JSONObject getInfo() throws IOException {
URL url = new URL("http://api.bukget.org/3/plugins/bukkit/playervaults/latest");
BufferedReader in = null;
@@ -67,7 +81,7 @@ public class Updater extends PlayerVaults {
in.close();
return json;
} catch(JSONException e) {
throw new IOException("Oh no!");
}
return null;
}
}
@@ -10,12 +10,14 @@ import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import com.drtshock.playervaults.PlayerVaults;
import com.drtshock.playervaults.commands.VaultViewInfo;
/**
* A class for managing actual IO to the files, loading inventories, and saving them.
*/
public class VaultManager {
public PlayerVaults plugin;
@@ -23,21 +25,22 @@ public class VaultManager {
public VaultManager(PlayerVaults instance) {
this.plugin = instance;
}
private final String directory = "plugins" + File.separator + "PlayerVaults" + File.separator + "vaults";
/**
* Method to save player's vault. Serialize his inventory. Save the
* vaults.yml
*
* @param player
* @throws IOException
* Saves the inventory to the specified player and vault number.
* @param inventory The inventory to be saved.
* @param player The player of whose file to save to.
* @param number The vault number.
* @throws IOException Uh oh!
*/
public void saveVault(Inventory inv, String player, int number) throws IOException {
YamlConfiguration yaml = playerVaultFile(player);
public void saveVault(Inventory inventory, String player, int number) throws IOException {
YamlConfiguration yaml = getPlayerVaultFile(player);
yaml.set("vault" + number, null);
List<String> list = Serialization.toString(inv);
List<String> list = Serialization.toString(inventory);
String[] ser = list.toArray(new String[list.size()]);
for (int x = 0; x < ser.length; x++) {
for(int x = 0; x < ser.length; x++) {
if (!ser[x].equalsIgnoreCase("null")) {
yaml.set("vault" + number + "." + x, ser[x]);
}
@@ -46,22 +49,22 @@ public class VaultManager {
}
/**
* Method to load player's vault. Deserialize his inventory
*
* TODO: Check to see if the path exists before we get it!
* Load the player's vault and return it.
* @param holder The holder of the vault.
* @param number The vault number.
*/
public void loadVault(Player player, String holder, int number) {
public Inventory loadVault(String holder, int number) {
VaultViewInfo info = new VaultViewInfo(holder, number);
Inventory inv = null;
if (PlayerVaults.OPENINVENTORIES.containsKey(info.toString())) {
inv = PlayerVaults.OPENINVENTORIES.get(info.toString());
} else {
YamlConfiguration playerFile = playerVaultFile(holder);
YamlConfiguration playerFile = getPlayerVaultFile(holder);
if (playerFile.getConfigurationSection("vault" + number) == null) {
inv = Bukkit.createInventory(player, 54, ChatColor.DARK_RED + "Vault #" + String.valueOf(number));
inv = Bukkit.createInventory(null, 54, ChatColor.DARK_RED + "Vault #" + String.valueOf(number));
} else {
List<String> data = new ArrayList<String>();
for (int x = 0; x < 54; x++) {
for(int x = 0; x < 54; x++) {
String line = playerFile.getString("vault" + number + "." + x);
if (line != null) {
data.add(line);
@@ -73,22 +76,20 @@ public class VaultManager {
}
PlayerVaults.OPENINVENTORIES.put(info.toString(), inv);
}
player.openInventory(inv);
return inv;
}
/**
* Gets an inventory without opening it. Used for dropping a players
* inventories on death.
*
* @param player
* @param number
* @return the inventory
* Gets an inventory without storing references to it. Used for dropping a players inventories on death.
* @param holder The holder of the vault.
* @param number The vault number.
* @return The inventory of the specified holder and vault number.
*/
public Inventory getVault(Player player, int number) {
YamlConfiguration playerFile = playerVaultFile(player.getName());
public Inventory getVault(String holder, int number) {
YamlConfiguration playerFile = getPlayerVaultFile(holder);
List<String> data = playerFile.getStringList("vault" + number);
if (data == null) {
Inventory inv = Bukkit.createInventory(player, 54, ChatColor.GREEN + "Vault #" + String.valueOf(number));
Inventory inv = Bukkit.createInventory(null, 54, ChatColor.GREEN + "Vault #" + String.valueOf(number));
return inv;
} else {
Inventory inv = Serialization.toInventory(data, number);
@@ -98,43 +99,42 @@ public class VaultManager {
/**
* Deletes a players vault.
*
* @param sender
* @param target
* @param number
* @throws IOException
* @param sender The sender of whom to send messages to.
* @param holder The vault holder.
* @param number The vault number.
* @throws IOException Uh oh!
*/
public void deleteVault(CommandSender sender, String target, int number) throws IOException {
String name = target.toLowerCase();
public void deleteVault(CommandSender sender, String holder, int number) throws IOException {
String name = holder.toLowerCase();
File file = new File(directory + File.separator + name.toLowerCase() + ".yml");
FileConfiguration playerFile = YamlConfiguration.loadConfiguration(file);
if (file.exists()) {
playerFile.set("vault" + number, null);
playerFile.save(file);
}
if (sender.getName().equalsIgnoreCase(target)) {
if (sender.getName().equalsIgnoreCase(holder)) {
sender.sendMessage(Lang.TITLE.toString() + Lang.DELETE_VAULT.toString().replace("%v", String.valueOf(number)));
} else {
sender.sendMessage(Lang.TITLE.toString() + Lang.DELETE_OTHER_VAULT.toString().replace("%v", String.valueOf(number)).replace("%p", target));
sender.sendMessage(Lang.TITLE.toString() + Lang.DELETE_OTHER_VAULT.toString().replace("%v", String.valueOf(number)).replace("%p", holder));
}
}
/**
* Get the player's vault file. Create if doesn't exist.
*
* @param player
* @return playerVaultFile file.
* Get the holder's vault file. Create if doesn't exist.
* @param holder The vault holder.
* @return The holder's vault config file.
*/
public YamlConfiguration playerVaultFile(String player) {
public YamlConfiguration getPlayerVaultFile(String holder) {
File folder = new File(directory);
if (!folder.exists()) {
folder.mkdir();
}
File file = new File(directory + File.separator + player.toLowerCase() + ".yml");
File file = new File(directory + File.separator + holder.toLowerCase() + ".yml");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
} catch(IOException e) {
// Who cares?
}
}
YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file);
@@ -143,15 +143,14 @@ public class VaultManager {
/**
* Save the players vault file.
*
* @param name
* @param yaml
* @throws IOException
* @param holder The vault holder of whose file to save.
* @param yaml The config to save.
* @throws IOException Uh oh!
*/
public void saveFile(String name, YamlConfiguration yaml) throws IOException {
File file = new File(directory + File.separator + name.toLowerCase() + ".yml");
public void saveFile(String holder, YamlConfiguration yaml) throws IOException {
File file = new File(directory + File.separator + holder.toLowerCase() + ".yml");
if (file.exists()) {
file.renameTo(new File(directory + File.separator + "backups" + File.separator + name.toLowerCase() + ".yml"));
file.renameTo(new File(directory + File.separator + "backups" + File.separator + holder.toLowerCase() + ".yml"));
}
yaml.save(file);
}