Compare commits

...

56 Commits

Author SHA1 Message Date
AppleDash
ed3ccceb1d
Merge pull request #103 from A248/master
Solve #100
2020-06-06 13:11:42 -04:00
A248
78d6c70658 Add and test EconomyManager#hasBalance to solve #100 2020-06-06 08:10:52 -04:00
AppleDash
2756e0c2d9 Add option to disable paying of online players. 2020-06-03 08:11:30 -04:00
AppleDash
bb02d78b88 Fix failing tests (bug with equals instead of compareTo) 2020-06-03 08:06:50 -04:00
AppleDash
77ba58e333 Add API to get player name from UUID. 2020-06-03 08:05:23 -04:00
AppleDash
c47524f863 Version bump & make some changes to the way table locking is used in MySQL. 2020-03-20 11:03:02 -04:00
AppleDash
afa4e9d36e Clean up code a bit 2020-02-02 10:05:26 -05:00
AppleDash
854c2f5f10 Experimental baltop fixes and actually use the value of the last name. 2020-02-02 08:53:31 -05:00
AppleDash
2856305330 Fix a bug where the CONSOLE account does not have an infinite balance on databases upgraded from older versions. 2020-01-24 02:06:54 -05:00
AppleDash
eab4b59501 Update SaneLib version 2020-01-24 01:50:18 -05:00
AppleDash
d2fea85ffe Improvements to tests & version bump to 0.16.1 2019-11-11 16:08:04 -05:00
AppleDash
5663077718 Possibly fix multi-server sync 2019-11-10 17:54:03 -05:00
AppleDash
83d8965c23 General code cleanup 2019-11-04 12:25:17 -05:00
AppleDash
fe4fc5018d Format code 2019-11-04 12:08:24 -05:00
AppleDash
c269fc1065 Fix MySQL storage backend 2019-11-04 11:51:25 -05:00
AppleDash
2c3fcea269 Remove flatfile DB backend 2019-11-04 05:02:01 -05:00
AppleDash
9fdb09fc79 JSON schema update, probably 2019-11-04 05:01:09 -05:00
AppleDash
dfaca9285e Version bump & MySQL schema update 2019-11-04 04:57:29 -05:00
AppleDash
e2cc0f3f03 Make async transactions not crash the server 2019-11-04 04:49:41 -05:00
AppleDash
db8970ebbd Experimental conversion to BigDecimal 2019-11-04 04:43:33 -05:00
AppleDash
f22618ebda Version bump to 0.15.0, fix async transactions 2019-10-31 08:05:22 -04:00
AppleDash
030911af20 Make work properly 2019-10-31 08:01:23 -04:00
AppleDash
c77d595497 ...I ain't the sharpest tool in the shed... 2019-10-30 07:37:01 -04:00
AppleDash
71a83a4139 the world was gonna roll me 2018-10-05 05:35:45 -04:00
AppleDash
e42c1fde65 SomeBODY once told me 2018-10-05 05:31:37 -04:00
AppleDash
1caee7db9f Improve tests. 2018-10-05 04:41:23 -04:00
AppleDash
25c6c2cd95 Make balancetop display names properly, possibly, and hope for the best. 2018-10-05 04:38:35 -04:00
AppleDash
3c84fe510e Implement support for storing an Economable's last name in the database. 2018-10-05 04:17:58 -04:00
AppleDash
71832bfffa Implement possibility to send a player a message when they are given money by an admin. 2018-10-05 04:09:37 -04:00
AppleDash
867535bb4d Version bump 0.14.0. Make the API needlessly more complex to try and fix some bugs. 2018-05-25 02:27:02 -04:00
AppleDash
6be741b047 Update deps - this SaneLib version fixes a big with config.yml not saving. 2018-03-01 07:58:51 -05:00
AppleDash
ccc7da8fe7 Bump SESS version 2018-02-08 19:03:42 -05:00
AppleDash
f92c7fddf7 beef 2018-02-08 19:03:03 -05:00
AppleDash
3721f22158 Version bump 2017-11-21 14:06:41 -05:00
AppleDash
08e87f2779 Attempt to fix baltop nullpointer 2017-11-21 14:01:52 -05:00
AppleDash
c51253b63b Only display single economable reload warning when necessary 2017-11-10 07:10:09 -05:00
AppleDash
328e3ad08d Fix weird Set construction and a little code cleanup - #67 #68 2017-11-10 07:06:44 -05:00
AppleDash
b8045511f1 🤔 2017-11-08 20:34:55 -05:00
AppleDash
2bfec13b00 Fix Maven repo bullshit 2017-11-08 20:29:52 -05:00
AppleDash
f9e8a868e5 Implement some various code improvements - credit to @sgdc3 - see #58 2017-09-23 07:24:28 -04:00
AppleDash
be8206ce10 Version bump - also the last commit tries to fix the translations file not existing too. 2017-09-21 22:58:51 -04:00
AppleDash
d399e7a6be Experimental multiserver sync support. 2017-09-21 22:58:00 -04:00
AppleDash
1f0df591fd Fix bug in baltop and in ecoadmin 2017-08-26 01:16:29 -04:00
AppleDash
5ac6ba1407 Deprecate SaneEconomy.getInstance() 2017-07-17 16:11:58 -04:00
AppleDash
9a00cddf24 0.12.7 - Fix version comparator AGAIN 2017-07-15 23:49:05 -04:00
AppleDash
31d311c229 Version checker improved + locale-override option 2017-07-15 17:02:04 -04:00
AppleDash
ca13e9a810 Oops 2017-07-15 16:39:37 -04:00
AppleDash
f9440b3f18 Version bump to 0.12.6 2017-07-15 16:37:26 -04:00
AppleDash
01938349cb Baltop improvements & mocking of Server 2017-07-15 16:36:32 -04:00
AppleDash
03cd019763 Version bump! 2017-07-14 01:23:44 -04:00
AppleDash
735c88f839 Root POM doesn't need reasonable versioning. 2017-07-14 01:19:32 -04:00
AppleDash
686b9ef871 SaneEconomyOnlineTime + version bump 2017-07-14 01:17:36 -04:00
AppleDash
c7f58edf44 Update EconomyStorageBackendMySQL.java 2017-07-14 01:11:50 -04:00
AppleDash
f54a43437d SaneEconomy: Only log database transactions if debug=true (in newer SaneLib) 2017-07-12 20:06:53 -04:00
AppleDash
c3f84698ce Colored balance format support. 2017-07-10 18:11:46 -04:00
AppleDash
5bd417a0fc Remove duplicate key from messages.yml 2017-07-10 18:09:55 -04:00
81 changed files with 2177 additions and 782 deletions

View File

@ -19,6 +19,7 @@ I decided that it was time for a change. I wanted a working, updated economy plu
* SaneEconomyCore - The main economy provider. * SaneEconomyCore - The main economy provider.
* SaneEconomySignShop - A side project written for a specific server. Unsupported. * SaneEconomySignShop - A side project written for a specific server. Unsupported.
* SaneEconomyMobKills - Another side project for the same server. Unsupported. * SaneEconomyMobKills - Another side project for the same server. Unsupported.
* SaneEconomyOnlineTime - A replacement for the old plugin TimeIsMoney. Unsupported for now.
## Development ## Development

View File

@ -6,10 +6,10 @@
<parent> <parent>
<groupId>org.appledash</groupId> <groupId>org.appledash</groupId>
<artifactId>SaneEconomy</artifactId> <artifactId>SaneEconomy</artifactId>
<version>0.12.3-SNAPSHOT</version> <version>0</version>
</parent> </parent>
<artifactId>SaneEconomyCore</artifactId> <artifactId>SaneEconomyCore</artifactId>
<version>0.12.3-SNAPSHOT</version> <version>0.17.2-SNAPSHOT</version>
<dependencies> <dependencies>
<dependency> <dependency>
@ -30,6 +30,7 @@
<resources> <resources>
<resource> <resource>
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<filtering>true</filtering>
</resource> </resource>
</resources> </resources>
<plugins> <plugins>
@ -92,6 +93,32 @@
<outputDirectory>../out/</outputDirectory> <outputDirectory>../out/</outputDirectory>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.7</version>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
<configuration>
<serverId>votuvo</serverId>
<nexusUrl>https://nexus.votuvo.com/</nexusUrl>
<skipStaging>true</skipStaging>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
<distributionManagement>
<snapshotRepository>
<id>votuvo</id>
<url>https://nexus.sw4t.net/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
</project> </project>

View File

@ -2,8 +2,10 @@ package org.appledash.saneeconomy;
import org.appledash.saneeconomy.economy.EconomyManager; import org.appledash.saneeconomy.economy.EconomyManager;
import org.appledash.saneeconomy.economy.logger.TransactionLogger; import org.appledash.saneeconomy.economy.logger.TransactionLogger;
import org.appledash.saneeconomy.vault.VaultHook;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
/** /**
* Created by appledash on 9/18/16. * Created by appledash on 9/18/16.
@ -24,4 +26,8 @@ public interface ISaneEconomy {
* @return TransactionLogger, if there is one. Otherwise, Optional.empty() * @return TransactionLogger, if there is one. Otherwise, Optional.empty()
*/ */
Optional<TransactionLogger> getTransactionLogger(); Optional<TransactionLogger> getTransactionLogger();
VaultHook getVaultHook();
String getLastName(UUID uuid);
} }

View File

@ -1,20 +1,28 @@
package org.appledash.saneeconomy; package org.appledash.saneeconomy;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import org.appledash.saneeconomy.command.*; import org.appledash.saneeconomy.command.*;
import org.appledash.saneeconomy.economy.EconomyManager; import org.appledash.saneeconomy.economy.EconomyManager;
import org.appledash.saneeconomy.economy.backend.EconomyStorageBackend;
import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendMySQL; import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendMySQL;
import org.appledash.saneeconomy.economy.logger.TransactionLogger; import org.appledash.saneeconomy.economy.logger.TransactionLogger;
import org.appledash.saneeconomy.event.SaneEconomyTransactionEvent;
import org.appledash.saneeconomy.listeners.JoinQuitListener; import org.appledash.saneeconomy.listeners.JoinQuitListener;
import org.appledash.saneeconomy.updates.GithubVersionChecker; import org.appledash.saneeconomy.updates.GithubVersionChecker;
import org.appledash.saneeconomy.utils.SaneEconomyConfiguration; import org.appledash.saneeconomy.utils.SaneEconomyConfiguration;
import org.appledash.saneeconomy.vault.VaultHook; import org.appledash.saneeconomy.vault.VaultHook;
import org.appledash.sanelib.SanePlugin; import org.appledash.sanelib.SanePlugin;
import org.appledash.sanelib.command.SaneCommand; import org.appledash.sanelib.command.SaneCommand;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import java.io.File; import java.io.File;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
@ -28,13 +36,15 @@ public class SaneEconomy extends SanePlugin implements ISaneEconomy {
private TransactionLogger transactionLogger; private TransactionLogger transactionLogger;
private GithubVersionChecker versionChecker; private GithubVersionChecker versionChecker;
private final Map<String, SaneCommand> COMMANDS = new HashMap<String, SaneCommand>() {{ private final Map<String, SaneCommand> commands = new HashMap<String, SaneCommand>() {
put("balance", new BalanceCommand(SaneEconomy.this)); {
put("ecoadmin", new EconomyAdminCommand(SaneEconomy.this)); this.put("balance", new BalanceCommand(SaneEconomy.this));
put("pay", new PayCommand(SaneEconomy.this)); this.put("ecoadmin", new EconomyAdminCommand(SaneEconomy.this));
put("saneeconomy", new SaneEcoCommand(SaneEconomy.this)); this.put("pay", new PayCommand(SaneEconomy.this));
put("balancetop", new BalanceTopCommand(SaneEconomy.this)); this.put("saneeconomy", new SaneEcoCommand(SaneEconomy.this));
}}; this.put("balancetop", new BalanceTopCommand(SaneEconomy.this));
}
};
public SaneEconomy() { public SaneEconomy() {
instance = this; instance = this;
@ -44,92 +54,152 @@ public class SaneEconomy extends SanePlugin implements ISaneEconomy {
public void onEnable() { public void onEnable() {
super.onEnable(); super.onEnable();
if (!loadConfig()) { /* Invalid backend type or connection error of some sort */ if (!this.loadConfig()) { /* Invalid backend type or connection error of some sort */
shutdown(); this.shutdown();
return; return;
} }
loadCommands(); if (this.getConfig().getBoolean("locale-override", false)) {
loadListeners(); Locale.setDefault(Locale.ENGLISH);
if (getServer().getPluginManager().isPluginEnabled("Vault")) {
vaultHook = new VaultHook(this);
vaultHook.hook();
getLogger().info("Hooked into Vault.");
} else {
getLogger().info("Not hooking into Vault because it isn't loaded.");
} }
versionChecker = new GithubVersionChecker("SaneEconomyCore", this.getDescription().getVersion()); this.loadCommands();
getServer().getScheduler().scheduleAsyncDelayedTask(this, versionChecker::checkUpdateAvailable); this.loadListeners();
getServer().getScheduler().runTaskTimerAsynchronously(this, () -> { if (this.getServer().getPluginManager().isPluginEnabled("Vault")) {
economyManager.getBackend().reloadTopPlayerBalances(); this.vaultHook = new VaultHook(this);
}, 0, (20 * 300) /* Update baltop every 5 minutes */); this.vaultHook.hook();
this.getLogger().info("Hooked into Vault.");
} else {
this.getLogger().info("Not hooking into Vault because it isn't loaded.");
}
if (this.getConfig().getBoolean("update-check", true)) {
this.versionChecker = new GithubVersionChecker("SaneEconomyCore", this.getDescription().getVersion().replace("-SNAPSHOT", ""));
this.getServer().getScheduler().runTaskAsynchronously(this, this.versionChecker::checkUpdateAvailable);
}
this.getServer().getScheduler().runTaskTimerAsynchronously(this, () -> {
this.economyManager.getBackend().reloadTopPlayerBalances();
}, 0L, (20L * this.getConfig().getLong("economy.baltop-update-interval", 300L)) /* Update baltop every 5 minutes by default */);
if (this.getConfig().getBoolean("multi-server-sync", false)) {
this.getServer().getPluginManager().registerEvents(new Listener() {
@EventHandler
public void onTransaction(SaneEconomyTransactionEvent evt) { // Trust me, I'm a doctor.
OfflinePlayer[] playersToSync = { evt.getTransaction().getSender().tryCastToPlayer(), evt.getTransaction().getReceiver().tryCastToPlayer() };
Player fakeSender = Iterables.getFirst(SaneEconomy.this.getServer().getOnlinePlayers(), null);
if (fakeSender == null) {
return;
}
Arrays.stream(playersToSync).filter(p -> (p != null) && !p.isOnline()).forEach(p -> {
ByteArrayDataOutput bado = ByteStreams.newDataOutput();
bado.writeUTF("Forward");
bado.writeUTF("ONLINE");
bado.writeUTF("SaneEconomy");
bado.writeUTF("SyncPlayer");
bado.writeUTF(p.getUniqueId().toString());
fakeSender.sendPluginMessage(SaneEconomy.this, "BungeeCord", bado.toByteArray());
});
}
}, this);
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", (channel, player, bytes) -> {
if (!channel.equals("BungeeCord")) {
return;
}
ByteArrayDataInput badi = ByteStreams.newDataInput(bytes);
String subChannel = badi.readUTF();
if (subChannel.equals("SaneEconomy")) {
String opCode = badi.readUTF();
if (opCode.equals("SyncPlayer")) {
String playerUuid = badi.readUTF();
this.economyManager.getBackend().reloadEconomable(String.format("player:%s", playerUuid), EconomyStorageBackend.EconomableReloadReason.CROSS_SERVER_SYNC);
} else {
this.getLogger().warning("Invalid OpCode received on SaneEconomy plugin message channel: " + opCode);
}
}
});
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
}
} }
@Override @Override
public void onDisable() { public void onDisable() {
if (vaultHook != null) { if (this.vaultHook != null) {
getLogger().info("Unhooking from Vault."); this.getLogger().info("Unhooking from Vault.");
vaultHook.unhook(); this.vaultHook.unhook();
} }
if (economyManager != null) { this.flushEconomyManager();
getLogger().info("Flushing database..."); }
economyManager.getBackend().waitUntilFlushed();
if (economyManager.getBackend() instanceof EconomyStorageBackendMySQL) { private void flushEconomyManager() {
((EconomyStorageBackendMySQL) economyManager.getBackend()).closeConnections(); if (this.economyManager != null) {
if (!((EconomyStorageBackendMySQL) economyManager.getBackend()).getConnection().getConnection().isFinished()) { this.getLogger().info("Flushing database...");
this.economyManager.getBackend().waitUntilFlushed();
if (this.economyManager.getBackend() instanceof EconomyStorageBackendMySQL) {
((EconomyStorageBackendMySQL) this.economyManager.getBackend()).closeConnections();
if (!((EconomyStorageBackendMySQL) this.economyManager.getBackend()).getConnection().getConnection().isFinished()) {
this.getLogger().warning("SaneDatabase didn't terminate all threads, something weird is going on?"); this.getLogger().warning("SaneDatabase didn't terminate all threads, something weird is going on?");
} }
} }
} }
} }
private boolean loadConfig() { public boolean loadConfig() {
File configFile = new File(getDataFolder(), "config.yml"); File configFile = new File(this.getDataFolder(), "config.yml");
if (configFile.exists() && getConfig().getBoolean("debug", false)) { if (configFile.exists() && this.getConfig().getBoolean("debug", false)) {
getLogger().info("Resetting configuration to default since debug == true."); this.getLogger().info("Resetting configuration to default since debug == true.");
configFile.delete(); configFile.delete();
saveDefaultConfig(); this.saveDefaultConfig();
reloadConfig(); this.reloadConfig();
getConfig().set("debug", true); this.getConfig().set("debug", true);
saveConfig(); this.saveConfig();
} else { } else {
saveDefaultConfig(); if (!configFile.exists()) {
reloadConfig(); this.saveDefaultConfig();
}
this.reloadConfig();
} }
this.flushEconomyManager(); // If we're reloading the configuration, we flush the old economy manager first
SaneEconomyConfiguration config = new SaneEconomyConfiguration(this); SaneEconomyConfiguration config = new SaneEconomyConfiguration(this);
economyManager = config.loadEconomyBackend(); this.economyManager = config.loadEconomyBackend();
transactionLogger = config.loadLogger(); this.transactionLogger = config.loadLogger();
saveConfig(); this.saveConfig();
return economyManager != null; return this.economyManager != null;
} }
private void loadCommands() { private void loadCommands() {
getLogger().info("Initializing commands..."); this.getLogger().info("Initializing commands...");
COMMANDS.forEach((name, command) -> getCommand(name).setExecutor(command)); this.commands.forEach((name, command) -> this.getCommand(name).setExecutor(command));
getLogger().info("Initialized commands."); this.getLogger().info("Initialized commands.");
} }
private void loadListeners() { private void loadListeners() {
getLogger().info("Initializing listeners..."); this.getLogger().info("Initializing listeners...");
getServer().getPluginManager().registerEvents(new JoinQuitListener(this), this); this.getServer().getPluginManager().registerEvents(new JoinQuitListener(this), this);
getLogger().info("Initialized listeners."); this.getLogger().info("Initialized listeners.");
} }
private void shutdown(){ private void shutdown() {
getServer().getPluginManager().disablePlugin(this); this.getServer().getPluginManager().disablePlugin(this);
} }
public GithubVersionChecker getVersionChecker() { public GithubVersionChecker getVersionChecker() {
return versionChecker; return this.versionChecker;
} }
/** /**
@ -138,7 +208,7 @@ public class SaneEconomy extends SanePlugin implements ISaneEconomy {
*/ */
@Override @Override
public EconomyManager getEconomyManager() { public EconomyManager getEconomyManager() {
return economyManager; return this.economyManager;
} }
/** /**
@ -147,13 +217,14 @@ public class SaneEconomy extends SanePlugin implements ISaneEconomy {
*/ */
@Override @Override
public Optional<TransactionLogger> getTransactionLogger() { public Optional<TransactionLogger> getTransactionLogger() {
return Optional.ofNullable(transactionLogger); return Optional.ofNullable(this.transactionLogger);
} }
/** /**
* Get the current plugin instance. * Get the current plugin instance.
* @return Instance * @return Instance
*/ */
@Deprecated
public static SaneEconomy getInstance() { public static SaneEconomy getInstance() {
return instance; return instance;
} }
@ -162,7 +233,17 @@ public class SaneEconomy extends SanePlugin implements ISaneEconomy {
* Get the logger for the plugin. * Get the logger for the plugin.
* @return Plugin logger. * @return Plugin logger.
*/ */
public static Logger logger(){ public static Logger logger() {
return instance.getLogger(); return instance.getLogger();
} }
@Override
public VaultHook getVaultHook() {
return this.vaultHook;
}
@Override
public String getLastName(UUID uuid) {
return this.economyManager.getBackend().getLastName("player:" + uuid.toString());
}
} }

View File

@ -30,8 +30,8 @@ public class BalanceCommand extends SaneCommand {
@Override @Override
public String[] getUsage() { public String[] getUsage() {
return new String[] { return new String[] {
"/<command> [player]" "/<command> [player]"
}; };
} }
@Override @Override
@ -66,9 +66,9 @@ public class BalanceCommand extends SaneCommand {
} }
if (sender == player) { if (sender == player) {
this.saneEconomy.getMessenger().sendMessage(sender, "Your balance is {1}.", saneEconomy.getEconomyManager().getFormattedBalance(Economable.wrap(player))); this.saneEconomy.getMessenger().sendMessage(sender, "Your balance is {1}.", this.saneEconomy.getEconomyManager().getFormattedBalance(Economable.wrap(player)));
} else { } else {
this.saneEconomy.getMessenger().sendMessage(sender, "Balance for {1} is {2}.", playerName, saneEconomy.getEconomyManager().getFormattedBalance(Economable.wrap(player))); this.saneEconomy.getMessenger().sendMessage(sender, "Balance for {1} is {2}.", playerName, this.saneEconomy.getEconomyManager().getFormattedBalance(Economable.wrap(player)));
} }
} }
} }

View File

@ -4,9 +4,9 @@ import org.appledash.saneeconomy.SaneEconomy;
import org.appledash.sanelib.command.SaneCommand; import org.appledash.sanelib.command.SaneCommand;
import org.appledash.sanelib.command.exception.CommandException; import org.appledash.sanelib.command.exception.CommandException;
import org.appledash.sanelib.command.exception.type.usage.TooManyArgumentsException; import org.appledash.sanelib.command.exception.type.usage.TooManyArgumentsException;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.math.BigDecimal;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -30,9 +30,9 @@ public class BalanceTopCommand extends SaneCommand {
@Override @Override
public String[] getUsage() { public String[] getUsage() {
return new String[] { return new String[] {
"/<command>", "/<command>",
"/<command> <page>" "/<command> <page>"
}; };
} }
@Override @Override
@ -48,14 +48,14 @@ public class BalanceTopCommand extends SaneCommand {
try { try {
page = Math.abs(Integer.parseInt(args[0])); page = Math.abs(Integer.parseInt(args[0]));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
this.saneEconomy.getMessenger().sendMessage(sender, "{1} is not a valid number."); this.saneEconomy.getMessenger().sendMessage(sender, "{1} is not a valid number.", args[0]);
return; return;
} }
} }
int offset = (page - 1) * nPerPage; int offset = (page - 1) * nPerPage;
Map<OfflinePlayer, Double> topBalances = saneEconomy.getEconomyManager().getTopPlayerBalances(nPerPage, offset); Map<String, BigDecimal> topBalances = this.saneEconomy.getEconomyManager().getTopBalances(nPerPage, offset);
if (topBalances.isEmpty()) { if (topBalances.isEmpty()) {
this.saneEconomy.getMessenger().sendMessage(sender, "There aren't enough players to display that page."); this.saneEconomy.getMessenger().sendMessage(sender, "There aren't enough players to display that page.");
@ -65,6 +65,12 @@ public class BalanceTopCommand extends SaneCommand {
AtomicInteger index = new AtomicInteger(offset + 1); /* I know it's stupid, but you can't do some_int++ from within the lambda. */ AtomicInteger index = new AtomicInteger(offset + 1); /* I know it's stupid, but you can't do some_int++ from within the lambda. */
this.saneEconomy.getMessenger().sendMessage(sender, "Top {1} players on page {2}:", topBalances.size(), page); this.saneEconomy.getMessenger().sendMessage(sender, "Top {1} players on page {2}:", topBalances.size(), page);
topBalances.forEach((player, balance) -> this.saneEconomy.getMessenger().sendMessage(sender, "[{1:02d}] {2} - {3}", index.getAndIncrement(), player.getName(), SaneEconomy.getInstance().getEconomyManager().getCurrency().formatAmount(balance)));
topBalances.forEach((player, balance) ->
this.saneEconomy.getMessenger().sendMessage(sender, "[{1:02d}] {2} - {3}",
index.getAndIncrement(),
player == null ? "<unknown>" : player,
this.saneEconomy.getEconomyManager().getCurrency().formatAmount(balance))
);
} }
} }

View File

@ -17,6 +17,8 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.math.BigDecimal;
/** /**
* Created by AppleDash on 6/13/2016. * Created by AppleDash on 6/13/2016.
* Blackjack is still best pony. * Blackjack is still best pony.
@ -37,8 +39,8 @@ public class EconomyAdminCommand extends SaneCommand {
@Override @Override
public String[] getUsage() { public String[] getUsage() {
return new String[] { return new String[] {
"/<command> <give/take/set> [player] <amount>" "/<command> <give/take/set> [player] <amount>"
}; };
} }
@Override @Override
@ -70,62 +72,88 @@ public class EconomyAdminCommand extends SaneCommand {
return; return;
} }
EconomyManager ecoMan = saneEconomy.getEconomyManager(); EconomyManager ecoMan = this.saneEconomy.getEconomyManager();
Economable economable = Economable.wrap(targetPlayer); Economable economable = Economable.wrap(targetPlayer);
double amount = NumberUtils.parseAndFilter(ecoMan.getCurrency(), sAmount); BigDecimal amount = NumberUtils.parseAndFilter(ecoMan.getCurrency(), sAmount);
if (amount <= 0) { if (!(subCommand.equalsIgnoreCase("set") && amount.equals(BigDecimal.ZERO)) && amount.compareTo(BigDecimal.ZERO) <= 0) { // If they're setting it to 0 it's fine, otherwise reject numbers under 1.
this.saneEconomy.getMessenger().sendMessage(sender, "{1} is not a positive number.", ((amount == -1) ? sAmount : String.valueOf(amount))); this.saneEconomy.getMessenger().sendMessage(sender, "{1} is not a positive number.", ((amount.equals(BigDecimal.ONE.negate())) ? sAmount : String.valueOf(amount)));
return; return;
} }
if (subCommand.equalsIgnoreCase("give")) { if (subCommand.equalsIgnoreCase("give")) {
Transaction transaction = new Transaction(Economable.wrap(sender), Economable.wrap(targetPlayer), amount, TransactionReason.ADMIN_GIVE); Transaction transaction = new Transaction(ecoMan.getCurrency(), Economable.wrap(sender), Economable.wrap(targetPlayer), amount, TransactionReason.ADMIN_GIVE);
TransactionResult result = ecoMan.transact(transaction); TransactionResult result = ecoMan.transact(transaction);
double newAmount = result.getToBalance(); BigDecimal newAmount = result.getToBalance();
this.saneEconomy.getMessenger().sendMessage(sender, "Added {1} to {2}. Their balance is now {3}.", this.saneEconomy.getMessenger().sendMessage(sender, "Added {1} to {2}. Their balance is now {3}.",
ecoMan.getCurrency().formatAmount(amount), ecoMan.getCurrency().formatAmount(amount),
sTargetPlayer, sTargetPlayer,
ecoMan.getCurrency().formatAmount(newAmount) ecoMan.getCurrency().formatAmount(newAmount)
); );
if (this.saneEconomy.getConfig().getBoolean("economy.notify-admin-give") && targetPlayer.isOnline()) {
this.saneEconomy.getMessenger().sendMessage((CommandSender) targetPlayer, "{1} has given you {2}. Your balance is now {3}.",
sender.getName(),
ecoMan.getCurrency().formatAmount(amount),
ecoMan.getCurrency().formatAmount(newAmount)
);
}
return; return;
} }
if (subCommand.equalsIgnoreCase("take")) { if (subCommand.equalsIgnoreCase("take")) {
Transaction transaction = new Transaction(Economable.wrap(targetPlayer), Economable.wrap(sender), amount, TransactionReason.ADMIN_TAKE); Transaction transaction = new Transaction(ecoMan.getCurrency(), Economable.wrap(targetPlayer), Economable.wrap(sender), amount, TransactionReason.ADMIN_TAKE);
TransactionResult result = ecoMan.transact(transaction); TransactionResult result = ecoMan.transact(transaction);
double newAmount = result.getFromBalance(); BigDecimal newAmount = result.getFromBalance();
this.saneEconomy.getMessenger().sendMessage(sender, "Took {1} from {2}. Their balance is now {3}.", this.saneEconomy.getMessenger().sendMessage(sender, "Took {1} from {2}. Their balance is now {3}.",
ecoMan.getCurrency().formatAmount(amount), ecoMan.getCurrency().formatAmount(amount),
sTargetPlayer, sTargetPlayer,
ecoMan.getCurrency().formatAmount(newAmount) ecoMan.getCurrency().formatAmount(newAmount)
); );
if (this.saneEconomy.getConfig().getBoolean("economy.notify-admin-take") && targetPlayer.isOnline()) {
this.saneEconomy.getMessenger().sendMessage((CommandSender) targetPlayer, "{1} has taken {2} from you. Your balance is now {3}.",
sender.getName(),
ecoMan.getCurrency().formatAmount(amount),
ecoMan.getCurrency().formatAmount(newAmount)
);
}
return; return;
} }
if (subCommand.equalsIgnoreCase("set")) { if (subCommand.equalsIgnoreCase("set")) {
double oldBal = ecoMan.getBalance(economable); BigDecimal oldBal = ecoMan.getBalance(economable);
ecoMan.setBalance(economable, amount); ecoMan.setBalance(economable, amount);
this.saneEconomy.getMessenger().sendMessage(sender, "Balance for {1} set to {2}.", sTargetPlayer, ecoMan.getCurrency().formatAmount(amount)); this.saneEconomy.getMessenger().sendMessage(sender, "Balance for {1} set to {2}.", sTargetPlayer, ecoMan.getCurrency().formatAmount(amount));
saneEconomy.getTransactionLogger().ifPresent((logger) -> { this.saneEconomy.getTransactionLogger().ifPresent((logger) -> {
// FIXME: This is a silly hack to get it to log. // FIXME: This is a silly hack to get it to log.
if (oldBal > 0.0) { if (oldBal.compareTo(BigDecimal.ZERO) > 0) {
logger.logTransaction(new Transaction( logger.logTransaction(new Transaction(
economable, Economable.CONSOLE, oldBal, TransactionReason.ADMIN_GIVE ecoMan.getCurrency(), economable, Economable.CONSOLE, oldBal, TransactionReason.ADMIN_TAKE
)); ));
} }
logger.logTransaction(new Transaction( logger.logTransaction(new Transaction(
Economable.CONSOLE, economable, amount, TransactionReason.ADMIN_GIVE ecoMan.getCurrency(), Economable.CONSOLE, economable, amount, TransactionReason.ADMIN_GIVE
)); ));
}); });
if (this.saneEconomy.getConfig().getBoolean("economy.notify-admin-set") && targetPlayer.isOnline()) {
this.saneEconomy.getMessenger().sendMessage((CommandSender) targetPlayer, "{1} has set your balance to {2}.",
sender.getName(),
ecoMan.getCurrency().formatAmount(amount)
);
}
return; return;
} }

View File

@ -15,6 +15,8 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.math.BigDecimal;
/** /**
* Created by AppleDash on 6/14/2016. * Created by AppleDash on 6/14/2016.
* Blackjack is still best pony. * Blackjack is still best pony.
@ -35,8 +37,8 @@ public class PayCommand extends SaneCommand {
@Override @Override
public String[] getUsage() { public String[] getUsage() {
return new String[] { return new String[] {
"/pay <player> <amount>" "/pay <player> <amount>"
}; };
} }
@Override @Override
@ -50,7 +52,7 @@ public class PayCommand extends SaneCommand {
throw new NeedPlayerException(); throw new NeedPlayerException();
} }
EconomyManager ecoMan = saneEconomy.getEconomyManager(); EconomyManager ecoMan = this.saneEconomy.getEconomyManager();
Player fromPlayer = (Player) sender; Player fromPlayer = (Player) sender;
String sToPlayer = args[0]; String sToPlayer = args[0];
@ -66,23 +68,28 @@ public class PayCommand extends SaneCommand {
return; return;
} }
String sAmount = args[1]; if (!this.saneEconomy.getConfig().getConfigurationSection("economy").getBoolean("pay-offline-players", true) && !toPlayer.isOnline()) {
double amount = NumberUtils.parseAndFilter(ecoMan.getCurrency(), sAmount); this.saneEconomy.getMessenger().sendMessage(sender, "You cannot pay an offline player.");
return;
}
if (amount <= 0) { String sAmount = args[1];
this.saneEconomy.getMessenger().sendMessage(sender, "{1} is not a positive number.", ((amount == -1) ? sAmount : String.valueOf(amount))); BigDecimal amount = NumberUtils.parseAndFilter(ecoMan.getCurrency(), sAmount);
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
this.saneEconomy.getMessenger().sendMessage(sender, "{1} is not a positive number.", ((amount.equals(BigDecimal.ONE.negate())) ? sAmount : String.valueOf(amount)));
return; return;
} }
/* Perform the actual transfer. False == They didn't have enough money */ /* Perform the actual transfer. False == They didn't have enough money */
Transaction transaction = new Transaction(Economable.wrap(fromPlayer), Economable.wrap(toPlayer), amount, TransactionReason.PLAYER_PAY); Transaction transaction = new Transaction(ecoMan.getCurrency(), Economable.wrap(fromPlayer), Economable.wrap(toPlayer), amount, TransactionReason.PLAYER_PAY);
TransactionResult result = ecoMan.transact(transaction); TransactionResult result = ecoMan.transact(transaction);
if (result.getStatus() != TransactionResult.Status.SUCCESS) { if (result.getStatus() != TransactionResult.Status.SUCCESS) {
this.saneEconomy.getMessenger().sendMessage(sender, "You do not have enough money to transfer {1} to {2}.", this.saneEconomy.getMessenger().sendMessage(sender, "You do not have enough money to transfer {1} to {2}.",
ecoMan.getCurrency().formatAmount(amount), ecoMan.getCurrency().formatAmount(amount),
sToPlayer sToPlayer
); );
return; return;
} }
@ -92,13 +99,13 @@ public class PayCommand extends SaneCommand {
this.saneEconomy.getMessenger().sendMessage(sender, "You have transferred {1} to {2}.", this.saneEconomy.getMessenger().sendMessage(sender, "You have transferred {1} to {2}.",
ecoMan.getCurrency().formatAmount(amount), ecoMan.getCurrency().formatAmount(amount),
sToPlayer sToPlayer
); );
if (toPlayer.isOnline()) { if (toPlayer.isOnline()) {
this.saneEconomy.getMessenger().sendMessage(((CommandSender) toPlayer), "You have received {1} from {2}.", this.saneEconomy.getMessenger().sendMessage(((CommandSender) toPlayer), "You have received {1} from {2}.",
ecoMan.getCurrency().formatAmount(amount), ecoMan.getCurrency().formatAmount(amount),
fromPlayer.getDisplayName() fromPlayer.getDisplayName()
); );
} }
} }
} }

View File

@ -26,8 +26,10 @@ public class SaneEcoCommand extends SaneCommand {
@Override @Override
public String[] getUsage() { public String[] getUsage() {
return new String[] { return new String[] {
"/<command> reload-database" "/<command> reload - Reload everything.",
}; "/<command> reload-database - Reload the database.",
"/<command> reload-config - Reload the configuration."
};
} }
@Override @Override
@ -40,8 +42,17 @@ public class SaneEcoCommand extends SaneCommand {
if (subCommand.equalsIgnoreCase("reload-database")) { if (subCommand.equalsIgnoreCase("reload-database")) {
this.saneEconomy.getMessenger().sendMessage(sender, "Reloading database..."); this.saneEconomy.getMessenger().sendMessage(sender, "Reloading database...");
saneEconomy.getEconomyManager().getBackend().reloadDatabase(); this.saneEconomy.getEconomyManager().getBackend().reloadDatabase();
this.saneEconomy.getMessenger().sendMessage(sender, "Database reloaded."); this.saneEconomy.getMessenger().sendMessage(sender, "Database reloaded.");
} else if (subCommand.equalsIgnoreCase("reload-config")) {
this.saneEconomy.getMessenger().sendMessage(sender, "Reloading configuration...");
this.saneEconomy.loadConfig();
this.saneEconomy.getMessenger().sendMessage(sender, "Configuration reloaded.");
} else if (subCommand.equalsIgnoreCase("reload")) {
this.saneEconomy.getMessenger().sendMessage(sender, "Reloading configuration and database...");
this.saneEconomy.loadConfig();
this.saneEconomy.getEconomyManager().getBackend().reloadDatabase();
this.saneEconomy.getMessenger().sendMessage(sender, "Configuration and database reloaded.");
} else { } else {
throw new InvalidUsageException(); throw new InvalidUsageException();
} }

View File

@ -2,8 +2,10 @@ package org.appledash.saneeconomy.economy;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.appledash.sanelib.messages.MessageUtils; import org.appledash.sanelib.messages.MessageUtils;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols; import java.text.DecimalFormatSymbols;
@ -28,6 +30,8 @@ public class Currency {
this.namePlural = namePlural; this.namePlural = namePlural;
this.format = format; this.format = format;
this.balanceFormat = balanceFormat; this.balanceFormat = balanceFormat;
this.format.setParseBigDecimal(true);
} }
public static Currency fromConfig(ConfigurationSection config) { public static Currency fromConfig(ConfigurationSection config) {
@ -47,17 +51,23 @@ public class Currency {
symbols.setGroupingSeparator(groupingSeparator.charAt(0)); symbols.setGroupingSeparator(groupingSeparator.charAt(0));
} }
String decimalSeparator = config.getString("decimal-separator", ".");
if (!Strings.isNullOrEmpty(decimalSeparator)) {
symbols.setDecimalSeparator(decimalSeparator.charAt(0));
}
format.setDecimalFormatSymbols(symbols); format.setDecimalFormatSymbols(symbols);
format.setGroupingUsed(true); format.setGroupingUsed(true);
format.setGroupingSize(3); format.setGroupingSize(3);
} }
return new Currency( return new Currency(
config.getString("name.singular", "dollar"), config.getString("name.singular", "dollar"),
config.getString("name.plural", "dollars"), config.getString("name.plural", "dollars"),
format, format,
config.getString("balance-format", "{1} {2}") config.getString("balance-format", "{1} {2}")
); );
} }
/** /**
@ -65,12 +75,10 @@ public class Currency {
* @param amount Money amount. * @param amount Money amount.
* @return Formatted amount string. * @return Formatted amount string.
*/ */
public String formatAmount(double amount) { public String formatAmount(BigDecimal amount) {
if (amount == 1) { return ChatColor.translateAlternateColorCodes('&',
return MessageUtils.indexedFormat(balanceFormat, format.format(amount), nameSingular); MessageUtils.indexedFormat(this.balanceFormat, this.format.format(amount), amount.compareTo(BigDecimal.ONE) == 0 ? this.nameSingular : this.namePlural)
} );
return MessageUtils.indexedFormat(balanceFormat, format.format(amount), namePlural);
} }
/** /**
@ -79,7 +87,7 @@ public class Currency {
* @return Singular name. * @return Singular name.
*/ */
public String getSingularName() { public String getSingularName() {
return nameSingular; return this.nameSingular;
} }
/** /**
@ -88,7 +96,7 @@ public class Currency {
* @return Plural name. * @return Plural name.
*/ */
public String getPluralName() { public String getPluralName() {
return namePlural; return this.namePlural;
} }
/** /**
@ -96,6 +104,6 @@ public class Currency {
* @return DecimalFormat instance * @return DecimalFormat instance
*/ */
public DecimalFormat getFormat() { public DecimalFormat getFormat() {
return format; return this.format;
} }
} }

View File

@ -1,18 +1,22 @@
package org.appledash.saneeconomy.economy; package org.appledash.saneeconomy.economy;
import org.appledash.saneeconomy.ISaneEconomy; import org.appledash.saneeconomy.ISaneEconomy;
import org.appledash.saneeconomy.SaneEconomy;
import org.appledash.saneeconomy.economy.backend.EconomyStorageBackend; import org.appledash.saneeconomy.economy.backend.EconomyStorageBackend;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
import org.appledash.saneeconomy.economy.economable.EconomableConsole;
import org.appledash.saneeconomy.economy.transaction.Transaction; import org.appledash.saneeconomy.economy.transaction.Transaction;
import org.appledash.saneeconomy.economy.transaction.TransactionResult; import org.appledash.saneeconomy.economy.transaction.TransactionResult;
import org.appledash.saneeconomy.event.SaneEconomyTransactionEvent; import org.appledash.saneeconomy.event.SaneEconomyTransactionEvent;
import org.appledash.saneeconomy.utils.MapUtil;
import org.appledash.saneeconomy.utils.NumberUtils; import org.appledash.saneeconomy.utils.NumberUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import java.math.BigDecimal;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.concurrent.*;
/** /**
* Created by AppleDash on 6/13/2016. * Created by AppleDash on 6/13/2016.
@ -25,6 +29,8 @@ public class EconomyManager {
private final Currency currency; private final Currency currency;
private final EconomyStorageBackend backend; private final EconomyStorageBackend backend;
private final String serverAccountName; private final String serverAccountName;
private static final BigDecimal REQUIRED_BALANCE_ACCURACY = new BigDecimal("0.0001");
public EconomyManager(ISaneEconomy saneEconomy, Currency currency, EconomyStorageBackend backend, String serverAccountName) { public EconomyManager(ISaneEconomy saneEconomy, Currency currency, EconomyStorageBackend backend, String serverAccountName) {
this.saneEconomy = saneEconomy; this.saneEconomy = saneEconomy;
@ -38,7 +44,7 @@ public class EconomyManager {
* @return Currency * @return Currency
*/ */
public Currency getCurrency() { public Currency getCurrency() {
return currency; return this.currency;
} }
/** /**
@ -47,7 +53,7 @@ public class EconomyManager {
* @return Formatted balance * @return Formatted balance
*/ */
public String getFormattedBalance(Economable player) { public String getFormattedBalance(Economable player) {
return currency.formatAmount(backend.getBalance(player)); return this.currency.formatAmount(this.backend.getBalance(player));
} }
/** /**
@ -56,7 +62,7 @@ public class EconomyManager {
* @return True if they have used the economy system before, false otherwise * @return True if they have used the economy system before, false otherwise
*/ */
public boolean accountExists(Economable player) { public boolean accountExists(Economable player) {
return backend.accountExists(player); return this.backend.accountExists(player);
} }
/** /**
@ -64,12 +70,12 @@ public class EconomyManager {
* @param targetPlayer Player to get balance of * @param targetPlayer Player to get balance of
* @return Player's balance * @return Player's balance
*/ */
public double getBalance(Economable targetPlayer) { public BigDecimal getBalance(Economable targetPlayer) {
if (targetPlayer == Economable.CONSOLE) { if (EconomableConsole.isConsole(targetPlayer)) {
return Double.MAX_VALUE; return new BigDecimal(Double.MAX_VALUE);
} }
return backend.getBalance(targetPlayer); return this.backend.getBalance(targetPlayer);
} }
@ -79,60 +85,51 @@ public class EconomyManager {
* @param requiredBalance How much money we're checking for * @param requiredBalance How much money we're checking for
* @return True if they have requiredBalance or more, false otherwise * @return True if they have requiredBalance or more, false otherwise
*/ */
public boolean hasBalance(Economable targetPlayer, double requiredBalance) { public boolean hasBalance(Economable targetPlayer, BigDecimal requiredBalance) {
return targetPlayer == Economable.CONSOLE || getBalance(targetPlayer) >= requiredBalance; return (EconomableConsole.isConsole(targetPlayer)) || (hasBalance(this.getBalance(targetPlayer), requiredBalance));
}
/**
* Compare account balance and required balance to a reasonable degree of accuracy. <br>
* <b>Visible for testing</b>
*
* @param accountBalance account balance
* @param requiredBalance required balance
* @return true if the account has the required balance to some degree of accuracy, false otherwise
*/
public boolean hasBalance(BigDecimal accountBalance, BigDecimal requiredBalance) {
if (accountBalance.compareTo(requiredBalance) >= 0) {
return true;
}
// Must compare to degree of accuracy
// See https://github.com/AppleDash/SaneEconomy/issues/100
BigDecimal difference = requiredBalance.subtract(accountBalance);
return difference.compareTo(REQUIRED_BALANCE_ACCURACY) < 0; // difference < PRECISION
} }
/** /**
* Add to a player's balance. * Add to a player's balance.
* This does not filter the amount.
* @param targetPlayer Player to add to * @param targetPlayer Player to add to
* @param amount Amount to add * @param amount Amount to add
* @throws IllegalArgumentException If amount is negative * @throws IllegalArgumentException If amount is negative
*/ */
private void addBalance(Economable targetPlayer, double amount) { private void addBalance(Economable targetPlayer, BigDecimal amount) {
amount = NumberUtils.filterAmount(currency, amount); this.setBalance(targetPlayer, this.backend.getBalance(targetPlayer).add(amount));
if (amount < 0) {
throw new IllegalArgumentException("Cannot add a negative amount!");
}
if (targetPlayer == Economable.CONSOLE) {
return;
}
double newAmount = backend.getBalance(targetPlayer) + amount;
setBalance(targetPlayer, newAmount);
} }
/** /**
* Subtract from a player's balance. * Subtract from a player's balance.
* If the subtraction would result in a negative balance, the balance is instead set to 0. * If the subtraction would result in a negative balance, the balance is instead set to 0.
* This does not filter the amount.
*
* @param targetPlayer Player to subtract from * @param targetPlayer Player to subtract from
* @param amount Amount to subtract * @param amount Amount to subtract
* @throws IllegalArgumentException If amount is negative * @throws IllegalArgumentException If amount is negative
*/ */
private void subtractBalance(Economable targetPlayer, double amount) { private void subtractBalance(Economable targetPlayer, BigDecimal amount) {
amount = NumberUtils.filterAmount(currency, amount); // Ensure we don't go negative.
this.setBalance(targetPlayer, this.backend.getBalance(targetPlayer).subtract(amount).max(BigDecimal.ZERO));
if (amount < 0) {
throw new IllegalArgumentException("Cannot subtract a negative amount!");
}
if (targetPlayer == Economable.CONSOLE) {
return;
}
double newAmount = backend.getBalance(targetPlayer) - amount;
/* Subtracting that much would result in a negative balance - don't do that */
if (newAmount <= 0.0D) {
newAmount = 0.0D;
}
setBalance(targetPlayer, newAmount);
} }
/** /**
@ -141,18 +138,14 @@ public class EconomyManager {
* @param amount Amount to set balance to * @param amount Amount to set balance to
* @throws IllegalArgumentException If amount is negative * @throws IllegalArgumentException If amount is negative
*/ */
public void setBalance(Economable targetPlayer, double amount) { public void setBalance(Economable targetPlayer, BigDecimal amount) {
amount = NumberUtils.filterAmount(currency, amount); amount = NumberUtils.filterAmount(this.currency, amount);
if (amount < 0) { if (EconomableConsole.isConsole(targetPlayer)) {
throw new IllegalArgumentException("Cannot subtract a negative amount!");
}
if (targetPlayer == Economable.CONSOLE) {
return; return;
} }
backend.setBalance(targetPlayer, amount); this.backend.setBalance(targetPlayer, amount);
} }
/** /**
@ -163,31 +156,54 @@ public class EconomyManager {
public TransactionResult transact(Transaction transaction) { public TransactionResult transact(Transaction transaction) {
Economable sender = transaction.getSender(); Economable sender = transaction.getSender();
Economable receiver = transaction.getReceiver(); Economable receiver = transaction.getReceiver();
double amount = transaction.getAmount(); // This amount is validated upon creation of Transaction BigDecimal amount = transaction.getAmount(); // This amount is validated and filtered upon creation of Transaction
if (Bukkit.getServer() != null) { // Bukkit.getServer() == null from our JUnit tests. if (Bukkit.getServer().getPluginManager() != null) { // Bukkit.getServer().getPluginManager() == null from our JUnit tests.
SaneEconomyTransactionEvent evt = new SaneEconomyTransactionEvent(transaction); SaneEconomyTransactionEvent evt = new SaneEconomyTransactionEvent(transaction);
if (Bukkit.isPrimaryThread()) {
Bukkit.getServer().getPluginManager().callEvent(evt);
if (evt.isCancelled()) {
return new TransactionResult(transaction, TransactionResult.Status.CANCELLED_BY_PLUGIN);
}
} else {
Future<SaneEconomyTransactionEvent> future = Bukkit.getServer().getScheduler().callSyncMethod(SaneEconomy.getInstance(), () -> {
Bukkit.getServer().getPluginManager().callEvent(evt);
return evt;
});
try {
if (future.get().isCancelled()) {
return new TransactionResult(transaction, TransactionResult.Status.CANCELLED_BY_PLUGIN);
}
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
/*
Bukkit.getServer().getPluginManager().callEvent(evt); Bukkit.getServer().getPluginManager().callEvent(evt);
if (evt.isCancelled()) { if (evt.isCancelled()) {
return new TransactionResult(transaction, TransactionResult.Status.CANCELLED_BY_PLUGIN); return new TransactionResult(transaction, TransactionResult.Status.CANCELLED_BY_PLUGIN);
} }*/
} }
if (transaction.isSenderAffected()) { // Sender should have balance taken from them if (transaction.isSenderAffected()) { // Sender should have balance taken from them
if (!hasBalance(sender, amount)) { if (!this.hasBalance(sender, amount)) {
return new TransactionResult(transaction, TransactionResult.Status.ERR_NOT_ENOUGH_FUNDS); return new TransactionResult(transaction, TransactionResult.Status.ERR_NOT_ENOUGH_FUNDS);
} }
subtractBalance(sender, amount); this.subtractBalance(sender, amount);
} }
if (transaction.isReceiverAffected()) { // Receiver should have balance added to them if (transaction.isReceiverAffected()) { // Receiver should have balance added to them
addBalance(receiver, amount); this.addBalance(receiver, amount);
} }
saneEconomy.getTransactionLogger().ifPresent((logger) -> logger.logTransaction(transaction)); this.saneEconomy.getTransactionLogger().ifPresent((logger) -> logger.logTransaction(transaction));
return new TransactionResult(transaction, getBalance(sender), getBalance(receiver)); return new TransactionResult(transaction, this.getBalance(sender), this.getBalance(receiver));
} }
/** /**
@ -195,21 +211,25 @@ public class EconomyManager {
* @param amount Maximum number of players to show. * @param amount Maximum number of players to show.
* @return Map of OfflinePlayer to Double * @return Map of OfflinePlayer to Double
*/ */
public Map<OfflinePlayer, Double> getTopPlayerBalances(int amount, int offset) { public Map<String, BigDecimal> getTopBalances(int amount, int offset) {
Map<UUID, Double> uuidBalances = backend.getTopPlayerBalances(amount, offset); LinkedHashMap<String, BigDecimal> playerNamesToBalances = this.backend.getTopBalances();
Map<OfflinePlayer, Double> playerBalances = new LinkedHashMap<>();
uuidBalances.forEach((uuid, balance) -> { /*uuidBalances.re((uuid, balance) -> {
if (Bukkit.getServer().getOfflinePlayer(uuid) != null) { String playerName = this.backend.getLastName(uuid);
playerBalances.put(Bukkit.getServer().getOfflinePlayer(uuid), balance);
if (playerName != null) {
if ((this.saneEconomy.getVaultHook() == null) || !this.saneEconomy.getVaultHook().hasPermission(offlinePlayer, "saneeconomy.balancetop.hide")) {
playerBalances.put(Bukkit.getServer().getOfflinePlayer(uuid), balance);
}
} }
}); });*/
return playerBalances;
return MapUtil.skipAndTake(playerNamesToBalances, offset, amount);
} }
public EconomyStorageBackend getBackend() { public EconomyStorageBackend getBackend() {
return backend; return this.backend;
} }
/** /**
@ -217,6 +237,6 @@ public class EconomyManager {
* @return Server economy account, or null if none. * @return Server economy account, or null if none.
*/ */
public String getServerAccountName() { public String getServerAccountName() {
return serverAccountName; return this.serverAccountName;
} }
} }

View File

@ -2,9 +2,9 @@ package org.appledash.saneeconomy.economy.backend;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
import java.math.BigDecimal;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID;
/** /**
* Created by AppleDash on 6/13/2016. * Created by AppleDash on 6/13/2016.
@ -25,27 +25,32 @@ public interface EconomyStorageBackend {
* @param economable Economable * @param economable Economable
* @return Player's current balance * @return Player's current balance
*/ */
double getBalance(Economable economable); BigDecimal getBalance(Economable economable);
/** /**
* Set the balance of an Economable, overwriting the old balance. * Set the balance of an Economable, overwriting the old balance.
* @param economable Economable * @param economable Economable
* @param newBalance Player's new balance * @param newBalance Player's new balance
*/ */
void setBalance(Economable economable, double newBalance); void setBalance(Economable economable, BigDecimal newBalance);
/** /**
* Get the UUIDs of the players who have the most money, along with how much money they have. * Get the UUIDs of the players who have the most money, along with how much money they have.
* @param amount Maximum number to get.
* @return Map of player UUIDs to amounts. * @return Map of player UUIDs to amounts.
*/ */
LinkedHashMap<UUID, Double> getTopPlayerBalances(int amount, int offset); LinkedHashMap<String, BigDecimal> getTopBalances();
/** /**
* Reload this backend's database from disk. * Reload this backend's database from disk.
*/ */
void reloadDatabase(); void reloadDatabase();
/**
* Reload data for just the Economable with the given unique identifier.
* @param uniqueIdentifier Unique identifier of Economable to reload data for.
*/
void reloadEconomable(String uniqueIdentifier, EconomableReloadReason reason);
/** /**
* Reload this backend's top balances. * Reload this backend's top balances.
*/ */
@ -55,10 +60,22 @@ public interface EconomyStorageBackend {
* Get the balances of all entities in this database. * Get the balances of all entities in this database.
* @return Map of unique identifiers to balances. * @return Map of unique identifiers to balances.
*/ */
Map<String, Double> getAllBalances(); Map<String, BigDecimal> getAllBalances();
/** /**
* Wait until all of the data in memory has been written out to disk. * Wait until all of the data in memory has been written out to disk.
*/ */
void waitUntilFlushed(); void waitUntilFlushed();
/**
* Get the last name associated with a unique ID.
*
* @param uuid Unique ID.
* @return Last name, or null if none.
*/
String getLastName(String uuid);
enum EconomableReloadReason {
CROSS_SERVER_SYNC, PLAYER_JOIN
}
} }

View File

@ -1,10 +1,12 @@
package org.appledash.saneeconomy.economy.backend.type; package org.appledash.saneeconomy.economy.backend.type;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import org.appledash.saneeconomy.SaneEconomy;
import org.appledash.saneeconomy.economy.backend.EconomyStorageBackend; import org.appledash.saneeconomy.economy.backend.EconomyStorageBackend;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
import org.appledash.saneeconomy.utils.MapUtil; import org.appledash.saneeconomy.utils.MapUtil;
import java.math.BigDecimal;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -16,43 +18,55 @@ import java.util.concurrent.ConcurrentHashMap;
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public abstract class EconomyStorageBackendCaching implements EconomyStorageBackend { public abstract class EconomyStorageBackendCaching implements EconomyStorageBackend {
protected Map<String, Double> balances = new ConcurrentHashMap<>(); protected Map<String, BigDecimal> balances = new ConcurrentHashMap<>();
private LinkedHashMap<UUID, Double> topPlayerBalances = new LinkedHashMap<>(); private LinkedHashMap<String, BigDecimal> topBalances = new LinkedHashMap<>();
protected Map<String, String> uuidToName = new HashMap<>();
@Override @Override
public boolean accountExists(Economable economable) { public boolean accountExists(Economable economable) {
return balances.containsKey(economable.getUniqueIdentifier()); return this.balances.containsKey(economable.getUniqueIdentifier());
} }
@Override @Override
public double getBalance(Economable economable) { public BigDecimal getBalance(Economable economable) {
if (!accountExists(economable)) { if (!this.accountExists(economable)) {
return 0.0D; return BigDecimal.ZERO;
} }
return balances.get(economable.getUniqueIdentifier()); return this.balances.get(economable.getUniqueIdentifier());
} }
@Override public LinkedHashMap<String, BigDecimal> getTopBalances() {
public LinkedHashMap<UUID, Double> getTopPlayerBalances(int amount, int offset) { return this.topBalances;
return MapUtil.skipAndTake(topPlayerBalances, offset, amount);
} }
@Override @Override
public void reloadTopPlayerBalances() { public void reloadTopPlayerBalances() {
Map<UUID, Double> playerBalances = new HashMap<>(); Map<String, BigDecimal> balances = new HashMap<>();
balances.forEach((identifier, balance) -> { this.balances.forEach((identifier, balance) ->
if (identifier.startsWith("player:")) { // FIXME: Come on now... balances.put(this.uuidToName.get(identifier), balance)
playerBalances.put(UUID.fromString(identifier.substring("player:".length())), balance); );
}
});
topPlayerBalances = MapUtil.sortByValue(playerBalances); this.topBalances = MapUtil.sortByValue(balances);
} }
@Override @Override
public Map<String, Double> getAllBalances() { public Map<String, BigDecimal> getAllBalances() {
return ImmutableMap.copyOf(balances); return ImmutableMap.copyOf(this.balances);
}
@Override
public void reloadEconomable(String uniqueIdentifier, EconomableReloadReason reason) {
if (reason == EconomableReloadReason.CROSS_SERVER_SYNC) {
SaneEconomy.logger().warning("Trying to reload a single Economable from backend which does not support this - " + this.getClass().getSimpleName() + ". Recommend switching to MySQL backend for multi-server support.");
}
this.reloadDatabase();
}
@Override
public String getLastName(String uuid) {
return this.uuidToName.get(uuid);
} }
} }

View File

@ -1,109 +0,0 @@
package org.appledash.saneeconomy.economy.backend.type;
import com.google.common.io.Files;
import org.appledash.saneeconomy.SaneEconomy;
import org.appledash.saneeconomy.economy.economable.Economable;
import java.io.*;
import java.util.Map;
import java.util.UUID;
/**
* Created by AppleDash on 6/13/2016.
* Blackjack is still best pony.
*/
public class EconomyStorageBackendFlatfile extends EconomyStorageBackendCaching {
private static final int SCHEMA_VERSION = 2;
private final File file;
public EconomyStorageBackendFlatfile(File file) {
this.file = file;
}
@SuppressWarnings("unchecked")
@Override
public synchronized void reloadDatabase() {
if (!file.exists()) {
return;
}
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
int schemaVer = ois.readInt();
if (schemaVer == 1) {
ois.close();
loadSchemaVersion1(file);
return;
}
if (schemaVer != SCHEMA_VERSION) {
// ???
SaneEconomy.logger().severe("Unrecognized flatfile database version " + schemaVer + ", cannot load database!");
return;
}
balances = (Map<String, Double>) ois.readObject();
ois.close();
} catch (IOException | ClassNotFoundException | ClassCastException e) {
SaneEconomy.logger().severe("Failed to load flatfile database!");
e.printStackTrace();
}
}
private void loadSchemaVersion1(File file) {
SaneEconomy.logger().info("Upgrading flatfile database from version 1.");
try {
Files.copy(file, new File(file.getParentFile(), file.getName() + "-backup"));
SaneEconomy.logger().info("Backed up old flatfile database.");
} catch (IOException e) {
throw new RuntimeException("Failed to back up flatfile database!");
}
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
ois.readInt(); // We already know it's 1.
Map<UUID, Double> oldBalances = (Map<UUID, Double>) ois.readObject();
oldBalances.forEach((uuid, balance) -> balances.put("player:" + uuid, balance));
ois.close();
/* Yes, this is kind of bad, but we want to make sure we're loading AND saving the new version of the DB. */
saveDatabase();
reloadDatabase();
} catch (IOException | ClassNotFoundException e) {
SaneEconomy.logger().severe("Failed to upgrade flatfile database! Recommend reporting this bug and reverting to an older version of the plugin.");
throw new RuntimeException("Failed to upgrade flatfile database!", e);
}
}
private void saveDatabase() {
if (file.exists()) {
file.delete();
}
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeInt(SCHEMA_VERSION);
oos.writeObject(balances);
oos.close();
} catch (IOException e) {
SaneEconomy.logger().severe("Failed to save flatfile database!");
}
}
@Override
public synchronized void setBalance(Economable player, double newBalance) {
balances.put(player.getUniqueIdentifier(), newBalance);
saveDatabase();
}
@Override
public void waitUntilFlushed() {
// Do nothing, database is automatically flushed on every write.
}
}

View File

@ -1,11 +1,12 @@
package org.appledash.saneeconomy.economy.backend.type; package org.appledash.saneeconomy.economy.backend.type;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
import java.io.*; import java.io.*;
import java.math.BigDecimal;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -15,29 +16,47 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public class EconomyStorageBackendJSON extends EconomyStorageBackendCaching { public class EconomyStorageBackendJSON extends EconomyStorageBackendCaching {
private final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create(); private final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
private File file; private final File file;
public EconomyStorageBackendJSON(File file) { public EconomyStorageBackendJSON(File file) {
this.file = file; this.file = file;
} }
@Override @Override
public void setBalance(Economable economable, double newBalance) { public void setBalance(Economable economable, BigDecimal newBalance) {
balances.put(economable.getUniqueIdentifier(), newBalance); this.balances.put(economable.getUniqueIdentifier(), newBalance);
saveDatabase(); this.uuidToName.put(economable.getUniqueIdentifier(), economable.getName());
this.saveDatabase();
} }
@Override @Override
@SuppressWarnings("unchecked")
public void reloadDatabase() { public void reloadDatabase() {
if (!file.exists()) { if (!this.file.exists()) {
return; return;
} }
try { try {
balances = new ConcurrentHashMap<>((Map)gson.fromJson(new FileReader(file), new TypeToken<Map<String, Double>>(){}.getType())); // try to load the old format and convert it
// if that fails, load the new format
DataHolderOld dataHolder = this.gson.fromJson(new FileReader(this.file), DataHolderOld.class);
this.balances = new ConcurrentHashMap<>();
this.uuidToName = new ConcurrentHashMap<>(dataHolder.uuidToName);
dataHolder.balances.forEach((s, bal) ->
this.balances.put(s, new BigDecimal(bal))
);
this.saveDatabase();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new RuntimeException("Failed to load database!", e); throw new RuntimeException("Failed to load database!", e);
} catch (Exception e) {
try {
DataHolder dataHolder = this.gson.fromJson(new FileReader(this.file), DataHolder.class);
this.balances = new ConcurrentHashMap<>(dataHolder.balances);
this.uuidToName = new ConcurrentHashMap<>(dataHolder.uuidToName);
} catch (FileNotFoundException ex) {
throw new RuntimeException("Failed to load database!", ex);
}
} }
} }
@ -47,11 +66,37 @@ public class EconomyStorageBackendJSON extends EconomyStorageBackendCaching {
} }
private synchronized void saveDatabase() { private synchronized void saveDatabase() {
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file, false))) { try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(this.file, false))) {
bufferedWriter.write(gson.toJson(balances)); DataHolder dataHolder = new DataHolder(this.balances, this.uuidToName);
bufferedWriter.close(); bufferedWriter.write(this.gson.toJson(dataHolder));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Failed to save database", e); throw new RuntimeException("Failed to save database", e);
} }
} }
@SuppressWarnings({"FieldMayBeFinal", "CanBeFinal"})
private static class DataHolderOld {
@SerializedName("balances")
private Map<String, Double> balances;
@SerializedName("uuidToName")
private Map<String, String> uuidToName;
DataHolderOld(Map<String, Double> balances, Map<String, String> uuidToName) {
this.balances = balances;
this.uuidToName = uuidToName;
}
}
@SuppressWarnings("FieldMayBeFinal")
private static class DataHolder {
@SerializedName("balances")
private Map<String, BigDecimal> balances;
@SerializedName("uuidToName")
private Map<String, String> uuidToName;
DataHolder(Map<String, BigDecimal> balances, Map<String, String> uuidToName) {
this.balances = balances;
this.uuidToName = uuidToName;
}
}
} }

View File

@ -4,6 +4,7 @@ import org.appledash.saneeconomy.economy.economable.Economable;
import org.appledash.saneeconomy.utils.database.MySQLConnection; import org.appledash.saneeconomy.utils.database.MySQLConnection;
import org.appledash.sanelib.database.DatabaseCredentials; import org.appledash.sanelib.database.DatabaseCredentials;
import java.math.BigDecimal;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -17,6 +18,9 @@ import java.util.logging.Logger;
*/ */
public class EconomyStorageBackendMySQL extends EconomyStorageBackendCaching { public class EconomyStorageBackendMySQL extends EconomyStorageBackendCaching {
private static final Logger LOGGER = Logger.getLogger("EconomyStorageBackendMySQL"); private static final Logger LOGGER = Logger.getLogger("EconomyStorageBackendMySQL");
private static final String SANEECONOMY_BALANCES = "saneeconomy_balances";
private static final String SANEECONOMY_SCHEMA = "saneeconomy_schema";
static { static {
LOGGER.setLevel(Level.FINEST); LOGGER.setLevel(Level.FINEST);
} }
@ -27,31 +31,47 @@ public class EconomyStorageBackendMySQL extends EconomyStorageBackendCaching {
} }
private void createTables() { private void createTables() {
try (Connection conn = dbConn.openConnection()) { try (Connection conn = this.dbConn.openConnection()) {
int schemaVersion; int schemaVersion;
if (!checkTableExists(dbConn.getTable("saneeconomy_schema"))) { if (!this.checkTableExists(this.dbConn.getTable(SANEECONOMY_SCHEMA))) {
schemaVersion = -1; schemaVersion = -1;
} else { } else {
PreparedStatement ps = conn.prepareStatement(String.format("SELECT `val` FROM `%s` WHERE `key` = 'schema_version'", dbConn.getTable("saneeconomy_schema"))); PreparedStatement ps = conn.prepareStatement(String.format("SELECT `val` FROM `%s` WHERE `key` = 'schema_version'", this.dbConn.getTable(SANEECONOMY_SCHEMA)));
ps.executeQuery(); ResultSet rs = ps.executeQuery();
ResultSet rs = ps.getResultSet();
if (!rs.next()) { if (!rs.next()) {
throw new RuntimeException("Invalid database schema!"); throw new RuntimeException("Invalid database schema!");
} }
schemaVersion = Integer.valueOf(rs.getString("val")); schemaVersion = Integer.parseInt(rs.getString("val"));
} }
if (schemaVersion == -1) { if (schemaVersion == -1) {
conn.prepareStatement(String.format("CREATE TABLE IF NOT EXISTS `%s` (`key` VARCHAR(32) PRIMARY KEY, `val` TEXT)", dbConn.getTable("saneeconomy_schema"))).executeUpdate(); conn.prepareStatement(String.format("CREATE TABLE IF NOT EXISTS `%s` (`key` VARCHAR(32) PRIMARY KEY, `val` TEXT)", this.dbConn.getTable(SANEECONOMY_SCHEMA))).executeUpdate();
conn.prepareStatement(String.format("REPLACE INTO %s (`key`, `val`) VALUES ('schema_version', 2)", dbConn.getTable("saneeconomy_schema"))); conn.prepareStatement(String.format("REPLACE INTO %s (`key`, `val`) VALUES ('schema_version', 4)", this.dbConn.getTable(SANEECONOMY_SCHEMA))).executeUpdate();
conn.prepareStatement(String.format("CREATE TABLE `%s` (unique_identifier VARCHAR(128) PRIMARY KEY, balance DECIMAL(18, 2))", dbConn.getTable("saneeconomy_balances"))).executeUpdate(); conn.prepareStatement(String.format("CREATE TABLE `%s` (unique_identifier VARCHAR(128) PRIMARY KEY, last_name VARCHAR(16), balance TEXT)", this.dbConn.getTable(SANEECONOMY_BALANCES))).executeUpdate();
schemaVersion = 2; schemaVersion = 4;
} }
if (schemaVersion != 2) { if (schemaVersion == 2) {
conn.prepareStatement(String.format("ALTER TABLE `%s` ADD `last_name` VARCHAR(16)", this.dbConn.getTable(SANEECONOMY_BALANCES))).executeUpdate();
conn.prepareStatement(String.format("REPLACE INTO %s (`key`, `val`) VALUES ('schema_version', 3)", this.dbConn.getTable(SANEECONOMY_SCHEMA))).executeUpdate();
schemaVersion = 3;
}
if (schemaVersion == 3) {
conn.prepareStatement(String.format("ALTER TABLE `%s` ADD `balance_new` TEXT", this.dbConn.getTable(SANEECONOMY_BALANCES))).executeUpdate();
conn.prepareStatement(String.format("UPDATE `%s` SET balance_new = balance", this.dbConn.getTable(SANEECONOMY_BALANCES))).executeUpdate();
conn.prepareStatement(String.format("ALTER TABLE `%s` DROP COLUMN `balance`", this.dbConn.getTable(SANEECONOMY_BALANCES))).executeUpdate();
conn.prepareStatement(String.format("ALTER TABLE `%s` CHANGE COLUMN `balance_new` `balance` TEXT", this.dbConn.getTable(SANEECONOMY_BALANCES))).executeUpdate();
conn.prepareStatement(String.format("REPLACE INTO %s (`key`, `val`) VALUES ('schema_version', 4)", this.dbConn.getTable(SANEECONOMY_SCHEMA))).executeUpdate();
schemaVersion = 4;
}
if (schemaVersion != 4) {
throw new RuntimeException("Invalid database schema version!"); throw new RuntimeException("Invalid database schema version!");
} }
} catch (SQLException e) { } catch (SQLException e) {
@ -60,9 +80,9 @@ public class EconomyStorageBackendMySQL extends EconomyStorageBackendCaching {
} }
private boolean checkTableExists(String tableName) { private boolean checkTableExists(String tableName) {
try (Connection conn = dbConn.openConnection()) { try (Connection conn = this.dbConn.openConnection()) {
PreparedStatement ps = conn.prepareStatement("SELECT * FROM information_schema.tables WHERE table_schema = ? AND table_name = ? LIMIT 1"); PreparedStatement ps = conn.prepareStatement("SELECT * FROM information_schema.tables WHERE table_schema = ? AND table_name = ? LIMIT 1");
ps.setString(1, dbConn.getCredentials().getDatabaseName()); ps.setString(1, this.dbConn.getCredentials().getDatabaseName());
ps.setString(2, tableName); ps.setString(2, tableName);
ps.executeQuery(); ps.executeQuery();
ResultSet rs = ps.getResultSet(); ResultSet rs = ps.getResultSet();
@ -75,16 +95,17 @@ public class EconomyStorageBackendMySQL extends EconomyStorageBackendCaching {
@Override @Override
public synchronized void reloadDatabase() { public synchronized void reloadDatabase() {
waitUntilFlushed(); this.waitUntilFlushed();
createTables(); this.createTables();
try (Connection conn = dbConn.openConnection()) { try (Connection conn = this.dbConn.openConnection()) {
PreparedStatement ps = dbConn.prepareStatement(conn, String.format("SELECT * FROM `%s`", dbConn.getTable("saneeconomy_balances"))); PreparedStatement ps = this.dbConn.prepareStatement(conn, String.format("SELECT * FROM `%s`", this.dbConn.getTable(SANEECONOMY_BALANCES)));
ResultSet rs = ps.executeQuery(); ResultSet rs = ps.executeQuery();
balances.clear(); this.balances.clear();
while (rs.next()) { while (rs.next()) {
balances.put(rs.getString("unique_identifier"), rs.getDouble("balance")); this.balances.put(rs.getString("unique_identifier"), new BigDecimal(rs.getString("balance")));
this.uuidToName.put(rs.getString("unique_identifier"), rs.getString("last_name"));
} }
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Failed to reload data from SQL.", e); throw new RuntimeException("Failed to reload data from SQL.", e);
@ -92,36 +113,39 @@ public class EconomyStorageBackendMySQL extends EconomyStorageBackendCaching {
} }
@Override @Override
public void setBalance(final Economable economable, final double newBalance) { public void setBalance(Economable economable, BigDecimal newBalance) {
final double oldBalance = getBalance(economable); BigDecimal oldBalance = this.getBalance(economable);
balances.put(economable.getUniqueIdentifier(), newBalance); this.balances.put(economable.getUniqueIdentifier(), newBalance);
this.uuidToName.put(economable.getUniqueIdentifier(), economable.getName());
dbConn.executeAsyncOperation("set_balance_" + economable.getUniqueIdentifier(), (conn) -> { this.dbConn.executeAsyncOperation("set_balance_" + economable.getUniqueIdentifier(), (conn) -> {
try { try {
ensureAccountExists(economable, conn); this.ensureAccountExists(economable, conn);
conn.prepareStatement("LOCK TABLE " + dbConn.getTable("saneeconomy_balances") + " WRITE").execute(); this.dbConn.lockTable(conn, SANEECONOMY_BALANCES);
PreparedStatement statement = dbConn.prepareStatement(conn, String.format("UPDATE `%s` SET balance = ? WHERE `unique_identifier` = ?", dbConn.getTable("saneeconomy_balances"))); PreparedStatement statement = this.dbConn.prepareStatement(conn, String.format("UPDATE `%s` SET balance = ?, last_name = ? WHERE `unique_identifier` = ?", this.dbConn.getTable(SANEECONOMY_BALANCES)));
statement.setDouble(1, newBalance); statement.setString(1, newBalance.toString());
statement.setString(2, economable.getUniqueIdentifier()); statement.setString(2, economable.getName());
statement.setString(3, economable.getUniqueIdentifier());
statement.executeUpdate(); statement.executeUpdate();
conn.prepareStatement("UNLOCK TABLES").execute(); this.dbConn.unlockTables(conn);
} catch (Exception e) { } catch (Exception e) {
balances.put(economable.getUniqueIdentifier(), oldBalance); this.balances.put(economable.getUniqueIdentifier(), oldBalance);
throw new RuntimeException("SQL error has occurred.", e); throw new RuntimeException("SQL error has occurred.", e);
} }
}); });
} }
private void ensureAccountExists(Economable economable, Connection conn) throws SQLException { private void ensureAccountExists(Economable economable, Connection conn) throws SQLException {
if (!accountExists(economable, conn)) { if (!this.accountExists(economable, conn)) {
PreparedStatement statement = dbConn.prepareStatement(conn, String.format("INSERT INTO `%s` (unique_identifier, balance) VALUES (?, 0.0)", dbConn.getTable("saneeconomy_balances"))); PreparedStatement statement = this.dbConn.prepareStatement(conn, String.format("INSERT INTO `%s` (unique_identifier, last_name, balance) VALUES (?, ?, 0.0)", this.dbConn.getTable(SANEECONOMY_BALANCES)));
statement.setString(1, economable.getUniqueIdentifier()); statement.setString(1, economable.getUniqueIdentifier());
statement.setString(2, economable.getName());
statement.executeUpdate(); statement.executeUpdate();
} }
} }
private boolean accountExists(Economable economable, Connection conn) throws SQLException { private boolean accountExists(Economable economable, Connection conn) throws SQLException {
PreparedStatement statement = dbConn.prepareStatement(conn, String.format("SELECT 1 FROM `%s` WHERE `unique_identifier` = ?", dbConn.getTable("saneeconomy_balances"))); PreparedStatement statement = this.dbConn.prepareStatement(conn, String.format("SELECT 1 FROM `%s` WHERE `unique_identifier` = ?", this.dbConn.getTable(SANEECONOMY_BALANCES)));
statement.setString(1, economable.getUniqueIdentifier()); statement.setString(1, economable.getUniqueIdentifier());
ResultSet rs = statement.executeQuery(); ResultSet rs = statement.executeQuery();
@ -131,14 +155,32 @@ public class EconomyStorageBackendMySQL extends EconomyStorageBackendCaching {
@Override @Override
public void waitUntilFlushed() { public void waitUntilFlushed() {
dbConn.waitUntilFlushed(); this.dbConn.waitUntilFlushed();
} }
public MySQLConnection getConnection() { public MySQLConnection getConnection() {
return dbConn; return this.dbConn;
} }
public void closeConnections() { public void closeConnections() {
this.dbConn.getConnection().cleanup(); this.dbConn.getConnection().cleanup();
} }
@Override
public void reloadEconomable(String uniqueIdentifier, EconomableReloadReason reason) {
this.dbConn.executeAsyncOperation("reload_economable_" + uniqueIdentifier, (conn) -> {
try {
PreparedStatement ps = conn.prepareStatement(String.format("SELECT balance FROM `%s` WHERE `unique_identifier` = ?", this.dbConn.getTable(SANEECONOMY_BALANCES)));
ps.setString(1, uniqueIdentifier);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
this.balances.put(uniqueIdentifier, new BigDecimal(rs.getString("balance")));
}
} catch (SQLException e) {
throw new RuntimeException("SQL error has occured", e);
}
});
}
} }

View File

@ -13,6 +13,10 @@ public interface Economable {
Economable PLUGIN = new EconomablePlugin(); Economable PLUGIN = new EconomablePlugin();
String getUniqueIdentifier(); String getUniqueIdentifier();
String getName();
default OfflinePlayer tryCastToPlayer() {
return null;
}
static Economable wrap(OfflinePlayer player) { static Economable wrap(OfflinePlayer player) {
return new EconomablePlayer(player); return new EconomablePlayer(player);

View File

@ -1,12 +1,32 @@
package org.appledash.saneeconomy.economy.economable; package org.appledash.saneeconomy.economy.economable;
import java.util.UUID;
/** /**
* Created by appledash on 9/21/16. * Created by appledash on 9/21/16.
* Blackjack is best pony. * Blackjack is best pony.
*/ */
public class EconomableConsole implements Economable { public class EconomableConsole implements Economable {
public static final UUID CONSOLE_UUID = new UUID( 0xf88708c237d84a0bL, 0x944259c68e517557L); // Pregenerated for performance
@Override @Override
public String getUniqueIdentifier() { public String getName() {
return "CONSOLE"; return "CONSOLE";
} }
@Override
public String getUniqueIdentifier() {
return "console:" + CONSOLE_UUID;
}
public static boolean isConsole(Economable economable) {
try {
UUID uuid = UUID.fromString(economable.getUniqueIdentifier().split(":")[1]);
// Fast comparison + fix for bugs with older databases
return economable == Economable.CONSOLE || (uuid.getLeastSignificantBits() == CONSOLE_UUID.getLeastSignificantBits() || uuid.getMostSignificantBits() == CONSOLE_UUID.getMostSignificantBits());
} catch (Exception e) {
return false;
}
}
} }

View File

@ -13,6 +13,12 @@ public class EconomableFaction implements Economable {
@Override @Override
public String getUniqueIdentifier() { public String getUniqueIdentifier() {
return "faction:" + factionUuid; return "faction:" + this.factionUuid;
}
@Override
public String getName() {
// FIXME
return this.factionUuid;
} }
} }

View File

@ -13,6 +13,11 @@ public class EconomableGeneric implements Economable {
@Override @Override
public String getUniqueIdentifier() { public String getUniqueIdentifier() {
return uniqueIdentifier; return this.uniqueIdentifier;
}
@Override
public String getName() {
return this.uniqueIdentifier.substring(16); // FIXME: Why 16?
} }
} }

View File

@ -13,8 +13,18 @@ public class EconomablePlayer implements Economable {
this.handle = handle; this.handle = handle;
} }
@Override
public String getName() {
return this.handle.getName();
}
@Override @Override
public String getUniqueIdentifier() { public String getUniqueIdentifier() {
return "player:" + handle.getUniqueId(); return "player:" + this.handle.getUniqueId();
}
@Override
public OfflinePlayer tryCastToPlayer() {
return this.handle;
} }
} }

View File

@ -9,4 +9,10 @@ public class EconomablePlugin implements Economable {
public String getUniqueIdentifier() { public String getUniqueIdentifier() {
return "PLUGIN"; return "PLUGIN";
} }
@Override
public String getName() {
// FIXME
return "PLUGIN";
}
} }

View File

@ -5,6 +5,7 @@ import org.appledash.saneeconomy.economy.transaction.TransactionReason;
import org.appledash.saneeconomy.utils.database.MySQLConnection; import org.appledash.saneeconomy.utils.database.MySQLConnection;
import org.appledash.sanelib.database.DatabaseCredentials; import org.appledash.sanelib.database.DatabaseCredentials;
import java.math.BigDecimal;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
@ -20,13 +21,13 @@ public class TransactionLoggerMySQL implements TransactionLogger {
this.dbConn = new MySQLConnection(credentials); this.dbConn = new MySQLConnection(credentials);
} }
private void logGeneric(String from, String to, double change, TransactionReason reason) { private void logGeneric(String from, String to, BigDecimal change, TransactionReason reason) {
this.dbConn.executeAsyncOperation("log_transaction", (conn) -> { this.dbConn.executeAsyncOperation("log_transaction", (conn) -> {
try { try {
PreparedStatement ps = conn.prepareStatement(String.format("INSERT INTO `%s` (`source`, `destination`, `amount`, `reason`) VALUES (?, ?, ?, ?)", dbConn.getTable("transaction_logs"))); PreparedStatement ps = conn.prepareStatement(String.format("INSERT INTO `%s` (`source`, `destination`, `amount`, `reason`) VALUES (?, ?, ?, ?)", this.dbConn.getTable("transaction_logs")));
ps.setString(1, from); ps.setString(1, from);
ps.setString(2, to); ps.setString(2, to);
ps.setDouble(3, change); ps.setString(3, change.toString());
ps.setString(4, reason.toString()); ps.setString(4, reason.toString());
ps.executeUpdate(); ps.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
@ -36,8 +37,8 @@ public class TransactionLoggerMySQL implements TransactionLogger {
} }
public boolean testConnection() { public boolean testConnection() {
if (dbConn.testConnection()) { if (this.dbConn.testConnection()) {
createTables(); this.createTables();
return true; return true;
} }
@ -45,8 +46,8 @@ public class TransactionLoggerMySQL implements TransactionLogger {
} }
private void createTables() { private void createTables() {
try (Connection conn = dbConn.openConnection()) { try (Connection conn = this.dbConn.openConnection()) {
PreparedStatement ps = conn.prepareStatement(String.format("CREATE TABLE IF NOT EXISTS `%s` (`source` VARCHAR(128), `destination` VARCHAR(128), `amount` DECIMAL(18, 2), `reason` VARCHAR(128), `logged` TIMESTAMP NOT NULL default CURRENT_TIMESTAMP)", dbConn.getTable("transaction_logs"))); PreparedStatement ps = conn.prepareStatement(String.format("CREATE TABLE IF NOT EXISTS `%s` (`source` VARCHAR(128), `destination` VARCHAR(128), `amount` DECIMAL(18, 2), `reason` VARCHAR(128), `logged` TIMESTAMP NOT NULL default CURRENT_TIMESTAMP)", this.dbConn.getTable("transaction_logs")));
ps.executeUpdate(); ps.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Failed to create transaction logger tables", e); throw new RuntimeException("Failed to create transaction logger tables", e);
@ -55,6 +56,6 @@ public class TransactionLoggerMySQL implements TransactionLogger {
@Override @Override
public void logTransaction(Transaction transaction) { public void logTransaction(Transaction transaction) {
logGeneric(transaction.getSender().getUniqueIdentifier(), transaction.getReceiver().getUniqueIdentifier(), transaction.getAmount(), transaction.getReason()); this.logGeneric(transaction.getSender().getUniqueIdentifier(), transaction.getReceiver().getUniqueIdentifier(), transaction.getAmount(), transaction.getReason());
} }
} }

View File

@ -1,7 +1,12 @@
package org.appledash.saneeconomy.economy.transaction; package org.appledash.saneeconomy.economy.transaction;
import org.appledash.saneeconomy.economy.Currency;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
import org.appledash.saneeconomy.economy.economable.EconomableConsole;
import org.appledash.saneeconomy.economy.transaction.TransactionReason.AffectedParties; import org.appledash.saneeconomy.economy.transaction.TransactionReason.AffectedParties;
import org.appledash.saneeconomy.utils.NumberUtils;
import java.math.BigDecimal;
/** /**
* Created by appledash on 9/21/16. * Created by appledash on 9/21/16.
@ -10,49 +15,50 @@ import org.appledash.saneeconomy.economy.transaction.TransactionReason.AffectedP
public class Transaction { public class Transaction {
private final Economable sender; private final Economable sender;
private final Economable receiver; private final Economable receiver;
private final double amount; private final BigDecimal amount;
private final TransactionReason reason; private final TransactionReason reason;
public Transaction(Economable sender, Economable receiver, double amount, TransactionReason reason) { public Transaction(Currency currency, Economable sender, Economable receiver, BigDecimal amount, TransactionReason reason) {
if (amount <= 0.0) { if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Cannot transact a zero or negative amount!"); throw new IllegalArgumentException("Cannot transact a zero or negative amount!");
} }
this.sender = sender; this.sender = sender;
this.receiver = receiver; this.receiver = receiver;
this.amount = amount; this.amount = NumberUtils.filterAmount(currency, amount);
this.reason = reason; this.reason = reason;
} }
public Economable getSender() { public Economable getSender() {
return sender; return this.sender;
} }
public Economable getReceiver() { public Economable getReceiver() {
return receiver; return this.receiver;
} }
public double getAmount() { public BigDecimal getAmount() {
return amount; return this.amount;
} }
public TransactionReason getReason() { public TransactionReason getReason() {
return reason; return this.reason;
} }
public boolean isSenderAffected() { public boolean isSenderAffected() {
if (sender == Economable.CONSOLE) { if (EconomableConsole.isConsole(this.sender)) {
return false; return false;
} }
return (reason.getAffectedParties() == AffectedParties.SENDER) || (reason.getAffectedParties() == AffectedParties.BOTH); return (this.reason.getAffectedParties() == AffectedParties.SENDER) || (this.reason.getAffectedParties() == AffectedParties.BOTH);
} }
public boolean isReceiverAffected() { public boolean isReceiverAffected() {
if (receiver == Economable.CONSOLE) { if (EconomableConsole.isConsole(this.receiver)) {
return false; return false;
} }
return (reason.getAffectedParties() == AffectedParties.RECEIVER) || (reason.getAffectedParties() == AffectedParties.BOTH); return (this.reason.getAffectedParties() == AffectedParties.RECEIVER) || (this.reason.getAffectedParties() == AffectedParties.BOTH);
} }
} }

View File

@ -36,7 +36,7 @@ public enum TransactionReason {
} }
public AffectedParties getAffectedParties() { public AffectedParties getAffectedParties() {
return affectedParties; return this.affectedParties;
} }
public enum AffectedParties { public enum AffectedParties {

View File

@ -1,16 +1,18 @@
package org.appledash.saneeconomy.economy.transaction; package org.appledash.saneeconomy.economy.transaction;
import java.math.BigDecimal;
/** /**
* Created by appledash on 9/21/16. * Created by appledash on 9/21/16.
* Blackjack is best pony. * Blackjack is best pony.
*/ */
public class TransactionResult { public class TransactionResult {
private final Transaction transaction; private final Transaction transaction;
private final double fromBalance; private final BigDecimal fromBalance;
private final double toBalance; private final BigDecimal toBalance;
private Status status; private Status status;
public TransactionResult(Transaction transaction, double fromBalance, double toBalance) { public TransactionResult(Transaction transaction, BigDecimal fromBalance, BigDecimal toBalance) {
this.transaction = transaction; this.transaction = transaction;
this.fromBalance = fromBalance; this.fromBalance = fromBalance;
this.toBalance = toBalance; this.toBalance = toBalance;
@ -18,24 +20,24 @@ public class TransactionResult {
} }
public TransactionResult(Transaction transaction, Status status) { public TransactionResult(Transaction transaction, Status status) {
this(transaction, -1, -1); this(transaction, BigDecimal.ONE.negate(), BigDecimal.ONE.negate());
this.status = status; this.status = status;
} }
public Transaction getTransaction() { public Transaction getTransaction() {
return transaction; return this.transaction;
} }
public double getFromBalance() { public BigDecimal getFromBalance() {
return fromBalance; return this.fromBalance;
} }
public double getToBalance() { public BigDecimal getToBalance() {
return toBalance; return this.toBalance;
} }
public Status getStatus() { public Status getStatus() {
return status; return this.status;
} }
public enum Status { public enum Status {
@ -50,7 +52,7 @@ public class TransactionResult {
} }
public String getMessage() { public String getMessage() {
return message; return this.message;
} }
} }
} }

View File

@ -12,7 +12,7 @@ import org.bukkit.event.HandlerList;
* This event is called whenever a Transaction occurs in the plugin. If you cancel this event, the transaction will be cancelled as well. * This event is called whenever a Transaction occurs in the plugin. If you cancel this event, the transaction will be cancelled as well.
*/ */
public class SaneEconomyTransactionEvent extends Event implements Cancellable { public class SaneEconomyTransactionEvent extends Event implements Cancellable {
private static HandlerList handlerList = new HandlerList(); private static final HandlerList handlerList = new HandlerList();
private final Transaction transaction; private final Transaction transaction;
private boolean isCancelled; private boolean isCancelled;
@ -36,7 +36,7 @@ public class SaneEconomyTransactionEvent extends Event implements Cancellable {
} }
public Transaction getTransaction() { public Transaction getTransaction() {
return transaction; return this.transaction;
} }
public static HandlerList getHandlerList() { public static HandlerList getHandlerList() {

View File

@ -1,6 +1,7 @@
package org.appledash.saneeconomy.listeners; package org.appledash.saneeconomy.listeners;
import org.appledash.saneeconomy.SaneEconomy; import org.appledash.saneeconomy.SaneEconomy;
import org.appledash.saneeconomy.economy.backend.EconomyStorageBackend;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
import org.appledash.saneeconomy.economy.transaction.Transaction; import org.appledash.saneeconomy.economy.transaction.Transaction;
import org.appledash.saneeconomy.economy.transaction.TransactionReason; import org.appledash.saneeconomy.economy.transaction.TransactionReason;
@ -13,6 +14,8 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import java.math.BigDecimal;
/** /**
* Created by AppleDash on 6/13/2016. * Created by AppleDash on 6/13/2016.
* Blackjack is still best pony. * Blackjack is still best pony.
@ -28,28 +31,28 @@ public class JoinQuitListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent evt) { public void onPlayerJoin(PlayerJoinEvent evt) {
Player player = evt.getPlayer(); Player player = evt.getPlayer();
Economable economable = Economable.wrap((OfflinePlayer) player); Economable economable = Economable.wrap((OfflinePlayer) player);
double startBalance = plugin.getConfig().getDouble("economy.start-balance", 0.0D); BigDecimal startBalance = BigDecimal.valueOf(this.plugin.getConfig().getDouble("economy.start-balance", 0.0D));
/* A starting balance is configured AND they haven't been given it yet. */ /* A starting balance is configured AND they haven't been given it yet. */
if ((startBalance > 0) && !plugin.getEconomyManager().accountExists(economable)) { if ((startBalance.compareTo(BigDecimal.ZERO) > 0) && !this.plugin.getEconomyManager().accountExists(economable)) {
plugin.getEconomyManager().transact(new Transaction( this.plugin.getEconomyManager().transact(new Transaction(
Economable.CONSOLE, economable, startBalance, TransactionReason.STARTING_BALANCE this.plugin.getEconomyManager().getCurrency(), Economable.CONSOLE, economable, startBalance, TransactionReason.STARTING_BALANCE
)); ));
if (plugin.getConfig().getBoolean("economy.notify-start-balance", true)) { if (this.plugin.getConfig().getBoolean("economy.notify-start-balance", true)) {
this.plugin.getMessenger().sendMessage(player, "You've been issued a starting balance of {1}!", plugin.getEconomyManager().getCurrency().formatAmount(startBalance)); this.plugin.getMessenger().sendMessage(player, "You've been issued a starting balance of {1}!", this.plugin.getEconomyManager().getCurrency().formatAmount(startBalance));
} }
} }
/* Update notification */ /* Update notification */
if (player.hasPermission("saneeconomy.update-notify") && plugin.getVersionChecker().isUpdateAvailable()) { if ((this.plugin.getVersionChecker() != null) && player.hasPermission("saneeconomy.update-notify") && this.plugin.getVersionChecker().isUpdateAvailable()) {
this.plugin.getMessenger().sendMessage(player, "An update is available! The currently-installed version is {1}, but the newest available is {2}. Please go to {3} to update!", plugin.getDescription().getVersion(), plugin.getVersionChecker().getNewestVersion(), GithubVersionChecker.DOWNLOAD_URL); this.plugin.getMessenger().sendMessage(player, "An update is available! The currently-installed version is {1}, but the newest available is {2}. Please go to {3} to update!", this.plugin.getDescription().getVersion(), this.plugin.getVersionChecker().getNewestVersion(), GithubVersionChecker.DOWNLOAD_URL);
} }
} }
@EventHandler @EventHandler
public void onPlayerLogin(AsyncPlayerPreLoginEvent evt) { public void onPlayerLogin(AsyncPlayerPreLoginEvent evt) {
Bukkit.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, () -> { Bukkit.getServer().getScheduler().runTaskAsynchronously(this.plugin, () -> {
plugin.getEconomyManager().getBackend().reloadDatabase(); // TODO: If servers start to lag when lots of people join, this is why. this.plugin.getEconomyManager().getBackend().reloadEconomable(String.format("player:%s", evt.getUniqueId()), EconomyStorageBackend.EconomableReloadReason.PLAYER_JOIN); // TODO: If servers start to lag when lots of people join, this is why.
}, 0); });
} }
} }

View File

@ -13,9 +13,10 @@ import org.appledash.saneeconomy.utils.WebUtils;
public class GithubVersionChecker { public class GithubVersionChecker {
public static final String DOWNLOAD_URL = "https://github.com/AppleDash/SaneEconomy/releases"; public static final String DOWNLOAD_URL = "https://github.com/AppleDash/SaneEconomy/releases";
private static final String RELEASES_URL = "https://api.github.com/repos/AppleDash/SaneEconomy/releases"; private static final String RELEASES_URL = "https://api.github.com/repos/AppleDash/SaneEconomy/releases";
private static String newestFound;
private boolean updateChecked; private boolean updateChecked;
private boolean updateAvailable; private boolean updateAvailable;
private static String newestVersion;
private final String pluginName; private final String pluginName;
private final String currentVersion; private final String currentVersion;
@ -29,9 +30,8 @@ public class GithubVersionChecker {
JsonArray array = (JsonArray)new JsonParser().parse(jsonContent); JsonArray array = (JsonArray)new JsonParser().parse(jsonContent);
int currentVersion = releaseToInt(this.currentVersion); String currentVersion = this.currentVersion;
int newestVersion = -1; String newestFound = null;
// JsonObject newestObj = null;
for (JsonElement elem : array) { for (JsonElement elem : array) {
if (elem instanceof JsonObject) { if (elem instanceof JsonObject) {
@ -44,34 +44,28 @@ public class GithubVersionChecker {
String releaseName = releaseObj.get("name").getAsString().split(" ")[0]; String releaseName = releaseObj.get("name").getAsString().split(" ")[0];
if (!releaseName.equalsIgnoreCase(pluginName)) { // Not for this plugin. if (!releaseName.equalsIgnoreCase(this.pluginName)) { // Not for this plugin.
continue; continue;
} }
String versionStr = releaseObj.get("tag_name").getAsString(); String versionStr = releaseObj.get("tag_name").getAsString();
int version = releaseToInt(versionStr);
if (version > newestVersion) { if (VersionComparer.isSemVerGreaterThan(newestFound, versionStr)) {
newestVersion = version; newestFound = versionStr;
GithubVersionChecker.newestVersion = versionStr; GithubVersionChecker.newestFound = versionStr;
// newestObj = releaseObj;
} }
} }
} }
updateChecked = true; this.updateChecked = true;
updateAvailable = newestVersion > currentVersion; this.updateAvailable = VersionComparer.isSemVerGreaterThan(currentVersion, newestFound);
}
private int releaseToInt(String release) {
return Integer.valueOf(release.trim().replace(".", ""));
} }
public boolean isUpdateAvailable() { public boolean isUpdateAvailable() {
return updateChecked && updateAvailable; return this.updateChecked && this.updateAvailable;
} }
public String getNewestVersion() { public String getNewestVersion() {
return newestVersion; return newestFound;
} }
} }

View File

@ -0,0 +1,35 @@
package org.appledash.saneeconomy.updates;
/**
* Created by appledash on 7/15/17.
* Blackjack is best pony.
*/
public final class VersionComparer {
private VersionComparer() {
}
public static boolean isSemVerGreaterThan(String first, String second) {
if (first == null) {
return true;
}
if (second == null) {
return false;
}
int[] firstParts = intifyParts(first);
int[] secondParts = intifyParts(second);
return computeInt(secondParts) > computeInt(firstParts);
}
private static int[] intifyParts(String version) {
String[] firstParts = version.split("\\.");
return new int[] { Integer.parseInt(firstParts[0]), Integer.parseInt(firstParts[1]), Integer.parseInt(firstParts[2]) };
}
private static int computeInt(int[] parts) {
return (parts[0] * 1000000) + (parts[1] * 1000) + parts[2];
}
}

View File

@ -6,11 +6,14 @@ import java.util.*;
* Created by appledash on 7/11/16. * Created by appledash on 7/11/16.
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public class MapUtil { public final class MapUtil {
private MapUtil() {
}
/* Originally found on StackOverflow: http://stackoverflow.com/a/2581754/1849152 */ /* Originally found on StackOverflow: http://stackoverflow.com/a/2581754/1849152 */
public static <K, V extends Comparable<? super V>> LinkedHashMap<K, V> sortByValue(Map<K, V> map) { public static <K, V extends Comparable<? super V>> LinkedHashMap<K, V> sortByValue(Map<K, V> map) {
List<Map.Entry<K, V>> list = List<Map.Entry<K, V>> list =
new LinkedList<>(map.entrySet()); new LinkedList<>(map.entrySet());
list.sort((o1, o2) -> -((o1.getValue()).compareTo(o2.getValue()))); list.sort((o1, o2) -> -((o1.getValue()).compareTo(o2.getValue())));
@ -27,6 +30,7 @@ public class MapUtil {
* @param nSkip Number of items to skip * @param nSkip Number of items to skip
* @return New LinkedHashMap, may be empty. * @return New LinkedHashMap, may be empty.
*/ */
@SuppressWarnings("CollectionDeclaredAsConcreteClass")
public static <K, V> LinkedHashMap<K, V> skip(LinkedHashMap<K, V> map, int nSkip) { public static <K, V> LinkedHashMap<K, V> skip(LinkedHashMap<K, V> map, int nSkip) {
LinkedHashMap<K, V> newMap = new LinkedHashMap<>(); LinkedHashMap<K, V> newMap = new LinkedHashMap<>();

View File

@ -3,6 +3,8 @@ package org.appledash.saneeconomy.utils;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.appledash.saneeconomy.economy.Currency; import org.appledash.saneeconomy.economy.Currency;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
@ -10,10 +12,13 @@ import java.text.ParseException;
* Created by AppleDash on 6/14/2016. * Created by AppleDash on 6/14/2016.
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public class NumberUtils { public final class NumberUtils {
private static final double INVALID_DOUBLE = -1; private static final BigDecimal INVALID_DOUBLE = BigDecimal.ONE.negate();
public static double parsePositiveDouble(String sDouble) { private NumberUtils() {
}
public static BigDecimal parsePositiveDouble(String sDouble) {
if (Strings.isNullOrEmpty(sDouble)) { if (Strings.isNullOrEmpty(sDouble)) {
return INVALID_DOUBLE; return INVALID_DOUBLE;
} }
@ -24,34 +29,54 @@ public class NumberUtils {
return INVALID_DOUBLE; return INVALID_DOUBLE;
} }
double doub; BigDecimal doub;
try { try {
doub = NumberFormat.getInstance().parse(sDouble).doubleValue(); doub = (BigDecimal) constructDecimalFormat().parseObject(sDouble);
} catch (ParseException | NumberFormatException e) { } catch (ParseException | NumberFormatException e) {
return INVALID_DOUBLE; return INVALID_DOUBLE;
} }
if (doub < 0) { if (doub.compareTo(BigDecimal.ZERO) < 0) {
return INVALID_DOUBLE; return INVALID_DOUBLE;
} }
if (Double.isInfinite(doub) || Double.isNaN(doub)) { /*if (Double.isInfinite(doub) || Double.isNaN(doub)) {
return INVALID_DOUBLE; return INVALID_DOUBLE;
} }*/
return doub; return doub;
} }
public static double filterAmount(Currency currency, double amount) { public static BigDecimal filterAmount(Currency currency, BigDecimal amount) {
try { try {
return NumberFormat.getInstance().parse(currency.getFormat().format(amount)).doubleValue(); return (BigDecimal) constructDecimalFormat().parse(currency.getFormat().format(amount.abs()));
} catch (ParseException e) { } catch (ParseException e) {
throw new NumberFormatException(); throw new NumberFormatException();
} }
} }
public static double parseAndFilter(Currency currency, String sDouble) { public static BigDecimal parseAndFilter(Currency currency, String sDouble) {
return filterAmount(currency, parsePositiveDouble(sDouble)); return filterAmount(currency, parsePositiveDouble(sDouble));
} }
public static boolean equals(BigDecimal left, BigDecimal right) {
if (left == null) {
throw new NullPointerException("left == null");
}
if (right == null) {
throw new NullPointerException("right == null");
}
return left.compareTo(right) == 0;
}
private static DecimalFormat constructDecimalFormat() {
DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getInstance();
decimalFormat.setParseBigDecimal(true);
return decimalFormat;
}
} }

View File

@ -9,7 +9,10 @@ import java.util.UUID;
* Created by appledash on 7/19/16. * Created by appledash on 7/19/16.
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public class PlayerUtils { public final class PlayerUtils {
private PlayerUtils() {
}
/** /**
* Get an online or offline player from Bukkit. * Get an online or offline player from Bukkit.
* This is guaranteed to be a player who has played before, but is not guaranteed to be currently online. * This is guaranteed to be a player who has played before, but is not guaranteed to be currently online.
@ -23,6 +26,7 @@ public class PlayerUtils {
return player; return player;
} }
//noinspection ReuseOfLocalVariable
player = Bukkit.getServer().getPlayer(playerNameOrUUID); player = Bukkit.getServer().getPlayer(playerNameOrUUID);
if (player == null) { if (player == null) {

View File

@ -5,7 +5,6 @@ import org.appledash.saneeconomy.SaneEconomy;
import org.appledash.saneeconomy.economy.Currency; import org.appledash.saneeconomy.economy.Currency;
import org.appledash.saneeconomy.economy.EconomyManager; import org.appledash.saneeconomy.economy.EconomyManager;
import org.appledash.saneeconomy.economy.backend.EconomyStorageBackend; import org.appledash.saneeconomy.economy.backend.EconomyStorageBackend;
import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendFlatfile;
import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendJSON; import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendJSON;
import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendMySQL; import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendMySQL;
import org.appledash.saneeconomy.economy.economable.EconomableGeneric; import org.appledash.saneeconomy.economy.economable.EconomableGeneric;
@ -34,38 +33,38 @@ public class SaneEconomyConfiguration {
} }
public EconomyManager loadEconomyBackend() { public EconomyManager loadEconomyBackend() {
logger.info("Initializing currency..."); this.logger.info("Initializing currency...");
Currency currency = Currency.fromConfig(rootConfig.getConfigurationSection("currency")); Currency currency = Currency.fromConfig(this.rootConfig.getConfigurationSection("currency"));
logger.info("Initialized currency: " + currency.getPluralName()); this.logger.info("Initialized currency: " + currency.getPluralName());
logger.info("Initializing economy storage backend..."); this.logger.info("Initializing economy storage backend...");
EconomyStorageBackend backend = loadBackend(rootConfig.getConfigurationSection("backend")); EconomyStorageBackend backend = this.loadBackend(this.rootConfig.getConfigurationSection("backend"));
if (backend == null) { if (backend == null) {
logger.severe("Failed to load backend!"); this.logger.severe("Failed to load backend!");
return null; return null;
} }
logger.info("Performing initial data load..."); this.logger.info("Performing initial data load...");
backend.reloadDatabase(); backend.reloadDatabase();
logger.info("Data loaded!"); this.logger.info("Data loaded!");
if (!Strings.isNullOrEmpty(rootConfig.getString("old-backend.type", null))) { if (!Strings.isNullOrEmpty(this.rootConfig.getString("old-backend.type", null))) {
logger.info("Old backend detected, converting... (This may take a minute or two.)"); this.logger.info("Old backend detected, converting... (This may take a minute or two.)");
EconomyStorageBackend oldBackend = loadBackend(rootConfig.getConfigurationSection("old-backend")); EconomyStorageBackend oldBackend = this.loadBackend(this.rootConfig.getConfigurationSection("old-backend"));
if (oldBackend == null) { if (oldBackend == null) {
logger.severe("Failed to load old backend!"); this.logger.severe("Failed to load old backend!");
return null; return null;
} }
oldBackend.reloadDatabase(); oldBackend.reloadDatabase();
convertBackends(oldBackend, backend); this.convertBackends(oldBackend, backend);
logger.info("Data converted, removing old config section."); this.logger.info("Data converted, removing old config section.");
rootConfig.set("old-backend", null); this.rootConfig.set("old-backend", null);
} }
return new EconomyManager(saneEconomy, currency, backend, rootConfig.getString("economy.server-account", null)); return new EconomyManager(this.saneEconomy, currency, backend, this.rootConfig.getString("economy.server-account", null));
} }
/** /**
@ -77,31 +76,26 @@ public class SaneEconomyConfiguration {
EconomyStorageBackend backend; EconomyStorageBackend backend;
String backendType = config.getString("type"); String backendType = config.getString("type");
if (backendType.equalsIgnoreCase("flatfile")) { if (backendType.equalsIgnoreCase("json")) {
String backendFileName = config.getString("file", "economy.db");
File backendFile = new File(saneEconomy.getDataFolder(), backendFileName);
backend = new EconomyStorageBackendFlatfile(backendFile);
logger.info("Initialized flatfile backend with file " + backendFile.getAbsolutePath());
} else if (backendType.equalsIgnoreCase("json")) {
String backendFileName = config.getString("file", "economy.json"); String backendFileName = config.getString("file", "economy.json");
File backendFile = new File(saneEconomy.getDataFolder(), backendFileName); File backendFile = new File(this.saneEconomy.getDataFolder(), backendFileName);
backend = new EconomyStorageBackendJSON(backendFile); backend = new EconomyStorageBackendJSON(backendFile);
logger.info("Initialized JSON backend with file " + backendFile.getAbsolutePath()); this.logger.info("Initialized JSON backend with file " + backendFile.getAbsolutePath());
} else if (backendType.equalsIgnoreCase("mysql")) { } else if (backendType.equalsIgnoreCase("mysql")) {
EconomyStorageBackendMySQL mySQLBackend = new EconomyStorageBackendMySQL(loadCredentials(config)); EconomyStorageBackendMySQL mySQLBackend = new EconomyStorageBackendMySQL(this.loadCredentials(config));
backend = mySQLBackend; backend = mySQLBackend;
logger.info("Initialized MySQL backend."); this.logger.info("Initialized MySQL backend.");
logger.info("Testing connection..."); this.logger.info("Testing connection...");
if (!mySQLBackend.getConnection().testConnection()) { if (!mySQLBackend.getConnection().testConnection()) {
logger.severe("MySQL connection failed - cannot continue!"); this.logger.severe("MySQL connection failed - cannot continue!");
return null; return null;
} }
logger.info("Connection successful!"); this.logger.info("Connection successful!");
} else { } else {
logger.severe("Unknown storage backend " + backendType + "!"); this.logger.severe("Unknown storage backend " + backendType + "!");
return null; return null;
} }
@ -111,38 +105,40 @@ public class SaneEconomyConfiguration {
/** /**
* Convert one EconomyStorageBackend to another. * Convert one EconomyStorageBackend to another.
* Right now, this just consists of converting all player balances. Data in the old backend is kept. * Right now, this just consists of converting all player balances. Data in the old backend is kept.
* Why is this in here?
* @param oldBackend Old backend * @param oldBackend Old backend
* @param newBackend New backend * @param newBackend New backend
*/ */
private void convertBackends(EconomyStorageBackend oldBackend, EconomyStorageBackend newBackend) { private void convertBackends(EconomyStorageBackend oldBackend, EconomyStorageBackend newBackend) {
oldBackend.getAllBalances().forEach((uniqueId, balance) -> { oldBackend.getAllBalances().forEach((uniqueId, balance) ->
newBackend.setBalance(new EconomableGeneric(uniqueId), balance); newBackend.setBalance(new EconomableGeneric(uniqueId), balance)
}); );
newBackend.waitUntilFlushed(); newBackend.waitUntilFlushed();
} }
public TransactionLogger loadLogger() { public TransactionLogger loadLogger() {
if (!rootConfig.getBoolean("log-transactions", false)) { if (!this.rootConfig.getBoolean("log-transactions", false)) {
return null; return null;
} }
logger.info("Attempting to load transaction logger..."); this.logger.info("Attempting to load transaction logger...");
if (rootConfig.getConfigurationSection("logger-database") == null) { if (this.rootConfig.getConfigurationSection("logger-database") == null) {
logger.severe("No transaction logger database defined, cannot possibly connect!"); this.logger.severe("No transaction logger database defined, cannot possibly connect!");
return null; return null;
} }
DatabaseCredentials credentials = loadCredentials(rootConfig.getConfigurationSection("logger-database")); DatabaseCredentials credentials = this.loadCredentials(this.rootConfig.getConfigurationSection("logger-database"));
TransactionLoggerMySQL transactionLogger = new TransactionLoggerMySQL(credentials); TransactionLoggerMySQL transactionLogger = new TransactionLoggerMySQL(credentials);
if (transactionLogger.testConnection()) { if (transactionLogger.testConnection()) {
logger.info("Initialized MySQL transaction logger."); this.logger.info("Initialized MySQL transaction logger.");
return transactionLogger; return transactionLogger;
} }
logger.severe("Failed to connect to MySQL database for transaction logger!"); this.logger.severe("Failed to connect to MySQL database for transaction logger!");
return null; return null;
} }
@ -162,7 +158,7 @@ public class SaneEconomyConfiguration {
boolean useSsl = config.getBoolean("use_ssl", false); boolean useSsl = config.getBoolean("use_ssl", false);
return new DatabaseCredentials( return new DatabaseCredentials(
databaseType, backendHost, backendPort, backendUser, backendPass, backendDb, tablePrefix, useSsl databaseType, backendHost, backendPort, backendUser, backendPass, backendDb, tablePrefix, useSsl
); );
} }
} }

View File

@ -11,21 +11,26 @@ import java.net.URL;
* Created by appledash on 7/11/16. * Created by appledash on 7/11/16.
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public class WebUtils { public final class WebUtils {
private WebUtils() {
}
public static String getContents(String url) { public static String getContents(String url) {
try { try {
String out = ""; StringBuilder out = new StringBuilder();
URL uri = new URL(url); URL uri = new URL(url);
BufferedReader br = new BufferedReader(new InputStreamReader(uri.openConnection().getInputStream())); BufferedReader br = new BufferedReader(new InputStreamReader(uri.openConnection().getInputStream()));
String line; String line;
//noinspection NestedAssignment
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
out += line + "\n"; out.append(line).append("\n");
} }
return out; br.close();
return out.toString();
} catch (IOException e) { } catch (IOException e) {
SaneEconomy.logger().warning("Failed to get contents of URL " + url);
throw new RuntimeException("Failed to get URL contents!", e); throw new RuntimeException("Failed to get URL contents!", e);
} }
} }

View File

@ -15,8 +15,10 @@ import java.util.logging.Logger;
*/ */
public class MySQLConnection { public class MySQLConnection {
private static final Logger LOGGER = Logger.getLogger("MySQLConnection"); private static final Logger LOGGER = Logger.getLogger("MySQLConnection");
public static final int FIVE_SECONDS = 5000;
private final DatabaseCredentials dbCredentials; private final DatabaseCredentials dbCredentials;
private final SaneDatabase saneDatabase; private final SaneDatabase saneDatabase;
private boolean canLockTables = true;
public MySQLConnection(DatabaseCredentials dbCredentials) { public MySQLConnection(DatabaseCredentials dbCredentials) {
this.dbCredentials = dbCredentials; this.dbCredentials = dbCredentials;
@ -34,13 +36,13 @@ public class MySQLConnection {
public PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException { public PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException {
PreparedStatement preparedStatement = conn.prepareStatement(sql); PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setQueryTimeout(dbCredentials.getQueryTimeout()); // 5 second timeout preparedStatement.setQueryTimeout(this.dbCredentials.getQueryTimeout()); // 5 second timeout
return preparedStatement; return preparedStatement;
} }
public boolean testConnection() { public boolean testConnection() {
try (Connection ignored = openConnection()) { try (Connection ignored = this.openConnection()) {
return true; return true;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@ -48,38 +50,63 @@ public class MySQLConnection {
} }
} }
public void lockTable(Connection conn, String tableName) throws SQLException {
if (!this.canLockTables) {
return;
}
try {
conn.prepareStatement("LOCK TABLE " + this.getTable(tableName) + " WRITE").execute();
this.canLockTables = true;
} catch (SQLException e) {
if (this.canLockTables) {
LOGGER.warning("Your MySQL user does not have privileges to LOCK TABLES - this may cause issues if you are running this plugin with the same database on multiple servers.");
}
this.canLockTables = false;
}
}
public void unlockTables(Connection conn) throws SQLException {
if (!this.canLockTables) {
return;
}
conn.prepareStatement("UNLOCK TABLES").execute();
}
public void executeAsyncOperation(String tag, Consumer<Connection> callback) { public void executeAsyncOperation(String tag, Consumer<Connection> callback) {
this.saneDatabase.runDatabaseOperationAsync(tag, () -> doExecuteAsyncOperation(1, callback)); this.saneDatabase.runDatabaseOperationAsync(tag, () -> this.doExecuteAsyncOperation(1, callback));
} }
// This is a bit weird because it has to account for recursion... // This is a bit weird because it has to account for recursion...
private void doExecuteAsyncOperation(int levels, Consumer<Connection> callback) { private void doExecuteAsyncOperation(int levels, Consumer<Connection> callback) {
try (Connection conn = openConnection()) { try (Connection conn = this.openConnection()) {
callback.accept(conn); callback.accept(conn);
} catch (Exception e) { } catch (Exception e) {
if (levels > dbCredentials.getMaxRetries()) { if (levels > this.dbCredentials.getMaxRetries()) {
throw new RuntimeException("This shouldn't happen (database error)", e); throw new RuntimeException("This shouldn't happen (database error)", e);
} }
LOGGER.severe("An internal SQL error has occured, trying up to " + (5 - levels) + " more times..."); LOGGER.severe("An internal SQL error has occured, trying up to " + (5 - levels) + " more times...");
e.printStackTrace(); e.printStackTrace();
levels++; levels++;
doExecuteAsyncOperation(levels, callback); this.doExecuteAsyncOperation(levels, callback);
} }
} }
public DatabaseCredentials getCredentials() { public DatabaseCredentials getCredentials() {
return dbCredentials; return this.dbCredentials;
} }
public String getTable(String tableName) { public String getTable(String tableName) {
return dbCredentials.getTablePrefix() + tableName; return this.dbCredentials.getTablePrefix() + tableName;
} }
public void waitUntilFlushed() { public void waitUntilFlushed() {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
while (!this.saneDatabase.areAllTransactionsDone()) { while (!this.saneDatabase.areAllTransactionsDone()) {
if ((System.currentTimeMillis() - startTime) > 5000) { if ((System.currentTimeMillis() - startTime) > FIVE_SECONDS) {
LOGGER.warning("Took too long to flush all transactions - something has probably hung :("); LOGGER.warning("Took too long to flush all transactions - something has probably hung :(");
break; break;
} }

View File

@ -11,6 +11,7 @@ import org.appledash.saneeconomy.economy.transaction.TransactionResult;
import org.appledash.saneeconomy.utils.PlayerUtils; import org.appledash.saneeconomy.utils.PlayerUtils;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
/** /**
@ -40,7 +41,7 @@ public class EconomySaneEconomy implements Economy {
@Override @Override
public String format(double v) { public String format(double v) {
return SaneEconomy.getInstance().getEconomyManager().getCurrency().formatAmount(v); return SaneEconomy.getInstance().getEconomyManager().getCurrency().formatAmount(new BigDecimal(v));
} }
@Override @Override
@ -55,7 +56,7 @@ public class EconomySaneEconomy implements Economy {
@Override @Override
public boolean hasAccount(String target) { public boolean hasAccount(String target) {
return SaneEconomy.getInstance().getEconomyManager().accountExists(makeEconomable(target)); return SaneEconomy.getInstance().getEconomyManager().accountExists(this.makeEconomable(target));
} }
@Override @Override
@ -65,120 +66,120 @@ public class EconomySaneEconomy implements Economy {
@Override @Override
public boolean hasAccount(String target, String worldName) { public boolean hasAccount(String target, String worldName) {
return hasAccount(target); return this.hasAccount(target);
} }
@Override @Override
public boolean hasAccount(OfflinePlayer offlinePlayer, String worldName) { public boolean hasAccount(OfflinePlayer offlinePlayer, String worldName) {
return hasAccount(offlinePlayer); return this.hasAccount(offlinePlayer);
} }
@Override @Override
public double getBalance(String target) { public double getBalance(String target) {
return SaneEconomy.getInstance().getEconomyManager().getBalance(makeEconomable(target)); return SaneEconomy.getInstance().getEconomyManager().getBalance(this.makeEconomable(target)).doubleValue();
} }
@Override @Override
public double getBalance(OfflinePlayer offlinePlayer) { public double getBalance(OfflinePlayer offlinePlayer) {
return SaneEconomy.getInstance().getEconomyManager().getBalance(Economable.wrap(offlinePlayer)); return SaneEconomy.getInstance().getEconomyManager().getBalance(Economable.wrap(offlinePlayer)).doubleValue();
} }
@Override @Override
public double getBalance(String target, String worldName) { public double getBalance(String target, String worldName) {
return getBalance(target); return this.getBalance(target);
} }
@Override @Override
public double getBalance(OfflinePlayer offlinePlayer, String worldName) { public double getBalance(OfflinePlayer offlinePlayer, String worldName) {
return getBalance(offlinePlayer); return this.getBalance(offlinePlayer);
} }
@Override @Override
public boolean has(String target, double amount) { public boolean has(String target, double amount) {
return SaneEconomy.getInstance().getEconomyManager().hasBalance(makeEconomable(target), amount); return SaneEconomy.getInstance().getEconomyManager().hasBalance(this.makeEconomable(target), new BigDecimal(amount));
} }
@Override @Override
public boolean has(OfflinePlayer offlinePlayer, double amount) { public boolean has(OfflinePlayer offlinePlayer, double amount) {
return SaneEconomy.getInstance().getEconomyManager().hasBalance(Economable.wrap(offlinePlayer), amount); return SaneEconomy.getInstance().getEconomyManager().hasBalance(Economable.wrap(offlinePlayer), new BigDecimal(amount));
} }
@Override @Override
public boolean has(String target, String worldName, double amount) { public boolean has(String target, String worldName, double amount) {
return has(target, amount); return this.has(target, amount);
} }
@Override @Override
public boolean has(OfflinePlayer offlinePlayer, String worldName, double amount) { public boolean has(OfflinePlayer offlinePlayer, String worldName, double amount) {
return has(offlinePlayer, amount); return this.has(offlinePlayer, amount);
} }
@Override @Override
public EconomyResponse withdrawPlayer(String target, double amount) { public EconomyResponse withdrawPlayer(String target, double amount) {
if (amount == 0) { if (amount == 0) {
return new EconomyResponse(amount, getBalance(target), EconomyResponse.ResponseType.SUCCESS, ""); return new EconomyResponse(amount, this.getBalance(target), EconomyResponse.ResponseType.SUCCESS, "");
} }
return transact(new Transaction( return this.transact(new Transaction(
makeEconomable(target), Economable.PLUGIN, amount, TransactionReason.PLUGIN_TAKE SaneEconomy.getInstance().getEconomyManager().getCurrency(), this.makeEconomable(target), Economable.PLUGIN, new BigDecimal(amount), TransactionReason.PLUGIN_TAKE
)); ));
} }
@Override @Override
public EconomyResponse withdrawPlayer(OfflinePlayer offlinePlayer, double amount) { public EconomyResponse withdrawPlayer(OfflinePlayer offlinePlayer, double amount) {
if (amount == 0) { if (amount == 0) {
return new EconomyResponse(amount, getBalance(offlinePlayer), EconomyResponse.ResponseType.SUCCESS, ""); return new EconomyResponse(amount, this.getBalance(offlinePlayer), EconomyResponse.ResponseType.SUCCESS, "");
} }
if (!has(offlinePlayer, amount)) { if (!this.has(offlinePlayer, amount)) {
return new EconomyResponse(amount, getBalance(offlinePlayer), EconomyResponse.ResponseType.FAILURE, "Insufficient funds."); return new EconomyResponse(amount, this.getBalance(offlinePlayer), EconomyResponse.ResponseType.FAILURE, "Insufficient funds.");
} }
return transact(new Transaction( return this.transact(new Transaction(
Economable.wrap(offlinePlayer), Economable.PLUGIN, amount, TransactionReason.PLUGIN_TAKE SaneEconomy.getInstance().getEconomyManager().getCurrency(), Economable.wrap(offlinePlayer), Economable.PLUGIN, new BigDecimal(amount), TransactionReason.PLUGIN_TAKE
)); ));
} }
@Override @Override
public EconomyResponse withdrawPlayer(String playerName, String worldName, double v) { public EconomyResponse withdrawPlayer(String playerName, String worldName, double v) {
return withdrawPlayer(playerName, v); return this.withdrawPlayer(playerName, v);
} }
@Override @Override
public EconomyResponse withdrawPlayer(OfflinePlayer offlinePlayer, String s, double v) { public EconomyResponse withdrawPlayer(OfflinePlayer offlinePlayer, String s, double v) {
return withdrawPlayer(offlinePlayer, v); return this.withdrawPlayer(offlinePlayer, v);
} }
@Override @Override
public EconomyResponse depositPlayer(String target, double amount) { public EconomyResponse depositPlayer(String target, double amount) {
if (amount == 0) { if (amount == 0) {
return new EconomyResponse(amount, getBalance(target), EconomyResponse.ResponseType.SUCCESS, ""); return new EconomyResponse(amount, this.getBalance(target), EconomyResponse.ResponseType.SUCCESS, "");
} }
return transact(new Transaction( return this.transact(new Transaction(
Economable.PLUGIN, makeEconomable(target), amount, TransactionReason.PLUGIN_GIVE SaneEconomy.getInstance().getEconomyManager().getCurrency(), Economable.PLUGIN, this.makeEconomable(target), new BigDecimal(amount), TransactionReason.PLUGIN_GIVE
)); ));
} }
@Override @Override
public EconomyResponse depositPlayer(OfflinePlayer offlinePlayer, double v) { public EconomyResponse depositPlayer(OfflinePlayer offlinePlayer, double v) {
if (v == 0) { if (v == 0) {
return new EconomyResponse(v, getBalance(offlinePlayer), EconomyResponse.ResponseType.SUCCESS, ""); return new EconomyResponse(v, this.getBalance(offlinePlayer), EconomyResponse.ResponseType.SUCCESS, "");
} }
return transact(new Transaction( return this.transact(new Transaction(
Economable.PLUGIN, Economable.wrap(offlinePlayer), v, TransactionReason.PLUGIN_GIVE SaneEconomy.getInstance().getEconomyManager().getCurrency(), Economable.PLUGIN, Economable.wrap(offlinePlayer), new BigDecimal(v), TransactionReason.PLUGIN_GIVE
)); ));
} }
@Override @Override
public EconomyResponse depositPlayer(String playerName, String worldName, double v) { public EconomyResponse depositPlayer(String playerName, String worldName, double v) {
return depositPlayer(playerName, v); return this.depositPlayer(playerName, v);
} }
@Override @Override
public EconomyResponse depositPlayer(OfflinePlayer offlinePlayer, String worldName, double v) { public EconomyResponse depositPlayer(OfflinePlayer offlinePlayer, String worldName, double v) {
return depositPlayer(offlinePlayer, v); return this.depositPlayer(offlinePlayer, v);
} }
@Override @Override
@ -270,7 +271,7 @@ public class EconomySaneEconomy implements Economy {
return Economable.CONSOLE; return Economable.CONSOLE;
} }
if (validatePlayer(input)) { if (this.validatePlayer(input)) {
return Economable.wrap(PlayerUtils.getOfflinePlayer(input)); return Economable.wrap(PlayerUtils.getOfflinePlayer(input));
} }
@ -281,7 +282,7 @@ public class EconomySaneEconomy implements Economy {
TransactionResult result = SaneEconomy.getInstance().getEconomyManager().transact(transaction); TransactionResult result = SaneEconomy.getInstance().getEconomyManager().transact(transaction);
if (result.getStatus() == TransactionResult.Status.SUCCESS) { if (result.getStatus() == TransactionResult.Status.SUCCESS) {
return new EconomyResponse(transaction.getAmount(), result.getToBalance(), EconomyResponse.ResponseType.SUCCESS, null); return new EconomyResponse(transaction.getAmount().doubleValue(), result.getToBalance().doubleValue(), EconomyResponse.ResponseType.SUCCESS, null);
} }
return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, result.getStatus().toString()); return new EconomyResponse(0, 0, EconomyResponse.ResponseType.FAILURE, result.getStatus().toString());

View File

@ -1,8 +1,12 @@
package org.appledash.saneeconomy.vault; package org.appledash.saneeconomy.vault;
import com.google.common.base.Strings;
import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission;
import org.appledash.saneeconomy.SaneEconomy; import org.appledash.saneeconomy.SaneEconomy;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.ServicePriority;
/** /**
@ -18,10 +22,20 @@ public class VaultHook {
} }
public void hook() { public void hook() {
Bukkit.getServicesManager().register(Economy.class, provider, plugin, ServicePriority.Normal); Bukkit.getServicesManager().register(Economy.class, this.provider, this.plugin, ServicePriority.Normal);
} }
public void unhook() { public void unhook() {
Bukkit.getServicesManager().unregister(Economy.class, provider); Bukkit.getServicesManager().unregister(Economy.class, this.provider);
}
public boolean hasPermission(OfflinePlayer offlinePlayer, String permNode) {
RegisteredServiceProvider<Permission> rsp = this.plugin.getServer().getServicesManager().getRegistration(Permission.class);
if ((offlinePlayer == null) || (offlinePlayer.getUniqueId() == null) || Strings.isNullOrEmpty(offlinePlayer.getName())) {
return false;
}
return (rsp != null) && rsp.getProvider().playerHas(null, offlinePlayer, permNode);
} }
} }

View File

@ -1,21 +1,34 @@
# Settings pertaining to the backend. Check the wiki for valid values here.
backend: backend:
type: json type: json
# Settings pertaining to the currency.
currency: currency:
name: name: # Currency name as displayed to players.
singular: dollar singular: dollar
plural: dollars plural: dollars
balance-format: '{1} {2}' balance-format: '{1} {2}' # The format for currency amounts when displayed to players. {1} = amount, {2} = currency name, properly pluralized
# I have no idea what any of these do but try tweaking them if weird things happen regarding currency amounts and you're not in Canada or the USA.
format: '0.00' format: '0.00'
grouping: 3 grouping: 3
grouping-separator: ',' grouping-separator: ','
decimal-separator: '.'
chat: chat:
prefix: '&b[&9Economy&b]&r ' prefix: '&b[&9Economy&b]&r ' # Prefix for chat messages sent by the plugin. Include a space at the end.
economy: economy:
start-balance: 0.0 start-balance: 0.0 # If this is greater than zero, players will be given this amount on their first join.
notify-start-balance: true notify-start-balance: true # Whether to send players a message when they receive their starting balance.
server-account: '$SERVER$' server-account: '$SERVER$' # Economy operations on an account with this name will appear to have an infinite balance.
baltop-update-interval: 300 # How often, in seconds, to reload the content of /baltop from the database.
notify-admin-give: false # Whether to notify players when /ecoadmin give is used on them.
notify-admin-take: false # Whether to notify players when /ecoadmin take is used on them.
notify-admin-set: false # Whether to notify players when /ecoadmin set is used on them.
pay-offline-players: true # Whether to allow paying offline players or not.
debug: false multi-server-sync: false # Experimental balance syncing without player rejoins, across BungeeCord networks.
update-check: true # Whether to check for updates to the plugin and notify admins about them.
locale-override: false # Whether to force the server's locale to be en_US.
debug: false # Debug mode, leave this set to false.
metrics: true # Whether to send anonymous metrics about the plugin's usage back to the developers, useful for fixing crashes.

View File

@ -5,14 +5,16 @@
# To be clear: DO NOT change 'message'. Simply add a new, indented 'translation' line below it, with the changed message. # To be clear: DO NOT change 'message'. Simply add a new, indented 'translation' line below it, with the changed message.
# Colors can also be used, by means of prefixing color codes with an '&' symbol. # Colors can also be used, by means of prefixing color codes with an '&' symbol.
# The order of placeholders can be changed. Anything after the :, like the '02d' in {1:02d} is a Java String.format specifier. If you don't know what that is, I recommend leaving it as-is. # The order of placeholders can be changed. Anything after the :, like the '02d' in {1:02d} is a Java String.format specifier. If you don't know what that is, I recommend leaving it as-is.
# IMPORTANT: If your translation has a colon ( : ) character inside of it, you must enclose the entire part after "message: " in single quotes ( ' ). # IMPORTANT: If your translation has a colon ( : ) character inside of it, you must enclose the entire part after "translation: " in single quotes ( ' ).
# If this file doesn't work for some reason, check your console for errors with "SnakeYAML" included in them. # If this file doesn't work for some reason, check your console for errors with "SnakeYAML" included in them.
####################################################################################################
########## READ ABOVE IF YOU INTEND TO EDIT THIS FILE, BEFORE ASKING FOR HELP! #####################
####################################################################################################
messages: messages:
- message: "You don't have permission to check the balance of {1}." - message: "You don't have permission to check the balance of {1}."
- message: "That player is not online." - message: "That player is not online."
- message: "You cannot pay yourself." - message: "You cannot pay yourself."
- message: "Usage: {1}" - message: "Usage: {1}"
- message: "That player does not exist."
- message: "Your balance is {1}." - message: "Your balance is {1}."
- message: "Balance for {1} is {2}." - message: "Balance for {1} is {2}."
- message: "Top {1} players on page {2}:" - message: "Top {1} players on page {2}:"
@ -21,6 +23,9 @@ messages:
- message: "Added {1} to {2}. Their balance is now {3}." - message: "Added {1} to {2}. Their balance is now {3}."
- message: "Took {1} from {2}. Their balance is now {3}." - message: "Took {1} from {2}. Their balance is now {3}."
- message: "Balance for {1} set to {2}." - message: "Balance for {1} set to {2}."
- message: "{1} has given you {2}. Your balance is now {3}."
- message: "{1} has taken {2} from you. Your balance is now {3}."
- message: "{1} has set your balance to {2}."
- message: "You do not have enough money to transfer {1} to {2}." - message: "You do not have enough money to transfer {1} to {2}."
- message: "You have transferred {1} to {2}." - message: "You have transferred {1} to {2}."
- message: "You have received {1} from {2}." - message: "You have received {1} from {2}."
@ -38,3 +43,4 @@ messages:
- message: "/<command> <page>" - message: "/<command> <page>"
- message: "/<command> <give/take/set> [player] <amount>" - message: "/<command> <give/take/set> [player] <amount>"
- message: "/<command> reload-database" - message: "/<command> reload-database"

View File

@ -1,7 +1,8 @@
name: SaneEconomy name: SaneEconomy
author: AppleDash author: AppleDash
main: org.appledash.saneeconomy.SaneEconomy main: org.appledash.saneeconomy.SaneEconomy
version: 0.12.3 version: ${project.version}
# api-version: '1.14'
load: STARTUP load: STARTUP
softdepend: [Vault] softdepend: [Vault]
commands: commands:
@ -50,6 +51,9 @@ permissions:
saneeconomy.balancetop: saneeconomy.balancetop:
description: Allows you to view the players on the server who have the most money. description: Allows you to view the players on the server who have the most money.
default: true default: true
saneeconomy.balancetop.hide:
description: Players with this node are hidden from /baltop
default: false
saneeconomy.update-notify: saneeconomy.update-notify:
description: Allows you to be notified of updates to the plugin on join. description: Allows you to be notified of updates to the plugin on join.
default: op default: op
@ -61,5 +65,6 @@ permissions:
saneeconomy.pay: true saneeconomy.pay: true
saneeconomy.admin: true saneeconomy.admin: true
saneeconomy.balancetop: true saneeconomy.balancetop: true
saneeconomy.balancetop.hide: false
saneeconomy.update-notify: true saneeconomy.update-notify: true
default: op default: op

View File

@ -4,6 +4,7 @@ import org.appledash.saneeconomy.economy.Currency;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
/** /**
@ -14,7 +15,7 @@ public class CurrencyTest {
@Test @Test
public void testCurrencyFormat() { public void testCurrencyFormat() {
Currency currency = new Currency("test dollar", "test dollars", new DecimalFormat("0.00")); Currency currency = new Currency("test dollar", "test dollars", new DecimalFormat("0.00"));
Assert.assertEquals(currency.formatAmount(1.0D), "1.00 test dollar"); Assert.assertEquals(currency.formatAmount(new BigDecimal("1.0")), "1.00 test dollar");
Assert.assertEquals(currency.formatAmount(1337.0D), "1337.00 test dollars"); Assert.assertEquals(currency.formatAmount(new BigDecimal("1337.0")), "1337.00 test dollars");
} }
} }

View File

@ -19,9 +19,9 @@ public class EconomableTest {
@Test @Test
public void testWrapFaction() { public void testWrapFaction() {
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
Economable economable = Economable.wrap(String.format("faction-%s", uuid.toString())); Economable economable = Economable.wrap(String.format("faction-%s", uuid));
Assert.assertEquals(economable.getClass(), EconomableFaction.class); Assert.assertEquals(economable.getClass(), EconomableFaction.class);
Assert.assertEquals(economable.getUniqueIdentifier(), String.format("faction:%s", uuid.toString())); Assert.assertEquals(economable.getUniqueIdentifier(), String.format("faction:%s", uuid));
} }
@Test @Test

View File

@ -1,5 +1,6 @@
package org.appledash.saneeconomy.test; package org.appledash.saneeconomy.test;
import com.google.common.collect.ImmutableList;
import org.appledash.saneeconomy.economy.Currency; import org.appledash.saneeconomy.economy.Currency;
import org.appledash.saneeconomy.economy.EconomyManager; import org.appledash.saneeconomy.economy.EconomyManager;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
@ -9,11 +10,16 @@ import org.appledash.saneeconomy.economy.transaction.TransactionResult;
import org.appledash.saneeconomy.test.mock.MockEconomyStorageBackend; import org.appledash.saneeconomy.test.mock.MockEconomyStorageBackend;
import org.appledash.saneeconomy.test.mock.MockOfflinePlayer; import org.appledash.saneeconomy.test.mock.MockOfflinePlayer;
import org.appledash.saneeconomy.test.mock.MockSaneEconomy; import org.appledash.saneeconomy.test.mock.MockSaneEconomy;
import org.appledash.saneeconomy.test.util.SaneEcoAssert;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
/** /**
* Created by AppleDash on 7/29/2016. * Created by AppleDash on 7/29/2016.
@ -24,9 +30,9 @@ public class EconomyManagerTest {
@Before @Before
public void setupEconomyManager() { public void setupEconomyManager() {
economyManager = new EconomyManager(new MockSaneEconomy(), this.economyManager = new EconomyManager(new MockSaneEconomy(),
new Currency("test dollar", "test dollars", new DecimalFormat("0.00")), new Currency("test dollar", "test dollars", new DecimalFormat("0.00")),
new MockEconomyStorageBackend(), null); new MockEconomyStorageBackend(), null);
} }
@Test @Test
@ -35,51 +41,102 @@ public class EconomyManagerTest {
Economable playerTwo = Economable.wrap(new MockOfflinePlayer("Two")); Economable playerTwo = Economable.wrap(new MockOfflinePlayer("Two"));
// Accounts should not exist // Accounts should not exist
Assert.assertFalse(economyManager.accountExists(playerOne)); Assert.assertFalse(this.economyManager.accountExists(playerOne));
Assert.assertFalse(economyManager.accountExists(playerTwo)); Assert.assertFalse(this.economyManager.accountExists(playerTwo));
Assert.assertEquals(0.0D, economyManager.getBalance(playerOne), 0.0); SaneEcoAssert.assertEquals(BigDecimal.ZERO, this.economyManager.getBalance(playerOne));
Assert.assertEquals(0.0D, economyManager.getBalance(playerTwo), 0.0); SaneEcoAssert.assertEquals(BigDecimal.ZERO, this.economyManager.getBalance(playerTwo));
economyManager.setBalance(playerOne, 100.0D); this.economyManager.setBalance(playerOne, new BigDecimal("100.0"));
// Now one should have an account, but two should not // Now one should have an account, but two should not
Assert.assertTrue(economyManager.accountExists(playerOne)); Assert.assertTrue(this.economyManager.accountExists(playerOne));
Assert.assertFalse(economyManager.accountExists(playerTwo)); Assert.assertFalse(this.economyManager.accountExists(playerTwo));
// One should have balance, two should not // One should have balance, two should not
Assert.assertEquals(100.0, economyManager.getBalance(playerOne), 0.0); SaneEcoAssert.assertEquals(new BigDecimal("100.00"), this.economyManager.getBalance(playerOne));
Assert.assertEquals(0.0, economyManager.getBalance(playerTwo), 0.0); SaneEcoAssert.assertEquals(BigDecimal.ZERO, this.economyManager.getBalance(playerTwo));
// One should be able to transfer to two // One should be able to transfer to two
Assert.assertTrue(economyManager.transact(new Transaction(playerOne, playerTwo, 50.0, TransactionReason.PLAYER_PAY)).getStatus() == TransactionResult.Status.SUCCESS); Assert.assertSame(this.economyManager.transact(new Transaction(this.economyManager.getCurrency(), playerOne, playerTwo, new BigDecimal("50.0"), TransactionReason.PLAYER_PAY)).getStatus(), TransactionResult.Status.SUCCESS);
// One should now have only 50 left, two should have 50 now // One should now have only 50 left, two should have 50 now
Assert.assertEquals(50.0, economyManager.getBalance(playerOne), 0.0); SaneEcoAssert.assertEquals("Player one should have 50 dollars", new BigDecimal("50.00"), this.economyManager.getBalance(playerOne));
Assert.assertEquals(50.0, economyManager.getBalance(playerTwo), 0.0); SaneEcoAssert.assertEquals("Player two should have 50 dollars", new BigDecimal("50.00"), this.economyManager.getBalance(playerTwo));
// Ensure that balance addition and subtraction works... // Ensure that balance addition and subtraction works...
Assert.assertEquals(25.0, economyManager.transact( SaneEcoAssert.assertEquals(new BigDecimal("25.00"), this.economyManager.transact(
new Transaction(playerOne, Economable.CONSOLE, 25.0, TransactionReason.TEST_TAKE) new Transaction(this.economyManager.getCurrency(), playerOne, Economable.CONSOLE, new BigDecimal("25.00"), TransactionReason.TEST_TAKE)
).getFromBalance(), 0.0); ).getFromBalance());
Assert.assertEquals(50.0, economyManager.transact( SaneEcoAssert.assertEquals(new BigDecimal("50.00"), this.economyManager.transact(
new Transaction(Economable.CONSOLE, playerOne, 25.0, TransactionReason.TEST_GIVE) new Transaction(this.economyManager.getCurrency(), Economable.CONSOLE, playerOne, new BigDecimal("25.00"), TransactionReason.TEST_GIVE)
).getToBalance(), 0.0); ).getToBalance());
Assert.assertEquals(TransactionResult.Status.ERR_NOT_ENOUGH_FUNDS, economyManager.transact( Assert.assertEquals(TransactionResult.Status.ERR_NOT_ENOUGH_FUNDS, this.economyManager.transact(
new Transaction(playerTwo, Economable.CONSOLE, Double.MAX_VALUE, TransactionReason.TEST_TAKE) new Transaction(this.economyManager.getCurrency(), playerTwo, Economable.CONSOLE, new BigDecimal(Double.MAX_VALUE), TransactionReason.TEST_TAKE)
).getStatus()); ).getStatus());
// Ensure that hasBalance works // Ensure that hasBalance works
Assert.assertTrue(economyManager.hasBalance(playerOne, 50.0)); Assert.assertTrue(this.economyManager.hasBalance(playerOne, new BigDecimal("50.00")));
Assert.assertFalse(economyManager.hasBalance(playerOne, 51.0)); Assert.assertFalse(this.economyManager.hasBalance(playerOne, new BigDecimal("51.00")));
} }
@Test(expected = IllegalArgumentException.class) @Test
public void testNegativeBalance() { public void testTopBalances() {
Economable economable = Economable.wrap(new MockOfflinePlayer("Bob")); Random random = new Random();
economyManager.setBalance(economable, -1.0); List<Economable> economables = new ArrayList<>(10);
Set<String> names = new HashSet<>();
for (int i = 0; i < 10; i++) {
Economable economable = Economable.wrap(new MockOfflinePlayer("Dude" + i));
names.add("Dude" + i);
economables.add(economable);
this.economyManager.setBalance(economable, new BigDecimal(random.nextInt(1000)));
}
this.economyManager.getBackend().reloadTopPlayerBalances();
List<BigDecimal> javaSortedBalances = economables.stream().map(this.economyManager::getBalance).sorted((left, right) -> -left.compareTo(right)).collect(Collectors.toList());
List<BigDecimal> ecoManTopBalances = ImmutableList.copyOf(this.economyManager.getTopBalances(10, 0).values());
Assert.assertTrue("List is not correctly sorted!", this.areListsEqual(javaSortedBalances, ecoManTopBalances));
Assert.assertEquals("Wrong number of top balances!", 5, this.economyManager.getTopBalances(5, 0).size());
this.economyManager.getTopBalances(10, 0).keySet().forEach(name -> Assert.assertTrue("Returned name in top balances not valid!", names.contains(name)));
} }
private <T> boolean areListsEqual(List<T> first, List<T> second) {
if (first.size() != second.size()) {
throw new IllegalArgumentException("Lists must be same length (first=" + first.size() + ", second=" + second.size() + ")");
}
for (int i = 0; i < first.size(); i++) {
if (!first.get(i).equals(second.get(i))) {
return false;
}
}
return true;
}
@Test
public void testHasRequiredBalance() {
for (int n = 0; n < 20; n++) { // in the absence of Junit 5's @RepeatedTest
BigDecimal bigDecimal = randomBigDecimal();
// We MUST modify the BigDecimal in some way otherwise the test will always succeed
// See https://github.com/AppleDash/SaneEconomy/issues/100
for (int m = 0; m < 20; m++) {
bigDecimal = bigDecimal.add(randomBigDecimal()).subtract(randomBigDecimal());
}
//
Assert.assertTrue("Account must have required balance despite loss of precision (repeat " + n + ")",
economyManager.hasBalance(bigDecimal, new BigDecimal(bigDecimal.doubleValue())));
}
}
private static BigDecimal randomBigDecimal() {
return new BigDecimal(ThreadLocalRandom.current().nextDouble());
}
} }

View File

@ -1,10 +1,12 @@
package org.appledash.saneeconomy.test; package org.appledash.saneeconomy.test;
import org.appledash.saneeconomy.economy.Currency; import org.appledash.saneeconomy.economy.Currency;
import org.appledash.saneeconomy.test.util.SaneEcoAssert;
import org.appledash.saneeconomy.utils.NumberUtils; import org.appledash.saneeconomy.utils.NumberUtils;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Locale; import java.util.Locale;
@ -16,21 +18,21 @@ public class NumberUtilsTest {
@Test @Test
public void testParsePositive() { public void testParsePositive() {
// Valid input // Valid input
Assert.assertEquals(69.0, NumberUtils.parsePositiveDouble("69.0"), 0.0); SaneEcoAssert.assertEquals(new BigDecimal("69.0"), NumberUtils.parsePositiveDouble("69.0"));
// Valid but not positive // Valid but not positive
Assert.assertEquals(-1.0, NumberUtils.parsePositiveDouble("-10.0"), 0.0); SaneEcoAssert.assertEquals(BigDecimal.ONE.negate(), NumberUtils.parsePositiveDouble("-10.0"));
// Invalid // Invalid
Assert.assertEquals(-1.0, NumberUtils.parsePositiveDouble("nan"), 0.0); SaneEcoAssert.assertEquals(BigDecimal.ONE.negate(), NumberUtils.parsePositiveDouble("nan"));
Assert.assertEquals(-1.0, NumberUtils.parsePositiveDouble("ponies"), 0.0); SaneEcoAssert.assertEquals(BigDecimal.ONE.negate(), NumberUtils.parsePositiveDouble("ponies"));
// Infinite // Infinite
Assert.assertEquals(-1.0, NumberUtils.parsePositiveDouble("1E1000000000"), 0.0); // TODO: Not needed with BigDecimal? Assert.assertEquals(BigDecimal.ONE.negate(), NumberUtils.parsePositiveDouble("1E1000000000"));
} }
@Test @Test
public void testFilter() { public void testFilter() {
Currency currency = new Currency(null, null, new DecimalFormat("0.00")); Currency currency = new Currency(null, null, new DecimalFormat("0.00"));
Assert.assertEquals(NumberUtils.filterAmount(currency, 1337.420D), 1337.42, 0.0); SaneEcoAssert.assertEquals(new BigDecimal("1337.42"), NumberUtils.filterAmount(currency, new BigDecimal("1337.420")));
} }
@Test @Test
@ -38,7 +40,7 @@ public class NumberUtilsTest {
Locale old = Locale.getDefault(); Locale old = Locale.getDefault();
Locale.setDefault(Locale.FRANCE); Locale.setDefault(Locale.FRANCE);
try { try {
testFilter(); this.testFilter();
} catch (Throwable e) { } catch (Throwable e) {
Locale.setDefault(old); Locale.setDefault(old);
throw e; throw e;
@ -46,4 +48,14 @@ public class NumberUtilsTest {
Locale.setDefault(old); Locale.setDefault(old);
} }
} }
@Test
public void testBigDecimalEquals() {
BigDecimal one = new BigDecimal("100.0");
BigDecimal two = new BigDecimal("100.00");
BigDecimal three = new BigDecimal("100.1");
Assert.assertTrue("100.0 should equal 100.00", NumberUtils.equals(one, two));
Assert.assertFalse("100.0 should not equal 100.1", NumberUtils.equals(one, three));
}
} }

View File

@ -0,0 +1,20 @@
package org.appledash.saneeconomy.test;
import org.appledash.saneeconomy.updates.VersionComparer;
import org.junit.Assert;
import org.junit.Test;
/**
* Created by appledash on 7/15/17.
* Blackjack is best pony.
*/
public class VersionComparerTest {
@Test
public void testVersionComparer() {
Assert.assertTrue(VersionComparer.isSemVerGreaterThan("0.12.6", "1.0.0"));
Assert.assertFalse(VersionComparer.isSemVerGreaterThan("2.0.0", "1.0.0"));
Assert.assertTrue(VersionComparer.isSemVerGreaterThan("0.1.0", "0.2.0"));
Assert.assertTrue(VersionComparer.isSemVerGreaterThan("1.0.0", "2.0.0"));
Assert.assertFalse(VersionComparer.isSemVerGreaterThan("0.12.6", "0.5.7"));
}
}

View File

@ -3,14 +3,17 @@ package org.appledash.saneeconomy.test.mock;
import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendCaching; import org.appledash.saneeconomy.economy.backend.type.EconomyStorageBackendCaching;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
import java.math.BigDecimal;
/** /**
* Created by AppleDash on 7/29/2016. * Created by AppleDash on 7/29/2016.
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public class MockEconomyStorageBackend extends EconomyStorageBackendCaching { public class MockEconomyStorageBackend extends EconomyStorageBackendCaching {
@Override @Override
public void setBalance(Economable player, double newBalance) { public void setBalance(Economable player, BigDecimal newBalance) {
balances.put(player.getUniqueIdentifier(), newBalance); this.balances.put(player.getUniqueIdentifier(), newBalance);
this.uuidToName.put(player.getUniqueIdentifier(), player.getName());
} }
@Override @Override

View File

@ -20,6 +20,7 @@ public class MockOfflinePlayer implements OfflinePlayer {
private MockOfflinePlayer(UUID uuid, String name) { private MockOfflinePlayer(UUID uuid, String name) {
this.uuid = uuid; this.uuid = uuid;
this.name = name; this.name = name;
MockServer.getInstance().addOfflinePlayer(this);
} }
public MockOfflinePlayer(String name) { public MockOfflinePlayer(String name) {
@ -33,12 +34,12 @@ public class MockOfflinePlayer implements OfflinePlayer {
@Override @Override
public String getName() { public String getName() {
return name; return this.name;
} }
@Override @Override
public UUID getUniqueId() { public UUID getUniqueId() {
return uuid; return this.uuid;
} }
@Override @Override

View File

@ -3,8 +3,10 @@ package org.appledash.saneeconomy.test.mock;
import org.appledash.saneeconomy.ISaneEconomy; import org.appledash.saneeconomy.ISaneEconomy;
import org.appledash.saneeconomy.economy.EconomyManager; import org.appledash.saneeconomy.economy.EconomyManager;
import org.appledash.saneeconomy.economy.logger.TransactionLogger; import org.appledash.saneeconomy.economy.logger.TransactionLogger;
import org.appledash.saneeconomy.vault.VaultHook;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
/** /**
* Created by appledash on 9/18/16. * Created by appledash on 9/18/16.
@ -20,4 +22,14 @@ public class MockSaneEconomy implements ISaneEconomy {
public Optional<TransactionLogger> getTransactionLogger() { public Optional<TransactionLogger> getTransactionLogger() {
return Optional.empty(); return Optional.empty();
} }
@Override
public VaultHook getVaultHook() {
return null;
}
@Override
public String getLastName(UUID uuid) {
return uuid.toString();
}
} }

View File

@ -0,0 +1,628 @@
package org.appledash.saneeconomy.test.mock;
import org.bukkit.*;
import org.bukkit.advancement.Advancement;
import org.bukkit.block.data.BlockData;
import org.bukkit.boss.*;
import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.help.HelpMap;
import org.bukkit.inventory.*;
import org.bukkit.loot.LootTable;
import org.bukkit.map.MapView;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicesManager;
import org.bukkit.plugin.messaging.Messenger;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scoreboard.ScoreboardManager;
import org.bukkit.util.CachedServerIcon;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Logger;
/**
* Created by appledash on 7/15/17.
* Blackjack is best pony.
*/
@SuppressWarnings("all")
public class MockServer implements Server {
public static MockServer instance;
public static MockServer getInstance() {
if (instance == null) {
instance = new MockServer();
Bukkit.setServer(instance);
}
return instance;
}
private final Logger logger = Logger.getLogger("MockServer");
private final Map<UUID, OfflinePlayer> offlinePlayers = new HashMap<>();
public void addOfflinePlayer(OfflinePlayer offlinePlayer) {
this.offlinePlayers.put(offlinePlayer.getUniqueId(), offlinePlayer);
}
@Override
public String getName() {
return "SaneEconomy Mock Server";
}
@Override
public String getVersion() {
return "0.1.0";
}
@Override
public String getBukkitVersion() {
return "Unknown";
}
@Override
public Collection<? extends Player> getOnlinePlayers() {
return null;
}
@Override
public int getMaxPlayers() {
return 0;
}
@Override
public int getPort() {
return 0;
}
@Override
public int getViewDistance() {
return 0;
}
@Override
public String getIp() {
return null;
}
@Override
public String getWorldType() {
return null;
}
@Override
public boolean getGenerateStructures() {
return false;
}
@Override
public boolean getAllowEnd() {
return false;
}
@Override
public boolean getAllowNether() {
return false;
}
@Override
public boolean hasWhitelist() {
return false;
}
@Override
public void setWhitelist(boolean b) {
}
@Override
public Set<OfflinePlayer> getWhitelistedPlayers() {
return null;
}
@Override
public void reloadWhitelist() {
}
@Override
public int broadcastMessage(String s) {
return 0;
}
@Override
public String getUpdateFolder() {
return null;
}
@Override
public File getUpdateFolderFile() {
return null;
}
@Override
public long getConnectionThrottle() {
return 0;
}
@Override
public int getTicksPerAnimalSpawns() {
return 0;
}
@Override
public int getTicksPerMonsterSpawns() {
return 0;
}
@Override
public Player getPlayer(String s) {
return null;
}
@Override
public Player getPlayerExact(String s) {
return null;
}
@Override
public List<Player> matchPlayer(String s) {
return null;
}
@Override
public Player getPlayer(UUID uuid) {
return null;
}
@Override
public PluginManager getPluginManager() {
return null;
}
@Override
public BukkitScheduler getScheduler() {
return null;
}
@Override
public ServicesManager getServicesManager() {
return null;
}
@Override
public List<World> getWorlds() {
return null;
}
@Override
public World createWorld(WorldCreator worldCreator) {
return null;
}
@Override
public boolean unloadWorld(String s, boolean b) {
return false;
}
@Override
public boolean unloadWorld(World world, boolean b) {
return false;
}
@Override
public World getWorld(String s) {
return null;
}
@Override
public World getWorld(UUID uuid) {
return null;
}
@Override
public MapView getMap(int id) {
return null;
}
@Override
public MapView createMap(World world) {
return null;
}
@Override
public ItemStack createExplorerMap(World world, Location location, StructureType structureType) {
return null;
}
@Override
public ItemStack createExplorerMap(World world, Location location, StructureType structureType, int radius, boolean findUnexplored) {
return null;
}
@Override
public void reload() {
}
@Override
public void reloadData() {
}
@Override
public Logger getLogger() {
return this.logger;
}
@Override
public PluginCommand getPluginCommand(String s) {
return null;
}
@Override
public void savePlayers() {
}
@Override
public boolean dispatchCommand(CommandSender commandSender, String s) throws CommandException {
return false;
}
@Override
public boolean addRecipe(Recipe recipe) {
return false;
}
@Override
public List<Recipe> getRecipesFor(ItemStack itemStack) {
return null;
}
@Override
public Iterator<Recipe> recipeIterator() {
return null;
}
@Override
public void clearRecipes() {
}
@Override
public void resetRecipes() {
}
@Override
public Map<String, String[]> getCommandAliases() {
return null;
}
@Override
public int getSpawnRadius() {
return 0;
}
@Override
public void setSpawnRadius(int i) {
}
@Override
public boolean getOnlineMode() {
return false;
}
@Override
public boolean getAllowFlight() {
return false;
}
@Override
public boolean isHardcore() {
return false;
}
@Override
public void shutdown() {
}
@Override
public int broadcast(String s, String s1) {
return 0;
}
@Override
public OfflinePlayer getOfflinePlayer(String s) {
return null;
}
@Override
public OfflinePlayer getOfflinePlayer(UUID uuid) {
return this.offlinePlayers.get(uuid);
}
@Override
public Set<String> getIPBans() {
return null;
}
@Override
public void banIP(String s) {
}
@Override
public void unbanIP(String s) {
}
@Override
public Set<OfflinePlayer> getBannedPlayers() {
return null;
}
@Override
public BanList getBanList(BanList.Type type) {
return null;
}
@Override
public Set<OfflinePlayer> getOperators() {
return null;
}
@Override
public GameMode getDefaultGameMode() {
return null;
}
@Override
public void setDefaultGameMode(GameMode gameMode) {
}
@Override
public ConsoleCommandSender getConsoleSender() {
return null;
}
@Override
public File getWorldContainer() {
return null;
}
@Override
public OfflinePlayer[] getOfflinePlayers() {
return new OfflinePlayer[0];
}
@Override
public Messenger getMessenger() {
return null;
}
@Override
public HelpMap getHelpMap() {
return null;
}
@Override
public Inventory createInventory(InventoryHolder inventoryHolder, InventoryType inventoryType) {
return null;
}
@Override
public Inventory createInventory(InventoryHolder inventoryHolder, InventoryType inventoryType, String s) {
return null;
}
@Override
public Inventory createInventory(InventoryHolder inventoryHolder, int i) throws IllegalArgumentException {
return null;
}
@Override
public Inventory createInventory(InventoryHolder inventoryHolder, int i, String s) throws IllegalArgumentException {
return null;
}
@Override
public Merchant createMerchant(String s) {
return null;
}
@Override
public int getMonsterSpawnLimit() {
return 0;
}
@Override
public int getAnimalSpawnLimit() {
return 0;
}
@Override
public int getWaterAnimalSpawnLimit() {
return 0;
}
@Override
public int getAmbientSpawnLimit() {
return 0;
}
@Override
public boolean isPrimaryThread() {
return false;
}
@Override
public String getMotd() {
return null;
}
@Override
public String getShutdownMessage() {
return null;
}
@Override
public Warning.WarningState getWarningState() {
return null;
}
@Override
public ItemFactory getItemFactory() {
return null;
}
@Override
public ScoreboardManager getScoreboardManager() {
return null;
}
@Override
public CachedServerIcon getServerIcon() {
return null;
}
@Override
public CachedServerIcon loadServerIcon(File file) {
return null;
}
@Override
public CachedServerIcon loadServerIcon(BufferedImage bufferedImage) {
return null;
}
@Override
public void setIdleTimeout(int i) {
}
@Override
public int getIdleTimeout() {
return 0;
}
@Override
public ChunkGenerator.ChunkData createChunkData(World world) {
return null;
}
@Override
public BossBar createBossBar(String s, BarColor barColor, BarStyle barStyle, BarFlag... barFlags) {
return null;
}
@Override
public KeyedBossBar createBossBar(NamespacedKey key, String title, BarColor color, BarStyle style, BarFlag... flags) {
return null;
}
@Override
public Iterator<KeyedBossBar> getBossBars() {
return null;
}
@Override
public KeyedBossBar getBossBar(NamespacedKey key) {
return null;
}
@Override
public boolean removeBossBar(NamespacedKey key) {
return false;
}
@Override
public Entity getEntity(UUID uuid) {
return null;
}
@Override
public Advancement getAdvancement(NamespacedKey namespacedKey) {
return null;
}
@Override
public Iterator<Advancement> advancementIterator() {
return null;
}
@Override
public BlockData createBlockData(Material material) {
return null;
}
@Override
public BlockData createBlockData(Material material, Consumer<BlockData> consumer) {
return null;
}
@Override
public BlockData createBlockData(String data) throws IllegalArgumentException {
return null;
}
@Override
public BlockData createBlockData(Material material, String data) throws IllegalArgumentException {
return null;
}
@Override
public <T extends Keyed> Tag<T> getTag(String registry, NamespacedKey tag, Class<T> clazz) {
return null;
}
@Override
public <T extends Keyed> Iterable<Tag<T>> getTags(String registry, Class<T> clazz) {
return null;
}
@Override
public LootTable getLootTable(NamespacedKey key) {
return null;
}
@Override
public List<Entity> selectEntities(CommandSender sender, String selector) throws IllegalArgumentException {
return null;
}
@Override
public UnsafeValues getUnsafe() {
return null;
}
@Override
public Spigot spigot() {
return null;
}
@Override
public void sendPluginMessage(Plugin plugin, String s, byte[] bytes) {
}
@Override
public Set<String> getListeningPluginChannels() {
return null;
}
}

View File

@ -0,0 +1,19 @@
package org.appledash.saneeconomy.test.util;
import org.appledash.saneeconomy.utils.NumberUtils;
import org.junit.Assert;
import java.math.BigDecimal;
public final class SaneEcoAssert {
private SaneEcoAssert() {
}
public static void assertEquals(BigDecimal left, BigDecimal right) {
Assert.assertTrue(String.format("%s != %s", left.toPlainString(), right.toPlainString()), NumberUtils.equals(left, right));
}
public static void assertEquals(String message, BigDecimal left, BigDecimal right) {
Assert.assertTrue(message, NumberUtils.equals(left, right));
}
}

View File

@ -5,18 +5,18 @@
<parent> <parent>
<artifactId>SaneEconomy</artifactId> <artifactId>SaneEconomy</artifactId>
<groupId>org.appledash</groupId> <groupId>org.appledash</groupId>
<version>0.12.3-SNAPSHOT</version> <version>0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>SaneEconomyMobKills</artifactId> <artifactId>SaneEconomyMobKills</artifactId>
<version>0.1.2-SNAPSHOT</version> <version>0.1.3-SNAPSHOT</version>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.appledash</groupId> <groupId>org.appledash</groupId>
<artifactId>SaneEconomyCore</artifactId> <artifactId>SaneEconomyCore</artifactId>
<version>0.12.3-SNAPSHOT</version> <version>0.17.2-SNAPSHOT</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -21,35 +21,35 @@ public class SaneEconomyMobKills extends SanePlugin {
@Override @Override
public void onEnable() { public void onEnable() {
saneEconomy = (SaneEconomy)getServer().getPluginManager().getPlugin("SaneEconomy"); this.saneEconomy = (SaneEconomy) this.getServer().getPluginManager().getPlugin("SaneEconomy");
super.onEnable(); super.onEnable();
YamlConfiguration amountsConfig; YamlConfiguration amountsConfig;
if (!(new File(getDataFolder(), "amounts.yml").exists())) { if (!(new File(this.getDataFolder(), "amounts.yml").exists())) {
amountsConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(getClass().getResourceAsStream("/amounts.yml"))); amountsConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getResourceAsStream("/amounts.yml")));
try { try {
amountsConfig.save(new File(getDataFolder(), "amounts.yml")); amountsConfig.save(new File(this.getDataFolder(), "amounts.yml"));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Failed to save amounts.yml to plugin data folder!"); throw new RuntimeException("Failed to save amounts.yml to plugin data folder!");
} }
} else { } else {
amountsConfig = YamlConfiguration.loadConfiguration(new File(getDataFolder(), "amounts.yml")); amountsConfig = YamlConfiguration.loadConfiguration(new File(this.getDataFolder(), "amounts.yml"));
} }
for (String entityTypeName : amountsConfig.getKeys(false)) { for (String entityTypeName : amountsConfig.getKeys(false)) {
double value = amountsConfig.getDouble(entityTypeName); double value = amountsConfig.getDouble(entityTypeName);
killAmounts.put(entityTypeName, value); this.killAmounts.put(entityTypeName, value);
} }
getServer().getPluginManager().registerEvents(new EntityDamageListener(this), this); this.getServer().getPluginManager().registerEvents(new EntityDamageListener(this), this);
} }
public SaneEconomy getSaneEconomy() { public SaneEconomy getSaneEconomy() {
return saneEconomy; return this.saneEconomy;
} }
public Map<String, Double> getKillAmounts() { public Map<String, Double> getKillAmounts() {
return killAmounts; return this.killAmounts;
} }
} }

View File

@ -12,6 +12,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityDeathEvent;
import java.math.BigDecimal;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -21,8 +22,8 @@ import java.util.UUID;
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public class EntityDamageListener implements Listener { public class EntityDamageListener implements Listener {
private SaneEconomyMobKills plugin; private final SaneEconomyMobKills plugin;
private Map<Integer, Map<UUID, Double>> damageDealt = new HashMap<>(); private final Map<Integer, Map<UUID, Double>> damageDealt = new HashMap<>();
public EntityDamageListener(SaneEconomyMobKills plugin) { public EntityDamageListener(SaneEconomyMobKills plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -41,16 +42,16 @@ public class EntityDamageListener implements Listener {
return; return;
} }
if (!plugin.getKillAmounts().containsKey(getEntityType(damagee))) { if (!this.plugin.getKillAmounts().containsKey(this.getEntityType(damagee))) {
return; return;
} }
Map<UUID, Double> damageDoneToThisEntity = new HashMap<>(); Map<UUID, Double> damageDoneToThisEntity = new HashMap<>();
if (damageDealt.containsKey(damagee.getEntityId())) { if (this.damageDealt.containsKey(damagee.getEntityId())) {
damageDoneToThisEntity = damageDealt.get(damagee.getEntityId()); damageDoneToThisEntity = this.damageDealt.get(damagee.getEntityId());
} else { } else {
damageDealt.put(damagee.getEntityId(), damageDoneToThisEntity); this.damageDealt.put(damagee.getEntityId(), damageDoneToThisEntity);
} }
double totalDamageDealt = 0; double totalDamageDealt = 0;
@ -66,32 +67,32 @@ public class EntityDamageListener implements Listener {
@EventHandler @EventHandler
public void onEntityDeath(EntityDeathEvent evt) { public void onEntityDeath(EntityDeathEvent evt) {
Entity entity = evt.getEntity(); LivingEntity entity = evt.getEntity();
if (!damageDealt.containsKey(entity.getEntityId())) { if (!this.damageDealt.containsKey(entity.getEntityId())) {
return; return;
} }
Map<UUID, Double> damageDoneToThisEntity = damageDealt.get(entity.getEntityId()); Map<UUID, Double> damageDoneToThisEntity = this.damageDealt.get(entity.getEntityId());
double totalDmg = ((LivingEntity) entity).getMaxHealth();//sumValues(damageDoneToThisEntity); double totalDmg = entity.getMaxHealth();//sumValues(damageDoneToThisEntity);
for (Map.Entry<UUID, Double> entry : damageDoneToThisEntity.entrySet()) { for (Map.Entry<UUID, Double> entry : damageDoneToThisEntity.entrySet()) {
double thisDmg = entry.getValue(); double thisDmg = entry.getValue();
double thisPercent = (thisDmg / totalDmg) * 100.0D; double thisPercent = (thisDmg / totalDmg) * 100.0D;
double thisAmount = plugin.getKillAmounts().get(getEntityType(entity)) * (thisPercent / 100); double thisAmount = this.plugin.getKillAmounts().get(this.getEntityType(entity)) * (thisPercent / 100);
OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(entry.getKey()); OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(entry.getKey());
if (offlinePlayer.isOnline()) { if (offlinePlayer.isOnline()) {
Player player = Bukkit.getServer().getPlayer(offlinePlayer.getUniqueId()); Player player = Bukkit.getServer().getPlayer(offlinePlayer.getUniqueId());
this.plugin.getMessenger().sendMessage(player, "You have been awarded {1} for doing {2:.2f}% of the damage required to kill that {3}!", plugin.getSaneEconomy().getEconomyManager().getCurrency().formatAmount(thisAmount), thisPercent, entity.getName()); this.plugin.getMessenger().sendMessage(player, "You have been awarded {1} for doing {2:.2f}% of the damage required to kill that {3}!", this.plugin.getSaneEconomy().getEconomyManager().getCurrency().formatAmount(BigDecimal.valueOf(thisAmount)), thisPercent, entity.getName());
} }
plugin.getSaneEconomy().getEconomyManager().transact(new Transaction( this.plugin.getSaneEconomy().getEconomyManager().transact(new Transaction(
Economable.PLUGIN, Economable.wrap(offlinePlayer), thisAmount, TransactionReason.PLUGIN_GIVE this.plugin.getSaneEconomy().getEconomyManager().getCurrency(), Economable.PLUGIN, Economable.wrap(offlinePlayer), BigDecimal.valueOf(thisAmount), TransactionReason.PLUGIN_GIVE
)); ));
} }
damageDealt.remove(evt.getEntity().getEntityId()); this.damageDealt.remove(evt.getEntity().getEntityId());
} }
private String getEntityType(Entity entity) { private String getEntityType(Entity entity) {

View File

@ -1,6 +1,6 @@
name: SaneEconomyMobKills name: SaneEconomyMobKills
description: A plugin to give players experience when they kill mobs. description: A plugin to give players experience when they kill mobs.
version: 0.1.1 version: ${project.version}
author: AppleDash author: AppleDash
main: org.appledash.saneeconomymobkills.SaneEconomyMobKills main: org.appledash.saneeconomymobkills.SaneEconomyMobKills
depend: [SaneEconomy] depend: [SaneEconomy]

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SaneEconomy</artifactId>
<groupId>org.appledash</groupId>
<version>0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>SaneEconomyOnlineTime</artifactId>
<version>0.1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.appledash</groupId>
<artifactId>SaneEconomyCore</artifactId>
<version>0.17.2-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>org.appledash:sanelib</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>org.appledash.sanelib</pattern>
<shadedPattern>org.appledash.saneeconomysignshop.shaded.sanelib</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<outputDirectory>../out/</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,47 @@
package org.appledash.saneeconomy.onlinetime;
import java.math.BigDecimal;
import java.util.Map;
/**
* Created by appledash on 7/13/17.
* Blackjack is best pony.
*/
public class Payout {
private final int secondsInterval;
private final BigDecimal amount;
private final String message;
private String permission;
private final long reportInterval;
public Payout(int secondsInterval, double amount, String message, long reportInterval) {
this.secondsInterval = secondsInterval;
this.amount = BigDecimal.valueOf(amount);
this.message = message;
this.reportInterval = reportInterval;
}
public int getSecondsInterval() {
return this.secondsInterval;
}
public BigDecimal getAmount() {
return this.amount;
}
public String getMessage() {
return this.message;
}
public static Payout fromConfigMap(Map<?, ?> values) {
return new Payout(Integer.parseInt(String.valueOf(values.get("seconds"))), Double.parseDouble(String.valueOf(values.get("amount"))), String.valueOf(values.get("message")), Long.parseLong(String.valueOf(values.get("report_interval"))));
}
public String getPermission() {
return this.permission;
}
public long getReportInterval() {
return this.reportInterval;
}
}

View File

@ -0,0 +1,76 @@
package org.appledash.saneeconomy.onlinetime;
import org.appledash.saneeconomy.SaneEconomy;
import org.appledash.saneeconomy.economy.economable.Economable;
import org.appledash.saneeconomy.economy.transaction.Transaction;
import org.appledash.saneeconomy.economy.transaction.TransactionReason;
import org.appledash.sanelib.SanePlugin;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.math.BigDecimal;
import java.util.*;
/**
* Created by appledash on 7/13/17.
* Blackjack is best pony.
*/
public class SaneEconomyOnlineTime extends SanePlugin implements Listener {
private final Map<UUID, Long> onlineSeconds = new HashMap<>();
private final Map<UUID, BigDecimal> reportingAmounts = new HashMap<>();
private final List<Payout> payouts = new ArrayList<>();
private SaneEconomy saneEconomy;
@Override
public void onEnable() {
super.onEnable();
this.saneEconomy = (SaneEconomy) this.getServer().getPluginManager().getPlugin("SaneEconomy");
this.saveDefaultConfig();
this.getConfig().getMapList("payouts").forEach(map -> {
this.payouts.add(Payout.fromConfigMap(map));
});
this.getServer().getScheduler().scheduleSyncRepeatingTask(this, () -> {
for (Player player : this.getServer().getOnlinePlayers()) {
long onlineSeconds = this.onlineSeconds.getOrDefault(player.getUniqueId(), 0L);
onlineSeconds++;
this.onlineSeconds.put(player.getUniqueId(), onlineSeconds);
for (Payout payout : this.payouts) {
if (payout.getPermission() != null && !player.hasPermission(payout.getPermission())) {
continue;
}
if ((onlineSeconds % payout.getSecondsInterval()) == 0) {
if (this.reportingAmounts.containsKey(player.getUniqueId())) {
this.reportingAmounts.put(player.getUniqueId(), this.reportingAmounts.get(player.getUniqueId()).add(payout.getAmount()));
} else {
this.reportingAmounts.put(player.getUniqueId(), payout.getAmount());
}
this.saneEconomy.getEconomyManager().transact(new Transaction(this.saneEconomy.getEconomyManager().getCurrency(), Economable.PLUGIN, Economable.wrap(player), payout.getAmount(), TransactionReason.PLUGIN_GIVE));
}
if ((onlineSeconds % payout.getReportInterval()) == 0) {
this.getMessenger().sendMessage(player, payout.getMessage(), this.saneEconomy.getEconomyManager().getCurrency().formatAmount(this.reportingAmounts.getOrDefault(player.getUniqueId(), BigDecimal.ZERO)), payout.getReportInterval());
this.reportingAmounts.put(player.getUniqueId(), BigDecimal.ZERO);
}
}
}
}, 0, 20);
this.getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent evt) {
this.onlineSeconds.remove(evt.getPlayer().getUniqueId());
this.reportingAmounts.remove(evt.getPlayer().getUniqueId());
}
}

View File

@ -0,0 +1,9 @@
chat:
prefix: '&b[&OnlineTime&b]&r '
silent: false
payouts:
- seconds: 10
amount: 1000
message: 'You have been paid {1} in the past {2} seconds of being online!'
report_interval: 30

View File

@ -0,0 +1,6 @@
name: SaneEconomyOnlineTime
main: org.appledash.saneeconomy.onlinetime.SaneEconomyOnlineTime
description: Pays players for being online!
author: AppleDash
version: ${project.version}
depend: [SaneEconomy]

View File

@ -5,18 +5,18 @@
<parent> <parent>
<artifactId>SaneEconomy</artifactId> <artifactId>SaneEconomy</artifactId>
<groupId>org.appledash</groupId> <groupId>org.appledash</groupId>
<version>0.12.3-SNAPSHOT</version> <version>0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>SaneEconomySignShop</artifactId> <artifactId>SaneEconomySignShop</artifactId>
<version>0.1.6-SNAPSHOT</version> <version>0.1.8-SNAPSHOT</version>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.appledash</groupId> <groupId>org.appledash</groupId>
<artifactId>SaneEconomyCore</artifactId> <artifactId>SaneEconomyCore</artifactId>
<version>0.12.3-SNAPSHOT</version> <version>0.17.2-SNAPSHOT</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -21,14 +21,14 @@ import java.io.InputStreamReader;
*/ */
public class SaneEconomySignShop extends SanePlugin { public class SaneEconomySignShop extends SanePlugin {
private ISaneEconomy saneEconomy; private ISaneEconomy saneEconomy;
private final SignShopManager signShopManager = new SignShopManager(new SignShopStorageJSON(new File(getDataFolder(), "shops.json"))); private final SignShopManager signShopManager = new SignShopManager(new SignShopStorageJSON(new File(this.getDataFolder(), "shops.json")));
private final LimitManager limitManager = new LimitManager(); private final LimitManager limitManager = new LimitManager();
@Override @Override
public void onEnable() { public void onEnable() {
if (!getServer().getPluginManager().isPluginEnabled("SaneEconomy")) { if (!this.getServer().getPluginManager().isPluginEnabled("SaneEconomy")) {
getLogger().severe("SaneEconomy is not enabled on this server - something is wrong here!"); this.getLogger().severe("SaneEconomy is not enabled on this server - something is wrong here!");
getServer().getPluginManager().disablePlugin(this); this.getServer().getPluginManager().disablePlugin(this);
return; return;
} }
@ -36,32 +36,32 @@ public class SaneEconomySignShop extends SanePlugin {
ItemDatabase.initItemDB(); ItemDatabase.initItemDB();
saneEconomy = (ISaneEconomy)getServer().getPluginManager().getPlugin("SaneEconomy"); this.saneEconomy = (ISaneEconomy) this.getServer().getPluginManager().getPlugin("SaneEconomy");
// If it's stupid but it works... it's probably still stupid. // If it's stupid but it works... it's probably still stupid.
getLogger().info(String.format("Hooked into SaneEconomy version %s.", ((Plugin)saneEconomy).getDescription().getVersion())); this.getLogger().info(String.format("Hooked into SaneEconomy version %s.", ((Plugin) this.saneEconomy).getDescription().getVersion()));
saveDefaultConfig(); this.saveDefaultConfig();
limitManager.loadLimits(YamlConfiguration.loadConfiguration(new InputStreamReader(getClass().getResourceAsStream("/limits.yml")))); // Always load from JAR this.limitManager.loadLimits(YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getResourceAsStream("/limits.yml")))); // Always load from JAR
signShopManager.loadSignShops(); this.signShopManager.loadSignShops();
getServer().getScheduler().scheduleSyncRepeatingTask(this, limitManager::incrementLimitsHourly, 0, 20 * 60 * 60); this.getServer().getScheduler().scheduleSyncRepeatingTask(this, this.limitManager::incrementLimitsHourly, 0, 20 * 60 * 60);
getServer().getPluginManager().registerEvents(new SignChangeListener(this), this); this.getServer().getPluginManager().registerEvents(new SignChangeListener(this), this);
getServer().getPluginManager().registerEvents(new InteractListener(this), this); this.getServer().getPluginManager().registerEvents(new InteractListener(this), this);
getServer().getPluginManager().registerEvents(new BreakListener(this), this); this.getServer().getPluginManager().registerEvents(new BreakListener(this), this);
} }
public SignShopManager getSignShopManager() { public SignShopManager getSignShopManager() {
return signShopManager; return this.signShopManager;
} }
public ISaneEconomy getSaneEconomy() { public ISaneEconomy getSaneEconomy() {
return saneEconomy; return this.saneEconomy;
} }
public LimitManager getLimitManager() { public LimitManager getLimitManager() {
return limitManager; return this.limitManager;
} }
} }

View File

@ -18,14 +18,14 @@ public class BreakListener implements Listener {
@EventHandler @EventHandler
public void onBlockBreak(BlockBreakEvent evt) { public void onBlockBreak(BlockBreakEvent evt) {
plugin.getSignShopManager().getSignShop(evt.getBlock().getLocation()).ifPresent((shop) -> { this.plugin.getSignShopManager().getSignShop(evt.getBlock().getLocation()).ifPresent((shop) -> {
if (!evt.getPlayer().hasPermission("saneeconomy.signshop.destroy.admin")) { if (!evt.getPlayer().hasPermission("saneeconomy.signshop.destroy.admin")) {
this.plugin.getMessenger().sendMessage(evt.getPlayer(), "You may not destroy that!"); this.plugin.getMessenger().sendMessage(evt.getPlayer(), "You may not destroy that!");
evt.setCancelled(true); evt.setCancelled(true);
return; return;
} }
plugin.getSignShopManager().removeSignShop(shop); this.plugin.getSignShopManager().removeSignShop(shop);
this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Sign shop destroyed!"); this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Sign shop destroyed!");
}); });
} }

View File

@ -16,6 +16,7 @@ import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.math.BigDecimal;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -49,7 +50,7 @@ public class InteractListener implements Listener {
return; return;
} }
Optional<SignShop> shopOptional = plugin.getSignShopManager().getSignShop(evt.getClickedBlock().getLocation()); Optional<SignShop> shopOptional = this.plugin.getSignShopManager().getSignShop(evt.getClickedBlock().getLocation());
if (!shopOptional.isPresent()) { if (!shopOptional.isPresent()) {
return; return;
@ -65,7 +66,7 @@ public class InteractListener implements Listener {
return; return;
} }
doBuy(shop, evt.getPlayer()); this.doBuy(shop, evt.getPlayer());
} }
// Sell // Sell
@ -76,15 +77,15 @@ public class InteractListener implements Listener {
return; return;
} }
doSell(shop, evt.getPlayer()); this.doSell(shop, evt.getPlayer());
} }
} }
private void doBuy(SignShop shop, Player player) { private void doBuy(SignShop shop, Player player) {
EconomyManager ecoMan = plugin.getSaneEconomy().getEconomyManager(); EconomyManager ecoMan = this.plugin.getSaneEconomy().getEconomyManager();
int quantity = player.isSneaking() ? 1 : shop.getQuantity(); int quantity = player.isSneaking() ? 1 : shop.getQuantity();
ShopTransaction shopTransaction = shop.makeTransaction(player, TransactionDirection.BUY, quantity); ShopTransaction shopTransaction = shop.makeTransaction(ecoMan.getCurrency(), player, TransactionDirection.BUY, quantity);
/* No buy limits for now! /* No buy limits for now!
if (!plugin.getLimitManager().shouldAllowTransaction(shopTransaction)) { if (!plugin.getLimitManager().shouldAllowTransaction(shopTransaction)) {
@ -103,42 +104,42 @@ public class InteractListener implements Listener {
return; return;
} }
ItemStack stack = shop.getItemStack().clone(); ItemStack stack = new ItemStack(shop.getItemStack()); /* Clone it so we don't modify the stack size in the shop */
stack.setAmount(quantity); stack.setAmount(quantity);
player.getInventory().addItem(stack); player.getInventory().addItem(stack);
this.plugin.getMessenger().sendMessage(player, "You have bought {1} {2} for {3}.", quantity, shop.getItemStack().getType().name(), ecoMan.getCurrency().formatAmount(shopTransaction.getPrice())); this.plugin.getMessenger().sendMessage(player, "You have bought {1} {2} for {3}.", quantity, shop.getItemStack().getType().name(), ecoMan.getCurrency().formatAmount(shopTransaction.getPrice()));
LOGGER.info(String.format("%s just bought %s for %s.", player.getName(), shop.getItemStack(), ecoMan.getCurrency().formatAmount(shopTransaction.getPrice()))); LOGGER.info(String.format("%s just bought %d %s for %s.", player.getName(), quantity, shop.getItemStack().getType().name(), ecoMan.getCurrency().formatAmount(shopTransaction.getPrice())));
} }
private void doSell(SignShop shop, Player player) { // TODO: Selling enchanted items private void doSell(SignShop shop, Player player) { // TODO: Selling enchanted items
EconomyManager ecoMan = plugin.getSaneEconomy().getEconomyManager(); EconomyManager ecoMan = this.plugin.getSaneEconomy().getEconomyManager();
int quantity = player.isSneaking() ? 1 : shop.getQuantity(); int quantity = player.isSneaking() ? 1 : shop.getQuantity();
double price = shop.getSellPrice(quantity); BigDecimal price = shop.getSellPrice(quantity);
if (!player.getInventory().containsAtLeast(new ItemStack(shop.getItemStack()), quantity)) { if (!player.getInventory().containsAtLeast(new ItemStack(shop.getItemStack()), quantity)) {
this.plugin.getMessenger().sendMessage(player, "You do not have {1} {2}!", quantity, shop.getItemStack().getType().name()); this.plugin.getMessenger().sendMessage(player, "You do not have {1} {2}!", quantity, shop.getItemStack().getType().name());
return; return;
} }
ShopTransaction shopTransaction = shop.makeTransaction(player, TransactionDirection.SELL, quantity); ShopTransaction shopTransaction = shop.makeTransaction(ecoMan.getCurrency(), player, TransactionDirection.SELL, quantity);
if (!plugin.getLimitManager().shouldAllowTransaction(shopTransaction)) { if (!this.plugin.getLimitManager().shouldAllowTransaction(shopTransaction)) {
this.plugin.getMessenger().sendMessage(player, "You have reached your selling limit for the time being. Try back in an hour or so."); this.plugin.getMessenger().sendMessage(player, "You have reached your selling limit for the time being. Try back in an hour or so.");
return; return;
} }
plugin.getLimitManager().setRemainingLimit(player, TransactionDirection.SELL, shop.getItem(), plugin.getLimitManager().getRemainingLimit(player, TransactionDirection.SELL, shop.getItem()) - quantity); this.plugin.getLimitManager().setRemainingLimit(player, TransactionDirection.SELL, shop.getItem(), this.plugin.getLimitManager().getRemainingLimit(player, TransactionDirection.SELL, shop.getItem()) - quantity);
ItemStack stack = shop.getItemStack().clone(); ItemStack stack = new ItemStack(shop.getItemStack()); /* Clone it so we don't modify the stack size in the shop */
stack.setAmount(quantity); stack.setAmount(quantity);
player.getInventory().removeItem(stack); // FIXME: This does not remove items with damage values that were detected by contains() player.getInventory().removeItem(stack); // FIXME: This does not remove items with damage values that were detected by contains()
ecoMan.transact(shopTransaction.makeEconomyTransaction()); ecoMan.transact(shopTransaction.makeEconomyTransaction());
this.plugin.getMessenger().sendMessage(player, "You have sold {1} {2} for {3}.", quantity, shop.getItemStack().getType().name(), ecoMan.getCurrency().formatAmount(price)); this.plugin.getMessenger().sendMessage(player, "You have sold {1} {2} for {3}.", quantity, shop.getItemStack().getType().name(), ecoMan.getCurrency().formatAmount(price));
LOGGER.info(String.format("%s just sold %s for %s.", player.getName(), shop.getItemStack(), ecoMan.getCurrency().formatAmount(price))); LOGGER.info(String.format("%s just sold %d %s for %s.", player.getName(), quantity, shop.getItemStack().getType().name(), ecoMan.getCurrency().formatAmount(price)));
} }
} }

View File

@ -1,11 +1,11 @@
package org.appledash.saneeconomysignshop.listeners; package org.appledash.saneeconomysignshop.listeners;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import net.md_5.bungee.api.ChatColor;
import org.appledash.saneeconomysignshop.SaneEconomySignShop; import org.appledash.saneeconomysignshop.SaneEconomySignShop;
import org.appledash.saneeconomysignshop.signshop.SignShop; import org.appledash.saneeconomysignshop.signshop.SignShop;
import org.appledash.saneeconomysignshop.util.ItemDatabase; import org.appledash.saneeconomysignshop.util.ItemDatabase;
import org.appledash.saneeconomysignshop.util.ItemDatabase.InvalidItemException; import org.appledash.saneeconomysignshop.util.ItemDatabase.InvalidItemException;
import org.bukkit.ChatColor;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -33,7 +33,7 @@ public class SignChangeListener implements Listener {
return; return;
} }
ParsedSignShop pss = parseSignShop(evt); ParsedSignShop pss = this.parseSignShop(evt);
if (pss.error != null) { if (pss.error != null) {
this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Cannot create shop: {1}", pss.error); this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Cannot create shop: {1}", pss.error);
@ -45,21 +45,21 @@ public class SignChangeListener implements Listener {
} }
SignShop signShop = pss.shop; SignShop signShop = pss.shop;
plugin.getSignShopManager().addSignShop(signShop); this.plugin.getSignShopManager().addSignShop(signShop);
evt.setLine(0, ChatColor.translateAlternateColorCodes('&', plugin.getConfig().getString("admin-shop-title"))); evt.setLine(0, ChatColor.translateAlternateColorCodes('&', this.plugin.getConfig().getString("admin-shop-title")));
this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Sign shop created!"); this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Sign shop created!");
this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Item: {1} x {2}", signShop.getQuantity(), signShop.getItemStack()); this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Item: {1} x {2}", signShop.getQuantity(), signShop.getItemStack());
if (signShop.canBuy()) { // The player be buying from the shop, not the other way around. if (signShop.canBuy()) { // The player be buying from the shop, not the other way around.
this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Will sell to players for {1}.", this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Will sell to players for {1}.",
plugin.getSaneEconomy().getEconomyManager().getCurrency().formatAmount(signShop.getBuyPrice()) this.plugin.getSaneEconomy().getEconomyManager().getCurrency().formatAmount(signShop.getBuyPrice())
); );
} }
if (signShop.canSell()) { // The player be selling to the shop, not the other way around. if (signShop.canSell()) { // The player be selling to the shop, not the other way around.
this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Will buy from players for {1}.", this.plugin.getMessenger().sendMessage(evt.getPlayer(), "Will buy from players for {1}.",
plugin.getSaneEconomy().getEconomyManager().getCurrency().formatAmount(signShop.getSellPrice()) this.plugin.getSaneEconomy().getEconomyManager().getCurrency().formatAmount(signShop.getSellPrice())
); );
} }
} }
@ -68,7 +68,7 @@ public class SignChangeListener implements Listener {
Player player = evt.getPlayer(); Player player = evt.getPlayer();
Location location = evt.getBlock().getLocation(); Location location = evt.getBlock().getLocation();
if ((lines[0] == null) || !lines[0].equalsIgnoreCase(plugin.getConfig().getString("admin-shop-trigger"))) { // First line must contain the trigger if ((lines[0] == null) || !lines[0].equalsIgnoreCase(this.plugin.getConfig().getString("admin-shop-trigger"))) { // First line must contain the trigger
return new ParsedSignShop(); return new ParsedSignShop();
} }
@ -101,8 +101,8 @@ public class SignChangeListener implements Listener {
return new ParsedSignShop("Invalid buy/sell prices specified."); return new ParsedSignShop("Invalid buy/sell prices specified.");
} }
double buy = Strings.isNullOrEmpty(m.group("buy")) ? -1.0 : Double.valueOf(m.group("buy")); double buy = Strings.isNullOrEmpty(m.group("buy")) ? -1.0 : Double.parseDouble(m.group("buy"));
double sell = Strings.isNullOrEmpty(m.group("sell")) ? -1.0 : Double.valueOf(m.group("sell")); double sell = Strings.isNullOrEmpty(m.group("sell")) ? -1.0 : Double.parseDouble(m.group("sell"));
if ((buy == -1) && (sell == -1)) { if ((buy == -1) && (sell == -1)) {
return new ParsedSignShop("Buy and sell amounts for this shop are both invalid."); return new ParsedSignShop("Buy and sell amounts for this shop are both invalid.");
@ -111,7 +111,7 @@ public class SignChangeListener implements Listener {
int itemAmount; int itemAmount;
try { try {
itemAmount = Integer.valueOf(amountRaw); itemAmount = Integer.parseInt(amountRaw);
if (itemAmount <= 0) { if (itemAmount <= 0) {
throw new NumberFormatException(); throw new NumberFormatException();
@ -123,7 +123,7 @@ public class SignChangeListener implements Listener {
return new ParsedSignShop(new SignShop(player.getUniqueId(), location, itemStack, itemAmount, buy, sell)); return new ParsedSignShop(new SignShop(player.getUniqueId(), location, itemStack, itemAmount, buy, sell));
} }
private class ParsedSignShop { private static final class ParsedSignShop {
private SignShop shop; private SignShop shop;
private String error; private String error;

View File

@ -1,24 +1,29 @@
package org.appledash.saneeconomysignshop.signshop; package org.appledash.saneeconomysignshop.signshop;
import org.appledash.saneeconomy.economy.Currency;
import org.appledash.saneeconomy.economy.economable.Economable; import org.appledash.saneeconomy.economy.economable.Economable;
import org.appledash.saneeconomy.economy.transaction.Transaction; import org.appledash.saneeconomy.economy.transaction.Transaction;
import org.appledash.saneeconomy.economy.transaction.TransactionReason; import org.appledash.saneeconomy.economy.transaction.TransactionReason;
import org.appledash.saneeconomysignshop.util.ItemInfo; import org.appledash.saneeconomysignshop.util.ItemInfo;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.math.BigDecimal;
/** /**
* Created by appledash on 1/1/17. * Created by appledash on 1/1/17.
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public class ShopTransaction { public class ShopTransaction {
private final Currency currency;
// Direction is always what the player is doing. BUY = player is buying from shop. // Direction is always what the player is doing. BUY = player is buying from shop.
private final TransactionDirection direction; private final TransactionDirection direction;
private final Player player; private final Player player;
private final ItemInfo item; private final ItemInfo item;
private final int quantity; private final int quantity;
private final double price; private final BigDecimal price;
public ShopTransaction(TransactionDirection direction, Player player, ItemInfo item, int quantity, double price) { public ShopTransaction(Currency currency, TransactionDirection direction, Player player, ItemInfo item, int quantity, BigDecimal price) {
this.currency = currency;
this.direction = direction; this.direction = direction;
this.player = player; this.player = player;
this.item = item; this.item = item;
@ -27,30 +32,30 @@ public class ShopTransaction {
} }
public TransactionDirection getDirection() { public TransactionDirection getDirection() {
return direction; return this.direction;
} }
public Player getPlayer() { public Player getPlayer() {
return player; return this.player;
} }
public ItemInfo getItem() { public ItemInfo getItem() {
return item; return this.item;
} }
public int getQuantity() { public int getQuantity() {
return quantity; return this.quantity;
} }
public double getPrice() { public BigDecimal getPrice() {
return price; return this.price;
} }
public Transaction makeEconomyTransaction() { public Transaction makeEconomyTransaction() {
if (direction == TransactionDirection.BUY) { if (this.direction == TransactionDirection.BUY) {
return new Transaction(Economable.wrap(player), Economable.PLUGIN, price, TransactionReason.PLUGIN_TAKE); return new Transaction(this.currency, Economable.wrap(this.player), Economable.PLUGIN, this.price, TransactionReason.PLUGIN_TAKE);
} else { } else {
return new Transaction(Economable.PLUGIN, Economable.wrap(player), price, TransactionReason.PLUGIN_GIVE); return new Transaction(this.currency, Economable.PLUGIN, Economable.wrap(this.player), this.price, TransactionReason.PLUGIN_GIVE);
} }
} }

View File

@ -1,5 +1,6 @@
package org.appledash.saneeconomysignshop.signshop; package org.appledash.saneeconomysignshop.signshop;
import org.appledash.saneeconomy.economy.Currency;
import org.appledash.saneeconomysignshop.signshop.ShopTransaction.TransactionDirection; import org.appledash.saneeconomysignshop.signshop.ShopTransaction.TransactionDirection;
import org.appledash.saneeconomysignshop.util.ItemInfo; import org.appledash.saneeconomysignshop.util.ItemInfo;
import org.appledash.saneeconomysignshop.util.SerializableLocation; import org.appledash.saneeconomysignshop.util.SerializableLocation;
@ -8,6 +9,7 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal;
import java.util.UUID; import java.util.UUID;
/** /**
@ -19,8 +21,8 @@ public class SignShop implements Serializable {
private final SerializableLocation location; private final SerializableLocation location;
private final ItemInfo item; private final ItemInfo item;
private final int quantity; private final int quantity;
private final double buyPrice; private final BigDecimal buyPrice;
private final double sellPrice; private final BigDecimal sellPrice;
public SignShop(UUID ownerUuid, Location location, ItemStack item, int quantity, double buyPrice, double sellPrice) { public SignShop(UUID ownerUuid, Location location, ItemStack item, int quantity, double buyPrice, double sellPrice) {
if ((ownerUuid == null) || (location == null) || (item == null)) { if ((ownerUuid == null) || (location == null) || (item == null)) {
@ -35,8 +37,8 @@ public class SignShop implements Serializable {
this.location = new SerializableLocation(location); this.location = new SerializableLocation(location);
this.item = new ItemInfo(item); this.item = new ItemInfo(item);
this.quantity = quantity; this.quantity = quantity;
this.buyPrice = buyPrice; this.buyPrice = BigDecimal.valueOf(buyPrice);
this.sellPrice = sellPrice; this.sellPrice = BigDecimal.valueOf(sellPrice);
} }
/** /**
@ -44,7 +46,7 @@ public class SignShop implements Serializable {
* @return Location * @return Location
*/ */
public Location getLocation() { public Location getLocation() {
return location.getBukkitLocation(); return this.location.getBukkitLocation();
} }
/** /**
@ -52,7 +54,7 @@ public class SignShop implements Serializable {
* @return Material representing item/block type * @return Material representing item/block type
*/ */
public ItemStack getItemStack() { public ItemStack getItemStack() {
return item.toItemStack(); return this.item.toItemStack();
} }
/** /**
@ -60,23 +62,23 @@ public class SignShop implements Serializable {
* @return ItemInfo representing the type and quantity of item * @return ItemInfo representing the type and quantity of item
*/ */
public ItemInfo getItem() { public ItemInfo getItem() {
return item; return this.item;
} }
/** /**
* Get the price that the player can buy this item from the server for * Get the price that the player can buy this item from the server for
* @return Buy price for this.getQuantity() items * @return Buy price for this.getQuantity() items
*/ */
public double getBuyPrice() { public BigDecimal getBuyPrice() {
return buyPrice; return this.buyPrice;
} }
/** /**
* Get the price that the player can sell this item to the server for * Get the price that the player can sell this item to the server for
* @return Buy price for this.getQuantity() items * @return Buy price for this.getQuantity() items
*/ */
public double getSellPrice() { public BigDecimal getSellPrice() {
return sellPrice; return this.sellPrice;
} }
/** /**
@ -85,8 +87,8 @@ public class SignShop implements Serializable {
* @param quantity Quantity of items to price * @param quantity Quantity of items to price
* @return Price to buy that number of items at this shop * @return Price to buy that number of items at this shop
*/ */
public double getBuyPrice(int quantity) { public BigDecimal getBuyPrice(int quantity) {
return this.buyPrice * ((float)quantity / (float)this.quantity); // TODO: Is this okay? return this.buyPrice.multiply(BigDecimal.valueOf((double) quantity / this.quantity)); // TODO: Is this okay?
} }
/** /**
@ -95,8 +97,8 @@ public class SignShop implements Serializable {
* @param quantity Quantity of items to price * @param quantity Quantity of items to price
* @return Price to sell that number of items at this shop * @return Price to sell that number of items at this shop
*/ */
public double getSellPrice(int quantity) { public BigDecimal getSellPrice(int quantity) {
return this.sellPrice * ((float)quantity / (float)this.quantity); // TODO: Is this okay? return this.sellPrice.multiply(BigDecimal.valueOf((double) quantity / this.quantity)); // TODO: Is this okay?
} }
/** /**
@ -104,7 +106,7 @@ public class SignShop implements Serializable {
* @return True if they can, false if they can't * @return True if they can, false if they can't
*/ */
public boolean canBuy() { public boolean canBuy() {
return buyPrice >= 0; return this.buyPrice.compareTo(BigDecimal.ZERO) >= 0;
} }
/** /**
@ -112,7 +114,7 @@ public class SignShop implements Serializable {
* @return True if they can, false if they can't * @return True if they can, false if they can't
*/ */
public boolean canSell() { public boolean canSell() {
return sellPrice >= 0; return this.sellPrice.compareTo(BigDecimal.ZERO) >= 0;
} }
/** /**
@ -120,7 +122,7 @@ public class SignShop implements Serializable {
* @return UUID * @return UUID
*/ */
public UUID getOwnerUuid() { public UUID getOwnerUuid() {
return ownerUuid; return this.ownerUuid;
} }
/** /**
@ -128,10 +130,10 @@ public class SignShop implements Serializable {
* @return Number of items * @return Number of items
*/ */
public int getQuantity() { public int getQuantity() {
return quantity; return this.quantity;
} }
public ShopTransaction makeTransaction(Player player, TransactionDirection direction, int quantity) { public ShopTransaction makeTransaction(Currency currency, Player player, TransactionDirection direction, int quantity) {
return new ShopTransaction(direction, player, item, quantity, (direction == TransactionDirection.BUY) ? getBuyPrice(quantity) : getSellPrice(quantity)); return new ShopTransaction(currency, direction, player, this.item, quantity, (direction == TransactionDirection.BUY) ? this.getBuyPrice(quantity) : this.getSellPrice(quantity));
} }
} }

View File

@ -17,18 +17,18 @@ public class SignShopManager {
} }
public void loadSignShops() { public void loadSignShops() {
storage.loadSignShops(); this.storage.loadSignShops();
} }
public void addSignShop(SignShop signShop) { public void addSignShop(SignShop signShop) {
storage.putSignShop(signShop); this.storage.putSignShop(signShop);
} }
public void removeSignShop(SignShop signShop) { public void removeSignShop(SignShop signShop) {
storage.removeSignShop(signShop); this.storage.removeSignShop(signShop);
} }
public Optional<SignShop> getSignShop(Location location) { public Optional<SignShop> getSignShop(Location location) {
return Optional.ofNullable(storage.getSignShops().get(location)); return Optional.ofNullable(this.storage.getSignShops().get(location));
} }
} }

View File

@ -29,36 +29,35 @@ public class SignShopStorageJSON implements SignShopStorage {
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void loadSignShops() { public void loadSignShops() {
if (!storageFile.exists()) { if (!this.storageFile.exists()) {
return; return;
} }
try { try {
List<SignShop> tempShops = gson.fromJson(new FileReader(storageFile), new TypeToken<List<SignShop>>(){}.getType()); List<SignShop> tempShops = this.gson.fromJson(new FileReader(this.storageFile), new TypeToken<List<SignShop>>() {} .getType());
tempShops.forEach((shop) -> cachedSignShops.put(shop.getLocation(), shop)); tempShops.forEach((shop) -> this.cachedSignShops.put(shop.getLocation(), shop));
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
throw new IllegalStateException("This shouldn't happen - the file " + storageFile.getAbsolutePath() + " disappeared while we were trying to read it!", e); throw new IllegalStateException("This shouldn't happen - the file " + this.storageFile.getAbsolutePath() + " disappeared while we were trying to read it!", e);
} }
saveSignShops(); this.saveSignShops();
} }
@Override @Override
public void putSignShop(SignShop signShop) { public void putSignShop(SignShop signShop) {
cachedSignShops.put(signShop.getLocation(), signShop); this.cachedSignShops.put(signShop.getLocation(), signShop);
saveSignShops(); this.saveSignShops();
} }
@Override @Override
public void removeSignShop(SignShop signShop) { public void removeSignShop(SignShop signShop) {
cachedSignShops.remove(signShop.getLocation()); this.cachedSignShops.remove(signShop.getLocation());
saveSignShops(); this.saveSignShops();
} }
private synchronized void saveSignShops() { private synchronized void saveSignShops() {
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(storageFile, false))) { try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(this.storageFile, false))) {
bufferedWriter.write(gson.toJson(ImmutableList.copyOf(cachedSignShops.values()))); bufferedWriter.write(this.gson.toJson(ImmutableList.copyOf(this.cachedSignShops.values())));
bufferedWriter.close();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Failed to save sign shops!", e); throw new RuntimeException("Failed to save sign shops!", e);
} }
@ -66,6 +65,6 @@ public class SignShopStorageJSON implements SignShopStorage {
@Override @Override
public Map<Location, SignShop> getSignShops() { public Map<Location, SignShop> getSignShops() {
return ImmutableMap.copyOf(cachedSignShops); return ImmutableMap.copyOf(this.cachedSignShops);
} }
} }

View File

@ -24,7 +24,7 @@ public class DefaultHashMap<K, V> extends HashMap<K, V> {
V value = super.get(key); V value = super.get(key);
if (value == null) { if (value == null) {
value = defaultSupplier.get((K)key); value = this.defaultSupplier.get((K)key);
this.put((K) key, value); this.put((K) key, value);
} }

View File

@ -15,13 +15,17 @@ import java.util.Optional;
* Created by appledash on 8/3/16. * Created by appledash on 8/3/16.
* Blackjack is still best pony. * Blackjack is still best pony.
*/ */
public class ItemDatabase { public final class ItemDatabase {
private static Map<String, Pair<Integer, Short>> itemMap = new HashMap<>(); private static Map<String, Pair<Integer, Short>> itemMap = new HashMap<>();
private ItemDatabase() {
}
public static void initItemDB() { public static void initItemDB() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(ItemDatabase.class.getResourceAsStream("/items.csv")))) { try (BufferedReader br = new BufferedReader(new InputStreamReader(ItemDatabase.class.getResourceAsStream("/items.csv")))) {
String line; String line;
//noinspection NestedAssignment
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
if (line.startsWith("#") || !line.contains(",")) { if (line.startsWith("#") || !line.contains(",")) {
continue; continue;
@ -29,23 +33,26 @@ public class ItemDatabase {
String[] split = line.split(","); String[] split = line.split(",");
String name = split[0]; String name = split[0];
int id = Integer.valueOf(split[1]); int id = Integer.parseInt(split[1]);
short damage = Short.valueOf(split[2]); short damage = Short.parseShort(split[2]);
itemMap.put(name.toLowerCase(), Pair.of(id, damage)); itemMap.put(name.toLowerCase(), Pair.of(id, damage));
} }
itemMap = ImmutableMap.copyOf(itemMap); itemMap = ImmutableMap.copyOf(itemMap);
} catch (IOException | NumberFormatException e) { } catch (IOException | NumberFormatException e) {
e.printStackTrace(); throw new RuntimeException("Failed to initialize item database!", e);
} }
} }
public static Optional<Pair<Integer, Short>> getIDAndDamageForName(String name) { public static Optional<Pair<Material, Short>> getIDAndDamageForName(String name) {
if (Material.getMaterial(name) != null) { if (Material.getMaterial(name) != null) {
return Optional.of(Pair.of(Material.getMaterial(name).getId(), (short) 0)); return Optional.of(Pair.of(Material.getMaterial(name), (short) 0));
} }
return Optional.ofNullable(itemMap.get(name.toLowerCase()));
return Optional.empty();
// TODO
//return Optional.ofNullable(itemMap.get(name.toLowerCase()));
} }
public static ItemStack parseGive(String rawItemName) throws InvalidItemException { public static ItemStack parseGive(String rawItemName) throws InvalidItemException {
@ -59,7 +66,7 @@ public class ItemDatabase {
damage = 0; damage = 0;
} else { // They typed 'tnt:something' } else { // They typed 'tnt:something'
try { try {
damage = Short.valueOf(splitItemName[1]); damage = Short.parseShort(splitItemName[1]);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new InvalidItemException("Damage value must be a number."); throw new InvalidItemException("Damage value must be a number.");
} }
@ -72,7 +79,7 @@ public class ItemDatabase {
Optional<Material> materialOptional = parseMaterialFromName(itemName); Optional<Material> materialOptional = parseMaterialFromName(itemName);
if (!materialOptional.isPresent()) { if (!materialOptional.isPresent()) {
Optional<Pair<Integer, Short>> parsedItem = getIDAndDamageForName(normalizeItemName(itemName)); Optional<Pair<Material, Short>> parsedItem = getIDAndDamageForName(normalizeItemName(itemName));
if (!parsedItem.isPresent()) { if (!parsedItem.isPresent()) {
throw new InvalidItemException("Item by that name does not exist."); throw new InvalidItemException("Item by that name does not exist.");
} }
@ -89,11 +96,6 @@ public class ItemDatabase {
} }
private static Optional<Material> parseMaterialFromName(String materialName) { private static Optional<Material> parseMaterialFromName(String materialName) {
// Try to parse an integral item ID first, for legacy reasons.
try {
return Optional.ofNullable(Material.getMaterial(Integer.valueOf(materialName)));
} catch (NumberFormatException ignored) { }
for (Material mat : Material.values()) { for (Material mat : Material.values()) {
if (normalizeItemName(mat.name()).equals(normalizeItemName(materialName))) { if (normalizeItemName(mat.name()).equals(normalizeItemName(materialName))) {
return Optional.of(mat); return Optional.of(mat);

View File

@ -26,7 +26,7 @@ public class ItemInfo implements Serializable {
} }
public ItemStack toItemStack() { public ItemStack toItemStack() {
return new ItemStack(material, amount, damage); return new ItemStack(this.material, this.amount, this.damage);
} }
@Override @Override
@ -42,6 +42,6 @@ public class ItemInfo implements Serializable {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(material, damage); return Objects.hash(this.material, this.damage);
} }
} }

View File

@ -17,10 +17,10 @@ public class ItemLimits {
} }
public int getHourlyGain() { public int getHourlyGain() {
return hourlyGain; return this.hourlyGain;
} }
public int getLimit() { public int getLimit() {
return limit; return this.limit;
} }
} }

View File

@ -21,7 +21,7 @@ public class LimitManager {
// private final Map<ItemInfo, ItemLimits> buyItemLimits = new DefaultHashMap<ItemInfo, ItemLimits>(() -> ItemLimits.DEFAULT); // private final Map<ItemInfo, ItemLimits> buyItemLimits = new DefaultHashMap<ItemInfo, ItemLimits>(() -> ItemLimits.DEFAULT);
private final Map<ItemInfo, ItemLimits> sellItemLimits = new DefaultHashMap<>(() -> ItemLimits.DEFAULT); private final Map<ItemInfo, ItemLimits> sellItemLimits = new DefaultHashMap<>(() -> ItemLimits.DEFAULT);
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final Map<UUID, Map<ItemInfo, Integer>> sellPlayerLimits = new DefaultHashMap<>(() -> new DefaultHashMap<>((info) -> sellItemLimits.get(info).getLimit())); private final Map<UUID, Map<ItemInfo, Integer>> sellPlayerLimits = new DefaultHashMap<>(() -> new DefaultHashMap<>((info) -> this.sellItemLimits.get(info).getLimit()));
// private final Map<TransactionDirection, Map<ItemInfo, ItemLimits>> itemLimits = new DefaultHashMap<>(() -> new DefaultHashMap<>(() -> ItemLimits.DEFAULT)); // private final Map<TransactionDirection, Map<ItemInfo, ItemLimits>> itemLimits = new DefaultHashMap<>(() -> new DefaultHashMap<>(() -> ItemLimits.DEFAULT));
// This is a slightly complex data structure. It works like this: // This is a slightly complex data structure. It works like this:
// It's a map of (limit types to (maps of players to (maps of materials to the remaining limit))). // It's a map of (limit types to (maps of players to (maps of materials to the remaining limit))).
@ -31,7 +31,7 @@ public class LimitManager {
public int getRemainingLimit(Player player, TransactionDirection type, ItemInfo stack) { public int getRemainingLimit(Player player, TransactionDirection type, ItemInfo stack) {
if (type == TransactionDirection.SELL) { if (type == TransactionDirection.SELL) {
return sellPlayerLimits.get(player.getUniqueId()).get(stack); return this.sellPlayerLimits.get(player.getUniqueId()).get(stack);
} }
throw new IllegalArgumentException("Don't know how to get limits for that TransactionDirection!"); throw new IllegalArgumentException("Don't know how to get limits for that TransactionDirection!");
@ -39,14 +39,14 @@ public class LimitManager {
public void setRemainingLimit(Player player, TransactionDirection type, ItemInfo stack, int limit) { public void setRemainingLimit(Player player, TransactionDirection type, ItemInfo stack, int limit) {
if (type == TransactionDirection.SELL) { if (type == TransactionDirection.SELL) {
if (sellPlayerLimits.get(player.getUniqueId()).get(stack) == -1) { if (this.sellPlayerLimits.get(player.getUniqueId()).get(stack) == -1) {
return; return;
} }
limit = Math.min(limit, sellItemLimits.get(stack).getLimit()); limit = Math.min(limit, this.sellItemLimits.get(stack).getLimit());
limit = Math.max(0, limit); limit = Math.max(0, limit);
sellPlayerLimits.get(player.getUniqueId()).put(stack, limit); this.sellPlayerLimits.get(player.getUniqueId()).put(stack, limit);
return; return;
} }
@ -55,20 +55,20 @@ public class LimitManager {
public boolean shouldAllowTransaction(ShopTransaction transaction) { public boolean shouldAllowTransaction(ShopTransaction transaction) {
// System.out.printf("Limit: %d, quantity: %d\n", limit, transaction.getQuantity()); // System.out.printf("Limit: %d, quantity: %d\n", limit, transaction.getQuantity());
return getRemainingLimit(transaction.getPlayer(), transaction.getDirection(), transaction.getItem()) >= transaction.getQuantity(); return this.getRemainingLimit(transaction.getPlayer(), transaction.getDirection(), transaction.getItem()) >= transaction.getQuantity();
} }
public void incrementLimitsHourly() { public void incrementLimitsHourly() {
// For every limit type // For every limit type
// For every player // For every player
// For every limit // For every limit
// Increment limit by the limit for the specific direction and item. // Increment limit by the limit for the specific direction and item.
sellPlayerLimits.forEach((playerUuid, itemToLimit) -> { this.sellPlayerLimits.forEach((playerUuid, itemToLimit) -> {
Map<ItemInfo, Integer> newLimits = new HashMap<>(); Map<ItemInfo, Integer> newLimits = new HashMap<>();
itemToLimit.forEach((itemInfo, currentLimit) -> itemToLimit.forEach((itemInfo, currentLimit) ->
newLimits.put(itemInfo, currentLimit + (sellItemLimits.get(itemInfo).getHourlyGain()))); newLimits.put(itemInfo, currentLimit + (this.sellItemLimits.get(itemInfo).getHourlyGain())));
itemToLimit.putAll(newLimits); itemToLimit.putAll(newLimits);
}); });
@ -77,8 +77,8 @@ public class LimitManager {
public void loadLimits(ConfigurationSection config) { public void loadLimits(ConfigurationSection config) {
for (Map<?, ?> map : config.getMapList("sell")) { for (Map<?, ?> map : config.getMapList("sell")) {
String itemName = String.valueOf(map.get("item")); String itemName = String.valueOf(map.get("item"));
int sellLimit = Integer.valueOf(String.valueOf(map.get("limit"))); int sellLimit = Integer.parseInt(String.valueOf(map.get("limit")));
int hourlyGain = Integer.valueOf(String.valueOf(map.get("gain"))); int hourlyGain = Integer.parseInt(String.valueOf(map.get("gain")));
ItemStack stack; ItemStack stack;
try { try {
@ -91,7 +91,7 @@ public class LimitManager {
ItemInfo itemInfo = new ItemInfo(stack); ItemInfo itemInfo = new ItemInfo(stack);
sellItemLimits.put(itemInfo, new ItemLimits(sellLimit, hourlyGain)); this.sellItemLimits.put(itemInfo, new ItemLimits(sellLimit, hourlyGain));
} }
} }

View File

@ -14,14 +14,14 @@ public class Pair<K, V> {
} }
public K getLeft() { public K getLeft() {
return left; return this.left;
} }
public V getRight() { public V getRight() {
return right; return this.right;
} }
public static <K, V> Pair<K, V> of(K k, V v) { public static <K, V> Pair<K, V> of(K k, V v) {
return new Pair<>(k, v); return new Pair<>(k, v);
} }
} }

View File

@ -28,6 +28,6 @@ public class SerializableLocation implements Serializable {
} }
public Location getBukkitLocation() { public Location getBukkitLocation() {
return new Location(Bukkit.getServer().getWorld(worldUuid), x, y, z, yaw, pitch); return new Location(Bukkit.getServer().getWorld(this.worldUuid), this.x, this.y, this.z, this.yaw, this.pitch);
} }
} }

View File

@ -1,2 +1,4 @@
admin-shop-trigger: '[Shop]' admin-shop-trigger: '[Shop]'
admin-shop-title: '&8[&6Shop&8]&r' admin-shop-title: '&8[&6Shop&8]&r'
chat:
prefix: '&b[&9Shops&b]&r '

View File

@ -44,7 +44,7 @@ sell:
- item: RABBIT - item: RABBIT
limit: 1280 limit: 1280
gain: 128 gain: 128
- item: BEEF - item: RAW_BEEF
limit: 1280 limit: 1280
gain: 128 gain: 128
- item: MUTTON - item: MUTTON

View File

@ -1,5 +1,5 @@
name: SaneEconomySignShop name: SaneEconomySignShop
main: org.appledash.saneeconomysignshop.SaneEconomySignShop main: org.appledash.saneeconomysignshop.SaneEconomySignShop
author: AppleDash author: AppleDash
version: 0.1.5 version: ${project.version}
depend: [SaneEconomy] depend: [SaneEconomy]

9
astyle.conf Normal file
View File

@ -0,0 +1,9 @@
--mode=java
--style=java # Bracket formatting
--indent=spaces=4
--indent-switches
--pad-oper
--pad-header
--add-brackets
--suffix=none # Do not backup original file
--lineend=linux

28
pom.xml
View File

@ -6,21 +6,17 @@
<groupId>org.appledash</groupId> <groupId>org.appledash</groupId>
<artifactId>SaneEconomy</artifactId> <artifactId>SaneEconomy</artifactId>
<version>0.12.3-SNAPSHOT</version> <version>0</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<module>SaneEconomyCore</module> <module>SaneEconomyCore</module>
<module>SaneEconomySignShop</module> <module>SaneEconomySignShop</module>
<module>SaneEconomyMobKills</module> <module>SaneEconomyMobKills</module>
<module>SaneEconomyOnlineTime</module>
</modules> </modules>
<repositories> <repositories>
<repository>
<id>spigot</id>
<name>Spigot</name>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository> <repository>
<id>vault</id> <id>vault</id>
<name>Vault</name> <name>Vault</name>
@ -28,7 +24,7 @@
</repository> </repository>
<repository> <repository>
<id>votuvo</id> <id>votuvo</id>
<url>https://mvn.votuvo.com/</url> <url>https://nexus.sw4t.net/repository/maven-public/</url>
</repository> </repository>
</repositories> </repositories>
@ -36,7 +32,8 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.12-R0.1-SNAPSHOT</version> <version>1.14.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.milkbowl.vault</groupId> <groupId>net.milkbowl.vault</groupId>
@ -46,7 +43,20 @@
<dependency> <dependency>
<groupId>org.appledash</groupId> <groupId>org.appledash</groupId>
<artifactId>sanelib</artifactId> <artifactId>sanelib</artifactId>
<version>0.3.1-SNAPSHOT</version> <version>0.4.1-SNAPSHOT</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>